diff --git a/app/assets/javascripts/error_tracking/components/error_details.vue b/app/assets/javascripts/error_tracking/components/error_details.vue index ab782f955c6..2000377530b 100644 --- a/app/assets/javascripts/error_tracking/components/error_details.vue +++ b/app/assets/javascripts/error_tracking/components/error_details.vue @@ -62,6 +62,34 @@ export default { showStacktrace() { return Boolean(!this.loadingStacktrace && this.stacktrace && this.stacktrace.length); }, + errorTitle() { + return `${this.error.title}`; + }, + errorUrl() { + return sprintf(__('Sentry event: %{external_url}'), { + external_url: this.error.external_url, + }); + }, + errorFirstSeen() { + return sprintf(__('First seen: %{first_seen}'), { first_seen: this.error.first_seen }); + }, + errorLastSeen() { + return sprintf(__('Last seen: %{last_seen}'), { last_seen: this.error.last_seen }); + }, + errorCount() { + return sprintf(__('Events: %{count}'), { count: this.error.count }); + }, + errorUserCount() { + return sprintf(__('Users: %{user_count}'), { user_count: this.error.user_count }); + }, + issueLink() { + return `${this.issueProjectPath}?issue[title]=${encodeURIComponent( + this.errorTitle, + )}&issue[description]=${encodeURIComponent(this.issueDescription)}`; + }, + issueDescription() { + return `${this.errorUrl}${this.errorFirstSeen}${this.errorLastSeen}${this.errorCount}${this.errorUserCount}`; + }, }, mounted() { this.startPollingDetails(this.issueDetailsPath); @@ -86,7 +114,7 @@ export default {
- + {{ __('Create issue') }}
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 723bcc8cf3d..6d38b58c301 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -205,14 +205,6 @@ module Ci scope :internal, -> { where(source: internal_sources) } scope :ci_sources, -> { where(config_source: ci_sources_values) } - - scope :sort_by_merge_request_pipelines, -> do - sql = 'CASE ci_pipelines.source WHEN (?) THEN 0 ELSE 1 END, ci_pipelines.id DESC' - query = ApplicationRecord.send(:sanitize_sql_array, [sql, sources[:merge_request_event]]) # rubocop:disable GitlabSecurity/PublicSend - - order(Arel.sql(query)) - end - scope :for_user, -> (user) { where(user: user) } scope :for_sha, -> (sha) { where(sha: sha) } scope :for_source_sha, -> (source_sha) { where(source_sha: source_sha) } @@ -221,22 +213,6 @@ module Ci scope :for_id, -> (id) { where(id: id) } scope :created_after, -> (time) { where('ci_pipelines.created_at > ?', time) } - scope :triggered_by_merge_request, -> (merge_request) do - where(source: :merge_request_event, merge_request: merge_request) - end - - scope :detached_merge_request_pipelines, -> (merge_request, sha) do - triggered_by_merge_request(merge_request).for_sha(sha) - end - - scope :merge_request_pipelines, -> (merge_request, source_sha) do - triggered_by_merge_request(merge_request).for_source_sha(source_sha) - end - - scope :triggered_for_branch, -> (ref) do - where(source: branch_pipeline_sources).where(ref: ref, tag: false) - end - scope :with_reports, -> (reports_scope) do where('EXISTS (?)', ::Ci::Build.latest.with_reports(reports_scope).where('ci_pipelines.id=ci_builds.commit_id').select(1)) end @@ -344,10 +320,6 @@ module Ci sources.reject { |source| source == "external" }.values end - def self.branch_pipeline_sources - @branch_pipeline_sources ||= sources.reject { |source| source == 'merge_request_event' }.values - end - def self.ci_sources_values config_sources.values_at(:repository_source, :auto_devops_source, :unknown_source) end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a40eaa34bd1..46ec98e3ffb 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1245,16 +1245,8 @@ class MergeRequest < ApplicationRecord end def all_pipelines - return Ci::Pipeline.none unless source_project - - shas = all_commit_shas - strong_memoize(:all_pipelines) do - Ci::Pipeline.from_union( - [source_project.ci_pipelines.merge_request_pipelines(self, shas), - source_project.ci_pipelines.detached_merge_request_pipelines(self, shas), - source_project.ci_pipelines.triggered_for_branch(source_branch).for_sha(shas)], - remove_duplicates: false).sort_by_merge_request_pipelines + MergeRequest::Pipelines.new(self).all end end diff --git a/app/models/merge_request/pipelines.rb b/app/models/merge_request/pipelines.rb new file mode 100644 index 00000000000..cba38f781a6 --- /dev/null +++ b/app/models/merge_request/pipelines.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +# A state object to centralize logic related to merge request pipelines +class MergeRequest::Pipelines + include Gitlab::Utils::StrongMemoize + + EVENT = 'merge_request_event' + + def initialize(merge_request) + @merge_request = merge_request + end + + attr_reader :merge_request + + delegate :all_commit_shas, :source_project, :source_branch, to: :merge_request + + def all + return Ci::Pipeline.none unless source_project + + strong_memoize(:all_pipelines) do + pipelines = Ci::Pipeline.from_union( + [source_pipelines, detached_pipelines, triggered_for_branch], + remove_duplicates: false) + + sort(pipelines) + end + end + + private + + def triggered_by_merge_request + source_project.ci_pipelines + .where(source: :merge_request_event, merge_request: merge_request) + end + + def detached_pipelines + triggered_by_merge_request.for_sha(all_commit_shas) + end + + def source_pipelines + triggered_by_merge_request.for_source_sha(all_commit_shas) + end + + def triggered_for_branch + source_project.ci_pipelines + .where(source: branch_pipeline_sources, ref: source_branch, tag: false) + .for_sha(all_commit_shas) + end + + def sources + ::Ci::Pipeline.sources + end + + def branch_pipeline_sources + strong_memoize(:branch_pipeline_sources) do + sources.reject { |source| source == EVENT }.values + end + end + + def sort(pipelines) + sql = 'CASE ci_pipelines.source WHEN (?) THEN 0 ELSE 1 END, ci_pipelines.id DESC' + query = ApplicationRecord.send(:sanitize_sql_array, [sql, sources[:merge_request_event]]) # rubocop:disable GitlabSecurity/PublicSend + + pipelines.order(Arel.sql(query)) + end +end diff --git a/doc/ssh/README.md b/doc/ssh/README.md index 01d86331a0a..80b8db84bed 100644 --- a/doc/ssh/README.md +++ b/doc/ssh/README.md @@ -273,6 +273,65 @@ git config core.sshCommand "ssh -o IdentitiesOnly=yes -i ~/.ssh/private-key-file This will not use the SSH Agent and requires at least Git 2.10. +## Multiple accounts on a single GitLab instance + +The [per-repository](#per-repository-ssh-keys) method also works for using +multiple accounts within a single GitLab instance. + +Alternatively, it is possible to directly assign aliases to hosts in +`~.ssh/config`. SSH and, by extension, Git will fail to log in if there is +an `IdentityFile` set outside of a `Host` block in `.ssh/config`. This is +due to how SSH assembles `IdentityFile` entries and is not changed by +setting `IdentitiesOnly` to `yes`. `IdentityFile` entries should point to +the private key of an SSH key pair. + +NOTE: **Note:** +Private and public keys should be readable by the user only. Accomplish this +on Linux and macOS by running: `chmod 0400 ~/.ssh/` and +`chmod 0400 ~/.ssh/`. + +```conf +# User1 Account Identity +Host + Hostname gitlab.com + PreferredAuthentications publickey + IdentityFile ~/.ssh/ + +# User2 Account Identity +Host + Hostname gitlab.com + PreferredAuthentications publickey + IdentityFile ~/.ssh/ +``` + +NOTE: **Note:** +The example `Host` aliases are defined as `user_1.gitlab.com` and +`user_2.gitlab.com` for efficiency and transparency. Advanced configurations +are more difficult to maintain; using this type of alias makes it easier to +understand when using other tools such as `git remote` subcommands. SSH +would understand any string as a `Host` alias thus `Tanuki1` and `Tanuki2`, +despite giving very little context as to where they point, would also work. + +Cloning the `gitlab` repository normally looks like this: + +```sh +git clone git@gitlab.com:gitlab-org/gitlab.git +``` + +To clone it for `user_1`, replace `gitlab.com` with the SSH alias `user_1.gitlab.com`: + +```sh +git clone git@:gitlab-org/gitlab.git +``` + +Fix a previously cloned repository using the `git remote` command. + +The example below assumes the remote repository is aliased as `origin`. + +```sh +git remote set-url origin git@:gitlab-org/gitlab.git +``` + ## Deploy keys ### Per-repository deploy keys diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 091adcefa66..a4f92ede7dc 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -6940,6 +6940,9 @@ msgstr "" msgid "Events" msgstr "" +msgid "Events: %{count}" +msgstr "" + msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again." msgstr "" @@ -7599,6 +7602,9 @@ msgstr "" msgid "First seen" msgstr "" +msgid "First seen: %{first_seen}" +msgstr "" + msgid "Fixed date" msgstr "" @@ -10038,6 +10044,9 @@ msgstr "" msgid "Last seen" msgstr "" +msgid "Last seen: %{last_seen}" +msgstr "" + msgid "Last successful update" msgstr "" @@ -15510,6 +15519,9 @@ msgstr "" msgid "Sentry event" msgstr "" +msgid "Sentry event: %{external_url}" +msgstr "" + msgid "Sep" msgstr "" @@ -16415,6 +16427,12 @@ msgstr "" msgid "Stage changes" msgstr "" +msgid "Stage data updated" +msgstr "" + +msgid "Stage removed" +msgstr "" + msgid "Staged" msgstr "" @@ -17524,6 +17542,9 @@ msgstr "" msgid "There was an error removing the e-mail." msgstr "" +msgid "There was an error removing your custom stage, please try again" +msgstr "" + msgid "There was an error resetting group pipeline minutes." msgstr "" @@ -19196,6 +19217,9 @@ msgstr "" msgid "Users with a Guest role or those who don't belong to any projects or groups don't count towards seats in use." msgstr "" +msgid "Users: %{user_count}" +msgstr "" + msgid "UsersSelect|%{name} + %{length} more" msgstr "" diff --git a/spec/frontend/error_tracking/components/error_details_spec.js b/spec/frontend/error_tracking/components/error_details_spec.js index ddc17dea291..cf91c840cc4 100644 --- a/spec/frontend/error_tracking/components/error_details_spec.js +++ b/spec/frontend/error_tracking/components/error_details_spec.js @@ -83,13 +83,34 @@ describe('ErrorDetails', () => { expect(wrapper.find(Stacktrace).exists()).toBe(false); }); - it('should allow a blank issue to be created', () => { + it('should allow an issue to be created with title and description', () => { store.state.details.loading = false; - store.state.details.error.id = 1; + store.state.details.error = { + id: 1, + title: 'Issue title', + external_url: 'http://sentry.gitlab.net/gitlab', + first_seen: '2017-05-26T13:32:48Z', + last_seen: '2018-05-26T13:32:48Z', + count: 12, + user_count: 2, + }; mountComponent(); const button = wrapper.find(GlButton); + const title = 'Issue title'; + const url = 'Sentry event: http://sentry.gitlab.net/gitlab'; + const firstSeen = 'First seen: 2017-05-26T13:32:48Z'; + const lastSeen = 'Last seen: 2018-05-26T13:32:48Z'; + const count = 'Events: 12'; + const userCount = 'Users: 2'; + + const issueDescription = `${url}${firstSeen}${lastSeen}${count}${userCount}`; + + const issueLink = `/test-project/issues/new?issue[title]=${encodeURIComponent( + title, + )}&issue[description]=${encodeURIComponent(issueDescription)}`; + expect(button.exists()).toBe(true); - expect(button.attributes().href).toBe(wrapper.props().issueProjectPath); + expect(button.attributes().href).toBe(issueLink); }); describe('Stacktrace', () => { diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 800ec3219e5..977f1fdd267 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -75,71 +75,6 @@ describe Ci::Pipeline, :mailer do end end - describe '.sort_by_merge_request_pipelines' do - subject { described_class.sort_by_merge_request_pipelines } - - context 'when branch pipelines exist' do - let!(:branch_pipeline_1) { create(:ci_pipeline, source: :push) } - let!(:branch_pipeline_2) { create(:ci_pipeline, source: :push) } - - it 'returns pipelines order by id' do - expect(subject).to eq([branch_pipeline_2, - branch_pipeline_1]) - end - end - - context 'when merge request pipelines exist' do - let!(:merge_request_pipeline_1) do - create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) - end - - let!(:merge_request_pipeline_2) do - create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) - end - - let(:merge_request) do - create(:merge_request, - source_project: project, - source_branch: 'feature', - target_project: project, - target_branch: 'master') - end - - it 'returns pipelines order by id' do - expect(subject).to eq([merge_request_pipeline_2, - merge_request_pipeline_1]) - end - end - - context 'when both branch pipeline and merge request pipeline exist' do - let!(:branch_pipeline_1) { create(:ci_pipeline, source: :push) } - let!(:branch_pipeline_2) { create(:ci_pipeline, source: :push) } - - let!(:merge_request_pipeline_1) do - create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) - end - - let!(:merge_request_pipeline_2) do - create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request) - end - - let(:merge_request) do - create(:merge_request, - source_project: project, - source_branch: 'feature', - target_project: project, - target_branch: 'master') - end - - it 'returns merge request pipeline first' do - expect(subject).to eq([merge_request_pipeline_2, - merge_request_pipeline_1, - branch_pipeline_2, - branch_pipeline_1]) - end - end - end - describe '.for_sha' do subject { described_class.for_sha(sha) } @@ -226,39 +161,6 @@ describe Ci::Pipeline, :mailer do end end - describe '.detached_merge_request_pipelines' do - subject { described_class.detached_merge_request_pipelines(merge_request, sha) } - - let!(:pipeline) do - create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, sha: merge_request.diff_head_sha) - end - - let(:merge_request) { create(:merge_request) } - let(:sha) { merge_request.diff_head_sha } - - it 'returns detached merge request pipelines' do - is_expected.to eq([pipeline]) - end - - context 'when sha does not exist' do - let(:sha) { 'abc' } - - it 'returns empty array' do - is_expected.to be_empty - end - end - - context 'when pipeline is merge request pipeline' do - let!(:pipeline) do - create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, source_sha: merge_request.diff_head_sha) - end - - it 'returns empty array' do - is_expected.to be_empty - end - end - end - describe '#detached_merge_request_pipeline?' do subject { pipeline.detached_merge_request_pipeline? } @@ -278,39 +180,6 @@ describe Ci::Pipeline, :mailer do end end - describe '.merge_request_pipelines' do - subject { described_class.merge_request_pipelines(merge_request, source_sha) } - - let!(:pipeline) do - create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, source_sha: merge_request.diff_head_sha) - end - - let(:merge_request) { create(:merge_request) } - let(:source_sha) { merge_request.diff_head_sha } - - it 'returns merge pipelines' do - is_expected.to eq([pipeline]) - end - - context 'when source sha is empty' do - let(:source_sha) { nil } - - it 'returns empty array' do - is_expected.to be_empty - end - end - - context 'when pipeline is detached merge request pipeline' do - let!(:pipeline) do - create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, sha: merge_request.diff_head_sha) - end - - it 'returns empty array' do - is_expected.to be_empty - end - end - end - describe '#merge_request_pipeline?' do subject { pipeline.merge_request_pipeline? } @@ -499,50 +368,6 @@ describe Ci::Pipeline, :mailer do end end - describe '.triggered_for_branch' do - subject { described_class.triggered_for_branch(ref) } - - let(:project) { create(:project, :repository) } - let(:ref) { 'feature' } - let!(:pipeline) { create(:ci_pipeline, ref: ref) } - - it 'returns the pipeline' do - is_expected.to eq([pipeline]) - end - - context 'when sha is not specified' do - it 'returns the pipeline' do - expect(described_class.triggered_for_branch(ref)).to eq([pipeline]) - end - end - - context 'when pipeline is triggered for tag' do - let(:ref) { 'v1.1.0' } - let!(:pipeline) { create(:ci_pipeline, ref: ref, tag: true) } - - it 'does not return the pipeline' do - is_expected.to be_empty - end - end - - context 'when pipeline is triggered for merge_request' do - let!(:merge_request) do - create(:merge_request, - :with_merge_request_pipeline, - source_project: project, - source_branch: ref, - target_project: project, - target_branch: 'master') - end - - let(:pipeline) { merge_request.pipelines_for_merge_request.first } - - it 'does not return the pipeline' do - is_expected.to be_empty - end - end - end - describe '.with_reports' do subject { described_class.with_reports(Ci::JobArtifact.test_reports) } diff --git a/spec/models/merge_request/pipelines_spec.rb b/spec/models/merge_request/pipelines_spec.rb new file mode 100644 index 00000000000..96f09eda647 --- /dev/null +++ b/spec/models/merge_request/pipelines_spec.rb @@ -0,0 +1,154 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe MergeRequest::Pipelines do + describe '#all' do + let(:merge_request) { create(:merge_request) } + let(:project) { merge_request.source_project } + + subject { described_class.new(merge_request) } + + shared_examples 'returning pipelines with proper ordering' do + let!(:all_pipelines) do + merge_request.all_commit_shas.map do |sha| + create(:ci_empty_pipeline, + project: project, sha: sha, ref: merge_request.source_branch) + end + end + + it 'returns all pipelines' do + expect(subject.all).not_to be_empty + expect(subject.all).to eq(all_pipelines.reverse) + end + end + + context 'with single merge_request_diffs' do + it_behaves_like 'returning pipelines with proper ordering' + end + + context 'with multiple irrelevant merge_request_diffs' do + before do + merge_request.update(target_branch: 'v1.0.0') + end + + it_behaves_like 'returning pipelines with proper ordering' + end + + context 'with unsaved merge request' do + let(:merge_request) { build(:merge_request) } + + let!(:pipeline) do + create(:ci_empty_pipeline, project: project, + sha: merge_request.diff_head_sha, ref: merge_request.source_branch) + end + + it 'returns pipelines from diff_head_sha' do + expect(subject.all).to contain_exactly(pipeline) + end + end + + context 'when pipelines exist for the branch and merge request' do + let(:source_ref) { 'feature' } + let(:target_ref) { 'master' } + + let!(:branch_pipeline) do + create(:ci_pipeline, source: :push, project: project, + ref: source_ref, sha: shas.second) + end + + let!(:tag_pipeline) do + create(:ci_pipeline, project: project, ref: source_ref, tag: true) + end + + let!(:detached_merge_request_pipeline) do + create(:ci_pipeline, source: :merge_request_event, project: project, + ref: source_ref, sha: shas.second, merge_request: merge_request) + end + + let(:merge_request) do + create(:merge_request, source_project: project, source_branch: source_ref, + target_project: project, target_branch: target_ref) + end + + let(:project) { create(:project, :repository) } + let(:shas) { project.repository.commits(source_ref, limit: 2).map(&:id) } + + before do + allow(merge_request).to receive(:all_commit_shas) { shas } + end + + it 'returns merge request pipeline first' do + expect(subject.all).to eq([detached_merge_request_pipeline, branch_pipeline]) + end + + context 'when there are a branch pipeline and a merge request pipeline' do + let!(:branch_pipeline_2) do + create(:ci_pipeline, source: :push, project: project, + ref: source_ref, sha: shas.first) + end + + let!(:detached_merge_request_pipeline_2) do + create(:ci_pipeline, source: :merge_request_event, project: project, + ref: source_ref, sha: shas.first, merge_request: merge_request) + end + + it 'returns merge request pipelines first' do + expect(subject.all) + .to eq([detached_merge_request_pipeline_2, + detached_merge_request_pipeline, + branch_pipeline_2, + branch_pipeline]) + end + end + + context 'when there are multiple merge request pipelines from the same branch' do + let!(:branch_pipeline_2) do + create(:ci_pipeline, source: :push, project: project, + ref: source_ref, sha: shas.first) + end + + let!(:detached_merge_request_pipeline_2) do + create(:ci_pipeline, source: :merge_request_event, project: project, + ref: source_ref, sha: shas.first, merge_request: merge_request_2) + end + + let(:merge_request_2) do + create(:merge_request, source_project: project, source_branch: source_ref, + target_project: project, target_branch: 'stable') + end + + before do + allow(merge_request_2).to receive(:all_commit_shas) { shas } + end + + it 'returns only related merge request pipelines' do + expect(subject.all) + .to eq([detached_merge_request_pipeline, + branch_pipeline_2, + branch_pipeline]) + + expect(described_class.new(merge_request_2).all) + .to eq([detached_merge_request_pipeline_2, + branch_pipeline_2, + branch_pipeline]) + end + end + + context 'when detached merge request pipeline is run on head ref of the merge request' do + let!(:detached_merge_request_pipeline) do + create(:ci_pipeline, source: :merge_request_event, project: project, + ref: merge_request.ref_path, sha: shas.second, merge_request: merge_request) + end + + it 'sets the head ref of the merge request to the pipeline ref' do + expect(detached_merge_request_pipeline.ref).to match(%r{refs/merge-requests/\d+/head}) + end + + it 'includes the detached merge request pipeline even though the ref is custom path' do + expect(merge_request.all_pipelines).to include(detached_merge_request_pipeline) + end + end + end + end +end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 656753da5f1..53d67113f9e 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1441,183 +1441,6 @@ describe MergeRequest do end end - describe '#all_pipelines' do - shared_examples 'returning pipelines with proper ordering' do - let!(:all_pipelines) do - subject.all_commit_shas.map do |sha| - create(:ci_empty_pipeline, - project: subject.source_project, - sha: sha, - ref: subject.source_branch) - end - end - - it 'returns all pipelines' do - expect(subject.all_pipelines).not_to be_empty - expect(subject.all_pipelines).to eq(all_pipelines.reverse) - end - end - - context 'with single merge_request_diffs' do - it_behaves_like 'returning pipelines with proper ordering' - end - - context 'with multiple irrelevant merge_request_diffs' do - before do - subject.update(target_branch: 'v1.0.0') - end - - it_behaves_like 'returning pipelines with proper ordering' - end - - context 'with unsaved merge request' do - subject { build(:merge_request) } - - let!(:pipeline) do - create(:ci_empty_pipeline, - project: subject.project, - sha: subject.diff_head_sha, - ref: subject.source_branch) - end - - it 'returns pipelines from diff_head_sha' do - expect(subject.all_pipelines).to contain_exactly(pipeline) - end - end - - context 'when pipelines exist for the branch and merge request' do - let(:source_ref) { 'feature' } - let(:target_ref) { 'master' } - - let!(:branch_pipeline) do - create(:ci_pipeline, - source: :push, - project: project, - ref: source_ref, - sha: shas.second) - end - - let!(:detached_merge_request_pipeline) do - create(:ci_pipeline, - source: :merge_request_event, - project: project, - ref: source_ref, - sha: shas.second, - merge_request: merge_request) - end - - let(:merge_request) do - create(:merge_request, - source_project: project, - source_branch: source_ref, - target_project: project, - target_branch: target_ref) - end - - let(:project) { create(:project, :repository) } - let(:shas) { project.repository.commits(source_ref, limit: 2).map(&:id) } - - before do - allow(merge_request).to receive(:all_commit_shas) { shas } - end - - it 'returns merge request pipeline first' do - expect(merge_request.all_pipelines) - .to eq([detached_merge_request_pipeline, - branch_pipeline]) - end - - context 'when there are a branch pipeline and a merge request pipeline' do - let!(:branch_pipeline_2) do - create(:ci_pipeline, - source: :push, - project: project, - ref: source_ref, - sha: shas.first) - end - - let!(:detached_merge_request_pipeline_2) do - create(:ci_pipeline, - source: :merge_request_event, - project: project, - ref: source_ref, - sha: shas.first, - merge_request: merge_request) - end - - it 'returns merge request pipelines first' do - expect(merge_request.all_pipelines) - .to eq([detached_merge_request_pipeline_2, - detached_merge_request_pipeline, - branch_pipeline_2, - branch_pipeline]) - end - end - - context 'when there are multiple merge request pipelines from the same branch' do - let!(:branch_pipeline_2) do - create(:ci_pipeline, - source: :push, - project: project, - ref: source_ref, - sha: shas.first) - end - - let!(:detached_merge_request_pipeline_2) do - create(:ci_pipeline, - source: :merge_request_event, - project: project, - ref: source_ref, - sha: shas.first, - merge_request: merge_request_2) - end - - let(:merge_request_2) do - create(:merge_request, - source_project: project, - source_branch: source_ref, - target_project: project, - target_branch: 'stable') - end - - before do - allow(merge_request_2).to receive(:all_commit_shas) { shas } - end - - it 'returns only related merge request pipelines' do - expect(merge_request.all_pipelines) - .to eq([detached_merge_request_pipeline, - branch_pipeline_2, - branch_pipeline]) - - expect(merge_request_2.all_pipelines) - .to eq([detached_merge_request_pipeline_2, - branch_pipeline_2, - branch_pipeline]) - end - end - - context 'when detached merge request pipeline is run on head ref of the merge request' do - let!(:detached_merge_request_pipeline) do - create(:ci_pipeline, - source: :merge_request_event, - project: project, - ref: merge_request.ref_path, - sha: shas.second, - merge_request: merge_request) - end - - it 'sets the head ref of the merge request to the pipeline ref' do - expect(detached_merge_request_pipeline.ref).to match(%r{refs/merge-requests/\d+/head}) - end - - it 'includes the detached merge request pipeline even though the ref is custom path' do - expect(merge_request.all_pipelines).to include(detached_merge_request_pipeline) - end - end - end - end - describe '#update_head_pipeline' do subject { merge_request.update_head_pipeline }