From 405d8b456f65c7165e8cca83ab0be4c9db6cadd2 Mon Sep 17 00:00:00 2001 From: Jose Ivan Vargas Date: Thu, 18 Jan 2018 16:22:20 -0600 Subject: [PATCH 01/38] Added pipeline status to the files view --- .../commit_pipeline_status_component.vue | 95 +++++++++++++++++++ .../tree/services/commit_pipeline_service.js | 11 +++ .../pages/projects/tree/show/index.js | 18 ++++ app/assets/stylesheets/pages/commits.scss | 9 ++ app/helpers/ci_status_helper.rb | 11 --- app/views/projects/commits/_commit.html.haml | 7 +- .../commit_pipeline_status_component_spec.js | 68 +++++++++++++ 7 files changed, 202 insertions(+), 17 deletions(-) create mode 100644 app/assets/javascripts/pages/projects/tree/components/commit_pipeline_status_component.vue create mode 100644 app/assets/javascripts/pages/projects/tree/services/commit_pipeline_service.js create mode 100644 spec/javascripts/commit/commit_pipeline_status_component_spec.js diff --git a/app/assets/javascripts/pages/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/pages/projects/tree/components/commit_pipeline_status_component.vue new file mode 100644 index 00000000000..2bd1e8e3266 --- /dev/null +++ b/app/assets/javascripts/pages/projects/tree/components/commit_pipeline_status_component.vue @@ -0,0 +1,95 @@ + + diff --git a/app/assets/javascripts/pages/projects/tree/services/commit_pipeline_service.js b/app/assets/javascripts/pages/projects/tree/services/commit_pipeline_service.js new file mode 100644 index 00000000000..4b4189bc2de --- /dev/null +++ b/app/assets/javascripts/pages/projects/tree/services/commit_pipeline_service.js @@ -0,0 +1,11 @@ +import axios from '~/lib/utils/axios_utils'; + +export default class CommitPipelineService { + constructor(endpoint) { + this.endpoint = endpoint; + } + + fetchData() { + return axios.get(this.endpoint); + } +} diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js index 28a0160f47d..7181d4dfd47 100644 --- a/app/assets/javascripts/pages/projects/tree/show/index.js +++ b/app/assets/javascripts/pages/projects/tree/show/index.js @@ -1,8 +1,10 @@ +import Vue from 'vue'; import TreeView from '../../../../tree'; import ShortcutsNavigation from '../../../../shortcuts_navigation'; import BlobViewer from '../../../../blob/viewer'; import NewCommitForm from '../../../../new_commit_form'; import { ajaxGet } from '../../../../lib/utils/common_utils'; +import commitPipelineStatus from '../components/commit_pipeline_status_component.vue'; export default () => { new ShortcutsNavigation(); // eslint-disable-line no-new @@ -11,5 +13,21 @@ export default () => { new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new $('#tree-slider').waitForImages(() => ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath)); + + const commitPipelineStatusEl = document.getElementById('commit-pipeline-status'); + // eslint-disable-next-line no-new + new Vue({ + el: '#commit-pipeline-status', + components: { + commitPipelineStatus, + }, + render(createElement) { + return createElement('commit-pipeline-status', { + props: { + endpoint: commitPipelineStatusEl.dataset.endpoint, + }, + }); + }, + }); }; diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index aeaa33bd3bd..adfd72556b8 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -195,6 +195,10 @@ .commit-actions { @media (min-width: $screen-sm-min) { font-size: 0; + + span { + font-size: 6px; + } } .ci-status-link { @@ -219,6 +223,11 @@ font-size: 14px; font-weight: $gl-font-weight-bold; } + + .ci-status-icon { + position: relative; + top: 3px; + } } .commit, diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index 636316da80a..e8365f1da1e 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -103,17 +103,6 @@ module CiStatusHelper tooltip_placement: tooltip_placement) end - def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left') - project = commit.project - path = pipelines_project_commit_path(project, commit) - - render_status_with_link( - 'commit', - commit.status(ref), - path, - tooltip_placement: tooltip_placement) - end - def render_pipeline_status(pipeline, tooltip_placement: 'auto left') project = pipeline.project path = project_pipeline_path(project, pipeline) diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index d66066a6d0b..436e1739180 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -26,9 +26,6 @@ %span.commit-row-message.visible-xs-inline · = commit.short_id - - if commit.status(ref) - .visible-xs-inline - = render_commit_status(commit, ref: ref) - if commit.description? %button.text-expander.hidden-xs.js-toggle-button{ type: "button" } ... @@ -48,9 +45,7 @@ - else = render partial: 'projects/commit/ajax_signature', locals: { commit: commit } - - if commit.status(ref) - = render_commit_status(commit, ref: ref) - + #commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } } = link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link" = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard")) = link_to_browse_code(project, commit) diff --git a/spec/javascripts/commit/commit_pipeline_status_component_spec.js b/spec/javascripts/commit/commit_pipeline_status_component_spec.js new file mode 100644 index 00000000000..f6fca9e97e5 --- /dev/null +++ b/spec/javascripts/commit/commit_pipeline_status_component_spec.js @@ -0,0 +1,68 @@ +import Vue from 'vue'; +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; +import commitPipelineStatus from '~/pages/projects/tree/components/commit_pipeline_status_component.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; + +describe('Commit pipeline status component', () => { + let vm; + let component; + let mock; + const mockCiStatus = { + details_path: '/root/hello-world/pipelines/1', + favicon: 'canceled.ico', + group: 'canceled', + has_details: true, + icon: 'status_canceled', + label: 'canceled', + text: 'canceled', + }; + + beforeEach(() => { + mock = new MockAdapter(axios); + mock.onGet('/dummy/endpoint').reply(() => { + const res = Promise.resolve([200, { + pipelines: [ + { + details: { + status: mockCiStatus, + }, + }, + ], + }]); + return res; + }); + component = Vue.extend(commitPipelineStatus); + }); + + afterEach(() => { + mock.reset(); + }); + + describe('While polling pipeline data', () => { + beforeEach(() => { + vm = mountComponent(component, { + endpoint: '/dummy/endpoint', + }); + }); + + afterEach(() => { + vm.poll.stop(); + vm.$destroy(); + }); + + it('contains a ciStatus when the polling is succesful ', (done) => { + setTimeout(() => { + expect(vm.ciStatus).toEqual(mockCiStatus); + done(); + }, 1000); + }); + + it('contains a ci-status icon when polling is succesful', (done) => { + setTimeout(() => { + expect(vm.$el.querySelector('.ci-status-icon')).not.toBe(null); + done(); + }); + }); + }); +}); From 6042454600d79f1d6fb8e216c78b3e8b619a7a3e Mon Sep 17 00:00:00 2001 From: Jose Ivan Vargas Date: Fri, 19 Jan 2018 15:09:42 -0600 Subject: [PATCH 02/38] Added changelog --- ...5779-realtime-update-of-pipeline-status-in-files-view.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/35779-realtime-update-of-pipeline-status-in-files-view.yml diff --git a/changelogs/unreleased/35779-realtime-update-of-pipeline-status-in-files-view.yml b/changelogs/unreleased/35779-realtime-update-of-pipeline-status-in-files-view.yml new file mode 100644 index 00000000000..82df00fe631 --- /dev/null +++ b/changelogs/unreleased/35779-realtime-update-of-pipeline-status-in-files-view.yml @@ -0,0 +1,5 @@ +--- +title: Add realtime ci status for the repository -> files view +merge_request: 16523 +author: +type: added From bfc2b8a3d2c06c80126365348ce75b3985185e83 Mon Sep 17 00:00:00 2001 From: Jose Ivan Vargas Date: Mon, 22 Jan 2018 15:57:32 -0600 Subject: [PATCH 03/38] Added realtime prop and corrected specs --- .../javascripts/pages/projects/show/index.js | 19 +++++++++++++++++++ .../pages/projects/tree/show/index.js | 2 +- .../commit_pipeline_status_component.vue | 19 +++++++++++++++---- .../tree/services/commit_pipeline_service.js | 0 features/project/project.feature | 1 + features/steps/shared/project.rb | 1 + .../commit_pipeline_status_component_spec.js | 9 +++++++-- 7 files changed, 44 insertions(+), 7 deletions(-) rename app/assets/javascripts/{pages => }/projects/tree/components/commit_pipeline_status_component.vue (81%) rename app/assets/javascripts/{pages => }/projects/tree/services/commit_pipeline_service.js (100%) diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js index 55154cdddcb..0213b46eb7c 100644 --- a/app/assets/javascripts/pages/projects/show/index.js +++ b/app/assets/javascripts/pages/projects/show/index.js @@ -1,3 +1,4 @@ +import Vue from 'vue'; import ShortcutsNavigation from '~/shortcuts_navigation'; import NotificationsForm from '~/notifications_form'; import UserCallout from '~/user_callout'; @@ -5,6 +6,7 @@ import TreeView from '~/tree'; import BlobViewer from '~/blob/viewer/index'; import Activities from '~/activities'; import { ajaxGet } from '~/lib/utils/common_utils'; +import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue'; import Star from '../../../star'; import notificationsDropdown from '../../../notifications_dropdown'; @@ -24,4 +26,21 @@ export default () => { $('#tree-slider').waitForImages(() => { ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); }); + + const commitPipelineStatusEl = document.getElementById('commit-pipeline-status'); + // eslint-disable-next-line no-new + new Vue({ + el: '#commit-pipeline-status', + components: { + commitPipelineStatus, + }, + render(createElement) { + return createElement('commit-pipeline-status', { + props: { + endpoint: commitPipelineStatusEl.dataset.endpoint, + realtime: false, + }, + }); + }, + }); }; diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js index 7181d4dfd47..c0b3f98e66d 100644 --- a/app/assets/javascripts/pages/projects/tree/show/index.js +++ b/app/assets/javascripts/pages/projects/tree/show/index.js @@ -1,10 +1,10 @@ import Vue from 'vue'; +import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue'; import TreeView from '../../../../tree'; import ShortcutsNavigation from '../../../../shortcuts_navigation'; import BlobViewer from '../../../../blob/viewer'; import NewCommitForm from '../../../../new_commit_form'; import { ajaxGet } from '../../../../lib/utils/common_utils'; -import commitPipelineStatus from '../components/commit_pipeline_status_component.vue'; export default () => { new ShortcutsNavigation(); // eslint-disable-line no-new diff --git a/app/assets/javascripts/pages/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue similarity index 81% rename from app/assets/javascripts/pages/projects/tree/components/commit_pipeline_status_component.vue rename to app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue index 2bd1e8e3266..e13acf8555a 100644 --- a/app/assets/javascripts/pages/projects/tree/components/commit_pipeline_status_component.vue +++ b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue @@ -18,22 +18,33 @@ type: String, required: true, }, + realtime: { + type: Boolean, + required: false, + default: true, + }, }, data() { return { ciStatus: {}, isLoading: true, service: {}, + stageTitle: '', }; }, mounted() { this.service = new CommitPipelineService(this.endpoint); - this.initPolling(); + if (this.realtime) { + this.initPolling(); + } else { + this.fetchPipelineCommitData(); + } }, methods: { successCallback(res) { if (res.data.pipelines.length > 0) { - this.ciStatus = res.data.pipelines[0].details.status; + this.ciStatus = res.data.pipelines[0].details.stages[0].status; + this.stageTitle = res.data.pipelines[0].details.stages[0].title; this.isLoading = false; } else { this.isLoading = true; @@ -86,8 +97,8 @@ > diff --git a/app/assets/javascripts/pages/projects/tree/services/commit_pipeline_service.js b/app/assets/javascripts/projects/tree/services/commit_pipeline_service.js similarity index 100% rename from app/assets/javascripts/pages/projects/tree/services/commit_pipeline_service.js rename to app/assets/javascripts/projects/tree/services/commit_pipeline_service.js diff --git a/features/project/project.feature b/features/project/project.feature index 23817ef3ac9..bcd72c5c5a3 100644 --- a/features/project/project.feature +++ b/features/project/project.feature @@ -23,6 +23,7 @@ Feature: Project And I visit project "Shop" page Then I should see project "Shop" README + @javascript Scenario: I should see last commit with CI Given project "Shop" has CI enabled Given project "Shop" has CI build diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index affbccccdf9..923d54a6545 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -218,6 +218,7 @@ module SharedProject end step 'I should see last commit with CI status' do + sleep 2 page.within ".blob-commit-info" do expect(page).to have_content(project.commit.sha[0..6]) expect(page).to have_link("Commit: skipped") diff --git a/spec/javascripts/commit/commit_pipeline_status_component_spec.js b/spec/javascripts/commit/commit_pipeline_status_component_spec.js index f6fca9e97e5..2a52097e0d5 100644 --- a/spec/javascripts/commit/commit_pipeline_status_component_spec.js +++ b/spec/javascripts/commit/commit_pipeline_status_component_spec.js @@ -1,7 +1,7 @@ import Vue from 'vue'; import MockAdapter from 'axios-mock-adapter'; import axios from '~/lib/utils/axios_utils'; -import commitPipelineStatus from '~/pages/projects/tree/components/commit_pipeline_status_component.vue'; +import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue'; import mountComponent from '../helpers/vue_mount_component_helper'; describe('Commit pipeline status component', () => { @@ -25,7 +25,12 @@ describe('Commit pipeline status component', () => { pipelines: [ { details: { - status: mockCiStatus, + stages: [ + { + status: mockCiStatus, + title: 'Commit: canceled', + }, + ], }, }, ], From 449b0ebaf6f3ca65b48f372293117acc9a7e0abc Mon Sep 17 00:00:00 2001 From: Jose Ivan Vargas Date: Mon, 22 Jan 2018 18:34:21 -0600 Subject: [PATCH 04/38] Restored some code, add hidden class --- .../javascripts/pages/projects/show/index.js | 18 ------------------ .../pages/projects/tree/show/index.js | 5 +++++ app/helpers/ci_status_helper.rb | 11 +++++++++++ app/views/projects/commits/_commit.html.haml | 8 +++++++- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js index 0213b46eb7c..4c42fda16d7 100644 --- a/app/assets/javascripts/pages/projects/show/index.js +++ b/app/assets/javascripts/pages/projects/show/index.js @@ -1,4 +1,3 @@ -import Vue from 'vue'; import ShortcutsNavigation from '~/shortcuts_navigation'; import NotificationsForm from '~/notifications_form'; import UserCallout from '~/user_callout'; @@ -26,21 +25,4 @@ export default () => { $('#tree-slider').waitForImages(() => { ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath); }); - - const commitPipelineStatusEl = document.getElementById('commit-pipeline-status'); - // eslint-disable-next-line no-new - new Vue({ - el: '#commit-pipeline-status', - components: { - commitPipelineStatus, - }, - render(createElement) { - return createElement('commit-pipeline-status', { - props: { - endpoint: commitPipelineStatusEl.dataset.endpoint, - realtime: false, - }, - }); - }, - }); }; diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js index c0b3f98e66d..f14c3f86687 100644 --- a/app/assets/javascripts/pages/projects/tree/show/index.js +++ b/app/assets/javascripts/pages/projects/tree/show/index.js @@ -15,6 +15,11 @@ export default () => { ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath)); const commitPipelineStatusEl = document.getElementById('commit-pipeline-status'); + const $statusLink = $('.ci-status-link'); + if ($statusLink.length > 0) { + $statusLink.remove(); + } + commitPipelineStatusEl.classList.remove('hidden'); // eslint-disable-next-line no-new new Vue({ el: '#commit-pipeline-status', diff --git a/app/helpers/ci_status_helper.rb b/app/helpers/ci_status_helper.rb index e8365f1da1e..636316da80a 100644 --- a/app/helpers/ci_status_helper.rb +++ b/app/helpers/ci_status_helper.rb @@ -103,6 +103,17 @@ module CiStatusHelper tooltip_placement: tooltip_placement) end + def render_commit_status(commit, ref: nil, tooltip_placement: 'auto left') + project = commit.project + path = pipelines_project_commit_path(project, commit) + + render_status_with_link( + 'commit', + commit.status(ref), + path, + tooltip_placement: tooltip_placement) + end + def render_pipeline_status(pipeline, tooltip_placement: 'auto left') project = pipeline.project path = project_pipeline_path(project, pipeline) diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 436e1739180..c94e10947e6 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -26,6 +26,9 @@ %span.commit-row-message.visible-xs-inline · = commit.short_id + - if commit.status(ref) + .visible-xs-inline + = render_commit_status(commit, ref: ref) - if commit.description? %button.text-expander.hidden-xs.js-toggle-button{ type: "button" } ... @@ -45,7 +48,10 @@ - else = render partial: 'projects/commit/ajax_signature', locals: { commit: commit } - #commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } } + - if commit.status(ref) + = render_commit_status(commit, ref: ref) + + #commit-pipeline-status.hidden{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } } = link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link" = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard")) = link_to_browse_code(project, commit) From a6dbb85e978cb05255fd78de44d6f7b364f6dabc Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 29 Jan 2018 17:46:23 +0100 Subject: [PATCH 05/38] Remove Rugged exception in cache rescue --- app/models/repository.rb | 2 +- lib/gitlab/git/tree.rb | 2 ++ spec/lib/gitlab/git/tree_spec.rb | 20 ++++++++------------ spec/models/repository_spec.rb | 8 +++----- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 872d4468ac8..21a5c6c17cd 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -949,7 +949,7 @@ class Repository end instance_variable_set(ivar, value) - rescue Rugged::ReferenceError, Gitlab::Git::Repository::NoRepository + rescue Gitlab::Git::Repository::NoRepository # Even if the above `#exists?` check passes these errors might still # occur (for example because of a non-existing HEAD). We want to # gracefully handle this and not cache anything diff --git a/lib/gitlab/git/tree.rb b/lib/gitlab/git/tree.rb index 5cf336af3c6..ba6058fd3c9 100644 --- a/lib/gitlab/git/tree.rb +++ b/lib/gitlab/git/tree.rb @@ -83,6 +83,8 @@ module Gitlab commit_id: sha ) end + rescue Rugged::ReferenceError + [] end end diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb index 86f7bcb8e38..001e406a930 100644 --- a/spec/lib/gitlab/git/tree_spec.rb +++ b/spec/lib/gitlab/git/tree_spec.rb @@ -80,22 +80,18 @@ describe Gitlab::Git::Tree, seed_helper: true do end describe '#where' do - context 'with gitaly disabled' do - before do - allow(Gitlab::GitalyClient).to receive(:feature_enabled?).and_return(false) - end - - it 'calls #tree_entries_from_rugged' do - expect(described_class).to receive(:tree_entries_from_rugged) - - described_class.where(repository, SeedRepo::Commit::ID, '/') + shared_examples '#where' do + it 'returns an empty array when called with an invalid ref' do + expect(described_class.where(repository, 'foobar-does-not-exist')).to eq([]) end end - it 'gets the tree entries from GitalyClient' do - expect_any_instance_of(Gitlab::GitalyClient::CommitService).to receive(:tree_entries) + context 'with gitaly' do + it_behaves_like '#where' + end - described_class.where(repository, SeedRepo::Commit::ID, '/') + context 'without gitaly', :skip_gitaly_mock do + it_behaves_like '#where' end end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index d4070b320ed..1102b1c9006 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -772,8 +772,7 @@ describe Repository do user, 'LICENSE', 'Copyright!', message: 'Add LICENSE', branch_name: 'master') - allow(repository).to receive(:file_on_head) - .and_raise(Rugged::ReferenceError) + allow(repository).to receive(:root_ref).and_raise(Gitlab::Git::Repository::NoRepository) expect(repository.license_blob).to be_nil end @@ -885,7 +884,7 @@ describe Repository do end it 'returns nil for empty repository' do - allow(repository).to receive(:file_on_head).and_raise(Rugged::ReferenceError) + allow(repository).to receive(:root_ref).and_raise(Gitlab::Git::Repository::NoRepository) expect(repository.gitlab_ci_yml).to be_nil end end @@ -1937,8 +1936,7 @@ describe Repository do describe '#avatar' do it 'returns nil if repo does not exist' do - expect(repository).to receive(:file_on_head) - .and_raise(Rugged::ReferenceError) + allow(repository).to receive(:root_ref).and_raise(Gitlab::Git::Repository::NoRepository) expect(repository.avatar).to eq(nil) end From 11191f938c3b865dbb3e203ecdb018fd91bd0c91 Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Mon, 29 Jan 2018 18:22:33 +0100 Subject: [PATCH 06/38] Move Repository#can_be_merged? to Gitlab::Git::Repository Fixes #42544 --- app/models/repository.rb | 20 +------------------- lib/gitlab/git/repository.rb | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/models/repository.rb b/app/models/repository.rb index 5b06dc5a39b..49a094ab2da 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -800,16 +800,6 @@ class Repository with_cache_hooks { raw.multi_action(user, **options) } end - def can_be_merged?(source_sha, target_branch) - raw_repository.gitaly_migrate(:can_be_merged) do |is_enabled| - if is_enabled - gitaly_can_be_merged?(source_sha, find_branch(target_branch).target) - else - rugged_can_be_merged?(source_sha, target_branch) - end - end - end - def merge(user, source_sha, merge_request, message) with_cache_hooks do raw_repository.merge(user, source_sha, merge_request.target_branch, message) do |commit_id| @@ -876,7 +866,7 @@ class Repository @root_ref_sha ||= commit(root_ref).sha end - delegate :merged_branch_names, to: :raw_repository + delegate :merged_branch_names, :can_be_merged?, to: :raw_repository def merge_base(first_commit_id, second_commit_id) first_commit_id = commit(first_commit_id).try(:id) || first_commit_id @@ -1095,12 +1085,4 @@ class Repository def initialize_raw_repository Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', Gitlab::GlRepository.gl_repository(project, is_wiki)) end - - def gitaly_can_be_merged?(their_commit, our_commit) - !raw_repository.gitaly_conflicts_client(our_commit, their_commit).conflicts? - end - - def rugged_can_be_merged?(their_commit, our_commit) - !rugged.merge_commits(our_commit, their_commit).conflicts? - end end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 3a7930154e5..ff2a5df5676 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -1392,6 +1392,16 @@ module Gitlab run_git(args).first.scrub.split(/^--$/) end + def can_be_merged?(source_sha, target_branch) + gitaly_migrate(:can_be_merged) do |is_enabled| + if is_enabled + gitaly_can_be_merged?(source_sha, find_branch(target_branch, true).target) + else + rugged_can_be_merged?(source_sha, target_branch) + end + end + end + def search_files_by_name(query, ref) safe_query = Regexp.escape(query.sub(/^\/*/, "")) @@ -2234,6 +2244,14 @@ module Gitlab run_git(['fetch', remote_name], env: env).last.zero? end + def gitaly_can_be_merged?(their_commit, our_commit) + !gitaly_conflicts_client(our_commit, their_commit).conflicts? + end + + def rugged_can_be_merged?(their_commit, our_commit) + !rugged.merge_commits(our_commit, their_commit).conflicts? + end + def gitlab_projects_error raise CommandError, @gitlab_projects.output end From d700659b04b6318a35c7637f9af04c52df63a87b Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 30 Jan 2018 20:34:11 +0000 Subject: [PATCH 07/38] Move mr widget related links into a vue file --- .../components/mr_widget_related_links.js | 37 ----- .../components/mr_widget_related_links.vue | 42 ++++++ .../vue_merge_request_widget/dependencies.js | 2 +- .../mr_widget_options.js | 6 +- .../mr_widget_related_links_spec.js | 141 +++++++----------- 5 files changed, 100 insertions(+), 128 deletions(-) delete mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js create mode 100644 app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js deleted file mode 100644 index 563267ad044..00000000000 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.js +++ /dev/null @@ -1,37 +0,0 @@ -export default { - name: 'MRWidgetRelatedLinks', - props: { - relatedLinks: { type: Object, required: true }, - state: { type: String, required: false }, - }, - computed: { - hasLinks() { - const { closing, mentioned, assignToMe } = this.relatedLinks; - return closing || mentioned || assignToMe; - }, - closesText() { - if (this.state === 'merged') { - return 'Closed'; - } - if (this.state === 'closed') { - return 'Did not close'; - } - return 'Closes'; - }, - }, - template: ` - - `, -}; diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue new file mode 100644 index 00000000000..080ec1c579c --- /dev/null +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue @@ -0,0 +1,42 @@ + + diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js index 2917090e073..515eb32f5d6 100644 --- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js +++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js @@ -15,7 +15,7 @@ export { default as WidgetHeader } from './components/mr_widget_header'; export { default as WidgetMergeHelp } from './components/mr_widget_merge_help'; export { default as WidgetPipeline } from './components/mr_widget_pipeline.vue'; export { default as WidgetDeployment } from './components/mr_widget_deployment'; -export { default as WidgetRelatedLinks } from './components/mr_widget_related_links'; +export { default as WidgetRelatedLinks } from './components/mr_widget_related_links.vue'; export { default as MergedState } from './components/states/mr_widget_merged.vue'; export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge.vue'; export { default as ClosedState } from './components/states/mr_widget_closed.vue'; diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js index 98d33f9efaa..6d3573de76c 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js @@ -63,7 +63,8 @@ export default { return this.mr.hasCI; }, shouldRenderRelatedLinks() { - return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState; + const { closing, mentioned, assignToMe } = this.mr.relatedLinks; + return (closing || mentioned || assignToMe) && !this.mr.isNothingToMergeState; }, shouldRenderDeployments() { return this.mr.deployments.length; @@ -257,7 +258,8 @@ export default { + :related-links="mr.relatedLinks" + /> // - // Only load once + +import { __ } from './locale'; +import axios from './lib/utils/axios_utils'; +import flash from './flash'; + +// Only load once let katexLoaded = false; // Loop over all math elements and render math @@ -33,19 +38,26 @@ export default function renderMath($els) { if (katexLoaded) { renderWithKaTeX($els); } else { - $.get(gon.katex_css_url, () => { - const css = $('', { - rel: 'stylesheet', - type: 'text/css', - href: gon.katex_css_url, - }); - css.appendTo('head'); - - // Load KaTeX js - $.getScript(gon.katex_js_url, () => { + axios.get(gon.katex_css_url) + .then(() => { + const css = $('', { + rel: 'stylesheet', + type: 'text/css', + href: gon.katex_css_url, + }); + css.appendTo('head'); + }) + .then(() => axios.get(gon.katex_js_url, { + responseType: 'text', + })) + .then(({ data }) => { + // Add katex js to our document + $.globalEval(data); + }) + .then(() => { katexLoaded = true; renderWithKaTeX($els); // Run KaTeX - }); - }); + }) + .catch(() => flash(__('An error occurred while rendering KaTeX'))); } } From 30e6cfa05a361cbdc12ddb7c12061f4c7221c61b Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 30 Jan 2018 15:45:23 -0800 Subject: [PATCH 09/38] Fix not all events being shown in group dashboard The group activity feed was limited to the first 20 projects found in the group, which caused activity from some projects to be omitted. A limit of 20 is applied to the query for events, so the extra pagination does little in the way of performance. Closes #42560 --- app/controllers/groups_controller.rb | 1 - .../unreleased/sh-fix-events-collection.yml | 5 ++++ spec/controllers/groups_controller_spec.rb | 24 +++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/sh-fix-events-collection.yml diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index eb53a522f90..75270a0889b 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -150,7 +150,6 @@ class GroupsController < Groups::ApplicationController @projects = GroupProjectsFinder.new(params: params, group: group, options: options, current_user: current_user) .execute .includes(:namespace) - .page(params[:page]) @events = EventCollection .new(@projects, offset: params[:offset].to_i, filter: event_filter) diff --git a/changelogs/unreleased/sh-fix-events-collection.yml b/changelogs/unreleased/sh-fix-events-collection.yml new file mode 100644 index 00000000000..50af39d9caf --- /dev/null +++ b/changelogs/unreleased/sh-fix-events-collection.yml @@ -0,0 +1,5 @@ +--- +title: Fix not all events being shown in group dashboard +merge_request: +author: +type: fixed diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb index a9cfd964dd5..492fed42d31 100644 --- a/spec/controllers/groups_controller_spec.rb +++ b/spec/controllers/groups_controller_spec.rb @@ -85,6 +85,30 @@ describe GroupsController do end end + describe 'GET #activity' do + render_views + + before do + sign_in(user) + project + end + + context 'as json' do + it 'includes all projects in event feed' do + 3.times do + project = create(:project, group: group) + create(:event, project: project) + end + + get :activity, id: group.to_param, format: :json + + expect(response).to have_gitlab_http_status(200) + expect(json_response['count']).to eq(3) + expect(assigns(:projects).limit_value).to be_nil + end + end + end + describe 'POST #create' do context 'when creating subgroups', :nested_groups do [true, false].each do |can_create_group_status| From 46a6edc7314ce8acab5d8ce04799bd3557bc26bc Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 26 Jan 2018 22:16:56 -0800 Subject: [PATCH 10/38] Remove N+1 queries with /projects/:project_id/{access_requests,members} API endpoints We can simplify the code quite a bit and improve performance by using grape-entity merge fields: https://github.com/ruby-grape/grape-entity/tree/v0.6.0#merge-fields Relates to #42030 --- .../sh-fix-project-members-api-perf.yml | 6 ++++++ lib/api/access_requests.rb | 6 +++--- lib/api/entities.rb | 21 +++++++------------ lib/api/members.rb | 13 ++++++------ lib/api/v3/members.rb | 15 ++++++------- spec/requests/api/members_spec.rb | 15 +++++++++++++ 6 files changed, 46 insertions(+), 30 deletions(-) create mode 100644 changelogs/unreleased/sh-fix-project-members-api-perf.yml diff --git a/changelogs/unreleased/sh-fix-project-members-api-perf.yml b/changelogs/unreleased/sh-fix-project-members-api-perf.yml new file mode 100644 index 00000000000..c3fff933547 --- /dev/null +++ b/changelogs/unreleased/sh-fix-project-members-api-perf.yml @@ -0,0 +1,6 @@ +--- +title: Remove N+1 queries with /projects/:project_id/{access_requests,members} API + endpoints +merge_request: +author: +type: performance diff --git a/lib/api/access_requests.rb b/lib/api/access_requests.rb index 374b611f55e..60ae5e6b9a2 100644 --- a/lib/api/access_requests.rb +++ b/lib/api/access_requests.rb @@ -24,7 +24,7 @@ module API access_requesters = AccessRequestsFinder.new(source).execute!(current_user) access_requesters = paginate(access_requesters.includes(:user)) - present access_requesters.map(&:user), with: Entities::AccessRequester, source: source + present access_requesters, with: Entities::AccessRequester end desc "Requests access for the authenticated user to a #{source_type}." do @@ -36,7 +36,7 @@ module API access_requester = source.request_access(current_user) if access_requester.persisted? - present access_requester.user, with: Entities::AccessRequester, access_requester: access_requester + present access_requester, with: Entities::AccessRequester else render_validation_error!(access_requester) end @@ -56,7 +56,7 @@ module API member = ::Members::ApproveAccessRequestService.new(source, current_user, declared_params).execute status :created - present member.user, with: Entities::Member, member: member + present member, with: Entities::Member end desc 'Denies an access request for the given user.' do diff --git a/lib/api/entities.rb b/lib/api/entities.rb index cb222697f32..e13463ec66b 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -205,22 +205,15 @@ module API expose :build_artifacts_size, as: :job_artifacts_size end - class Member < UserBasic - expose :access_level do |user, options| - member = options[:member] || options[:source].members.find_by(user_id: user.id) - member.access_level - end - expose :expires_at do |user, options| - member = options[:member] || options[:source].members.find_by(user_id: user.id) - member.expires_at - end + class Member < Grape::Entity + expose :user, merge: true, using: UserBasic + expose :access_level + expose :expires_at end - class AccessRequester < UserBasic - expose :requested_at do |user, options| - access_requester = options[:access_requester] || options[:source].requesters.find_by(user_id: user.id) - access_requester.requested_at - end + class AccessRequester < Grape::Entity + expose :user, merge: true, using: UserBasic + expose :requested_at end class Group < Grape::Entity diff --git a/lib/api/members.rb b/lib/api/members.rb index 130c6d6da71..bc1de37284a 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -21,10 +21,11 @@ module API get ":id/members" do source = find_source(source_type, params[:id]) - users = source.users - users = users.merge(User.search(params[:query])) if params[:query].present? + members = source.members.where.not(user_id: nil).includes(:user) + members = members.joins(:user).merge(User.search(params[:query])) if params[:query].present? + members = paginate(members) - present paginate(users), with: Entities::Member, source: source + present members, with: Entities::Member end desc 'Gets a member of a group or project.' do @@ -39,7 +40,7 @@ module API members = source.members member = members.find_by!(user_id: params[:user_id]) - present member.user, with: Entities::Member, member: member + present member, with: Entities::Member end desc 'Adds a member to a group or project.' do @@ -62,7 +63,7 @@ module API if !member not_allowed! # This currently can only be reached in EE elsif member.persisted? && member.valid? - present member.user, with: Entities::Member, member: member + present member, with: Entities::Member else render_validation_error!(member) end @@ -83,7 +84,7 @@ module API member = source.members.find_by!(user_id: params.delete(:user_id)) if member.update_attributes(declared_params(include_missing: false)) - present member.user, with: Entities::Member, member: member + present member, with: Entities::Member else render_validation_error!(member) end diff --git a/lib/api/v3/members.rb b/lib/api/v3/members.rb index 46145cac7a5..d7bde8ceb89 100644 --- a/lib/api/v3/members.rb +++ b/lib/api/v3/members.rb @@ -22,10 +22,11 @@ module API get ":id/members" do source = find_source(source_type, params[:id]) - users = source.users - users = users.merge(User.search(params[:query])) if params[:query].present? + members = source.members.where.not(user_id: nil).includes(:user) + members = members.joins(:user).merge(User.search(params[:query])) if params[:query].present? + members = paginate(members) - present paginate(users), with: ::API::Entities::Member, source: source + present members, with: ::API::Entities::Member end desc 'Gets a member of a group or project.' do @@ -40,7 +41,7 @@ module API members = source.members member = members.find_by!(user_id: params[:user_id]) - present member.user, with: ::API::Entities::Member, member: member + present member, with: ::API::Entities::Member end desc 'Adds a member to a group or project.' do @@ -69,7 +70,7 @@ module API end if member.persisted? && member.valid? - present member.user, with: ::API::Entities::Member, member: member + present member, with: ::API::Entities::Member else # This is to ensure back-compatibility but 400 behavior should be used # for all validation errors in 9.0! @@ -93,7 +94,7 @@ module API member = source.members.find_by!(user_id: params.delete(:user_id)) if member.update_attributes(declared_params(include_missing: false)) - present member.user, with: ::API::Entities::Member, member: member + present member, with: ::API::Entities::Member else # This is to ensure back-compatibility but 400 behavior should be used # for all validation errors in 9.0! @@ -125,7 +126,7 @@ module API else ::Members::DestroyService.new(source, current_user, declared_params).execute - present member.user, with: ::API::Entities::Member, member: member + present member, with: ::API::Entities::Member end end end diff --git a/spec/requests/api/members_spec.rb b/spec/requests/api/members_spec.rb index 73bd4785b11..ec500838eb2 100644 --- a/spec/requests/api/members_spec.rb +++ b/spec/requests/api/members_spec.rb @@ -44,6 +44,21 @@ describe API::Members do end end + it 'avoids N+1 queries' do + # Establish baseline + get api("/#{source_type.pluralize}/#{source.id}/members", master) + + control = ActiveRecord::QueryRecorder.new do + get api("/#{source_type.pluralize}/#{source.id}/members", master) + end + + project.add_developer(create(:user)) + + expect do + get api("/#{source_type.pluralize}/#{source.id}/members", master) + end.not_to exceed_query_limit(control) + end + it 'does not return invitees' do create(:"#{source_type}_member", invite_token: '123', invite_email: 'test@abc.com', source: source, user: nil) From 94a3dbca33634dd4f6a1de8b14cd2a4f0a9a0abc Mon Sep 17 00:00:00 2001 From: Zeger-Jan van de Weg Date: Tue, 30 Jan 2018 11:11:47 +0100 Subject: [PATCH 11/38] Gitaly Server info for admin panel Implements the client side for gitlab-org/gitaly#819. Which is a server info command. This checks the server version and git binairy version on the server. A small UI was added for administrators, so they can check the status of the Gitaly server. This is done for each storage the monolith knows. Because of this commit, gitlab-org/gitlab-ce!15580 is now closed. That MR removed the Git version too, but didn't replace it with anything. --- GITALY_SERVER_VERSION | 2 +- Gemfile | 2 +- Gemfile.lock | 8 +- .../admin/gitaly_servers_controller.rb | 5 + app/views/admin/dashboard/index.html.haml | 10 +- .../admin/gitaly_servers/index.html.haml | 31 ++ .../unreleased/zj-gitaly-server-info.yml | 5 + config/routes/admin.rb | 2 + lib/gitaly/server.rb | 43 ++ lib/gitlab/gitaly_client/server_service.rb | 16 + locale/gitlab.pot | 492 ++++++++++++++---- .../admin/gitaly_servers_controller_spec.rb | 15 + spec/lib/gitaly/server_spec.rb | 30 ++ 13 files changed, 555 insertions(+), 106 deletions(-) create mode 100644 app/controllers/admin/gitaly_servers_controller.rb create mode 100644 app/views/admin/gitaly_servers/index.html.haml create mode 100644 changelogs/unreleased/zj-gitaly-server-info.yml create mode 100644 lib/gitaly/server.rb create mode 100644 lib/gitlab/gitaly_client/server_service.rb create mode 100644 spec/controllers/admin/gitaly_servers_controller_spec.rb create mode 100644 spec/lib/gitaly/server_spec.rb diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index b7c0622b4f4..62df9f538d8 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.74.0 +0.76.0 diff --git a/Gemfile b/Gemfile index 05f72b6482f..1c9bd409084 100644 --- a/Gemfile +++ b/Gemfile @@ -406,7 +406,7 @@ group :ed25519 do end # Gitaly GRPC client -gem 'gitaly-proto', '~> 0.78.0', require: 'gitaly' +gem 'gitaly-proto', '~> 0.83.0', require: 'gitaly' gem 'toml-rb', '~> 0.3.15', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 1a3c8f42469..042809f918f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -285,7 +285,7 @@ GEM po_to_json (>= 1.0.0) rails (>= 3.2.0) gherkin-ruby (0.3.2) - gitaly-proto (0.78.0) + gitaly-proto (0.83.0) google-protobuf (~> 3.1) grpc (~> 1.0) github-linguist (4.7.6) @@ -340,7 +340,7 @@ GEM mime-types (~> 3.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) - google-protobuf (3.4.1.1) + google-protobuf (3.5.1.1-universal-darwin) googleapis-common-protos-types (1.0.1) google-protobuf (~> 3.0) googleauth (0.5.3) @@ -369,7 +369,7 @@ GEM rake grape_logging (1.7.0) grape - grpc (1.8.3) + grpc (1.8.3-universal-darwin) google-protobuf (~> 3.1) googleapis-common-protos-types (~> 1.0.0) googleauth (>= 0.5.1, < 0.7) @@ -1056,7 +1056,7 @@ DEPENDENCIES gettext (~> 3.2.2) gettext_i18n_rails (~> 1.8.0) gettext_i18n_rails_js (~> 1.2.0) - gitaly-proto (~> 0.78.0) + gitaly-proto (~> 0.83.0) github-linguist (~> 4.7.0) gitlab-flowdock-git-hook (~> 1.0.1) gitlab-markup (~> 1.6.2) diff --git a/app/controllers/admin/gitaly_servers_controller.rb b/app/controllers/admin/gitaly_servers_controller.rb new file mode 100644 index 00000000000..11c4dfe3d8d --- /dev/null +++ b/app/controllers/admin/gitaly_servers_controller.rb @@ -0,0 +1,5 @@ +class Admin::GitalyServersController < Admin::ApplicationController + def index + @gitaly_servers = Gitaly::Server.all + end +end diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 509f559c120..d251f75a8fd 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -138,19 +138,11 @@ GitLab API %span.pull-right = API::API::version - %p - Gitaly - %span.pull-right - = Gitlab::GitalyClient.expected_server_version - if Gitlab.config.pages.enabled %p GitLab Pages %span.pull-right = Gitlab::Pages::VERSION - %p - Git - %span.pull-right - = Gitlab::Git.version %p Ruby %span.pull-right @@ -163,6 +155,8 @@ = Gitlab::Database.adapter_name %span.pull-right = Gitlab::Database.version + %p + = link_to "Gitaly Servers", admin_gitaly_servers_path .row .col-md-4 .info-well diff --git a/app/views/admin/gitaly_servers/index.html.haml b/app/views/admin/gitaly_servers/index.html.haml new file mode 100644 index 00000000000..231f94dc95d --- /dev/null +++ b/app/views/admin/gitaly_servers/index.html.haml @@ -0,0 +1,31 @@ +- breadcrumb_title _("Gitaly Servers") + +%h3.page-title= _("Gitaly Servers") +%hr +.gitaly_servers + - if @gitaly_servers.any? + .table-holder + %table.table.responsive-table + %thead.hidden-sm.hidden-xs + %tr + %th= _("Storage") + %th= n_("Gitaly|Address") + %th= _("Server version") + %th= _("Git version") + %th= _("Up to date") + - @gitaly_servers.each do |server| + %tr + %td + = server.storage + %td + = server.address + %td + = server.server_version + %td + = server.git_binary_version + %td + = boolean_to_icon(server.up_to_date?) + - else + .empty-state + .text-center + %h4= _("No connection could be made to a Gitaly Server, please check your logs!") diff --git a/changelogs/unreleased/zj-gitaly-server-info.yml b/changelogs/unreleased/zj-gitaly-server-info.yml new file mode 100644 index 00000000000..cf6295f2bbc --- /dev/null +++ b/changelogs/unreleased/zj-gitaly-server-info.yml @@ -0,0 +1,5 @@ +--- +title: Add Gitaly Servers admin dashboard +merge_request: +author: +type: added diff --git a/config/routes/admin.rb b/config/routes/admin.rb index e22fb440abc..57e401c9b89 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -24,6 +24,8 @@ namespace :admin do resource :impersonation, only: :destroy resources :abuse_reports, only: [:index, :destroy] + resources :gitaly_servers, only: [:index] + resources :spam_logs, only: [:index, :destroy] do member do post :mark_as_ham diff --git a/lib/gitaly/server.rb b/lib/gitaly/server.rb new file mode 100644 index 00000000000..605e93022e7 --- /dev/null +++ b/lib/gitaly/server.rb @@ -0,0 +1,43 @@ +module Gitaly + class Server + def self.all + Gitlab.config.repositories.storages.keys.map { |s| Gitaly::Server.new(s) } + end + + attr_reader :storage + + def initialize(storage) + @storage = storage + end + + def server_version + info.server_version + end + + def git_binary_version + info.git_version + end + + def up_to_date? + server_version == Gitlab::GitalyClient.expected_server_version + end + + def address + Gitlab::GitalyClient.address(@storage) + rescue RuntimeError => e + "Error getting the address: #{e.message}" + end + + private + + def info + @info ||= + begin + Gitlab::GitalyClient::ServerService.new(@storage).info + rescue GRPC::Unavailable, GRPC::GRPC::DeadlineExceeded + # This will show the server as being out of date + Gitaly::ServerInfoResponse.new(git_version: '', server_version: '') + end + end + end +end diff --git a/lib/gitlab/gitaly_client/server_service.rb b/lib/gitlab/gitaly_client/server_service.rb new file mode 100644 index 00000000000..2e1076d1f66 --- /dev/null +++ b/lib/gitlab/gitaly_client/server_service.rb @@ -0,0 +1,16 @@ +module Gitlab + module GitalyClient + # Meant for extraction of server data, and later maybe to perform misc task + # + # Not meant for connection logic, look in Gitlab::GitalyClient + class ServerService + def initialize(storage) + @storage = storage + end + + def info + GitalyClient.call(@storage, :server_service, :server_info, Gitaly::ServerInfoRequest.new) + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 74d76caf47d..94458d60e01 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8,8 +8,8 @@ msgid "" msgstr "" "Project-Id-Version: gitlab 1.0.0\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2017-12-05 20:31+0100\n" -"PO-Revision-Date: 2017-12-05 20:31+0100\n" +"POT-Creation-Date: 2018-01-30 14:59+0100\n" +"PO-Revision-Date: 2018-01-30 14:59+0100\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: \n" @@ -23,17 +23,27 @@ msgid_plural "%d commits" msgstr[0] "" msgstr[1] "" +msgid "%d issue" +msgid_plural "%d issues" +msgstr[0] "" +msgstr[1] "" + msgid "%d layer" msgid_plural "%d layers" msgstr[0] "" msgstr[1] "" +msgid "%d merge request" +msgid_plural "%d merge requests" +msgstr[0] "" +msgstr[1] "" + msgid "%s additional commit has been omitted to prevent performance issues." msgid_plural "%s additional commits have been omitted to prevent performance issues." msgstr[0] "" msgstr[1] "" -msgid "%{commit_author_link} committed %{commit_timeago}" +msgid "%{commit_author_link} authored %{commit_timeago}" msgstr "" msgid "%{count} participant" @@ -47,9 +57,6 @@ msgstr "" msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will allow access on the next attempt." msgstr "" -msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will block access for %{number_of_seconds} seconds." -msgstr "" - msgid "%{number_of_failures} of %{maximum_failures} failures. GitLab will not retry automatically. Reset storage information when the problem is resolved." msgstr "" @@ -117,6 +124,21 @@ msgstr "" msgid "Add new directory" msgstr "" +msgid "AdminArea|Stop all jobs" +msgstr "" + +msgid "AdminArea|Stop all jobs?" +msgstr "" + +msgid "AdminArea|Stop jobs" +msgstr "" + +msgid "AdminArea|Stopping jobs failed" +msgstr "" + +msgid "AdminArea|You’re about to stop all jobs. This will halt all current jobs that are running." +msgstr "" + msgid "AdminHealthPageLink|health page" msgstr "" @@ -126,12 +148,18 @@ msgstr "" msgid "All" msgstr "" +msgid "An error occurred previewing the blob" +msgstr "" + msgid "An error occurred when toggling the notification subscription" msgstr "" msgid "An error occurred while fetching sidebar data" msgstr "" +msgid "An error occurred while retrieving diff" +msgstr "" + msgid "An error occurred. Please try again." msgstr "" @@ -156,9 +184,6 @@ msgstr "" msgid "Are you sure you want to discard your changes?" msgstr "" -msgid "Are you sure you want to leave this group?" -msgstr "" - msgid "Are you sure you want to reset registration token?" msgstr "" @@ -186,15 +211,15 @@ msgstr "" msgid "Author" msgstr "" +msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." +msgstr "" + msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly." msgstr "" msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly." msgstr "" -msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly." -msgstr "" - msgid "AutoDevOps|Auto DevOps (Beta)" msgstr "" @@ -216,6 +241,9 @@ msgstr "" msgid "Available" msgstr "" +msgid "Avatar will be removed. Are you sure?" +msgstr "" + msgid "Branch" msgid_plural "Branches" msgstr[0] "" @@ -359,15 +387,24 @@ msgstr "" msgid "ChangeTypeAction|Revert" msgstr "" +msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes." +msgstr "" + msgid "Changelog" msgstr "" +msgid "Changes are shown as if the source revision was being merged into the target revision." +msgstr "" + msgid "Charts" msgstr "" msgid "Chat" msgstr "" +msgid "Check interval" +msgstr "" + msgid "Checking %{text} availability…" msgstr "" @@ -380,6 +417,15 @@ msgstr "" msgid "Cherry-pick this merge request" msgstr "" +msgid "Choose File ..." +msgstr "" + +msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request." +msgstr "" + +msgid "Choose file..." +msgstr "" + msgid "CiStatusLabel|canceled" msgstr "" @@ -437,6 +483,9 @@ msgstr "" msgid "CircuitBreakerApiLink|circuitbreaker api" msgstr "" +msgid "Click to expand text" +msgstr "" + msgid "Clone repository" msgstr "" @@ -446,27 +495,24 @@ msgstr "" msgid "ClusterIntegration|%{appList} was successfully installed on your cluster" msgstr "" -msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which incur additional costs. See %{pricingLink}" -msgstr "" - msgid "ClusterIntegration|API URL" msgstr "" -msgid "ClusterIntegration|Active" -msgstr "" - msgid "ClusterIntegration|Add an existing cluster" msgstr "" msgid "ClusterIntegration|Add cluster" msgstr "" -msgid "ClusterIntegration|All" +msgid "ClusterIntegration|Advanced options on this cluster's integration" msgstr "" msgid "ClusterIntegration|Applications" msgstr "" +msgid "ClusterIntegration|Are you sure you want to remove this cluster's integration? This will not delete your actual cluster." +msgstr "" + msgid "ClusterIntegration|CA Certificate" msgstr "" @@ -476,6 +522,9 @@ msgstr "" msgid "ClusterIntegration|Choose how to set up cluster integration" msgstr "" +msgid "ClusterIntegration|Choose which of your project's environments will use this cluster." +msgstr "" + msgid "ClusterIntegration|Cluster" msgstr "" @@ -506,6 +555,12 @@ msgstr "" msgid "ClusterIntegration|Clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}" msgstr "" +msgid "ClusterIntegration|Clusters can be used to deploy applications and to provide Review Apps for this project" +msgstr "" + +msgid "ClusterIntegration|Control how your cluster integrates with GitLab" +msgstr "" + msgid "ClusterIntegration|Copy API URL" msgstr "" @@ -518,7 +573,7 @@ msgstr "" msgid "ClusterIntegration|Copy cluster name" msgstr "" -msgid "ClusterIntegration|Create a new cluster on Google Engine right from GitLab" +msgid "ClusterIntegration|Create a new cluster on Google Kubernetes Engine right from GitLab" msgstr "" msgid "ClusterIntegration|Create cluster" @@ -530,19 +585,16 @@ msgstr "" msgid "ClusterIntegration|Create on GKE" msgstr "" -msgid "ClusterIntegration|Enable cluster integration" -msgstr "" - msgid "ClusterIntegration|Enter the details for an existing Kubernetes cluster" msgstr "" msgid "ClusterIntegration|Enter the details for your cluster" msgstr "" -msgid "ClusterIntegration|Environment pattern" +msgid "ClusterIntegration|Environment scope" msgstr "" -msgid "ClusterIntegration|GKE pricing" +msgid "ClusterIntegration|GitLab Integration" msgstr "" msgid "ClusterIntegration|GitLab Runner" @@ -560,18 +612,12 @@ msgstr "" msgid "ClusterIntegration|Helm Tiller" msgstr "" -msgid "ClusterIntegration|Inactive" -msgstr "" - msgid "ClusterIntegration|Ingress" msgstr "" msgid "ClusterIntegration|Install" msgstr "" -msgid "ClusterIntegration|Install applications on your cluster. Read more about %{helpLink}" -msgstr "" - msgid "ClusterIntegration|Installed" msgstr "" @@ -581,27 +627,27 @@ msgstr "" msgid "ClusterIntegration|Integrate cluster automation" msgstr "" +msgid "ClusterIntegration|Integration status" +msgstr "" + msgid "ClusterIntegration|Learn more about %{link_to_documentation}" msgstr "" msgid "ClusterIntegration|Learn more about Clusters" msgstr "" +msgid "ClusterIntegration|Learn more about environments" +msgstr "" + msgid "ClusterIntegration|Machine type" msgstr "" msgid "ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters" msgstr "" -msgid "ClusterIntegration|Manage cluster integration on your GitLab project" -msgstr "" - msgid "ClusterIntegration|Manage your cluster by visiting %{link_gke}" msgstr "" -msgid "ClusterIntegration|Multiple clusters are available in GitLab Entreprise Edition Premium and Ultimate" -msgstr "" - msgid "ClusterIntegration|Note:" msgstr "" @@ -614,12 +660,6 @@ msgstr "" msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:" msgstr "" -msgid "ClusterIntegration|Problem setting up the cluster" -msgstr "" - -msgid "ClusterIntegration|Problem setting up the clusters list" -msgstr "" - msgid "ClusterIntegration|Project ID" msgstr "" @@ -629,6 +669,9 @@ msgstr "" msgid "ClusterIntegration|Project namespace (optional, unique)" msgstr "" +msgid "ClusterIntegration|Prometheus" +msgstr "" + msgid "ClusterIntegration|Read our %{link_to_help_page} on cluster integration." msgstr "" @@ -638,7 +681,7 @@ msgstr "" msgid "ClusterIntegration|Remove integration" msgstr "" -msgid "ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine." +msgid "ClusterIntegration|Remove this cluster's configuration from this project. This will not delete your actual cluster." msgstr "" msgid "ClusterIntegration|Request to begin installing failed" @@ -674,9 +717,6 @@ msgstr "" msgid "ClusterIntegration|Something went wrong while installing %{title}" msgstr "" -msgid "ClusterIntegration|There are no clusters to show" -msgstr "" - msgid "ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below" msgstr "" @@ -698,6 +738,9 @@ msgstr "" msgid "ClusterIntegration|access to Google Kubernetes Engine" msgstr "" +msgid "ClusterIntegration|check the pricing here" +msgstr "" + msgid "ClusterIntegration|cluster" msgstr "" @@ -745,15 +788,42 @@ msgstr "" msgid "Commits feed" msgstr "" +msgid "Commits|An error occurred while fetching merge requests data." +msgstr "" + msgid "Commits|History" msgstr "" +msgid "Commits|No related merge requests found" +msgstr "" + msgid "Committed by" msgstr "" msgid "Compare" msgstr "" +msgid "Compare Git revisions" +msgstr "" + +msgid "Compare Revisions" +msgstr "" + +msgid "CompareBranches|%{source_branch} and %{target_branch} are the same." +msgstr "" + +msgid "CompareBranches|Compare" +msgstr "" + +msgid "CompareBranches|Source" +msgstr "" + +msgid "CompareBranches|Target" +msgstr "" + +msgid "CompareBranches|There isn't anything to compare." +msgstr "" + msgid "Container Registry" msgstr "" @@ -805,6 +875,9 @@ msgstr "" msgid "Contributors" msgstr "" +msgid "ContributorsPage|%{startDate} – %{endDate}" +msgstr "" + msgid "ContributorsPage|Building repository graph." msgstr "" @@ -874,9 +947,6 @@ msgstr "" msgid "Cycle Analytics" msgstr "" -msgid "Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project." -msgstr "" - msgid "CycleAnalyticsStage|Code" msgstr "" @@ -930,6 +1000,9 @@ msgstr "" msgid "Details" msgstr "" +msgid "Diffs|No file name available" +msgstr "" + msgid "Directory name" msgstr "" @@ -996,9 +1069,6 @@ msgstr "" msgid "Environments|Environments" msgstr "" -msgid "Environments|Environments are places where code gets deployed, such as staging or production." -msgstr "" - msgid "Environments|Job" msgstr "" @@ -1029,6 +1099,9 @@ msgstr "" msgid "Environments|You don't have any environments right now." msgstr "" +msgid "Error fetching refs" +msgstr "" + msgid "Error occurred when toggling the notification subscription" msgstr "" @@ -1121,12 +1194,21 @@ msgstr "" msgid "GPG Keys" msgstr "" +msgid "Generate a default set of labels" +msgstr "" + msgid "Git storage health information has been reset" msgstr "" +msgid "Git version" +msgstr "" + msgid "GitLab Runner section" msgstr "" +msgid "Gitaly Servers" +msgstr "" + msgid "Go to your fork" msgstr "" @@ -1172,10 +1254,7 @@ msgstr "" msgid "GroupsEmptyState|You can manage your group member’s permissions and access to each project in the group." msgstr "" -msgid "GroupsTreeRole|as" -msgstr "" - -msgid "GroupsTree|Are you sure you want to leave the \"${this.group.fullName}\" group?" +msgid "GroupsTree|Are you sure you want to leave the \"${group.fullName}\" group?" msgstr "" msgid "GroupsTree|Create a project in this group." @@ -1223,6 +1302,11 @@ msgstr "" msgid "HealthCheck|Unhealthy" msgstr "" +msgid "Hide value" +msgid_plural "Hide values" +msgstr[0] "" +msgstr[1] "" + msgid "History" msgstr "" @@ -1235,6 +1319,9 @@ msgstr "" msgid "Install a Runner compatible with GitLab CI" msgstr "" +msgid "Interested parties can even contribute by pushing commits if they want to." +msgstr "" + msgid "Internal - The group and any internal projects can be viewed by any logged in user." msgstr "" @@ -1256,6 +1343,9 @@ msgstr "" msgid "Issues" msgstr "" +msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." +msgstr "" + msgid "Jan" msgstr "" @@ -1283,6 +1373,9 @@ msgstr "" msgid "Labels" msgstr "" +msgid "Labels can be applied to issues and merge requests to categorize them." +msgstr "" + msgid "Last %d day" msgid_plural "Last %d days" msgstr[0] "" @@ -1327,10 +1420,8 @@ msgstr "" msgid "Leave project" msgstr "" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "" -msgstr[1] "" +msgid "Loading the GitLab IDE..." +msgstr "" msgid "Lock" msgstr "" @@ -1368,9 +1459,27 @@ msgstr "" msgid "Merge request" msgstr "" +msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" +msgstr "" + +msgid "Merged" +msgstr "" + msgid "Messages" msgstr "" +msgid "Milestones|Delete milestone" +msgstr "" + +msgid "Milestones|Delete milestone %{milestoneTitle}?" +msgstr "" + +msgid "Milestones|Failed to delete milestone %{milestoneTitle}" +msgstr "" + +msgid "Milestones|Milestone %{milestoneTitle} was not found" +msgstr "" + msgid "MissingSSHKeyWarningLink|add an SSH key" msgstr "" @@ -1409,6 +1518,9 @@ msgstr "" msgid "New issue" msgstr "" +msgid "New label" +msgstr "" + msgid "New merge request" msgstr "" @@ -1427,7 +1539,10 @@ msgstr "" msgid "New tag" msgstr "" -msgid "No container images stored for this project. Add one by following the instructions above." +msgid "No connection could be made to a Gitaly Server, please check your logs!" +msgstr "" + +msgid "No file chosen" msgstr "" msgid "No repository" @@ -1505,6 +1620,12 @@ msgstr "" msgid "Notifications" msgstr "" +msgid "Notifications off" +msgstr "" + +msgid "Notifications on" +msgstr "" + msgid "Nov" msgstr "" @@ -1514,9 +1635,6 @@ msgstr "" msgid "Number of access attempts" msgstr "" -msgid "Number of failures before backing off" -msgstr "" - msgid "Oct" msgstr "" @@ -1559,9 +1677,6 @@ msgstr "" msgid "Password" msgstr "" -msgid "People without permission will never get a notification and won\\'t be able to comment." -msgstr "" - msgid "Pipeline" msgstr "" @@ -1646,6 +1761,12 @@ msgstr "" msgid "Pipelines for last year" msgstr "" +msgid "Pipelines|Build with confidence" +msgstr "" + +msgid "Pipelines|Get started with Pipelines" +msgstr "" + msgid "Pipeline|all" msgstr "" @@ -1658,6 +1779,15 @@ msgstr "" msgid "Pipeline|with stages" msgstr "" +msgid "Play" +msgstr "" + +msgid "Please enable billing for one of your projects to be able to create a cluster, then try again." +msgstr "" + +msgid "Please solve the reCAPTCHA" +msgstr "" + msgid "Preferences" msgstr "" @@ -1721,6 +1851,15 @@ msgstr "" msgid "Project access must be granted explicitly to each user." msgstr "" +msgid "Project avatar" +msgstr "" + +msgid "Project avatar in repository: %{link}" +msgstr "" + +msgid "Project cache successfully reset." +msgstr "" + msgid "Project details" msgstr "" @@ -1760,12 +1899,6 @@ msgstr "" msgid "ProjectNetworkGraph|Graph" msgstr "" -msgid "ProjectSettings|Immediately run a pipeline on the default branch" -msgstr "" - -msgid "ProjectSettings|Problem setting up the CI/CD settings JavaScript" -msgstr "" - msgid "Projects" msgstr "" @@ -1844,6 +1977,9 @@ msgstr "" msgid "RefSwitcher|Tags" msgstr "" +msgid "Register / Sign In" +msgstr "" + msgid "Related Commits" msgstr "" @@ -1865,6 +2001,9 @@ msgstr "" msgid "Remind later" msgstr "" +msgid "Remove avatar" +msgstr "" + msgid "Remove project" msgstr "" @@ -1883,6 +2022,11 @@ msgstr "" msgid "Reset runners registration token" msgstr "" +msgid "Reveal value" +msgid_plural "Reveal values" +msgstr[0] "" +msgstr[1] "" + msgid "Revert this commit" msgstr "" @@ -1892,9 +2036,6 @@ msgstr "" msgid "SSH Keys" msgstr "" -msgid "Save" -msgstr "" - msgid "Save changes" msgstr "" @@ -1916,9 +2057,6 @@ msgstr "" msgid "Seconds before reseting failure information" msgstr "" -msgid "Seconds to wait after a storage failure" -msgstr "" - msgid "Seconds to wait for a storage access attempt" msgstr "" @@ -1928,6 +2066,9 @@ msgstr "" msgid "Select a timezone" msgstr "" +msgid "Select branch/tag" +msgstr "" + msgid "Select target branch" msgstr "" @@ -1937,6 +2078,9 @@ msgstr "" msgid "September" msgstr "" +msgid "Server version" +msgstr "" + msgid "Service Templates" msgstr "" @@ -1975,7 +2119,7 @@ msgstr "" msgid "Something went wrong on our end." msgstr "" -msgid "Something went wrong trying to change the locked state of this ${this.issuableDisplayName}" +msgid "Something went wrong when toggling the button" msgstr "" msgid "Something went wrong while fetching the projects." @@ -1984,6 +2128,9 @@ msgstr "" msgid "Something went wrong while fetching the registry list." msgstr "" +msgid "Something went wrong. Please try again." +msgstr "" + msgid "Sort by" msgstr "" @@ -2104,10 +2251,10 @@ msgstr "" msgid "Stopped" msgstr "" -msgid "Subgroups" +msgid "Storage" msgstr "" -msgid "Subscribe" +msgid "Subgroups" msgstr "" msgid "Switch branch/tag" @@ -2199,7 +2346,10 @@ msgstr "" msgid "Team" msgstr "" -msgid "The circuitbreaker backoff threshold should be lower than the failure count threshold" +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project" +msgstr "" + +msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project. You can register or sign in to create issues for this project." msgstr "" msgid "The coding stage shows the time from the first commit to creating the merge request. The data will automatically be added here once you create your first merge request." @@ -2214,10 +2364,10 @@ msgstr "" msgid "The issue stage shows the time it takes from creating an issue to assigning the issue to a milestone, or add the issue to a list on your Issue Board. Begin creating issues to see data for this stage." msgstr "" -msgid "The number of attempts GitLab will make to access a storage." +msgid "The maximum file size allowed is 200KB." msgstr "" -msgid "The number of failures after which GitLab will start temporarily disabling access to a storage shard on a host" +msgid "The number of attempts GitLab will make to access a storage." msgstr "" msgid "The number of failures of after which GitLab will completely prevent access to the storage. The number of failures can be reset in the admin interface: %{link_to_health_page} or using the %{api_documentation_link}." @@ -2226,9 +2376,6 @@ msgstr "" msgid "The phase of the development lifecycle." msgstr "" -msgid "The pipelines schedule runs pipelines in the future, repeatedly, for specific branches or tags. Those scheduled pipelines will inherit limited project access based on their associated user." -msgstr "" - msgid "The planning stage shows the time from the previous step to pushing your first commit. This time will be added automatically once you push your first commit." msgstr "" @@ -2259,16 +2406,25 @@ msgstr "" msgid "The time in seconds GitLab will try to access storage. After this time a timeout error will be raised." msgstr "" +msgid "The time in seconds between storage checks. When a previous check did complete yet, GitLab will skip a check." +msgstr "" + msgid "The time taken by each data entry gathered by that stage." msgstr "" msgid "The value lying at the midpoint of a series of observed values. E.g., between 3, 5, 9, the median is 5. Between 3, 5, 7, 8, the median is (5+7)/2 = 6." msgstr "" +msgid "There are no issues to show" +msgstr "" + +msgid "There are no merge requests to show" +msgstr "" + msgid "There are problems accessing Git storage: " msgstr "" -msgid "This branch has changed since you started editing. Would you like to create a new branch?" +msgid "This directory" msgstr "" msgid "This is a confidential issue." @@ -2283,12 +2439,36 @@ msgstr "" msgid "This issue is locked." msgstr "" +msgid "This job depends on a user to trigger its process. Often they are used to deploy code to production environments" +msgstr "" + +msgid "This job depends on upstream jobs that need to succeed in order for this job to be triggered" +msgstr "" + +msgid "This job has not been triggered yet" +msgstr "" + +msgid "This job has not started yet" +msgstr "" + +msgid "This job is in pending state and is waiting to be picked by a runner" +msgstr "" + +msgid "This job requires a manual action" +msgstr "" + msgid "This means you can not push code until you create an empty repository or import existing one." msgstr "" msgid "This merge request is locked." msgstr "" +msgid "This project" +msgstr "" + +msgid "This repository" +msgstr "" + msgid "Time before an issue gets scheduled" msgstr "" @@ -2437,6 +2617,12 @@ msgstr[1] "" msgid "Time|s" msgstr "" +msgid "ToggleButton|Toggle Status: OFF" +msgstr "" + +msgid "ToggleButton|Toggle Status: ON" +msgstr "" + msgid "Total Time" msgstr "" @@ -2446,6 +2632,12 @@ msgstr "" msgid "Total test time for all commits/merges" msgstr "" +msgid "Trigger this manual action" +msgstr "" + +msgid "Unable to reset project cache." +msgstr "" + msgid "Unlock" msgstr "" @@ -2455,7 +2647,7 @@ msgstr "" msgid "Unstar" msgstr "" -msgid "Unsubscribe" +msgid "Up to date" msgstr "" msgid "Upload New File" @@ -2464,6 +2656,9 @@ msgstr "" msgid "Upload file" msgstr "" +msgid "Upload new avatar" +msgstr "" + msgid "UploadLink|click to upload" msgstr "" @@ -2497,10 +2692,13 @@ msgstr "" msgid "Want to see the data? Please ask an administrator for access." msgstr "" +msgid "We could not verify that one of your projects on GCP has billing enabled. Please try again." +msgstr "" + msgid "We don't have enough data to show this stage." msgstr "" -msgid "When access to a storage fails. GitLab will prevent access to the storage for the time specified here. This allows the filesystem to recover. Repositories on failing shards are temporarly unavailable" +msgid "We want to be sure it is you, please confirm you are not a robot." msgstr "" msgid "Wiki" @@ -2623,9 +2821,15 @@ msgstr "" msgid "You are on a read-only GitLab instance." msgstr "" +msgid "You can also star a label to make it a priority label." +msgstr "" + msgid "You can only add files when you are on a branch" msgstr "" +msgid "You can only edit files when you are on a branch" +msgstr "" + msgid "You cannot write to this read-only GitLab instance." msgstr "" @@ -2662,6 +2866,9 @@ msgstr "" msgid "You won't be able to pull or push project code via SSH until you add an SSH key to your profile" msgstr "" +msgid "You'll need to use different branch names to get a valid comparison." +msgstr "" + msgid "Your comment will not be visible to the public." msgstr "" @@ -2682,12 +2889,113 @@ msgid_plural "days" msgstr[0] "" msgstr[1] "" +msgid "merge request" +msgid_plural "merge requests" +msgstr[0] "" +msgstr[1] "" + +msgid "mrWidget|Cancel automatic merge" +msgstr "" + +msgid "mrWidget|Checking ability to merge automatically" +msgstr "" + +msgid "mrWidget|Cherry-pick" +msgstr "" + +msgid "mrWidget|Cherry-pick this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Closed by" +msgstr "" + +msgid "mrWidget|Merge" +msgstr "" + +msgid "mrWidget|Merge failed." +msgstr "" + +msgid "mrWidget|Merge locally" +msgstr "" + +msgid "mrWidget|Merged by" +msgstr "" + +msgid "mrWidget|Refresh" +msgstr "" + +msgid "mrWidget|Refresh now" +msgstr "" + +msgid "mrWidget|Refreshing now" +msgstr "" + +msgid "mrWidget|Remove Source Branch" +msgstr "" + +msgid "mrWidget|Remove source branch" +msgstr "" + +msgid "mrWidget|Resolve conflicts" +msgstr "" + +msgid "mrWidget|Revert" +msgstr "" + +msgid "mrWidget|Revert this merge request in a new merge request" +msgstr "" + +msgid "mrWidget|Set by" +msgstr "" + +msgid "mrWidget|The changes were merged into" +msgstr "" + +msgid "mrWidget|The changes were not merged into" +msgstr "" + +msgid "mrWidget|The changes will be merged into" +msgstr "" + +msgid "mrWidget|The source branch has been removed" +msgstr "" + +msgid "mrWidget|The source branch is being removed" +msgstr "" + +msgid "mrWidget|The source branch will be removed" +msgstr "" + +msgid "mrWidget|The source branch will not be removed" +msgstr "" + +msgid "mrWidget|There are merge conflicts" +msgstr "" + +msgid "mrWidget|This merge request failed to be merged automatically" +msgstr "" + +msgid "mrWidget|This merge request is in the process of being merged" +msgstr "" + +msgid "mrWidget|This project is archived, write access has been disabled" +msgstr "" + +msgid "mrWidget|You can remove source branch now" +msgstr "" + +msgid "mrWidget|to be merged automatically when the pipeline succeeds" +msgstr "" + msgid "new merge request" msgstr "" msgid "notification emails" msgstr "" +msgid "or" +msgstr "" + msgid "parent" msgid_plural "parents" msgstr[0] "" diff --git a/spec/controllers/admin/gitaly_servers_controller_spec.rb b/spec/controllers/admin/gitaly_servers_controller_spec.rb new file mode 100644 index 00000000000..b7378aa37d0 --- /dev/null +++ b/spec/controllers/admin/gitaly_servers_controller_spec.rb @@ -0,0 +1,15 @@ +require 'spec_helper' + +describe Admin::GitalyServersController do + describe '#index' do + before do + sign_in(create(:admin)) + end + + it 'shows the gitaly servers page' do + get :index + + expect(response).to have_gitlab_http_status(200) + end + end +end diff --git a/spec/lib/gitaly/server_spec.rb b/spec/lib/gitaly/server_spec.rb new file mode 100644 index 00000000000..ed5d56e91d4 --- /dev/null +++ b/spec/lib/gitaly/server_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe Gitaly::Server do + describe '.all' do + let(:storages) { Gitlab.config.repositories.storages } + + it 'includes all storages' do + expect(storages.count).to eq(described_class.all.count) + expect(storages.keys).to eq(described_class.all.map(&:storage)) + end + end + + subject { described_class.all.first } + + it { is_expected.to respond_to(:server_version) } + it { is_expected.to respond_to(:git_binary_version) } + it { is_expected.to respond_to(:up_to_date?) } + it { is_expected.to respond_to(:address) } + + describe 'request memoization' do + context 'when requesting multiple properties', :request_store do + it 'uses memoization for the info request' do + expect do + subject.server_version + subject.up_to_date? + end.to change { Gitlab::GitalyClient.get_request_count }.by(1) + end + end + end +end From 787ac467a2d59b5798fe9da088da43cf09bfaf86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 15 Jan 2018 11:14:55 +0100 Subject: [PATCH 12/38] Add a QA scenario for creating an issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- qa/qa/factory/resource/issue.rb | 2 -- .../features/project/create_issue_spec.rb | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 qa/qa/specs/features/project/create_issue_spec.rb diff --git a/qa/qa/factory/resource/issue.rb b/qa/qa/factory/resource/issue.rb index 06e7e8df56c..95f48e20b3e 100644 --- a/qa/qa/factory/resource/issue.rb +++ b/qa/qa/factory/resource/issue.rb @@ -1,5 +1,3 @@ -require 'securerandom' - module QA module Factory module Resource diff --git a/qa/qa/specs/features/project/create_issue_spec.rb b/qa/qa/specs/features/project/create_issue_spec.rb new file mode 100644 index 00000000000..b73f108c2d9 --- /dev/null +++ b/qa/qa/specs/features/project/create_issue_spec.rb @@ -0,0 +1,18 @@ +module QA + feature 'creates issue', :core do + let(:issue_title) { 'issue title' } + + scenario 'user creates issue' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } + + Factory::Resource::Issue.fabricate! do |issue| + issue.title = issue_title + end + + Page::Menu::Side.act { click_issues } + + expect(page).to have_content(issue_title) + end + end +end From 2f4d088fb4f3d17622655951dcc16255646d0f7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 30 Jan 2018 18:18:48 +0100 Subject: [PATCH 13/38] Introduce a new QA::Gitlab::Page::Component::Dropzone class MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- qa/qa.rb | 7 +++++++ qa/qa/page/base.rb | 15 --------------- qa/qa/page/component/dropzone.rb | 29 +++++++++++++++++++++++++++++ qa/qa/page/project/issue/show.rb | 7 +++++-- 4 files changed, 41 insertions(+), 17 deletions(-) create mode 100644 qa/qa/page/component/dropzone.rb diff --git a/qa/qa.rb b/qa/qa.rb index bd24f241747..8630e2a522c 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -150,6 +150,13 @@ module QA autoload :Main, 'qa/page/mattermost/main' autoload :Login, 'qa/page/mattermost/login' end + + ## + # Classes describing components that are used by several pages. + # + module Component + autoload :Dropzone, 'qa/page/component/dropzone' + end end ## diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 7a2d9731205..5c3af4b9115 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -97,21 +97,6 @@ module QA views.map(&:errors).flatten end - # Not tested and not expected to work with multiple dropzones - # instantiated on one page because there is no distinguishing - # attribute per dropzone file field. - def attach_file_to_dropzone(attachment, dropzone_form_container) - filename = File.basename(attachment) - - field_style = { visibility: 'visible', height: '', width: '' } - attach_file(attachment, class: 'dz-hidden-input', make_visible: field_style) - - # Wait for link to be appended to dropzone text - wait(reload: false) do - find("#{dropzone_form_container} textarea").value.match(filename) - end - end - class DSL attr_reader :views diff --git a/qa/qa/page/component/dropzone.rb b/qa/qa/page/component/dropzone.rb new file mode 100644 index 00000000000..5e6fdff20eb --- /dev/null +++ b/qa/qa/page/component/dropzone.rb @@ -0,0 +1,29 @@ +module QA + module Page + module Component + class Dropzone + attr_reader :page, :container + + def initialize(page, container) + @page = page + @container = container + end + + # Not tested and not expected to work with multiple dropzones + # instantiated on one page because there is no distinguishing + # attribute per dropzone file field. + def attach_file(attachment) + filename = File.basename(attachment) + + field_style = { visibility: 'visible', height: '', width: '' } + page.attach_file(attachment, class: 'dz-hidden-input', make_visible: field_style) + + # Wait for link to be appended to dropzone text + page.wait(reload: false) do + page.find("#{container} textarea").value.match(filename) + end + end + end + end + end +end diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb index 10644c0fecc..364a2c61665 100644 --- a/qa/qa/page/project/issue/show.rb +++ b/qa/qa/page/project/issue/show.rb @@ -23,10 +23,13 @@ module QA # Adds a comment to an issue # attachment option should be an absolute path - def comment(text, attachment:) + def comment(text, attachment: nil) fill_in(with: text, name: 'note[note]') - attach_file_to_dropzone(attachment, '.new-note') if attachment + unless attachment.nil? + QA::Page::Component::Dropzone.new(page, '.new-note') + .attach_file(attachment) + end click_on 'Comment' end From afe665d9c0e57adb32c6e4f972d41aa5b3174dee Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Tue, 30 Jan 2018 15:19:58 +0100 Subject: [PATCH 14/38] Remove Rugged::Walk implementation of #log --- lib/gitlab/git/repository.rb | 25 +------------- lib/gitlab/gitaly_client/commit_service.rb | 2 +- spec/lib/gitlab/git/repository_spec.rb | 38 ---------------------- 3 files changed, 2 insertions(+), 63 deletions(-) diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 7127f7858ee..e8011c8d033 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -462,7 +462,6 @@ module Gitlab path: nil, follow: false, skip_merges: false, - disable_walk: false, after: nil, before: nil } @@ -494,11 +493,7 @@ module Gitlab return [] end - if log_using_shell?(options) - log_by_shell(sha, options) - else - log_by_walk(sha, options) - end + log_by_shell(sha, options) end def count_commits(options) @@ -1635,24 +1630,6 @@ module Gitlab end end - def log_using_shell?(options) - options[:path].present? || - options[:disable_walk] || - options[:skip_merges] || - options[:after] || - options[:before] - end - - def log_by_walk(sha, options) - walk_options = { - show: sha, - sort: Rugged::SORT_NONE, - limit: options[:limit], - offset: options[:offset] - } - Rugged::Walker.walk(rugged, walk_options).to_a - end - # Gitaly note: JV: although #log_by_shell shells out to Git I think the # complexity is such that we should migrate it as Ruby before trying to # do it in Go. diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb index cadc7149301..5767f06b0ce 100644 --- a/lib/gitlab/gitaly_client/commit_service.rb +++ b/lib/gitlab/gitaly_client/commit_service.rb @@ -257,7 +257,7 @@ module Gitlab offset: options[:offset], follow: options[:follow], skip_merges: options[:skip_merges], - disable_walk: options[:disable_walk] + disable_walk: true # This option is deprecated. The 'walk' implementation is being removed. ) request.after = GitalyClient.timestamp(options[:after]) if options[:after] request.before = GitalyClient.timestamp(options[:before]) if options[:before] diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index bf01e6ef8e8..e8e4a832115 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -905,44 +905,6 @@ describe Gitlab::Git::Repository, seed_helper: true do end end - context "compare results between log_by_walk and log_by_shell" do - let(:options) { { ref: "master" } } - let(:commits_by_walk) { repository.log(options).map(&:id) } - let(:commits_by_shell) { repository.log(options.merge({ disable_walk: true })).map(&:id) } - - it { expect(commits_by_walk).to eq(commits_by_shell) } - - context "with limit" do - let(:options) { { ref: "master", limit: 1 } } - - it { expect(commits_by_walk).to eq(commits_by_shell) } - end - - context "with offset" do - let(:options) { { ref: "master", offset: 1 } } - - it { expect(commits_by_walk).to eq(commits_by_shell) } - end - - context "with skip_merges" do - let(:options) { { ref: "master", skip_merges: true } } - - it { expect(commits_by_walk).to eq(commits_by_shell) } - end - - context "with path" do - let(:options) { { ref: "master", path: "encoding" } } - - it { expect(commits_by_walk).to eq(commits_by_shell) } - - context "with follow" do - let(:options) { { ref: "master", path: "encoding", follow: true } } - - it { expect(commits_by_walk).to eq(commits_by_shell) } - end - end - end - context "where provides 'after' timestamp" do options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') } From 514826f50b618e881f418c1756dfbf5d895713e7 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 31 Jan 2018 12:23:48 +0000 Subject: [PATCH 15/38] Fix broken test --- .../components/mr_widget_related_links.vue | 3 ++- .../javascripts/vue_merge_request_widget/mr_widget_options.js | 3 +-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue index 080ec1c579c..88d0fcd70f5 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_related_links.vue @@ -7,6 +7,7 @@ relatedLinks: { type: Object, required: true, + default: () => ({}), }, state: { type: String, @@ -33,7 +34,7 @@ {{ closesText }}

- Mentions + {{ s__("mrWidget|Mentions") }}

diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js index 6d3573de76c..edf67fcd0a7 100644 --- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js +++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.js @@ -63,8 +63,7 @@ export default { return this.mr.hasCI; }, shouldRenderRelatedLinks() { - const { closing, mentioned, assignToMe } = this.mr.relatedLinks; - return (closing || mentioned || assignToMe) && !this.mr.isNothingToMergeState; + return !!this.mr.relatedLinks && !this.mr.isNothingToMergeState; }, shouldRenderDeployments() { return this.mr.deployments.length; From 406e10b89c41db709217367d8093d80ad00b02df Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 31 Jan 2018 16:23:15 +0100 Subject: [PATCH 16/38] refactor groups controller to match EE --- app/controllers/groups_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 75270a0889b..bb652832cb1 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -118,10 +118,10 @@ class GroupsController < Groups::ApplicationController end def group_params - params.require(:group).permit(group_params_ce) + params.require(:group).permit(group_params_attributes) end - def group_params_ce + def group_params_attributes [ :avatar, :description, From 2f1a55fa4ef7bbb19ba1bc4de9543878682253d7 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 31 Jan 2018 15:24:54 +0000 Subject: [PATCH 17/38] Use axios instead of jquery ajax for setCiStatusFavicon --- .../javascripts/lib/utils/common_utils.js | 17 +++----- spec/javascripts/job_spec.js | 21 +++------- .../lib/utils/common_utils_spec.js | 39 ++++++++++++------- 3 files changed, 38 insertions(+), 39 deletions(-) diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js index 03918762842..8018ec411c1 100644 --- a/app/assets/javascripts/lib/utils/common_utils.js +++ b/app/assets/javascripts/lib/utils/common_utils.js @@ -1,4 +1,5 @@ import { getLocationHash } from './url_utility'; +import axios from './axios_utils'; export const getPagePath = (index = 0) => $('body').attr('data-page').split(':')[index]; @@ -382,22 +383,16 @@ export const resetFavicon = () => { } }; -export const setCiStatusFavicon = (pageUrl) => { - $.ajax({ - url: pageUrl, - dataType: 'json', - success: (data) => { +export const setCiStatusFavicon = pageUrl => + axios.get(pageUrl) + .then(({ data }) => { if (data && data.favicon) { setFavicon(data.favicon); } else { resetFavicon(); } - }, - error: () => { - resetFavicon(); - }, - }); -}; + }) + .catch(resetFavicon); export const spriteIcon = (icon, className = '') => { const classAttribute = className.length > 0 ? `class="${className}"` : ''; diff --git a/spec/javascripts/job_spec.js b/spec/javascripts/job_spec.js index feb341d22e6..0452934ea9e 100644 --- a/spec/javascripts/job_spec.js +++ b/spec/javascripts/job_spec.js @@ -58,8 +58,7 @@ describe('Job', () => { it('updates the build trace on an interval', function () { const deferred1 = $.Deferred(); const deferred2 = $.Deferred(); - const deferred3 = $.Deferred(); - spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise()); + spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise()); spyOn(urlUtils, 'visitUrl'); deferred1.resolve({ @@ -70,9 +69,7 @@ describe('Job', () => { complete: false, }); - deferred2.resolve(); - - deferred3.resolve({ + deferred2.resolve({ html: 'More', status: 'running', state: 'finalstate', @@ -94,9 +91,8 @@ describe('Job', () => { it('replaces the entire build trace', () => { const deferred1 = $.Deferred(); const deferred2 = $.Deferred(); - const deferred3 = $.Deferred(); - spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise()); + spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise()); spyOn(urlUtils, 'visitUrl'); @@ -107,9 +103,7 @@ describe('Job', () => { complete: false, }); - deferred2.resolve(); - - deferred3.resolve({ + deferred2.resolve({ html: 'Different', status: 'running', append: false, @@ -170,9 +164,8 @@ describe('Job', () => { it('shows incremented size', () => { const deferred1 = $.Deferred(); const deferred2 = $.Deferred(); - const deferred3 = $.Deferred(); - spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise(), deferred3.promise()); + spyOn($, 'ajax').and.returnValues(deferred1.promise(), deferred2.promise()); spyOn(urlUtils, 'visitUrl'); @@ -184,8 +177,6 @@ describe('Job', () => { total: 100, }); - deferred2.resolve(); - this.job = new Job(); expect( @@ -194,7 +185,7 @@ describe('Job', () => { jasmine.clock().tick(4001); - deferred3.resolve({ + deferred2.resolve({ html: 'Update', status: 'success', append: true, diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js index 0a9d815f469..1052b4e7c20 100644 --- a/spec/javascripts/lib/utils/common_utils_spec.js +++ b/spec/javascripts/lib/utils/common_utils_spec.js @@ -1,6 +1,8 @@ /* eslint-disable promise/catch-or-return */ import * as commonUtils from '~/lib/utils/common_utils'; +import axios from '~/lib/utils/axios_utils'; +import MockAdapter from 'axios-mock-adapter'; describe('common_utils', () => { describe('parseUrl', () => { @@ -413,37 +415,48 @@ describe('common_utils', () => { describe('setCiStatusFavicon', () => { const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/-/jobs/1/status.json`; + let mock; beforeEach(() => { const favicon = document.createElement('link'); favicon.setAttribute('id', 'favicon'); document.body.appendChild(favicon); + mock = new MockAdapter(axios); }); afterEach(() => { + mock.restore(); document.body.removeChild(document.getElementById('favicon')); }); - it('should reset favicon in case of error', () => { - const favicon = document.getElementById('favicon'); - spyOn($, 'ajax').and.callFake(function (options) { - options.error(); - expect(favicon.getAttribute('href')).toEqual('null'); - }); + it('should reset favicon in case of error', (done) => { + mock.onGet(BUILD_URL).networkError(); - commonUtils.setCiStatusFavicon(BUILD_URL); + commonUtils.setCiStatusFavicon(BUILD_URL) + .then(() => { + const favicon = document.getElementById('favicon'); + expect(favicon.getAttribute('href')).toEqual('null'); + done(); + }) + // Error is already caught in catch() block of setCiStatusFavicon, + // It won't throw another error for us to catch + .catch(done.fail); }); - it('should set page favicon to CI status favicon based on provided status', () => { + it('should set page favicon to CI status favicon based on provided status', (done) => { const FAVICON_PATH = '//icon_status_success'; - const favicon = document.getElementById('favicon'); - spyOn($, 'ajax').and.callFake(function (options) { - options.success({ favicon: FAVICON_PATH }); - expect(favicon.getAttribute('href')).toEqual(FAVICON_PATH); + mock.onGet(BUILD_URL).reply(200, { + favicon: FAVICON_PATH, }); - commonUtils.setCiStatusFavicon(BUILD_URL); + commonUtils.setCiStatusFavicon(BUILD_URL) + .then(() => { + const favicon = document.getElementById('favicon'); + expect(favicon.getAttribute('href')).toEqual(FAVICON_PATH); + done(); + }) + .catch(done.fail); }); }); From c01f81fa084284719dc305486980b5e6c58d429f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Wed, 31 Jan 2018 17:02:50 +0000 Subject: [PATCH 18/38] [ci skip] Fix example commands to refer to the correct versions --- changelogs/unreleased/fix-install-docs.yml | 5 +++++ doc/install/installation.md | 8 ++++---- lib/system_check/app/git_version_check.rb | 2 +- 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/fix-install-docs.yml diff --git a/changelogs/unreleased/fix-install-docs.yml b/changelogs/unreleased/fix-install-docs.yml new file mode 100644 index 00000000000..c2c0dd1364b --- /dev/null +++ b/changelogs/unreleased/fix-install-docs.yml @@ -0,0 +1,5 @@ +--- +title: Update minimum git version to 2.9.5 +merge_request: 16683 +author: +type: other diff --git a/doc/install/installation.md b/doc/install/installation.md index 18e29271d0f..6eb8890cc4f 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -80,7 +80,7 @@ Make sure you have the right version of Git installed # Install Git sudo apt-get install -y git-core - # Make sure Git is version 2.14.3 or higher + # Make sure Git is version 2.9.5 or higher git --version Is the system packaged Git too old? Remove it and compile from source. @@ -93,9 +93,9 @@ Is the system packaged Git too old? Remove it and compile from source. # Download and compile from source cd /tmp - curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.8.4.tar.gz - echo '626e319f8a24fc0866167ea5f6bf3e2f38f69d6cb2e59e150f13709ca3ebf301 git-2.8.4.tar.gz' | shasum -a256 -c - && tar -xzf git-2.8.4.tar.gz - cd git-2.8.4/ + curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.14.3.tar.gz + echo '023ffff6d3ba8a1bea779dfecc0ed0bb4ad68ab8601d14435dd8c08416f78d7f git-2.14.3.tar.gz' | shasum -a256 -c - && tar -xzf git-2.14.3.tar.gz + cd git-2.14.3/ ./configure make prefix=/usr/local all diff --git a/lib/system_check/app/git_version_check.rb b/lib/system_check/app/git_version_check.rb index 6ee8c8874ec..44ec888c197 100644 --- a/lib/system_check/app/git_version_check.rb +++ b/lib/system_check/app/git_version_check.rb @@ -5,7 +5,7 @@ module SystemCheck set_check_pass -> { "yes (#{self.current_version})" } def self.required_version - @required_version ||= Gitlab::VersionInfo.new(2, 7, 3) + @required_version ||= Gitlab::VersionInfo.new(2, 9, 5) end def self.current_version From 2b6307f6ad9d09156c42befe4babbfea40dad052 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Sat, 27 Jan 2018 14:35:53 +0900 Subject: [PATCH 19/38] Enable RuboCop Style/RegexpLiteral --- .rubocop_todo.yml | 4 +++- app/controllers/help_controller.rb | 2 +- app/controllers/projects_controller.rb | 2 +- app/helpers/sidekiq_helper.rb | 6 ++--- app/helpers/submodule_helper.rb | 10 ++++----- app/helpers/tree_helper.rb | 2 +- app/models/ci/build.rb | 2 +- app/models/commit_status.rb | 2 +- app/models/concerns/taskable.rb | 4 ++-- app/models/environment.rb | 2 +- app/models/project.rb | 4 ++-- app/models/project_services/asana_service.rb | 2 +- .../project_services/issue_tracker_service.rb | 4 ++-- app/models/project_services/jira_service.rb | 2 +- app/models/user.rb | 4 ++-- .../42497-rubocop-style-regexpliteral.yml | 5 +++++ config/initializers/1_settings.rb | 6 ++--- .../initializers/active_record_data_types.rb | 2 +- config/routes/admin.rb | 2 +- config/routes/group.rb | 4 ++-- config/routes/project.rb | 10 ++++----- config/routes/uploads.rb | 10 ++++----- .../steps/project/source/markdown_render.rb | 6 ++--- lib/api/templates.rb | 8 +++---- lib/api/v3/projects.rb | 2 +- lib/api/v3/templates.rb | 8 +++---- lib/banzai/filter/emoji_filter.rb | 4 ++-- lib/banzai/filter/gollum_tags_filter.rb | 4 ++-- lib/container_registry/registry.rb | 2 +- lib/extracts_path.rb | 4 ++-- .../populate_untracked_uploads.rb | 2 +- .../ci/build/artifacts/metadata/entry.rb | 2 +- .../dependency_linker/composer_json_linker.rb | 2 +- .../dependency_linker/gemfile_linker.rb | 2 +- .../dependency_linker/podspec_linker.rb | 2 +- lib/gitlab/email/reply_parser.rb | 2 +- lib/gitlab/file_detector.rb | 22 +++++++++---------- lib/gitlab/git.rb | 2 +- lib/gitlab/git/blob.rb | 4 ++-- lib/gitlab/git/path_helper.rb | 2 +- lib/gitlab/git/ref.rb | 2 +- lib/gitlab/git/repository.rb | 4 ++-- lib/gitlab/git/repository_mirroring.rb | 2 +- .../github_import/representation/diff_note.rb | 2 +- .../github_import/representation/note.rb | 2 +- lib/gitlab/import_export/file_importer.rb | 2 +- lib/gitlab/metrics/subscribers/action_view.rb | 2 +- lib/gitlab/middleware/go.rb | 4 ++-- lib/gitlab/middleware/static.rb | 2 +- .../spend_time_and_date_separator.rb | 2 +- lib/gitlab/repo_path.rb | 2 +- lib/gitlab/setup_helper.rb | 2 +- lib/gitlab/sherlock/file_sample.rb | 2 +- lib/gitlab/sherlock/middleware.rb | 2 +- lib/gitlab/sherlock/query.rb | 4 ++-- lib/gitlab/upgrader.rb | 2 +- qa/qa/page/menu/side.rb | 2 +- qa/spec/runtime/rsa_key.rb | 2 +- .../cop/migration/update_column_in_batches.rb | 2 +- .../projects/jobs_controller_spec.rb | 4 ++-- .../projects/todos_controller_spec.rb | 4 ++-- spec/features/atom/users_spec.rb | 4 ++-- .../user_resolves_conflicts_spec.rb | 10 ++++----- spec/helpers/groups_helper_spec.rb | 4 ++-- spec/helpers/labels_helper_spec.rb | 2 +- spec/helpers/version_check_helper_spec.rb | 2 +- .../commit_range_reference_filter_spec.rb | 4 ++-- .../filter/commit_reference_filter_spec.rb | 6 ++--- .../external_issue_reference_filter_spec.rb | 2 +- .../banzai/filter/image_link_filter_spec.rb | 4 ++-- .../filter/issue_reference_filter_spec.rb | 6 ++--- .../merge_request_reference_filter_spec.rb | 4 ++-- .../filter/snippet_reference_filter_spec.rb | 6 ++--- .../filter/user_reference_filter_spec.rb | 4 ++-- spec/lib/gitlab/git/repository_spec.rb | 2 +- spec/lib/gitlab/path_regex_spec.rb | 2 +- spec/mailers/notify_spec.rb | 2 +- .../project_services/jira_service_spec.rb | 4 ++-- ...rge_when_pipeline_succeeds_service_spec.rb | 2 +- spec/services/system_note_service_spec.rb | 4 ++-- spec/tasks/gitlab/backup_rake_spec.rb | 2 +- spec/tasks/gitlab/git_rake_spec.rb | 2 +- spec/uploaders/file_uploader_spec.rb | 2 +- spec/uploaders/job_artifact_uploader_spec.rb | 2 +- 84 files changed, 156 insertions(+), 149 deletions(-) create mode 100644 changelogs/unreleased/42497-rubocop-style-regexpliteral.yml diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 442d61bcf4f..7a12c8473f3 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -704,7 +704,9 @@ Style/RedundantSelf: # Configuration parameters: EnforcedStyle, AllowInnerSlashes. # SupportedStyles: slashes, percent_r, mixed Style/RegexpLiteral: - Enabled: false + Enabled: true + EnforcedStyle: mixed + AllowInnerSlashes: false # Offense count: 36 # Cop supports --auto-correct. diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index 38f379dbf4f..a394521698c 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -5,7 +5,7 @@ class HelpController < ApplicationController # Taken from Jekyll # https://github.com/jekyll/jekyll/blob/3.5-stable/lib/jekyll/document.rb#L13 - YAML_FRONT_MATTER_REGEXP = %r!\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)!m + YAML_FRONT_MATTER_REGEXP = /\A(---\s*\n.*?\n?)^((---|\.\.\.)\s*$\n?)/m def index # Remove YAML frontmatter so that it doesn't look weird diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index e6e2b219e6a..8158934322d 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -403,6 +403,6 @@ class ProjectsController < Projects::ApplicationController # to # localhost/group/project # - redirect_to request.original_url.sub(/\.git\/?\Z/, '') if params[:format] == 'git' + redirect_to request.original_url.sub(%r{\.git/?\Z}, '') if params[:format] == 'git' end end diff --git a/app/helpers/sidekiq_helper.rb b/app/helpers/sidekiq_helper.rb index 55f4da0ef85..50aeb7f4b82 100644 --- a/app/helpers/sidekiq_helper.rb +++ b/app/helpers/sidekiq_helper.rb @@ -1,12 +1,12 @@ module SidekiqHelper - SIDEKIQ_PS_REGEXP = /\A + SIDEKIQ_PS_REGEXP = %r{\A (?\d+)\s+ (?[\d\.,]+)\s+ (?[\d\.,]+)\s+ - (?[DIEKNRSTVWXZNLpsl\+<>\/\d]+)\s+ + (?[DIEKNRSTVWXZNLpsl\+<>/\d]+)\s+ (?.+?)\s+ (?(?:ruby\d+:\s+)?sidekiq.*\].*) - \z/x + \z}x def parse_sidekiq_ps(line) match = line.strip.match(SIDEKIQ_PS_REGEXP) diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb index 1db9ae3839c..9151543dfdc 100644 --- a/app/helpers/submodule_helper.rb +++ b/app/helpers/submodule_helper.rb @@ -11,7 +11,7 @@ module SubmoduleHelper url = File.join(Gitlab.config.gitlab.url, @project.full_path) end - if url =~ /([^\/:]+)\/([^\/]+(?:\.git)?)\Z/ + if url =~ %r{([^/:]+)/([^/]+(?:\.git)?)\Z} namespace, project = $1, $2 gitlab_hosts = [Gitlab.config.gitlab.url, Gitlab.config.gitlab_shell.ssh_path_prefix] @@ -23,7 +23,7 @@ module SubmoduleHelper end end - namespace.sub!(/\A\//, '') + namespace.sub!(%r{\A/}, '') project.rstrip! project.sub!(/\.git\z/, '') @@ -47,11 +47,11 @@ module SubmoduleHelper protected def github_dot_com_url?(url) - url =~ /github\.com[\/:][^\/]+\/[^\/]+\Z/ + url =~ %r{github\.com[/:][^/]+/[^/]+\Z} end def gitlab_dot_com_url?(url) - url =~ /gitlab\.com[\/:][^\/]+\/[^\/]+\Z/ + url =~ %r{gitlab\.com[/:][^/]+/[^/]+\Z} end def self_url?(url, namespace, project) @@ -65,7 +65,7 @@ module SubmoduleHelper def relative_self_url?(url) # (./)?(../repo.git) || (./)?(../../project/repo.git) ) - url =~ /\A((\.\/)?(\.\.\/))(?!(\.\.)|(.*\/)).*(\.git)?\z/ || url =~ /\A((\.\/)?(\.\.\/){2})(?!(\.\.))([^\/]*)\/(?!(\.\.)|(.*\/)).*(\.git)?\z/ + url =~ %r{\A((\./)?(\.\./))(?!(\.\.)|(.*/)).*(\.git)?\z} || url =~ %r{\A((\./)?(\.\./){2})(?!(\.\.))([^/]*)/(?!(\.\.)|(.*/)).*(\.git)?\z} end def standard_links(host, namespace, project, commit) diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index 5b2ea38a03d..d39cac0f510 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -110,7 +110,7 @@ module TreeHelper # returns the relative path of the first subdir that doesn't have only one directory descendant def flatten_tree(root_path, tree) - return tree.flat_path.sub(/\A#{root_path}\//, '') if tree.flat_path.present? + return tree.flat_path.sub(%r{\A#{root_path}/}, '') if tree.flat_path.present? subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path) if subtree.count == 1 && subtree.first.dir? diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 6ced5fb0e24..78906e7a968 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -292,7 +292,7 @@ module Ci def repo_url auth = "gitlab-ci-token:#{ensure_token!}@" - project.http_url_to_repo.sub(/^https?:\/\//) do |prefix| + project.http_url_to_repo.sub(%r{^https?://}) do |prefix| prefix + auth end end diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index c0263c0b4e2..3469d5d795c 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -141,7 +141,7 @@ class CommitStatus < ActiveRecord::Base end def group_name - name.to_s.gsub(/\d+[\s:\/\\]+\d+\s*/, '').strip + name.to_s.gsub(%r{\d+[\s:/\\]+\d+\s*}, '').strip end def failed_but_allowed? diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb index d07041c2fdf..ccd6f0e0a7d 100644 --- a/app/models/concerns/taskable.rb +++ b/app/models/concerns/taskable.rb @@ -9,13 +9,13 @@ require 'task_list/filter' module Taskable COMPLETED = 'completed'.freeze INCOMPLETE = 'incomplete'.freeze - ITEM_PATTERN = / + ITEM_PATTERN = %r{ ^ \s*(?:[-+*]|(?:\d+\.)) # list prefix required - task item has to be always in a list \s+ # whitespace prefix has to be always presented for a list item (\[\s\]|\[[xX]\]) # checkbox (\s.+) # followed by whitespace and some text. - /x + }x def self.get_tasks(content) content.to_s.scan(ITEM_PATTERN).map do |checkbox, label| diff --git a/app/models/environment.rb b/app/models/environment.rb index bf69b4c50f0..2f6eae605ee 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -115,7 +115,7 @@ class Environment < ActiveRecord::Base def formatted_external_url return nil unless external_url - external_url.gsub(/\A.*?:\/\//, '') + external_url.gsub(%r{\A.*?://}, '') end def stop_action? diff --git a/app/models/project.rb b/app/models/project.rb index d0d0fd6e093..4def590a7a9 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -234,7 +234,7 @@ class Project < ActiveRecord::Base validates :creator, presence: true, on: :create validates :description, length: { maximum: 2000 }, allow_blank: true validates :ci_config_path, - format: { without: /(\.{2}|\A\/)/, + format: { without: %r{(\.{2}|\A/)}, message: 'cannot include leading slash or directory traversal.' }, length: { maximum: 255 }, allow_blank: true @@ -1338,7 +1338,7 @@ class Project < ActiveRecord::Base host = "#{subdomain}.#{Settings.pages.host}".downcase # The host in URL always needs to be downcased - url = Gitlab.config.pages.url.sub(/^https?:\/\//) do |prefix| + url = Gitlab.config.pages.url.sub(%r{^https?://}) do |prefix| "#{prefix}#{subdomain}." end.downcase diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb index 9ce2d1153a7..109258d1eb7 100644 --- a/app/models/project_services/asana_service.rb +++ b/app/models/project_services/asana_service.rb @@ -84,7 +84,7 @@ http://app.asana.com/-/account_api' # - fix/ed/es/ing # - close/s/d # - closing - issue_finder = /(fix\w*|clos[ei]\w*+)?\W*(?:https:\/\/app\.asana\.com\/\d+\/\d+\/(\d+)|#(\d+))/i + issue_finder = %r{(fix\w*|clos[ei]\w*+)?\W*(?:https://app\.asana\.com/\d+/\d+/(\d+)|#(\d+))}i message.scan(issue_finder).each do |tuple| # tuple will be diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb index 31984c5d7ed..5fb15c383ca 100644 --- a/app/models/project_services/issue_tracker_service.rb +++ b/app/models/project_services/issue_tracker_service.rb @@ -10,9 +10,9 @@ class IssueTrackerService < Service # overriden patterns. See ReferenceRegexes::EXTERNAL_PATTERN def self.reference_pattern(only_long: false) if only_long - %r{(\b[A-Z][A-Z0-9_]+-)(?\d+)} + /(\b[A-Z][A-Z0-9_]+-)(?\d+)/ else - %r{(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?\d+)} + /(\b[A-Z][A-Z0-9_]+-|#{Issue.reference_prefix})(?\d+)/ end end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 23147d7f666..30eafe31454 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -19,7 +19,7 @@ class JiraService < IssueTrackerService # {PROJECT-KEY}-{NUMBER} Examples: JIRA-1, PROJECT-1 def self.reference_pattern(only_long: true) - @reference_pattern ||= %r{(?\b([A-Z][A-Z0-9_]+-)\d+)} + @reference_pattern ||= /(?\b([A-Z][A-Z0-9_]+-)\d+)/ end def initialize_properties diff --git a/app/models/user.rb b/app/models/user.rb index 9403da98268..fb5d56a68b0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -842,13 +842,13 @@ class User < ActiveRecord::Base end def full_website_url - return "http://#{website_url}" if website_url !~ /\Ahttps?:\/\// + return "http://#{website_url}" if website_url !~ %r{\Ahttps?://} website_url end def short_website_url - website_url.sub(/\Ahttps?:\/\//, '') + website_url.sub(%r{\Ahttps?://}, '') end def all_ssh_keys diff --git a/changelogs/unreleased/42497-rubocop-style-regexpliteral.yml b/changelogs/unreleased/42497-rubocop-style-regexpliteral.yml new file mode 100644 index 00000000000..6053883bac4 --- /dev/null +++ b/changelogs/unreleased/42497-rubocop-style-regexpliteral.yml @@ -0,0 +1,5 @@ +--- +title: Enable RuboCop Style/RegexpLiteral +merge_request: 16752 +author: Takuya Noguchi +type: other diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 899e612ffbd..5b4e6b5db88 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -110,7 +110,7 @@ class Settings < Settingslogic url = "http://#{url}" unless url.start_with?('http') # Get rid of the path so that we don't even have to encode it - url_without_path = url.sub(%r{(https?://[^\/]+)/?.*}, '\1') + url_without_path = url.sub(%r{(https?://[^/]+)/?.*}, '\1') URI.parse(url_without_path).host end @@ -469,10 +469,10 @@ end # repository_downloads_path value. # repositories_storages = Settings.repositories.storages.values -repository_downloads_path = Settings.gitlab['repository_downloads_path'].to_s.gsub(/\/$/, '') +repository_downloads_path = Settings.gitlab['repository_downloads_path'].to_s.gsub(%r{/$}, '') repository_downloads_full_path = File.expand_path(repository_downloads_path, Settings.gitlab['user_home']) -if repository_downloads_path.blank? || repositories_storages.any? { |rs| [repository_downloads_path, repository_downloads_full_path].include?(rs['path'].gsub(/\/$/, '')) } +if repository_downloads_path.blank? || repositories_storages.any? { |rs| [repository_downloads_path, repository_downloads_full_path].include?(rs['path'].gsub(%r{/$}, '')) } Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') end diff --git a/config/initializers/active_record_data_types.rb b/config/initializers/active_record_data_types.rb index 0359e14b232..fda13d0c4cb 100644 --- a/config/initializers/active_record_data_types.rb +++ b/config/initializers/active_record_data_types.rb @@ -54,7 +54,7 @@ elsif Gitlab::Database.mysql? def initialize_type_map(mapping) super mapping - mapping.register_type(%r(timestamp)i) do |sql_type| + mapping.register_type(/timestamp/i) do |sql_type| precision = extract_precision(sql_type) ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter::MysqlDateTimeWithTimeZone.new(precision: precision) end diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 57e401c9b89..3cca1210e39 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -1,5 +1,5 @@ namespace :admin do - resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do + resources :users, constraints: { id: %r{[a-zA-Z./0-9_\-]+} } do resources :keys, only: [:show, :destroy] resources :identities, except: [:show] resources :impersonation_tokens, only: [:index, :create] do diff --git a/config/routes/group.rb b/config/routes/group.rb index 976837a246d..24c76bc55ab 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -35,7 +35,7 @@ constraints(GroupUrlConstrainer.new) do post :toggle_subscription, on: :member end - resources :milestones, constraints: { id: /[^\/]+/ }, only: [:index, :show, :edit, :update, :new, :create] do + resources :milestones, constraints: { id: %r{[^/]+} }, only: [:index, :show, :edit, :update, :new, :create] do member do get :merge_requests get :participants @@ -52,7 +52,7 @@ constraints(GroupUrlConstrainer.new) do resources :uploads, only: [:create] do collection do - get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ } + get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} } end end end diff --git a/config/routes/project.rb b/config/routes/project.rb index 0496bd85b4e..bcaa68c8ce5 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -40,7 +40,7 @@ constraints(ProjectUrlConstrainer.new) do # # Templates # - get '/templates/:template_type/:key' => 'templates#show', as: :template, constraints: { key: /[^\/]+/ } + get '/templates/:template_type/:key' => 'templates#show', as: :template, constraints: { key: %r{[^/]+} } resource :avatar, only: [:show, :destroy] resources :commit, only: [:show], constraints: { id: /\h{7,40}/ } do @@ -55,7 +55,7 @@ constraints(ProjectUrlConstrainer.new) do end resource :pages, only: [:show, :destroy] do - resources :domains, only: [:show, :new, :create, :destroy], controller: 'pages_domains', constraints: { id: /[^\/]+/ } + resources :domains, only: [:show, :new, :create, :destroy], controller: 'pages_domains', constraints: { id: %r{[^/]+} } end resources :snippets, concerns: :awardable, constraints: { id: /\d+/ } do @@ -65,7 +65,7 @@ constraints(ProjectUrlConstrainer.new) do end end - resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do + resources :services, constraints: { id: %r{[^/]+} }, only: [:index, :edit, :update] do member do put :test end @@ -346,7 +346,7 @@ constraints(ProjectUrlConstrainer.new) do end end - resources :project_members, except: [:show, :new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ }, concerns: :access_requestable do + resources :project_members, except: [:show, :new, :edit], constraints: { id: %r{[a-zA-Z./0-9_\-#%+]+} }, concerns: :access_requestable do collection do delete :leave @@ -379,7 +379,7 @@ constraints(ProjectUrlConstrainer.new) do resources :uploads, only: [:create] do collection do - get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ } + get ":secret/:filename", action: :show, as: :show, constraints: { filename: %r{[^/]+} } end end diff --git a/config/routes/uploads.rb b/config/routes/uploads.rb index d7bca8310e4..6370645bcb9 100644 --- a/config/routes/uploads.rb +++ b/config/routes/uploads.rb @@ -2,17 +2,17 @@ scope path: :uploads do # Note attachments and User/Group/Project avatars get "-/system/:model/:mounted_as/:id/:filename", to: "uploads#show", - constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ } + constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: %r{[^/]+} } # show uploads for models, snippets (notes) available for now get '-/system/:model/:id/:secret/:filename', to: 'uploads#show', - constraints: { model: /personal_snippet/, id: /\d+/, filename: /[^\/]+/ } + constraints: { model: /personal_snippet/, id: /\d+/, filename: %r{[^/]+} } # show temporary uploads get '-/system/temp/:secret/:filename', to: 'uploads#show', - constraints: { filename: /[^\/]+/ } + constraints: { filename: %r{[^/]+} } # Appearance get "-/system/:model/:mounted_as/:id/:filename", @@ -22,7 +22,7 @@ scope path: :uploads do # Project markdown uploads get ":namespace_id/:project_id/:secret/:filename", to: "projects/uploads#show", - constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /[^\/]+/ } + constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: %r{[^/]+} } # create uploads for models, snippets (notes) available for now post ':model', @@ -34,4 +34,4 @@ end # Redirect old note attachments path to new uploads path. get "files/note/:id/:filename", to: redirect("uploads/note/attachment/%{id}/%{filename}"), - constraints: { filename: /[^\/]+/ } + constraints: { filename: %r{[^/]+} } diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb index fc4ef26f6b6..db99c179439 100644 --- a/features/steps/project/source/markdown_render.rb +++ b/features/steps/project/source/markdown_render.rb @@ -193,7 +193,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end step 'The link with text "/ID" should have url "tree/markdownID"' do - find('a', text: /^\/#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id' + find('a', text: %r{^/#id$})['href'] == current_host + project_tree_path(@project, "markdown") + '#id' end step 'The link with text "README.mdID" '\ @@ -203,7 +203,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps step 'The link with text "d/README.mdID" should have '\ 'url "blob/markdown/d/README.mdID"' do - find('a', text: /^d\/README.md#id$/)['href'] == current_host + project_blob_path(@project, "d/markdown/README.md") + '#id' + find('a', text: %r{^d/README.md#id$})['href'] == current_host + project_blob_path(@project, "d/markdown/README.md") + '#id' end step 'The link with text "ID" should have url "blob/markdown/README.mdID"' do @@ -212,7 +212,7 @@ class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps end step 'The link with text "/ID" should have url "blob/markdown/README.mdID"' do - find('a', text: /^\/#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id' + find('a', text: %r{^/#id$})['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id' end # Wiki diff --git a/lib/api/templates.rb b/lib/api/templates.rb index 6550b331fb8..41862768a3f 100644 --- a/lib/api/templates.rb +++ b/lib/api/templates.rb @@ -17,15 +17,15 @@ module API } }.freeze PROJECT_TEMPLATE_REGEX = - /[\<\{\[] + %r{[\<\{\[] (project|description| one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here - [\>\}\]]/xi.freeze + [\>\}\]]}xi.freeze YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze FULLNAME_TEMPLATE_REGEX = - /[\<\{\[] + %r{[\<\{\[] (fullname|name\sof\s(author|copyright\sowner)) - [\>\}\]]/xi.freeze + [\>\}\]]}xi.freeze helpers do def parsed_license_template diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb index a7f0813bf74..c856ba99f09 100644 --- a/lib/api/v3/projects.rb +++ b/lib/api/v3/projects.rb @@ -173,7 +173,7 @@ module API use :sort_params use :pagination end - get "/search/:query", requirements: { query: /[^\/]+/ } do + get "/search/:query", requirements: { query: %r{[^/]+} } do search_service = Search::GlobalService.new(current_user, search: params[:query]).execute projects = search_service.objects('projects', params[:page], false) projects = projects.reorder(params[:order_by] => params[:sort]) diff --git a/lib/api/v3/templates.rb b/lib/api/v3/templates.rb index 7298203df10..b82b02b5f49 100644 --- a/lib/api/v3/templates.rb +++ b/lib/api/v3/templates.rb @@ -16,15 +16,15 @@ module API } }.freeze PROJECT_TEMPLATE_REGEX = - /[\<\{\[] + %r{[\<\{\[] (project|description| one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here - [\>\}\]]/xi.freeze + [\>\}\]]}xi.freeze YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze FULLNAME_TEMPLATE_REGEX = - /[\<\{\[] + %r{[\<\{\[] (fullname|name\sof\s(author|copyright\sowner)) - [\>\}\]]/xi.freeze + [\>\}\]]}xi.freeze DEPRECATION_MESSAGE = ' This endpoint is deprecated and has been removed in V4.'.freeze helpers do diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb index 6255a611dbe..b82c6ca6393 100644 --- a/lib/banzai/filter/emoji_filter.rb +++ b/lib/banzai/filter/emoji_filter.rb @@ -54,9 +54,9 @@ module Banzai # Build a regexp that matches all valid :emoji: names. def self.emoji_pattern @emoji_pattern ||= - /(?<=[^[:alnum:]:]|\n|^) + %r{(?<=[^[:alnum:]:]|\n|^) :(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}): - (?=[^[:alnum:]:]|$)/x + (?=[^[:alnum:]:]|$)}x end # Build a regexp that matches all valid unicode emojis names. diff --git a/lib/banzai/filter/gollum_tags_filter.rb b/lib/banzai/filter/gollum_tags_filter.rb index 2e259904673..c2b42673376 100644 --- a/lib/banzai/filter/gollum_tags_filter.rb +++ b/lib/banzai/filter/gollum_tags_filter.rb @@ -51,10 +51,10 @@ module Banzai # See https://github.com/gollum/gollum/wiki # # Rubular: http://rubular.com/r/7dQnE5CUCH - TAGS_PATTERN = %r{\[\[(.+?)\]\]}.freeze + TAGS_PATTERN = /\[\[(.+?)\]\]/.freeze # Pattern to match allowed image extensions - ALLOWED_IMAGE_EXTENSIONS = %r{.+(jpg|png|gif|svg|bmp)\z}i.freeze + ALLOWED_IMAGE_EXTENSIONS = /.+(jpg|png|gif|svg|bmp)\z/i.freeze def call search_text_nodes(doc).each do |node| diff --git a/lib/container_registry/registry.rb b/lib/container_registry/registry.rb index 63bce655f57..f90d711474a 100644 --- a/lib/container_registry/registry.rb +++ b/lib/container_registry/registry.rb @@ -11,7 +11,7 @@ module ContainerRegistry private def default_path - @uri.sub(/^https?:\/\//, '') + @uri.sub(%r{^https?://}, '') end end end diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index d8aca3304c5..a9b04c183ad 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -56,7 +56,7 @@ module ExtractsPath if valid_refs.length == 0 # No exact ref match, so just try our best - pair = id.match(/([^\/]+)(.*)/).captures + pair = id.match(%r{([^/]+)(.*)}).captures else # There is a distinct possibility that multiple refs prefix the ID. # Use the longest match to maximize the chance that we have the @@ -68,7 +68,7 @@ module ExtractsPath end # Remove ending slashes from path - pair[1].gsub!(/^\/|\/$/, '') + pair[1].gsub!(%r{^/|/$}, '') pair end diff --git a/lib/gitlab/background_migration/populate_untracked_uploads.rb b/lib/gitlab/background_migration/populate_untracked_uploads.rb index 81e95e5832d..d60e41d9f9d 100644 --- a/lib/gitlab/background_migration/populate_untracked_uploads.rb +++ b/lib/gitlab/background_migration/populate_untracked_uploads.rb @@ -12,7 +12,7 @@ module Gitlab # Ends with /:random_hex/:filename FILE_UPLOADER_PATH = %r{/\h+/[^/]+\z} - FULL_PATH_CAPTURE = %r{\A(.+)#{FILE_UPLOADER_PATH}} + FULL_PATH_CAPTURE = /\A(.+)#{FILE_UPLOADER_PATH}/ # These regex patterns are tested against a relative path, relative to # the upload directory. diff --git a/lib/gitlab/ci/build/artifacts/metadata/entry.rb b/lib/gitlab/ci/build/artifacts/metadata/entry.rb index 5b2f09e03ea..428c0505808 100644 --- a/lib/gitlab/ci/build/artifacts/metadata/entry.rb +++ b/lib/gitlab/ci/build/artifacts/metadata/entry.rb @@ -97,7 +97,7 @@ module Gitlab end def total_size - descendant_pattern = %r{^#{Regexp.escape(@path.to_s)}} + descendant_pattern = /^#{Regexp.escape(@path.to_s)}/ entries.sum do |path, entry| (entry[:size] if path =~ descendant_pattern).to_i end diff --git a/lib/gitlab/dependency_linker/composer_json_linker.rb b/lib/gitlab/dependency_linker/composer_json_linker.rb index 0245bf4077a..cfd4ec15125 100644 --- a/lib/gitlab/dependency_linker/composer_json_linker.rb +++ b/lib/gitlab/dependency_linker/composer_json_linker.rb @@ -11,7 +11,7 @@ module Gitlab end def package_url(name) - "https://packagist.org/packages/#{name}" if name =~ %r{\A#{REPO_REGEX}\z} + "https://packagist.org/packages/#{name}" if name =~ /\A#{REPO_REGEX}\z/ end end end diff --git a/lib/gitlab/dependency_linker/gemfile_linker.rb b/lib/gitlab/dependency_linker/gemfile_linker.rb index d034ea67387..bfea836bcb2 100644 --- a/lib/gitlab/dependency_linker/gemfile_linker.rb +++ b/lib/gitlab/dependency_linker/gemfile_linker.rb @@ -15,7 +15,7 @@ module Gitlab link_regex(/(github:|:github\s*=>)\s*['"](?[^'"]+)['"]/, &method(:github_url)) # Link `git: "https://gitlab.example.com/user/repo"` to https://gitlab.example.com/user/repo - link_regex(%r{(git:|:git\s*=>)\s*['"](?#{URL_REGEX})['"]}, &:itself) + link_regex(/(git:|:git\s*=>)\s*['"](?#{URL_REGEX})['"]/, &:itself) # Link `source "https://rubygems.org"` to https://rubygems.org link_method_call('source', URL_REGEX, &:itself) diff --git a/lib/gitlab/dependency_linker/podspec_linker.rb b/lib/gitlab/dependency_linker/podspec_linker.rb index a52c7a02439..924e55e9820 100644 --- a/lib/gitlab/dependency_linker/podspec_linker.rb +++ b/lib/gitlab/dependency_linker/podspec_linker.rb @@ -12,7 +12,7 @@ module Gitlab def link_dependencies link_method_call('homepage', URL_REGEX, &:itself) - link_regex(%r{(git:|:git\s*=>)\s*['"](?#{URL_REGEX})['"]}, &:itself) + link_regex(/(git:|:git\s*=>)\s*['"](?#{URL_REGEX})['"]/, &:itself) link_method_call('license', &method(:license_url)) link_regex(/license\s*=\s*\{\s*(type:|:type\s*=>)\s*#{STRING_REGEX}/, &method(:license_url)) diff --git a/lib/gitlab/email/reply_parser.rb b/lib/gitlab/email/reply_parser.rb index 558df87f36d..01c28d051ee 100644 --- a/lib/gitlab/email/reply_parser.rb +++ b/lib/gitlab/email/reply_parser.rb @@ -43,7 +43,7 @@ module Gitlab return "" unless decoded # Certain trigger phrases that means we didn't parse correctly - if decoded =~ /(Content\-Type\:|multipart\/alternative|text\/plain)/ + if decoded =~ %r{(Content\-Type\:|multipart/alternative|text/plain)} return "" end diff --git a/lib/gitlab/file_detector.rb b/lib/gitlab/file_detector.rb index 0e9ef4f897c..cc2638172ec 100644 --- a/lib/gitlab/file_detector.rb +++ b/lib/gitlab/file_detector.rb @@ -6,14 +6,14 @@ module Gitlab module FileDetector PATTERNS = { # Project files - readme: /\Areadme[^\/]*\z/i, - changelog: /\A(changelog|history|changes|news)[^\/]*\z/i, - license: /\A(licen[sc]e|copying)(\.[^\/]+)?\z/i, - contributing: /\Acontributing[^\/]*\z/i, + readme: %r{\Areadme[^/]*\z}i, + changelog: %r{\A(changelog|history|changes|news)[^/]*\z}i, + license: %r{\A(licen[sc]e|copying)(\.[^/]+)?\z}i, + contributing: %r{\Acontributing[^/]*\z}i, version: 'version', avatar: /\Alogo\.(png|jpg|gif)\z/, - issue_template: /\A\.gitlab\/issue_templates\/[^\/]+\.md\z/, - merge_request_template: /\A\.gitlab\/merge_request_templates\/[^\/]+\.md\z/, + issue_template: %r{\A\.gitlab/issue_templates/[^/]+\.md\z}, + merge_request_template: %r{\A\.gitlab/merge_request_templates/[^/]+\.md\z}, # Configuration files gitignore: '.gitignore', @@ -22,17 +22,17 @@ module Gitlab route_map: '.gitlab/route-map.yml', # Dependency files - cartfile: /\ACartfile[^\/]*\z/, + cartfile: %r{\ACartfile[^/]*\z}, composer_json: 'composer.json', gemfile: /\A(Gemfile|gems\.rb)\z/, gemfile_lock: 'Gemfile.lock', - gemspec: /\A[^\/]*\.gemspec\z/, + gemspec: %r{\A[^/]*\.gemspec\z}, godeps_json: 'Godeps.json', package_json: 'package.json', podfile: 'Podfile', - podspec_json: /\A[^\/]*\.podspec\.json\z/, - podspec: /\A[^\/]*\.podspec\z/, - requirements_txt: /\A[^\/]*requirements\.txt\z/, + podspec_json: %r{\A[^/]*\.podspec\.json\z}, + podspec: %r{\A[^/]*\.podspec\z}, + requirements_txt: %r{\A[^/]*requirements\.txt\z}, yarn_lock: 'yarn.lock' }.freeze diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb index 71647099f83..c77db0f685f 100644 --- a/lib/gitlab/git.rb +++ b/lib/gitlab/git.rb @@ -11,7 +11,7 @@ module Gitlab include Gitlab::EncodingHelper def ref_name(ref) - encode!(ref).sub(/\Arefs\/(tags|heads|remotes)\//, '') + encode!(ref).sub(%r{\Arefs/(tags|heads|remotes)/}, '') end def branch_name(ref) diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index 13120120223..4828301dbb9 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -107,7 +107,7 @@ module Gitlab def find_entry_by_path(repository, root_id, path) root_tree = repository.lookup(root_id) # Strip leading slashes - path[/^\/*/] = '' + path[%r{^/*}] = '' path_arr = path.split('/') entry = root_tree.find do |entry| @@ -140,7 +140,7 @@ module Gitlab def find_by_gitaly(repository, sha, path, limit: MAX_DATA_DISPLAY_SIZE) return unless path - path = path.sub(/\A\/*/, '') + path = path.sub(%r{\A/*}, '') path = '/' if path.empty? name = File.basename(path) diff --git a/lib/gitlab/git/path_helper.rb b/lib/gitlab/git/path_helper.rb index 42c80aabd0a..155cf52f050 100644 --- a/lib/gitlab/git/path_helper.rb +++ b/lib/gitlab/git/path_helper.rb @@ -6,7 +6,7 @@ module Gitlab class << self def normalize_path(filename) # Strip all leading slashes so that //foo -> foo - filename[/^\/*/] = '' + filename[%r{^/*}] = '' # Expand relative paths (e.g. foo/../bar) filename = Pathname.new(filename) diff --git a/lib/gitlab/git/ref.rb b/lib/gitlab/git/ref.rb index a3ba9475ad0..fa71a4e7ea7 100644 --- a/lib/gitlab/git/ref.rb +++ b/lib/gitlab/git/ref.rb @@ -23,7 +23,7 @@ module Gitlab # Ex. # Ref.extract_branch_name('refs/heads/master') #=> 'master' def self.extract_branch_name(str) - str.gsub(/\Arefs\/heads\//, '') + str.gsub(%r{\Arefs/heads/}, '') end # Gitaly: this method will probably be migrated indirectly via its call sites. diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 8137c582c0f..6d3b918aaae 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -1397,7 +1397,7 @@ module Gitlab end def search_files_by_name(query, ref) - safe_query = Regexp.escape(query.sub(/^\/*/, "")) + safe_query = Regexp.escape(query.sub(%r{^/*}, "")) return [] if empty? || safe_query.blank? @@ -2025,7 +2025,7 @@ module Gitlab target_commit = Gitlab::Git::Commit.find(self, rugged_ref.target) Gitlab::Git::Branch.new(self, rugged_ref.name, rugged_ref.target, target_commit) rescue Rugged::ReferenceError => e - raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ /'refs\/heads\/#{ref}'/ + raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ %r{'refs/heads/#{ref}'} raise InvalidRef.new("Invalid reference #{start_point}") end diff --git a/lib/gitlab/git/repository_mirroring.rb b/lib/gitlab/git/repository_mirroring.rb index effb1f0ca19..dc424a433fb 100644 --- a/lib/gitlab/git/repository_mirroring.rb +++ b/lib/gitlab/git/repository_mirroring.rb @@ -43,7 +43,7 @@ module Gitlab branches = [] rugged.references.each("refs/remotes/#{remote_name}/*").map do |ref| - name = ref.name.sub(/\Arefs\/remotes\/#{remote_name}\//, '') + name = ref.name.sub(%r{\Arefs/remotes/#{remote_name}/}, '') begin target_commit = Gitlab::Git::Commit.find(self, ref.target) diff --git a/lib/gitlab/github_import/representation/diff_note.rb b/lib/gitlab/github_import/representation/diff_note.rb index bb7439a0641..be1334ca98a 100644 --- a/lib/gitlab/github_import/representation/diff_note.rb +++ b/lib/gitlab/github_import/representation/diff_note.rb @@ -13,7 +13,7 @@ module Gitlab :diff_hunk, :author, :note, :created_at, :updated_at, :github_id - NOTEABLE_ID_REGEX = /\/pull\/(?\d+)/i + NOTEABLE_ID_REGEX = %r{/pull/(?\d+)}i # Builds a diff note from a GitHub API response. # diff --git a/lib/gitlab/github_import/representation/note.rb b/lib/gitlab/github_import/representation/note.rb index a68bc4c002f..070e3b2db8d 100644 --- a/lib/gitlab/github_import/representation/note.rb +++ b/lib/gitlab/github_import/representation/note.rb @@ -12,7 +12,7 @@ module Gitlab expose_attribute :noteable_id, :noteable_type, :author, :note, :created_at, :updated_at, :github_id - NOTEABLE_TYPE_REGEX = /\/(?(pull|issues))\/(?\d+)/i + NOTEABLE_TYPE_REGEX = %r{/(?(pull|issues))/(?\d+)}i # Builds a note from a GitHub API response. # diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb index 5c971564a73..0f4c3498036 100644 --- a/lib/gitlab/import_export/file_importer.rb +++ b/lib/gitlab/import_export/file_importer.rb @@ -59,7 +59,7 @@ module Gitlab end def extracted_files - Dir.glob("#{@shared.export_path}/**/*", File::FNM_DOTMATCH).reject { |f| f =~ /.*\/\.{1,2}$/ } + Dir.glob("#{@shared.export_path}/**/*", File::FNM_DOTMATCH).reject { |f| f =~ %r{.*/\.{1,2}$} } end end end diff --git a/lib/gitlab/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb index 274436ca2b4..b600e8a2a50 100644 --- a/lib/gitlab/metrics/subscribers/action_view.rb +++ b/lib/gitlab/metrics/subscribers/action_view.rb @@ -34,7 +34,7 @@ module Gitlab end def relative_path(path) - path.gsub(/^#{Rails.root.to_s}\/?/, '') + path.gsub(%r{^#{Rails.root.to_s}/?}, '') end def values_for(event) diff --git a/lib/gitlab/middleware/go.rb b/lib/gitlab/middleware/go.rb index c6a56277922..afbc2600634 100644 --- a/lib/gitlab/middleware/go.rb +++ b/lib/gitlab/middleware/go.rb @@ -56,12 +56,12 @@ module Gitlab end def strip_url(url) - url.gsub(/\Ahttps?:\/\//, '') + url.gsub(%r{\Ahttps?://}, '') end def project_path(request) path_info = request.env["PATH_INFO"] - path_info.sub!(/^\//, '') + path_info.sub!(%r{^/}, '') project_path_match = "#{path_info}/".match(PROJECT_PATH_REGEX) return unless project_path_match diff --git a/lib/gitlab/middleware/static.rb b/lib/gitlab/middleware/static.rb index 85ffa8aca68..aa1e9dc0fdb 100644 --- a/lib/gitlab/middleware/static.rb +++ b/lib/gitlab/middleware/static.rb @@ -1,7 +1,7 @@ module Gitlab module Middleware class Static < ActionDispatch::Static - UPLOADS_REGEX = /\A\/uploads(\/|\z)/.freeze + UPLOADS_REGEX = %r{\A/uploads(/|\z)}.freeze def call(env) return @app.call(env) if env['PATH_INFO'] =~ UPLOADS_REGEX diff --git a/lib/gitlab/quick_actions/spend_time_and_date_separator.rb b/lib/gitlab/quick_actions/spend_time_and_date_separator.rb index 3f52402b31f..7328c517a30 100644 --- a/lib/gitlab/quick_actions/spend_time_and_date_separator.rb +++ b/lib/gitlab/quick_actions/spend_time_and_date_separator.rb @@ -9,7 +9,7 @@ module Gitlab # if date doesn't present return time with current date # in other cases return nil class SpendTimeAndDateSeparator - DATE_REGEX = /(\d{2,4}[\/\-.]\d{1,2}[\/\-.]\d{1,2})/ + DATE_REGEX = %r{(\d{2,4}[/\-.]\d{1,2}[/\-.]\d{1,2})} def initialize(spend_command_arg) @spend_arg = spend_command_arg diff --git a/lib/gitlab/repo_path.rb b/lib/gitlab/repo_path.rb index 3591fa9145e..79265cf952d 100644 --- a/lib/gitlab/repo_path.rb +++ b/lib/gitlab/repo_path.rb @@ -30,7 +30,7 @@ module Gitlab raise NotFoundError.new("No known storage path matches #{repo_path.inspect}") end - result.sub(/\A\/*/, '') + result.sub(%r{\A/*}, '') end def self.find_project(project_path) diff --git a/lib/gitlab/setup_helper.rb b/lib/gitlab/setup_helper.rb index d01213bb6e0..e90a90508a2 100644 --- a/lib/gitlab/setup_helper.rb +++ b/lib/gitlab/setup_helper.rb @@ -31,7 +31,7 @@ module Gitlab storages << { name: 'test_second_storage', path: Rails.root.join('tmp', 'tests', 'second_storage').to_s } end - config = { socket_path: address.sub(%r{\Aunix:}, ''), storage: storages } + config = { socket_path: address.sub(/\Aunix:/, ''), storage: storages } config[:auth] = { token: 'secret' } if Rails.env.test? config[:'gitaly-ruby'] = { dir: File.join(gitaly_dir, 'ruby') } if gitaly_ruby config[:'gitlab-shell'] = { dir: Gitlab.config.gitlab_shell.path } diff --git a/lib/gitlab/sherlock/file_sample.rb b/lib/gitlab/sherlock/file_sample.rb index 8a3e1a5e5bf..89072b01f2e 100644 --- a/lib/gitlab/sherlock/file_sample.rb +++ b/lib/gitlab/sherlock/file_sample.rb @@ -16,7 +16,7 @@ module Gitlab end def relative_path - @relative_path ||= @file.gsub(/^#{Rails.root.to_s}\/?/, '') + @relative_path ||= @file.gsub(%r{^#{Rails.root.to_s}/?}, '') end def to_param diff --git a/lib/gitlab/sherlock/middleware.rb b/lib/gitlab/sherlock/middleware.rb index 687332fc5fc..4c88e33699a 100644 --- a/lib/gitlab/sherlock/middleware.rb +++ b/lib/gitlab/sherlock/middleware.rb @@ -2,7 +2,7 @@ module Gitlab module Sherlock # Rack middleware used for tracking request metrics. class Middleware - CONTENT_TYPES = /text\/html|application\/json/i + CONTENT_TYPES = %r{text/html|application/json}i IGNORE_PATHS = %r{^/sherlock} diff --git a/lib/gitlab/sherlock/query.rb b/lib/gitlab/sherlock/query.rb index 948bf5e6528..02ddc3f47eb 100644 --- a/lib/gitlab/sherlock/query.rb +++ b/lib/gitlab/sherlock/query.rb @@ -4,7 +4,7 @@ module Gitlab attr_reader :id, :query, :started_at, :finished_at, :backtrace # SQL identifiers that should be prefixed with newlines. - PREFIX_NEWLINE = / + PREFIX_NEWLINE = %r{ \s+(FROM |(LEFT|RIGHT)?INNER\s+JOIN |(LEFT|RIGHT)?OUTER\s+JOIN @@ -13,7 +13,7 @@ module Gitlab |GROUP\s+BY |ORDER\s+BY |LIMIT - |OFFSET)\s+/ix # Vim indent breaks when this is on a newline :< + |OFFSET)\s+}ix # Vim indent breaks when this is on a newline :< # Creates a new Query using a String and a separate Array of bindings. # diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb index d545f2f95f1..024be6aca44 100644 --- a/lib/gitlab/upgrader.rb +++ b/lib/gitlab/upgrader.rb @@ -49,7 +49,7 @@ module Gitlab def fetch_git_tags remote_tags, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} ls-remote --tags https://gitlab.com/gitlab-org/gitlab-ce.git)) - remote_tags.split("\n").grep(/tags\/v#{current_version.major}/) + remote_tags.split("\n").grep(%r{tags/v#{current_version.major}}) end def update_commands diff --git a/qa/qa/page/menu/side.rb b/qa/qa/page/menu/side.rb index 5fdcea20029..7e028add2ef 100644 --- a/qa/qa/page/menu/side.rb +++ b/qa/qa/page/menu/side.rb @@ -7,7 +7,7 @@ module QA element :settings_link, 'link_to edit_project_path' element :repository_link, "title: 'Repository'" element :pipelines_settings_link, "title: 'CI / CD'" - element :issues_link, %r{link_to.*shortcuts-issues} + element :issues_link, /link_to.*shortcuts-issues/ element :issues_link_text, "Issues" element :top_level_items, '.sidebar-top-level-items' element :activity_link, "title: 'Activity'" diff --git a/qa/spec/runtime/rsa_key.rb b/qa/spec/runtime/rsa_key.rb index ff277b9077b..6d7ab4dcd2e 100644 --- a/qa/spec/runtime/rsa_key.rb +++ b/qa/spec/runtime/rsa_key.rb @@ -3,7 +3,7 @@ describe QA::Runtime::RSAKey do subject { described_class.new.public_key } it 'generates a public RSA key' do - expect(subject).to match(/\Assh\-rsa AAAA[0-9A-Za-z+\/]+={0,3}\z/) + expect(subject).to match(%r{\Assh\-rsa AAAA[0-9A-Za-z+/]+={0,3}\z}) end end end diff --git a/rubocop/cop/migration/update_column_in_batches.rb b/rubocop/cop/migration/update_column_in_batches.rb index db2b5564297..5461abc5ee0 100644 --- a/rubocop/cop/migration/update_column_in_batches.rb +++ b/rubocop/cop/migration/update_column_in_batches.rb @@ -29,7 +29,7 @@ module RuboCop path = Pathname.new(source_name).relative_path_from(rails_root) dirname = File.dirname(path) .sub(%r{\Adb/(migrate|post_migrate)}, 'spec/migrations') - filename = File.basename(source_name, '.rb').sub(%r{\A\d+_}, '') + filename = File.basename(source_name, '.rb').sub(/\A\d+_/, '') File.join(dirname, "#{filename}_spec.rb") end diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index e6a4e7c8257..db595430979 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -137,8 +137,8 @@ describe Projects::JobsController do it 'exposes needed information' do expect(response).to have_gitlab_http_status(:ok) - expect(json_response['raw_path']).to match(/jobs\/\d+\/raw\z/) - expect(json_response.dig('merge_request', 'path')).to match(/merge_requests\/\d+\z/) + expect(json_response['raw_path']).to match(%r{jobs/\d+/raw\z}) + expect(json_response.dig('merge_request', 'path')).to match(%r{merge_requests/\d+\z}) expect(json_response['new_issue_path']) .to include('/issues/new') end diff --git a/spec/controllers/projects/todos_controller_spec.rb b/spec/controllers/projects/todos_controller_spec.rb index e2524be7724..1ce7e84bef9 100644 --- a/spec/controllers/projects/todos_controller_spec.rb +++ b/spec/controllers/projects/todos_controller_spec.rb @@ -36,7 +36,7 @@ describe Projects::TodosController do expect(response).to have_gitlab_http_status(200) expect(json_response['count']).to eq 1 - expect(json_response['delete_path']).to match(/\/dashboard\/todos\/\d{1}/) + expect(json_response['delete_path']).to match(%r{/dashboard/todos/\d{1}}) end end @@ -104,7 +104,7 @@ describe Projects::TodosController do expect(response).to have_gitlab_http_status(200) expect(json_response['count']).to eq 1 - expect(json_response['delete_path']).to match(/\/dashboard\/todos\/\d{1}/) + expect(json_response['delete_path']).to match(%r{/dashboard/todos/\d{1}}) end end diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb index 782f42aab04..2d074c115dd 100644 --- a/spec/features/atom/users_spec.rb +++ b/spec/features/atom/users_spec.rb @@ -64,7 +64,7 @@ describe "User Feed" do end it 'has XHTML summaries in issue descriptions' do - expect(body).to match /


/ + expect(body).to match %r{
} end it 'has XHTML summaries in notes' do @@ -72,7 +72,7 @@ describe "User Feed" do end it 'has XHTML summaries in merge request descriptions' do - expect(body).to match /Here is the fix: ]*>]*\/><\/a>/ + expect(body).to match %r{Here is the fix: ]*>]*/>} end it 'has push event commit ID' do diff --git a/spec/features/merge_request/user_resolves_conflicts_spec.rb b/spec/features/merge_request/user_resolves_conflicts_spec.rb index 61861d33952..19995559fae 100644 --- a/spec/features/merge_request/user_resolves_conflicts_spec.rb +++ b/spec/features/merge_request/user_resolves_conflicts_spec.rb @@ -100,12 +100,12 @@ describe 'Merge request > User resolves conflicts', :js do end it 'shows a link to the conflict resolution page' do - expect(page).to have_link('conflicts', href: /\/conflicts\Z/) + expect(page).to have_link('conflicts', href: %r{/conflicts\Z}) end context 'in Inline view mode' do before do - click_link('conflicts', href: /\/conflicts\Z/) + click_link('conflicts', href: %r{/conflicts\Z}) end include_examples "conflicts are resolved in Interactive mode" @@ -114,7 +114,7 @@ describe 'Merge request > User resolves conflicts', :js do context 'in Parallel view mode' do before do - click_link('conflicts', href: /\/conflicts\Z/) + click_link('conflicts', href: %r{/conflicts\Z}) click_button 'Side-by-side' end @@ -128,7 +128,7 @@ describe 'Merge request > User resolves conflicts', :js do before do visit project_merge_request_path(project, merge_request) - click_link('conflicts', href: /\/conflicts\Z/) + click_link('conflicts', href: %r{/conflicts\Z}) end it 'conflicts can not be resolved in Interactive mode' do @@ -181,7 +181,7 @@ describe 'Merge request > User resolves conflicts', :js do end it 'does not show a link to the conflict resolution page' do - expect(page).not_to have_link('conflicts', href: /\/conflicts\Z/) + expect(page).not_to have_link('conflicts', href: %r{/conflicts\Z}) end it 'shows an error if the conflicts page is visited directly' do diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb index 32432ee1e81..5f608fe18d9 100644 --- a/spec/helpers/groups_helper_spec.rb +++ b/spec/helpers/groups_helper_spec.rb @@ -105,7 +105,7 @@ describe GroupsHelper do it 'outputs the groups in the correct order' do expect(helper.group_title(very_deep_nested_group)) - .to match(/
  • #{deep_nested_group.name}.*<\/li>.*#{very_deep_nested_group.name}<\/a>/m) + .to match(%r{
  • #{deep_nested_group.name}.*
  • .*#{very_deep_nested_group.name}}m) end end @@ -120,7 +120,7 @@ describe GroupsHelper do let(:possible_help_texts) do { default_help: "This setting will be applied to all subgroups unless overridden by a group owner", - ancestor_locked_but_you_can_override: /This setting is applied on .+<\/a>\. You can override the setting or .+/, + ancestor_locked_but_you_can_override: %r{This setting is applied on .+\. You can override the setting or .+}, ancestor_locked_so_ask_the_owner: /This setting is applied on .+\. To share projects in this group with another group, ask the owner to override the setting or remove the share with group lock from .+/, ancestor_locked_and_has_been_overridden: /This setting is applied on .+ and has been overridden on this subgroup/ } diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index 0286d36952c..619baa78bfa 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -104,7 +104,7 @@ describe LabelsHelper do context 'with a tooltip argument' do context 'set to false' do it 'does not include the has-tooltip class' do - expect(link_to_label(label, tooltip: false)).not_to match %r{has-tooltip} + expect(link_to_label(label, tooltip: false)).not_to match /has-tooltip/ end end end diff --git a/spec/helpers/version_check_helper_spec.rb b/spec/helpers/version_check_helper_spec.rb index fa8cfda3b86..b5b15726816 100644 --- a/spec/helpers/version_check_helper_spec.rb +++ b/spec/helpers/version_check_helper_spec.rb @@ -27,7 +27,7 @@ describe VersionCheckHelper do end it 'should have a VersionCheck url as the src' do - expect(@image_tag).to match(/src="https:\/\/version\.host\.com\/check\.svg\?gitlab_info=xxx"/) + expect(@image_tag).to match(%r{src="https://version\.host\.com/check\.svg\?gitlab_info=xxx"}) end end end diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb index 935146c17fc..a41a28a56f1 100644 --- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb @@ -53,7 +53,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do doc = reference_filter("See (#{reference}.)") exp = Regexp.escape(range.reference_link_text) - expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/) + expect(doc.to_html).to match(%r{\(#{exp}\.\)}) end it 'ignores invalid commit IDs' do @@ -222,7 +222,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do doc = reference_filter("Fixed (#{reference}.)") exp = Regexp.escape(range.reference_link_text(project)) - expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/) + expect(doc.to_html).to match(%r{\(#{exp}\.\)}) end it 'ignores invalid commit IDs on the referenced project' do diff --git a/spec/lib/banzai/filter/commit_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_reference_filter_spec.rb index 080a5f57da9..35f8792ff35 100644 --- a/spec/lib/banzai/filter/commit_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_reference_filter_spec.rb @@ -42,7 +42,7 @@ describe Banzai::Filter::CommitReferenceFilter do it 'links with adjacent text' do doc = reference_filter("See (#{reference}.)") - expect(doc.to_html).to match(/\(#{commit.short_id}<\/a>\.\)/) + expect(doc.to_html).to match(%r{\(#{commit.short_id}\.\)}) end it 'ignores invalid commit IDs' do @@ -199,12 +199,12 @@ describe Banzai::Filter::CommitReferenceFilter do it 'links with adjacent text' do doc = reference_filter("Fixed (#{reference}.)") - expect(doc.to_html).to match(/\(#{commit.reference_link_text(project)}<\/a>\.\)/) + expect(doc.to_html).to match(%r{\(#{commit.reference_link_text(project)}\.\)}) end it 'ignores invalid commit IDs on the referenced project' do act = "Committed #{invalidate_reference(reference)}" - expect(reference_filter(act).to_html).to match(/#{Regexp.escape(invalidate_reference(reference))}<\/a>/) + expect(reference_filter(act).to_html).to match(%r{#{Regexp.escape(invalidate_reference(reference))}}) end end end diff --git a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb index a0d391d981c..d9018a7e4fe 100644 --- a/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/external_issue_reference_filter_spec.rb @@ -49,7 +49,7 @@ describe Banzai::Filter::ExternalIssueReferenceFilter do it 'links with adjacent text' do doc = filter("Issue (#{reference}.)") - expect(doc.to_html).to match(/\(#{reference}<\/a>\.\)/) + expect(doc.to_html).to match(%r{\(#{reference}\.\)}) end it 'includes a title attribute' do diff --git a/spec/lib/banzai/filter/image_link_filter_spec.rb b/spec/lib/banzai/filter/image_link_filter_spec.rb index 51920869545..c84b98eb225 100644 --- a/spec/lib/banzai/filter/image_link_filter_spec.rb +++ b/spec/lib/banzai/filter/image_link_filter_spec.rb @@ -14,7 +14,7 @@ describe Banzai::Filter::ImageLinkFilter do it 'does not wrap a duplicate link' do doc = filter(%Q(#{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')})) - expect(doc.to_html).to match /^]*><\/a>$/ + expect(doc.to_html).to match %r{^]*>$} end it 'works with external images' do @@ -24,6 +24,6 @@ describe Banzai::Filter::ImageLinkFilter do it 'works with inline images' do doc = filter(%Q(

    test #{image('/uploads/e90decf88d8f96fe9e1389afc2e4a91f/test.jpg')} inline

    )) - expect(doc.to_html).to match /^

    test ]*>]*><\/a> inline<\/p>$/ + expect(doc.to_html).to match %r{^

    test ]*>]*> inline

    $} end end diff --git a/spec/lib/banzai/filter/issue_reference_filter_spec.rb b/spec/lib/banzai/filter/issue_reference_filter_spec.rb index 3a5f52ea23f..905fbb9434b 100644 --- a/spec/lib/banzai/filter/issue_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/issue_reference_filter_spec.rb @@ -288,7 +288,7 @@ describe Banzai::Filter::IssueReferenceFilter do it 'links with adjacent text' do doc = reference_filter("Fixed (#{reference}.)") - expect(doc.to_html).to match(/\(#{Regexp.escape(issue.to_reference(project))} \(comment 123\)<\/a>\.\)/) + expect(doc.to_html).to match(%r{\(#{Regexp.escape(issue.to_reference(project))} \(comment 123\)\.\)}) end it 'includes default classes' do @@ -317,7 +317,7 @@ describe Banzai::Filter::IssueReferenceFilter do it 'links with adjacent text' do doc = reference_filter("Fixed (#{reference_link}.)") - expect(doc.to_html).to match(/\(Reference<\/a>\.\)/) + expect(doc.to_html).to match(%r{\(Reference\.\)}) end it 'includes default classes' do @@ -346,7 +346,7 @@ describe Banzai::Filter::IssueReferenceFilter do it 'links with adjacent text' do doc = reference_filter("Fixed (#{reference_link}.)") - expect(doc.to_html).to match(/\(Reference<\/a>\.\)/) + expect(doc.to_html).to match(%r{\(Reference\.\)}) end it 'includes default classes' do diff --git a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb index 158844e25ae..eeb82822f68 100644 --- a/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/merge_request_reference_filter_spec.rb @@ -42,7 +42,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter do it 'links with adjacent text' do doc = reference_filter("Merge (#{reference}.)") - expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/) + expect(doc.to_html).to match(%r{\(#{Regexp.escape(reference)}\.\)}) end it 'ignores invalid merge IDs' do @@ -211,7 +211,7 @@ describe Banzai::Filter::MergeRequestReferenceFilter do it 'links with adjacent text' do doc = reference_filter("Merge (#{reference}.)") - expect(doc.to_html).to match(/\(#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)<\/a>\.\)/) + expect(doc.to_html).to match(%r{\(#{Regexp.escape(merge.to_reference(project))} \(diffs, comment 123\)\.\)}) end end diff --git a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb index 3a07a6dc179..e068e02d4fc 100644 --- a/spec/lib/banzai/filter/snippet_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/snippet_reference_filter_spec.rb @@ -28,7 +28,7 @@ describe Banzai::Filter::SnippetReferenceFilter do it 'links with adjacent text' do doc = reference_filter("Snippet (#{reference}.)") - expect(doc.to_html).to match(/\(#{Regexp.escape(reference)}<\/a>\.\)/) + expect(doc.to_html).to match(%r{\(#{Regexp.escape(reference)}\.\)}) end it 'ignores invalid snippet IDs' do @@ -192,13 +192,13 @@ describe Banzai::Filter::SnippetReferenceFilter do it 'links with adjacent text' do doc = reference_filter("See (#{reference}.)") - expect(doc.to_html).to match(/\(#{Regexp.escape(snippet.to_reference(project))}<\/a>\.\)/) + expect(doc.to_html).to match(%r{\(#{Regexp.escape(snippet.to_reference(project))}\.\)}) end it 'ignores invalid snippet IDs on the referenced project' do act = "See #{invalidate_reference(reference)}" - expect(reference_filter(act).to_html).to match(/#{Regexp.escape(invalidate_reference(reference))}<\/a>/) + expect(reference_filter(act).to_html).to match(%r{#{Regexp.escape(invalidate_reference(reference))}}) end end diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb index c76adc262fc..2f86a046d28 100644 --- a/spec/lib/banzai/filter/user_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb @@ -146,7 +146,7 @@ describe Banzai::Filter::UserReferenceFilter do it 'links with adjacent text' do doc = reference_filter("Mention me (#{reference}.)") - expect(doc.to_html).to match(/\(#{reference}<\/a>\.\)/) + expect(doc.to_html).to match(%r{\(#{reference}\.\)}) end it 'includes default classes' do @@ -172,7 +172,7 @@ describe Banzai::Filter::UserReferenceFilter do it 'links with adjacent text' do doc = reference_filter("Mention me (#{reference}.)") - expect(doc.to_html).to match(/\(User<\/a>\.\)/) + expect(doc.to_html).to match(%r{\(User\.\)}) end it 'includes a data-user attribute' do diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index bf01e6ef8e8..57194ec9f0a 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -249,7 +249,7 @@ describe Gitlab::Git::Repository, seed_helper: true do end shared_examples 'archive check' do |extenstion| - it { expect(metadata['ArchivePath']).to match(/tmp\/gitlab-git-test.git\/gitlab-git-test-master-#{SeedRepo::LastCommit::ID}/) } + it { expect(metadata['ArchivePath']).to match(%r{tmp/gitlab-git-test.git/gitlab-git-test-master-#{SeedRepo::LastCommit::ID}}) } it { expect(metadata['ArchivePath']).to end_with extenstion } end diff --git a/spec/lib/gitlab/path_regex_spec.rb b/spec/lib/gitlab/path_regex_spec.rb index 0ae90069b7f..85991c38363 100644 --- a/spec/lib/gitlab/path_regex_spec.rb +++ b/spec/lib/gitlab/path_regex_spec.rb @@ -121,7 +121,7 @@ describe Gitlab::PathRegex do STARTING_WITH_NAMESPACE = %r{^/\*namespace_id/:(project_)?id} NON_PARAM_PARTS = %r{[^:*][a-z\-_/]*} ANY_OTHER_PATH_PART = %r{[a-z\-_/:]*} - WILDCARD_SEGMENT = %r{\*} + WILDCARD_SEGMENT = /\*/ let(:namespaced_wildcard_routes) do routes_without_format.select do |p| p =~ %r{#{STARTING_WITH_NAMESPACE}/#{NON_PARAM_PARTS}/#{ANY_OTHER_PATH_PART}#{WILDCARD_SEGMENT}} diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 7a8e798e3c9..59eda025108 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -1357,7 +1357,7 @@ describe Notify do matcher :have_part_with do |expected| match do |actual| - actual.body.parts.any? { |part| part.content_type.try(:match, %r(#{expected})) } + actual.body.parts.any? { |part| part.content_type.try(:match, /#{expected}/) } end end end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index 1eaaadf56c5..748c366efca 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -205,7 +205,7 @@ describe JiraService do @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) expect(WebMock).to have_requested(:post, @comment_url).with( - body: /#{custom_base_url}\/#{project.full_path}\/commit\/#{merge_request.diff_head_sha}/ + body: %r{#{custom_base_url}/#{project.full_path}/commit/#{merge_request.diff_head_sha}} ).once end @@ -220,7 +220,7 @@ describe JiraService do @jira_service.close_issue(merge_request, ExternalIssue.new("JIRA-123", project)) expect(WebMock).to have_requested(:post, @comment_url).with( - body: /#{Gitlab.config.gitlab.url}\/#{project.full_path}\/commit\/#{merge_request.diff_head_sha}/ + body: %r{#{Gitlab.config.gitlab.url}/#{project.full_path}/commit/#{merge_request.diff_head_sha}} ).once end diff --git a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb index f17db70faf6..240aa638f79 100644 --- a/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_pipeline_succeeds_service_spec.rb @@ -43,7 +43,7 @@ describe MergeRequests::MergeWhenPipelineSucceedsService do it 'creates a system note' do note = merge_request.notes.last - expect(note.note).to match /enabled an automatic merge when the pipeline for (\w+\/\w+@)?\h{8}/ + expect(note.note).to match %r{enabled an automatic merge when the pipeline for (\w+/\w+@)?\h{8}} end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index ab3a257f36f..ab3aa18cf4e 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -308,7 +308,7 @@ describe SystemNoteService do end it "posts the 'merge when pipeline succeeds' system note" do - expect(subject.note).to match(/enabled an automatic merge when the pipeline for (\w+\/\w+@)?\h{40} succeeds/) + expect(subject.note).to match(%r{enabled an automatic merge when the pipeline for (\w+/\w+@)?\h{40} succeeds}) end end @@ -695,7 +695,7 @@ describe SystemNoteService do commit = double(title: '
    This is a test
    ', short_id: '12345678') escaped = '<pre>This is a test</pre>' - expect(described_class.new_commit_summary([commit])).to all(match(%r[- #{escaped}])) + expect(described_class.new_commit_summary([commit])).to all(match(/- #{escaped}/)) end end diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index b41c3b3958a..168facd51a6 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -165,7 +165,7 @@ describe 'gitlab:app namespace rake task' do expect(tar_contents).to match('pages.tar.gz') expect(tar_contents).to match('lfs.tar.gz') expect(tar_contents).to match('registry.tar.gz') - expect(tar_contents).not_to match(/^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|pages.tar.gz|artifacts.tar.gz|registry.tar.gz)\/$/) + expect(tar_contents).not_to match(%r{^.{4,9}[rwx].* (database.sql.gz|uploads.tar.gz|repositories|builds.tar.gz|pages.tar.gz|artifacts.tar.gz|registry.tar.gz)/$}) end it 'deletes temp directories' do diff --git a/spec/tasks/gitlab/git_rake_spec.rb b/spec/tasks/gitlab/git_rake_spec.rb index dacc5dc5ae7..9aebf7b0b4a 100644 --- a/spec/tasks/gitlab/git_rake_spec.rb +++ b/spec/tasks/gitlab/git_rake_spec.rb @@ -19,7 +19,7 @@ describe 'gitlab:git rake tasks' do describe 'fsck' do it 'outputs the integrity check for a repo' do - expect { run_rake_task('gitlab:git:fsck') }.to output(/Performed Checking integrity at .*@hashed\/1\/2\/test.git/).to_stdout + expect { run_rake_task('gitlab:git:fsck') }.to output(%r{Performed Checking integrity at .*@hashed/1/2/test.git}).to_stdout end it 'errors out about config.lock issues' do diff --git a/spec/uploaders/file_uploader_spec.rb b/spec/uploaders/file_uploader_spec.rb index fd195d6f9b8..845516e5004 100644 --- a/spec/uploaders/file_uploader_spec.rb +++ b/spec/uploaders/file_uploader_spec.rb @@ -112,7 +112,7 @@ describe FileUploader do fixture = Rails.root.join('spec', 'fixtures', 'rails_sample.jpg') uploader.store!(fixture_file_upload(fixture)) - expect(uploader.relative_path).to match(/\A\h{32}\/rails_sample.jpg\z/) + expect(uploader.relative_path).to match(%r{\A\h{32}/rails_sample.jpg\z}) end end end diff --git a/spec/uploaders/job_artifact_uploader_spec.rb b/spec/uploaders/job_artifact_uploader_spec.rb index 98a4373e9d0..a067c3e75f4 100644 --- a/spec/uploaders/job_artifact_uploader_spec.rb +++ b/spec/uploaders/job_artifact_uploader_spec.rb @@ -12,7 +12,7 @@ describe JobArtifactUploader do context 'when using local storage' do it { is_expected.to start_with(local_path) } - it { is_expected.to match(/\h{2}\/\h{2}\/\h{64}\/\d{4}_\d{1,2}_\d{1,2}\/\d+\/\d+\z/) } + it { is_expected.to match(%r{\h{2}/\h{2}/\h{64}/\d{4}_\d{1,2}_\d{1,2}/\d+/\d+\z}) } it { is_expected.to end_with(path) } end end From a2718ebaffd431b1eec91f3ee467d9632309b5f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 4 Aug 2017 19:14:04 +0200 Subject: [PATCH 20/38] Make user/author use project.creator in most factories MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/factories/commits.rb | 2 +- spec/factories/deployments.rb | 3 ++- spec/factories/events.rb | 2 +- spec/factories/issues.rb | 2 +- spec/factories/merge_requests.rb | 2 +- spec/factories/notes.rb | 2 +- spec/factories/project_wikis.rb | 2 +- spec/factories/sent_notifications.rb | 2 +- spec/factories/snippets.rb | 1 + spec/factories/subscriptions.rb | 2 +- spec/factories/timelogs.rb | 2 +- spec/factories/todos.rb | 4 ++-- .../features/dashboard/merge_requests_spec.rb | 22 ++++++++++--------- .../merge_request/user_awards_emoji_spec.rb | 2 +- .../slash_commands/issue_search_spec.rb | 2 +- spec/policies/project_policy_spec.rb | 2 +- spec/services/issues/close_service_spec.rb | 2 +- spec/services/issues/update_service_spec.rb | 3 ++- .../merge_requests/close_service_spec.rb | 2 +- .../merge_requests/ff_merge_service_spec.rb | 3 ++- .../merge_requests/reopen_service_spec.rb | 2 +- .../merge_requests/update_service_spec.rb | 3 ++- spec/services/notification_service_spec.rb | 6 +++-- 23 files changed, 42 insertions(+), 33 deletions(-) diff --git a/spec/factories/commits.rb b/spec/factories/commits.rb index 84a8bc56640..d5d819d862a 100644 --- a/spec/factories/commits.rb +++ b/spec/factories/commits.rb @@ -23,7 +23,7 @@ FactoryBot.define do end after(:build) do |commit, evaluator| - allow(commit).to receive(:author).and_return(evaluator.author || build(:author)) + allow(commit).to receive(:author).and_return(evaluator.author || build_stubbed(:author)) end trait :without_author do diff --git a/spec/factories/deployments.rb b/spec/factories/deployments.rb index 9d7d5e56611..cac56695319 100644 --- a/spec/factories/deployments.rb +++ b/spec/factories/deployments.rb @@ -3,13 +3,14 @@ FactoryBot.define do sha '97de212e80737a608d939f648d959671fb0a0142' ref 'master' tag false - user + user nil project nil deployable factory: :ci_build environment factory: :environment after(:build) do |deployment, evaluator| deployment.project ||= deployment.environment.project + deployment.user ||= deployment.project.creator unless deployment.project.repository_exists? allow(deployment.project.repository).to receive(:create_ref) diff --git a/spec/factories/events.rb b/spec/factories/events.rb index ed275243ac9..5798b81ecad 100644 --- a/spec/factories/events.rb +++ b/spec/factories/events.rb @@ -1,7 +1,7 @@ FactoryBot.define do factory :event do project - author factory: :user + author(factory: :user) { project.creator } action Event::JOINED trait(:created) { action Event::CREATED } diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb index 71dc169c6a2..998080a3dd5 100644 --- a/spec/factories/issues.rb +++ b/spec/factories/issues.rb @@ -1,8 +1,8 @@ FactoryBot.define do factory :issue do title { generate(:title) } - author project + author { project.creator } trait :confidential do confidential true diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 40558c88d15..d26cb0c3417 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -1,9 +1,9 @@ FactoryBot.define do factory :merge_request do title { generate(:title) } - author association :source_project, :repository, factory: :project target_project { source_project } + author { source_project.creator } # $ git log --pretty=oneline feature..master # 5937ac0a7beb003549fc5fd26fc247adbce4a52e Add submodule from gitlab.com diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 707ecbd6be5..2defb4935ad 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -6,7 +6,7 @@ FactoryBot.define do factory :note do project note { generate(:title) } - author + author { project&.creator || create(:user) } on_issue factory :note_on_commit, traits: [:on_commit] diff --git a/spec/factories/project_wikis.rb b/spec/factories/project_wikis.rb index 89d8248f9f4..db2eb4fc863 100644 --- a/spec/factories/project_wikis.rb +++ b/spec/factories/project_wikis.rb @@ -3,7 +3,7 @@ FactoryBot.define do skip_create project - user factory: :user + user { project.creator } initialize_with { new(project, user) } end end diff --git a/spec/factories/sent_notifications.rb b/spec/factories/sent_notifications.rb index 80872067233..b0174dd06b7 100644 --- a/spec/factories/sent_notifications.rb +++ b/spec/factories/sent_notifications.rb @@ -1,7 +1,7 @@ FactoryBot.define do factory :sent_notification do project - recipient factory: :user + recipient { project.creator } noteable { create(:issue, project: project) } reply_key { SentNotification.reply_key } end diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb index 2ab9a56d255..dc12b562108 100644 --- a/spec/factories/snippets.rb +++ b/spec/factories/snippets.rb @@ -21,6 +21,7 @@ FactoryBot.define do factory :project_snippet, parent: :snippet, class: :ProjectSnippet do project + author { project.creator } end factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do diff --git a/spec/factories/subscriptions.rb b/spec/factories/subscriptions.rb index a4bc4e87b0a..8f7ab74ec70 100644 --- a/spec/factories/subscriptions.rb +++ b/spec/factories/subscriptions.rb @@ -1,7 +1,7 @@ FactoryBot.define do factory :subscription do - user project + user { project.creator } subscribable factory: :issue end end diff --git a/spec/factories/timelogs.rb b/spec/factories/timelogs.rb index af34b0681e2..b45f06b9a0a 100644 --- a/spec/factories/timelogs.rb +++ b/spec/factories/timelogs.rb @@ -3,7 +3,7 @@ FactoryBot.define do factory :timelog do time_spent 3600 - user issue + user { issue.project.creator } end end diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb index 6a6de665dd1..94f8caedfa6 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -1,8 +1,8 @@ FactoryBot.define do factory :todo do project - author - user + author { project.creator } + user { project.creator } target factory: :issue action { Todo::ASSIGNED } diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb index 991d360ccaf..744041ac425 100644 --- a/spec/features/dashboard/merge_requests_spec.rb +++ b/spec/features/dashboard/merge_requests_spec.rb @@ -44,36 +44,38 @@ feature 'Dashboard Merge Requests' do context 'merge requests exist' do let!(:assigned_merge_request) do - create(:merge_request, assignee: current_user, target_project: project, source_project: project) + create(:merge_request, + assignee: current_user, + source_project: project, + author: create(:user)) end let!(:assigned_merge_request_from_fork) do create(:merge_request, source_branch: 'markdown', assignee: current_user, - target_project: public_project, source_project: forked_project - ) + target_project: public_project, source_project: forked_project, + author: create(:user)) end let!(:authored_merge_request) do create(:merge_request, - source_branch: 'markdown', author: current_user, - target_project: project, source_project: project - ) + source_branch: 'markdown', + source_project: project, + author: current_user) end let!(:authored_merge_request_from_fork) do create(:merge_request, source_branch: 'feature_conflict', author: current_user, - target_project: public_project, source_project: forked_project - ) + target_project: public_project, source_project: forked_project) end let!(:other_merge_request) do create(:merge_request, source_branch: 'fix', - target_project: project, source_project: project - ) + source_project: project, + author: create(:user)) end before do diff --git a/spec/features/merge_request/user_awards_emoji_spec.rb b/spec/features/merge_request/user_awards_emoji_spec.rb index 15a0878fb16..2f24cfbd9e3 100644 --- a/spec/features/merge_request/user_awards_emoji_spec.rb +++ b/spec/features/merge_request/user_awards_emoji_spec.rb @@ -3,7 +3,7 @@ require 'rails_helper' describe 'Merge request > User awards emoji', :js do let(:project) { create(:project, :public, :repository) } let(:user) { project.creator } - let(:merge_request) { create(:merge_request, source_project: project) } + let(:merge_request) { create(:merge_request, source_project: project, author: create(:user)) } describe 'logged in' do before do diff --git a/spec/lib/gitlab/slash_commands/issue_search_spec.rb b/spec/lib/gitlab/slash_commands/issue_search_spec.rb index e41e5254dde..35d01efc1bd 100644 --- a/spec/lib/gitlab/slash_commands/issue_search_spec.rb +++ b/spec/lib/gitlab/slash_commands/issue_search_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::SlashCommands::IssueSearch do let!(:issue) { create(:issue, project: project, title: 'find me') } let!(:confidential) { create(:issue, :confidential, project: project, title: 'mepmep find') } let(:project) { create(:project) } - let(:user) { issue.author } + let(:user) { create(:user) } let(:regex_match) { described_class.match("issue search find") } subject do diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index f2593a1a75c..129344f105f 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -92,7 +92,7 @@ describe ProjectPolicy do it 'does not include the read_issue permission when the issue author is not a member of the private project' do project = create(:project, :private) - issue = create(:issue, project: project) + issue = create(:issue, project: project, author: create(:user)) user = issue.author expect(project.team.member?(issue.author)).to be false diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index 8897a64a138..47c1ebbeb81 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -4,7 +4,7 @@ describe Issues::CloseService do let(:user) { create(:user) } let(:user2) { create(:user) } let(:guest) { create(:user) } - let(:issue) { create(:issue, assignees: [user2]) } + let(:issue) { create(:issue, assignees: [user2], author: create(:user)) } let(:project) { issue.project } let!(:todo) { create(:todo, :assigned, user: user, project: project, target: issue, author: user2) } diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 1cb6f2e097f..41237dd7160 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -13,7 +13,8 @@ describe Issues::UpdateService, :mailer do create(:issue, title: 'Old title', description: "for #{user2.to_reference}", assignee_ids: [user3.id], - project: project) + project: project, + author: create(:user)) end before do diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index 4d12de3ecce..216e0cd4266 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -4,7 +4,7 @@ describe MergeRequests::CloseService do let(:user) { create(:user) } let(:user2) { create(:user) } let(:guest) { create(:user) } - let(:merge_request) { create(:merge_request, assignee: user2) } + let(:merge_request) { create(:merge_request, assignee: user2, author: create(:user)) } let(:project) { merge_request.project } let!(:todo) { create(:todo, :assigned, user: user, project: project, target: merge_request, author: user2) } diff --git a/spec/services/merge_requests/ff_merge_service_spec.rb b/spec/services/merge_requests/ff_merge_service_spec.rb index aa90feeef89..5ef6365fcc9 100644 --- a/spec/services/merge_requests/ff_merge_service_spec.rb +++ b/spec/services/merge_requests/ff_merge_service_spec.rb @@ -7,7 +7,8 @@ describe MergeRequests::FfMergeService do create(:merge_request, source_branch: 'flatten-dir', target_branch: 'improve/awesome', - assignee: user2) + assignee: user2, + author: create(:user)) end let(:project) { merge_request.project } diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb index a44d63e5f9f..9ee37c51d95 100644 --- a/spec/services/merge_requests/reopen_service_spec.rb +++ b/spec/services/merge_requests/reopen_service_spec.rb @@ -4,7 +4,7 @@ describe MergeRequests::ReopenService do let(:user) { create(:user) } let(:user2) { create(:user) } let(:guest) { create(:user) } - let(:merge_request) { create(:merge_request, :closed, assignee: user2) } + let(:merge_request) { create(:merge_request, :closed, assignee: user2, author: create(:user)) } let(:project) { merge_request.project } before do diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 2238da2d14d..c31259239ee 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -12,7 +12,8 @@ describe MergeRequests::UpdateService, :mailer do create(:merge_request, :simple, title: 'Old title', description: "FYI #{user2.to_reference}", assignee_id: user3.id, - source_project: project) + source_project: project, + author: create(:user)) end before do diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 5c59455e3e1..35eb84e5e88 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -458,7 +458,7 @@ describe NotificationService, :mailer do context "merge request diff note" do let(:project) { create(:project, :repository) } let(:user) { create(:user) } - let(:merge_request) { create(:merge_request, source_project: project, assignee: user) } + let(:merge_request) { create(:merge_request, source_project: project, assignee: user, author: create(:user)) } let(:note) { create(:diff_note_on_merge_request, project: project, noteable: merge_request) } before do @@ -469,11 +469,13 @@ describe NotificationService, :mailer do describe '#new_note' do it "records sent notifications" do - # Ensure create SentNotification by noteable = merge_request 6 times, not noteable = note + # 3 SentNotification are sent: the MR assignee and author, and the @u_watcher expect(SentNotification).to receive(:record_note).with(note, any_args).exactly(3).times.and_call_original notification.new_note(note) + expect(SentNotification.last(3).map(&:recipient).map(&:id)) + .to contain_exactly(merge_request.assignee.id, merge_request.author.id, @u_watcher.id) expect(SentNotification.last.in_reply_to_discussion_id).to eq(note.discussion_id) end end From c6311207eef6cf66f77d410269efc7581288cb2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 31 Jan 2018 18:30:31 +0100 Subject: [PATCH 21/38] Fix a JSON schema that doesn't include enough fields MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/fixtures/api/schemas/public_api/v4/user/basic.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/fixtures/api/schemas/public_api/v4/user/basic.json b/spec/fixtures/api/schemas/public_api/v4/user/basic.json index bf330d8278c..2d815be32a6 100644 --- a/spec/fixtures/api/schemas/public_api/v4/user/basic.json +++ b/spec/fixtures/api/schemas/public_api/v4/user/basic.json @@ -2,12 +2,16 @@ "type": ["object", "null"], "required": [ "id", + "name", + "username", "state", "avatar_url", "web_url" ], "properties": { "id": { "type": "integer" }, + "name": { "type": "string" }, + "username": { "type": "string" }, "state": { "type": "string" }, "avatar_url": { "type": "string" }, "web_url": { "type": "string" } From 73582d9fe786364d585768e1766a29b342b34b33 Mon Sep 17 00:00:00 2001 From: James Ramsay Date: Wed, 31 Jan 2018 14:32:35 -0500 Subject: [PATCH 22/38] Fix subgroup creation docs --- .../subgroups/img/create_subgroup_button.png | Bin 8402 -> 11161 bytes doc/user/group/subgroups/index.md | 3 ++- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/user/group/subgroups/img/create_subgroup_button.png b/doc/user/group/subgroups/img/create_subgroup_button.png index 000b54c2855e972734a8a7e80164898072b1f642..d1355d4b2c3187a50e3fb3aa314f0829fb7097c0 100644 GIT binary patch literal 11161 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 G)BgZ@31w^m diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md index 161a3af9903..2a982344e5f 100644 --- a/doc/user/group/subgroups/index.md +++ b/doc/user/group/subgroups/index.md @@ -90,7 +90,8 @@ structure. To create a subgroup: -1. In the group's dashboard go to the **Subgroups** page and click **New subgroup**. +1. In the group's dashboard expand the **New project** split button, select + **New subgroup** and click the **New subgroup** button. ![Subgroups page](img/create_subgroup_button.png) From 10a2d95a7b9627a47a338d68c24eb936bc9da01b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 31 Jan 2018 21:53:40 +0000 Subject: [PATCH 23/38] Remove namespaced internationalization import --- app/assets/javascripts/awards_handler.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index d9341837149..87109a802e5 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -1,7 +1,7 @@ /* eslint-disable class-methods-use-this */ import _ from 'underscore'; import Cookies from 'js-cookie'; -import { s__ } from './locale'; +import { __ } from './locale'; import { isInIssuePage, updateTooltipTitle } from './lib/utils/common_utils'; import flash from './flash'; import axios from './lib/utils/axios_utils'; @@ -451,7 +451,7 @@ class AwardsHandler { callback(); } }) - .catch(() => flash(s__('Something went wrong on our end.'))); + .catch(() => flash(__('Something went wrong on our end.'))); } } From 9709b5ed1471f70186a95812e2fc0a8537f5eaa3 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 31 Jan 2018 15:56:26 -0600 Subject: [PATCH 24/38] Replace $.ajax in activity calendar with axios --- .../javascripts/users/activity_calendar.js | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/app/assets/javascripts/users/activity_calendar.js b/app/assets/javascripts/users/activity_calendar.js index 0ca54faa71c..57306322aa4 100644 --- a/app/assets/javascripts/users/activity_calendar.js +++ b/app/assets/javascripts/users/activity_calendar.js @@ -1,7 +1,10 @@ import _ from 'underscore'; import { scaleLinear, scaleThreshold } from 'd3-scale'; import { select } from 'd3-selection'; -import { getDayName, getDayDifference } from '../lib/utils/datetime_utility'; +import { getDayName, getDayDifference } from '~/lib/utils/datetime_utility'; +import axios from '~/lib/utils/axios_utils'; +import flash from '~/flash'; +import { __ } from '~/locale'; const d3 = { select, scaleLinear, scaleThreshold }; @@ -221,14 +224,16 @@ export default class ActivityCalendar { this.currentSelectedDate.getDate(), ].join('-'); - $.ajax({ - url: this.calendarActivitiesPath, - data: { date }, - cache: false, - dataType: 'html', - beforeSend: () => $('.user-calendar-activities').html(LOADING_HTML), - success: data => $('.user-calendar-activities').html(data), - }); + $('.user-calendar-activities').html(LOADING_HTML); + + axios.get(this.calendarActivitiesPath, { + params: { + date, + }, + responseType: 'text', + }) + .then(({ data }) => $('.user-calendar-activities').html(data)) + .catch(() => flash(__('An error occurred while retrieving calendar activity'))); } else { this.currentSelectedDate = ''; $('.user-calendar-activities').html(''); From b8c43a73d3be399271d499800f3ee8e179738788 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Wed, 31 Jan 2018 16:27:03 -0600 Subject: [PATCH 25/38] Replace $.ajax in find file with axios --- app/assets/javascripts/project_find_file.js | 24 ++++++++++----------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js index 0da32b4a3cc..586d188350f 100644 --- a/app/assets/javascripts/project_find_file.js +++ b/app/assets/javascripts/project_find_file.js @@ -1,6 +1,9 @@ /* eslint-disable func-names, space-before-function-paren, no-var, prefer-rest-params, wrap-iife, quotes, consistent-return, one-var, one-var-declaration-per-line, no-cond-assign, max-len, object-shorthand, no-param-reassign, comma-dangle, prefer-template, no-unused-vars, no-return-assign */ import fuzzaldrinPlus from 'fuzzaldrin-plus'; +import axios from '~/lib/utils/axios_utils'; +import flash from '~/flash'; +import { __ } from '~/locale'; // highlight text(awefwbwgtc -> awefwbwgtc ) const highlighter = function(element, text, matches) { @@ -72,19 +75,14 @@ export default class ProjectFindFile { // files pathes load load(url) { - return $.ajax({ - url: url, - method: "get", - dataType: "json", - success: (function(_this) { - return function(data) { - _this.element.find(".loading").hide(); - _this.filePaths = data; - _this.findFile(); - return _this.element.find(".files-slider tr.tree-item").eq(0).addClass("selected").focus(); - }; - })(this) - }); + axios.get(url) + .then(({ data }) => { + this.element.find('.loading').hide(); + this.filePaths = data; + this.findFile(); + this.element.find('.files-slider tr.tree-item').eq(0).addClass('selected').focus(); + }) + .catch(() => flash(__('An error occurred while loading filenames'))); } // render result From 9291048fc0b4afe228ccf3b59b8260f6ddd09005 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 31 Jan 2018 20:20:38 -0800 Subject: [PATCH 26/38] Remove grpc and google-protobuf platform-specific version markers in Gemfile.lock Closes gitlab-org/omnibus-gitlab#3128 --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index f770a7019e7..2ddf8221a06 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -340,7 +340,7 @@ GEM mime-types (~> 3.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) - google-protobuf (3.5.1.1-universal-darwin) + google-protobuf (3.5.1.1) googleapis-common-protos-types (1.0.1) google-protobuf (~> 3.0) googleauth (0.5.3) @@ -369,7 +369,7 @@ GEM rake grape_logging (1.7.0) grape - grpc (1.8.3-universal-darwin) + grpc (1.8.3) google-protobuf (~> 3.1) googleapis-common-protos-types (~> 1.0.0) googleauth (>= 0.5.1, < 0.7) From 7f0ebeff1affcd4f5155790cc5a5884b052695af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarka=20Kadlecova=CC=81?= Date: Wed, 24 Jan 2018 07:06:24 +0100 Subject: [PATCH 27/38] Include subgroup issuables on the group page --- .../concerns/issuable_collections.rb | 1 + app/finders/group_projects_finder.rb | 11 ++- app/finders/issuable_finder.rb | 4 +- changelogs/unreleased/30106-group-issues.yml | 5 ++ spec/features/groups/issues_spec.rb | 65 ++++++++++------ spec/finders/group_projects_finder_spec.rb | 76 +++++++++++++++++-- spec/finders/issues_finder_spec.rb | 44 ++++++++--- spec/finders/merge_requests_finder_spec.rb | 41 +++++++--- 8 files changed, 198 insertions(+), 49 deletions(-) create mode 100644 changelogs/unreleased/30106-group-issues.yml diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb index b25e753a5ad..755e324a53f 100644 --- a/app/controllers/concerns/issuable_collections.rb +++ b/app/controllers/concerns/issuable_collections.rb @@ -84,6 +84,7 @@ module IssuableCollections @filter_params[:project_id] = @project.id elsif @group @filter_params[:group_id] = @group.id + @filter_params[:include_subgroups] = true else # TODO: this filter ignore issues/mr created in public or # internal repos where you are not a member. Enable this filter diff --git a/app/finders/group_projects_finder.rb b/app/finders/group_projects_finder.rb index f2d3b90b8e2..f73cf8adb4d 100644 --- a/app/finders/group_projects_finder.rb +++ b/app/finders/group_projects_finder.rb @@ -87,8 +87,17 @@ class GroupProjectsFinder < ProjectsFinder options.fetch(:only_shared, false) end + # subgroups are supported only for owned projects not for shared + def include_subgroups? + options.fetch(:include_subgroups, false) + end + def owned_projects - group.projects + if include_subgroups? + Project.where(namespace_id: group.self_and_descendants.select(:id)) + else + group.projects + end end def shared_projects diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 493e7985d75..0fe3000ca01 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -43,6 +43,7 @@ class IssuableFinder search sort state + include_subgroups ].freeze ARRAY_PARAMS = { label_name: [], iids: [], assignee_username: [] }.freeze @@ -148,7 +149,8 @@ class IssuableFinder if current_user && params[:authorized_only].presence && !current_user_related? current_user.authorized_projects elsif group - GroupProjectsFinder.new(group: group, current_user: current_user).execute + finder_options = { include_subgroups: params[:include_subgroups], only_owned: true } + GroupProjectsFinder.new(group: group, current_user: current_user, options: finder_options).execute else ProjectsFinder.new(current_user: current_user, project_ids_relation: item_project_ids(items)).execute end diff --git a/changelogs/unreleased/30106-group-issues.yml b/changelogs/unreleased/30106-group-issues.yml new file mode 100644 index 00000000000..d24996e6087 --- /dev/null +++ b/changelogs/unreleased/30106-group-issues.yml @@ -0,0 +1,5 @@ +--- +title: Include subgroup issues and merge requests on the group page +merge_request: +author: +type: changed diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb index cdf7aceb13c..450bc0ff8cf 100644 --- a/spec/features/groups/issues_spec.rb +++ b/spec/features/groups/issues_spec.rb @@ -3,40 +3,61 @@ require 'spec_helper' feature 'Group issues page' do include FilteredSearchHelpers - let(:path) { issues_group_path(group) } - let(:issuable) { create(:issue, project: project, title: "this is my created issuable")} + context 'with shared examples' do + let(:path) { issues_group_path(group) } + let(:issuable) { create(:issue, project: project, title: "this is my created issuable")} - include_examples 'project features apply to issuables', Issue + include_examples 'project features apply to issuables', Issue - context 'rss feed' do - let(:access_level) { ProjectFeature::ENABLED } + context 'rss feed' do + let(:access_level) { ProjectFeature::ENABLED } - context 'when signed in' do - let(:user) { user_in_group } + context 'when signed in' do + let(:user) { user_in_group } - it_behaves_like "it has an RSS button with current_user's RSS token" - it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" + it_behaves_like "it has an RSS button with current_user's RSS token" + it_behaves_like "an autodiscoverable RSS feed with current_user's RSS token" + end + + context 'when signed out' do + let(:user) { nil } + + it_behaves_like "it has an RSS button without an RSS token" + it_behaves_like "an autodiscoverable RSS feed without an RSS token" + end end - context 'when signed out' do - let(:user) { nil } + context 'assignee', :js do + let(:access_level) { ProjectFeature::ENABLED } + let(:user) { user_in_group } + let(:user2) { user_outside_group } + let(:path) { issues_group_path(group) } - it_behaves_like "it has an RSS button without an RSS token" - it_behaves_like "an autodiscoverable RSS feed without an RSS token" + it 'filters by only group users' do + filtered_search.set('assignee:') + + expect(find('#js-dropdown-assignee .filter-dropdown')).to have_content(user.name) + expect(find('#js-dropdown-assignee .filter-dropdown')).not_to have_content(user2.name) + end end end - context 'assignee', :js do - let(:access_level) { ProjectFeature::ENABLED } - let(:user) { user_in_group } - let(:user2) { user_outside_group } - let(:path) { issues_group_path(group) } + context 'issues list', :nested_groups do + let(:group) { create(:group)} + let(:subgroup) { create(:group, parent: group) } + let(:project) { create(:project, :public, group: group)} + let(:subgroup_project) { create(:project, :public, group: subgroup)} + let!(:issue) { create(:issue, project: project, title: 'root group issue') } + let!(:subgroup_issue) { create(:issue, project: subgroup_project, title: 'subgroup issue') } - it 'filters by only group users' do - filtered_search.set('assignee:') + it 'returns all group and subgroup issues' do + visit issues_group_path(group) - expect(find('#js-dropdown-assignee .filter-dropdown')).to have_content(user.name) - expect(find('#js-dropdown-assignee .filter-dropdown')).not_to have_content(user2.name) + page.within('.issuable-list') do + expect(page).to have_selector('li.issue', count: 2) + expect(page).to have_content('root group issue') + expect(page).to have_content('subgroup issue') + end end end end diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb index 27a09d7c6f5..be80ee7d767 100644 --- a/spec/finders/group_projects_finder_spec.rb +++ b/spec/finders/group_projects_finder_spec.rb @@ -2,6 +2,7 @@ require 'spec_helper' describe GroupProjectsFinder do let(:group) { create(:group) } + let(:subgroup) { create(:group, parent: group) } let(:current_user) { create(:user) } let(:options) { {} } @@ -12,6 +13,8 @@ describe GroupProjectsFinder do let!(:shared_project_1) { create(:project, :public, path: '3') } let!(:shared_project_2) { create(:project, :private, path: '4') } let!(:shared_project_3) { create(:project, :internal, path: '5') } + let!(:subgroup_project) { create(:project, :public, path: '6', group: subgroup) } + let!(:subgroup_private_project) { create(:project, :private, path: '7', group: subgroup) } before do shared_project_1.project_group_links.create(group_access: Gitlab::Access::MASTER, group: group) @@ -35,11 +38,31 @@ describe GroupProjectsFinder do context "only owned" do let(:options) { { only_owned: true } } - it { is_expected.to match_array([private_project, public_project]) } + context 'with subgroups projects', :nested_groups do + before do + options[:include_subgroups] = true + end + + it { is_expected.to match_array([private_project, public_project, subgroup_project, subgroup_private_project]) } + end + + context 'without subgroups projects' do + it { is_expected.to match_array([private_project, public_project]) } + end end context "all" do - it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) } + context 'with subgroups projects', :nested_groups do + before do + options[:include_subgroups] = true + end + + it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project, subgroup_project, subgroup_private_project]) } + end + + context 'without subgroups projects' do + it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) } + end end end @@ -71,9 +94,20 @@ describe GroupProjectsFinder do context "without external user" do before do private_project.add_master(current_user) + subgroup_private_project.add_master(current_user) end - it { is_expected.to match_array([private_project, public_project]) } + context 'with subgroups projects', :nested_groups do + before do + options[:include_subgroups] = true + end + + it { is_expected.to match_array([private_project, public_project, subgroup_project, subgroup_private_project]) } + end + + context 'without subgroups projects' do + it { is_expected.to match_array([private_project, public_project]) } + end end context "with external user" do @@ -81,12 +115,32 @@ describe GroupProjectsFinder do current_user.update_attributes(external: true) end - it { is_expected.to eq([public_project]) } + context 'with subgroups projects', :nested_groups do + before do + options[:include_subgroups] = true + end + + it { is_expected.to match_array([public_project, subgroup_project]) } + end + + context 'without subgroups projects' do + it { is_expected.to eq([public_project]) } + end end end context "all" do - it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, public_project]) } + context 'with subgroups projects', :nested_groups do + before do + options[:include_subgroups] = true + end + + it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, public_project, subgroup_project]) } + end + + context 'without subgroups projects' do + it { is_expected.to match_array([shared_project_3, shared_project_2, shared_project_1, public_project]) } + end end end @@ -100,7 +154,17 @@ describe GroupProjectsFinder do context "only owned" do let(:options) { { only_owned: true } } - it { is_expected.to eq([public_project]) } + context 'with subgroups projects', :nested_groups do + before do + options[:include_subgroups] = true + end + + it { is_expected.to match_array([public_project, subgroup_project]) } + end + + context 'without subgroups projects' do + it { is_expected.to eq([public_project]) } + end end end end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 47fd98234f9..abb7631d7d7 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -3,13 +3,17 @@ require 'spec_helper' describe IssuesFinder do set(:user) { create(:user) } set(:user2) { create(:user) } - set(:project1) { create(:project) } + set(:group) { create(:group) } + set(:subgroup) { create(:group, parent: group) } + set(:project1) { create(:project, group: group) } set(:project2) { create(:project) } + set(:project3) { create(:project, group: subgroup) } set(:milestone) { create(:milestone, project: project1) } set(:label) { create(:label, project: project2) } set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago) } set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab') } set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 1.week.from_now) } + set(:issue4) { create(:issue, project: project3) } set(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) } set(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) } set(:award_emoji3) { create(:award_emoji, name: 'thumbsdown', user: user, awardable: issue3) } @@ -25,10 +29,12 @@ describe IssuesFinder do project1.add_master(user) project2.add_developer(user) project2.add_developer(user2) + project3.add_developer(user) issue1 issue2 issue3 + issue4 award_emoji1 award_emoji2 @@ -39,7 +45,7 @@ describe IssuesFinder do let(:scope) { 'all' } it 'returns all issues' do - expect(issues).to contain_exactly(issue1, issue2, issue3) + expect(issues).to contain_exactly(issue1, issue2, issue3, issue4) end context 'filtering by assignee ID' do @@ -50,6 +56,26 @@ describe IssuesFinder do end end + context 'filtering by group_id' do + let(:params) { { group_id: group.id } } + + context 'when include_subgroup param not set' do + it 'returns all group issues' do + expect(issues).to contain_exactly(issue1) + end + end + + context 'when include_subgroup param is true', :nested_groups do + before do + params[:include_subgroups] = true + end + + it 'returns all group and subgroup issues' do + expect(issues).to contain_exactly(issue1, issue4) + end + end + end + context 'filtering by author ID' do let(:params) { { author_id: user2.id } } @@ -87,7 +113,7 @@ describe IssuesFinder do let(:params) { { milestone_title: Milestone::None.title } } it 'returns issues with no milestone' do - expect(issues).to contain_exactly(issue2, issue3) + expect(issues).to contain_exactly(issue2, issue3, issue4) end end @@ -185,7 +211,7 @@ describe IssuesFinder do let(:params) { { label_name: Label::None.title } } it 'returns issues with no labels' do - expect(issues).to contain_exactly(issue1, issue3) + expect(issues).to contain_exactly(issue1, issue3, issue4) end end @@ -210,7 +236,7 @@ describe IssuesFinder do let(:params) { { state: 'opened' } } it 'returns only opened issues' do - expect(issues).to contain_exactly(issue1, issue2, issue3) + expect(issues).to contain_exactly(issue1, issue2, issue3, issue4) end end @@ -226,7 +252,7 @@ describe IssuesFinder do let(:params) { { state: 'all' } } it 'returns all issues' do - expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue) + expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue, issue4) end end @@ -234,7 +260,7 @@ describe IssuesFinder do let(:params) { { state: 'invalid_state' } } it 'returns all issues' do - expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue) + expect(issues).to contain_exactly(issue1, issue2, issue3, closed_issue, issue4) end end end @@ -338,7 +364,7 @@ describe IssuesFinder do end it "doesn't return issues if feature disabled" do - [project1, project2].each do |project| + [project1, project2, project3].each do |project| project.project_feature.update!(issues_access_level: ProjectFeature::DISABLED) end @@ -351,7 +377,7 @@ describe IssuesFinder do it 'returns the number of rows for the default state' do finder = described_class.new(user) - expect(finder.row_count).to eq(3) + expect(finder.row_count).to eq(4) end it 'returns the number of rows for a given state' do diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 687ffaec7cc..9385c892c9e 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -6,31 +6,36 @@ describe MergeRequestsFinder do let(:user) { create :user } let(:user2) { create :user } - let(:project1) { create(:project, :public) } + let(:group) { create(:group) } + let(:subgroup) { create(:group, parent: group) } + let(:project1) { create(:project, :public, group: group) } let(:project2) { fork_project(project1, user) } let(:project3) do p = fork_project(project1, user) p.update!(archived: true) p end + let(:project4) { create(:project, :public, group: subgroup) } let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) } let!(:merge_request2) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1, state: 'closed') } let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2) } let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3) } + let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4) } before do project1.add_master(user) project2.add_developer(user) project3.add_developer(user) project2.add_developer(user2) + project4.add_developer(user) end describe "#execute" do it 'filters by scope' do params = { scope: 'authored', state: 'opened' } merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(3) + expect(merge_requests.size).to eq(4) end it 'filters by project' do @@ -39,10 +44,26 @@ describe MergeRequestsFinder do expect(merge_requests.size).to eq(1) end + it 'filters by group' do + params = { group_id: group.id } + + merge_requests = described_class.new(user, params).execute + + expect(merge_requests.size).to eq(2) + end + + it 'filters by group including subgroups', :nested_groups do + params = { group_id: group.id, include_subgroups: true } + + merge_requests = described_class.new(user, params).execute + + expect(merge_requests.size).to eq(3) + end + it 'filters by non_archived' do params = { non_archived: true } merge_requests = described_class.new(user, params).execute - expect(merge_requests.size).to eq(3) + expect(merge_requests.size).to eq(4) end it 'filters by iid' do @@ -73,14 +94,14 @@ describe MergeRequestsFinder do end context 'with created_after and created_before params' do - let(:project4) { create(:project, forked_from_project: project1) } + let(:new_project) { create(:project, forked_from_project: project1) } let!(:new_merge_request) do create(:merge_request, :simple, author: user, created_at: 1.week.from_now, - source_project: project4, + source_project: new_project, target_project: project1) end @@ -89,12 +110,12 @@ describe MergeRequestsFinder do :simple, author: user, created_at: 1.week.ago, - source_project: project4, - target_project: project4) + source_project: new_project, + target_project: new_project) end before do - project4.add_master(user) + new_project.add_master(user) end it 'filters by created_after' do @@ -106,7 +127,7 @@ describe MergeRequestsFinder do end it 'filters by created_before' do - params = { project_id: project4.id, created_before: old_merge_request.created_at + 1.second } + params = { project_id: new_project.id, created_before: old_merge_request.created_at + 1.second } merge_requests = described_class.new(user, params).execute @@ -119,7 +140,7 @@ describe MergeRequestsFinder do it 'returns the number of rows for the default state' do finder = described_class.new(user) - expect(finder.row_count).to eq(3) + expect(finder.row_count).to eq(4) end it 'returns the number of rows for a given state' do From b5b304466fbd8904196afeaa65adeb5282b21987 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Feb 2018 10:34:12 +0000 Subject: [PATCH 28/38] Converted mini_pipeline_graph_dropdown.js to axios --- .../mini_pipeline_graph_dropdown.js | 26 +++--- .../mini_pipeline_graph_dropdown_spec.js | 79 ++++++++++++------- 2 files changed, 61 insertions(+), 44 deletions(-) diff --git a/app/assets/javascripts/mini_pipeline_graph_dropdown.js b/app/assets/javascripts/mini_pipeline_graph_dropdown.js index ca3d271663b..c7bccd483ac 100644 --- a/app/assets/javascripts/mini_pipeline_graph_dropdown.js +++ b/app/assets/javascripts/mini_pipeline_graph_dropdown.js @@ -1,5 +1,6 @@ /* eslint-disable no-new */ -import Flash from './flash'; +import flash from './flash'; +import axios from './lib/utils/axios_utils'; /** * In each pipelines table we have a mini pipeline graph for each pipeline. @@ -78,27 +79,22 @@ export default class MiniPipelineGraph { const button = e.relatedTarget; const endpoint = button.dataset.stageEndpoint; - return $.ajax({ - dataType: 'json', - type: 'GET', - url: endpoint, - beforeSend: () => { - this.renderBuildsList(button, ''); - this.toggleLoading(button); - }, - success: (data) => { + this.renderBuildsList(button, ''); + this.toggleLoading(button); + + axios.get(endpoint) + .then(({ data }) => { this.toggleLoading(button); this.renderBuildsList(button, data.html); this.stopDropdownClickPropagation(); - }, - error: () => { + }) + .catch(() => { this.toggleLoading(button); if ($(button).parent().hasClass('open')) { $(button).dropdown('toggle'); } - new Flash('An error occurred while fetching the builds.', 'alert'); - }, - }); + flash('An error occurred while fetching the builds.', 'alert'); + }); } /** diff --git a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js index 481b46c3ac6..6fa6f44f953 100644 --- a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js +++ b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js @@ -1,7 +1,9 @@ /* eslint-disable no-new */ +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; -import '~/flash'; +import timeoutPromise from './helpers/set_timeout_promise_helper'; describe('Mini Pipeline Graph Dropdown', () => { preloadFixtures('static/mini_dropdown_graph.html.raw'); @@ -27,6 +29,16 @@ describe('Mini Pipeline Graph Dropdown', () => { }); describe('When dropdown is clicked', () => { + let mock; + + beforeEach(() => { + mock = new MockAdapter(axios); + }); + + afterEach(() => { + mock.restore(); + }); + it('should call getBuildsList', () => { const getBuildsListSpy = spyOn( MiniPipelineGraph.prototype, @@ -41,46 +53,55 @@ describe('Mini Pipeline Graph Dropdown', () => { }); it('should make a request to the endpoint provided in the html', () => { - const ajaxSpy = spyOn($, 'ajax').and.callFake(function () {}); + const ajaxSpy = spyOn(axios, 'get').and.callThrough(); + + mock.onGet('foobar').reply(200, { + html: '', + }); new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents(); document.querySelector('.js-builds-dropdown-button').click(); - expect(ajaxSpy.calls.allArgs()[0][0].url).toEqual('foobar'); + expect(ajaxSpy.calls.allArgs()[0][0]).toEqual('foobar'); }); - it('should not close when user uses cmd/ctrl + click', () => { - spyOn($, 'ajax').and.callFake(function (params) { - params.success({ - html: `
  • - - - build - - -
  • `, - }); + it('should not close when user uses cmd/ctrl + click', (done) => { + mock.onGet('foobar').reply(200, { + html: `
  • + + + build + + +
  • `, }); new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents(); document.querySelector('.js-builds-dropdown-button').click(); - document.querySelector('a.mini-pipeline-graph-dropdown-item').click(); + timeoutPromise() + .then(() => { + document.querySelector('a.mini-pipeline-graph-dropdown-item').click(); + }) + .then(timeoutPromise) + .then(() => { + expect($('.js-builds-dropdown-list').is(':visible')).toEqual(true); + }) + .then(done) + .catch(done.fail); + }); - expect($('.js-builds-dropdown-list').is(':visible')).toEqual(true); + it('should close the dropdown when request returns an error', (done) => { + mock.onGet('foobar').networkError(); + + new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents(); + + document.querySelector('.js-builds-dropdown-button').click(); + + setTimeout(() => { + expect($('.js-builds-dropdown-tests .dropdown').hasClass('open')).toEqual(false); + done(); + }); }); }); - - it('should close the dropdown when request returns an error', (done) => { - spyOn($, 'ajax').and.callFake(options => options.error()); - - new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents(); - - document.querySelector('.js-builds-dropdown-button').click(); - - setTimeout(() => { - expect($('.js-builds-dropdown-tests .dropdown').hasClass('open')).toEqual(false); - done(); - }, 0); - }); }); From c09a89c7557a93b728bab9eef9175abb1fd458d4 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Feb 2018 10:42:49 +0000 Subject: [PATCH 29/38] Converted branch_graph.js to axios --- app/assets/javascripts/network/branch_graph.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js index 5aad3908eb6..d3edcb724f1 100644 --- a/app/assets/javascripts/network/branch_graph.js +++ b/app/assets/javascripts/network/branch_graph.js @@ -1,5 +1,8 @@ /* eslint-disable func-names, space-before-function-paren, no-var, wrap-iife, quotes, comma-dangle, one-var, one-var-declaration-per-line, no-mixed-operators, no-loop-func, no-floating-decimal, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase, max-len */ +import { __ } from '../locale'; +import axios from '../lib/utils/axios_utils'; +import flash from '../flash'; import Raphael from './raphael'; export default (function() { @@ -26,16 +29,13 @@ export default (function() { } BranchGraph.prototype.load = function() { - return $.ajax({ - url: this.options.url, - method: "get", - dataType: "json", - success: $.proxy(function(data) { + axios.get(this.options.url) + .then(({ data }) => { $(".loading", this.element).hide(); this.prepareData(data.days, data.commits); - return this.buildGraph(); - }, this) - }); + this.buildGraph(); + }) + .catch(() => __('Error fetching network graph.')); }; BranchGraph.prototype.prepareData = function(days, commits) { From 1c8553f21e1cbfa730f47ea8c0be4080a9238f89 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Feb 2018 10:50:18 +0000 Subject: [PATCH 30/38] Converted notes.js to axios --- app/assets/javascripts/notes.js | 35 ++++++++++++++------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index a2b8e6f6495..bcb342f407f 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -16,6 +16,7 @@ import Autosize from 'autosize'; import 'vendor/jquery.caret'; // required by jquery.atwho import 'vendor/jquery.atwho'; import AjaxCache from '~/lib/utils/ajax_cache'; +import axios from './lib/utils/axios_utils'; import { getLocationHash } from './lib/utils/url_utility'; import Flash from './flash'; import CommentTypeToggle from './comment_type_toggle'; @@ -252,26 +253,20 @@ export default class Notes { return; } this.refreshing = true; - return $.ajax({ - url: this.notes_url, - headers: { 'X-Last-Fetched-At': this.last_fetched_at }, - dataType: 'json', - success: (function(_this) { - return function(data) { - var notes; - notes = data.notes; - _this.last_fetched_at = data.last_fetched_at; - _this.setPollingInterval(data.notes.length); - return $.each(notes, function(i, note) { - _this.renderNote(note); - }); - }; - })(this) - }).always((function(_this) { - return function() { - return _this.refreshing = false; - }; - })(this)); + axios.get(this.notes_url, { + headers: { + 'X-Last-Fetched-At': this.last_fetched_at, + }, + }).then(({ data }) => { + const notes = data.notes; + this.last_fetched_at = data.last_fetched_at; + this.setPollingInterval(data.notes.length); + $.each(notes, (i, note) => this.renderNote(note)); + + this.refreshing = false; + }).catch(() => { + this.refreshing = false; + }); } /** From ab8e3a5595a441eb5b24cd7db5a877b65dcef704 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Feb 2018 10:53:36 +0000 Subject: [PATCH 31/38] Converted notifications_form.js to axios --- app/assets/javascripts/notifications_form.js | 38 ++++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/app/assets/javascripts/notifications_form.js b/app/assets/javascripts/notifications_form.js index 4534360d577..4e0afe13590 100644 --- a/app/assets/javascripts/notifications_form.js +++ b/app/assets/javascripts/notifications_form.js @@ -1,3 +1,7 @@ +import { __ } from './locale'; +import axios from './lib/utils/axios_utils'; +import flash from './flash'; + export default class NotificationsForm { constructor() { this.toggleCheckbox = this.toggleCheckbox.bind(this); @@ -27,24 +31,20 @@ export default class NotificationsForm { saveEvent($checkbox, $parent) { const form = $parent.parents('form:first'); - return $.ajax({ - url: form.attr('action'), - method: form.attr('method'), - dataType: 'json', - data: form.serialize(), - beforeSend: () => { - this.showCheckboxLoadingSpinner($parent); - }, - }).done((data) => { - $checkbox.enable(); - if (data.saved) { - $parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done'); - setTimeout(() => { - $parent.removeClass('is-loading') - .find('.custom-notification-event-loading') - .toggleClass('fa-spin fa-spinner fa-check is-done'); - }, 2000); - } - }); + this.showCheckboxLoadingSpinner($parent); + + axios[form.attr('method')](form.attr('action'), form.serialize()) + .then(({ data }) => { + $checkbox.enable(); + if (data.saved) { + $parent.find('.custom-notification-event-loading').toggleClass('fa-spin fa-spinner fa-check is-done'); + setTimeout(() => { + $parent.removeClass('is-loading') + .find('.custom-notification-event-loading') + .toggleClass('fa-spin fa-spinner fa-check is-done'); + }, 2000); + } + }) + .catch(() => flash(__('There was an error saving your notification settings.'))); } } From ee1c471bad95cb640ea63393954825dd5a68a9e2 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Feb 2018 11:12:37 +0000 Subject: [PATCH 32/38] Converted pager.js to axios --- app/assets/javascripts/pager.js | 33 +++++++------- spec/javascripts/pager_spec.js | 76 ++++++++++++++++++++++++++------- 2 files changed, 77 insertions(+), 32 deletions(-) diff --git a/app/assets/javascripts/pager.js b/app/assets/javascripts/pager.js index 6552a88b606..fd3105b1960 100644 --- a/app/assets/javascripts/pager.js +++ b/app/assets/javascripts/pager.js @@ -1,4 +1,5 @@ import { getParameterByName } from '~/lib/utils/common_utils'; +import axios from './lib/utils/axios_utils'; import { removeParams } from './lib/utils/url_utility'; const ENDLESS_SCROLL_BOTTOM_PX = 400; @@ -22,24 +23,22 @@ export default { getOld() { this.loading.show(); - $.ajax({ - type: 'GET', - url: this.url, - data: `limit=${this.limit}&offset=${this.offset}`, - dataType: 'json', - error: () => this.loading.hide(), - success: (data) => { - this.append(data.count, this.prepareData(data.html)); - this.callback(); - - // keep loading until we've filled the viewport height - if (!this.disable && !this.isScrollable()) { - this.getOld(); - } else { - this.loading.hide(); - } + axios.get(this.url, { + params: { + limit: this.limit, + offset: this.offset, }, - }); + }).then(({ data }) => { + this.append(data.count, this.prepareData(data.html)); + this.callback(); + + // keep loading until we've filled the viewport height + if (!this.disable && !this.isScrollable()) { + this.getOld(); + } else { + this.loading.hide(); + } + }).catch(() => this.loading.hide()); }, append(count, html) { diff --git a/spec/javascripts/pager_spec.js b/spec/javascripts/pager_spec.js index 2fd87754238..fd9b83e3514 100644 --- a/spec/javascripts/pager_spec.js +++ b/spec/javascripts/pager_spec.js @@ -1,5 +1,6 @@ /* global fixture */ - +import MockAdapter from 'axios-mock-adapter'; +import axios from '~/lib/utils/axios_utils'; import * as utils from '~/lib/utils/url_utility'; import Pager from '~/pager'; @@ -9,7 +10,6 @@ describe('pager', () => { beforeEach(() => { setFixtures('
    '); - spyOn($, 'ajax'); }); afterEach(() => { @@ -47,39 +47,85 @@ describe('pager', () => { }); describe('getOld', () => { + const urlRegex = /\/some_list(.*)$/; + let mock; + + function mockSuccess() { + mock.onGet(urlRegex).reply(200, { + count: 20, + html: '', + }); + } + + function mockError() { + mock.onGet(urlRegex).networkError(); + } + beforeEach(() => { setFixtures('
    '); + spyOn(axios, 'get').and.callThrough(); + Pager.init(); + + mock = new MockAdapter(axios); }); - it('shows loader while loading next page', () => { + afterEach(() => { + mock.restore(); + }); + + it('shows loader while loading next page', (done) => { + mockSuccess(); + spyOn(Pager.loading, 'show'); Pager.getOld(); - expect(Pager.loading.show).toHaveBeenCalled(); + + setTimeout(() => { + expect(Pager.loading.show).toHaveBeenCalled(); + + done(); + }); }); - it('hides loader on success', () => { - spyOn($, 'ajax').and.callFake(options => options.success({})); + it('hides loader on success', (done) => { + mockSuccess(); + spyOn(Pager.loading, 'hide'); Pager.getOld(); - expect(Pager.loading.hide).toHaveBeenCalled(); + + setTimeout(() => { + expect(Pager.loading.hide).toHaveBeenCalled(); + + done(); + }); }); - it('hides loader on error', () => { - spyOn($, 'ajax').and.callFake(options => options.error()); + it('hides loader on error', (done) => { + mockError(); + spyOn(Pager.loading, 'hide'); Pager.getOld(); - expect(Pager.loading.hide).toHaveBeenCalled(); + + setTimeout(() => { + expect(Pager.loading.hide).toHaveBeenCalled(); + + done(); + }); }); - it('sends request to url with offset and limit params', () => { - spyOn($, 'ajax'); + it('sends request to url with offset and limit params', (done) => { Pager.offset = 100; Pager.limit = 20; Pager.getOld(); - const [{ data, url }] = $.ajax.calls.argsFor(0); - expect(data).toBe('limit=20&offset=100'); - expect(url).toBe('/some_list'); + + setTimeout(() => { + const [url, params] = $.ajax.calls.argsFor(0); + console.log(url, params); + // expect(data).toBe('limit=20&offset=100'); + // expect(url).toBe('/some_list'); + + done(); + }); }); }); }); From 43f1088f5b086b95a3d5cdd90a33d26bb483cba5 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Feb 2018 11:18:01 +0000 Subject: [PATCH 33/38] Converted usage_ping.js to use axios also removed dependancy on jQuery because it is super simple & not required --- .../pages/admin/cohorts/usage_ping.js | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/pages/admin/cohorts/usage_ping.js b/app/assets/javascripts/pages/admin/cohorts/usage_ping.js index 2389056bd02..914a9661c27 100644 --- a/app/assets/javascripts/pages/admin/cohorts/usage_ping.js +++ b/app/assets/javascripts/pages/admin/cohorts/usage_ping.js @@ -1,12 +1,13 @@ -export default function UsagePing() { - const usageDataUrl = $('.usage-data').data('endpoint'); +import axios from '../../../lib/utils/axios_utils'; +import { __ } from '../../../locale'; +import flash from '../../../flash'; - $.ajax({ - type: 'GET', - url: usageDataUrl, - dataType: 'html', - success(html) { - $('.usage-data').html(html); - }, - }); +export default function UsagePing() { + const el = document.querySelector('.usage-data'); + + axios.get(el.dataset.endpoint, { + responseType: 'text', + }).then(({ data }) => { + el.innerHTML = data; + }).catch(() => flash(__('Error fetching usage ping data.'))); } From 4cea24f89c757dafcba190b46d1f5f866acd50f6 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Feb 2018 11:32:31 +0000 Subject: [PATCH 34/38] Converted todos.js to axios --- .../pages/dashboard/todos/index/todos.js | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js index e976a3d2f1d..b3f6a72fdcb 100644 --- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js +++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js @@ -2,6 +2,9 @@ import { visitUrl } from '~/lib/utils/url_utility'; import UsersSelect from '~/users_select'; import { isMetaClick } from '~/lib/utils/common_utils'; +import { __ } from '../../../../locale'; +import flash from '../../../../flash'; +import axios from '../../../../lib/utils/axios_utils'; export default class Todos { constructor() { @@ -59,18 +62,12 @@ export default class Todos { const target = e.target; target.setAttribute('disabled', true); target.classList.add('disabled'); - $.ajax({ - type: 'POST', - url: target.dataset.href, - dataType: 'json', - data: { - '_method': target.dataset.method, - }, - success: (data) => { + + axios[target.dataset.method](target.dataset.href) + .then(({ data }) => { this.updateRowState(target); - return this.updateBadges(data); - }, - }); + this.updateBadges(data); + }).catch(() => flash(__('Error updating todo status.'))); } updateRowState(target) { @@ -98,19 +95,15 @@ export default class Todos { e.preventDefault(); const target = e.currentTarget; - const requestData = { '_method': target.dataset.method, ids: this.todo_ids }; target.setAttribute('disabled', true); target.classList.add('disabled'); - $.ajax({ - type: 'POST', - url: target.dataset.href, - dataType: 'json', - data: requestData, - success: (data) => { - this.updateAllState(target, data); - return this.updateBadges(data); - }, - }); + + axios[target.dataset.method](target.dataset.href, { + ids: this.todo_ids, + }).then(({ data }) => { + this.updateAllState(target, data); + this.updateBadges(data); + }).catch(() => flash(__('Error updating status for all todos.'))); } updateAllState(target, data) { From fe2e8dba22471eb39a703672c5d8039c20e94fa6 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 1 Feb 2018 12:26:55 +0000 Subject: [PATCH 35/38] fixed infinite loop crashing tests --- spec/javascripts/pager_spec.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/spec/javascripts/pager_spec.js b/spec/javascripts/pager_spec.js index fd9b83e3514..b09494f0b77 100644 --- a/spec/javascripts/pager_spec.js +++ b/spec/javascripts/pager_spec.js @@ -47,12 +47,12 @@ describe('pager', () => { }); describe('getOld', () => { - const urlRegex = /\/some_list(.*)$/; + const urlRegex = /(.*)some_list(.*)$/; let mock; function mockSuccess() { mock.onGet(urlRegex).reply(200, { - count: 20, + count: 0, html: '', }); } @@ -65,9 +65,9 @@ describe('pager', () => { setFixtures('
    '); spyOn(axios, 'get').and.callThrough(); - Pager.init(); - mock = new MockAdapter(axios); + + Pager.init(); }); afterEach(() => { @@ -119,10 +119,15 @@ describe('pager', () => { Pager.getOld(); setTimeout(() => { - const [url, params] = $.ajax.calls.argsFor(0); - console.log(url, params); - // expect(data).toBe('limit=20&offset=100'); - // expect(url).toBe('/some_list'); + const [url, params] = axios.get.calls.argsFor(0); + + expect(params).toEqual({ + params: { + limit: 20, + offset: 100, + }, + }); + expect(url).toBe('/some_list'); done(); }); From a51dea0d43dd9a7995c710558b18da4f2696e284 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 1 Feb 2018 13:37:47 +0100 Subject: [PATCH 36/38] Improve doc/development/automatic_ce_ee_merge.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/development/automatic_ce_ee_merge.md | 29 ++++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/doc/development/automatic_ce_ee_merge.md b/doc/development/automatic_ce_ee_merge.md index 5a784b6de06..cf6314f9521 100644 --- a/doc/development/automatic_ce_ee_merge.md +++ b/doc/development/automatic_ce_ee_merge.md @@ -5,17 +5,32 @@ Enterprise Edition (look for the [`CE Upstream` merge requests]). This merge is done automatically in a [scheduled pipeline](https://gitlab.com/gitlab-org/release-tools/-/jobs/43201679). -If a merge is already in progress, the job [doesn't create a new one](https://gitlab.com/gitlab-org/release-tools/-/jobs/43157687). -**If you are pinged in a `CE Upstream` merge request to resolve a conflict, -please resolve the conflict as soon as possible or ask someone else to do it!** +## What to do if you are pinged in a `CE Upstream` merge request to resolve a conflict? ->**Note:** -It's ok to resolve more conflicts than the one that you are asked to resolve. In -that case, it's a good habit to ask for a double-check on your resolution by -someone who is familiar with the code you touched. +1. Please resolve the conflict as soon as possible or ask someone else to do it + - It's ok to resolve more conflicts than the one that you are asked to resolve. + In that case, it's a good habit to ask for a double-check on your resolution + by someone who is familiar with the code you touched. +1. Once you have resolved your conflicts, push to the branch (no force-push) +1. Assign the merge request to the next person that has to resolve a conflict +1. If all conflicts are resolved after your resolution is pushed, keep the merge + request assigned to you: **you are now responsible for the merge request to be + green** +1. If you need any help, you can ping the current [release managers], or ask in + the `#ce-to-ee` Slack channel + +A few notes about the automatic CE->EE merge job: + +- If a merge is already in progress, the job + [doesn't create a new one](https://gitlab.com/gitlab-org/release-tools/-/jobs/43157687). +- If there is nothing to merge (i.e. EE is up-to-date with CE), the job doesn't + create a new one +- The job posts messages to the `#ce-to-ee` Slack channel to inform what's the + current CE->EE merge status (e.g. "A new MR has been created", "A MR is still pending") [`CE Upstream` merge requests]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests?label_name%5B%5D=CE+upstream +[release managers]: https://about.gitlab.com/release-managers/ ## Always merge EE merge requests before their CE counterparts From 4aedec11922c1794652c342a8cb9767a1ad04756 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 1 Feb 2018 15:44:36 +0100 Subject: [PATCH 37/38] Ban Rugged from Repository --- scripts/lint-rugged | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/lint-rugged b/scripts/lint-rugged index 03f780f880b..cabd083e9f9 100755 --- a/scripts/lint-rugged +++ b/scripts/lint-rugged @@ -1,7 +1,7 @@ #!/usr/bin/env ruby ALLOWED = [ - # Can be deleted (?) once rugged is no longer used in production. Doesn't make Rugged calls. + # Can be fixed once Rugged is no longer used in production. Doesn't make Rugged calls. 'config/initializers/8_metrics.rb', # Can be deleted once wiki's are fully (mandatory) migrated @@ -13,9 +13,6 @@ ALLOWED = [ # Needs to be migrated, https://gitlab.com/gitlab-org/gitaly/issues/954 'lib/tasks/gitlab/cleanup.rake', - # https://gitlab.com/gitlab-org/gitaly/issues/961 - 'app/models/repository.rb', - # The only place where Rugged code is still allowed in production 'lib/gitlab/git/' ].freeze From 1123d9dc460353cbc3b46606cc2235f0433f35e1 Mon Sep 17 00:00:00 2001 From: Jose Ivan Vargas Date: Tue, 23 Jan 2018 12:50:58 -0600 Subject: [PATCH 38/38] Added more tests and corrected typos --- .../javascripts/pages/projects/show/index.js | 1 - .../pages/projects/tree/show/index.js | 35 ++++---- .../commit_pipeline_status_component.vue | 78 ++++++++++------- app/assets/stylesheets/pages/commits.scss | 10 ++- app/views/projects/commits/_commit.html.haml | 2 +- features/project/project.feature | 1 - features/steps/shared/project.rb | 1 - .../commit_pipeline_status_component_spec.js | 85 +++++++++++++------ 8 files changed, 131 insertions(+), 82 deletions(-) diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js index 4c42fda16d7..55154cdddcb 100644 --- a/app/assets/javascripts/pages/projects/show/index.js +++ b/app/assets/javascripts/pages/projects/show/index.js @@ -5,7 +5,6 @@ import TreeView from '~/tree'; import BlobViewer from '~/blob/viewer/index'; import Activities from '~/activities'; import { ajaxGet } from '~/lib/utils/common_utils'; -import commitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue'; import Star from '../../../star'; import notificationsDropdown from '../../../notifications_dropdown'; diff --git a/app/assets/javascripts/pages/projects/tree/show/index.js b/app/assets/javascripts/pages/projects/tree/show/index.js index f14c3f86687..c4b3356e478 100644 --- a/app/assets/javascripts/pages/projects/tree/show/index.js +++ b/app/assets/javascripts/pages/projects/tree/show/index.js @@ -15,24 +15,23 @@ export default () => { ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath)); const commitPipelineStatusEl = document.getElementById('commit-pipeline-status'); - const $statusLink = $('.ci-status-link'); - if ($statusLink.length > 0) { - $statusLink.remove(); + const statusLink = document.querySelector('.commit-actions .ci-status-link'); + if (statusLink != null) { + statusLink.remove(); + // eslint-disable-next-line no-new + new Vue({ + el: commitPipelineStatusEl, + components: { + commitPipelineStatus, + }, + render(createElement) { + return createElement('commit-pipeline-status', { + props: { + endpoint: commitPipelineStatusEl.dataset.endpoint, + }, + }); + }, + }); } - commitPipelineStatusEl.classList.remove('hidden'); - // eslint-disable-next-line no-new - new Vue({ - el: '#commit-pipeline-status', - components: { - commitPipelineStatus, - }, - render(createElement) { - return createElement('commit-pipeline-status', { - props: { - endpoint: commitPipelineStatusEl.dataset.endpoint, - }, - }); - }, - }); }; diff --git a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue index e13acf8555a..63f20a0041d 100644 --- a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue +++ b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue @@ -1,8 +1,10 @@ diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index adfd72556b8..17801ed5910 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -196,6 +196,14 @@ @media (min-width: $screen-sm-min) { font-size: 0; + div { + display: inline; + } + + .fa-spinner { + font-size: 12px; + } + span { font-size: 6px; } @@ -226,7 +234,7 @@ .ci-status-icon { position: relative; - top: 3px; + top: 1px; } } diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index c94e10947e6..90272ad9554 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -51,7 +51,7 @@ - if commit.status(ref) = render_commit_status(commit, ref: ref) - #commit-pipeline-status.hidden{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } } + #commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id) } } = link_to commit.short_id, link, class: "commit-sha btn btn-transparent btn-link" = clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard")) = link_to_browse_code(project, commit) diff --git a/features/project/project.feature b/features/project/project.feature index bcd72c5c5a3..23817ef3ac9 100644 --- a/features/project/project.feature +++ b/features/project/project.feature @@ -23,7 +23,6 @@ Feature: Project And I visit project "Shop" page Then I should see project "Shop" README - @javascript Scenario: I should see last commit with CI Given project "Shop" has CI enabled Given project "Shop" has CI build diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 923d54a6545..affbccccdf9 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -218,7 +218,6 @@ module SharedProject end step 'I should see last commit with CI status' do - sleep 2 page.within ".blob-commit-info" do expect(page).to have_content(project.commit.sha[0..6]) expect(page).to have_link("Commit: skipped") diff --git a/spec/javascripts/commit/commit_pipeline_status_component_spec.js b/spec/javascripts/commit/commit_pipeline_status_component_spec.js index 2a52097e0d5..90f290e845e 100644 --- a/spec/javascripts/commit/commit_pipeline_status_component_spec.js +++ b/spec/javascripts/commit/commit_pipeline_status_component_spec.js @@ -6,7 +6,7 @@ import mountComponent from '../helpers/vue_mount_component_helper'; describe('Commit pipeline status component', () => { let vm; - let component; + let Component; let mock; const mockCiStatus = { details_path: '/root/hello-world/pipelines/1', @@ -19,34 +19,25 @@ describe('Commit pipeline status component', () => { }; beforeEach(() => { - mock = new MockAdapter(axios); - mock.onGet('/dummy/endpoint').reply(() => { - const res = Promise.resolve([200, { - pipelines: [ - { - details: { - stages: [ - { - status: mockCiStatus, - title: 'Commit: canceled', - }, - ], - }, - }, - ], - }]); - return res; - }); - component = Vue.extend(commitPipelineStatus); + Component = Vue.extend(commitPipelineStatus); }); - afterEach(() => { - mock.reset(); - }); - - describe('While polling pipeline data', () => { + describe('While polling pipeline data succesfully', () => { beforeEach(() => { - vm = mountComponent(component, { + mock = new MockAdapter(axios); + mock.onGet('/dummy/endpoint').reply(() => { + const res = Promise.resolve([200, { + pipelines: [ + { + details: { + status: mockCiStatus, + }, + }, + ], + }]); + return res; + }); + vm = mountComponent(Component, { endpoint: '/dummy/endpoint', }); }); @@ -54,18 +45,58 @@ describe('Commit pipeline status component', () => { afterEach(() => { vm.poll.stop(); vm.$destroy(); + mock.restore(); + }); + + it('shows the loading icon when polling is starting', (done) => { + expect(vm.$el.querySelector('.loading-container')).not.toBe(null); + setTimeout(() => { + expect(vm.$el.querySelector('.loading-container')).toBe(null); + done(); + }); }); it('contains a ciStatus when the polling is succesful ', (done) => { setTimeout(() => { expect(vm.ciStatus).toEqual(mockCiStatus); done(); - }, 1000); + }); }); it('contains a ci-status icon when polling is succesful', (done) => { setTimeout(() => { expect(vm.$el.querySelector('.ci-status-icon')).not.toBe(null); + expect(vm.$el.querySelector('.ci-status-icon').classList).toContain(`ci-status-icon-${mockCiStatus.group}`); + done(); + }); + }); + }); + + describe('When polling data was not succesful', () => { + beforeEach(() => { + mock = new MockAdapter(axios); + mock.onGet('/dummy/endpoint').reply(() => { + const res = Promise.reject([502, { }]); + return res; + }); + vm = new Component({ + props: { + endpoint: '/dummy/endpoint', + }, + }); + }); + + afterEach(() => { + vm.poll.stop(); + vm.$destroy(); + mock.restore(); + }); + + it('calls an errorCallback', (done) => { + spyOn(vm, 'errorCallback').and.callThrough(); + vm.$mount(); + setTimeout(() => { + expect(vm.errorCallback.calls.count()).toEqual(1); done(); }); });