diff options
197 files changed, 1622 insertions, 839 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d30deef0096..782a43cbe74 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -107,7 +107,7 @@ setup-test-env: <<: *dedicated-runner stage: prepare script: - - bundle exec rake assets:precompile 2>/dev/null + - bundle exec rake gitlab:assets:compile 2>/dev/null - bundle exec ruby -Ispec -e 'require "spec_helper" ; TestEnv.init' artifacts: expire_in: 7d @@ -280,6 +280,7 @@ group :development, :test do gem 'rspec-retry', '~> 0.4.5' gem 'spinach-rails', '~> 0.2.1' gem 'spinach-rerun-reporter', '~> 0.0.2' + gem 'rspec_profiling' # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826) gem 'minitest', '~> 5.7.0' diff --git a/Gemfile.lock b/Gemfile.lock index 133e47e1ea4..671d7788a86 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -642,6 +642,11 @@ GEM rspec-retry (0.4.5) rspec-core rspec-support (3.5.0) + rspec_profiling (0.0.4) + activerecord + pg + rails + sqlite3 rubocop (0.46.0) parser (>= 2.3.1.1, < 3.0) powerpack (~> 0.1) @@ -743,6 +748,7 @@ GEM actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) + sqlite3 (1.3.11) stackprof (0.2.10) state_machines (0.4.0) state_machines-activemodel (0.4.0) @@ -965,6 +971,7 @@ DEPENDENCIES rqrcode-rails3 (~> 0.1.7) rspec-rails (~> 3.5.0) rspec-retry (~> 0.4.5) + rspec_profiling rubocop (~> 0.46.0) rubocop-rspec (~> 1.9.1) ruby-fogbugz (~> 0.2.1) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index f0615481ed2..4849aab50f4 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -84,7 +84,6 @@ var $sidebarGutterToggle = $('.js-sidebar-toggle'); var $flash = $('.flash-container'); var bootstrapBreakpoint = bp.getBreakpointSize(); - var checkInitialSidebarSize; var fitSidebarForSize; // Set the default path for all cookies to GitLab's root directory @@ -246,19 +245,11 @@ return $document.trigger('breakpoint:change', [bootstrapBreakpoint]); } }; - checkInitialSidebarSize = function () { - bootstrapBreakpoint = bp.getBreakpointSize(); - if (bootstrapBreakpoint === 'xs' || 'sm') { - return $document.trigger('breakpoint:change', [bootstrapBreakpoint]); - } - }; $window.off('resize.app').on('resize.app', function () { return fitSidebarForSize(); }); gl.awardsHandler = new AwardsHandler(); - checkInitialSidebarSize(); new Aside(); - // bind sidebar events new gl.Sidebar(); }); diff --git a/app/assets/javascripts/commits.js b/app/assets/javascripts/commits.js index cabeae74ae3..c6fdfbcaa10 100644 --- a/app/assets/javascripts/commits.js +++ b/app/assets/javascripts/commits.js @@ -3,7 +3,7 @@ (function() { this.CommitsList = (function() { - function CommitsList() {} + var CommitsList = {}; CommitsList.timer = null; @@ -20,6 +20,7 @@ }); this.content = $("#commits-list"); this.searchField = $("#commits-search"); + this.lastSearch = this.searchField.val(); return this.initSearch(); }; @@ -37,6 +38,7 @@ var commitsUrl, form, search; form = $(".commits-search-form"); search = CommitsList.searchField.val(); + if (search === CommitsList.lastSearch) return; commitsUrl = form.attr("action") + '?' + form.serialize(); CommitsList.content.fadeTo('fast', 0.5); return $.ajax({ @@ -47,12 +49,16 @@ return CommitsList.content.fadeTo('fast', 1.0); }, success: function(data) { + CommitsList.lastSearch = search; CommitsList.content.html(data.html); return history.replaceState({ page: commitsUrl // Change url so if user reload a page - search results are saved }, document.title, commitsUrl); }, + error: function() { + CommitsList.lastSearch = null; + }, dataType: "json" }); }; diff --git a/app/assets/javascripts/diff.js.es6 b/app/assets/javascripts/diff.js.es6 index 5e1a4c948aa..35a029194d0 100644 --- a/app/assets/javascripts/diff.js.es6 +++ b/app/assets/javascripts/diff.js.es6 @@ -1,5 +1,7 @@ /* eslint-disable class-methods-use-this */ +//= require lib/utils/url_utility */ + (() => { const UNFOLD_COUNT = 20; @@ -104,11 +106,11 @@ } highlighSelectedLine() { + const hash = gl.utils.getLocationHash(); const $diffFiles = $('.diff-file'); $diffFiles.find('.hll').removeClass('hll'); - if (window.location.hash !== '') { - const hash = window.location.hash.replace('#', ''); + if (hash) { $diffFiles .find(`tr#${hash}:not(.match) td, td#${hash}, td[data-line-code="${hash}"]`) .addClass('hll'); diff --git a/app/assets/javascripts/droplab/droplab_ajax.js b/app/assets/javascripts/droplab/droplab_ajax.js index f7fed0987a2..c290e1a8355 100644 --- a/app/assets/javascripts/droplab/droplab_ajax.js +++ b/app/assets/javascripts/droplab/droplab_ajax.js @@ -9,6 +9,7 @@ require('../window')(function(w){ w.droplabAjax = { _loadUrlData: function _loadUrlData(url) { + var self = this; return new Promise(function(resolve, reject) { var xhr = new XMLHttpRequest; xhr.open('GET', url, true); @@ -16,6 +17,7 @@ require('../window')(function(w){ if(xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { var data = JSON.parse(xhr.responseText); + self.cache[url] = data; return resolve(data); } else { return reject([xhr.responseText, xhr.status]); @@ -26,8 +28,21 @@ require('../window')(function(w){ }); }, + _loadData: function _loadData(data, config, self) { + if (config.loadingTemplate) { + var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]'); + + if (dataLoadingTemplate) { + dataLoadingTemplate.outerHTML = self.listTemplate; + } + } + + self.hook.list[config.method].call(self.hook.list, data); + }, + init: function init(hook) { var self = this; + self.cache = self.cache || {}; var config = hook.config.droplabAjax; this.hook = hook; @@ -50,22 +65,16 @@ require('../window')(function(w){ dynamicList.outerHTML = loadingTemplate.outerHTML; } - this._loadUrlData(config.endpoint) - .then(function(d) { - if (config.loadingTemplate) { - var dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]'); - - if (dataLoadingTemplate) { - dataLoadingTemplate.outerHTML = self.listTemplate; - } - } - - if (!self.hook.list.hidden) { - self.hook.list[config.method].call(self.hook.list, d); - } - }).catch(function(e) { - throw new droplabAjaxException(e.message || e); - }); + if (self.cache[config.endpoint]) { + self._loadData(self.cache[config.endpoint], config, self); + } else { + this._loadUrlData(config.endpoint) + .then(function(d) { + self._loadData(d, config, self); + }).catch(function(e) { + throw new droplabAjaxException(e.message || e); + }); + } }, destroy: function() { diff --git a/app/assets/javascripts/droplab/droplab_ajax_filter.js b/app/assets/javascripts/droplab/droplab_ajax_filter.js index 86a08d0d01d..b63d73066cb 100644 --- a/app/assets/javascripts/droplab/droplab_ajax_filter.js +++ b/app/assets/javascripts/droplab/droplab_ajax_filter.js @@ -72,32 +72,22 @@ require('../window')(function(w){ var params = config.params || {}; params[config.searchKey] = searchValue; var self = this; - this._loadUrlData(config.endpoint + this.buildParams(params)).then(function(data) { - if (config.loadingTemplate && self.hook.list.data === undefined || - self.hook.list.data.length === 0) { - const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]'); - - if (dataLoadingTemplate) { - dataLoadingTemplate.outerHTML = self.listTemplate; - } - } - - if (!self.destroyed) { - var hookListChildren = self.hook.list.list.children; - var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic'); - - if (onlyDynamicList && data.length === 0) { - self.hook.list.hide(); - } - - self.hook.list.setData.call(self.hook.list, data); - } - self.notLoading(); - self.hook.list.currentIndex = 0; - }); + self.cache = self.cache || {}; + var url = config.endpoint + this.buildParams(params); + var urlCachedData = self.cache[url]; + + if (urlCachedData) { + self._loadData(urlCachedData, config, self); + } else { + this._loadUrlData(url) + .then(function(data) { + self._loadData(data, config, self); + }); + } }, _loadUrlData: function _loadUrlData(url) { + var self = this; return new Promise(function(resolve, reject) { var xhr = new XMLHttpRequest; xhr.open('GET', url, true); @@ -105,6 +95,7 @@ require('../window')(function(w){ if(xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { var data = JSON.parse(xhr.responseText); + self.cache[url] = data; return resolve(data); } else { return reject([xhr.responseText, xhr.status]); @@ -115,6 +106,30 @@ require('../window')(function(w){ }); }, + _loadData: function _loadData(data, config, self) { + if (config.loadingTemplate && self.hook.list.data === undefined || + self.hook.list.data.length === 0) { + const dataLoadingTemplate = self.hook.list.list.querySelector('[data-loading-template]'); + + if (dataLoadingTemplate) { + dataLoadingTemplate.outerHTML = self.listTemplate; + } + } + + if (!self.destroyed) { + var hookListChildren = self.hook.list.list.children; + var onlyDynamicList = hookListChildren.length === 1 && hookListChildren[0].hasAttribute('data-dynamic'); + + if (onlyDynamicList && data.length === 0) { + self.hook.list.hide(); + } + + self.hook.list.setData.call(self.hook.list, data); + } + self.notLoading(); + self.hook.list.currentIndex = 0; + }, + buildParams: function(params) { if (!params) return ''; var paramsArray = Object.keys(params).map(function(param) { diff --git a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 index eeab10fba17..de3fa116717 100644 --- a/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 +++ b/app/assets/javascripts/filtered_search/dropdown_utils.js.es6 @@ -28,7 +28,12 @@ if (lastToken !== searchToken) { const title = updatedItem.title.toLowerCase(); let value = lastToken.value.toLowerCase(); - value = value.replace(/"(.*?)"/g, str => str.slice(1).slice(0, -1)); + + // Removes the first character if it is a quotation so that we can search + // with multiple words + if ((value[0] === '"' || value[0] === '\'') && title.indexOf(' ') !== -1) { + value = value.slice(1); + } // Eg. filterSymbol = ~ for labels const matchWithoutSymbol = lastToken.symbol === filterSymbol && title.indexOf(value) !== -1; @@ -83,8 +88,9 @@ const selectionStart = input.selectionStart; let inputValue = input.value; // Replace all spaces inside quote marks with underscores + // (will continue to match entire string until an end quote is found if any) // This helps with matching the beginning & end of a token:key - inputValue = inputValue.replace(/("(.*?)"|:\s+)/g, str => str.replace(/\s/g, '_')); + inputValue = inputValue.replace(/(('[^']*'{0,1})|("[^"]*"{0,1})|:\s+)/g, str => str.replace(/\s/g, '_')); // Get the right position for the word selected // Regex matches first space diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 index 8d62324b79f..029564ffc61 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js.es6 @@ -196,7 +196,8 @@ }); if (searchToken) { - paths.push(`search=${encodeURIComponent(searchToken)}`); + const sanitized = searchToken.split(' ').map(t => encodeURIComponent(t)).join('+'); + paths.push(`search=${sanitized}`); } Turbolinks.visit(`?scope=all&utf8=✓&${paths.join('&')}`); diff --git a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 index e46373024b6..e6b53cd4b55 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 +++ b/app/assets/javascripts/filtered_search/filtered_search_token_keys.js.es6 @@ -21,6 +21,15 @@ symbol: '~', }]; + const alternativeTokenKeys = [{ + key: 'label', + type: 'string', + param: 'name', + symbol: '~', + }]; + + const tokenKeysWithAlternative = tokenKeys.concat(alternativeTokenKeys); + const conditions = [{ url: 'assignee_id=0', tokenKey: 'assignee', @@ -44,6 +53,10 @@ return tokenKeys; } + static getAlternatives() { + return alternativeTokenKeys; + } + static getConditions() { return conditions; } @@ -57,7 +70,7 @@ } static searchByKeyParam(keyParam) { - return tokenKeys.find((tokenKey) => { + return tokenKeysWithAlternative.find((tokenKey) => { let tokenKeyParam = tokenKey.key; if (tokenKey.param) { diff --git a/app/assets/javascripts/issuable_context.js b/app/assets/javascripts/issuable_context.js index 9c53cdee58e..c77fbb6a1c7 100644 --- a/app/assets/javascripts/issuable_context.js +++ b/app/assets/javascripts/issuable_context.js @@ -1,5 +1,7 @@ /* 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 UsersSelect */ +/* global Cookies */ +/* global bp */ (function() { this.IssuableContext = (function() { @@ -37,6 +39,13 @@ }, 0); } }); + window.addEventListener('beforeunload', function() { + // collapsed_gutter cookie hides the sidebar + var bpBreakpoint = bp.getBreakpointSize(); + if (bpBreakpoint === 'xs' || bpBreakpoint === 'sm') { + Cookies.set('collapsed_gutter', true); + } + }); $(".right-sidebar").niceScroll(); } diff --git a/app/assets/javascripts/lib/ace.js b/app/assets/javascripts/lib/ace.js index 4cdf99cae72..9cdc0309503 100644 --- a/app/assets/javascripts/lib/ace.js +++ b/app/assets/javascripts/lib/ace.js @@ -1,2 +1,3 @@ -/*= require ace-rails-ap */ +/*= require ace/ace */ /*= require ace/ext-searchbox */ +/*= require ./ace/ace_config_paths */ diff --git a/app/assets/javascripts/lib/ace/ace_config_paths.js.erb b/app/assets/javascripts/lib/ace/ace_config_paths.js.erb new file mode 100644 index 00000000000..25e623f0fdc --- /dev/null +++ b/app/assets/javascripts/lib/ace/ace_config_paths.js.erb @@ -0,0 +1,25 @@ +<% +ace_gem_path = Bundler.rubygems.find_name('ace-rails-ap').first.full_gem_path +ace_workers = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/worker-*.js'].sort.map do |file| + File.basename(file, '.js').sub(/^worker-/, '') +end +ace_modes = Dir[ace_gem_path + '/vendor/assets/javascripts/ace/mode-*.js'].sort.map do |file| + File.basename(file, '.js').sub(/^mode-/, '') +end +%> + +(function() { + window.gon = window.gon || {}; + var basePath = (window.gon.relative_url_root || '').replace(/\/$/, '') + '/assets/ace'; + ace.config.set('basePath', basePath); + + // configure paths for all worker modules +<% ace_workers.each do |worker| %> + ace.config.setModuleUrl('ace/mode/<%= worker %>_worker', basePath + '/worker-<%= worker %>.js'); +<% end %> + + // configure paths for all mode modules +<% ace_modes.each do |mode| %> + ace.config.setModuleUrl('ace/mode/<%= mode %>', basePath + '/mode-<%= mode %>.js'); +<% end %> +})(); diff --git a/app/assets/javascripts/line_highlighter.js b/app/assets/javascripts/line_highlighter.js index 4620715a521..2f147704c22 100644 --- a/app/assets/javascripts/line_highlighter.js +++ b/app/assets/javascripts/line_highlighter.js @@ -74,8 +74,9 @@ // If not done this way, the line number anchor will sometimes keep its // active state even when the event is cancelled, resulting in an ugly border // around the link and/or a persisted underline text decoration. - return $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) { - return event.preventDefault(); + $('#blob-content-holder').on('click', 'a[data-line-number]', function(event) { + event.preventDefault(); + event.stopPropagation(); }); }; diff --git a/app/assets/javascripts/profile/profile.js.es6 b/app/assets/javascripts/profile/profile.js.es6 index 6dbaae25f2a..5aec9c813fe 100644 --- a/app/assets/javascripts/profile/profile.js.es6 +++ b/app/assets/javascripts/profile/profile.js.es6 @@ -36,6 +36,7 @@ } onSubmitForm(e) { + e.preventDefault(); return this.saveForm(); } diff --git a/app/assets/javascripts/users/calendar.js b/app/assets/javascripts/users/calendar.js index 7ffc546ffc1..e7280d643d3 100644 --- a/app/assets/javascripts/users/calendar.js +++ b/app/assets/javascripts/users/calendar.js @@ -158,7 +158,7 @@ }; Calendar.prototype.renderMonths = function() { - return this.svg.append('g').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) { + return this.svg.append('g').attr('direction', 'ltr').selectAll('text').data(this.months).enter().append('text').attr('x', function(date) { return date.x; }).attr('y', 10).attr('class', 'user-contrib-text').text((function(_this) { return function(date) { diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index 8392b98f0a7..1d59700543c 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -37,6 +37,8 @@ display: inline-block; margin-left: 4px; margin-bottom: 2px; + flex-shrink: 0; + -webkit-flex-shrink: 0; &.s16 { margin-right: 4px; } &.s24 { margin-right: 4px; } diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index ef921a8c6a9..1d2d1bfc0d7 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -1,6 +1,7 @@ .calender-block { padding-left: 0; padding-right: 0; + direction: rtl; @media (min-width: $screen-sm-min) and (max-width: $screen-md-max) { overflow-x: scroll; diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 755eddefa42..6bfb9a6d1cb 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -125,7 +125,8 @@ top: 100%; left: 0; z-index: 9; - width: 240px; + max-width: 280px; + min-width: 240px; margin-top: 2px; margin-bottom: 0; font-size: 14px; diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss index 4b05ec691a8..e3da467a27c 100644 --- a/app/assets/stylesheets/framework/filters.scss +++ b/app/assets/stylesheets/framework/filters.scss @@ -132,6 +132,11 @@ display: flex; -webkit-flex-direction: column; flex-direction: column; + + &> span { + white-space: normal; + word-break: break-all; + } } } @@ -141,10 +146,6 @@ } } -.hint-dropdown { - width: 250px; -} - .filter-dropdown-loading { padding: 8px 16px; } diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 24a1ce2b84d..2a01bc4d44d 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -71,7 +71,7 @@ header { &:focus, &:active { background-color: $gray-light; - color: darken($gl-text-color-secondary, 30%); + color: $gl-text-color; .todos-pending-count { background: darken($todo-alert-blue, 10%); diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 07cb669a46e..7809d4866f1 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -178,7 +178,7 @@ $count-arrow-border: #dce0e5; $save-project-loader-color: #555; $divergence-graph-bar-bg: #ccc; $divergence-graph-separator-bg: #ccc; -$general-hover-transition-duration: 150ms; +$general-hover-transition-duration: 100ms; $general-hover-transition-curve: linear; diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss index 93cc5a8cf0a..4ef95d27f4f 100644 --- a/app/assets/stylesheets/pages/issuable.scss +++ b/app/assets/stylesheets/pages/issuable.scss @@ -26,10 +26,6 @@ border: 0; } } - - .container-fluid { - @extend .fixed-width-container; - } } } diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 45ff9f7ff5f..ab68b360f93 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -420,10 +420,6 @@ .merge-request-tabs-holder { background-color: $white-light; - .container-limited { - max-width: $limited-layout-width; - } - &.affix { top: 100px; left: 0; @@ -433,10 +429,26 @@ @media (max-width: $screen-xs-max) { right: 0; } + + .merge-request-tabs-container { + padding-left: $gl-padding; + padding-right: $gl-padding; + } } +} - &:not(.affix) .container-fluid { - padding-left: 0; - padding-right: 0; +.limit-container-width { + .merge-request-tabs-container { + max-width: $limited-layout-width; + margin-left: auto; + margin-right: auto; + } +} + +.limit-container-width:not(.container-limited) { + .merge-request-tabs-holder:not(.affix) { + .merge-request-tabs-container { + max-width: $limited-layout-width - ($gl-padding * 2); + } } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 5190faad308..47dfc22d533 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -214,9 +214,9 @@ &:not(:last-child) { &::after { content: ''; - width: 8px; + width: 7px; position: absolute; - right: -8px; + right: -7px; top: 10px; border-bottom: 2px solid $border-color; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 1b0bf4554e6..8b59c20cb65 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -198,7 +198,7 @@ margin: 15px 5px 0 0; input { - height: 27px; + height: 28px; } } @@ -523,7 +523,7 @@ a.deploy-project-label { &:hover, &:focus { - color: darken($notes-light-color, 15%); + color: $gl-text-color; } } diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index 6db4e1dc1bc..d7a45bacd35 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -18,15 +18,14 @@ class AutocompleteController < ApplicationController if params[:search].blank? # Include current user if available to filter by "Me" if params[:current_user].present? && current_user + @users = @users.where.not(id: current_user.id) @users = [current_user, *@users] end if params[:author_id].present? author = User.find_by_id(params[:author_id]) - @users = [author, *@users] if author + @users = [author, *@users].uniq if author end - - @users.uniq! end render json: @users, only: [:name, :username, :id], methods: [:avatar_url] diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index a62c6211372..26e17a7553e 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -22,6 +22,7 @@ class Explore::ProjectsController < Explore::ApplicationController def trending @projects = filter_projects(Project.trending) + @projects = @projects.sort(@sort = params[:sort]) @projects = @projects.page(params[:page]) respond_to do |format| diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb index 9b45ed6b6af..886934a3f67 100644 --- a/app/controllers/projects/builds_controller.rb +++ b/app/controllers/projects/builds_controller.rb @@ -94,7 +94,7 @@ class Projects::BuildsController < Projects::ApplicationController private def build - @build ||= project.builds.find_by!(id: params[:id]).present(user: current_user) + @build ||= project.builds.find_by!(id: params[:id]).present(current_user: current_user) end def build_path(build) diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 3602b3d5e58..667f4870c7a 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -32,12 +32,6 @@ class Projects::RefsController < Projects::ApplicationController redirect_to new_path end - format.js do - @ref = params[:ref] - define_tree_vars - tree - render "tree" - end end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index e33a58d3771..2df8b071e13 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -13,49 +13,6 @@ class ApplicationSetting < ActiveRecord::Base [\r\n] # any number of newline characters }x - DEFAULTS_CE = { - after_sign_up_text: nil, - akismet_enabled: false, - container_registry_token_expire_delay: 5, - default_branch_protection: Settings.gitlab['default_branch_protection'], - default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], - default_projects_limit: Settings.gitlab['default_projects_limit'], - default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], - disabled_oauth_sign_in_sources: [], - domain_whitelist: Settings.gitlab['domain_whitelist'], - gravatar_enabled: Settings.gravatar['enabled'], - help_page_text: nil, - housekeeping_bitmaps_enabled: true, - housekeeping_enabled: true, - housekeeping_full_repack_period: 50, - housekeeping_gc_period: 200, - housekeeping_incremental_repack_period: 10, - import_sources: Gitlab::ImportSources.values, - koding_enabled: false, - koding_url: nil, - max_artifacts_size: Settings.artifacts['max_size'], - max_attachment_size: Settings.gitlab['max_attachment_size'], - plantuml_enabled: false, - plantuml_url: nil, - recaptcha_enabled: false, - repository_checks_enabled: true, - repository_storages: ['default'], - require_two_factor_authentication: false, - restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], - session_expire_delay: Settings.gitlab['session_expire_delay'], - send_user_confirmation_email: false, - shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], - shared_runners_text: nil, - sidekiq_throttling_enabled: false, - sign_in_text: nil, - signin_enabled: Settings.gitlab['signin_enabled'], - signup_enabled: Settings.gitlab['signup_enabled'], - two_factor_grace_period: 48, - user_default_external: false - } - - DEFAULTS = DEFAULTS_CE - serialize :restricted_visibility_levels serialize :import_sources serialize :disabled_oauth_sign_in_sources, Array @@ -199,14 +156,64 @@ class ApplicationSetting < ActiveRecord::Base def self.expire Rails.cache.delete(CACHE_KEY) + rescue + # Gracefully handle when Redis is not available. For example, + # omnibus may fail here during gitlab:assets:compile. end def self.cached Rails.cache.fetch(CACHE_KEY) end + def self.defaults_ce + { + after_sign_up_text: nil, + akismet_enabled: false, + container_registry_token_expire_delay: 5, + default_branch_protection: Settings.gitlab['default_branch_protection'], + default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'], + default_projects_limit: Settings.gitlab['default_projects_limit'], + default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'], + disabled_oauth_sign_in_sources: [], + domain_whitelist: Settings.gitlab['domain_whitelist'], + gravatar_enabled: Settings.gravatar['enabled'], + help_page_text: nil, + housekeeping_bitmaps_enabled: true, + housekeeping_enabled: true, + housekeeping_full_repack_period: 50, + housekeeping_gc_period: 200, + housekeeping_incremental_repack_period: 10, + import_sources: Gitlab::ImportSources.values, + koding_enabled: false, + koding_url: nil, + max_artifacts_size: Settings.artifacts['max_size'], + max_attachment_size: Settings.gitlab['max_attachment_size'], + plantuml_enabled: false, + plantuml_url: nil, + recaptcha_enabled: false, + repository_checks_enabled: true, + repository_storages: ['default'], + require_two_factor_authentication: false, + restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], + session_expire_delay: Settings.gitlab['session_expire_delay'], + send_user_confirmation_email: false, + shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], + shared_runners_text: nil, + sidekiq_throttling_enabled: false, + sign_in_text: nil, + signin_enabled: Settings.gitlab['signin_enabled'], + signup_enabled: Settings.gitlab['signup_enabled'], + two_factor_grace_period: 48, + user_default_external: false + } + end + + def self.defaults + defaults_ce + end + def self.create_from_defaults - create(DEFAULTS) + create(defaults) end def home_page_url_column_exist diff --git a/app/models/repository.rb b/app/models/repository.rb index 43dba86e5ed..d77b7692d75 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1188,7 +1188,18 @@ class Repository end def tags_sorted_by_committed_date - tags.sort_by { |tag| tag.dereferenced_target.committed_date } + tags.sort_by do |tag| + # Annotated tags can point to any object (e.g. a blob), but generally + # tags point to a commit. If we don't have a commit, then just default + # to putting the tag at the end of the list. + target = tag.dereferenced_target + + if target + target.committed_date + else + Time.now + end + end end def keep_around_ref_name(sha) diff --git a/app/presenters/README.md b/app/presenters/README.md index 3edd63451e7..a4d592b54d6 100644 --- a/app/presenters/README.md +++ b/app/presenters/README.md @@ -113,7 +113,7 @@ detects the presenter based on the presented subject's class. class Projects::LabelsController < Projects::ApplicationController def edit @label = Gitlab::View::Presenter::Factory - .new(@label, user: current_user) + .new(@label, current_user: current_user) .fabricate! end end @@ -132,7 +132,7 @@ and then in the controller: ```ruby class Projects::LabelsController < Projects::ApplicationController def edit - @label = @label.present(user: current_user) + @label = @label.present(current_user: current_user) end end ``` @@ -147,7 +147,7 @@ end You can also present the model in the view: ```ruby -- label = @label.present(current_user) +- label = @label.present(current_user: current_user) %div{ class: label.text_color } = render partial: label, label: label diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml index 601fb7f0f3f..c00c7f7407e 100644 --- a/app/views/ci/status/_badge.html.haml +++ b/app/views/ci/status/_badge.html.haml @@ -1,7 +1,8 @@ - status = local_assigns.fetch(:status) +- link = local_assigns.fetch(:link, true) - css_classes = "ci-status ci-#{status.group}" -- if status.has_details? +- if link && status.has_details? = link_to status.details_path, class: css_classes do = custom_icon(status.icon) = status.text diff --git a/app/views/projects/builds/_header.html.haml b/app/views/projects/builds/_header.html.haml index b15be0d861d..736b485bf06 100644 --- a/app/views/projects/builds/_header.html.haml +++ b/app/views/projects/builds/_header.html.haml @@ -1,8 +1,8 @@ .content-block.build-header .header-content - = render 'ci/status/badge', status: @build.detailed_status(current_user) + = render 'ci/status/badge', status: @build.detailed_status(current_user), link: false Build - %strong ##{@build.id} + %strong.js-build-id ##{@build.id} in pipeline = link_to pipeline_path(@build.pipeline) do %strong ##{@build.pipeline.id} diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 114865935d6..84787155168 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -9,7 +9,7 @@ %fieldset.append-bottom-0 .row .form-group.col-md-9 - = f.label :name, class: 'label-light' do + = f.label :name, class: 'label-light', for: 'project_name_edit' do Project name = f.text_field :name, class: "form-control", id: "project_name_edit" diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 18e8372ecab..5fbed8b9ab8 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -19,10 +19,8 @@ = render 'shared/issuable/nav', type: :issues .nav-controls - if current_user - = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn append-right-10' do + = link_to url_for(params.merge(format: :atom, private_token: current_user.private_token)), class: 'btn append-right-10 has-tooltip', title: 'Subscribe' do = icon('rss') - %span.icon-label - Subscribe - if can? current_user, :create_issue, @project = link_to new_namespace_project_issue_path(@project.namespace, @project, diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index eade0c2a668..9585a9a3ad4 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -52,7 +52,7 @@ = render 'award_emoji/awards_block', awardable: @merge_request, inline: true .merge-request-tabs-holder{ class: ("js-tabs-affix" unless ENV['RAILS_ENV'] == 'test') } - %div{ class: container_class } + .merge-request-tabs-container %ul.merge-request-tabs.nav-links.no-top.no-bottom %li.notes-tab = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: { target: 'div#notes', action: 'notes', toggle: 'tab' } do @@ -115,4 +115,3 @@ }); var mrRefreshWidgetUrl = "#{mr_widget_refresh_url(@merge_request)}"; - diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index fc338dcf887..f1a80f1d5e1 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -17,4 +17,4 @@ - disabled_title = @service.disabled_title = link_to 'Test settings', test_namespace_project_service_path(@project.namespace, @project, @service), class: "btn #{disabled_class}", title: disabled_title - = link_to "Cancel", namespace_project_services_path(@project.namespace, @project), class: "btn btn-cancel" + = link_to "Cancel", namespace_project_settings_integrations_path(@project.namespace, @project), class: "btn btn-cancel" diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index e9644ca0f12..55360dadbc4 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -47,10 +47,6 @@ %li.filter-dropdown-item{ 'data-value' => 'none' } %button.btn.btn-link No Assignee - - if current_user - %li.filter-dropdown-item{ 'data-value' => current_user.to_reference } - %button.btn.btn-link - Assigned to me %li.divider %ul.filter-dropdown{ 'data-dynamic' => true, 'data-dropdown' => true } %li.filter-dropdown-item diff --git a/changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml b/changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml new file mode 100644 index 00000000000..4678297cfd4 --- /dev/null +++ b/changelogs/unreleased/26844-new-search-bar-performs-a-new-request-for-each-tag.yml @@ -0,0 +1,4 @@ +--- +title: Add caching of droplab ajax requests +merge_request: 8725 +author: diff --git a/changelogs/unreleased/26947-build-status-self-link.yml b/changelogs/unreleased/26947-build-status-self-link.yml new file mode 100644 index 00000000000..15c5821874e --- /dev/null +++ b/changelogs/unreleased/26947-build-status-self-link.yml @@ -0,0 +1,4 @@ +--- +title: Add link verification to badge partial in order to render a badge without a link +merge_request: 8740 +author: diff --git a/changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml b/changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml new file mode 100644 index 00000000000..0f0a8940f72 --- /dev/null +++ b/changelogs/unreleased/27044-fix-explore-sorting-on-trending.yml @@ -0,0 +1,4 @@ +--- +title: Fix /explore sorting +merge_request: +author: diff --git a/changelogs/unreleased/27248-filtered-search-does-not-allow-filtering-labels-with-multiple-words.yml b/changelogs/unreleased/27248-filtered-search-does-not-allow-filtering-labels-with-multiple-words.yml new file mode 100644 index 00000000000..6e036923158 --- /dev/null +++ b/changelogs/unreleased/27248-filtered-search-does-not-allow-filtering-labels-with-multiple-words.yml @@ -0,0 +1,4 @@ +--- +title: Fix filtering with multiple words +merge_request: 8830 +author: diff --git a/changelogs/unreleased/27259-label-for-references-the-wrong-associated-text-input.yml b/changelogs/unreleased/27259-label-for-references-the-wrong-associated-text-input.yml new file mode 100644 index 00000000000..2591f161bc5 --- /dev/null +++ b/changelogs/unreleased/27259-label-for-references-the-wrong-associated-text-input.yml @@ -0,0 +1,4 @@ +--- +title: Fix project name label's for reference in project settings +merge_request: 8795 +author: diff --git a/changelogs/unreleased/27277-small-mini-pipeline-graph-glitch-upon-hover.yml b/changelogs/unreleased/27277-small-mini-pipeline-graph-glitch-upon-hover.yml new file mode 100644 index 00000000000..9456251025b --- /dev/null +++ b/changelogs/unreleased/27277-small-mini-pipeline-graph-glitch-upon-hover.yml @@ -0,0 +1,4 @@ +--- +title: fixed small mini pipeline graph line glitch +merge_request: 8804 +author: diff --git a/changelogs/unreleased/contribution-calendar-scroll.yml b/changelogs/unreleased/contribution-calendar-scroll.yml new file mode 100644 index 00000000000..a504d59e61c --- /dev/null +++ b/changelogs/unreleased/contribution-calendar-scroll.yml @@ -0,0 +1,4 @@ +--- +title: contribution calendar scrolls from right to left +merge_request: +author: diff --git a/changelogs/unreleased/fix-cancel-integration-settings.yml b/changelogs/unreleased/fix-cancel-integration-settings.yml new file mode 100644 index 00000000000..294b0aa5db9 --- /dev/null +++ b/changelogs/unreleased/fix-cancel-integration-settings.yml @@ -0,0 +1,4 @@ +--- +title: Fixed services form cancel not redirecting back the integrations settings view +merge_request: 8843 +author: diff --git a/changelogs/unreleased/fix-search-bar-search-param.yml b/changelogs/unreleased/fix-search-bar-search-param.yml new file mode 100644 index 00000000000..4df14d3bf13 --- /dev/null +++ b/changelogs/unreleased/fix-search-bar-search-param.yml @@ -0,0 +1,4 @@ +--- +title: Fix search bar search param encoding +merge_request: 8753 +author: diff --git a/changelogs/unreleased/get-rid-of-water-from-notification_service_spec-to-make-it-DRY.yml b/changelogs/unreleased/get-rid-of-water-from-notification_service_spec-to-make-it-DRY.yml new file mode 100644 index 00000000000..f60417d185e --- /dev/null +++ b/changelogs/unreleased/get-rid-of-water-from-notification_service_spec-to-make-it-DRY.yml @@ -0,0 +1,4 @@ +--- +title: Make notification_service spec DRYer by making test reusable +merge_request: +author: YarNayar diff --git a/changelogs/unreleased/hardcode-title-system-note.yml b/changelogs/unreleased/hardcode-title-system-note.yml new file mode 100644 index 00000000000..1b0a63efa51 --- /dev/null +++ b/changelogs/unreleased/hardcode-title-system-note.yml @@ -0,0 +1,4 @@ +--- +title: Ensure autogenerated title does not cause failing spec +merge_request: 8963 +author: brian m. carlson diff --git a/changelogs/unreleased/improve-ci-example-php-doc.yml b/changelogs/unreleased/improve-ci-example-php-doc.yml new file mode 100644 index 00000000000..39a85e3d261 --- /dev/null +++ b/changelogs/unreleased/improve-ci-example-php-doc.yml @@ -0,0 +1,4 @@ +--- +title: Changed composer installer script in the CI PHP example doc +merge_request: 4342 +author: Jeffrey Cafferata diff --git a/changelogs/unreleased/issuable-sidebar-bug.yml b/changelogs/unreleased/issuable-sidebar-bug.yml new file mode 100644 index 00000000000..4086292eb89 --- /dev/null +++ b/changelogs/unreleased/issuable-sidebar-bug.yml @@ -0,0 +1,4 @@ +--- +title: Fixed Issuable sidebar not closing on smaller/mobile sized screens +merge_request: +author: diff --git a/changelogs/unreleased/issue_27211.yml b/changelogs/unreleased/issue_27211.yml new file mode 100644 index 00000000000..ad48fec5d85 --- /dev/null +++ b/changelogs/unreleased/issue_27211.yml @@ -0,0 +1,4 @@ +--- +title: Remove unused js response from refs controller +merge_request: +author: diff --git a/changelogs/unreleased/mr-tabs-container-offset.yml b/changelogs/unreleased/mr-tabs-container-offset.yml new file mode 100644 index 00000000000..c5df8abfcf2 --- /dev/null +++ b/changelogs/unreleased/mr-tabs-container-offset.yml @@ -0,0 +1,4 @@ +--- +title: Fixed merge requests tab extra margin when fixed to window +merge_request: +author: diff --git a/changelogs/unreleased/relative-url-assets.yml b/changelogs/unreleased/relative-url-assets.yml new file mode 100644 index 00000000000..0877664aca4 --- /dev/null +++ b/changelogs/unreleased/relative-url-assets.yml @@ -0,0 +1,4 @@ +--- +title: allow relative url change without recompiling frontend assets +merge_request: 8831 +author: diff --git a/changelogs/unreleased/revert-filter-assigned-to-me.yml b/changelogs/unreleased/revert-filter-assigned-to-me.yml new file mode 100644 index 00000000000..37f9d2f5fc4 --- /dev/null +++ b/changelogs/unreleased/revert-filter-assigned-to-me.yml @@ -0,0 +1,4 @@ +--- +title: Revert 3f17f29a +merge_request: 8785 +author: diff --git a/changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml b/changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml new file mode 100644 index 00000000000..ff2b38f21f2 --- /dev/null +++ b/changelogs/unreleased/sh-fix-annotated-tags-pointing-to-blob.yml @@ -0,0 +1,4 @@ +--- +title: Fix Error 500 when repositories contain annotated tags pointing to blobs +merge_request: +author: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 906ec11f012..4f33aad8693 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -419,10 +419,4 @@ if Rails.env.test? end # Force a refresh of application settings at startup -begin - ApplicationSetting.expire - Ci::ApplicationSetting.expire -rescue - # Gracefully handle when Redis is not available. For example, - # omnibus may fail here during assets:precompile. -end +ApplicationSetting.expire diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb index ed88c8ee1b8..2bd159ca7f1 100644 --- a/config/initializers/5_backend.rb +++ b/config/initializers/5_backend.rb @@ -1,9 +1,3 @@ -# GIT over SSH -require_dependency Rails.root.join('lib/gitlab/backend/shell') - -# GitLab shell adapter -require_dependency Rails.root.join('lib/gitlab/backend/shell_adapter') - required_version = Gitlab::VersionInfo.parse(Gitlab::Shell.version_required) current_version = Gitlab::VersionInfo.parse(Gitlab::Shell.new.version) diff --git a/config/initializers/rspec_profiling.rb b/config/initializers/rspec_profiling.rb new file mode 100644 index 00000000000..f462e654b2c --- /dev/null +++ b/config/initializers/rspec_profiling.rb @@ -0,0 +1,14 @@ +module RspecProfilingConnection + def establish_connection + ::RspecProfiling::Collectors::PSQL::Result.establish_connection(ENV['RSPEC_PROFILING_POSTGRES_URL']) + end +end + +if Rails.env.test? + RspecProfiling.configure do |config| + if ENV['RSPEC_PROFILING_POSTGRES_URL'] + RspecProfiling::Collectors::PSQL.prepend(RspecProfilingConnection) + config.collector = RspecProfiling::Collectors::PSQL + end + end +end diff --git a/doc/README.md b/doc/README.md index 993b30ccdb5..909740211a6 100644 --- a/doc/README.md +++ b/doc/README.md @@ -44,7 +44,7 @@ - [Operations](administration/operations.md) Keeping GitLab up and running. - [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects. - [Repository checks](administration/repository_checks.md) Periodic Git repository checks. -- [Repository storages](administration/repository_storages.md) Manage the paths used to store repositories. +- [Repository storage paths](administration/repository_storage_paths.md) Manage the paths used to store repositories. - [Security](security/README.md) Learn what you can do to further secure your GitLab instance. - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [Update](update/README.md) Update guides to upgrade your installation. @@ -61,7 +61,6 @@ - [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs. - [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability. - [Container Registry](administration/container_registry.md) Configure Docker Registry with GitLab. -- [Multiple mountpoints for the repositories storage](administration/repository_storages.md) Define multiple repository storage paths to distribute the storage load. ## Contributor documentation diff --git a/doc/administration/raketasks/maintenance.md b/doc/administration/raketasks/maintenance.md index 33b9b28433a..5b6ee354887 100644 --- a/doc/administration/raketasks/maintenance.md +++ b/doc/administration/raketasks/maintenance.md @@ -172,14 +172,14 @@ Omnibus packages. ``` cd /home/git/gitlab -sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production ``` For omnibus versions, the unoptimized assets (JavaScript, CSS) are frozen at the release of upstream GitLab. The omnibus version includes optimized versions of those assets. Unless you are modifying the JavaScript / CSS code on your production machine after installing the package, there should be no reason to redo -rake assets:precompile on the production machine. If you suspect that assets +rake gitlab:assets:compile on the production machine. If you suspect that assets have been corrupted, you should reinstall the omnibus package. ## Tracking Deployments diff --git a/doc/administration/repository_checks.md b/doc/administration/repository_checks.md index bc2b1f20ed3..ee37ea49874 100644 --- a/doc/administration/repository_checks.md +++ b/doc/administration/repository_checks.md @@ -13,12 +13,12 @@ checks failed you can see their output on the admin log page under ## Periodic checks -GitLab periodically runs a repository check on all project repositories and -wiki repositories in order to detect data corruption problems. A -project will be checked no more than once per week. If any projects +When enabled, GitLab periodically runs a repository check on all project +repositories and wiki repositories in order to detect data corruption problems. +A project will be checked no more than once per month. If any projects fail their repository checks all GitLab administrators will receive an email -notification of the situation. This notification is sent out no more -than once a day. +notification of the situation. This notification is sent out once a week on +Sunday, by default. ## Disabling periodic checks diff --git a/doc/administration/repository_storage_paths.md b/doc/administration/repository_storage_paths.md new file mode 100644 index 00000000000..d6aa6101026 --- /dev/null +++ b/doc/administration/repository_storage_paths.md @@ -0,0 +1,102 @@ +# Repository storage paths + +> [Introduced][ce-4578] in GitLab 8.10. + +GitLab allows you to define multiple repository storage paths to distribute the +storage load between several mount points. + +>**Notes:** +> +- You must have at least one storage path called `default`. +- The paths are defined in key-value pairs. The key is an arbitrary name you + can pick to name the file path. +- The target directories and any of its subpaths must not be a symlink. + +## Configure GitLab + +>**Warning:** +In order for [backups] to work correctly, the storage path must **not** be a +mount point and the GitLab user should have correct permissions for the parent +directory of the path. In Omnibus GitLab this is taken care of automatically, +but for source installations you should be extra careful. +> +The thing is that for compatibility reasons `gitlab.yml` has a different +structure than Omnibus. In `gitlab.yml` you indicate the path for the +repositories, for example `/home/git/repositories`, while in Omnibus you +indicate `git_data_dirs`, which for the example above would be `/home/git`. +Then, Omnibus will create a `repositories` directory under that path to use with +`gitlab.yml`. +> +This little detail matters because while restoring a backup, the current +contents of `/home/git/repositories` [are moved to][raketask] `/home/git/repositories.old`, +so if `/home/git/repositories` is the mount point, then `mv` would be moving +things between mount points, and bad things could happen. Ideally, +`/home/git` would be the mount point, so then things would be moving within the +same mount point. This is guaranteed with Omnibus installations (because they +don't specify the full repository path but the parent path), but not for source +installations. + +--- + +Now that you've read that big fat warning above, let's edit the configuration +files and add the full paths of the alternative repository storage paths. In +the example below, we add two more mountpoints that are named `nfs` and `cephfs` +respectively. + +**For installations from source** + +1. Edit `gitlab.yml` and add the storage paths: + + ```yaml + repositories: + # Paths where repositories can be stored. Give the canonicalized absolute pathname. + # NOTE: REPOS PATHS MUST NOT CONTAIN ANY SYMLINK!!! + storages: # You must have at least a 'default' storage path. + default: /home/git/repositories + nfs: /mnt/nfs/repositories + cephfs: /mnt/cephfs/repositories + ``` + +1. [Restart GitLab] for the changes to take effect. + +>**Note:** +The [`gitlab_shell: repos_path` entry][repospath] in `gitlab.yml` will be +deprecated and replaced by `repositories: storages` in the future, so if you +are upgrading from a version prior to 8.10, make sure to add the configuration +as described in the step above. After you make the changes and confirm they are +working, you can remove the `repos_path` line. + +--- + +**For Omnibus installations** + +1. Edit `/etc/gitlab/gitlab.rb` by appending the rest of the paths to the + default one: + + ```ruby + git_data_dirs({ + "default" => "/var/opt/gitlab/git-data", + "nfs" => "/mnt/nfs/git-data", + "cephfs" => "/mnt/cephfs/git-data" + }) + ``` + + Note that Omnibus stores the repositories in a `repositories` subdirectory + of the `git-data` directory. + +## Choose where new project repositories will be stored + +Once you set the multiple storage paths, you can choose where new projects will +be stored via the **Application Settings** in the Admin area. + +![Choose repository storage path in Admin area](img/repository_storages_admin_ui.png) + +Beginning with GitLab 8.13.4, multiple paths can be chosen. New projects will be +randomly placed on one of the selected paths. + +[ce-4578]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4578 +[restart gitlab]: restart_gitlab.md#installations-from-source +[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure +[backups]: ../raketasks/backup_restore.md +[raketask]: https://gitlab.com/gitlab-org/gitlab-ce/blob/033e5423a2594e08a7ebcd2379bd2331f4c39032/lib/backup/repository.rb#L54-56 +[repospath]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-9-stable/config/gitlab.yml.example#L457 diff --git a/doc/administration/repository_storages.md b/doc/administration/repository_storages.md index ab70557b69a..9d41ba77f34 100644 --- a/doc/administration/repository_storages.md +++ b/doc/administration/repository_storages.md @@ -1,102 +1,3 @@ # Repository storages -> [Introduced][ce-4578] in GitLab 8.10. - -GitLab allows you to define multiple repository storage paths to distribute the -storage load between several mount points. - ->**Notes:** -> -- You must have at least one storage path called `default`. -- The paths are defined in key-value pairs. The key is an arbitrary name you - can pick to name the file path. -- The target directories and any of its subpaths must not be a symlink. - -## Configure GitLab - ->**Warning:** -In order for [backups] to work correctly, the storage path must **not** be a -mount point and the GitLab user should have correct permissions for the parent -directory of the path. In Omnibus GitLab this is taken care of automatically, -but for source installations you should be extra careful. -> -The thing is that for compatibility reasons `gitlab.yml` has a different -structure than Omnibus. In `gitlab.yml` you indicate the path for the -repositories, for example `/home/git/repositories`, while in Omnibus you -indicate `git_data_dirs`, which for the example above would be `/home/git`. -Then, Omnibus will create a `repositories` directory under that path to use with -`gitlab.yml`. -> -This little detail matters because while restoring a backup, the current -contents of `/home/git/repositories` [are moved to][raketask] `/home/git/repositories.old`, -so if `/home/git/repositories` is the mount point, then `mv` would be moving -things between mount points, and bad things could happen. Ideally, -`/home/git` would be the mount point, so then things would be moving within the -same mount point. This is guaranteed with Omnibus installations (because they -don't specify the full repository path but the parent path), but not for source -installations. - ---- - -Now that you've read that big fat warning above, let's edit the configuration -files and add the full paths of the alternative repository storage paths. In -the example below, we add two more mountpoints that are named `nfs` and `cephfs` -respectively. - -**For installations from source** - -1. Edit `gitlab.yml` and add the storage paths: - - ```yaml - repositories: - # Paths where repositories can be stored. Give the canonicalized absolute pathname. - # NOTE: REPOS PATHS MUST NOT CONTAIN ANY SYMLINK!!! - storages: # You must have at least a 'default' storage path. - default: /home/git/repositories - nfs: /mnt/nfs/repositories - cephfs: /mnt/cephfs/repositories - ``` - -1. [Restart GitLab] for the changes to take effect. - ->**Note:** -The [`gitlab_shell: repos_path` entry][repospath] in `gitlab.yml` will be -deprecated and replaced by `repositories: storages` in the future, so if you -are upgrading from a version prior to 8.10, make sure to add the configuration -as described in the step above. After you make the changes and confirm they are -working, you can remove the `repos_path` line. - ---- - -**For Omnibus installations** - -1. Edit `/etc/gitlab/gitlab.rb` by appending the rest of the paths to the - default one: - - ```ruby - git_data_dirs({ - "default" => "/var/opt/gitlab/git-data", - "nfs" => "/mnt/nfs/git-data", - "cephfs" => "/mnt/cephfs/git-data" - }) - ``` - - Note that Omnibus stores the repositories in a `repositories` subdirectory - of the `git-data` directory. - -## Choose where new project repositories will be stored - -Once you set the multiple storage paths, you can choose where new projects will -be stored via the **Application Settings** in the Admin area. - -![Choose repository storage path in Admin area](img/repository_storages_admin_ui.png) - -Beginning with GitLab 8.13.4, multiple paths can be chosen. New projects will be -randomly placed on one of the selected paths. - -[ce-4578]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4578 -[restart gitlab]: restart_gitlab.md#installations-from-source -[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure -[backups]: ../raketasks/backup_restore.md -[raketask]: https://gitlab.com/gitlab-org/gitlab-ce/blob/033e5423a2594e08a7ebcd2379bd2331f4c39032/lib/backup/repository.rb#L54-56 -[repospath]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-9-stable/config/gitlab.yml.example#L457 +This document was moved to a [new location](repository_storage_paths.md). diff --git a/doc/api/commits.md b/doc/api/commits.md index 5c11d0f83bb..53ce381c8ae 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -245,7 +245,7 @@ Example response: ```json [ { - "diff": "--- a/doc/update/5.4-to-6.0.md\n+++ b/doc/update/5.4-to-6.0.md\n@@ -71,6 +71,8 @@\n sudo -u git -H bundle exec rake migrate_keys RAILS_ENV=production\n sudo -u git -H bundle exec rake migrate_inline_notes RAILS_ENV=production\n \n+sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production\n+\n ```\n \n ### 6. Update config files", + "diff": "--- a/doc/update/5.4-to-6.0.md\n+++ b/doc/update/5.4-to-6.0.md\n@@ -71,6 +71,8 @@\n sudo -u git -H bundle exec rake migrate_keys RAILS_ENV=production\n sudo -u git -H bundle exec rake migrate_inline_notes RAILS_ENV=production\n \n+sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production\n+\n ```\n \n ### 6. Update config files", "new_path": "doc/update/5.4-to-6.0.md", "old_path": "doc/update/5.4-to-6.0.md", "a_mode": null, diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index ffc310ec8c7..5377bf9ee80 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -14,6 +14,12 @@ Apart from those, here is an collection of tutorials and guides on setting up yo - [Test a Phoenix application](test-phoenix-application.md) - [Using `dpl` as deployment tool](deployment/README.md) - [Example project that shows how to use Review Apps](https://gitlab.com/gitlab-examples/review-apps-nginx/) +- [Run PHP Composer & NPM scripts then deploy them to a staging server](deployment/composer-npm-deploy.md) +- Help your favorite programming language and GitLab by sending a merge request + with a guide for that language. + +## Outside the documentation + - [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) - [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples) - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) diff --git a/doc/ci/examples/deployment/composer-npm-deploy.md b/doc/ci/examples/deployment/composer-npm-deploy.md new file mode 100644 index 00000000000..5334a73e1f5 --- /dev/null +++ b/doc/ci/examples/deployment/composer-npm-deploy.md @@ -0,0 +1,156 @@ +## Running Composer and NPM scripts with deployment via SCP + +This guide covers the building dependencies of a PHP project while compiling assets via an NPM script. + +While is possible to create your own image with custom PHP and Node JS versions, for brevity, we will use an existing [Docker image](https://hub.docker.com/r/tetraweb/php/) that contains both PHP and NodeJS installed. + + +```yaml +image: tetraweb/php +``` + +The next step is to install zip/unzip packages and make composer available. We will place these in the `before_script` section: + +```yaml +before_script: + - apt-get update + - apt-get install zip unzip + - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" + - php composer-setup.php + - php -r "unlink('composer-setup.php');" +``` + +This will make sure we have all requirements ready. Next, we want to run `composer update` to fetch all PHP dependencies and `npm install` to load node packages, then run the `npm` script. We need to append them into `before_script` section: + +```yaml +before_script: + # ... + - php composer.phar update + - npm install + - npm run deploy +``` + +In this particular case, the `npm deploy` script is a Gulp script that does the following: + +1. Compile CSS & JS +2. Create sprites +3. Copy various assets (images, fonts) around +4. Replace some strings + +All these operations will put all files into a `build` folder, which is ready to be deployed to a live server. + +### How to transfer files to a live server? + +You have multiple options: rsync, scp, sftp and so on. For now, we will use scp. + +To make this work, you need to add a GitLab Secret Variable (accessible on _gitlab.example/your-project-name/variables_). That variable will be called `STAGING_PRIVATE_KEY` and it's the **private** ssh key of your server. + +#### Security tip + +Create a user that has access **only** to the folder that needs to be updated! + +After you create that variable, you need to make sure that key will be added to the docker container on run: + +```yaml +before_script: + # - .... + - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' + - mkdir -p ~/.ssh + - eval $(ssh-agent -s) + - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' +``` + +In order, this means that: + +1. We check if the `ssh-agent` is available and we install it if it's not; +2. We create the `~/.ssh` folder; +3. We make sure we're running bash; +4. We disable host checking (we don't ask for user accept when we first connect to a server; and since every build will equal a first connect, we kind of need this) + +And this is basically all you need in the `before_script` section. + +## How to deploy things? + +As we stated above, we need to deploy the `build` folder from the docker image to our server. To do so, we create a new job: + +```yaml +stage_deploy: + artifacts: + paths: + - build/ + only: + - dev + script: + - ssh-add <(echo "$STAGING_PRIVATE_KEY") + - ssh -p22 server_user@server_host "mkdir htdocs/wp-content/themes/_tmp" + - scp -P22 -r build/* server_user@server_host:htdocs/wp-content/themes/_tmp + - ssh -p22 server_user@server_host "mv htdocs/wp-content/themes/live htdocs/wp-content/themes/_old && mv htdocs/wp-content/themes/_tmp htdocs/wp-content/themes/live" + - ssh -p22 server_user@server_host "rm -rf htdocs/wp-content/themes/_old" +``` + +### What's going on here? + +1. `only:dev` means that this build will run only when something is pushed to the `dev` branch. You can remove this block completely and have everything be ran on every push (but probably this is something you don't want) +2. `ssh-add ...` we will add that private key you added on the web UI to the docker container +3. We will connect via `ssh` and create a new `_tmp` folder +4. We will connect via `scp` and upload the `build` folder (which was generated by a `npm` script) to our previously created `_tmp` folder +5. We will connect again to `ssh` and move the `live` folder to an `_old` folder, then move `_tmp` to `live`. +6. We connect to ssh and remove the `_old` folder + +What's the deal with the artifacts? We just tell GitLab CI to keep the `build` directory (later on, you can download that as needed). + +#### Why we do it this way? + +If you're using this only for stage server, you could do this in two steps: + +```yaml +- ssh -p22 server_user@server_host "rm -rf htdocs/wp-content/themes/live/*" +- scp -P22 -r build/* server_user@server_host:htdocs/wp-content/themes/live +``` + +The problem is that there will be a small period of time when you won't have the app on your server. + +So we use so many steps because we want to make sure that at any given time we have a functional app in place. + +## Where to go next? + +Since this was a WordPress project, I gave real life code snippets. Some ideas you can pursuit: + +- Having a slightly different script for `master` branch will allow you to deploy to a production server from that branch and to a stage server from any other branches; +- Instead of pushing it live, you can push it to WordPress official repo (with creating a SVN commit & stuff); +- You could generate i18n text domains on the fly. + +--- + +Our final `.gitlab-ci.yml` will look like this: + +```yaml +image: tetraweb/php + +before_script: + - apt-get update + - apt-get install zip unzip + - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" + - php composer-setup.php + - php -r "unlink('composer-setup.php');" + - php composer.phar update + - npm install + - npm run deploy + - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )' + - mkdir -p ~/.ssh + - eval $(ssh-agent -s) + - '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config' + +stage_deploy: + artifacts: + paths: + - build/ + only: + - dev + script: + - ssh-add <(echo "$STAGING_PRIVATE_KEY") + - ssh -p22 server_user@server_host "mkdir htdocs/wp-content/themes/_tmp" + - scp -P22 -r build/* server_user@server_host:htdocs/wp-content/themes/_tmp + - ssh -p22 server_user@server_host "mv htdocs/wp-content/themes/live htdocs/wp-content/themes/_old && mv htdocs/wp-content/themes/_tmp htdocs/wp-content/themes/live" + - ssh -p22 server_user@server_host "rm -rf htdocs/wp-content/themes/_old" +```
\ No newline at end of file diff --git a/doc/ci/examples/php.md b/doc/ci/examples/php.md index 82ffb841729..5eeec92d976 100644 --- a/doc/ci/examples/php.md +++ b/doc/ci/examples/php.md @@ -235,7 +235,11 @@ cache: before_script: # Install composer dependencies -- curl --silent --show-error https://getcomposer.org/installer | php +- wget https://composer.github.io/installer.sig -O - -q | tr -d '\n' > installer.sig +- php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" +- php -r "if (hash_file('SHA384', 'composer-setup.php') === file_get_contents('installer.sig')) { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" +- php composer-setup.php +- php -r "unlink('composer-setup.php'); unlink('installer.sig');" - php composer.phar install ... diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 75a0897eb15..f11257be5c3 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -86,7 +86,7 @@ used for time of the build. The configuration of this feature is covered in ### before_script `before_script` is used to define the command that should be run before all -builds, including deploy builds. This can be an array or a multi-line string. +builds, including deploy builds, but after the restoration of artifacts. This can be an array or a multi-line string. ### after_script diff --git a/doc/development/README.md b/doc/development/README.md index 6f2ca7b8590..265df98fb87 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -18,6 +18,7 @@ - [Frontend guidelines](frontend.md) - [SQL guidelines](sql.md) for working with SQL queries - [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers +- [`Gemfile` guidelines](gemfile.md) ## Process diff --git a/doc/development/gemfile.md b/doc/development/gemfile.md new file mode 100644 index 00000000000..ec9718cea71 --- /dev/null +++ b/doc/development/gemfile.md @@ -0,0 +1,14 @@ +# `Gemfile` guidelines + +When adding a new entry to `Gemfile` or upgrading an existing dependency pay +attention to the following rules. + +## No gems fetched from git repositories + +We do not allow gems that are fetched from git repositories. All gems have +to be available in the RubyGems index. We want to minimize external build +dependencies and build times. + +## License compliance + +Refer to [licensing guidelines](licensing.md) for ensuring license compliance. diff --git a/doc/development/performance.md b/doc/development/performance.md index f936a49a2aa..c1f129e576c 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -211,6 +211,41 @@ suite first. See the [StackProf documentation](https://github.com/tmm1/stackprof/blob/master/README.md) for details. +## RSpec profiling + +GitLab's development environment also includes the +[rspec_profiling](https://github.com/foraker/rspec_profiling) gem, which is used +to collect data on spec execution times. This is useful for analyzing the +performance of the test suite itself, or seeing how the performance of a spec +may have changed over time. + +To activate profiling in your local environment, run the following: + +``` +$ export RSPEC_PROFILING=yes +$ rake rspec_profiling:install +``` + +This creates an SQLite3 database in `tmp/rspec_profiling`, into which statistics +are saved every time you run specs with the `RSPEC_PROFILING` environment +variable set. + +Ad-hoc investigation of the collected results can be performed in an interactive +shell: + +``` +$ rake rspec_profiling:console +irb(main):001:0> results.count +=> 231 +irb(main):002:0> results.last.attributes.keys +=> ["id", "commit", "date", "file", "line_number", "description", "time", "status", "exception", "query_count", "query_time", "request_count", "request_time", "created_at", "updated_at"] +irb(main):003:0> results.where(status: "passed").average(:time).to_s +=> "0.211340155844156" +``` +These results can also be placed into a PostgreSQL database by setting the +`RSPEC_PROFILING_POSTGRES_URL` variable. This is used to profile the test suite +when running in the CI environment. + ## Importance of Changes When working on performance improvements, it's important to always ask yourself diff --git a/doc/development/ux_guide/components.md b/doc/development/ux_guide/components.md index 706bb180912..1b19587a0b8 100644 --- a/doc/development/ux_guide/components.md +++ b/doc/development/ux_guide/components.md @@ -109,7 +109,7 @@ Dropdowns are used to allow users to choose one (or many) options from a list of ### Max size -The max height for dropdowns should target **10-15 items**. If the height of the dropdown is too large, the list becomes very hard to parse and it is easy to visually lose track of the item you are looking for. Usability also suffers as more mouse movement is required, and you have a larger area in which you hijack the scroll away from the page level. While it may initially seem counterintuitive to not show as many items as you can, it is actually quicker and easier to process the information when it is cropped at a reasonable height. +The max height for dropdowns should target **10-15** single line items, or **7-10** multi-line items. If the height of the dropdown is too large, the list becomes very hard to parse and it is easy to visually lose track of the item you are looking for. Usability also suffers as more mouse movement is required, and you have a larger area in which you hijack the scroll away from the page level. While it may initially seem counterintuitive to not show as many items as you can, it is actually quicker and easier to process the information when it is cropped at a reasonable height. --- diff --git a/doc/development/ux_guide/copy.md b/doc/development/ux_guide/copy.md index 31cc9dd2a53..5b65d531e54 100644 --- a/doc/development/ux_guide/copy.md +++ b/doc/development/ux_guide/copy.md @@ -102,6 +102,12 @@ When using the <kbd>Alt</kbd> keystrokes in Windows, use the numeric keypad, not ## Terminology Only use the terms in the tables below. +### Projects and Groups + +| Term | Use | :no_entry_sign: Don't | +| ---- | --- | ----- | +| Members | When discussing the people who are a part of a project or a group. | Don't use `users`. | + ### Issues #### Adjectives (states) @@ -117,7 +123,7 @@ Use `5 open issues` and don’t use `5 pending issues`. #### Verbs (actions) -| Term | Use | Don’t | +| Term | Use | :no_entry_sign: Don’t | | ---- | --- | --- | | Add | Add an issue | Don’t use `create` or `new` | | View | View an open or closed issue || @@ -158,7 +164,7 @@ The form should be titled `Edit issue`. The submit button should be labeled `Sav #### Verbs (actions) -| Term | Use | Don’t | +| Term | Use | :no_entry_sign: Don’t | | ---- | --- | --- | | Add | Add a merge request | Do not use `create` or `new` | | View | View an open or merged merge request || diff --git a/doc/gitlab-basics/add-image.md b/doc/gitlab-basics/add-image.md index 476b48a217c..1a44123aa81 100644 --- a/doc/gitlab-basics/add-image.md +++ b/doc/gitlab-basics/add-image.md @@ -1,62 +1,56 @@ # How to add an image -The following are the steps to add images to your repository in -GitLab: +Using your standard tool for copying files (e.g. Finder in Mac OS, or Explorer +in Windows, or...), put the image file into the GitLab project. You can find the +project as a regular folder in your files. -Find the image that you’d like to add. +Go to your [shell](command-line-commands.md), and move into the folder of your +Gitlab project. This usually means running the following command until you get +to the desired destination: -In your computer files, find the GitLab project to which you'd like to add the image -(you'll find it as a regular file). Click on every file until you find exactly where you'd -like to add the image. There, paste the image. - -Go to your [shell](command-line-commands.md), and add the following commands: - -Add this command for every directory that you'd like to open: ``` -cd NAME-OF-FILE-YOU'D-LIKE-TO-OPEN +cd NAME-OF-FOLDER-YOU'D-LIKE-TO-OPEN ``` -Create a new branch: -``` -git checkout -b NAME-OF-BRANCH -``` +Check if your image is actually present in the directory (if you are in Windows, +use `dir` instead): -Check if your image was correctly added to the directory: ``` ls ``` You should see the name of the image in the list shown. -Move up the hierarchy through directories: -``` -cd ../ -``` +Check the status: -Check the status and you should see your image’s name in red: ``` git status ``` -Add your changes: +Your image's name should appear in red, so `git` took notice of it! Now add it +to the repository: + ``` git add NAME-OF-YOUR-IMAGE ``` -Check the status and you should see your image’s name in green: +Check the status again, your image's name should have turned green: + ``` git status ``` -Add the commit: +Commit: + ``` -git commit -m “DESCRIBE COMMIT IN A FEW WORDS” +git commit -m "DESCRIBE COMMIT IN A FEW WORDS" ``` -Now you can push (send) your changes (in the branch NAME-OF-BRANCH) to GitLab (the git remote named 'origin'): +Now you can push (send) your changes (in the branch NAME-OF-BRANCH) to GitLab +(the git remote named 'origin'): + ``` git push origin NAME-OF-BRANCH ``` -Your image will be added to your branch in your repository in GitLab. Create a [Merge Request](add-merge-request.md) -to integrate your changes to your project. +Your image will be added to your branch in your repository in GitLab. diff --git a/doc/install/installation.md b/doc/install/installation.md index 3e7674e13ab..425c5d93efb 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -448,7 +448,7 @@ Check if GitLab and its environment are configured correctly: ### Compile Assets - sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production + sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production ### Start Your GitLab Instance diff --git a/doc/install/relative_url.md b/doc/install/relative_url.md index 44d2a14f366..713d11b75e4 100644 --- a/doc/install/relative_url.md +++ b/doc/install/relative_url.md @@ -113,14 +113,6 @@ Make sure to follow all steps below: If you are using a custom init script, make sure to edit the above gitlab-workhorse setting as needed. -1. After all the above changes recompile the assets. This is an important task - and will take some time to complete depending on the server resources: - - ``` - cd /home/git/gitlab - sudo -u git -H bundle exec rake assets:clean assets:precompile RAILS_ENV=production - ``` - 1. [Restart GitLab][] for the changes to take effect. ### Disable relative URL in GitLab diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index 54d523b59fd..154a0f817da 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -57,7 +57,7 @@ sudo -u git -H bundle clean sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production # Clean up assets and cache -sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production +sudo -u git -H bundle exec rake gitlab:assets:clean gitlab:assets:compile cache:clear RAILS_ENV=production ``` ### 4. Update gitlab-workhorse to the corresponding version diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md index df33d54cc26..cc688a7f99c 100644 --- a/doc/user/profile/account/two_factor_authentication.md +++ b/doc/user/profile/account/two_factor_authentication.md @@ -140,70 +140,68 @@ into the password field. ## Recovery options -If you lose your code generation device (such as your mobile phone) and you need -to disable two-factor authentication on your account, you have several options. +To disable two-factor authentication on your account (for example, if you +have lost your code generation device) you can: +* [Use a saved recovery code](#use-a-saved-recovery-code) +* [Generate new recovery codes using SSH](#generate-new-recovery-codes-using-SSH) +* [Ask a GitLab administrator to disable two-factor authentication on your account](#ask-a-gitlab-administrator-to-disable-two-factor-authentication-on-your-account) ### Use a saved recovery code -When you enabled two-factor authentication for your account, a series of -recovery codes were generated. If you saved those codes somewhere safe, you -may use one to sign in. +Enabling two-factor authentication for your account generated several recovery +codes. If you saved these codes, you can use one of them to sign in. -First, enter your username/email and password on the GitLab sign in page. When -prompted for a two-factor code, enter one of the recovery codes you saved -previously. +To use a recovery code, enter your username/email and password on the GitLab +sign-in page. When prompted for a two-factor code, enter the recovery code. -> **Note:** Once a particular recovery code has been used, it cannot be used again. - You may still use the other saved recovery codes at a later time. +> **Note:** Once you use a recovery code, you cannot re-use it. You can still + use the other recovery codes you saved. ### Generate new recovery codes using SSH -It's not uncommon for users to forget to save the recovery codes when enabling -two-factor authentication. If you have an SSH key added to your GitLab account, -you can generate a new set of recovery codes using SSH. - -Run `ssh git@gitlab.example.com 2fa_recovery_codes`. You will be prompted to -confirm that you wish to generate new codes. If you choose to continue, any -previously saved codes will be invalidated. - -```bash -$ ssh git@gitlab.example.com 2fa_recovery_codes -Are you sure you want to generate new two-factor recovery codes? -Any existing recovery codes you saved will be invalidated. (yes/no) -yes - -Your two-factor authentication recovery codes are: - -119135e5a3ebce8e -11f6v2a498810dcd -3924c7ab2089c902 -e79a3398bfe4f224 -34bd7b74adbc8861 -f061691d5107df1a -169bf32a18e63e7f -b510e7422e81c947 -20dbed24c5e74663 -df9d3b9403b9c9f0 - -During sign in, use one of the codes above when prompted for -your two-factor code. Then, visit your Profile Settings and add -a new device so you do not lose access to your account again. -``` - -Next, go to the GitLab sign in page and enter your username/email and password. -When prompted for a two-factor code, enter one of the recovery codes obtained -from the command line output. - -> **Note:** After signing in, you should immediately visit your **Profile Settings - -> Account** to set up two-factor authentication with a new device. - -### Ask a GitLab administrator to disable two-factor on your account - -If the above two methods are not possible, you may ask a GitLab global -administrator to disable two-factor authentication for your account. Please -be aware that this will temporarily leave your account in a less secure state. -You should sign in and re-enable two-factor authentication as soon as possible -after the administrator disables it. +Users often forget to save their recovery codes when enabling two-factor +authentication. If an SSH key is added to your GitLab account, you can generate +a new set of recovery codes with SSH. + +1. Run `ssh git@gitlab.example.com 2fa_recovery_codes`. +2. You are prompted to confirm that you want to generate new codes. Continuing this process invalidates previously saved codes. + ``` + bash + $ ssh git@gitlab.example.com 2fa_recovery_codes + Are you sure you want to generate new two-factor recovery codes? + Any existing recovery codes you saved will be invalidated. (yes/no) + + yes + + Your two-factor authentication recovery codes are: + + 119135e5a3ebce8e + 11f6v2a498810dcd + 3924c7ab2089c902 + e79a3398bfe4f224 + 34bd7b74adbc8861 + f061691d5107df1a + 169bf32a18e63e7f + b510e7422e81c947 + 20dbed24c5e74663 + df9d3b9403b9c9f0 + + During sign in, use one of the codes above when prompted for your + two-factor code. Then, visit your Profile Settings and add a new device + so you do not lose access to your account again. + ``` +3. Go to the GitLab sign-in page and enter your username/email and password. When prompted for a two-factor code, enter one of the recovery codes obtained +from the command-line output. + +> **Note:** After signing in, visit your **Profile Settings -> Account** immediately to set up two-factor authentication with a new + device. + +### Ask a GitLab administrator to disable two-factor authentication on your account + +If you cannot use a saved recovery code or generate new recovery codes, ask a +GitLab global administrator to disable two-factor authentication for your +account. This will temporarily leave your account in a less secure state. +Sign in and re-enable two-factor authentication as soon as possible. ## Note to GitLab administrators diff --git a/lib/gitlab/ci/trace_reader.rb b/lib/gitlab/ci/trace_reader.rb index 37e51536e8f..1d7ddeb3e0f 100644 --- a/lib/gitlab/ci/trace_reader.rb +++ b/lib/gitlab/ci/trace_reader.rb @@ -42,6 +42,7 @@ module Gitlab end chunks.join.lines.last(max_lines).join + .force_encoding(Encoding.default_external) end end end diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb index 4ebd48a3fc7..e20f5f6f514 100644 --- a/lib/gitlab/current_settings.rb +++ b/lib/gitlab/current_settings.rb @@ -30,7 +30,7 @@ module Gitlab end def in_memory_application_settings - @in_memory_application_settings ||= ::ApplicationSetting.new(::ApplicationSetting::DEFAULTS) + @in_memory_application_settings ||= ::ApplicationSetting.new(::ApplicationSetting.defaults) # In case migrations the application_settings table is not created yet, # we fallback to a simple OpenStruct rescue ActiveRecord::StatementInvalid, ActiveRecord::UnknownAttributeError @@ -38,7 +38,7 @@ module Gitlab end def fake_application_settings - OpenStruct.new(::ApplicationSetting::DEFAULTS) + OpenStruct.new(::ApplicationSetting.defaults) end private diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/shell.rb index 82e194c1af1..82e194c1af1 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/shell.rb diff --git a/lib/gitlab/backend/shell_adapter.rb b/lib/gitlab/shell_adapter.rb index fbe2a7a0d72..fbe2a7a0d72 100644 --- a/lib/gitlab/backend/shell_adapter.rb +++ b/lib/gitlab/shell_adapter.rb diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb index f3567f3ef85..e78d0c34a02 100644 --- a/lib/gitlab/upgrader.rb +++ b/lib/gitlab/upgrader.rb @@ -61,7 +61,7 @@ module Gitlab "Switch to new version" => %W(#{Gitlab.config.git.bin_path} checkout v#{latest_version}), "Install gems" => %W(bundle), "Migrate DB" => %W(bundle exec rake db:migrate), - "Recompile assets" => %W(bundle exec rake assets:clean assets:precompile), + "Recompile assets" => %W(bundle exec rake gitlab:assets:clean gitlab:assets:compile), "Clear cache" => %W(bundle exec rake cache:clear) } end diff --git a/lib/gitlab/view/presenter/base.rb b/lib/gitlab/view/presenter/base.rb index 83c8ba5c1cf..dbfe0941e4d 100644 --- a/lib/gitlab/view/presenter/base.rb +++ b/lib/gitlab/view/presenter/base.rb @@ -1,6 +1,8 @@ module Gitlab module View module Presenter + CannotOverrideMethodError = Class.new(StandardError) + module Base extend ActiveSupport::Concern diff --git a/lib/gitlab/view/presenter/delegated.rb b/lib/gitlab/view/presenter/delegated.rb index f4d330c590e..387ff0f5d43 100644 --- a/lib/gitlab/view/presenter/delegated.rb +++ b/lib/gitlab/view/presenter/delegated.rb @@ -8,6 +8,10 @@ module Gitlab @subject = subject attributes.each do |key, value| + if subject.respond_to?(key) + raise CannotOverrideMethodError.new("#{subject} already respond to #{key}!") + end + define_singleton_method(key) { value } end diff --git a/lib/support/deploy/deploy.sh b/lib/support/deploy/deploy.sh index adea4c7a747..ab46c47d8f5 100755 --- a/lib/support/deploy/deploy.sh +++ b/lib/support/deploy/deploy.sh @@ -31,8 +31,8 @@ echo 'Deploy: Bundle and migrate' sudo -u git -H bundle --without aws development test mysql --deployment sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production -sudo -u git -H bundle exec rake assets:clean RAILS_ENV=production -sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production +sudo -u git -H bundle exec rake gitlab:assets:clean RAILS_ENV=production +sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production # return stashed changes (if necessary) diff --git a/lib/tasks/gitlab/assets.rake b/lib/tasks/gitlab/assets.rake new file mode 100644 index 00000000000..5d884bf9f66 --- /dev/null +++ b/lib/tasks/gitlab/assets.rake @@ -0,0 +1,47 @@ +namespace :gitlab do + namespace :assets do + desc 'GitLab | Assets | Compile all frontend assets' + task :compile do + Rake::Task['assets:precompile'].invoke + Rake::Task['gitlab:assets:fix_urls'].invoke + end + + desc 'GitLab | Assets | Clean up old compiled frontend assets' + task :clean do + Rake::Task['assets:clean'].invoke + end + + desc 'GitLab | Assets | Remove all compiled frontend assets' + task :purge do + Rake::Task['assets:clobber'].invoke + end + + desc 'GitLab | Assets | Fix all absolute url references in CSS' + task :fix_urls do + css_files = Dir['public/assets/*.css'] + css_files.each do | file | + # replace url(/assets/*) with url(./*) + puts "Fixing #{file}" + system "sed", "-i", "-e", 's/url(\([\"\']\?\)\/assets\//url(\1.\//g', file + + # rewrite the corresponding gzip file (if it exists) + gzip = "#{file}.gz" + if File.exist?(gzip) + puts "Fixing #{gzip}" + + FileUtils.rm(gzip) + mtime = File.stat(file).mtime + + File.open(gzip, 'wb+') do |f| + gz = Zlib::GzipWriter.new(f, Zlib::BEST_COMPRESSION) + gz.mtime = mtime + gz.write IO.binread(file) + gz.close + + File.utime(mtime, mtime, f.path) + end + end + end + end + end +end diff --git a/spec/controllers/explore/projects_controller_spec.rb b/spec/controllers/explore/projects_controller_spec.rb new file mode 100644 index 00000000000..9dceeca168d --- /dev/null +++ b/spec/controllers/explore/projects_controller_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Explore::ProjectsController do + describe 'GET #trending' do + context 'sorting by update date' do + let(:project1) { create(:empty_project, :public, updated_at: 3.days.ago) } + let(:project2) { create(:empty_project, :public, updated_at: 1.day.ago) } + + before do + create(:trending_project, project: project1) + create(:trending_project, project: project2) + end + + it 'sorts by last updated' do + get :trending, sort: 'updated_desc' + + expect(assigns(:projects)).to eq [project2, project1] + end + + it 'sorts by oldest updated' do + get :trending, sort: 'updated_asc' + + expect(assigns(:projects)).to eq [project1, project2] + end + end + end +end diff --git a/spec/factories/trending_project.rb b/spec/factories/trending_project.rb new file mode 100644 index 00000000000..246176611dc --- /dev/null +++ b/spec/factories/trending_project.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + # TrendingProject + factory :trending_project, class: 'TrendingProject' do + project + end +end diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb index 8a155c3bfc5..93763f092fb 100644 --- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -43,14 +43,6 @@ describe 'Dropdown assignee', js: true, feature: true do expect(page).to have_css(js_dropdown_assignee, visible: true) end - it 'shows assigned to me link' do - filtered_search.set('assignee:') - - page.within js_dropdown_assignee do - expect(page).to have_content('Assigned to me') - end - end - it 'closes when the search bar is unfocused' do find('body').click() @@ -129,14 +121,6 @@ describe 'Dropdown assignee', js: true, feature: true do filtered_search.set('assignee:') end - it 'filters by current user' do - page.within js_dropdown_assignee do - click_button 'Assigned to me' - end - - expect(filtered_search.value).to eq("assignee:#{user.to_reference} ") - end - it 'fills in the assignee username when the assignee has not been filtered' do click_assignee(user_jacob.name) @@ -185,4 +169,22 @@ describe 'Dropdown assignee', js: true, feature: true do expect(page).to have_css(js_dropdown_assignee, visible: true) end end + + describe 'caching requests' do + it 'caches requests after the first load' do + filtered_search.set('assignee') + send_keys_to_filtered_search(':') + initial_size = dropdown_assignee_size + + expect(initial_size).to be > 0 + + new_user = create(:user) + project.team << [new_user, :master] + find('.filtered-search-input-container .clear-search').click + filtered_search.set('assignee') + send_keys_to_filtered_search(':') + + expect(dropdown_assignee_size).to eq(initial_size) + end + end end diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb index a5d5d9d4c5e..59e302f0e2d 100644 --- a/spec/features/issues/filtered_search/dropdown_author_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb @@ -157,4 +157,22 @@ describe 'Dropdown author', js: true, feature: true do expect(page).to have_css(js_dropdown_author, visible: true) end end + + describe 'caching requests' do + it 'caches requests after the first load' do + filtered_search.set('author') + send_keys_to_filtered_search(':') + initial_size = dropdown_author_size + + expect(initial_size).to be > 0 + + new_user = create(:user) + project.team << [new_user, :master] + find('.filtered-search-input-container .clear-search').click + filtered_search.set('author') + send_keys_to_filtered_search(':') + + expect(dropdown_author_size).to eq(initial_size) + end + end end diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb index f09ad2dd86b..5079eb8dd00 100644 --- a/spec/features/issues/filtered_search/dropdown_label_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb @@ -249,4 +249,21 @@ describe 'Dropdown label', js: true, feature: true do expect(page).to have_css(js_dropdown_label, visible: true) end end + + describe 'caching requests' do + it 'caches requests after the first load' do + filtered_search.set('label') + send_keys_to_filtered_search(':') + initial_size = dropdown_label_size + + expect(initial_size).to be > 0 + + create(:label, project: project) + find('.filtered-search-input-container .clear-search').click + filtered_search.set('label') + send_keys_to_filtered_search(':') + + expect(dropdown_label_size).to eq(initial_size) + end + end end diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb index 134e58ad586..0ce16715b86 100644 --- a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb @@ -219,4 +219,21 @@ describe 'Dropdown milestone', js: true, feature: true do expect(page).to have_css(js_dropdown_milestone, visible: true) end end + + describe 'caching requests' do + it 'caches requests after the first load' do + filtered_search.set('milestone') + send_keys_to_filtered_search(':') + initial_size = dropdown_milestone_size + + expect(initial_size).to be > 0 + + create(:milestone, project: project) + find('.filtered-search-input-container .clear-search').click + filtered_search.set('milestone') + send_keys_to_filtered_search(':') + + expect(dropdown_milestone_size).to eq(initial_size) + end + end end diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb index f48a0193545..3f70a6aa75f 100644 --- a/spec/features/issues/filtered_search/filter_issues_spec.rb +++ b/spec/features/issues/filtered_search/filter_issues_spec.rb @@ -773,7 +773,7 @@ describe 'Filter issues', js: true, feature: true do describe 'RSS feeds' do it 'updates atom feed link for project issues' do visit namespace_project_issues_path(project.namespace, project, milestone_title: milestone.title, assignee_id: user.id) - link = find('.nav-controls a', text: 'Subscribe') + link = find_link('Subscribe') params = CGI.parse(URI.parse(link[:href]).query) auto_discovery_link = find('link[type="application/atom+xml"]', visible: false) auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query) diff --git a/spec/features/issues/issue_sidebar_spec.rb b/spec/features/issues/issue_sidebar_spec.rb index bc068b5e7e0..1eb981942ea 100644 --- a/spec/features/issues/issue_sidebar_spec.rb +++ b/spec/features/issues/issue_sidebar_spec.rb @@ -2,6 +2,7 @@ require 'rails_helper' feature 'Issue Sidebar', feature: true do include WaitForAjax + include MobileHelpers let(:project) { create(:project, :public) } let(:issue) { create(:issue, project: project) } @@ -59,6 +60,23 @@ feature 'Issue Sidebar', feature: true do end end + context 'sidebar', js: true do + it 'changes size when the screen size is smaller' do + sidebar_selector = 'aside.right-sidebar.right-sidebar-collapsed' + # Resize the window + resize_screen_sm + # Make sure the sidebar is collapsed + expect(page).to have_css(sidebar_selector) + # Once is collapsed let's open the sidebard and reload + open_issue_sidebar + refresh + expect(page).to have_css(sidebar_selector) + # Restore the window size as it was including the sidebar + restore_window_size + open_issue_sidebar + end + end + context 'creating a new label', js: true do it 'shows option to crate a new label is present' do page.within('.block.labels') do @@ -109,4 +127,11 @@ feature 'Issue Sidebar', feature: true do def visit_issue(project, issue) visit namespace_project_issue_path(project.namespace, project, issue) end + + def open_issue_sidebar + page.within('aside.right-sidebar.right-sidebar-collapsed') do + find('.js-sidebar-toggle').click + sleep 1 + end + end end diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb index 55d5d082c6e..5d0314d5c09 100644 --- a/spec/features/projects/project_settings_spec.rb +++ b/spec/features/projects/project_settings_spec.rb @@ -37,7 +37,7 @@ describe 'Edit Project Settings', feature: true do it 'shows errors for invalid project path/name' do visit edit_namespace_project_path(project.namespace, project) - fill_in 'Project name', with: 'foo&bar' + fill_in 'project_name', with: 'foo&bar' fill_in 'Path', with: 'foo&bar' click_button 'Rename project' @@ -53,7 +53,7 @@ describe 'Edit Project Settings', feature: true do it 'shows error for invalid project name' do visit edit_namespace_project_path(project.namespace, project) - fill_in 'Project name', with: '🚀 foo bar ☁️' + fill_in 'project_name', with: '🚀 foo bar ☁️' click_button 'Rename project' diff --git a/spec/javascripts/commits_spec.js.es6 b/spec/javascripts/commits_spec.js.es6 new file mode 100644 index 00000000000..bb9a9072f3a --- /dev/null +++ b/spec/javascripts/commits_spec.js.es6 @@ -0,0 +1,50 @@ +/* global CommitsList */ + +//= require jquery.endless-scroll +//= require pager +//= require commits + +(() => { + describe('Commits List', () => { + beforeEach(() => { + setFixtures(` + <form class="commits-search-form" action="/h5bp/html5-boilerplate/commits/master"> + <input id="commits-search"> + </form> + <ol id="commits-list"></ol> + `); + }); + + it('should be defined', () => { + expect(CommitsList).toBeDefined(); + }); + + describe('on entering input', () => { + let ajaxSpy; + + beforeEach(() => { + CommitsList.init(25); + CommitsList.searchField.val(''); + spyOn(history, 'replaceState').and.stub(); + ajaxSpy = spyOn(jQuery, 'ajax').and.callFake((req) => { + req.success({ + data: '<li>Result</li>', + }); + }); + }); + + it('should save the last search string', () => { + CommitsList.searchField.val('GitLab'); + CommitsList.filterResults(); + expect(ajaxSpy).toHaveBeenCalled(); + expect(CommitsList.lastSearch).toEqual('GitLab'); + }); + + it('should not make ajax call if the input does not change', () => { + CommitsList.filterResults(); + expect(ajaxSpy).not.toHaveBeenCalled(); + expect(CommitsList.lastSearch).toEqual(''); + }); + }); + }); +})(); diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6 b/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6 index 19bd8d53219..89e49b7c511 100644 --- a/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6 +++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6 @@ -64,6 +64,68 @@ const updatedItem = gl.DropdownUtils.filterWithSymbol('@', input, item); expect(updatedItem.droplab_hidden).toBe(false); }); + + describe('filters multiple word title', () => { + const multipleWordItem = { + title: 'Community Contributions', + }; + + it('should filter with double quote', () => { + input.value = 'label:"'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should filter with double quote and symbol', () => { + input.value = 'label:~"'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should filter with double quote and multiple words', () => { + input.value = 'label:"community con'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should filter with double quote, symbol and multiple words', () => { + input.value = 'label:~"community con'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should filter with single quote', () => { + input.value = 'label:\''; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should filter with single quote and symbol', () => { + input.value = 'label:~\''; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should filter with single quote and multiple words', () => { + input.value = 'label:\'community con'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); + + it('should filter with single quote, symbol and multiple words', () => { + input.value = 'label:~\'community con'; + + const updatedItem = gl.DropdownUtils.filterWithSymbol('~', input, multipleWordItem); + expect(updatedItem.droplab_hidden).toBe(false); + }); + }); }); describe('filterHint', () => { @@ -130,5 +192,99 @@ expect(result).toBe(false); }); }); + + describe('getInputSelectionPosition', () => { + describe('word with trailing spaces', () => { + const value = 'label:none '; + + it('should return selectionStart when cursor is at the trailing space', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 11, + value, + }); + + expect(left).toBe(11); + expect(right).toBe(11); + }); + + it('should return input when cursor is at the start of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 0, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(10); + }); + + it('should return input when cursor is at the middle of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 7, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(10); + }); + + it('should return input when cursor is at the end of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 10, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(10); + }); + }); + + describe('multiple words', () => { + const value = 'label:~"Community Contribution"'; + + it('should return input when cursor is after the first word', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 17, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(31); + }); + + it('should return input when cursor is before the second word', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 18, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(31); + }); + }); + + describe('incomplete multiple words', () => { + const value = 'label:~"Community Contribution'; + + it('should return entire input when cursor is at the start of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 0, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(30); + }); + + it('should return entire input when cursor is at the end of input', () => { + const { left, right } = gl.DropdownUtils.getInputSelectionPosition({ + selectionStart: 30, + value, + }); + + expect(left).toBe(0); + expect(right).toBe(30); + }); + }); + }); }); })(); diff --git a/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6 new file mode 100644 index 00000000000..c8b5c2b36ad --- /dev/null +++ b/spec/javascripts/filtered_search/filtered_search_manager_spec.js.es6 @@ -0,0 +1,68 @@ +/* global Turbolinks */ + +//= require turbolinks +//= require lib/utils/common_utils +//= require filtered_search/filtered_search_token_keys +//= require filtered_search/filtered_search_tokenizer +//= require filtered_search/filtered_search_dropdown_manager +//= require filtered_search/filtered_search_manager + +(() => { + describe('Filtered Search Manager', () => { + describe('search', () => { + let manager; + const defaultParams = '?scope=all&utf8=✓&state=opened'; + + function getInput() { + return document.querySelector('.filtered-search'); + } + + beforeEach(() => { + setFixtures(` + <input type='text' class='filtered-search' /> + `); + + spyOn(gl.FilteredSearchManager.prototype, 'bindEvents').and.callFake(() => {}); + spyOn(gl.FilteredSearchManager.prototype, 'loadSearchParamsFromURL').and.callFake(() => {}); + spyOn(gl.FilteredSearchDropdownManager.prototype, 'setDropdown').and.callFake(() => {}); + spyOn(gl.utils, 'getParameterByName').and.returnValue(null); + + manager = new gl.FilteredSearchManager(); + }); + + afterEach(() => { + getInput().outerHTML = ''; + }); + + it('should search with a single word', () => { + getInput().value = 'searchTerm'; + + spyOn(Turbolinks, 'visit').and.callFake((url) => { + expect(url).toEqual(`${defaultParams}&search=searchTerm`); + }); + + manager.search(); + }); + + it('should search with multiple words', () => { + getInput().value = 'awesome search terms'; + + spyOn(Turbolinks, 'visit').and.callFake((url) => { + expect(url).toEqual(`${defaultParams}&search=awesome+search+terms`); + }); + + manager.search(); + }); + + it('should search with special characters', () => { + getInput().value = '~!@#$%^&*()_+{}:<>,.?/'; + + spyOn(Turbolinks, 'visit').and.callFake((url) => { + expect(url).toEqual(`${defaultParams}&search=~!%40%23%24%25%5E%26*()_%2B%7B%7D%3A%3C%3E%2C.%3F%2F`); + }); + + manager.search(); + }); + }); + }); +})(); diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6 index 6df7c0e44ef..9d9097419ea 100644 --- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6 +++ b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6 @@ -72,6 +72,12 @@ const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); expect(result).toEqual(tokenKeys[0]); }); + + it('should return alternative tokenKey when found by key param', () => { + const tokenKeys = gl.FilteredSearchTokenKeys.getAlternatives(); + const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`); + expect(result).toEqual(tokenKeys[0]); + }); }); describe('searchByConditionUrl', () => { diff --git a/spec/lib/gitlab/ci/trace_reader_spec.rb b/spec/lib/gitlab/ci/trace_reader_spec.rb index f06d78694d6..ff5551bf703 100644 --- a/spec/lib/gitlab/ci/trace_reader_spec.rb +++ b/spec/lib/gitlab/ci/trace_reader_spec.rb @@ -11,13 +11,25 @@ describe Gitlab::Ci::TraceReader do last_lines = random_lines expected = lines.last(last_lines).join + result = subject.read(last_lines: last_lines) - expect(subject.read(last_lines: last_lines)).to eq(expected) + expect(result).to eq(expected) + expect(result.encoding).to eq(Encoding.default_external) end end it 'returns everything if trying to get too many lines' do - expect(build_subject.read(last_lines: lines.size * 2)).to eq(lines.join) + result = build_subject.read(last_lines: lines.size * 2) + + expect(result).to eq(lines.join) + expect(result.encoding).to eq(Encoding.default_external) + end + + it 'returns all contents if last_lines is not specified' do + result = build_subject.read + + expect(result).to eq(lines.join) + expect(result.encoding).to eq(Encoding.default_external) end it 'raises an error if not passing an integer for last_lines' do diff --git a/spec/lib/gitlab/view/presenter/delegated_spec.rb b/spec/lib/gitlab/view/presenter/delegated_spec.rb index 888ab80cad5..e9d4af54389 100644 --- a/spec/lib/gitlab/view/presenter/delegated_spec.rb +++ b/spec/lib/gitlab/view/presenter/delegated_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::View::Presenter::Delegated do - let(:project) { double(:project, bar: 'baz') } + let(:project) { double(:project, user: 'John Doe') } let(:presenter_class) do Class.new(described_class) end @@ -12,10 +12,14 @@ describe Gitlab::View::Presenter::Delegated do describe '#initialize' do it 'takes arbitrary key/values and exposes them' do - presenter = presenter_class.new(project, user: 'user', foo: 'bar') + presenter = presenter_class.new(project, current_user: 'Jane Doe') - expect(presenter.user).to eq('user') - expect(presenter.foo).to eq('bar') + expect(presenter.current_user).to eq('Jane Doe') + end + + it 'raise an error if the presentee already respond to method' do + expect { presenter_class.new(project, user: 'Jane Doe') }. + to raise_error Gitlab::View::Presenter::CannotOverrideMethodError end end @@ -23,7 +27,7 @@ describe Gitlab::View::Presenter::Delegated do it 'forwards missing methods to subject' do presenter = presenter_class.new(project) - expect(presenter.bar).to eq('baz') + expect(presenter.user).to eq('John Doe') end end end diff --git a/spec/lib/gitlab/view/presenter/factory_spec.rb b/spec/lib/gitlab/view/presenter/factory_spec.rb index 55c5ecbf92f..70d2e22b48f 100644 --- a/spec/lib/gitlab/view/presenter/factory_spec.rb +++ b/spec/lib/gitlab/view/presenter/factory_spec.rb @@ -22,13 +22,6 @@ describe Gitlab::View::Presenter::Factory do end describe '#fabricate!' do - it 'exposes given params' do - presenter = described_class.new(build, user: 'user', foo: 'bar').fabricate! - - expect(presenter.user).to eq('user') - expect(presenter.foo).to eq('bar') - end - it 'detects the presenter based on the given subject' do presenter = described_class.new(build).fabricate! diff --git a/spec/lib/gitlab/view/presenter/simple_spec.rb b/spec/lib/gitlab/view/presenter/simple_spec.rb index b489bdf1981..1795ed2405b 100644 --- a/spec/lib/gitlab/view/presenter/simple_spec.rb +++ b/spec/lib/gitlab/view/presenter/simple_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::View::Presenter::Simple do - let(:project) { double(:project) } + let(:project) { double(:project, user: 'John Doe') } let(:presenter_class) do Class.new(described_class) end @@ -12,10 +12,15 @@ describe Gitlab::View::Presenter::Simple do describe '#initialize' do it 'takes arbitrary key/values and exposes them' do - presenter = presenter_class.new(project, user: 'user', foo: 'bar') + presenter = presenter_class.new(project, current_user: 'Jane Doe') - expect(presenter.user).to eq('user') - expect(presenter.foo).to eq('bar') + expect(presenter.current_user).to eq('Jane Doe') + end + + it 'override the presentee attributes' do + presenter = presenter_class.new(project, user: 'Jane Doe') + + expect(presenter.user).to eq('Jane Doe') end end @@ -23,7 +28,7 @@ describe Gitlab::View::Presenter::Simple do it 'does not forward missing methods to subject' do presenter = presenter_class.new(project) - expect { presenter.foo }.to raise_error(NoMethodError) + expect { presenter.user }.to raise_error(NoMethodError) end end end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index 4d57efd3c53..2f4a33a1868 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Ability, lib: true do describe '.can_edit_note?' do let(:project) { create(:empty_project) } - let!(:note) { create(:note_on_issue, project: project) } + let(:note) { create(:note_on_issue, project: project) } context 'using an anonymous user' do it 'returns false' do @@ -60,7 +60,7 @@ describe Ability, lib: true do describe '.users_that_can_read_project' do context 'using a public project' do it 'returns all the users' do - project = create(:project, :public) + project = create(:empty_project, :public) user = build(:user) expect(described_class.users_that_can_read_project([user], project)). @@ -69,7 +69,7 @@ describe Ability, lib: true do end context 'using an internal project' do - let(:project) { create(:project, :internal) } + let(:project) { create(:empty_project, :internal) } it 'returns users that are administrators' do user = build(:user, admin: true) @@ -120,7 +120,7 @@ describe Ability, lib: true do end context 'using a private project' do - let(:project) { create(:project, :private) } + let(:project) { create(:empty_project, :private) } it 'returns users that are administrators' do user = build(:user, admin: true) @@ -247,7 +247,7 @@ describe Ability, lib: true do end describe '.project_disabled_features_rules' do - let(:project) { create(:project, wiki_access_level: ProjectFeature::DISABLED) } + let(:project) { create(:empty_project, wiki_access_level: ProjectFeature::DISABLED) } subject { described_class.allowed(project.owner, project) } diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index f031876e812..47cd5075a7d 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Ci::Build, :models do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:build) { create(:ci_build, pipeline: pipeline) } let(:test_trace) { 'This is a test' } diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 2bdd611aeed..426be74cd02 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -284,7 +284,7 @@ describe Ci::Pipeline, models: true do end describe 'merge request metrics' do - let(:project) { FactoryGirl.create :project } + let(:project) { create(:project, :repository) } let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) } let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) } @@ -339,7 +339,7 @@ describe Ci::Pipeline, models: true do end context 'with non-empty project' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, @@ -890,7 +890,7 @@ describe Ci::Pipeline, models: true do end describe "#merge_requests" do - let(:project) { FactoryGirl.create :project } + let(:project) { create(:project, :repository) } let(:pipeline) { FactoryGirl.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) } it "returns merge requests whose `diff_head_sha` matches the pipeline's SHA" do @@ -956,7 +956,7 @@ describe Ci::Pipeline, models: true do end describe 'notifications when pipeline success or failed' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 2b856ca7af7..3f32248e52b 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -91,8 +91,7 @@ describe Ci::Runner, models: true do end describe '#can_pick?' do - let(:project) { create(:project) } - let(:pipeline) { create(:ci_pipeline, project: project) } + let(:pipeline) { create(:ci_pipeline) } let(:build) { create(:ci_build, pipeline: pipeline) } let(:runner) { create(:ci_runner) } @@ -321,8 +320,8 @@ describe Ci::Runner, models: true do describe '.assignable_for' do let(:runner) { create(:ci_runner) } - let(:project) { create(:project) } - let(:another_project) { create(:project) } + let(:project) { create(:empty_project) } + let(:another_project) { create(:empty_project) } before do project.runners << runner diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb index 30782ca75a0..e4bddf67096 100644 --- a/spec/models/commit_range_spec.rb +++ b/spec/models/commit_range_spec.rb @@ -7,7 +7,7 @@ describe CommitRange, models: true do it { is_expected.to include_module(Referable) } end - let!(:project) { create(:project, :public) } + let!(:project) { create(:project, :public, :repository) } let!(:commit1) { project.commit("HEAD~2") } let!(:commit2) { project.commit } diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index b2202f0fd44..32f9366a14c 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Commit, models: true do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:commit) { project.commit } describe 'modules' do @@ -34,7 +34,7 @@ describe Commit, models: true do end describe '#to_reference' do - let(:project) { create(:project, path: 'sample-project') } + let(:project) { create(:project, :repository, path: 'sample-project') } let(:commit) { project.commit } it 'returns a String reference to the object' do @@ -42,13 +42,13 @@ describe Commit, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:project, :repository, name: 'another-project', namespace: project.namespace) expect(commit.to_reference(another_project)).to eq "sample-project@#{commit.id}" end end describe '#reference_link_text' do - let(:project) { create(:project, path: 'sample-project') } + let(:project) { create(:project, :repository, path: 'sample-project') } let(:commit) { project.commit } it 'returns a String reference to the object' do @@ -56,7 +56,7 @@ describe Commit, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:project, :repository, name: 'another-project', namespace: project.namespace) expect(commit.reference_link_text(another_project)).to eq "sample-project@#{commit.short_id}" end end @@ -131,7 +131,7 @@ eos describe '#closes_issues' do let(:issue) { create :issue, project: project } - let(:other_project) { create :project, :public } + let(:other_project) { create(:empty_project, :public) } let(:other_issue) { create :issue, project: other_project } let(:commiter) { create :user } @@ -154,7 +154,7 @@ eos end it_behaves_like 'a mentionable' do - subject { create(:project).commit } + subject { create(:project, :repository).commit } let(:author) { create(:user, email: subject.author_email) } let(:backref_text) { "commit #{subject.id}" } diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb index 64ea607eb95..bf4394f7d5b 100644 --- a/spec/models/commit_status_spec.rb +++ b/spec/models/commit_status_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe CommitStatus, models: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, project: project, sha: project.commit.id) diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb index 49ab3c4b6e9..da003dbf794 100644 --- a/spec/models/compare_spec.rb +++ b/spec/models/compare_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Compare, models: true do include RepoHelpers - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:commit) { project.commit } let(:start_commit) { sample_image_commit } diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index d7d31892e12..545a11912e3 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -301,7 +301,7 @@ describe Issue, "Issuable" do end describe '#labels_array' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:bug) { create(:label, project: project, title: 'bug') } let(:issue) { create(:issue, project: project) } @@ -315,7 +315,7 @@ describe Issue, "Issuable" do end describe '#user_notes_count' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:issue1) { create(:issue, project: project) } let(:issue2) { create(:issue, project: project) } @@ -359,7 +359,7 @@ describe Issue, "Issuable" do end describe ".with_label" do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:bug) { create(:label, project: project, title: 'bug') } let(:feature) { create(:label, project: project, title: 'feature') } let(:enhancement) { create(:label, project: project, title: 'enhancement') } diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index b73028f0bc0..2092576e981 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -13,7 +13,7 @@ describe Mentionable do end describe 'references' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:mentionable) { Example.new } it 'excludes JIRA references' do @@ -83,13 +83,13 @@ describe Issue, "Mentionable" do end describe '#create_cross_references!' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:author) { build(:user) } let(:commit) { project.commit } let(:commit2) { project.commit } let!(:issue) do - create(:issue, project: project, description: commit.to_reference) + create(:issue, project: project, description: "See #{commit.to_reference}") end it 'correctly removes already-mentioned Commits' do @@ -100,7 +100,7 @@ describe Issue, "Mentionable" do end describe '#create_new_cross_references!' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:author) { create(:author) } let(:issues) { create_list(:issue, 2, project: project, author: author) } diff --git a/spec/models/concerns/milestoneish_spec.rb b/spec/models/concerns/milestoneish_spec.rb index 0e097559b59..ad703a6c8bb 100644 --- a/spec/models/concerns/milestoneish_spec.rb +++ b/spec/models/concerns/milestoneish_spec.rb @@ -7,7 +7,7 @@ describe Milestone, 'Milestoneish' do let(:member) { create(:user) } let(:guest) { create(:user) } let(:admin) { create(:admin) } - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:milestone) { create(:milestone, project: project) } let!(:issue) { create(:issue, project: project, milestone: milestone) } let!(:security_issue_1) { create(:issue, :confidential, project: project, author: author, milestone: milestone) } diff --git a/spec/models/concerns/project_features_compatibility_spec.rb b/spec/models/concerns/project_features_compatibility_spec.rb index 9041690023f..6cf5877424d 100644 --- a/spec/models/concerns/project_features_compatibility_spec.rb +++ b/spec/models/concerns/project_features_compatibility_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ProjectFeaturesCompatibility do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:features) { %w(issues wiki builds merge_requests snippets) } # We had issues_enabled, snippets_enabled, builds_enabled, merge_requests_enabled and issues_enabled fields on projects table diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb index 70f985afefb..3b7cc7d9e2e 100644 --- a/spec/models/cycle_analytics/code_spec.rb +++ b/spec/models/cycle_analytics/code_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#code', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb index e4b6a8f4518..5c73edbbc53 100644 --- a/spec/models/cycle_analytics/issue_spec.rb +++ b/spec/models/cycle_analytics/issue_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#issue', models: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb index dc5b04852d6..55483fc876a 100644 --- a/spec/models/cycle_analytics/plan_spec.rb +++ b/spec/models/cycle_analytics/plan_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#plan', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/cycle_analytics/production_spec.rb b/spec/models/cycle_analytics/production_spec.rb index 5e99188f318..591bbdddf55 100644 --- a/spec/models/cycle_analytics/production_spec.rb +++ b/spec/models/cycle_analytics/production_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#production', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb index 45baa5f7006..33d2c0a7416 100644 --- a/spec/models/cycle_analytics/review_spec.rb +++ b/spec/models/cycle_analytics/review_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#review', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/cycle_analytics/staging_spec.rb b/spec/models/cycle_analytics/staging_spec.rb index 77625aad580..00693d67475 100644 --- a/spec/models/cycle_analytics/staging_spec.rb +++ b/spec/models/cycle_analytics/staging_spec.rb @@ -3,9 +3,10 @@ require 'spec_helper' describe 'CycleAnalytics#staging', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } + subject { CycleAnalytics.new(project, from: from_date) } generate_cycle_analytics_spec( diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb index 27a117d2d76..f857ea6cbec 100644 --- a/spec/models/cycle_analytics/test_spec.rb +++ b/spec/models/cycle_analytics/test_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'CycleAnalytics#test', feature: true do extend CycleAnalyticsHelpers::TestGeneration - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:from_date) { 10.days.ago } let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb index 8a1e337c1a3..aacc178a19e 100644 --- a/spec/models/deploy_keys_project_spec.rb +++ b/spec/models/deploy_keys_project_spec.rb @@ -12,7 +12,7 @@ describe DeployKeysProject, models: true do end describe "Destroying" do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } subject { create(:deploy_keys_project, project: project) } let(:deploy_key) { subject.deploy_key } @@ -39,7 +39,7 @@ describe DeployKeysProject, models: true do end context "when the deploy key is used by more than one project" do - let!(:other_project) { create(:project) } + let!(:other_project) { create(:empty_project) } before do other_project.deploy_keys << deploy_key diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index ca594a320c0..fc4435a2f64 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -17,7 +17,7 @@ describe Deployment, models: true do it { is_expected.to validate_presence_of(:sha) } describe '#includes_commit?' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:environment) { create(:environment, project: project) } let(:deployment) do create(:deployment, environment: environment, sha: project.commit.id) diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index 3db5937a4f3..9ea3a4b7020 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe DiffNote, models: true do include RepoHelpers - let(:project) { create(:project) } - let(:merge_request) { create(:merge_request, source_project: project) } + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.project } let(:commit) { project.commit(sample_commit.id) } let(:path) { "files/ruby/popen.rb" } diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 96efe1696c3..b2e06541e66 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -32,7 +32,7 @@ describe Environment, models: true do end describe '#includes_commit?' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } context 'without a last deployment' do it "returns false" do @@ -81,7 +81,7 @@ describe Environment, models: true do end describe '#first_deployment_for' do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let!(:deployment) { create(:deployment, environment: environment, ref: commit.parent.id) } let!(:deployment1) { create(:deployment, environment: environment, ref: commit.id) } let(:head_commit) { project.commit } diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index f8660da031d..349474bb656 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -27,7 +27,7 @@ describe Event, models: true do end describe "Push event" do - let(:project) { create(:project, :private) } + let(:project) { create(:empty_project, :private) } let(:user) { project.owner } let(:event) { create_event(project, user) } @@ -187,7 +187,7 @@ describe Event, models: true do end context 'merge request diff note event' do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:merge_request) { create(:merge_request, source_project: project, author: author, assignee: assignee) } let(:note_on_merge_request) { create(:legacy_diff_note_on_merge_request, noteable: merge_request, project: project) } let(:target) { note_on_merge_request } @@ -202,7 +202,7 @@ describe Event, models: true do end context 'private project' do - let(:project) { create(:project, :private) } + let(:project) { create(:empty_project, :private) } it do expect(event.visible_to_user?(non_member)).to eq false diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb index 1863581f57b..454550c9710 100644 --- a/spec/models/forked_project_link_spec.rb +++ b/spec/models/forked_project_link_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ForkedProjectLink, "add link on fork" do - let(:project_from) { create(:project) } + let(:project_from) { create(:project, :repository) } let(:namespace) { create(:namespace) } let(:user) { create(:user, namespace: namespace) } @@ -21,7 +21,7 @@ end describe '#forked?' do let(:forked_project_link) { build(:forked_project_link) } - let(:project_from) { create(:project) } + let(:project_from) { create(:project, :repository) } let(:project_to) { create(:project, forked_project_link: forked_project_link) } before :each do diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb index d87684fd49e..cacbab8bcb1 100644 --- a/spec/models/global_milestone_spec.rb +++ b/spec/models/global_milestone_spec.rb @@ -4,9 +4,9 @@ describe GlobalMilestone, models: true do let(:user) { create(:user) } let(:user2) { create(:user) } let(:group) { create(:group) } - let(:project1) { create(:project, group: group) } - let(:project2) { create(:project, path: 'gitlab-ci', group: group) } - let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) } + let(:project1) { create(:empty_project, group: group) } + let(:project2) { create(:empty_project, path: 'gitlab-ci', group: group) } + let(:project3) { create(:empty_project, path: 'cookbook-gitlab', group: group) } describe '.build_collection' do let(:milestone1_due_date) { 2.weeks.from_now.to_date } diff --git a/spec/models/group_milestone_spec.rb b/spec/models/group_milestone_spec.rb index 601167c3bd3..916afb7aaf5 100644 --- a/spec/models/group_milestone_spec.rb +++ b/spec/models/group_milestone_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe GroupMilestone, models: true do let(:group) { create(:group) } - let(:project) { create(:project, group: group) } + let(:project) { create(:empty_project, group: group) } let(:project_milestone) do create(:milestone, title: "Milestone v1.2", project: project) end diff --git a/spec/models/guest_spec.rb b/spec/models/guest_spec.rb index d79f929f7a1..582b54c0712 100644 --- a/spec/models/guest_spec.rb +++ b/spec/models/guest_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' describe Guest, lib: true do - let(:public_project) { create(:project, :public) } - let(:private_project) { create(:project, :private) } - let(:internal_project) { create(:project, :internal) } + let(:public_project) { build_stubbed(:empty_project, :public) } + let(:private_project) { build_stubbed(:empty_project, :private) } + let(:internal_project) { build_stubbed(:empty_project, :internal) } describe '.can_pull?' do context 'when project is private' do diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index ad2b710041a..e8caad00c44 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -4,7 +4,7 @@ describe SystemHook, models: true do describe "execute" do let(:system_hook) { create(:system_hook) } let(:user) { create(:user) } - let(:project) { create(:project, namespace: user.namespace) } + let(:project) { create(:empty_project, namespace: user.namespace) } let(:group) { create(:group) } before do diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index e52b9d75cef..9d4db1bfb52 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -25,7 +25,7 @@ describe WebHook, models: true do end describe "execute" do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:project_hook) { create(:project_hook) } before(:each) do diff --git a/spec/models/issue/metrics_spec.rb b/spec/models/issue/metrics_spec.rb index 2459a49f095..08712f2a768 100644 --- a/spec/models/issue/metrics_spec.rb +++ b/spec/models/issue/metrics_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Issue::Metrics, models: true do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } subject { create(:issue, project: project) } diff --git a/spec/models/issue_collection_spec.rb b/spec/models/issue_collection_spec.rb index d742c814680..d8aed25c041 100644 --- a/spec/models/issue_collection_spec.rb +++ b/spec/models/issue_collection_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe IssueCollection do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:issue1) { create(:issue, project: project) } let(:issue2) { create(:issue, project: project) } let(:collection) { described_class.new([issue1, issue2]) } diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 61d72925736..40c0a75c364 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -35,7 +35,7 @@ describe Issue, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:empty_project, name: 'another-project', namespace: project.namespace) expect(issue.to_reference(another_project)).to eq "sample-project#1" end end @@ -60,9 +60,9 @@ describe Issue, models: true do end describe '#closed_by_merge_requests' do - let(:project) { create(:project) } - let(:issue) { create(:issue, project: project, state: "opened")} - let(:closed_issue) { build(:issue, project: project, state: "closed")} + let(:project) { create(:project, :repository) } + let(:issue) { create(:issue, project: project)} + let(:closed_issue) { build(:issue, :closed, project: project)} let(:mr) do opts = { @@ -104,7 +104,7 @@ describe Issue, models: true do describe '#referenced_merge_requests' do it 'returns the referenced merge requests' do - project = create(:project, :public) + project = create(:empty_project, :public) mr1 = create(:merge_request, source_project: project, @@ -137,7 +137,7 @@ describe Issue, models: true do end context 'user is reporter in project issue belongs to' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:issue) { create(:issue, project: project) } before { project.team << [user, :reporter] } @@ -151,7 +151,7 @@ describe Issue, models: true do context 'checking destination project also' do subject { issue.can_move?(user, to_project) } - let(:to_project) { create(:project) } + let(:to_project) { create(:empty_project) } context 'destination project allowed' do before { to_project.team << [user, :reporter] } @@ -246,7 +246,7 @@ describe Issue, models: true do describe '#participants' do context 'using a public project' do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:issue) { create(:issue, project: project) } let!(:note1) do @@ -268,7 +268,7 @@ describe Issue, models: true do context 'using a private project' do it 'does not include mentioned users that do not have access to the project' do - project = create(:project) + project = create(:empty_project) user = create(:user) issue = create(:issue, project: project) diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb index 4f7c8a36cb5..16e2144d6a1 100644 --- a/spec/models/member_spec.rb +++ b/spec/models/member_spec.rb @@ -481,7 +481,7 @@ describe Member, models: true do describe "destroying a record", truncate: true do it "refreshes user's authorized projects" do - project = create(:project, :private) + project = create(:empty_project, :private) user = create(:user) member = project.team << [user, :reporter] diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index 68f72f5c86e..90d14c2c0b9 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -83,8 +83,8 @@ describe ProjectMember, models: true do describe '.import_team' do before do - @project_1 = create :project - @project_2 = create :project + @project_1 = create(:empty_project) + @project_2 = create(:empty_project) @user_1 = create :user @user_2 = create :user @@ -131,8 +131,8 @@ describe ProjectMember, models: true do describe '.truncate_teams' do before do - @project_1 = create :project - @project_2 = create :project + @project_1 = create(:empty_project) + @project_2 = create(:empty_project) @user_1 = create :user @user_2 = create :user diff --git a/spec/models/merge_request/metrics_spec.rb b/spec/models/merge_request/metrics_spec.rb index 255db41cb19..9afed311e27 100644 --- a/spec/models/merge_request/metrics_spec.rb +++ b/spec/models/merge_request/metrics_spec.rb @@ -1,9 +1,7 @@ require 'spec_helper' describe MergeRequest::Metrics, models: true do - let(:project) { create(:project) } - - subject { create(:merge_request, source_project: project) } + subject { create(:merge_request) } describe "when recording the default set of metrics on merge request save" do it "records the merge time" do diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 064f29d2d66..3cee2b7714f 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -24,7 +24,7 @@ describe Milestone, models: true do it { is_expected.to have_many(:issues) } end - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:milestone) { create(:milestone, project: project) } let(:issue) { create(:issue, project: project) } let(:user) { create(:user) } @@ -44,7 +44,7 @@ describe Milestone, models: true do end it "accepts the same title in another project" do - project = build(:project) + project = build(:empty_project) new_milestone = Milestone.new(project: project, title: milestone.title) expect(new_milestone).to be_valid @@ -257,7 +257,7 @@ describe Milestone, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:empty_project, name: 'another-project', namespace: project.namespace) expect(milestone.to_reference(another_project)).to eq "sample-project%1" end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 8d613a88ca0..4e96f19eb6f 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -107,7 +107,7 @@ describe Namespace, models: true do describe '#move_dir' do before do @namespace = create :namespace - @project = create :project, namespace: @namespace + @project = create(:empty_project, namespace: @namespace) allow(@namespace).to receive(:path_changed?).and_return(true) end @@ -139,7 +139,7 @@ describe Namespace, models: true do end describe :rm_dir do - let!(:project) { create(:project, namespace: namespace) } + let!(:project) { create(:empty_project, namespace: namespace) } let!(:path) { File.join(Gitlab.config.repositories.storages.default, namespace.path) } it "removes its dirs when deleted" do diff --git a/spec/models/network/graph_spec.rb b/spec/models/network/graph_spec.rb index b76513d2a3c..492c4e01bd8 100644 --- a/spec/models/network/graph_spec.rb +++ b/spec/models/network/graph_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Network::Graph, models: true do - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let!(:note_on_commit) { create(:note_on_commit, project: project) } it '#initialize' do diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 6f9ae655fed..1cde9e04951 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -42,7 +42,7 @@ describe Note, models: true do context 'when noteable and note project differ' do subject do build(:note, noteable: build_stubbed(:issue), - project: build_stubbed(:project)) + project: build_stubbed(:empty_project)) end it { is_expected.to be_invalid } @@ -93,8 +93,8 @@ describe Note, models: true do describe 'authorization' do before do - @p1 = create(:project) - @p2 = create(:project) + @p1 = create(:empty_project) + @p2 = create(:empty_project) @u1 = create(:user) @u2 = create(:user) @u3 = create(:user) @@ -191,10 +191,10 @@ describe Note, models: true do describe "cross_reference_not_visible_for?" do let(:private_user) { create(:user) } - let(:private_project) { create(:project, namespace: private_user.namespace).tap { |p| p.team << [private_user, :master] } } + let(:private_project) { create(:empty_project, namespace: private_user.namespace) { |p| p.team << [private_user, :master] } } let(:private_issue) { create(:issue, project: private_project) } - let(:ext_proj) { create(:project, :public) } + let(:ext_proj) { create(:empty_project, :public) } let(:ext_issue) { create(:issue, project: ext_proj) } let(:note) do @@ -237,7 +237,7 @@ describe Note, models: true do describe '#participants' do it 'includes the note author' do - project = create(:project, :public) + project = create(:empty_project, :public) issue = create(:issue, project: project) note = create(:note_on_issue, noteable: issue, project: project) diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb index a55d43ab2f9..8589f1eb712 100644 --- a/spec/models/project_feature_spec.rb +++ b/spec/models/project_feature_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe ProjectFeature do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:user) { create(:user) } describe '#feature_available?' do @@ -35,7 +35,7 @@ describe ProjectFeature do it "returns true when user is a member of project group" do group = create(:group) - project = create(:project, namespace: group) + project = create(:empty_project, namespace: group) group.add_developer(user) features.each do |feature| diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb index 47397a822c1..59a4ae1b799 100644 --- a/spec/models/project_group_link_spec.rb +++ b/spec/models/project_group_link_spec.rb @@ -17,7 +17,7 @@ describe ProjectGroupLink do describe "destroying a record", truncate: true do it "refreshes group users' authorized projects" do - project = create(:project, :private) + project = create(:empty_project, :private) group = create(:group) reporter = create(:user) group_users = group.users diff --git a/spec/models/project_label_spec.rb b/spec/models/project_label_spec.rb index 4d538cac007..9cdbfa44e5b 100644 --- a/spec/models/project_label_spec.rb +++ b/spec/models/project_label_spec.rb @@ -100,7 +100,7 @@ describe ProjectLabel, models: true do end context 'cross project reference' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'using name' do it 'returns cross reference with label name' do diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb index 8e5145e824b..48aef3a93f2 100644 --- a/spec/models/project_services/asana_service_spec.rb +++ b/spec/models/project_services/asana_service_spec.rb @@ -18,7 +18,7 @@ describe AsanaService, models: true do describe 'Execute' do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } def create_data_for_commits(*messages) { diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb index 4c5acb7990b..96f00af898e 100644 --- a/spec/models/project_services/assembla_service_spec.rb +++ b/spec/models/project_services/assembla_service_spec.rb @@ -8,7 +8,7 @@ describe AssemblaService, models: true do describe "Execute" do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do @assembla_service = AssemblaService.new diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb index a3b9d084a75..953e664fb66 100644 --- a/spec/models/project_services/campfire_service_spec.rb +++ b/spec/models/project_services/campfire_service_spec.rb @@ -22,7 +22,7 @@ describe CampfireService, models: true do describe "#execute" do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do @campfire_service = CampfireService.new diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb index 42c2ed668bc..f9307d6de7b 100644 --- a/spec/models/project_services/drone_ci_service_spec.rb +++ b/spec/models/project_services/drone_ci_service_spec.rb @@ -27,7 +27,7 @@ describe DroneCiService, models: true, caching: true do shared_context :drone_ci_service do let(:drone) { DroneCiService.new } - let(:project) { create(:project, name: 'project') } + let(:project) { create(:project, :repository, name: 'project') } let(:path) { "#{project.namespace.path}/#{project.path}" } let(:drone_url) { 'http://drone.example.com' } let(:sha) { '2ab7834c' } diff --git a/spec/models/project_services/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb index 342d86aeca9..bdeea1db1e3 100644 --- a/spec/models/project_services/external_wiki_service_spec.rb +++ b/spec/models/project_services/external_wiki_service_spec.rb @@ -23,7 +23,7 @@ describe ExternalWikiService, models: true do end describe 'External wiki' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'when it is active' do before do diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb index d6db02d6e76..a97e8c6e4ce 100644 --- a/spec/models/project_services/flowdock_service_spec.rb +++ b/spec/models/project_services/flowdock_service_spec.rb @@ -22,7 +22,7 @@ describe FlowdockService, models: true do describe "Execute" do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do @flowdock_service = FlowdockService.new diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb index 529044d1d8b..a13fbae03eb 100644 --- a/spec/models/project_services/gemnasium_service_spec.rb +++ b/spec/models/project_services/gemnasium_service_spec.rb @@ -24,7 +24,7 @@ describe GemnasiumService, models: true do describe "Execute" do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } before do @gemnasium_service = GemnasiumService.new diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb index 9b80f0e7296..dcb70ee28a8 100644 --- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -8,21 +8,21 @@ describe GitlabIssueTrackerService, models: true do describe 'Validations' do context 'when service is active' do - subject { described_class.new(project: create(:project), active: true) } + subject { described_class.new(project: create(:empty_project), active: true) } it { is_expected.to validate_presence_of(:issues_url) } it_behaves_like 'issue tracker service URL attribute', :issues_url end context 'when service is inactive' do - subject { described_class.new(project: create(:project), active: false) } + subject { described_class.new(project: create(:empty_project), active: false) } it { is_expected.not_to validate_presence_of(:issues_url) } end end describe 'project and issue urls' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'with absolute urls' do before do diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index 2da3a9cb09f..bf422ac7ce1 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -22,8 +22,8 @@ describe HipchatService, models: true do describe "Execute" do let(:hipchat) { HipchatService.new } - let(:user) { create(:user, username: 'username') } - let(:project) { create(:project, name: 'project') } + let(:user) { create(:user) } + let(:project) { create(:project, :repository) } let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' } let(:project_name) { project.name_with_namespace.gsub(/\s/, '') } let(:token) { 'verySecret' } @@ -165,7 +165,7 @@ describe HipchatService, models: true do context "Note events" do let(:user) { create(:user) } - let(:project) { create(:project, creator_id: user.id) } + let(:project) { create(:project, :repository, creator: user) } context 'when commit comment event triggered' do let(:commit_note) do diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb index f8c45b37561..b9fb6f3f6f4 100644 --- a/spec/models/project_services/irker_service_spec.rb +++ b/spec/models/project_services/irker_service_spec.rb @@ -25,7 +25,7 @@ describe IrkerService, models: true do describe 'Execute' do let(:irker) { IrkerService.new } let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:sample_data) do Gitlab::DataBuilder::Push.build_sample(project, user) end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 862e3a72a73..2f6b159d76e 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -71,7 +71,7 @@ describe JiraService, models: true do describe '#close_issue' do let(:custom_base_url) { 'http://custom_url' } let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let(:merge_request) { create(:merge_request) } before do @@ -207,12 +207,12 @@ describe JiraService, models: true do end describe "Stored password invalidation" do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context "when a password was previously set" do before do @jira_service = JiraService.create!( - project: create(:project), + project: project, properties: { url: 'http://jira.example.com/rest/api/2', username: 'mic', @@ -252,7 +252,7 @@ describe JiraService, models: true do context "when no password was previously set" do before do @jira_service = JiraService.create( - project: create(:project), + project: project, properties: { url: 'http://jira.example.com/rest/api/2', username: 'mic' @@ -281,7 +281,7 @@ describe JiraService, models: true do end describe 'description and title' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'when it is not set' do before do @@ -316,7 +316,7 @@ describe JiraService, models: true do end describe 'project and issue urls' do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } context 'when gitlab.yml was initialized' do before do diff --git a/spec/models/project_services/pipeline_email_service_spec.rb b/spec/models/project_services/pipeline_email_service_spec.rb index 7c8824485f5..03932895b0e 100644 --- a/spec/models/project_services/pipeline_email_service_spec.rb +++ b/spec/models/project_services/pipeline_email_service_spec.rb @@ -7,7 +7,7 @@ describe PipelinesEmailService do create(:ci_pipeline, project: project, sha: project.commit('master').sha) end - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:recipient) { 'test@gitlab.com' } let(:data) do diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb index 8fc92a9ab51..a7e7594a7d5 100644 --- a/spec/models/project_services/pushover_service_spec.rb +++ b/spec/models/project_services/pushover_service_spec.rb @@ -27,7 +27,7 @@ describe PushoverService, models: true do describe 'Execute' do let(:pushover) { PushoverService.new } let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:sample_data) do Gitlab::DataBuilder::Push.build_sample(project, user) end diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 0475cecaa2d..942eeab251d 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -265,10 +265,10 @@ describe ProjectTeam, models: true do let(:group) { create(:group) } let(:developer) { create(:user) } let(:master) { create(:user) } - let(:personal_project) { create(:project, namespace: developer.namespace) } - let(:group_project) { create(:project, namespace: group) } - let(:members_project) { create(:project) } - let(:shared_project) { create(:project) } + let(:personal_project) { create(:empty_project, namespace: developer.namespace) } + let(:group_project) { create(:empty_project, namespace: group) } + let(:members_project) { create(:empty_project) } + let(:shared_project) { create(:empty_project) } before do group.add_master(master) @@ -330,7 +330,7 @@ describe ProjectTeam, models: true do reporter = create(:user) promoted_guest = create(:user) guest = create(:user) - project = create(:project) + project = create(:empty_project) project.add_master(master) project.add_reporter(reporter) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 99ca53938c8..829b69093c9 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -4,7 +4,7 @@ describe Repository, models: true do include RepoHelpers TestBlob = Struct.new(:name) - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:repository) { project.repository } let(:user) { create(:user) } @@ -90,6 +90,30 @@ describe Repository, models: true do it { is_expected.to eq(['v1.1.0', 'v1.0.0']) } end + + context 'annotated tag pointing to a blob' do + let(:annotated_tag_name) { 'annotated-tag' } + + subject { repository.tags_sorted_by('updated_asc').map(&:name) } + + before do + options = { message: 'test tag message\n', + tagger: { name: 'John Smith', email: 'john@gmail.com' } } + repository.rugged.tags.create(annotated_tag_name, 'a48e4fc218069f68ef2e769dd8dfea3991362175', options) + + double_first = double(committed_date: Time.now - 1.second) + double_last = double(committed_date: Time.now) + + allow(tag_a).to receive(:dereferenced_target).and_return(double_last) + allow(tag_b).to receive(:dereferenced_target).and_return(double_first) + end + + it { is_expected.to eq(['v1.1.0', 'v1.0.0', annotated_tag_name]) } + + after do + repository.rugged.tags.delete(annotated_tag_name) + end + end end end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 691511cd93f..0e2f07e945f 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -12,7 +12,7 @@ describe Service, models: true do end describe "Testable" do - let(:project) { create :project } + let(:project) { create(:project, :repository) } before do allow(@service).to receive(:project).and_return(project) @@ -35,7 +35,7 @@ describe Service, models: true do end describe "With commits" do - let(:project) { create :project } + let(:project) { create(:project, :repository) } before do allow(@service).to receive(:project).and_return(project) @@ -60,7 +60,7 @@ describe Service, models: true do api_key: '123456789' }) end - let(:project) { create(:project) } + let(:project) { create(:empty_project) } describe 'is prefilled for projects pushover service' do it "has all fields prefilled" do @@ -79,7 +79,7 @@ describe Service, models: true do describe "{property}_changed?" do let(:service) do BambooService.create( - project: create(:project), + project: create(:empty_project), properties: { bamboo_url: 'http://gitlab.com', username: 'mic', @@ -119,7 +119,7 @@ describe Service, models: true do describe "{property}_touched?" do let(:service) do BambooService.create( - project: create(:project), + project: create(:empty_project), properties: { bamboo_url: 'http://gitlab.com', username: 'mic', @@ -159,7 +159,7 @@ describe Service, models: true do describe "{property}_was" do let(:service) do BambooService.create( - project: create(:project), + project: create(:empty_project), properties: { bamboo_url: 'http://gitlab.com', username: 'mic', @@ -199,7 +199,7 @@ describe Service, models: true do describe 'initialize service with no properties' do let(:service) do GitlabIssueTrackerService.create( - project: create(:project), + project: create(:empty_project), title: 'random title' ) end @@ -214,7 +214,7 @@ describe Service, models: true do end describe "callbacks" do - let(:project) { create(:project) } + let(:project) { create(:empty_project) } let!(:service) do RedmineService.new( project: project, diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 7425a897769..219ab1989ea 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -42,7 +42,7 @@ describe Snippet, models: true do end it 'supports a cross-project reference' do - another_project = build(:project, name: 'another-project', namespace: project.namespace) + another_project = build(:empty_project, name: 'another-project', namespace: project.namespace) expect(snippet.to_reference(another_project)).to eq "sample-project$1" end end @@ -55,7 +55,7 @@ describe Snippet, models: true do end it 'still returns shortest reference when project arg present' do - another_project = build(:project, name: 'another-project') + another_project = build(:empty_project, name: 'another-project') expect(snippet.to_reference(another_project)).to eq "$1" end end @@ -173,7 +173,7 @@ describe Snippet, models: true do end describe '#participants' do - let(:project) { create(:project, :public) } + let(:project) { create(:empty_project, :public) } let(:snippet) { create(:snippet, content: 'foo', project: project) } let!(:note1) do diff --git a/spec/models/todo_spec.rb b/spec/models/todo_spec.rb index 623b82c01d8..8017d1c3324 100644 --- a/spec/models/todo_spec.rb +++ b/spec/models/todo_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe Todo, models: true do - let(:project) { create(:project) } - let(:commit) { project.commit } let(:issue) { create(:issue) } describe 'relationships' do @@ -82,6 +80,9 @@ describe Todo, models: true do describe '#target' do context 'for commits' do + let(:project) { create(:project, :repository) } + let(:commit) { project.commit } + it 'returns an instance of Commit when exists' do subject.project = project subject.target_type = 'Commit' @@ -109,6 +110,9 @@ describe Todo, models: true do describe '#target_reference' do it 'returns the short commit id for commits' do + project = create(:project, :repository) + commit = project.commit + subject.project = project subject.target_type = 'Commit' subject.commit_id = commit.id diff --git a/spec/models/tree_spec.rb b/spec/models/tree_spec.rb index 0737999e125..a87983b7492 100644 --- a/spec/models/tree_spec.rb +++ b/spec/models/tree_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Tree, models: true do - let(:repository) { create(:project).repository } + let(:repository) { create(:project, :repository).repository } let(:sha) { repository.root_ref } subject { described_class.new(repository, '54fcc214') } diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 0adfc30fe58..6ca5ad747d1 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1382,14 +1382,14 @@ describe User, models: true do let!(:user) { create(:user) } let!(:group) { create(:group) } let!(:nested_group) { create(:group, parent: group) } - let!(:project) { create(:project, namespace: group) } - let!(:nested_project) { create(:project, namespace: nested_group) } + let!(:project) { create(:empty_project, namespace: group) } + let!(:nested_project) { create(:empty_project, namespace: nested_group) } before do group.add_owner(user) # Add more data to ensure method does not include wrong projects - other_project = create(:project, namespace: create(:group, :nested)) + other_project = create(:empty_project, namespace: create(:group, :nested)) other_project.add_developer(create(:user)) end diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 2878e0cb59b..5a3ffc284f2 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -6,7 +6,7 @@ describe API::Branches, api: true do let(:user) { create(:user) } let(:user2) { create(:user) } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:project, :repository, creator: user) } let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:guest) { create(:project_member, :guest, user: user2, project: project) } let!(:branch_name) { 'feature' } diff --git a/spec/requests/api/builds_spec.rb b/spec/requests/api/builds_spec.rb index 7be7acebb19..645e36683bc 100644 --- a/spec/requests/api/builds_spec.rb +++ b/spec/requests/api/builds_spec.rb @@ -5,7 +5,7 @@ describe API::Builds, api: true do let(:user) { create(:user) } let(:api_user) { user } - let!(:project) { create(:project, creator_id: user.id, public_builds: false) } + let!(:project) { create(:project, :repository, creator: user, public_builds: false) } let!(:developer) { create(:project_member, :developer, user: user, project: project) } let(:reporter) { create(:project_member, :reporter, project: project) } let(:guest) { create(:project_member, :guest, project: project) } diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index c1c7c0882de..88361def3cf 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe API::CommitStatuses, api: true do include ApiHelpers - let!(:project) { create(:project) } + let!(:project) { create(:project, :repository) } let(:commit) { project.repository.commit } let(:commit_status) { create(:commit_status, pipeline: pipeline) } let(:guest) { create_user(:guest) } diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 7f8ea5251f0..af9028a8978 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -5,7 +5,7 @@ describe API::Commits, api: true do include ApiHelpers let(:user) { create(:user) } let(:user2) { create(:user) } - let!(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } + let!(:project) { create(:project, :repository, creator: user, namespace: user.namespace) } let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:guest) { create(:project_member, :guest, user: user2, project: project) } let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') } diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 685da28c673..5e26e779366 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -3,8 +3,8 @@ require 'spec_helper' describe API::Files, api: true do include ApiHelpers let(:user) { create(:user) } - let!(:project) { create(:project, namespace: user.namespace ) } - let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } } + let!(:project) { create(:project, :repository, namespace: user.namespace ) } + let(:guest) { create(:user) { |u| project.add_guest(u) } } let(:file_path) { 'files/ruby/popen.rb' } let(:params) do { diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb index df29099bc2f..92ac4fd334d 100644 --- a/spec/requests/api/fork_spec.rb +++ b/spec/requests/api/fork_spec.rb @@ -4,7 +4,6 @@ describe API::Projects, api: true do include ApiHelpers let(:user) { create(:user) } let(:user2) { create(:user) } - let(:user3) { create(:user) } let(:admin) { create(:admin) } let(:group) { create(:group) } let(:group2) do @@ -13,17 +12,14 @@ describe API::Projects, api: true do group end - let(:project) do - create(:project, creator_id: user.id, namespace: user.namespace) - end - - let(:project_user2) do - create(:project_member, :reporter, user: user2, project: project) - end - describe 'POST /projects/fork/:id' do - before { project_user2 } - before { user3 } + let(:project) do + create(:project, :repository, creator: user, namespace: user.namespace) + end + + before do + project.add_reporter(user2) + end context 'when authenticated' do it 'forks if user has sufficient access to project' do @@ -49,7 +45,8 @@ describe API::Projects, api: true do end it 'fails on missing project access for the project to fork' do - post api("/projects/fork/#{project.id}", user3) + new_user = create(:user) + post api("/projects/fork/#{project.id}", new_user) expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 91202244227..ffeacb15f17 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -4,7 +4,7 @@ describe API::Internal, api: true do include ApiHelpers let(:user) { create(:user) } let(:key) { create(:key, user: user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:secret_token) { Gitlab::Shell.secret_token } describe "GET /internal/check", no_db: true do diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 71a7994e544..21a2c583aa8 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -6,12 +6,10 @@ describe API::MergeRequests, api: true do let(:user) { create(:user) } let(:admin) { create(:user, :admin) } let(:non_member) { create(:user) } - let!(:project) { create(:project, :public, creator_id: user.id, namespace: user.namespace) } - let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) } - let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) } - let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') } - let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") } - let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") } + let!(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace) } + let!(:merge_request) { create(:merge_request, :simple, author: user, assignee: user, source_project: project, title: "Test", created_at: base_time) } + let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, title: "Closed test", created_at: base_time + 1.second) } + let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') } let(:milestone) { create(:milestone, title: '1.0.0', project: project) } before do @@ -556,11 +554,12 @@ describe API::MergeRequests, api: true do original_count = merge_request.notes.size post api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user), note: "My comment" + expect(response).to have_http_status(201) expect(json_response['note']).to eq('My comment') expect(json_response['author']['name']).to eq(user.name) expect(json_response['author']['username']).to eq(user.username) - expect(merge_request.notes.size).to eq(original_count + 1) + expect(merge_request.reload.notes.size).to eq(original_count + 1) end it "returns 400 if note is missing" do @@ -576,6 +575,9 @@ describe API::MergeRequests, api: true do end describe "GET :id/merge_requests/:merge_request_id/comments" do + let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") } + let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") } + it "returns merge_request comments ordered by created_at" do get api("/projects/#{project.id}/merge_requests/#{merge_request.id}/comments", user) expect(response).to have_http_status(200) diff --git a/spec/requests/api/pipelines_spec.rb b/spec/requests/api/pipelines_spec.rb index 9a01f7fa1c4..b7a0b5a9e13 100644 --- a/spec/requests/api/pipelines_spec.rb +++ b/spec/requests/api/pipelines_spec.rb @@ -5,7 +5,7 @@ describe API::Pipelines, api: true do let(:user) { create(:user) } let(:non_member) { create(:user) } - let(:project) { create(:project, creator_id: user.id) } + let(:project) { create(:project, :repository, creator: user) } let!(:pipeline) do create(:ci_empty_pipeline, project: project, sha: project.commit.id, diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index cc5c532de83..a1db81ce18c 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -17,6 +17,7 @@ describe API::Projects, api: true do let(:project3) do create(:project, :private, + :repository, name: 'second_project', path: 'second_project', creator_id: user.id, diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 0b19fa38c55..c61208e395c 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -8,7 +8,7 @@ describe API::Repositories, api: true do let(:user) { create(:user) } let(:guest) { create(:user).tap { |u| create(:project_member, :guest, user: u, project: project) } } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:project, :repository, creator: user) } let!(:master) { create(:project_member, :master, user: user, project: project) } describe "GET /projects/:id/repository/tree" do @@ -74,7 +74,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository tree' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -144,7 +144,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository blob' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -198,7 +198,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository raw blob' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -273,7 +273,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository archive' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -347,7 +347,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository compare' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -394,7 +394,7 @@ describe API::Repositories, api: true do context 'when unauthenticated', 'and project is public' do it_behaves_like 'repository contributors' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb index a1c32ae65ba..898d2b27e5c 100644 --- a/spec/requests/api/tags_spec.rb +++ b/spec/requests/api/tags_spec.rb @@ -7,7 +7,7 @@ describe API::Tags, api: true do let(:user) { create(:user) } let(:user2) { create(:user) } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:project, :repository, creator: user) } let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:guest) { create(:project_member, :guest, user: user2, project: project) } @@ -29,7 +29,7 @@ describe API::Tags, api: true do context 'when unauthenticated' do it_behaves_like 'repository tags' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end @@ -88,7 +88,7 @@ describe API::Tags, api: true do context 'when unauthenticated' do it_behaves_like 'repository tag' do - let(:project) { create(:project, :public) } + let(:project) { create(:project, :public, :repository) } let(:current_user) { nil } end end diff --git a/spec/requests/api/triggers_spec.rb b/spec/requests/api/triggers_spec.rb index cd01283b655..84104aa66ee 100644 --- a/spec/requests/api/triggers_spec.rb +++ b/spec/requests/api/triggers_spec.rb @@ -7,7 +7,7 @@ describe API::Triggers do let(:user2) { create(:user) } let!(:trigger_token) { 'secure_token' } let!(:trigger_token_2) { 'secure_token_2' } - let!(:project) { create(:project, creator_id: user.id) } + let!(:project) { create(:project, :repository, creator: user) } let!(:master) { create(:project_member, :master, user: user, project: project) } let!(:developer) { create(:project_member, :developer, user: user2, project: project) } let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) } diff --git a/spec/requests/ci/api/triggers_spec.rb b/spec/requests/ci/api/triggers_spec.rb index 2d434ab5dd8..a30be767119 100644 --- a/spec/requests/ci/api/triggers_spec.rb +++ b/spec/requests/ci/api/triggers_spec.rb @@ -5,9 +5,9 @@ describe Ci::API::Triggers do describe 'POST /projects/:project_id/refs/:ref/trigger' do let!(:trigger_token) { 'secure token' } - let!(:project) { FactoryGirl.create(:project, ci_id: 10) } - let!(:project2) { FactoryGirl.create(:empty_project, ci_id: 11) } - let!(:trigger) { FactoryGirl.create(:ci_trigger, project: project, token: trigger_token) } + let!(:project) { create(:project, :repository, ci_id: 10) } + let!(:project2) { create(:empty_project, ci_id: 11) } + let!(:trigger) { create(:ci_trigger, project: project, token: trigger_token) } let(:options) do { token: trigger_token diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index 6a5ad6deb74..4a16824de04 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -12,7 +12,7 @@ describe 'Git HTTP requests', lib: true do describe "User with no identities" do let(:user) { create(:user) } - let(:project) { create(:project, path: 'project.git-project') } + let(:project) { create(:project, :repository, path: 'project.git-project') } context "when the project doesn't exist" do context "when no authentication is provided" do diff --git a/spec/requests/projects/artifacts_controller_spec.rb b/spec/requests/projects/artifacts_controller_spec.rb index e02f0eacc93..d20866c0d44 100644 --- a/spec/requests/projects/artifacts_controller_spec.rb +++ b/spec/requests/projects/artifacts_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::ArtifactsController do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb index 28b485e4b15..0edbffbcd3b 100644 --- a/spec/requests/projects/cycle_analytics_events_spec.rb +++ b/spec/requests/projects/cycle_analytics_events_spec.rb @@ -4,7 +4,7 @@ describe 'cycle analytics events' do include ApiHelpers let(:user) { create(:user) } - let(:project) { create(:project, public_builds: false) } + let(:project) { create(:project, :repository, public_builds: false) } let(:issue) { create(:issue, project: project, created_at: 2.days.ago) } describe 'GET /:namespace/:project/cycle_analytics/events/issues' do diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index bfbee7ca35f..309985a5d90 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -33,6 +33,49 @@ describe NotificationService, services: true do end end + # Next shared examples are intended to test notifications of "participants" + # + # they take the following parameters: + # * issuable + # * notification trigger + # * participant + # + shared_examples 'participating by note notification' do + it 'emails the participant' do + create(:note_on_issue, noteable: issuable, project_id: project.id, note: 'anything', author: participant) + + notification_trigger + + should_email(participant) + end + end + + shared_examples 'participating by assignee notification' do + it 'emails the participant' do + issuable.update_attribute(:assignee, participant) + + notification_trigger + + should_email(participant) + end + end + + shared_examples 'participating by author notification' do + it 'emails the participant' do + issuable.author = participant + + notification_trigger + + should_email(participant) + end + end + + shared_examples_for 'participating notifications' do + it_should_behave_like 'participating by note notification' + it_should_behave_like 'participating by author notification' + it_should_behave_like 'participating by assignee notification' + end + describe 'Keys' do describe '#new_key' do let!(:key) { create(:personal_key) } @@ -588,32 +631,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - issue.update_attribute(:assignee, @u_lazy_participant) - notification.reassigned_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) } - - before { notification.reassigned_issue(issue, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - issue.author = @u_lazy_participant - notification.reassigned_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { issue } + let(:notification_trigger) { notification.reassigned_issue(issue, @u_disabled) } end end @@ -720,32 +741,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - issue.update_attribute(:assignee, @u_lazy_participant) - notification.close_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) } - - before { notification.close_issue(issue, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - issue.author = @u_lazy_participant - notification.close_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { issue } + let(:notification_trigger) { notification.close_issue(issue, @u_disabled) } end end @@ -772,32 +771,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - issue.update_attribute(:assignee, @u_lazy_participant) - notification.reopen_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: 'anything', author: @u_lazy_participant) } - - before { notification.reopen_issue(issue, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - issue.author = @u_lazy_participant - notification.reopen_issue(issue, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { issue } + let(:notification_trigger) { notification.reopen_issue(issue, @u_disabled) } end end end @@ -858,31 +835,28 @@ describe NotificationService, services: true do end context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.new_merge_request(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } + it_should_behave_like 'participating by assignee notification' do + let(:participant) { create(:user, username: 'user-participant')} + let(:issuable) { merge_request } + let(:notification_trigger) { notification.new_merge_request(merge_request, @u_disabled) } end - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.new_merge_request(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } + it_should_behave_like 'participating by note notification' do + let(:participant) { create(:user, username: 'user-participant')} + let(:issuable) { merge_request } + let(:notification_trigger) { notification.new_merge_request(merge_request, @u_disabled) } end context 'by author' do + let(:participant) { create(:user, username: 'user-participant')} + before do - merge_request.author = @u_lazy_participant + merge_request.author = participant merge_request.save notification.new_merge_request(merge_request, @u_disabled) end - it { should_not_email(@u_lazy_participant) } + it { should_not_email(participant) } end end end @@ -917,33 +891,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.reassigned_merge_request(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.reassigned_merge_request(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.reassigned_merge_request(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { merge_request } + let(:notification_trigger) { notification.reassigned_merge_request(merge_request, @u_disabled) } end end @@ -1014,33 +965,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.close_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.close_mr(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.close_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { merge_request } + let(:notification_trigger) { notification.close_mr(merge_request, @u_disabled) } end end @@ -1081,33 +1009,10 @@ describe NotificationService, services: true do should_not_email(@u_watcher) end - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.merge_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.merge_mr(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.merge_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { merge_request } + let(:notification_trigger) { notification.merge_mr(merge_request, @u_disabled) } end end @@ -1134,33 +1039,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.reopen_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.reopen_mr(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.reopen_mr(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { merge_request } + let(:notification_trigger) { notification.reopen_mr(merge_request, @u_disabled) } end end @@ -1180,33 +1062,10 @@ describe NotificationService, services: true do should_not_email(@u_lazy_participant) end - context 'participating' do - context 'by assignee' do - before do - merge_request.update_attribute(:assignee, @u_lazy_participant) - notification.resolve_all_discussions(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end - - context 'by note' do - let!(:note) { create(:note_on_issue, noteable: merge_request, project_id: project.id, note: 'anything', author: @u_lazy_participant) } - - before { notification.resolve_all_discussions(merge_request, @u_disabled) } - - it { should_email(@u_lazy_participant) } - end - - context 'by author' do - before do - merge_request.author = @u_lazy_participant - merge_request.save - notification.resolve_all_discussions(merge_request, @u_disabled) - end - - it { should_email(@u_lazy_participant) } - end + it_behaves_like 'participating notifications' do + let(:participant) { create(:user, username: 'user-participant') } + let(:issuable) { merge_request } + let(:notification_trigger) { notification.resolve_all_discussions(merge_request, @u_disabled) } end end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 9f5a0ac4ec6..bd7269045e1 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -245,6 +245,8 @@ describe SystemNoteService, services: true do end describe '.change_title' do + let(:noteable) { create(:issue, project: project, title: 'Lorem ipsum') } + subject { described_class.change_title(noteable, project, author, 'Old title') } context 'when noteable responds to `title`' do @@ -252,7 +254,7 @@ describe SystemNoteService, services: true do it 'sets the note text' do expect(subject.note). - to eq "changed title from **{-Old title-}** to **{+#{noteable.title}+}**" + to eq "changed title from **{-Old title-}** to **{+Lorem ipsum+}**" end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e160c11111b..ab38dac65c5 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -9,6 +9,10 @@ require 'rspec/rails' require 'shoulda/matchers' require 'rspec/retry' +if ENV['RSPEC_PROFILING_POSTGRES_URL'] || ENV['RSPEC_PROFILING'] + require 'rspec_profiling/rspec' +end + if ENV['CI'] && !ENV['NO_KNAPSACK'] require 'knapsack' Knapsack::Adapters::RSpecAdapter.bind diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb index 35b40d73191..10b90b40ba7 100644 --- a/spec/support/cycle_analytics_helpers/test_generation.rb +++ b/spec/support/cycle_analytics_helpers/test_generation.rb @@ -54,7 +54,7 @@ module CycleAnalyticsHelpers end context "when the data belongs to another project" do - let(:other_project) { create(:project) } + let(:other_project) { create(:project, :repository) } it "returns nil" do # Use a stub to "trick" the data/condition functions diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index f57c82809a6..87936bb4859 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -12,7 +12,7 @@ shared_context 'mentionable context' do let!(:mentioned_mr) { create(:merge_request, source_project: project) } let(:mentioned_commit) { project.commit("HEAD~1") } - let(:ext_proj) { create(:project, :public) } + let(:ext_proj) { create(:project, :public, :repository) } let(:ext_issue) { create(:issue, project: ext_proj) } let(:ext_mr) { create(:merge_request, :simple, source_project: ext_proj) } let(:ext_commit) { ext_proj.commit("HEAD~2") } diff --git a/spec/support/mobile_helpers.rb b/spec/support/mobile_helpers.rb new file mode 100644 index 00000000000..20d5849bcab --- /dev/null +++ b/spec/support/mobile_helpers.rb @@ -0,0 +1,13 @@ +module MobileHelpers + def resize_screen_sm + resize_window(900, 768) + end + + def restore_window_size + resize_window(1366, 768) + end + + def resize_window(width, height) + page.driver.resize_window width, height + end +end diff --git a/spec/support/slack_mattermost_notifications_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb index 74d9b8c6313..704922b6cf4 100644 --- a/spec/support/slack_mattermost_notifications_shared_examples.rb +++ b/spec/support/slack_mattermost_notifications_shared_examples.rb @@ -26,7 +26,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do describe "#execute" do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:username) { 'slack_username' } let(:channel) { 'slack_channel' } @@ -196,7 +196,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do describe "Note events" do let(:user) { create(:user) } - let(:project) { create(:project, creator_id: user.id) } + let(:project) { create(:project, :repository, creator: user) } before do allow(chat_service).to receive_messages( @@ -269,7 +269,7 @@ RSpec.shared_examples 'slack or mattermost notifications' do describe 'Pipeline events' do let(:user) { create(:user) } - let(:project) { create(:project) } + let(:project) { create(:project, :repository) } let(:pipeline) do create(:ci_pipeline, diff --git a/spec/views/ci/lints/show.html.haml_spec.rb b/spec/views/ci/lints/show.html.haml_spec.rb index 2dac5ee23c8..3390ae247ff 100644 --- a/spec/views/ci/lints/show.html.haml_spec.rb +++ b/spec/views/ci/lints/show.html.haml_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'ci/lints/show' do - include Devise::TestHelpers + include Devise::Test::ControllerHelpers describe 'XSS protection' do let(:config_processor) { Ci::GitlabCiYamlProcessor.new(YAML.dump(content)) } diff --git a/spec/views/projects/builds/show.html.haml_spec.rb b/spec/views/projects/builds/show.html.haml_spec.rb index 745d0c745bd..44870cfcfb3 100644 --- a/spec/views/projects/builds/show.html.haml_spec.rb +++ b/spec/views/projects/builds/show.html.haml_spec.rb @@ -15,6 +15,36 @@ describe 'projects/builds/show', :view do allow(view).to receive(:can?).and_return(true) end + describe 'build information in header' do + let(:build) do + create(:ci_build, :success, environment: 'staging') + end + + before do + render + end + + it 'shows status name' do + expect(rendered).to have_css('.ci-status.ci-success', text: 'passed') + end + + it 'does not render a link to the build' do + expect(rendered).not_to have_link('passed') + end + + it 'shows build id' do + expect(rendered).to have_css('.js-build-id', text: build.id) + end + + it 'shows a link to the pipeline' do + expect(rendered).to have_link(build.pipeline.id) + end + + it 'shows a link to the commit' do + expect(rendered).to have_link(build.pipeline.short_sha) + end + end + describe 'environment info in build view' do context 'build with latest deployment' do let(:build) do |