Commit 3beb0eef authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce

parents e629f659 18de34bd
No related merge requests found
Remove this section and replace it with a description of your MR. Also follow the
checklist below and check off any tasks that are done. If a certain task can not
be done you should explain so in the MR body. You are free to remove any
sections that do not apply to your MR.
When gathering statistics (e.g. the output of `EXPLAIN ANALYZE`) you should make
sure your database has enough data. Having around 10 000 rows in the tables
being queries should provide a reasonable estimate of how a query will behave.
Also make sure that PostgreSQL uses the following settings:
* `random_page_cost`: `1`
* `work_mem`: `16MB`
* `maintenance_work_mem`: at least `64MB`
* `shared_buffers`: at least `256MB`
If you have access to GitLab.com's staging environment you should also run your
measurements there, and include the results in this MR.
## Database Checklist
When adding migrations:
- [ ] Updated `db/schema.rb`
- [ ] Added a `down` method so the migration can be reverted
- [ ] Added the output of the migration(s) to the MR body
- [ ] Added the execution time of the migration(s) to the MR body
- [ ] Added tests for the migration in `spec/migrations` if necessary (e.g. when
migrating data)
- [ ] Made sure the migration won't interfere with a running GitLab cluster,
for example by disabling transactions for long running migrations
When adding or modifying queries to improve performance:
- [ ] Included the raw SQL queries of the relevant queries
- [ ] Included the output of `EXPLAIN ANALYZE` and execution timings of the
relevant queries
- [ ] Added tests for the relevant changes
When adding indexes:
- [ ] Described the need for these indexes in the MR body
- [ ] Made sure existing indexes can not be reused instead
When adding foreign keys to existing tables:
- [ ] Included a migration to remove orphaned rows in the source table
- [ ] Removed any instances of `dependent: ...` that may no longer be necessary
When adding tables:
- [ ] Ordered columns based on their type sizes in descending order
- [ ] Added foreign keys if necessary
- [ ] Added indexes if necessary
When removing columns, tables, indexes or other structures:
- [ ] Removed these in a post-deployment migration
- [ ] Made sure the application no longer uses (or ignores) these structures
## General Checklist
- [ ] [Changelog entry](https://docs.gitlab.com/ce/development/changelog.html) added, if necessary
- [ ] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md)
- [ ] API support added
- [ ] Tests added for this feature/bug
- Review
- [ ] Has been reviewed by UX
- [ ] Has been reviewed by Frontend
- [ ] Has been reviewed by Backend
- [ ] Has been reviewed by Database
- [ ] Conform by the [merge request performance guides](http://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
- [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides)
- [ ] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
......@@ -207,6 +207,13 @@ Layout/SpaceAroundKeyword:
Layout/SpaceAroundOperators:
Enabled: true
# Checks that block braces have or don't have a space before the opening
# brace depending on configuration.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: space, no_space
Layout/SpaceBeforeBlockBraces:
Enabled: true
# No spaces before commas.
Layout/SpaceBeforeComma:
Enabled: true
......
......@@ -26,13 +26,6 @@ Layout/IndentArray:
Layout/IndentHash:
Enabled: false
# Offense count: 174
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle, SupportedStyles.
# SupportedStyles: space, no_space
Layout/SpaceBeforeBlockBraces:
Enabled: false
# Offense count: 8
# Cop supports --auto-correct.
# Configuration parameters: AllowForAlignment.
......
......@@ -744,6 +744,7 @@ GEM
rubocop-gitlab-security (0.0.6)
rubocop (>= 0.47.1)
rubocop-rspec (1.15.1)
rubocop (>= 0.42.0)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
ruby-prof (0.16.2)
......
/* global ListIssue */
/* global bp */
import Vue from 'vue';
import bp from '../../../breakpoints';
const ModalStore = gl.issueBoards.ModalStore;
......
/* eslint-disable func-names, space-before-function-paren, wrap-iife, one-var, no-var, one-var-declaration-per-line, quotes, no-shadow, prefer-arrow-callback, prefer-template, consistent-return, no-return-assign, new-parens, no-param-reassign, max-len */
export const breakpoints = {
lg: 1200,
md: 992,
sm: 768,
xs: 0,
};
var Breakpoints = (function() {
var BreakpointInstance, instance;
const BreakpointInstance = {
windowWidth: () => window.innerWidth,
getBreakpointSize() {
const windowWidth = this.windowWidth();
function Breakpoints() {}
const breakpoint = Object.keys(breakpoints).find(key => windowWidth > breakpoints[key]);
instance = null;
return breakpoint;
},
};
BreakpointInstance = (function() {
var BREAKPOINTS;
BREAKPOINTS = ["xs", "sm", "md", "lg"];
function BreakpointInstance() {
this.setup();
}
BreakpointInstance.prototype.setup = function() {
var allDeviceSelector, els;
allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
return ".device-" + breakpoint;
});
if ($(allDeviceSelector.join(",")).length) {
return;
}
// Create all the elements
els = $.map(BREAKPOINTS, function(breakpoint) {
return "<div class='device-" + breakpoint + " visible-" + breakpoint + "'></div>";
});
return $("body").append(els.join(''));
};
BreakpointInstance.prototype.visibleDevice = function() {
var allDeviceSelector;
allDeviceSelector = BREAKPOINTS.map(function(breakpoint) {
return ".device-" + breakpoint;
});
return $(allDeviceSelector.join(",")).filter(":visible");
};
BreakpointInstance.prototype.getBreakpointSize = function() {
var $visibleDevice;
$visibleDevice = this.visibleDevice;
// TODO: Consider refactoring in light of turbolinks removal.
// the page refreshed via turbolinks
if (!$visibleDevice().length) {
this.setup();
}
$visibleDevice = this.visibleDevice();
return $visibleDevice.attr("class").split("visible-")[1];
};
return BreakpointInstance;
})();
Breakpoints.get = function() {
return instance != null ? instance : instance = new BreakpointInstance;
};
return Breakpoints;
})();
$(() => { window.bp = Breakpoints.get(); });
window.Breakpoints = Breakpoints;
export default BreakpointInstance;
/* eslint-disable func-names, wrap-iife, no-use-before-define,
consistent-return, prefer-rest-params */
/* global Breakpoints */
import _ from 'underscore';
import bp from './breakpoints';
import { bytesToKiB } from './lib/utils/number_utils';
window.Build = (function () {
......@@ -34,8 +33,6 @@ window.Build = (function () {
this.$scrollBottomBtn = $('.js-scroll-down');
clearTimeout(Build.timeout);
// Init breakpoint checker
this.bp = Breakpoints.get();
this.initSidebar();
this.populateJobs(this.buildStage);
......@@ -230,7 +227,7 @@ window.Build = (function () {
};
Build.prototype.shouldHideSidebarForViewport = function () {
const bootstrapBreakpoint = this.bp.getBreakpointSize();
const bootstrapBreakpoint = bp.getBreakpointSize();
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
};
......
......@@ -76,6 +76,7 @@ import initLegacyFilters from './init_legacy_filters';
import initIssuableSidebar from './init_issuable_sidebar';
import GpgBadges from './gpg_badges';
import UserFeatureHelper from './helpers/user_feature_helper';
import initChangesDropdown from './init_changes_dropdown';
(function() {
var Dispatcher;
......@@ -228,6 +229,7 @@ import UserFeatureHelper from './helpers/user_feature_helper';
break;
case 'projects:compare:show':
new gl.Diff();
initChangesDropdown();
break;
case 'projects:branches:new':
case 'projects:branches:create':
......@@ -320,6 +322,7 @@ import UserFeatureHelper from './helpers/user_feature_helper';
container: '.js-commit-pipeline-graph',
}).bindEvents();
initNotes();
initChangesDropdown();
$('.commit-info.branches').load(document.querySelector('.js-commit-box').dataset.commitPath);
break;
case 'projects:commit:pipelines':
......
/* eslint-disable wrap-iife, func-names, space-before-function-paren, comma-dangle, prefer-template, consistent-return, class-methods-use-this, arrow-body-style, no-unused-vars, no-underscore-dangle, no-new, max-len, no-sequences, no-unused-expressions, no-param-reassign */
/* global dateFormat */
/* global Pikaday */
import Pikaday from 'pikaday';
import DateFix from './lib/utils/datefix';
class DueDateSelect {
......
/* global bp */
import Cookies from 'js-cookie';
import './breakpoints';
import bp from './breakpoints';
export const canShowActiveSubItems = (el) => {
const isHiddenByMedia = bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md';
......@@ -49,7 +48,8 @@ export const hideSubLevelItems = (el) => {
el.classList.remove('is-showing-fly-out');
el.classList.remove('is-over');
subItems.style.display = 'none';
subItems.style.display = '';
subItems.style.transform = '';
subItems.classList.remove('is-above');
};
......
import stickyMonitor from './lib/utils/sticky';
export default () => {
stickyMonitor(document.querySelector('.js-diff-files-changed'));
$('.js-diff-stats-dropdown').glDropdown({
filterable: true,
remoteFilter: false,
});
};
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-new, comma-dangle, quotes, prefer-arrow-callback, consistent-return, one-var, no-var, one-var-declaration-per-line, no-underscore-dangle, max-len */
/* global bp */
import Cookies from 'js-cookie';
import bp from './breakpoints';
import UsersSelect from './users_select';
const PARTICIPANTS_ROW_COUNT = 7;
......
......@@ -2,8 +2,8 @@
/* global GitLab */
/* global Autosave */
/* global dateFormat */
/* global Pikaday */
import Pikaday from 'pikaday';
import UsersSelect from './users_select';
import GfmAutoComplete from './gfm_auto_complete';
import ZenMode from './zen_mode';
......
......@@ -40,7 +40,7 @@
label: 'New issue',
path: this.job.new_issue_path,
cssClass: 'js-new-issue btn btn-new btn-inverted visible-md-block visible-lg-block',
type: 'ujs-link',
type: 'link',
});
}
......
/* eslint-disable func-names, space-before-function-paren, no-var, quotes, consistent-return, prefer-arrow-callback, comma-dangle, object-shorthand, no-new, max-len, no-multi-spaces, import/newline-after-import, import/first */
/* global bp */
/* global Flash */
/* global ConfirmDangerModal */
/* global Aside */
......@@ -7,7 +6,6 @@
import jQuery from 'jquery';
import _ from 'underscore';
import Cookies from 'js-cookie';
import Pikaday from 'pikaday';
import Dropzone from 'dropzone';
import Sortable from 'vendor/Sortable';
......@@ -20,7 +18,6 @@ import 'vendor/fuzzaldrin-plus';
window.jQuery = jQuery;
window.$ = jQuery;
window._ = _;
window.Pikaday = Pikaday;
window.Dropzone = Dropzone;
window.Sortable = Sortable;
......@@ -68,7 +65,7 @@ import './api';
import './aside';
import './autosave';
import loadAwardsHandler from './awards_handler';
import './breakpoints';
import bp from './breakpoints';
import './broadcast_message';
import './build';
import './build_artifacts';
......
/* global Pikaday */
/* global dateFormat */
import Pikaday from 'pikaday';
(() => {
// Add datepickers to all `js-access-expiration-date` elements. If those elements are
// children of an element with the `clearable-input` class, and have a sibling
......
/* eslint-disable no-new, class-methods-use-this */
/* global Breakpoints */
/* global Flash */
/* global notes */
import Cookies from 'js-cookie';
import './breakpoints';
import './flash';
import BlobForkSuggestion from './blob/blob_fork_suggestion';
import stickyMonitor from './lib/utils/sticky';
import initChangesDropdown from './init_changes_dropdown';
import bp from './breakpoints';
/* eslint-disable max-len */
// MergeRequestTabs
......@@ -134,7 +133,7 @@ import stickyMonitor from './lib/utils/sticky';
this.destroyPipelinesView();
} else if (this.isDiffAction(action)) {
this.loadDiff($target.attr('href'));
if (Breakpoints.get().getBreakpointSize() !== 'lg') {
if (bp.getBreakpointSize() !== 'lg') {
this.shrinkView();
}
if (this.diffViewType() === 'parallel') {
......@@ -145,7 +144,7 @@ import stickyMonitor from './lib/utils/sticky';
this.resetViewContainer();
this.mountPipelinesView();
} else {
if (Breakpoints.get().getBreakpointSize() !== 'xs') {
if (bp.getBreakpointSize() !== 'xs') {
this.expandView();
}
this.resetViewContainer();
......@@ -267,9 +266,7 @@ import stickyMonitor from './lib/utils/sticky';
const $container = $('#diffs');
$container.html(data.html);
this.initChangesDropdown();
stickyMonitor(document.querySelector('.js-diff-files-changed'));
initChangesDropdown();
if (typeof gl.diffNotesCompileComponents !== 'undefined') {
gl.diffNotesCompileComponents();
......@@ -319,13 +316,6 @@ import stickyMonitor from './lib/utils/sticky';
});
}
initChangesDropdown() {
$('.js-diff-stats-dropdown').glDropdown({
filterable: true,
remoteFilter: false,
});
}
// Show or hide the loading spinner
//
// status - Boolean, true to show, false to hide
......@@ -401,7 +391,7 @@ import stickyMonitor from './lib/utils/sticky';
// Screen space on small screens is usually very sparse
// So we dont affix the tabs on these
if (Breakpoints.get().getBreakpointSize() === 'xs' || !$tabs.length) return;
if (bp.getBreakpointSize() === 'xs' || !$tabs.length) return;
/**
If the browser does not support position sticky, it returns the position as static.
......
<script>
/* global Breakpoints */
import d3 from 'd3';
import monitoringLegends from './monitoring_legends.vue';
import monitoringFlag from './monitoring_flag.vue';
......@@ -8,6 +7,7 @@
import eventHub from '../event_hub';
import measurements from '../utils/measurements';
import { formatRelevantDigits } from '../../lib/utils/number_utils';
import bp from '../../breakpoints';
const bisectDate = d3.bisector(d => d.time).left;
......@@ -42,7 +42,6 @@
yScale: {},
margin: {},
data: [],
breakpointHandler: Breakpoints.get(),
unitOfDisplay: '',
areaColorRgb: '#8fbce8',
lineColorRgb: '#1f78d1',
......@@ -96,7 +95,7 @@
methods: {
draw() {
const breakpointSize = this.breakpointHandler.getBreakpointSize();
const breakpointSize = bp.getBreakpointSize();
const query = this.columnData.queries[0];
this.margin = measurements.large.margin;
if (breakpointSize === 'xs' || breakpointSize === 'sm') {
......
import Cookies from 'js-cookie';
import _ from 'underscore';
/* global bp */
import './breakpoints';
import bp from './breakpoints';
export default class NewNavSidebar {
constructor() {
......
import _ from 'underscore';
import Cookies from 'js-cookie';
export default {
init() {
if (!this.initialized) {
if (Cookies.get('new_nav') === 'true' && $('.js-issuable-sidebar').length) return;
this.$window = $(window);
this.$rightSidebar = $('.js-right-sidebar');
this.$navHeight = $('.navbar-gitlab').outerHeight() +
......
......@@ -120,7 +120,7 @@ export default {
</a>
<a
v-if="action.type === 'ujs-link'"
v-else-if="action.type === 'ujs-link'"
:href="action.path"
data-method="post"
rel="nofollow"
......@@ -129,7 +129,7 @@ export default {
</a>
<button
v-else="action.type === 'button'"
v-else-if="action.type === 'button'"
@click="onClickAction(action)"
:disabled="action.isLoading"
:class="action.cssClass"
......
/* global Breakpoints */
import './breakpoints';
import bp from './breakpoints';
export default class Wikis {
constructor() {
this.bp = Breakpoints.get();
this.sidebarEl = document.querySelector('.js-wiki-sidebar');
this.sidebarExpanded = false;
......@@ -41,15 +38,15 @@ export default class Wikis {
this.renderSidebar();
}
sidebarCanCollapse() {
const bootstrapBreakpoint = this.bp.getBreakpointSize();
static sidebarCanCollapse() {
const bootstrapBreakpoint = bp.getBreakpointSize();
return bootstrapBreakpoint === 'xs' || bootstrapBreakpoint === 'sm';
}
renderSidebar() {
if (!this.sidebarEl) return;
const { classList } = this.sidebarEl;
if (this.sidebarExpanded || !this.sidebarCanCollapse()) {
if (this.sidebarExpanded || !Wikis.sidebarCanCollapse()) {
if (!classList.contains('right-sidebar-expanded')) {
classList.remove('right-sidebar-collapsed');
classList.add('right-sidebar-expanded');
......
......@@ -47,7 +47,7 @@
font-family: $monospace_font;
display: block;
font-size: $code_font_size !important;
line-height: 19px;
min-height: 19px;
white-space: nowrap;
i {
......
......@@ -104,11 +104,14 @@ $new-sidebar-collapsed-width: 50px;
&.sidebar-icons-only {
width: $new-sidebar-collapsed-width;
.nav-item-name,
.badge,
.project-title {
display: none;
}
.nav-item-name {
opacity: 0;
}
}
&.nav-sidebar-expanded {
......@@ -182,7 +185,7 @@ $new-sidebar-collapsed-width: 50px;
> li {
a {
padding: 8px 16px 8px 50px;
padding: 8px 16px 8px 40px;
&:hover,
&:focus {
......@@ -215,6 +218,10 @@ $new-sidebar-collapsed-width: 50px;
&:hover {
color: $gl-text-color;
svg {
fill: $gl-text-color;
}
}
}
......@@ -301,6 +308,7 @@ $new-sidebar-collapsed-width: 50px;
> a {
margin-left: 4px;
padding-left: 12px;
}
.badge {
......@@ -361,7 +369,7 @@ $new-sidebar-collapsed-width: 50px;
.sidebar-icons-only {
.context-header {
height: 60px;
height: 61px;
a {
padding: 10px 4px;
......
......@@ -574,10 +574,14 @@
@media (min-width: $screen-sm-min) {
position: -webkit-sticky;
position: sticky;
top: 84px;
top: 34px;
background-color: $white-light;
z-index: 190;
&.diff-files-changed-merge-request {
top: 84px;
}
+ .files,
+ .alert {
margin-top: 1px;
......
......@@ -35,7 +35,7 @@
.commit-box,
.info-well,
.commit-ci-menu,
.files-changed,
.files-changed-inner,
.limited-header-width,
.limited-width-notes {
@extend .fixed-width-container;
......
......@@ -566,14 +566,14 @@ a.deploy-project-label {
&::before {
content: "OR";
position: absolute;
left: 0;
top: 40%;
left: -10px;
top: 50%;
z-index: 10;
padding: 8px 0;
text-align: center;
background-color: $white-light;
color: $gl-text-color-tertiary;
transform: translateX(-50%);
transform: translateY(-50%);
font-size: 12px;
font-weight: bold;
line-height: 20px;
......@@ -581,8 +581,8 @@ a.deploy-project-label {
// Mobile
@media (max-width: $screen-xs-max) {
left: 50%;
top: 10px;
transform: translateY(-50%);
top: 0;
transform: translateX(-50%);
padding: 0 8px;
}
}
......
......@@ -117,7 +117,7 @@ class ApplicationController < ActionController::Base
Raven.capture_exception(exception) if sentry_enabled?
application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace
application_trace.map!{ |t| " #{t}\n" }
application_trace.map! { |t| " #{t}\n" }
logger.error "\n#{exception.class.name} (#{exception.message}):\n#{application_trace.join}"
end
......
......@@ -15,7 +15,7 @@ class Import::GitlabController < Import::BaseController
@already_added_projects = current_user.created_projects.where(import_type: "gitlab")
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos = @repos.to_a.reject{ |repo| already_added_projects_names.include? repo["path_with_namespace"] }
@repos = @repos.to_a.reject { |repo| already_added_projects_names.include? repo["path_with_namespace"] }
end
def jobs
......
......@@ -257,18 +257,6 @@ class Projects::IssuesController < Projects::ApplicationController
return render_404 unless @project.feature_available?(:issues, current_user)
end
def redirect_to_external_issue_tracker
external = @project.external_issue_tracker
return unless external
if action_name == 'new'
redirect_to external.new_issue_path
else
redirect_to external.issue_tracker_path
end
end
def issue_params
params.require(:issue).permit(*issue_params_attributes)
end
......
......@@ -3,7 +3,7 @@ module GraphHelper
refs = ""
# Commit::ref_names already strips the refs/XXX from important refs (e.g. refs/heads/XXX)
# so anything leftover is internally used by GitLab
commit_refs = commit.ref_names(repo).reject{ |name| name.starts_with?('refs/') }
commit_refs = commit.ref_names(repo).reject { |name| name.starts_with?('refs/') }
refs << commit_refs.join(' ')
# append note count
......
......@@ -151,7 +151,7 @@ module IssuablesHelper
end
def issuable_labels_tooltip(labels, limit: 5)
first, last = labels.partition.with_index{ |_, i| i < limit }
first, last = labels.partition.with_index { |_, i| i < limit }
label_names = first.collect(&:name)
label_names << "and #{last.size} more" unless last.empty?
......@@ -329,7 +329,7 @@ module IssuablesHelper
end
def selected_template(issuable)
params[:issuable_template] if issuable_templates(issuable).any?{ |template| template[:name] == params[:issuable_template] }
params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] }
end
def issuable_todo_button_data(issuable, todo, is_collapsed)
......
......@@ -206,7 +206,7 @@ module Network
# Visit branching chains
leaves.each do |l|
parents = l.parents(@map).select{|p| p.space.zero?}
parents = l.parents(@map).select {|p| p.space.zero?}
parents.each do |p|
place_chain(p, l.time)
end
......
......@@ -77,20 +77,20 @@ class Note < ActiveRecord::Base
# Scopes
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
scope :system, ->{ where(system: true) }
scope :user, ->{ where(system: false) }
scope :common, ->{ where(noteable_type: ["", nil]) }
scope :fresh, ->{ order(created_at: :asc, id: :asc) }
scope :updated_after, ->(time){ where('updated_at > ?', time) }
scope :inc_author_project, ->{ includes(:project, :author) }
scope :inc_author, ->{ includes(:author) }
scope :system, -> { where(system: true) }
scope :user, -> { where(system: false) }
scope :common, -> { where(noteable_type: ["", nil]) }
scope :fresh, -> { order(created_at: :asc, id: :asc) }
scope :updated_after, ->(time) { where('updated_at > ?', time) }
scope :inc_author_project, -> { includes(:project, :author) }
scope :inc_author, -> { includes(:author) }
scope :inc_relations_for_view, -> do
includes(:project, :author, :updated_by, :resolved_by, :award_emoji, :system_note_metadata)
end
scope :diff_notes, ->{ where(type: %w(LegacyDiffNote DiffNote)) }
scope :new_diff_notes, ->{ where(type: 'DiffNote') }
scope :non_diff_notes, ->{ where(type: ['Note', 'DiscussionNote', nil]) }
scope :diff_notes, -> { where(type: %w(LegacyDiffNote DiffNote)) }
scope :new_diff_notes, -> { where(type: 'DiffNote') }
scope :non_diff_notes, -> { where(type: ['Note', 'DiscussionNote', nil]) }
scope :with_associations, -> do
# FYI noteable cannot be loaded for LegacyDiffNote for commits
......
......@@ -8,5 +8,13 @@ class RedirectRoute < ActiveRecord::Base
presence: true,
uniqueness: { case_sensitive: false }
scope :matching_path_and_descendants, -> (path) { where('redirect_routes.path = ? OR redirect_routes.path LIKE ?', path, "#{sanitize_sql_like(path)}/%") }
scope :matching_path_and_descendants, -> (path) do
wheres = if Gitlab::Database.postgresql?
'LOWER(redirect_routes.path) = LOWER(?) OR LOWER(redirect_routes.path) LIKE LOWER(?)'
else
'redirect_routes.path = ? OR redirect_routes.path LIKE ?'
end
where(wheres, path, "#{sanitize_sql_like(path)}/%")
end
end
......@@ -148,6 +148,8 @@ class User < ActiveRecord::Base
uniqueness: { case_sensitive: false }
validate :namespace_uniq, if: :username_changed?
validate :namespace_move_dir_allowed, if: :username_changed?
validate :avatar_type, if: ->(user) { user.avatar.present? && user.avatar_changed? }
validate :unique_email, if: :email_changed?
validate :owns_notification_email, if: :notification_email_changed?
......@@ -487,6 +489,12 @@ class User < ActiveRecord::Base
end
end
def namespace_move_dir_allowed
if namespace&.any_project_has_container_registry_tags?
errors.add(:username, 'cannot be changed if a personal project has container registry tags.')
end
end
def avatar_type
unless avatar.image?
errors.add :avatar, "only images allowed"
......
......@@ -85,13 +85,13 @@ module Ci
end
def register_failure
failed_attempt_counter.increase
attempt_counter.increase
failed_attempt_counter.increment
attempt_counter.increment
end
def register_success(job)
job_queue_duration_seconds.observe({ shared_runner: @runner.shared? }, Time.now - job.created_at)
attempt_counter.increase
attempt_counter.increment
end
def failed_attempt_counter
......
......@@ -172,11 +172,11 @@ module Projects
end
def register_attempt
pages_deployments_total_counter.increase
pages_deployments_total_counter.increment
end
def register_failure
pages_deployments_failed_total_counter.increase
pages_deployments_failed_total_counter.increment
end
def pages_deployments_total_counter
......
......@@ -2,8 +2,10 @@
- show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true)
- can_create_note = !@diff_notes_disabled && can?(current_user, :create_note, diffs.project)
- diff_files = diffs.diff_files
- merge_request = local_assigns.fetch(:merge_request, false)
.content-block.oneline-block.files-changed.diff-files-changed.js-diff-files-changed
.content-block.oneline-block.files-changed.diff-files-changed.js-diff-files-changed{ class: ("diff-files-changed-merge-request" if merge_request) }
.files-changed-inner
.inline-parallel-buttons
- if !diffs_expanded? && diff_files.any? { |diff_file| diff_file.collapsed? }
= link_to 'Expand all', url_for(params.merge(expanded: 1, format: nil)), class: 'btn btn-default'
......
- if @merge_request_diff.collected? || @merge_request_diff.overflow?
= render 'projects/merge_requests/diffs/versions'
= render "projects/diffs/diffs", diffs: @diffs, environment: @environment
= render "projects/diffs/diffs", diffs: @diffs, environment: @environment, merge_request: true
- elsif @merge_request_diff.empty?
.nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch}
......@@ -3,7 +3,7 @@
= page_specific_javascript_bundle_tag('common_vue')
= page_specific_javascript_bundle_tag('sidebar')
%aside.right-sidebar.js-right-sidebar{ data: { "offset-top" => "50", "spy" => "affix", signed: { in: current_user.present? } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
%aside.right-sidebar.js-right-sidebar.js-issuable-sidebar{ data: { "offset-top" => ("50" unless show_new_nav?), "spy" => ("affix" unless show_new_nav?), signed: { in: current_user.present? } }, class: sidebar_gutter_collapsed_class, 'aria-live' => 'polite' }
.issuable-sidebar{ data: { endpoint: "#{issuable_json_path(issuable)}" } }
- can_edit_issuable = can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.block.issuable-sidebar-header
......
......@@ -14,11 +14,26 @@ Options = Struct.new(
:dry_run,
:force,
:merge_request,
:title
:title,
:type
)
INVALID_TYPE = -1
class ChangelogOptionParser
def self.parse(argv)
Type = Struct.new(:name, :description)
TYPES = [
Type.new('added', 'New feature'),
Type.new('fixed', 'Bug fix'),
Type.new('changed', 'Feature change'),
Type.new('deprecated', 'New deprecation'),
Type.new('removed', 'Feature removal'),
Type.new('security', 'Security fix'),
Type.new('other', 'Other')
].freeze
TYPES_OFFSET = 1
class << self
def parse(argv)
options = Options.new
parser = OptionParser.new do |opts|
......@@ -46,6 +61,10 @@ class ChangelogOptionParser
options.author = git_user_name if value
end
opts.on('-t', '--type [string]', String, "The category of the change, valid options are: #{TYPES.map(&:name).join(', ')}") do |value|
options.type = parse_type(value)
end
opts.on('-h', '--help', 'Print help message') do
$stdout.puts opts
exit
......@@ -60,9 +79,43 @@ class ChangelogOptionParser
options
end
def self.git_user_name
def read_type
read_type_message
type = TYPES[$stdin.getc.to_i - TYPES_OFFSET]
assert_valid_type!(type)
type.name
end
private
def parse_type(name)
type_found = TYPES.find do |type|
type.name == name
end
type_found ? type_found.name : INVALID_TYPE
end
def read_type_message
$stdout.puts "\n>> Please specify the index for the category of your change:"
TYPES.each_with_index do |type, index|
$stdout.puts "#{index + TYPES_OFFSET}. #{type.description}"
end
$stdout.print "\n?> "
end
def assert_valid_type!(type)
unless type
$stderr.puts "Invalid category index, please select an index between 1 and #{TYPES.length}"
exit 1
end
end
def git_user_name
%x{git config user.name}.strip
end
end
end
class ChangelogEntry
......@@ -72,8 +125,12 @@ class ChangelogEntry
@options = options
assert_feature_branch!
assert_new_file!
assert_title!
assert_new_file!
# Read type from $stdin unless is already set
options.type ||= ChangelogOptionParser.read_type
assert_valid_type!
$stdout.puts "\e[32mcreate\e[0m #{file_path}"
$stdout.puts contents
......@@ -90,7 +147,8 @@ class ChangelogEntry
yaml_content = YAML.dump(
'title' => title,
'merge_request' => options.merge_request,
'author' => options.author
'author' => options.author,
'type' => options.type
)
remove_trailing_whitespace(yaml_content)
end
......@@ -129,6 +187,12 @@ class ChangelogEntry
" to use the title from the previous commit."
end
def assert_valid_type!
return unless options.type && options.type == INVALID_TYPE
fail_with 'Invalid category given!'
end
def title
if options.title.empty?
last_commit_subject
......
---
title: Added type to CHANGELOG entries
merge_request:
author: Jacopo Beschi @jacopo-beschi
---
title: Fix the alignment of line numbers to lines of code in code viewer
merge_request: 13403
author: Trevor Flynn
\ No newline at end of file
---
title: Fixes new issue button for failed job returning 404
merge_request:
author:
---
title: Align OR separator to center in new project page
merge_request:
author:
---
title: Remove hidden symlinks from project import files
merge_request:
author:
---
title: Fix destroy of case-insensitive conflicting redirects
merge_request: 13357
author:
---
title: Add missing validation error for username change with container registry tags
merge_request: 13356
author:
---
title: Disallow Git URLs that include a username or hostname beginning with a non-alphanumeric
character
merge_request:
author:
......@@ -71,7 +71,7 @@ class Settings < Settingslogic
# check that `current` (string or integer) is a contant in `modul`.
def verify_constant(modul, current, default)
constant = modul.constants.find{ |name| modul.const_get(name) == current }
constant = modul.constants.find { |name| modul.const_get(name) == current }
value = constant.nil? ? default : modul.const_get(constant)
if current.is_a? String
value = modul.const_get(current.upcase) rescue default
......
......@@ -3,6 +3,9 @@
resource :repository, only: [:create] do
member do
get ':ref/archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex, ref: /.+/ }, action: 'archive', as: 'archive'
# deprecated since GitLab 9.5
get 'archive', constraints: { format: Gitlab::PathRegex.archive_formats_regex }, as: 'archive_alternative'
end
end
......
......@@ -10,7 +10,7 @@ class AddUniqueIndexToLabels < ActiveRecord::Migration
def up
select_all('SELECT title, project_id, COUNT(id) as cnt FROM labels GROUP BY project_id, title HAVING COUNT(id) > 1').each do |label|
label_title = quote_string(label['title'])
duplicated_ids = select_all("SELECT id FROM labels WHERE project_id = #{label['project_id']} AND title = '#{label_title}' ORDER BY id ASC").map{ |label| label['id'] }
duplicated_ids = select_all("SELECT id FROM labels WHERE project_id = #{label['project_id']} AND title = '#{label_title}' ORDER BY id ASC").map { |label| label['id'] }
label_id = duplicated_ids.first
duplicated_ids.delete(label_id)
......
......@@ -30,7 +30,7 @@ class CleanupNamespacelessPendingDeleteProjects < ActiveRecord::Migration
private
def pending_delete_batch
connection.exec_query(find_batch).map{ |row| row['id'].to_i }
connection.exec_query(find_batch).map { |row| row['id'].to_i }
end
BATCH_SIZE = 5000
......
---
toc: false
---
# GitLab Documentation
Welcome to [GitLab](https://about.gitlab.com/), a Git-based fully featured
......
......@@ -42,6 +42,10 @@ GitLab does not recommend using EFS with GitLab.
are allocated. For smaller volumes, users may experience decent performance
for a period of time due to 'Burst Credits'. Over a period of weeks to months
credits may run out and performance will bottom out.
- To keep "Burst Credits" available, it may be necessary to provision more space
with 'dummy data'. However, this may get expensive.
- Another option to maintain "Burst Credits" is to use FS Cache on the server so
that AWS doesn't always have to go into EFS to access files.
- For larger volumes, allocated IOPS may not be the problem. Workloads where
many small files are written in a serialized manner are not well-suited for EFS.
EBS with an NFS server on top will perform much better.
......
......@@ -15,11 +15,14 @@ following format:
title: "Going through change[log]s"
merge_request: 1972
author: Ozzy Osbourne
type: added
```
The `merge_request` value is a reference to a merge request that adds this
entry, and the `author` key is used to give attribution to community
contributors. **Both are optional**.
The `type` field maps the category of the change,
valid options are: added, fixed, changed, deprecated, removed, security, other. **Type field is mandatory**.
Community contributors and core team members are encouraged to add their name to
the `author` field. GitLab team members **should not**.
......@@ -94,6 +97,19 @@ Its simplest usage is to provide the value for `title`:
$ bin/changelog 'Hey DZ, I added a feature to GitLab!'
```
At this point the script would ask you to select the category of the change (mapped to the `type` field in the entry):
```text
>> Please specify the category of your change:
1. New feature
2. Bug fix
3. Feature change
4. New deprecation
5. Feature removal
6. Security fix
7. Other
```
The entry filename is based on the name of the current Git branch. If you run
the command above on a branch called `feature/hey-dz`, it will generate a
`changelogs/unreleased/feature-hey-dz.yml` file.
......@@ -106,6 +122,7 @@ create changelogs/unreleased/my-feature.yml
title: Hey DZ, I added a feature to GitLab!
merge_request:
author:
type:
```
If you're working on the GitLab EE repository, the entry will be added to
`changelogs/unreleased-ee/` instead.
......@@ -113,12 +130,13 @@ If you're working on the GitLab EE repository, the entry will be added to
#### Arguments
| Argument | Shorthand | Purpose |
| ----------------- | --------- | --------------------------------------------- |
| ----------------- | --------- | ---------------------------------------------------------------------------------------------------------- |
| [`--amend`] | | Amend the previous commit |
| [`--force`] | `-f` | Overwrite an existing entry |
| [`--merge-request`] | `-m` | Set merge request ID |
| [`--dry-run`] | `-n` | Don't actually write anything, just print |
| [`--git-username`] | `-u` | Use Git user.name configuration as the author |
| [`--type`] | `-t` | The category of the change, valid options are: added, fixed, changed, deprecated, removed, security, other |
| [`--help`] | `-h` | Print help message |
[`--amend`]: #-amend
......@@ -126,6 +144,7 @@ If you're working on the GitLab EE repository, the entry will be added to
[`--merge-request`]: #-merge-request-or-m
[`--dry-run`]: #-dry-run-or-n
[`--git-username`]: #-git-username-or-u
[`--type`]: #-type-or-t
[`--help`]: #-help
##### `--amend`
......@@ -147,6 +166,7 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Added an awesome new feature to GitLab
merge_request:
author:
type:
```
##### `--force` or `-f`
......@@ -164,6 +184,7 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Hey DZ, I added a feature to GitLab!
merge_request: 1983
author:
type:
```
##### `--merge-request` or `-m`
......@@ -178,6 +199,7 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Hey DZ, I added a feature to GitLab!
merge_request: 1983
author:
type:
```
##### `--dry-run` or `-n`
......@@ -192,6 +214,7 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Added an awesome new feature to GitLab
merge_request:
author:
type:
$ ls changelogs/unreleased/
```
......@@ -211,6 +234,21 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Hey DZ, I added a feature to GitLab!
merge_request:
author: Jane Doe
type:
```
##### `--type` or `-t`
Use the **`--type`** or **`-t`** argument to provide the `type` value:
```text
$ bin/changelog 'Hey DZ, I added a feature to GitLab!' -t added
create changelogs/unreleased/feature-hey-dz.yml
---
title: Hey DZ, I added a feature to GitLab!
merge_request:
author:
type: added
```
### History and Reasoning
......
......@@ -66,6 +66,9 @@ Install the required packages (needed to compile Ruby and native extensions to R
sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake
Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
you can [install re2 manually](https://github.com/google/re2/wiki/Install).
If you want to use Kerberos for user authentication, then install libkrb5-dev:
sudo apt-get install libkrb5-dev
......
# GitLab Helm Chart
> Officially supported cloud providers are Google Container Service and Azure Container Service.
> These Helm charts are in beta. GitLab is working on a [cloud-native](http://docs.gitlab.com/omnibus/package-information/cloud_native.html) set of [Charts](https://gitlab.com/charts/helm.gitlab.io) which will replace these.
> Officially supported schedulers are Kubernetes and Terraform.
> Officially supported cloud providers are Google Container Service and Azure Container Service.
The `gitlab` Helm chart deploys GitLab into your Kubernetes cluster.
......
# GitLab-Omnibus Helm Chart
> These Helm charts are in beta. GitLab is working on a [cloud-native](http://docs.gitlab.com/omnibus/package-information/cloud_native.html) set of [Charts](https://gitlab.com/charts/helm.gitlab.io) which will replace these.
> Officially supported cloud providers are Google Container Service and Azure Container Service.
This work is based partially on: https://github.com/lwolf/kubernetes-gitlab/. GitLab would like to thank Sergey Nuzhdin for his work.
## Introduction
This chart provides an easy way to get started with GitLab, provisioning an installation with nearly all functionality enabled. SSL is automatically provisioned as well via [Let's Encrypt](https://letsencrypt.org/).
The deployment includes:
- A [GitLab Omnibus](https://docs.gitlab.com/omnibus/) Pod, including Mattermost, Container Registry, and Prometheus
- An auto-scaling [GitLab Runner](https://docs.gitlab.com/runner/) using the Kubernetes executor
- [Redis](https://github.com/kubernetes/charts/tree/master/stable/redis)
- [PostgreSQL](https://github.com/kubernetes/charts/tree/master/stable/postgresql)
- [NGINX Ingress](https://github.com/kubernetes/charts/tree/master/stable/nginx-ingress)
- Persistent Volume Claims for Data, Registry, Postgres, and Redis
A video demonstration of GitLab utilizing this chart [is available](https://about.gitlab.com/handbook/sales/demo/).
Terms:
- Google Cloud Platform (**GCP**)
- Google Container Engine (**GKE**)
- Azure Container Service (**ACS**)
- Kubernetes (**k8s**)
## Prerequisites
- _At least_ 4 GB of RAM available on your cluster, in chunks of 1 GB. 41GB of storage and 2 CPU are also required.
- Kubernetes 1.4+ with Beta APIs enabled
- [Persistent Volume](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) provisioner support in the underlying infrastructure
- An [external IP address](#networking-prerequisites)
- A [wildcard DNS entry](#networking-prerequisites), which resolves to the external IP address
- The `kubectl` CLI installed locally and authenticated for the cluster
- The Helm Client installed locally
- The Helm Server (Tiller) already installed and running in the cluster, by running `helm init`
- The GitLab Helm Repo [added to your Helm Client](index.md#add-the-gitlab-helm-repository)
### Networking Prerequisites
This chart configures a GitLab server and Kubernetes cluster which can support dynamic [Review Apps](https://docs.gitlab.com/ee/ci/review_apps/index.html), as well as services like the integrated [Container Registry](https://docs.gitlab.com/ee/user/project/container_registry.html) and [Mattermost](https://docs.gitlab.com/omnibus/gitlab-mattermost/).
To support the GitLab services and dynamic environments, a wildcard DNS entry is required which resolves to the external Load Balancer IP.
To provision an external IP on GCP and Azure, simply request a new address from the Networking section. Ensure that the region matches the region your container cluster is created in. Note, it is important that the IP is not assigned at this point in time. It will be automatically assigned once the Helm chart is installed, and assigned to the Load Balancer.
Now that an external IP address has been allocated, ensure that the wildcard DNS entry you would like to use resolves to this IP. Please consult the documentation for your DNS service for more information on creating DNS records.
## Configuring and Installing GitLab
For most installations, only two parameters are required:
- `baseIP`: the desired [external IP address](#networking-prerequisites)
- `baseDomain`: the [base domain](#networking-prerequisites) with the wildcard host entry resolving to the `baseIP`. For example, `mycompany.io`.
Other common configuration options:
- `gitlab`: Choose the [desired edition](https://about.gitlab.com/products), either `ee` or `ce`. `ce` is the default.
- `gitlabEELicense`: For Enterprise Edition, the [license](https://docs.gitlab.com/ee/user/admin_area/license.html) can be installed directly via the Chart
- `provider`: Optimizes the deployment for a cloud provider. The default is `gke` for GCP, with `acs` also supported for Azure.
- `legoEmail`: Email address to use when requesting new SSL certificates from Let's Encrypt
For additional configuration options, consult the [values.yaml](https://gitlab.com/charts/charts.gitlab.io/blob/master/charts/gitlab-omnibus/values.yaml).
These settings can either be passed directly on the command line:
```bash
helm install --name gitlab --set baseDomain=gitlab.io,baseIP=1.1.1.1,gitlab=ee,gitlabEELicense=$LICENSE,legoEmail=email@gitlab.com gitlab/gitlab-omnibus
```
or within a YAML file:
```bash
helm install --name gitlab -f values.yaml gitlab/gitlab-omnibus
```
> **Note:**
If you are using a machine type with support for less than 4 attached disks, like an Azure trial, you should disable dedicated storage for [Postgres and Redis](#persistent-storage).
### Choosing a different GitLab release version
The version of GitLab installed is based on the `gitlab` setting (see [section](#choosing-gitlab-edition) above), and
the value of the corresponding helm setting: `gitlabCEImage` or `gitabEEImage`.
```yaml
gitlab: CE
gitlabCEImage: gitlab/gitlab-ce:9.1.2-ce.0
gitlabEEImage: gitlab/gitlab-ee:9.1.2-ee.0
```
The different images can be found in the [gitlab-ce](https://hub.docker.com/r/gitlab/gitlab-ce/tags/) and [gitlab-ee](https://hub.docker.com/r/gitlab/gitlab-ee/tags/)
repositories on Docker Hub.
> **Note:**
There is no guarantee that other release versions of GitLab, other than what are
used by default in the chart, will be supported by a chart install.
### Persistent storage
By default, persistent storage is enabled for GitLab and the charts it depends
on (Redis and PostgreSQL).
Components can have their claim size set from your `values.yaml`, along with whether to provision separate storage for Postgres and Redis.
Basic configuration:
```yaml
redisImage: redis:3.2.10
redisDedicatedStorage: true
redisStorageSize: 5Gi
postgresImage: postgres:9.6.3
# If you disable postgresDedicatedStorage, you should consider bumping up gitlabRailsStorageSize
postgresDedicatedStorage: true
postgresStorageSize: 30Gi
gitlabRailsStorageSize: 30Gi
gitlabRegistryStorageSize: 30Gi
gitlabConfigStorageSize: 1Gi
```
### Routing and SSL
Ingress routing and SSL are automatically configured within this Chart. An NGINX ingress is provisioned and configured, and will route traffic to any service. SSL certificates are automatically created and configured by [kube-lego](https://github.com/kubernetes/charts/tree/master/stable/kube-lego).
> **Note:**
Let's Encrypt limits a single TLD to five certificate requests within a single week. This means that common DNS wildcard services like [xip.io](http://xip.io) and [nip.io](http://nip.io) are unlikely to work.
## Installing GitLab using the Helm Chart
> You may see a temporary error message `SchedulerPredicates failed due to PersistentVolumeClaim is not bound` while storage provisions. Once the storage provisions, the pods will automatically restart. This may take a couple minutes depending on your cloud provider. If the error persists, please review the [prerequisites](#prerequisites) to ensure you have enough RAM, CPU, and storage.
Once you have reviewed the [configuration settings](#configuring-and-installing-gitlab), you can install the chart. We recommending saving your configuration options in a `values.yaml` file for easier upgrades in the future.
For example:
```bash
helm install --name gitlab -f values.yaml gitlab/gitlab-omnibus
```
or passing them on the command line:
```bash
helm install --name gitlab --set baseDomain=gitlab.io,baseIP=1.1.1.1,gitlab=ee,gitlabEELicense=$LICENSE,legoEmail=email@gitlab.com gitlab/gitlab-omnibus
```
## Updating GitLab using the Helm Chart
Once your GitLab Chart is installed, configuration changes and chart updates
should we done using `helm upgrade`
```bash
helm upgrade -f <CONFIG_VALUES_FILE> <RELEASE-NAME> gitlab/gitlab
```
where:
- `<CONFIG_VALUES_FILE>` is the path to values file containing your custom
[configuration] (#configuring-and-installing-gitlab).
- `<RELEASE-NAME>` is the name you gave the chart when installing it.
In the [Install section](#installing-gitlab-using-the-helm-chart) we called it `gitlab`.
## Uninstalling GitLab using the Helm Chart
To uninstall the GitLab Chart, run the following:
```bash
helm delete <RELEASE-NAME>
```
where:
- `<RELEASE-NAME>` is the name you gave the chart when installing it.
In the [Install section](#installing) we called it `gitlab`.
[kube-srv]: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services---service-types
[storageclass]: https://kubernetes.io/docs/concepts/storage/persistent-volumes/#storageclasses
# GitLab Runner Helm Chart
> Officially supported cloud providers are Google Container Service and Azure Container Service.
> These Helm charts are in beta. GitLab is working on a [cloud-native](http://docs.gitlab.com/omnibus/package-information/cloud_native.html) set of [Charts](https://gitlab.com/charts/helm.gitlab.io) which will replace these.
> Officially supported schedulers are Kubernetes and Terraform.
> Officially supported cloud providers are Google Container Service and Azure Container Service.
The `gitlab-runner` Helm chart deploys a GitLab Runner instance into your
Kubernetes cluster.
......
# Installing GitLab on Kubernetes
> Officially supported cloud providers are Google Container Service and Azure Container Service.
> These Helm charts are in beta. GitLab is working on a [cloud-native](http://docs.gitlab.com/omnibus/package-information/cloud_native.html) set of [Charts](https://gitlab.com/charts/helm.gitlab.io) which will replace these.
> Officially supported schedulers are Kubernetes, Terraform and Tectonic.
> Officially supported cloud providers are Google Container Service and Azure Container Service.
The easiest method to deploy GitLab in [Kubernetes](https://kubernetes.io/) is
to take advantage of the official GitLab Helm charts. [Helm] is a package
......@@ -35,12 +35,11 @@ helm init
## Using the GitLab Helm Charts
GitLab makes available two Helm Charts, one for the GitLab server and another
for the Runner. More detailed information on installing and configuring each
Chart can be found below:
GitLab makes available three Helm Charts: an easy to use bundled chart, and a specific chart for GitLab itself and the Runner.
- [Install GitLab](gitlab_chart.md)
- [Install GitLab Runner](gitlab_runner_chart.md)
- [gitlab-omnibus](gitlab_omnibus.md): The easiest way to get started. Includes everything needed to run GitLab, including: a Runner, Container Registry, automatic SSL, and an Ingress.
- [gitlab](gitlab_chart.md): Just the GitLab service, with optional Postgres and Redis.
- [gitlab-runner](gitlab_runner_chart.md): GitLab Runner, to process CI jobs.
[chart]: https://github.com/kubernetes/charts
[helm-quick]: https://github.com/kubernetes/helm/blob/master/docs/quickstart.md
......
......@@ -264,6 +264,16 @@ sudo systemctl daemon-reload
### 9. Install libs, migrations, etc.
GitLab 9.0.11 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24570)
a dependency on on the `re2` regular expression library. To install this dependency:
```bash
sudo apt-get install libre2-dev
```
Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
you can [install re2 manually](https://github.com/google/re2/wiki/Install).
```bash
cd /home/git/gitlab
......
......@@ -264,6 +264,16 @@ sudo systemctl daemon-reload
### 9. Install libs, migrations, etc.
GitLab 9.1.8 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24570)
a dependency on on the `re2` regular expression library. To install this dependency:
```bash
sudo apt-get install libre2-dev
```
Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
you can [install re2 manually](https://github.com/google/re2/wiki/Install).
```bash
cd /home/git/gitlab
......
......@@ -222,6 +222,16 @@ sudo systemctl daemon-reload
### 10. Install libs, migrations, etc.
GitLab 9.2.8 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24570)
a dependency on on the `re2` regular expression library. To install this dependency:
```bash
sudo apt-get install libre2-dev
```
Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
you can [install re2 manually](https://github.com/google/re2/wiki/Install).
```bash
cd /home/git/gitlab
......
......@@ -258,6 +258,16 @@ sudo systemctl daemon-reload
### 12. Install libs, migrations, etc.
GitLab 9.3.8 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24570)
a dependency on on the `re2` regular expression library. To install this dependency:
```bash
sudo apt-get install libre2-dev
```
Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
you can [install re2 manually](https://github.com/google/re2/wiki/Install).
```bash
cd /home/git/gitlab
......
......@@ -271,6 +271,16 @@ sudo systemctl daemon-reload
### 12. Install libs, migrations, etc.
GitLab 9.4 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/24570)
a dependency on on the `re2` regular expression library. To install this dependency:
```bash
sudo apt-get install libre2-dev
```
Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
you can [install re2 manually](https://github.com/google/re2/wiki/Install).
```bash
cd /home/git/gitlab
......
......@@ -295,6 +295,10 @@ sudo -u git -H bundle clean
# Run database migrations
sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
# Compile GetText PO files
sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
# Update node dependencies and recompile assets
sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
......
......@@ -132,7 +132,7 @@ module API
expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id
expose :namespace, using: 'API::Entities::Namespace'
expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? }
expose :forked_from_project, using: Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? }
expose :import_status
expose :import_error, if: lambda { |_project, options| options[:user_can_admin_project] }
expose :avatar_url do |user, options|
......
......@@ -68,7 +68,7 @@ module API
expose :lfs_enabled?, as: :lfs_enabled
expose :creator_id
expose :namespace, using: 'API::Entities::Namespace'
expose :forked_from_project, using: ::API::Entities::BasicProjectDetails, if: lambda{ |project, options| project.forked? }
expose :forked_from_project, using: ::API::Entities::BasicProjectDetails, if: lambda { |project, options| project.forked? }
expose :avatar_url do |user, options|
user.avatar_url(only_path: false)
end
......
......@@ -198,11 +198,11 @@ module Backup
end
def archives_to_backup
ARCHIVES_TO_BACKUP.map{ |name| (name + ".tar.gz") unless skipped?(name) }.compact
ARCHIVES_TO_BACKUP.map { |name| (name + ".tar.gz") unless skipped?(name) }.compact
end
def folders_to_backup
FOLDERS_TO_BACKUP.reject{ |name| skipped?(name) }
FOLDERS_TO_BACKUP.reject { |name| skipped?(name) }
end
def disabled_features
......
......@@ -6,6 +6,8 @@ class ProjectUrlConstrainer
return false unless DynamicPathValidator.valid_project_path?(full_path)
# We intentionally allow SELECT(*) here so result of this query can be used
# as cache for further Project.find_by_full_path calls within request
Project.find_by_full_path(full_path, follow_redirects: request.get?).present?
end
end
......@@ -98,10 +98,11 @@ module Gitlab
if status.zero?
@ee_branch_found = ee_branch_prefix
else
_, status = step("Fetching origin/#{ee_branch_suffix}", %W[git fetch origin #{ee_branch_suffix}])
return
end
_, status = step("Fetching origin/#{ee_branch_suffix}", %W[git fetch origin #{ee_branch_suffix}])
if status.zero?
@ee_branch_found = ee_branch_suffix
else
......
......@@ -10,7 +10,7 @@ module Gitlab
def exists?
request = Gitaly::RepositoryExistsRequest.new(repository: @gitaly_repo)
GitalyClient.call(@storage, :repository_service, :exists, request).exists
GitalyClient.call(@storage, :repository_service, :repository_exists, request).exists
end
def garbage_collect(create_bitmap)
......
......@@ -71,7 +71,7 @@ module Gitlab
end
def config
Gitlab.config.omniauth.providers.find{|provider| provider.name == "gitlab"}
Gitlab.config.omniauth.providers.find {|provider| provider.name == "gitlab"}
end
def gitlab_options
......
......@@ -47,12 +47,16 @@ module Gitlab
end
def remove_symlinks!
Dir["#{@shared.export_path}/**/*"].each do |path|
extracted_files.each do |path|
FileUtils.rm(path) if File.lstat(path).symlink?
end
true
end
def extracted_files
Dir.glob("#{@shared.export_path}/**/*", File::FNM_DOTMATCH).reject { |f| f =~ /.*\/\.{1,2}$/ }
end
end
end
end
......@@ -19,6 +19,8 @@ module Gitlab
return false if internal?(uri)
return true if blocked_port?(uri.port)
return true if blocked_user_or_hostname?(uri.user)
return true if blocked_user_or_hostname?(uri.hostname)
server_ips = Resolv.getaddresses(uri.hostname)
return true if (blocked_ips & server_ips).any?
......@@ -37,6 +39,12 @@ module Gitlab
port < 1024 && !VALID_PORTS.include?(port)
end
def blocked_user_or_hostname?(value)
return false if value.blank?
value !~ /\A\p{Alnum}/
end
def internal?(uri)
internal_web?(uri) || internal_shell?(uri)
end
......
......@@ -25,6 +25,39 @@ map $http_upgrade $connection_upgrade_gitlab {
'' close;
}
## NGINX 'combined' log format with filtered query strings
log_format gitlab_access $remote_addr - $remote_user [$time_local] "$request_method $gitlab_filtered_request_uri $server_protocol" $status $body_bytes_sent "$gitlab_filtered_http_referer" "$http_user_agent";
## Remove private_token from the request URI
# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&rss_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
map $request_uri $gitlab_temp_request_uri_1 {
default $request_uri;
~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
## Remove authenticity_token from the request URI
# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
map $gitlab_temp_request_uri_1 $gitlab_temp_request_uri_2 {
default $gitlab_temp_request_uri_1;
~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
## Remove rss_token from the request URI
# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=[FILTERED]&...
map $gitlab_temp_request_uri_2 $gitlab_filtered_request_uri {
default $gitlab_temp_request_uri_2;
~(?i)^(?<start>.*)(?<temp>[\?&]rss[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
## A version of the referer without the query string
map $http_referer $gitlab_filtered_http_referer {
default $http_referer;
~^(?<temp>.*)\? $temp;
}
## Normal HTTP host
server {
## Either remove "default_server" from the listen line below,
......@@ -46,7 +79,7 @@ server {
# set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24
## Individual nginx logs for this GitLab vhost
access_log /var/log/nginx/gitlab_access.log;
access_log /var/log/nginx/gitlab_access.log gitlab_access;
error_log /var/log/nginx/gitlab_error.log;
location / {
......
......@@ -18,6 +18,9 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache off;
# The same address as passed to GitLab Pages: `-listen-proxy`
proxy_pass http://localhost:8090/;
}
......
......@@ -67,6 +67,9 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache off;
# The same address as passed to GitLab Pages: `-listen-proxy`
proxy_pass http://localhost:8090/;
}
......
......@@ -29,6 +29,41 @@ map $http_upgrade $connection_upgrade_gitlab_ssl {
'' close;
}
## NGINX 'combined' log format with filtered query strings
log_format gitlab_ssl_access $remote_addr - $remote_user [$time_local] "$request_method $gitlab_ssl_filtered_request_uri $server_protocol" $status $body_bytes_sent "$gitlab_ssl_filtered_http_referer" "$http_user_agent";
## Remove private_token from the request URI
# In: /foo?private_token=unfiltered&authenticity_token=unfiltered&rss_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
map $request_uri $gitlab_ssl_temp_request_uri_1 {
default $request_uri;
~(?i)^(?<start>.*)(?<temp>[\?&]private[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
## Remove authenticity_token from the request URI
# In: /foo?private_token=[FILTERED]&authenticity_token=unfiltered&rss_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
map $gitlab_ssl_temp_request_uri_1 $gitlab_ssl_temp_request_uri_2 {
default $gitlab_ssl_temp_request_uri_1;
~(?i)^(?<start>.*)(?<temp>[\?&]authenticity[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
## Remove rss_token from the request URI
# In: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=unfiltered&...
# Out: /foo?private_token=[FILTERED]&authenticity_token=[FILTERED]&rss_token=[FILTERED]&...
map $gitlab_ssl_temp_request_uri_2 $gitlab_ssl_filtered_request_uri {
default $gitlab_ssl_temp_request_uri_2;
~(?i)^(?<start>.*)(?<temp>[\?&]rss[\-_]token)=[^&]*(?<rest>.*)$ "$start$temp=[FILTERED]$rest";
}
## A version of the referer without the query string
map $http_referer $gitlab_ssl_filtered_http_referer {
default $http_referer;
~^(?<temp>.*)\? $temp;
}
## Redirects all HTTP traffic to the HTTPS host
server {
## Either remove "default_server" from the listen line below,
......@@ -40,7 +75,7 @@ server {
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
server_tokens off; ## Don't show the nginx version number, a security best practice
return 301 https://$http_host$request_uri;
access_log /var/log/nginx/gitlab_access.log;
access_log /var/log/nginx/gitlab_access.log gitlab_ssl_access;
error_log /var/log/nginx/gitlab_error.log;
}
......@@ -93,7 +128,7 @@ server {
# set_real_ip_from YOUR_TRUSTED_ADDRESS; ## Replace this with something like 192.168.1.0/24
## Individual nginx logs for this GitLab vhost
access_log /var/log/nginx/gitlab_access.log;
access_log /var/log/nginx/gitlab_access.log gitlab_ssl_access;
error_log /var/log/nginx/gitlab_error.log;
location / {
......
......@@ -41,8 +41,6 @@ namespace :gitlab do
end
namespace :gitlab_shell do
include SystemCheck::Helpers
desc "GitLab | Check the configuration of GitLab Shell"
task check: :environment do
warn_user_is_not_gitlab
......@@ -249,8 +247,6 @@ namespace :gitlab do
end
namespace :sidekiq do
include SystemCheck::Helpers
desc "GitLab | Check the configuration of Sidekiq"
task check: :environment do
warn_user_is_not_gitlab
......@@ -309,8 +305,6 @@ namespace :gitlab do
end
namespace :incoming_email do
include SystemCheck::Helpers
desc "GitLab | Check the configuration of Reply by email"
task check: :environment do
warn_user_is_not_gitlab
......@@ -444,8 +438,6 @@ namespace :gitlab do
end
namespace :ldap do
include SystemCheck::Helpers
task :check, [:limit] => :environment do |_, args|
# Only show up to 100 results because LDAP directories can be very big.
# This setting only affects the `rake gitlab:check` script.
......@@ -501,8 +493,6 @@ namespace :gitlab do
end
namespace :repo do
include SystemCheck::Helpers
desc "GitLab | Check the integrity of the repositories managed by GitLab"
task check: :environment do
Gitlab.config.repositories.storages.each do |name, repository_storage|
......@@ -517,8 +507,6 @@ namespace :gitlab do
end
namespace :user do
include SystemCheck::Helpers
desc "GitLab | Check the integrity of a specific user's repositories"
task :check_repos, [:username] => :environment do |t, args|
username = args[:username] || prompt("Check repository integrity for fsername? ".color(:blue))
......
......@@ -4,5 +4,5 @@ require 'tasks/gitlab/task_helpers'
StateMachines::Machine.ignore_method_conflicts = true if ENV['CRON']
namespace :gitlab do
include Gitlab::TaskHelpers
extend SystemCheck::Helpers
end
......@@ -5,6 +5,8 @@ module Gitlab
TaskAbortedByUserError = Class.new(StandardError)
module TaskHelpers
extend self
# Ask if the user wants to continue
#
# Returns "yes" the user chose to continue
......
......@@ -4,7 +4,8 @@ load File.expand_path('../../bin/changelog', __dir__)
describe 'bin/changelog' do
describe ChangelogOptionParser do
it 'parses --ammend' do
describe '.parse' do
it 'parses --amend' do
options = described_class.parse(%w[foo bar --amend])
expect(options.amend).to eq true
......@@ -44,6 +45,14 @@ describe 'bin/changelog' do
end
end
it 'parses --type and -t' do
%w[--type -t].each do |flag|
options = described_class.parse(%W[foo #{flag} security])
expect(options.type).to eq 'security'
end
end
it 'parses -h' do
expect do
expect { described_class.parse(%w[foo -h bar]) }.to output.to_stdout
......@@ -56,4 +65,29 @@ describe 'bin/changelog' do
expect(options.title).to eq 'foo bar baz'
end
end
describe '.read_type' do
let(:type) { '1' }
it 'reads type from $stdin' do
expect($stdin).to receive(:getc).and_return(type)
expect do
expect(described_class.read_type).to eq('added')
end.to output.to_stdout
end
context 'invalid type given' do
let(:type) { '99' }
it 'shows error message and exits the program' do
allow($stdin).to receive(:getc).and_return(type)
expect do
expect do
expect { described_class.read_type }.to raise_error(SystemExit)
end.to output("Invalid category index, please select an index between 1 and 7\n").to_stderr
end.to output.to_stdout
end
end
end
end
end
......@@ -127,7 +127,7 @@ describe Admin::UsersController do
describe 'POST create' do
it 'creates the user' do
expect{ post :create, user: attributes_for(:user) }.to change{ User.count }.by(1)
expect { post :create, user: attributes_for(:user) }.to change { User.count }.by(1)
end
it 'shows only one error message for an invalid email' do
......
require 'spec_helper'
describe AutocompleteController do
let!(:project) { create(:project) }
let!(:user) { create(:user) }
let(:project) { create(:project) }
let(:user) { project.owner }
context 'GET users' do
let!(:user2) { create(:user) }
......@@ -11,7 +11,6 @@ describe AutocompleteController do
context 'project members' do
before do
sign_in(user)
project.add_master(user)
end
describe 'GET #users with project ID' do
......@@ -19,11 +18,11 @@ describe AutocompleteController do
get(:users, project_id: project.id)
end
let(:body) { JSON.parse(response.body) }
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq 2 }
it { expect(body.map { |u| u["username"] }).to include(user.username) }
it 'returns the project members' do
expect(json_response).to be_kind_of(Array)
expect(json_response.size).to eq(1)
expect(json_response.map { |u| u["username"] }).to include(user.username)
end
end
describe 'GET #users with unknown project' do
......@@ -39,20 +38,20 @@ describe AutocompleteController do
let(:group) { create(:group) }
before do
sign_in(user)
group.add_owner(user)
sign_in(user)
end
let(:body) { JSON.parse(response.body) }
describe 'GET #users with group ID' do
before do
get(:users, group_id: group.id)
end
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq 1 }
it { expect(body.first["username"]).to eq user.username }
it 'returns the group members' do
expect(json_response).to be_kind_of(Array)
expect(json_response.size).to eq(1)
expect(json_response.first["username"]).to eq user.username
end
end
describe 'GET #users with unknown group ID' do
......@@ -65,23 +64,22 @@ describe AutocompleteController do
end
context 'non-member login for public project' do
let!(:project) { create(:project, :public) }
let(:project) { create(:project, :public) }
before do
sign_in(non_member)
project.add_master(user)
end
let(:body) { JSON.parse(response.body) }
describe 'GET #users with project ID' do
before do
get(:users, project_id: project.id, current_user: true)
end
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq 3 }
it { expect(body.map { |u| u['username'] }).to include(user.username, non_member.username) }
it 'returns the project members and non-members' do
expect(json_response).to be_kind_of(Array)
expect(json_response.size).to eq(2)
expect(json_response.map { |u| u['username'] }).to include(user.username, non_member.username)
end
end
end
......@@ -91,10 +89,8 @@ describe AutocompleteController do
get(:users)
end
let(:body) { JSON.parse(response.body) }
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq User.count }
it { expect(json_response).to be_kind_of(Array) }
it { expect(json_response.size).to eq User.count }
end
context 'user order' do
......@@ -106,7 +102,7 @@ describe AutocompleteController do
sign_in(user)
get(:users, search: 'user')
response_usernames = JSON.parse(response.body).map { |user| user['username'] }
response_usernames = json_response.map { |user| user['username'] }
expect(response_usernames.take(3)).to match_array([user.username, reported_user.username, user1.username])
end
......@@ -120,15 +116,12 @@ describe AutocompleteController do
get(:users, per_page: per_page)
end
let(:body) { JSON.parse(response.body) }
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq per_page }
it { expect(json_response).to be_kind_of(Array) }
it { expect(json_response.size).to eq(per_page) }
end
context 'unauthenticated user' do
let(:public_project) { create(:project, :public) }
let(:body) { JSON.parse(response.body) }
describe 'GET #users with public project' do
before do
......@@ -136,8 +129,8 @@ describe AutocompleteController do
get(:users, project_id: public_project.id)
end
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq 2 }
it { expect(json_response).to be_kind_of(Array) }
it { expect(json_response.size).to eq 2 }
end
describe 'GET #users with project' do
......@@ -170,8 +163,8 @@ describe AutocompleteController do
get(:users)
end
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq 0 }
it { expect(json_response).to be_kind_of(Array) }
it { expect(json_response).to be_empty }
end
describe 'GET #users with todo filter' do
......@@ -179,14 +172,12 @@ describe AutocompleteController do
get :users, todo_filter: true
expect(response.status).to eq 200
expect(body).to be_kind_of(Array)
expect(json_response).to be_kind_of(Array)
end
end
end
context 'author of issuable included' do
let(:body) { JSON.parse(response.body) }
context 'authenticated' do
before do
sign_in(user)
......@@ -195,13 +186,13 @@ describe AutocompleteController do
it 'includes the author' do
get(:users, author_id: non_member.id)
expect(body.first["username"]).to eq non_member.username
expect(json_response.first["username"]).to eq non_member.username
end
it 'rejects non existent user ids' do
get(:users, author_id: 99999)
expect(body.collect { |u| u['id'] }).not_to include(99999)
expect(json_response.collect { |u| u['id'] }).not_to include(99999)
end
end
......@@ -209,7 +200,7 @@ describe AutocompleteController do
it 'returns empty result' do
get(:users, author_id: non_member.id)
expect(body).to be_empty
expect(json_response).to be_empty
end
end
end
......@@ -222,10 +213,9 @@ describe AutocompleteController do
it 'skips the user IDs passed' do
get(:users, skip_users: [user, user2].map(&:id))
other_user_ids = [non_member, project.owner, project.creator].map(&:id)
response_user_ids = JSON.parse(response.body).map { |user| user['id'] }
response_user_ids = json_response.map { |user| user['id'] }
expect(response_user_ids).to contain_exactly(*other_user_ids)
expect(response_user_ids).to contain_exactly(non_member.id)
end
end
end
......@@ -249,17 +239,15 @@ describe AutocompleteController do
get(:projects, project_id: project.id)
end
let(:body) { JSON.parse(response.body) }
it 'returns projects' do
expect(json_response).to be_kind_of(Array)
expect(json_response.size).to eq(2)
it do
expect(body).to be_kind_of(Array)
expect(body.size).to eq 2
expect(json_response.first['id']).to eq(0)
expect(json_response.first['name_with_namespace']).to eq 'No project'
expect(body.first['id']).to eq 0
expect(body.first['name_with_namespace']).to eq 'No project'
expect(body.last['id']).to eq authorized_project.id
expect(body.last['name_with_namespace']).to eq authorized_project.name_with_namespace
expect(json_response.last['id']).to eq authorized_project.id
expect(json_response.last['name_with_namespace']).to eq authorized_project.name_with_namespace
end
end
end
......@@ -275,14 +263,12 @@ describe AutocompleteController do
get(:projects, project_id: project.id, search: 'rugged')
end
let(:body) { JSON.parse(response.body) }
it do
expect(body).to be_kind_of(Array)
expect(body.size).to eq 2
it 'returns projects' do
expect(json_response).to be_kind_of(Array)
expect(json_response.size).to eq(2)
expect(body.last['id']).to eq authorized_search_project.id
expect(body.last['name_with_namespace']).to eq authorized_search_project.name_with_namespace
expect(json_response.last['id']).to eq authorized_search_project.id
expect(json_response.last['name_with_namespace']).to eq authorized_search_project.name_with_namespace
end
end
end
......@@ -304,11 +290,9 @@ describe AutocompleteController do
get(:projects, project_id: project.id)
end
let(:body) { JSON.parse(response.body) }
it do
expect(body).to be_kind_of(Array)
expect(body.size).to eq 3 # Of a total of 4
it 'returns projects' do
expect(json_response).to be_kind_of(Array)
expect(json_response.size).to eq 3 # Of a total of 4
end
end
end
......@@ -328,11 +312,9 @@ describe AutocompleteController do
get(:projects, project_id: project.id, offset_id: authorized_project.id)
end
let(:body) { JSON.parse(response.body) }
it do
expect(body.detect { |item| item['id'] == 0 }).to be_nil # 'No project' is not there
expect(body.detect { |item| item['id'] == authorized_project.id }).to be_nil # Offset project is not there either
it 'returns "No project"' do
expect(json_response.detect { |item| item['id'] == 0 }).to be_nil # 'No project' is not there
expect(json_response.detect { |item| item['id'] == authorized_project.id }).to be_nil # Offset project is not there either
end
end
end
......@@ -349,13 +331,10 @@ describe AutocompleteController do
get(:projects, project_id: project.id)
end
let(:body) { JSON.parse(response.body) }
it do
expect(body).to be_kind_of(Array)
expect(body.size).to eq 1 # 'No project'
expect(body.first['id']).to eq 0
it 'returns a single "No project"' do
expect(json_response).to be_kind_of(Array)
expect(json_response.size).to eq(1) # 'No project'
expect(json_response.first['id']).to eq 0
end
end
end
......
......@@ -24,7 +24,7 @@ describe InvitesController do
describe 'GET #decline' do
it 'declines user' do
get :decline, id: token
expect{member.reload}.to raise_error ActiveRecord::RecordNotFound
expect {member.reload}.to raise_error ActiveRecord::RecordNotFound
expect(response).to have_http_status(302)
expect(flash[:notice]).to include 'You have declined the invitation to join'
......
......@@ -292,13 +292,13 @@ describe Projects::IssuesController do
it 'rejects an issue recognized as a spam' do
expect(Gitlab::Recaptcha).to receive(:load_configurations!).and_return(true)
expect { update_spam_issue }.not_to change{ issue.reload.title }
expect { update_spam_issue }.not_to change { issue.reload.title }
end
it 'rejects an issue recognized as a spam when recaptcha disabled' do
stub_application_setting(recaptcha_enabled: false)
expect { update_spam_issue }.not_to change{ issue.reload.title }
expect { update_spam_issue }.not_to change { issue.reload.title }
end
it 'creates a spam log' do
......@@ -358,7 +358,7 @@ describe Projects::IssuesController do
end
it 'accepts an issue after recaptcha is verified' do
expect{ update_verified_issue }.to change{ issue.reload.title }.to(spammy_title)
expect { update_verified_issue }.to change { issue.reload.title }.to(spammy_title)
end
it 'marks spam log as recaptcha_verified' do
......
......@@ -67,7 +67,7 @@ describe Projects::TodosController do
end
it "doesn't create todo" do
expect{ go }.not_to change { user.todos.count }
expect { go }.not_to change { user.todos.count }
expect(response).to have_http_status(404)
end
end
......@@ -135,7 +135,7 @@ describe Projects::TodosController do
end
it "doesn't create todo" do
expect{ go }.not_to change { user.todos.count }
expect { go }.not_to change { user.todos.count }
expect(response).to have_http_status(404)
end
end
......
......@@ -15,7 +15,7 @@ describe RegistrationsController do
it 'signs the user in' do
allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(false)
expect { post(:create, user_params) }.not_to change{ ActionMailer::Base.deliveries.size }
expect { post(:create, user_params) }.not_to change { ActionMailer::Base.deliveries.size }
expect(subject.current_user).not_to be_nil
end
end
......
......@@ -138,7 +138,7 @@ describe Snippets::NotesController do
end
it "deletes the note" do
expect{ delete :destroy, request_params }.to change{ Note.count }.from(1).to(0)
expect { delete :destroy, request_params }.to change { Note.count }.from(1).to(0)
end
context 'system note' do
......@@ -147,7 +147,7 @@ describe Snippets::NotesController do
end
it "does not delete the note" do
expect{ delete :destroy, request_params }.not_to change{ Note.count }
expect { delete :destroy, request_params }.not_to change { Note.count }
end
end
end
......@@ -166,7 +166,7 @@ describe Snippets::NotesController do
end
it "does not update the note" do
expect{ delete :destroy, request_params }.not_to change{ Note.count }
expect { delete :destroy, request_params }.not_to change { Note.count }
end
end
end
......
......@@ -8,12 +8,47 @@ FactoryGirl.define do
factory :project, class: 'Project' do
sequence(:name) { |n| "project#{n}" }
path { name.downcase.gsub(/\s/, '_') }
namespace
creator
# Behaves differently to nil due to cache_has_external_issue_tracker
has_external_issue_tracker false
# Associations
namespace
creator { group ? create(:user) : namespace&.owner }
# Nest Project Feature attributes
transient do
wiki_access_level ProjectFeature::ENABLED
builds_access_level ProjectFeature::ENABLED
snippets_access_level ProjectFeature::ENABLED
issues_access_level ProjectFeature::ENABLED
merge_requests_access_level ProjectFeature::ENABLED
repository_access_level ProjectFeature::ENABLED
end
after(:create) do |project, evaluator|
# Builds and MRs can't have higher visibility level than repository access level.
builds_access_level = [evaluator.builds_access_level, evaluator.repository_access_level].min
merge_requests_access_level = [evaluator.merge_requests_access_level, evaluator.repository_access_level].min
project.project_feature.update_columns(
wiki_access_level: evaluator.wiki_access_level,
builds_access_level: builds_access_level,
snippets_access_level: evaluator.snippets_access_level,
issues_access_level: evaluator.issues_access_level,
merge_requests_access_level: merge_requests_access_level,
repository_access_level: evaluator.repository_access_level)
# Normally the class Projects::CreateService is used for creating
# projects, and this class takes care of making sure the owner and current
# user have access to the project. Our specs don't use said service class,
# thus we must manually refresh things here.
unless project.group || project.pending_delete
project.add_master(project.owner)
end
project.group&.refresh_members_authorized_projects
end
trait :public do
visibility_level Gitlab::VisibilityLevel::PUBLIC
end
......@@ -67,30 +102,28 @@ FactoryGirl.define do
test_repo
transient do
create_template nil
create_templates nil
end
after :create do |project, evaluator|
if evaluator.create_template
args = evaluator.create_template
project.add_user(args[:user], args[:access])
if evaluator.create_templates
templates_path = "#{evaluator.create_templates}_templates"
project.repository.create_file(
args[:user],
".gitlab/#{args[:path]}/bug.md",
project.creator,
".gitlab/#{templates_path}/bug.md",
'something valid',
message: 'test 3',
branch_name: 'master')
project.repository.create_file(
args[:user],
".gitlab/#{args[:path]}/template_test.md",
project.creator,
".gitlab/#{templates_path}/template_test.md",
'template_test',
message: 'test 1',
branch_name: 'master')
project.repository.create_file(
args[:user],
".gitlab/#{args[:path]}/feature_proposal.md",
project.creator,
".gitlab/#{templates_path}/feature_proposal.md",
'feature_proposal',
message: 'test 2',
branch_name: 'master')
......@@ -142,44 +175,6 @@ FactoryGirl.define do
trait(:repository_enabled) { repository_access_level ProjectFeature::ENABLED }
trait(:repository_disabled) { repository_access_level ProjectFeature::DISABLED }
trait(:repository_private) { repository_access_level ProjectFeature::PRIVATE }
# Nest Project Feature attributes
transient do
wiki_access_level ProjectFeature::ENABLED
builds_access_level ProjectFeature::ENABLED
snippets_access_level ProjectFeature::ENABLED
issues_access_level ProjectFeature::ENABLED
merge_requests_access_level ProjectFeature::ENABLED
repository_access_level ProjectFeature::ENABLED
end
after(:create) do |project, evaluator|
# Builds and MRs can't have higher visibility level than repository access level.
builds_access_level = [evaluator.builds_access_level, evaluator.repository_access_level].min
merge_requests_access_level = [evaluator.merge_requests_access_level, evaluator.repository_access_level].min
project.project_feature
.update_attributes!(
wiki_access_level: evaluator.wiki_access_level,
builds_access_level: builds_access_level,
snippets_access_level: evaluator.snippets_access_level,
issues_access_level: evaluator.issues_access_level,
merge_requests_access_level: merge_requests_access_level,
repository_access_level: evaluator.repository_access_level
)
# Normally the class Projects::CreateService is used for creating
# projects, and this class takes care of making sure the owner and current
# user have access to the project. Our specs don't use said service class,
# thus we must manually refresh things here.
owner = project.owner
if owner && owner.is_a?(User) && !project.pending_delete
project.members.create!(user: owner, access_level: Gitlab::Access::MASTER)
end
project.group&.refresh_members_authorized_projects
end
end
# Project with empty repository
......
FactoryGirl.define do
factory :user, aliases: [:author, :assignee, :recipient, :owner, :creator, :resource_owner] do
factory :user, aliases: [:author, :assignee, :recipient, :owner, :resource_owner] do
email { generate(:email) }
name { generate(:name) }
username { generate(:username) }
......@@ -8,6 +8,10 @@ FactoryGirl.define do
confirmation_token { nil }
can_create_group true
after(:stub) do |user|
user.notification_email = user.email
end
before(:create) do |user|
user.ensure_rss_token
end
......
......@@ -16,7 +16,7 @@ feature 'Password reset' do
user.send_reset_password_instructions
user.update_attribute(:reset_password_sent_at, 5.minutes.ago)
expect{ forgot_password(user) }.to change{ user.reset_password_sent_at }
expect { forgot_password(user) }.to change { user.reset_password_sent_at }
expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions'))
expect(current_path).to eq new_user_session_path
end
......@@ -27,7 +27,7 @@ feature 'Password reset' do
# Reload because PG handles datetime less precisely than Ruby/Rails
user.reload
expect{ forgot_password(user) }.not_to change{ user.reset_password_sent_at }
expect { forgot_password(user) }.not_to change { user.reset_password_sent_at }
expect(page).to have_content(I18n.t('devise.passwords.send_paranoid_instructions'))
expect(current_path).to eq new_user_session_path
end
......
......@@ -104,7 +104,7 @@ feature 'Users', js: true do
end
def errors_on_page(page)
page.find('#error_explanation').find('ul').all('li').map{ |item| item.text }.join("\n")
page.find('#error_explanation').find('ul').all('li').map { |item| item.text }.join("\n")
end
def number_of_errors_on_page(page)
......
import bp, {
breakpoints,
} from '~/breakpoints';
describe('breakpoints', () => {
Object.keys(breakpoints).forEach((key) => {
const size = breakpoints[key];
it(`returns ${key} when larger than ${size}`, () => {
spyOn(bp, 'windowWidth').and.returnValue(size + 10);
expect(bp.getBreakpointSize()).toBe(key);
});
});
});
/* global bp */
import Cookies from 'js-cookie';
import {
calculateTop,
......@@ -7,6 +6,7 @@ import {
canShowSubItems,
canShowActiveSubItems,
} from '~/fly_out_nav';
import bp from '~/breakpoints';
describe('Fly out sidebar navigation', () => {
let el;
......@@ -59,7 +59,7 @@ describe('Fly out sidebar navigation', () => {
expect(
el.querySelector('.sidebar-sub-level-items').style.display,
).toBe('none');
).toBe('');
});
it('does not hude subitems on mobile', () => {
......
......@@ -15,7 +15,7 @@ describe Bitbucket::Paginator do
expect(paginator.items).to match(['item_2'])
allow(paginator).to receive(:fetch_next_page).and_return(nil)
expect{ paginator.items }.to raise_error(StopIteration)
expect { paginator.items }.to raise_error(StopIteration)
end
end
end
......@@ -1323,11 +1323,11 @@ EOT
describe "Error handling" do
it "fails to parse YAML" do
expect{GitlabCiYamlProcessor.new("invalid: yaml: test")}.to raise_error(Psych::SyntaxError)
expect {GitlabCiYamlProcessor.new("invalid: yaml: test")}.to raise_error(Psych::SyntaxError)
end
it "indicates that object is invalid" do
expect{GitlabCiYamlProcessor.new("invalid_yaml")}.to raise_error(GitlabCiYamlProcessor::ValidationError)
expect {GitlabCiYamlProcessor.new("invalid_yaml")}.to raise_error(GitlabCiYamlProcessor::ValidationError)
end
it "returns errors if tags parameter is invalid" do
......
......@@ -56,7 +56,7 @@ describe ExtractsPath do
context 'subclass overrides get_id' do
it 'uses ref returned by get_id' do
allow_any_instance_of(self.class).to receive(:get_id){ '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' }
allow_any_instance_of(self.class).to receive(:get_id) { '38008cb17ce1466d8fec2dfa6f6ab8dcfe5cf49e' }
assign_ref_vars
......
......@@ -15,7 +15,7 @@ describe Gitlab::BitbucketImport::ProjectCreator do
has_wiki?: false)
end
let(:namespace){ create(:group, owner: user) }
let(:namespace) { create(:group, owner: user) }
let(:token) { "asdasd12345" }
let(:secret) { "sekrettt" }
let(:access_params) { { bitbucket_access_token: token, bitbucket_access_token_secret: secret } }
......
......@@ -422,11 +422,11 @@ describe Gitlab::Git::Repository, seed_helper: true do
it "should fail if we create an existing branch" do
@repo.create_branch('duplicated_branch', 'master')
expect{@repo.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists")
expect {@repo.create_branch('duplicated_branch', 'master')}.to raise_error("Branch duplicated_branch already exists")
end
it "should fail if we create a branch from a non existing ref" do
expect{@repo.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge")
expect {@repo.create_branch('branch_based_in_wrong_ref', 'master_2_the_revenge')}.to raise_error("Invalid reference master_2_the_revenge")
end
after(:all) do
......
require 'spec_helper'
describe Gitlab::GitalyClient::RepositoryService do
set(:project) { create(:project) }
let(:storage_name) { project.repository_storage }
let(:relative_path) { project.path_with_namespace + '.git' }
let(:client) { described_class.new(project.repository) }
describe '#exists?' do
it 'sends an exists message' do
expect_any_instance_of(Gitaly::RepositoryService::Stub)
.to receive(:exists)
.with(gitaly_request_with_path(storage_name, relative_path), kind_of(Hash))
.and_call_original
client.exists?
end
end
end
......@@ -12,7 +12,7 @@ describe Gitlab::GitlabImport::ProjectCreator do
owner: { name: "john" }
}.with_indifferent_access
end
let(:namespace){ create(:group, owner: user) }
let(:namespace) { create(:group, owner: user) }
let(:token) { "asdffg" }
let(:access_params) { { gitlab_access_token: token } }
......
......@@ -9,7 +9,7 @@ describe Gitlab::GoogleCodeImport::ProjectCreator do
"repositoryUrls" => ["https://vim.googlecode.com/git/"]
)
end
let(:namespace){ create(:group, owner: user) }
let(:namespace) { create(:group, owner: user) }
before do
namespace.add_owner(user)
......
require 'spec_helper'
describe Gitlab::ImportExport::AttributeCleaner do
let(:relation_class){ double('relation_class').as_null_object }
let(:relation_class) { double('relation_class').as_null_object }
let(:unsafe_hash) do
{
'id' => 101,
......
......@@ -5,6 +5,7 @@ describe Gitlab::ImportExport::FileImporter do
let(:export_path) { "#{Dir.tmpdir}/file_importer_spec" }
let(:valid_file) { "#{shared.export_path}/valid.json" }
let(:symlink_file) { "#{shared.export_path}/invalid.json" }
let(:hidden_symlink_file) { "#{shared.export_path}/.hidden" }
let(:subfolder_symlink_file) { "#{shared.export_path}/subfolder/invalid.json" }
before do
......@@ -25,6 +26,10 @@ describe Gitlab::ImportExport::FileImporter do
expect(File.exist?(symlink_file)).to be false
end
it 'removes hidden symlinks in root folder' do
expect(File.exist?(hidden_symlink_file)).to be false
end
it 'removes symlinks in subfolders' do
expect(File.exist?(subfolder_symlink_file)).to be false
end
......
......@@ -7,7 +7,7 @@ describe Gitlab::LDAP::Config do
describe '#initialize' do
it 'requires a provider' do
expect{ described_class.new }.to raise_error ArgumentError
expect { described_class.new }.to raise_error ArgumentError
end
it 'works' do
......@@ -15,7 +15,7 @@ describe Gitlab::LDAP::Config do
end
it 'raises an error if a unknown provider is used' do
expect{ described_class.new 'unknown' }.to raise_error(RuntimeError)
expect { described_class.new 'unknown' }.to raise_error(RuntimeError)
end
end
......
......@@ -61,12 +61,12 @@ describe Gitlab::LDAP::User do
it "finds the user if already existing" do
create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
expect{ ldap_user.save }.not_to change{ User.count }
expect { ldap_user.save }.not_to change { User.count }
end
it "connects to existing non-ldap user if the email matches" do
existing_user = create(:omniauth_user, email: 'john@example.com', provider: "twitter")
expect{ ldap_user.save }.not_to change{ User.count }
expect { ldap_user.save }.not_to change { User.count }
existing_user.reload
expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
......@@ -75,7 +75,7 @@ describe Gitlab::LDAP::User do
it 'connects to existing ldap user if the extern_uid changes' do
existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain')
expect{ ldap_user.save }.not_to change{ User.count }
expect { ldap_user.save }.not_to change { User.count }
existing_user.reload
expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
......@@ -85,7 +85,7 @@ describe Gitlab::LDAP::User do
it 'connects to existing ldap user if the extern_uid changes and email address has upper case characters' do
existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'old-uid', provider: 'ldapmain')
expect{ ldap_user_upper_case.save }.not_to change{ User.count }
expect { ldap_user_upper_case.save }.not_to change { User.count }
existing_user.reload
expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
......@@ -106,7 +106,7 @@ describe Gitlab::LDAP::User do
end
it "creates a new user if not found" do
expect{ ldap_user.save }.to change{ User.count }.by(1)
expect { ldap_user.save }.to change { User.count }.by(1)
end
context 'when signup is disabled' do
......
......@@ -147,7 +147,7 @@ describe Gitlab::OAuth::User do
end
it 'throws an error' do
expect{ oauth_user.save }.to raise_error StandardError
expect { oauth_user.save }.to raise_error StandardError
end
end
......@@ -157,7 +157,7 @@ describe Gitlab::OAuth::User do
end
it 'throws an error' do
expect{ oauth_user.save }.to raise_error StandardError
expect { oauth_user.save }.to raise_error StandardError
end
end
end
......
......@@ -109,7 +109,7 @@ describe Gitlab::Saml::User do
end
it 'does not throw an error' do
expect{ saml_user.save }.not_to raise_error
expect { saml_user.save }.not_to raise_error
end
end
......@@ -119,7 +119,7 @@ describe Gitlab::Saml::User do
end
it 'throws an error' do
expect{ saml_user.save }.to raise_error StandardError
expect { saml_user.save }.to raise_error StandardError
end
end
end
......
......@@ -19,7 +19,7 @@ describe Gitlab::SQL::Union do
empty_relation = User.none
union = described_class.new([empty_relation, relation_1, relation_2])
expect{User.where("users.id IN (#{union.to_sql})").to_a}.not_to raise_error
expect {User.where("users.id IN (#{union.to_sql})").to_a}.not_to raise_error
expect(union.to_sql).to eq("#{to_sql(relation_1)}\nUNION\n#{to_sql(relation_2)}")
end
end
......
require 'spec_helper'
describe Gitlab::Template::IssueTemplate do
subject { described_class }
let(:user) { create(:user) }
let(:project) do
create(:project,
:repository,
create_template: {
user: user,
access: Gitlab::Access::MASTER,
path: 'issue_templates'
})
end
let(:project) { create(:project, :repository, create_templates: :issue) }
describe '.all' do
it 'strips the md suffix' do
expect(subject.all(project).first.name).not_to end_with('.issue_template')
expect(described_class.all(project).first.name).not_to end_with('.issue_template')
end
it 'combines the globals and rest' do
all = subject.all(project).map(&:name)
all = described_class.all(project).map(&:name)
expect(all).to include('bug')
expect(all).to include('feature_proposal')
expect(all).to include('template_test')
end
end
describe '.find' do
it 'returns nil if the file does not exist' do
expect { subject.find('mepmep-yadida', project) }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
expect { described_class.find('mepmep-yadida', project) }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
it 'returns the issue object of a valid file' do
ruby = subject.find('bug', project)
ruby = described_class.find('bug', project)
expect(ruby).to be_a described_class
expect(ruby.name).to eq('bug')
......@@ -44,21 +31,17 @@ describe Gitlab::Template::IssueTemplate do
describe '.by_category' do
it 'return array of templates' do
all = subject.by_category('', project).map(&:name)
all = described_class.by_category('', project).map(&:name)
expect(all).to include('bug')
expect(all).to include('feature_proposal')
expect(all).to include('template_test')
end
context 'when repo is bare or empty' do
let(:empty_project) { create(:project) }
before do
empty_project.add_user(user, Gitlab::Access::MASTER)
end
it "returns empty array" do
templates = subject.by_category('', empty_project)
templates = described_class.by_category('', empty_project)
expect(templates).to be_empty
end
end
......@@ -66,26 +49,23 @@ describe Gitlab::Template::IssueTemplate do
describe '#content' do
it 'loads the full file' do
issue_template = subject.new('.gitlab/issue_templates/bug.md', project)
issue_template = described_class.new('.gitlab/issue_templates/bug.md', project)
expect(issue_template.name).to eq 'bug'
expect(issue_template.content).to eq('something valid')
end
it 'raises error when file is not found' do
issue_template = subject.new('.gitlab/issue_templates/bugnot.md', project)
issue_template = described_class.new('.gitlab/issue_templates/bugnot.md', project)
expect { issue_template.content }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
context "when repo is empty" do
let(:empty_project) { create(:project) }
before do
empty_project.add_user(user, Gitlab::Access::MASTER)
end
it "raises file not found" do
issue_template = subject.new('.gitlab/issue_templates/not_existent.md', empty_project)
issue_template = described_class.new('.gitlab/issue_templates/not_existent.md', empty_project)
expect { issue_template.content }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
end
......
require 'spec_helper'
describe Gitlab::Template::MergeRequestTemplate do
subject { described_class }
let(:user) { create(:user) }
let(:project) do
create(:project,
:repository,
create_template: {
user: user,
access: Gitlab::Access::MASTER,
path: 'merge_request_templates'
})
end
let(:project) { create(:project, :repository, create_templates: :merge_request) }
describe '.all' do
it 'strips the md suffix' do
expect(subject.all(project).first.name).not_to end_with('.issue_template')
expect(described_class.all(project).first.name).not_to end_with('.issue_template')
end
it 'combines the globals and rest' do
all = subject.all(project).map(&:name)
all = described_class.all(project).map(&:name)
expect(all).to include('bug')
expect(all).to include('feature_proposal')
expect(all).to include('template_test')
end
end
describe '.find' do
it 'returns nil if the file does not exist' do
expect { subject.find('mepmep-yadida', project) }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
expect { described_class.find('mepmep-yadida', project) }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
it 'returns the merge request object of a valid file' do
ruby = subject.find('bug', project)
ruby = described_class.find('bug', project)
expect(ruby).to be_a described_class
expect(ruby.name).to eq('bug')
......@@ -44,21 +31,17 @@ describe Gitlab::Template::MergeRequestTemplate do
describe '.by_category' do
it 'return array of templates' do
all = subject.by_category('', project).map(&:name)
all = described_class.by_category('', project).map(&:name)
expect(all).to include('bug')
expect(all).to include('feature_proposal')
expect(all).to include('template_test')
end
context 'when repo is bare or empty' do
let(:empty_project) { create(:project) }
before do
empty_project.add_user(user, Gitlab::Access::MASTER)
end
it "returns empty array" do
templates = subject.by_category('', empty_project)
templates = described_class.by_category('', empty_project)
expect(templates).to be_empty
end
end
......@@ -66,26 +49,23 @@ describe Gitlab::Template::MergeRequestTemplate do
describe '#content' do
it 'loads the full file' do
issue_template = subject.new('.gitlab/merge_request_templates/bug.md', project)
issue_template = described_class.new('.gitlab/merge_request_templates/bug.md', project)
expect(issue_template.name).to eq 'bug'
expect(issue_template.content).to eq('something valid')
end
it 'raises error when file is not found' do
issue_template = subject.new('.gitlab/merge_request_templates/bugnot.md', project)
issue_template = described_class.new('.gitlab/merge_request_templates/bugnot.md', project)
expect { issue_template.content }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
context "when repo is empty" do
let(:empty_project) { create(:project) }
before do
empty_project.add_user(user, Gitlab::Access::MASTER)
end
it "raises file not found" do
issue_template = subject.new('.gitlab/merge_request_templates/not_existent.md', empty_project)
issue_template = described_class.new('.gitlab/merge_request_templates/not_existent.md', empty_project)
expect { issue_template.content }.to raise_error(Gitlab::Template::Finders::RepoTemplateFinder::FileNotFoundError)
end
end
......
......@@ -20,6 +20,34 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://gitlab.com:25/foo/foo.git')).to be true
end
it 'returns true for a non-alphanumeric hostname' do
stub_resolv
aggregate_failures do
expect(described_class).to be_blocked_url('ssh://-oProxyCommand=whoami/a')
# The leading character here is a Unicode "soft hyphen"
expect(described_class).to be_blocked_url('ssh://­oProxyCommand=whoami/a')
# Unicode alphanumerics are allowed
expect(described_class).not_to be_blocked_url('ssh://ğitlab.com/a')
end
end
it 'returns true for a non-alphanumeric username' do
stub_resolv
aggregate_failures do
expect(described_class).to be_blocked_url('ssh://-oProxyCommand=whoami@example.com/a')
# The leading character here is a Unicode "soft hyphen"
expect(described_class).to be_blocked_url('ssh://­oProxyCommand=whoami@example.com/a')
# Unicode alphanumerics are allowed
expect(described_class).not_to be_blocked_url('ssh://ğitlab@example.com/a')
end
end
it 'returns true for invalid URL' do
expect(described_class.blocked_url?('http://:8080')).to be true
end
......@@ -28,4 +56,10 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git')).to be false
end
end
# Resolv does not support resolving UTF-8 domain names
# See https://bugs.ruby-lang.org/issues/4270
def stub_resolv
allow(Resolv).to receive(:getaddresses).and_return([])
end
end
......@@ -50,8 +50,8 @@ describe 'Gitlab::VersionInfo' do
context 'unknown' do
it { expect(@unknown).not_to be @v0_0_1 }
it { expect(@unknown).not_to be Gitlab::VersionInfo.new }
it { expect{@unknown > @v0_0_1}.to raise_error(ArgumentError) }
it { expect{@unknown < @v0_0_1}.to raise_error(ArgumentError) }
it { expect {@unknown > @v0_0_1}.to raise_error(ArgumentError) }
it { expect {@unknown < @v0_0_1}.to raise_error(ArgumentError) }
end
context 'parse' do
......
......@@ -27,7 +27,7 @@ describe JSONWebToken::RSAToken do
subject { JWT.decode(rsa_encoded, rsa_key) }
it { expect{subject}.not_to raise_error }
it { expect {subject}.not_to raise_error }
it { expect(subject.first).to include('key' => 'value') }
it do
expect(subject.second).to eq(
......@@ -41,7 +41,7 @@ describe JSONWebToken::RSAToken do
let(:new_key) { OpenSSL::PKey::RSA.generate(512) }
subject { JWT.decode(rsa_encoded, new_key) }
it { expect{subject}.to raise_error(JWT::DecodeError) }
it { expect {subject}.to raise_error(JWT::DecodeError) }
end
end
end
......@@ -240,7 +240,7 @@ describe SystemCheck::SimpleExecutor do
context 'when there is an exception' do
it 'rescues the exception' do
expect{ subject.run_check(BugousCheck) }.not_to raise_exception
expect { subject.run_check(BugousCheck) }.not_to raise_exception
end
end
end
......
......@@ -425,7 +425,7 @@ describe CommitStatus do
end
it "raise exception when trying to update" do
expect{ commit_status.save }.to raise_error(ActiveRecord::StaleObjectError)
expect { commit_status.save }.to raise_error(ActiveRecord::StaleObjectError)
end
end
......
......@@ -350,7 +350,7 @@ describe Issue do
subject { create(:issue, project: create(:project, :repository)) }
let(:backref_text) { "issue #{subject.to_reference}" }
let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
let(:set_mentionable_text) { ->(txt) { subject.description = txt } }
end
it_behaves_like 'a Taskable' do
......
......@@ -681,7 +681,7 @@ describe MergeRequest do
end
it 'does not crash' do
expect{ subject.diverged_commits_count }.not_to raise_error
expect { subject.diverged_commits_count }.not_to raise_error
end
it 'returns 0' do
......@@ -753,7 +753,7 @@ describe MergeRequest do
subject { create(:merge_request, :simple) }
let(:backref_text) { "merge request #{subject.to_reference}" }
let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
let(:set_mentionable_text) { ->(txt) { subject.description = txt } }
end
it_behaves_like 'a Taskable' do
......
......@@ -1301,7 +1301,7 @@ describe Project do
subject { project.rename_repo }
it { expect{subject}.to raise_error(StandardError) }
it { expect {subject}.to raise_error(StandardError) }
end
end
......
......@@ -20,8 +20,16 @@ describe RedirectRoute do
let!(:redirect4) { group.redirect_routes.create(path: 'gitlabb/test/foo/bar') }
let!(:redirect5) { group.redirect_routes.create(path: 'gitlabb/test/baz') }
context 'when the redirect route matches with same casing' do
it 'returns correct routes' do
expect(described_class.matching_path_and_descendants('gitlabb/test')).to match_array([redirect2, redirect3, redirect4, redirect5])
end
end
context 'when the redirect route matches with different casing' do
it 'returns correct routes' do
expect(described_class.matching_path_and_descendants('GitLABB/test')).to match_array([redirect2, redirect3, redirect4, redirect5])
end
end
end
end
......@@ -1224,7 +1224,7 @@ describe Repository, models: true do
end
describe 'skip_merges option' do
subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map{ |k| k.id } }
subject { repository.commits(Gitlab::Git::BRANCH_REF_PREFIX + "'test'", limit: 100, skip_merges: true).map { |k| k.id } }
it { is_expected.not_to include('e56497bb5f03a90a51293fc6d516788730953899') }
end
......
......@@ -145,11 +145,13 @@ describe Route do
describe '#delete_conflicting_redirects' do
context 'when a redirect route with the same path exists' do
context 'when the redirect route has matching case' do
let!(:redirect1) { route.create_redirect(route.path) }
it 'deletes the redirect' do
expect do
route.delete_conflicting_redirects
expect(route.conflicting_redirects).to be_empty
end.to change { RedirectRoute.count }.by(-1)
end
context 'when redirect routes with paths descending from the route path exists' do
......@@ -159,19 +161,35 @@ describe Route do
let!(:other_redirect) { route.create_redirect("other") }
it 'deletes all redirects with paths that descend from the route path' do
expect do
route.delete_conflicting_redirects
expect(route.conflicting_redirects).to be_empty
end.to change { RedirectRoute.count }.by(-4)
end
end
end
context 'when the redirect route is differently cased' do
let!(:redirect1) { route.create_redirect(route.path.upcase) }
it 'deletes the redirect' do
expect do
route.delete_conflicting_redirects
end.to change { RedirectRoute.count }.by(-1)
end
end
end
end
describe '#conflicting_redirects' do
it 'returns an ActiveRecord::Relation' do
expect(route.conflicting_redirects).to be_an(ActiveRecord::Relation)
end
context 'when a redirect route with the same path exists' do
context 'when the redirect route has matching case' do
let!(:redirect1) { route.create_redirect(route.path) }
it 'returns the redirect route' do
expect(route.conflicting_redirects).to be_an(ActiveRecord::Relation)
expect(route.conflicting_redirects).to match_array([redirect1])
end
......@@ -182,10 +200,18 @@ describe Route do
let!(:other_redirect) { route.create_redirect("other") }
it 'returns the redirect routes' do
expect(route.conflicting_redirects).to be_an(ActiveRecord::Relation)
expect(route.conflicting_redirects).to match_array([redirect1, redirect2, redirect3, redirect4])
end
end
end
context 'when the redirect route is differently cased' do
let!(:redirect1) { route.create_redirect(route.path.upcase) }
it 'returns the redirect route' do
expect(route.conflicting_redirects).to match_array([redirect1])
end
end
end
end
end
......@@ -118,6 +118,17 @@ describe User do
expect(user).to validate_uniqueness_of(:username).case_insensitive
end
context 'when username is changed' do
let(:user) { build_stubbed(:user, username: 'old_path', namespace: build_stubbed(:namespace)) }
it 'validates move_dir is allowed for the namespace' do
expect(user.namespace).to receive(:any_project_has_container_registry_tags?).and_return(true)
user.username = 'new_path'
expect(user).to be_invalid
expect(user.errors.messages[:username].first).to match('cannot be changed if a personal project has container registry tags')
end
end
end
it { is_expected.to validate_presence_of(:projects_limit) }
......
......@@ -44,8 +44,8 @@ describe API::CommitStatuses do
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(statuses_id).to contain_exactly(status3.id, status4.id, status5.id, status6.id)
json_response.sort_by!{ |status| status['id'] }
expect(json_response.map{ |status| status['allow_failure'] }).to eq([true, false, false, false])
json_response.sort_by! { |status| status['id'] }
expect(json_response.map { |status| status['allow_failure'] }).to eq([true, false, false, false])
end
end
......
......@@ -182,7 +182,7 @@ describe API::DeployKeys do
delete api("/projects/#{project.id}/deploy_keys/#{deploy_key.id}", admin)
expect(response).to have_http_status(204)
end.to change{ project.deploy_keys.count }.by(-1)
end.to change { project.deploy_keys.count }.by(-1)
end
it 'returns 404 Not Found with invalid ID' do
......
......@@ -88,7 +88,7 @@ describe API::GroupVariables do
it 'creates variable' do
expect do
post api("/groups/#{group.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true
end.to change{group.variables.count}.by(1)
end.to change {group.variables.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
......@@ -99,7 +99,7 @@ describe API::GroupVariables do
it 'creates variable with optional attributes' do
expect do
post api("/groups/#{group.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2'
end.to change{group.variables.count}.by(1)
end.to change {group.variables.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
......@@ -110,7 +110,7 @@ describe API::GroupVariables do
it 'does not allow to duplicate variable key' do
expect do
post api("/groups/#{group.id}/variables", user), key: variable.key, value: 'VALUE_2'
end.to change{group.variables.count}.by(0)
end.to change {group.variables.count}.by(0)
expect(response).to have_http_status(400)
end
......@@ -192,7 +192,7 @@ describe API::GroupVariables do
delete api("/groups/#{group.id}/variables/#{variable.key}", user)
expect(response).to have_http_status(204)
end.to change{group.variables.count}.by(-1)
end.to change {group.variables.count}.by(-1)
end
it 'responds with 404 Not Found if requesting non-existing variable' do
......
......@@ -56,7 +56,7 @@ describe API::Helpers do
end
def doorkeeper_guard_returns(value)
allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ value }
allow_any_instance_of(self.class).to receive(:doorkeeper_guard) { value }
end
def error!(message, status, header)
......@@ -161,7 +161,7 @@ describe API::Helpers do
describe "when authenticating using a user's private token" do
it "returns nil for an invalid token" do
env[API::APIGuard::PRIVATE_TOKEN_HEADER] = 'invalid token'
allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false }
allow_any_instance_of(self.class).to receive(:doorkeeper_guard) { false }
expect(current_user).to be_nil
end
......
......@@ -326,7 +326,7 @@ describe API::MergeRequests do
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
response_dates = json_response.map { |merge_request| merge_request['created_at'] }
expect(response_dates).to eq(response_dates.sort)
end
......@@ -337,7 +337,7 @@ describe API::MergeRequests do
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
response_dates = json_response.map { |merge_request| merge_request['created_at'] }
expect(response_dates).to eq(response_dates.sort.reverse)
end
......@@ -348,7 +348,7 @@ describe API::MergeRequests do
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
response_dates = json_response.map{ |merge_request| merge_request['updated_at'] }
response_dates = json_response.map { |merge_request| merge_request['updated_at'] }
expect(response_dates).to eq(response_dates.sort.reverse)
end
......@@ -359,7 +359,7 @@ describe API::MergeRequests do
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
response_dates = json_response.map { |merge_request| merge_request['created_at'] }
expect(response_dates).to eq(response_dates.sort)
end
end
......
......@@ -53,7 +53,7 @@ describe API::PipelineSchedules do
it 'returns matched pipeline schedules' do
get api("/projects/#{project.id}/pipeline_schedules", developer), scope: target
expect(json_response.map{ |r| r['active'] }).to all(eq(active?(target)))
expect(json_response.map { |r| r['active'] }).to all(eq(active?(target)))
end
end
......
......@@ -8,8 +8,8 @@ describe API::Projects do
let(:user2) { create(:user) }
let(:user3) { create(:user) }
let(:admin) { create(:admin) }
let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
let(:project) { create(:project, namespace: user.namespace) }
let(:project2) { create(:project, path: 'project2', namespace: user.namespace) }
let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') }
let(:project_member) { create(:project_member, :developer, user: user3, project: project) }
let(:user4) { create(:user) }
......
......@@ -683,7 +683,7 @@ describe API::Runner do
end
context 'when job has been updated recently' do
it { expect{ patch_the_trace }.not_to change { job.updated_at }}
it { expect { patch_the_trace }.not_to change { job.updated_at }}
it "changes the job's trace" do
patch_the_trace
......@@ -692,7 +692,7 @@ describe API::Runner do
end
context 'when Runner makes a force-patch' do
it { expect{ force_patch_the_trace }.not_to change { job.updated_at }}
it { expect { force_patch_the_trace }.not_to change { job.updated_at }}
it "doesn't change the build.trace" do
force_patch_the_trace
......
......@@ -36,7 +36,7 @@ describe API::Runners do
it 'returns user available runners' do
get api('/runners', user)
shared = json_response.any?{ |r| r['is_shared'] }
shared = json_response.any? { |r| r['is_shared'] }
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
......@@ -46,7 +46,7 @@ describe API::Runners do
it 'filters runners by scope' do
get api('/runners?scope=active', user)
shared = json_response.any?{ |r| r['is_shared'] }
shared = json_response.any? { |r| r['is_shared'] }
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
......@@ -74,7 +74,7 @@ describe API::Runners do
it 'returns all runners' do
get api('/runners/all', admin)
shared = json_response.any?{ |r| r['is_shared'] }
shared = json_response.any? { |r| r['is_shared'] }
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
......@@ -93,7 +93,7 @@ describe API::Runners do
it 'filters runners by scope' do
get api('/runners/all?scope=specific', admin)
shared = json_response.any?{ |r| r['is_shared'] }
shared = json_response.any? { |r| r['is_shared'] }
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
......@@ -277,7 +277,7 @@ describe API::Runners do
delete api("/runners/#{shared_runner.id}", admin)
expect(response).to have_http_status(204)
end.to change{ Ci::Runner.shared.count }.by(-1)
end.to change { Ci::Runner.shared.count }.by(-1)
end
end
......@@ -287,7 +287,7 @@ describe API::Runners do
delete api("/runners/#{unused_specific_runner.id}", admin)
expect(response).to have_http_status(204)
end.to change{ Ci::Runner.specific.count }.by(-1)
end.to change { Ci::Runner.specific.count }.by(-1)
end
it 'deletes used runner' do
......@@ -295,7 +295,7 @@ describe API::Runners do
delete api("/runners/#{specific_runner.id}", admin)
expect(response).to have_http_status(204)
end.to change{ Ci::Runner.specific.count }.by(-1)
end.to change { Ci::Runner.specific.count }.by(-1)
end
end
......@@ -330,7 +330,7 @@ describe API::Runners do
delete api("/runners/#{specific_runner.id}", user)
expect(response).to have_http_status(204)
end.to change{ Ci::Runner.specific.count }.by(-1)
end.to change { Ci::Runner.specific.count }.by(-1)
end
end
end
......@@ -349,7 +349,7 @@ describe API::Runners do
it "returns project's runners" do
get api("/projects/#{project.id}/runners", user)
shared = json_response.any?{ |r| r['is_shared'] }
shared = json_response.any? { |r| r['is_shared'] }
expect(response).to have_http_status(200)
expect(response).to include_pagination_headers
expect(json_response).to be_an Array
......@@ -385,14 +385,14 @@ describe API::Runners do
it 'enables specific runner' do
expect do
post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
end.to change{ project.runners.count }.by(+1)
end.to change { project.runners.count }.by(+1)
expect(response).to have_http_status(201)
end
it 'avoids changes when enabling already enabled runner' do
expect do
post api("/projects/#{project.id}/runners", user), runner_id: specific_runner.id
end.to change{ project.runners.count }.by(0)
end.to change { project.runners.count }.by(0)
expect(response).to have_http_status(409)
end
......@@ -401,7 +401,7 @@ describe API::Runners do
expect do
post api("/projects/#{project.id}/runners", user), runner_id: specific_runner2.id
end.to change{ project.runners.count }.by(0)
end.to change { project.runners.count }.by(0)
expect(response).to have_http_status(403)
end
......@@ -416,7 +416,7 @@ describe API::Runners do
it 'enables any specific runner' do
expect do
post api("/projects/#{project.id}/runners", admin), runner_id: unused_specific_runner.id
end.to change{ project.runners.count }.by(+1)
end.to change { project.runners.count }.by(+1)
expect(response).to have_http_status(201)
end
end
......@@ -461,7 +461,7 @@ describe API::Runners do
delete api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user)
expect(response).to have_http_status(204)
end.to change{ project.runners.count }.by(-1)
end.to change { project.runners.count }.by(-1)
end
end
......@@ -469,7 +469,7 @@ describe API::Runners do
it "does not disable project's runner" do
expect do
delete api("/projects/#{project.id}/runners/#{specific_runner.id}", user)
end.to change{ project.runners.count }.by(0)
end.to change { project.runners.count }.by(0)
expect(response).to have_http_status(403)
end
end
......
......@@ -52,10 +52,10 @@ describe API::Snippets do
expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
public_snippet.id,
public_snippet_other.id)
expect(json_response.map{ |snippet| snippet['web_url']} ).to include(
expect(json_response.map { |snippet| snippet['web_url']} ).to include(
"http://localhost/snippets/#{public_snippet.id}",
"http://localhost/snippets/#{public_snippet_other.id}")
expect(json_response.map{ |snippet| snippet['raw_url']} ).to include(
expect(json_response.map { |snippet| snippet['raw_url']} ).to include(
"http://localhost/snippets/#{public_snippet.id}/raw",
"http://localhost/snippets/#{public_snippet_other.id}/raw")
end
......
......@@ -185,7 +185,7 @@ describe API::Triggers do
expect do
post api("/projects/#{project.id}/triggers", user),
description: 'trigger'
end.to change{project.triggers.count}.by(1)
end.to change {project.triggers.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response).to include('description' => 'trigger')
......@@ -288,7 +288,7 @@ describe API::Triggers do
delete api("/projects/#{project.id}/triggers/#{trigger.id}", user)
expect(response).to have_http_status(204)
end.to change{project.triggers.count}.by(-1)
end.to change {project.triggers.count}.by(-1)
end
it 'responds with 404 Not Found if requesting non-existing trigger' do
......
......@@ -87,7 +87,7 @@ describe API::V3::DeployKeys do
expect do
post v3_api("/projects/#{project.id}/#{path}", admin), key_attrs
end.to change{ project.deploy_keys.count }.by(1)
end.to change { project.deploy_keys.count }.by(1)
end
it 'returns an existing ssh key when attempting to add a duplicate' do
......@@ -122,7 +122,7 @@ describe API::V3::DeployKeys do
it 'should delete existing key' do
expect do
delete v3_api("/projects/#{project.id}/#{path}/#{deploy_key.id}", admin)
end.to change{ project.deploy_keys.count }.by(-1)
end.to change { project.deploy_keys.count }.by(-1)
end
it 'should return 404 Not Found with invalid ID' do
......
......@@ -90,7 +90,7 @@ describe API::MergeRequests do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
response_dates = json_response.map { |merge_request| merge_request['created_at'] }
expect(response_dates).to eq(response_dates.sort)
end
......@@ -99,7 +99,7 @@ describe API::MergeRequests do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
response_dates = json_response.map { |merge_request| merge_request['created_at'] }
expect(response_dates).to eq(response_dates.sort.reverse)
end
......@@ -108,7 +108,7 @@ describe API::MergeRequests do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
response_dates = json_response.map{ |merge_request| merge_request['updated_at'] }
response_dates = json_response.map { |merge_request| merge_request['updated_at'] }
expect(response_dates).to eq(response_dates.sort.reverse)
end
......@@ -117,7 +117,7 @@ describe API::MergeRequests do
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(3)
response_dates = json_response.map{ |merge_request| merge_request['created_at'] }
response_dates = json_response.map { |merge_request| merge_request['created_at'] }
expect(response_dates).to eq(response_dates.sort)
end
end
......
......@@ -30,7 +30,7 @@ describe API::ProjectSnippets do
expect(response).to have_http_status(200)
expect(json_response.size).to eq(3)
expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
expect(json_response.map { |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
expect(json_response.last).to have_key('web_url')
end
......
......@@ -38,7 +38,7 @@ describe API::V3::Runners do
delete v3_api("/runners/#{shared_runner.id}", admin)
expect(response).to have_http_status(200)
end.to change{ Ci::Runner.shared.count }.by(-1)
end.to change { Ci::Runner.shared.count }.by(-1)
end
end
......@@ -48,7 +48,7 @@ describe API::V3::Runners do
delete v3_api("/runners/#{unused_specific_runner.id}", admin)
expect(response).to have_http_status(200)
end.to change{ Ci::Runner.specific.count }.by(-1)
end.to change { Ci::Runner.specific.count }.by(-1)
end
it 'deletes used runner' do
......@@ -56,7 +56,7 @@ describe API::V3::Runners do
delete v3_api("/runners/#{specific_runner.id}", admin)
expect(response).to have_http_status(200)
end.to change{ Ci::Runner.specific.count }.by(-1)
end.to change { Ci::Runner.specific.count }.by(-1)
end
end
......@@ -91,7 +91,7 @@ describe API::V3::Runners do
delete v3_api("/runners/#{specific_runner.id}", user)
expect(response).to have_http_status(200)
end.to change{ Ci::Runner.specific.count }.by(-1)
end.to change { Ci::Runner.specific.count }.by(-1)
end
end
end
......@@ -113,7 +113,7 @@ describe API::V3::Runners do
delete v3_api("/projects/#{project.id}/runners/#{two_projects_runner.id}", user)
expect(response).to have_http_status(200)
end.to change{ project.runners.count }.by(-1)
end.to change { project.runners.count }.by(-1)
end
end
......@@ -121,7 +121,7 @@ describe API::V3::Runners do
it "does not disable project's runner" do
expect do
delete v3_api("/projects/#{project.id}/runners/#{specific_runner.id}", user)
end.to change{ project.runners.count }.by(0)
end.to change { project.runners.count }.by(0)
expect(response).to have_http_status(403)
end
end
......
......@@ -45,10 +45,10 @@ describe API::V3::Snippets do
expect(json_response.map { |snippet| snippet['id']} ).to contain_exactly(
public_snippet.id,
public_snippet_other.id)
expect(json_response.map{ |snippet| snippet['web_url']} ).to include(
expect(json_response.map { |snippet| snippet['web_url']} ).to include(
"http://localhost/snippets/#{public_snippet.id}",
"http://localhost/snippets/#{public_snippet_other.id}")
expect(json_response.map{ |snippet| snippet['raw_url']} ).to include(
expect(json_response.map { |snippet| snippet['raw_url']} ).to include(
"http://localhost/snippets/#{public_snippet.id}/raw",
"http://localhost/snippets/#{public_snippet_other.id}/raw")
end
......
......@@ -171,7 +171,7 @@ describe API::V3::Triggers do
it 'creates trigger' do
expect do
post v3_api("/projects/#{project.id}/triggers", user)
end.to change{project.triggers.count}.by(1)
end.to change {project.triggers.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response).to be_a(Hash)
......@@ -202,7 +202,7 @@ describe API::V3::Triggers do
delete v3_api("/projects/#{project.id}/triggers/#{trigger.token}", user)
expect(response).to have_http_status(200)
end.to change{project.triggers.count}.by(-1)
end.to change {project.triggers.count}.by(-1)
end
it 'responds with 404 Not Found if requesting non-existing trigger' do
......
......@@ -74,7 +74,7 @@ describe API::Variables do
it 'creates variable' do
expect do
post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2', protected: true
end.to change{project.variables.count}.by(1)
end.to change {project.variables.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
......@@ -85,7 +85,7 @@ describe API::Variables do
it 'creates variable with optional attributes' do
expect do
post api("/projects/#{project.id}/variables", user), key: 'TEST_VARIABLE_2', value: 'VALUE_2'
end.to change{project.variables.count}.by(1)
end.to change {project.variables.count}.by(1)
expect(response).to have_http_status(201)
expect(json_response['key']).to eq('TEST_VARIABLE_2')
......@@ -96,7 +96,7 @@ describe API::Variables do
it 'does not allow to duplicate variable key' do
expect do
post api("/projects/#{project.id}/variables", user), key: variable.key, value: 'VALUE_2'
end.to change{project.variables.count}.by(0)
end.to change {project.variables.count}.by(0)
expect(response).to have_http_status(400)
end
......@@ -166,7 +166,7 @@ describe API::Variables do
delete api("/projects/#{project.id}/variables/#{variable.key}", user)
expect(response).to have_http_status(204)
end.to change{project.variables.count}.by(-1)
end.to change {project.variables.count}.by(-1)
end
it 'responds with 404 Not Found if requesting non-existing variable' do
......
......@@ -421,7 +421,7 @@ describe Ci::API::Builds do
end
context 'when build has been updated recently' do
it { expect{ patch_the_trace }.not_to change { build.updated_at }}
it { expect { patch_the_trace }.not_to change { build.updated_at }}
it 'changes the build trace' do
patch_the_trace
......@@ -430,7 +430,7 @@ describe Ci::API::Builds do
end
context 'when Runner makes a force-patch' do
it { expect{ force_patch_the_trace }.not_to change { build.updated_at }}
it { expect { force_patch_the_trace }.not_to change { build.updated_at }}
it "doesn't change the build.trace" do
force_patch_the_trace
......
......@@ -47,7 +47,7 @@ describe AnalyticsBuildEntity do
let(:finished_at) { nil }
it 'does not blow up' do
expect{ subject[:date] }.not_to raise_error
expect { subject[:date] }.not_to raise_error
end
it 'shows the right message' do
......@@ -63,7 +63,7 @@ describe AnalyticsBuildEntity do
let(:started_at) { nil }
it 'does not blow up' do
expect{ subject[:date] }.not_to raise_error
expect { subject[:date] }.not_to raise_error
end
it 'shows the right message' do
......@@ -79,7 +79,7 @@ describe AnalyticsBuildEntity do
let(:finished_at) { nil }
it 'does not blow up' do
expect{ subject[:date] }.not_to raise_error
expect { subject[:date] }.not_to raise_error
end
it 'shows the right message' do
......
......@@ -111,7 +111,7 @@ describe PipelineSerializer do
shared_examples 'no N+1 queries' do
it 'verifies number of queries', :request_store do
recorded = ActiveRecord::QueryRecorder.new { subject }
expect(recorded.count).to be_within(1).of(59)
expect(recorded.count).to be_within(1).of(57)
expect(recorded.cached_count).to eq(0)
end
end
......
......@@ -132,7 +132,7 @@ describe GitPushService, services: true do
end
it "creates a new pipeline" do
expect{ subject }.to change{ Ci::Pipeline.count }
expect { subject }.to change { Ci::Pipeline.count }
expect(Ci::Pipeline.last).to be_push
end
end
......
......@@ -39,7 +39,7 @@ describe GitTagPushService do
end
it "creates a new pipeline" do
expect{ subject }.to change{ Ci::Pipeline.count }
expect { subject }.to change { Ci::Pipeline.count }
expect(Ci::Pipeline.last).to be_push
end
end
......
......@@ -118,7 +118,7 @@ describe Issuable::BulkUpdateService do
context 'when the new assignee ID is not present' do
it 'does not unassign' do
expect { bulk_update(issue, assignee_ids: []) }
.not_to change{ issue.reload.assignees }
.not_to change { issue.reload.assignees }
end
end
end
......
......@@ -351,14 +351,14 @@ describe Issues::CreateService do
end
it 'marks related spam_log as recaptcha_verified' do
expect { issue }.to change{SpamLog.last.recaptcha_verified}.from(false).to(true)
expect { issue }.to change {SpamLog.last.recaptcha_verified}.from(false).to(true)
end
context 'when spam log does not belong to a user' do
let(:log_user) { create(:user) }
it 'does not mark spam_log as recaptcha_verified' do
expect { issue }.not_to change{SpamLog.last.recaptcha_verified}
expect { issue }.not_to change {SpamLog.last.recaptcha_verified}
end
end
end
......@@ -378,7 +378,7 @@ describe Issues::CreateService do
end
it 'creates a new spam_log' do
expect{issue}.to change{SpamLog.count}.from(0).to(1)
expect {issue}.to change {SpamLog.count}.from(0).to(1)
end
it 'assigns a spam_log to an issue' do
......
......@@ -478,7 +478,7 @@ describe Issues::UpdateService, :mailer do
feature_visibility_attr = :"#{issue.model_name.plural}_access_level"
project.project_feature.update_attribute(feature_visibility_attr, ProjectFeature::PRIVATE)
expect{ update_issue(assignee_ids: [assignee.id]) }.not_to change{ issue.assignees }
expect { update_issue(assignee_ids: [assignee.id]) }.not_to change { issue.assignees }
end
end
end
......
......@@ -509,7 +509,7 @@ describe MergeRequests::UpdateService, :mailer do
feature_visibility_attr = :"#{merge_request.model_name.plural}_access_level"
project.project_feature.update_attribute(feature_visibility_attr, ProjectFeature::PRIVATE)
expect{ update_merge_request(assignee_id: assignee) }.not_to change{ merge_request.assignee }
expect { update_merge_request(assignee_id: assignee) }.not_to change { merge_request.assignee }
end
end
end
......
......@@ -85,7 +85,7 @@ describe NotificationService, :mailer do
it { expect(notification.new_key(key)).to be_truthy }
it 'sends email to key owner' do
expect{ notification.new_key(key) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
expect { notification.new_key(key) }.to change { ActionMailer::Base.deliveries.size }.by(1)
end
end
end
......@@ -97,7 +97,7 @@ describe NotificationService, :mailer do
it { expect(notification.new_gpg_key(key)).to be_truthy }
it 'sends email to key owner' do
expect{ notification.new_gpg_key(key) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
expect { notification.new_gpg_key(key) }.to change { ActionMailer::Base.deliveries.size }.by(1)
end
end
end
......@@ -109,7 +109,7 @@ describe NotificationService, :mailer do
it { expect(notification.new_email(email)).to be_truthy }
it 'sends email to email owner' do
expect{ notification.new_email(email) }.to change{ ActionMailer::Base.deliveries.size }.by(1)
expect { notification.new_email(email) }.to change { ActionMailer::Base.deliveries.size }.by(1)
end
end
end
......
......@@ -19,7 +19,7 @@ describe ProtectedBranches::UpdateService do
let(:user) { create(:user) }
it "raises error" do
expect{ service.execute(protected_branch) }.to raise_error(Gitlab::Access::AccessDeniedError)
expect { service.execute(protected_branch) }.to raise_error(Gitlab::Access::AccessDeniedError)
end
end
end
......
......@@ -19,7 +19,7 @@ describe ProtectedTags::UpdateService do
let(:user) { create(:user) }
it "raises error" do
expect{ service.execute(protected_tag) }.to raise_error(Gitlab::Access::AccessDeniedError)
expect { service.execute(protected_tag) }.to raise_error(Gitlab::Access::AccessDeniedError)
end
end
end
......
......@@ -35,7 +35,7 @@ describe SystemNoteService do
context 'metadata' do
it 'creates a new system note metadata record' do
expect { subject }.to change{ SystemNoteMetadata.count }.from(0).to(1)
expect { subject }.to change { SystemNoteMetadata.count }.from(0).to(1)
end
it 'creates a record correctly' do
......@@ -477,7 +477,7 @@ describe SystemNoteService do
end
it 'does not create a system note metadata record' do
expect { subject }.not_to change{ SystemNoteMetadata.count }
expect { subject }.not_to change { SystemNoteMetadata.count }
end
end
......
......@@ -586,13 +586,13 @@ describe TodoService do
it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is pending' do
create(:todo, :directly_addressed, user: member, project: project, target: addressed_mr_assigned, author: author)
expect{ service.update_merge_request(addressed_mr_assigned, author) }.not_to change(member.todos, :count)
expect { service.update_merge_request(addressed_mr_assigned, author) }.not_to change(member.todos, :count)
end
it 'does not create a directly addressed todo if user was already mentioned or addressed and todo is done' do
create(:todo, :directly_addressed, user: skipped, project: project, target: addressed_mr_assigned, author: author)
expect{ service.update_merge_request(addressed_mr_assigned, author, skip_users) }.not_to change(skipped.todos, :count)
expect { service.update_merge_request(addressed_mr_assigned, author, skip_users) }.not_to change(skipped.todos, :count)
end
context 'with a task list' do
......
......@@ -50,7 +50,7 @@ shared_examples_for 'group and project milestones' do |route_definition|
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
expect(json_response.map{ |m| m['id'] }).to match_array([closed_milestone.id, other_milestone.id])
expect(json_response.map { |m| m['id'] }).to match_array([closed_milestone.id, other_milestone.id])
end
it 'does not return any milestone if none found' do
......
......@@ -28,6 +28,6 @@ RSpec::Matchers.define :make_queries_matching do |matcher, expected_count = nil|
def query_count(regex, &block)
@recorder = ActiveRecord::QueryRecorder.new(&block).log
@recorder.select{ |q| q.match(regex) }
@recorder.select { |q| q.match(regex) }
end
end
......@@ -5,11 +5,15 @@ module RakeHelpers
end
def stub_warn_user_is_not_gitlab
allow_any_instance_of(Object).to receive(:warn_user_is_not_gitlab)
allow(main_object).to receive(:warn_user_is_not_gitlab)
end
def silence_output
allow($stdout).to receive(:puts)
allow($stdout).to receive(:print)
allow(main_object).to receive(:puts)
allow(main_object).to receive(:print)
end
def main_object
@main_object ||= TOPLEVEL_BINDING.eval('self')
end
end
......@@ -2,14 +2,14 @@ require 'rake_helper'
Rake.application.rake_require 'tasks/config_lint'
describe ConfigLint do
let(:files){ ['lib/support/fake.sh'] }
let(:files) { ['lib/support/fake.sh'] }
it 'errors out if any bash scripts have errors' do
expect { described_class.run(files){ system('exit 1') } }.to raise_error(SystemExit)
expect { described_class.run(files) { system('exit 1') } }.to raise_error(SystemExit)
end
it 'passes if all scripts are fine' do
expect { described_class.run(files){ system('exit 0') } }.not_to raise_error
expect { described_class.run(files) { system('exit 0') } }.not_to raise_error
end
end
......
......@@ -20,7 +20,7 @@ describe 'gitlab:gitaly namespace rake task' do
context 'when an underlying Git command fail' do
it 'aborts and display a help message' do
expect_any_instance_of(Object)
expect(main_object)
.to receive(:checkout_or_clone_version).and_raise 'Git error'
expect { run_rake_task('gitlab:gitaly:install', clone_path) }.to raise_error 'Git error'
......@@ -33,7 +33,7 @@ describe 'gitlab:gitaly namespace rake task' do
end
it 'calls checkout_or_clone_version with the right arguments' do
expect_any_instance_of(Object)
expect(main_object)
.to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path)
run_rake_task('gitlab:gitaly:install', clone_path)
......@@ -58,13 +58,13 @@ describe 'gitlab:gitaly namespace rake task' do
context 'gmake is available' do
before do
expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
allow_any_instance_of(Object).to receive(:run_command!).with(command_preamble + ['gmake']).and_return(true)
expect(main_object).to receive(:checkout_or_clone_version)
allow(main_object).to receive(:run_command!).with(command_preamble + ['gmake']).and_return(true)
end
it 'calls gmake in the gitaly directory' do
expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0])
expect_any_instance_of(Object).to receive(:run_command!).with(command_preamble + ['gmake']).and_return(true)
expect(main_object).to receive(:run_command!).with(command_preamble + ['gmake']).and_return(true)
run_rake_task('gitlab:gitaly:install', clone_path)
end
......@@ -72,13 +72,13 @@ describe 'gitlab:gitaly namespace rake task' do
context 'gmake is not available' do
before do
expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
allow_any_instance_of(Object).to receive(:run_command!).with(command_preamble + ['make']).and_return(true)
expect(main_object).to receive(:checkout_or_clone_version)
allow(main_object).to receive(:run_command!).with(command_preamble + ['make']).and_return(true)
end
it 'calls make in the gitaly directory' do
expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 42])
expect_any_instance_of(Object).to receive(:run_command!).with(command_preamble + ['make']).and_return(true)
expect(main_object).to receive(:run_command!).with(command_preamble + ['make']).and_return(true)
run_rake_task('gitlab:gitaly:install', clone_path)
end
......
......@@ -22,7 +22,8 @@ describe 'gitlab:shell rake tasks' do
describe 'create_hooks task' do
it 'calls gitlab-shell bin/create_hooks' do
expect_any_instance_of(Object).to receive(:system)
.with("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks", *repository_storage_paths_args)
.with("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks",
*Gitlab::TaskHelpers.repository_storage_paths_args)
run_rake_task('gitlab:shell:create_hooks')
end
......
......@@ -20,7 +20,7 @@ describe 'gitlab:workhorse namespace rake task' do
context 'when an underlying Git command fail' do
it 'aborts and display a help message' do
expect_any_instance_of(Object)
expect(main_object)
.to receive(:checkout_or_clone_version).and_raise 'Git error'
expect { run_rake_task('gitlab:workhorse:install', clone_path) }.to raise_error 'Git error'
......@@ -33,7 +33,7 @@ describe 'gitlab:workhorse namespace rake task' do
end
it 'calls checkout_or_clone_version with the right arguments' do
expect_any_instance_of(Object)
expect(main_object)
.to receive(:checkout_or_clone_version).with(version: version, repo: repo, target_dir: clone_path)
run_rake_task('gitlab:workhorse:install', clone_path)
......@@ -48,13 +48,13 @@ describe 'gitlab:workhorse namespace rake task' do
context 'gmake is available' do
before do
expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
allow_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true)
expect(main_object).to receive(:checkout_or_clone_version)
allow(Object).to receive(:run_command!).with(['gmake']).and_return(true)
end
it 'calls gmake in the gitlab-workhorse directory' do
expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['/usr/bin/gmake', 0])
expect_any_instance_of(Object).to receive(:run_command!).with(['gmake']).and_return(true)
expect(main_object).to receive(:run_command!).with(['gmake']).and_return(true)
run_rake_task('gitlab:workhorse:install', clone_path)
end
......@@ -62,13 +62,13 @@ describe 'gitlab:workhorse namespace rake task' do
context 'gmake is not available' do
before do
expect_any_instance_of(Object).to receive(:checkout_or_clone_version)
allow_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true)
expect(main_object).to receive(:checkout_or_clone_version)
allow(main_object).to receive(:run_command!).with(['make']).and_return(true)
end
it 'calls make in the gitlab-workhorse directory' do
expect(Gitlab::Popen).to receive(:popen).with(%w[which gmake]).and_return(['', 42])
expect_any_instance_of(Object).to receive(:run_command!).with(['make']).and_return(true)
expect(main_object).to receive(:run_command!).with(['make']).and_return(true)
run_rake_task('gitlab:workhorse:install', clone_path)
end
......
......@@ -41,7 +41,7 @@ describe NewIssueWorker do
let(:issue) { create(:issue, project: project, description: "issue for #{mentioned.to_reference}") }
it 'creates a new event record' do
expect{ worker.perform(issue.id, user.id) }.to change { Event.count }.from(0).to(1)
expect { worker.perform(issue.id, user.id) }.to change { Event.count }.from(0).to(1)
end
it 'creates a notification for the assignee' do
......
......@@ -43,7 +43,7 @@ describe NewMergeRequestWorker do
end
it 'creates a new event record' do
expect{ worker.perform(merge_request.id, user.id) }.to change { Event.count }.from(0).to(1)
expect { worker.perform(merge_request.id, user.id) }.to change { Event.count }.from(0).to(1)
end
it 'creates a notification for the assignee' do
......
......@@ -23,7 +23,7 @@ describe PipelineScheduleWorker do
context 'when there is a scheduled pipeline within next_run_at' do
it 'creates a new pipeline' do
expect{ subject }.to change { project.pipelines.count }.by(1)
expect { subject }.to change { project.pipelines.count }.by(1)
expect(Ci::Pipeline.last).to be_schedule
end
......
......@@ -78,7 +78,7 @@ describe PostReceive do
stub_ci_pipeline_to_return_yaml_file
end
it { expect{ subject }.to change{ Ci::Pipeline.count }.by(2) }
it { expect { subject }.to change { Ci::Pipeline.count }.by(2) }
end
context "does not create a Ci::Pipeline" do
......@@ -86,7 +86,7 @@ describe PostReceive do
stub_ci_pipeline_yaml_file(nil)
end
it { expect{ subject }.not_to change{ Ci::Pipeline.count } }
it { expect { subject }.not_to change { Ci::Pipeline.count } }
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment