From 6fbdc5ed5224154b89cf351e11a8f9db48e6d7f0 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Wed, 24 Oct 2018 18:01:44 +0200 Subject: Apply patches when creating MR via email This allows users to add patches as attachments to merge request created via email. When an email to create a merge request is sent, all the attachments ending in `.patch` will be applied to the branch specified in the subject of the email. If the branch did not exist, it will be created from the HEAD of the repository. When the patches could not be applied, the error message will be replied to the user. The patches can have a maximum combined size of 2MB for now. --- app/services/commits/commit_patch_service.rb | 61 +++++++ app/services/commits/create_service.rb | 7 +- app/services/merge_requests/build_service.rb | 4 +- app/workers/email_receiver_worker.rb | 2 + changelogs/unreleased/bvl-patches-via-mail.yml | 5 + doc/user/project/merge_requests/index.md | 17 ++ .../email/handler/create_merge_request_handler.rb | 50 +++++- lib/gitlab/email/receiver.rb | 1 + lib/gitlab/git/patches/collection.rb | 33 ++++ lib/gitlab/git/patches/commit_patches.rb | 31 ++++ lib/gitlab/git/patches/patch.rb | 19 +++ lib/gitlab/gitaly_client/operation_service.rb | 23 +++ .../emails/merge_request_multiple_patches.eml | 181 +++++++++++++++++++++ .../merge_request_with_conflicting_patch.eml | 45 +++++ .../merge_request_with_patch_and_target_branch.eml | 44 +++++ .../emails/valid_merge_request_with_patch.eml | 151 +++++++++++++++++ .../patchfiles/0001-A-commit-from-a-patch.patch | 19 +++ ...This-does-not-apply-to-the-feature-branch.patch | 23 +++ .../handler/create_merge_request_handler_spec.rb | 69 ++++++++ spec/lib/gitlab/git/patches/collection_spec.rb | 28 ++++ spec/lib/gitlab/git/patches/commit_patches_spec.rb | 49 ++++++ spec/lib/gitlab/git/patches/patch_spec.rb | 16 ++ .../gitlab/gitaly_client/operation_service_spec.rb | 33 ++++ spec/services/commits/commit_patch_service_spec.rb | 92 +++++++++++ spec/services/merge_requests/build_service_spec.rb | 8 + spec/workers/email_receiver_worker_spec.rb | 15 ++ 26 files changed, 1022 insertions(+), 4 deletions(-) create mode 100644 app/services/commits/commit_patch_service.rb create mode 100644 changelogs/unreleased/bvl-patches-via-mail.yml create mode 100644 lib/gitlab/git/patches/collection.rb create mode 100644 lib/gitlab/git/patches/commit_patches.rb create mode 100644 lib/gitlab/git/patches/patch.rb create mode 100644 spec/fixtures/emails/merge_request_multiple_patches.eml create mode 100644 spec/fixtures/emails/merge_request_with_conflicting_patch.eml create mode 100644 spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml create mode 100644 spec/fixtures/emails/valid_merge_request_with_patch.eml create mode 100644 spec/fixtures/patchfiles/0001-A-commit-from-a-patch.patch create mode 100644 spec/fixtures/patchfiles/0001-This-does-not-apply-to-the-feature-branch.patch create mode 100644 spec/lib/gitlab/git/patches/collection_spec.rb create mode 100644 spec/lib/gitlab/git/patches/commit_patches_spec.rb create mode 100644 spec/lib/gitlab/git/patches/patch_spec.rb create mode 100644 spec/services/commits/commit_patch_service_spec.rb diff --git a/app/services/commits/commit_patch_service.rb b/app/services/commits/commit_patch_service.rb new file mode 100644 index 00000000000..9253cfaac20 --- /dev/null +++ b/app/services/commits/commit_patch_service.rb @@ -0,0 +1,61 @@ +# frozen_string_literal: true + +module Commits + class CommitPatchService < CreateService + # Requires: + # - project: `Project` to be committed into + # - user: `User` that will be the committer + # - params: + # - branch_name: `String` the branch that will be committed into + # - start_branch: `String` the branch that will will started from + # - patches: `Gitlab::Git::Patches::Collection` that contains the patches + def initialize(*args) + super + + @patches = Gitlab::Git::Patches::Collection.new(Array(params[:patches])) + end + + private + + def new_branch? + !repository.branch_exists?(@branch_name) + end + + def create_commit! + if @start_branch && new_branch? + prepare_branch! + end + + Gitlab::Git::Patches::CommitPatches + .new(current_user, project.repository, @branch_name, @patches) + .commit + end + + def prepare_branch! + branch_result = CreateBranchService.new(project, current_user) + .execute(@branch_name, @start_branch) + + if branch_result[:status] != :success + raise ChangeError, branch_result[:message] + end + end + + # Overridden from the Commits::CreateService, to skip some validations we + # don't need: + # - validate_on_branch! + # Not needed, the patches are applied on top of HEAD if the branch did not + # exist + # - validate_branch_existence! + # Not needed because we continue applying patches on the branch if it + # already existed, and create it if it did not exist. + def validate! + validate_patches! + validate_new_branch_name! if new_branch? + validate_permissions! + end + + def validate_patches! + raise_error("Patches are too big") unless @patches.valid_size? + end + end +end diff --git a/app/services/commits/create_service.rb b/app/services/commits/create_service.rb index 3ce9acc833c..34593e12bd5 100644 --- a/app/services/commits/create_service.rb +++ b/app/services/commits/create_service.rb @@ -19,7 +19,12 @@ module Commits new_commit = create_commit! success(result: new_commit) - rescue ValidationError, ChangeError, Gitlab::Git::Index::IndexError, Gitlab::Git::CommitError, Gitlab::Git::PreReceiveError => ex + rescue ValidationError, + ChangeError, + Gitlab::Git::Index::IndexError, + Gitlab::Git::CommitError, + Gitlab::Git::PreReceiveError, + Gitlab::Git::CommandError => ex error(ex.message) end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 0e76d2cc3ab..6c9e566109a 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -6,8 +6,10 @@ module MergeRequests def execute @params_issue_iid = params.delete(:issue_iid) + self.merge_request = MergeRequest.new + merge_quick_actions_into_params!(merge_request) + merge_request.assign_attributes(params) - self.merge_request = MergeRequest.new(params) merge_request.author = current_user merge_request.compare_commits = [] merge_request.source_project = find_source_project diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 12706613ac2..bf637f82df2 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -40,6 +40,8 @@ class EmailReceiverWorker "You are not allowed to perform this action. If you believe this is in error, contact a staff member." when Gitlab::Email::NoteableNotFoundError "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member." + when Gitlab::Email::InvalidAttachment + error.message when Gitlab::Email::InvalidRecordError can_retry = true error.message diff --git a/changelogs/unreleased/bvl-patches-via-mail.yml b/changelogs/unreleased/bvl-patches-via-mail.yml new file mode 100644 index 00000000000..6fd9e6a956c --- /dev/null +++ b/changelogs/unreleased/bvl-patches-via-mail.yml @@ -0,0 +1,5 @@ +--- +title: Allow adding patches when creating a merge request via email +merge_request: 22723 +author: Serdar Dogruyol +type: added diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index 0a7f7d37384..6de2ab07fc4 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -166,6 +166,23 @@ administrator to do so. ![Create new merge requests by email](img/create_from_email.png) +### Adding patches when creating a merge request via e-mail + +> **Note**: This feature was [implemented in GitLab 11.5](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22723) + +You can add commits to the merge request being created by adding +patches as attachments to the email, all attachments with a filename +ending in `.patch` will be considered patches. The patches will be processed +ordered by name. + +The combined size of the patches can be 2MB. + +If the source branch from the subject does not exist, it will be +created from the repository's HEAD or the specified target branch to +apply the patches. The target branch can be specified using the +[`/target_branch` quick action](../quick_actions.md). If the source +branch already exists, the patches will be applied on top of it. + ## Find the merge request that introduced a change > **Note**: this feature was [implemented in GitLab 10.5](https://gitlab.com/gitlab-org/gitlab-ce/issues/2383). diff --git a/lib/gitlab/email/handler/create_merge_request_handler.rb b/lib/gitlab/email/handler/create_merge_request_handler.rb index e68ae60ff98..5772727e855 100644 --- a/lib/gitlab/email/handler/create_merge_request_handler.rb +++ b/lib/gitlab/email/handler/create_merge_request_handler.rb @@ -44,10 +44,26 @@ module Gitlab @project ||= Project.find_by_full_path(project_path) end + def metrics_params + super.merge(includes_patches: patch_attachments.any?) + end + private + def build_merge_request + MergeRequests::BuildService.new(project, author, merge_request_params).execute + end + def create_merge_request - merge_request = MergeRequests::BuildService.new(project, author, merge_request_params).execute + merge_request = build_merge_request + + if patch_attachments.any? + apply_patches_to_source_branch(start_branch: merge_request.target_branch) + remove_patch_attachments + # Rebuild the merge request as the source branch might just have + # been created, so we should re-validate. + merge_request = build_merge_request + end if merge_request.errors.any? merge_request @@ -59,12 +75,42 @@ module Gitlab def merge_request_params params = { source_project_id: project.id, - source_branch: mail.subject, + source_branch: source_branch, target_project_id: project.id } params[:description] = message if message.present? params end + + def apply_patches_to_source_branch(start_branch:) + patches = patch_attachments.map { |patch| patch.body.decoded } + + result = Commits::CommitPatchService + .new(project, author, branch_name: source_branch, patches: patches, start_branch: start_branch) + .execute + + if result[:status] != :success + message = "Could not apply patches to #{source_branch}:\n#{result[:message]}" + raise InvalidAttachment, message + end + end + + def remove_patch_attachments + patch_attachments.each { |patch| mail.parts.delete(patch) } + # reset the message, so it needs to be reporocessed when the attachments + # have been modified + @message = nil + end + + def patch_attachments + @patches ||= mail.attachments + .select { |attachment| attachment.filename.ends_with?('.patch') } + .sort_by(&:filename) + end + + def source_branch + @source_branch ||= mail.subject + end end end end diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index d8c594ad0e7..3a689967a64 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -18,6 +18,7 @@ module Gitlab InvalidIssueError = Class.new(InvalidRecordError) InvalidMergeRequestError = Class.new(InvalidRecordError) UnknownIncomingEmail = Class.new(ProcessingError) + InvalidAttachment = Class.new(ProcessingError) class Receiver def initialize(raw) diff --git a/lib/gitlab/git/patches/collection.rb b/lib/gitlab/git/patches/collection.rb new file mode 100644 index 00000000000..ad6b5d32abc --- /dev/null +++ b/lib/gitlab/git/patches/collection.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module Gitlab + module Git + module Patches + class Collection + MAX_PATCH_SIZE = 2.megabytes + + def initialize(one_or_more_patches) + @patches = Array(one_or_more_patches).map do |patch_content| + Gitlab::Git::Patches::Patch.new(patch_content) + end + end + + def content + @patches.map(&:content).join("\n") + end + + def valid_size? + size < MAX_PATCH_SIZE + end + + # rubocop: disable CodeReuse/ActiveRecord + # `@patches` is not an `ActiveRecord` relation, but an `Enumerable` + # We're using sum from `ActiveSupport` + def size + @size ||= @patches.sum(&:size) + end + # rubocop: enable CodeReuse/ActiveRecord + end + end + end +end diff --git a/lib/gitlab/git/patches/commit_patches.rb b/lib/gitlab/git/patches/commit_patches.rb new file mode 100644 index 00000000000..c62994432d3 --- /dev/null +++ b/lib/gitlab/git/patches/commit_patches.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Gitlab + module Git + module Patches + class CommitPatches + include Gitlab::Git::WrapsGitalyErrors + + def initialize(user, repository, branch, patch_collection) + @user, @repository, @branch, @patches = user, repository, branch, patch_collection + end + + def commit + repository.with_cache_hooks do + wrapped_gitaly_errors do + operation_service.user_commit_patches(user, branch, patches.content) + end + end + end + + private + + attr_reader :user, :repository, :branch, :patches + + def operation_service + repository.raw.gitaly_operation_client + end + end + end + end +end diff --git a/lib/gitlab/git/patches/patch.rb b/lib/gitlab/git/patches/patch.rb new file mode 100644 index 00000000000..fe6ae1b5b00 --- /dev/null +++ b/lib/gitlab/git/patches/patch.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +module Gitlab + module Git + module Patches + class Patch + attr_reader :content + + def initialize(content) + @content = content + end + + def size + content.bytesize + end + end + end + end +end diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb index 1f42f657f68..4c78b790ce5 100644 --- a/lib/gitlab/gitaly_client/operation_service.rb +++ b/lib/gitlab/gitaly_client/operation_service.rb @@ -299,6 +299,29 @@ module Gitlab Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update) end + def user_commit_patches(user, branch_name, patches) + header = Gitaly::UserApplyPatchRequest::Header.new( + repository: @gitaly_repo, + user: Gitlab::Git::User.from_gitlab(user).to_gitaly, + target_branch: encode_binary(branch_name) + ) + reader = binary_stringio(patches) + + chunks = Enumerator.new do |chunk| + chunk.yield Gitaly::UserApplyPatchRequest.new(header: header) + + until reader.eof? + patch_chunk = reader.read(MAX_MSG_SIZE) + + chunk.yield(Gitaly::UserApplyPatchRequest.new(patches: patch_chunk)) + end + end + + response = GitalyClient.call(@repository.storage, :operation_service, :user_apply_patch, chunks) + + Gitlab::Git::OperationService::BranchUpdate.from_gitaly(response.branch_update) + end + private def call_cherry_pick_or_revert(rpc, user:, commit:, branch_name:, message:, start_branch_name:, start_repository:) diff --git a/spec/fixtures/emails/merge_request_multiple_patches.eml b/spec/fixtures/emails/merge_request_multiple_patches.eml new file mode 100644 index 00000000000..311b99a525d --- /dev/null +++ b/spec/fixtures/emails/merge_request_multiple_patches.eml @@ -0,0 +1,181 @@ +From: "Jake the Dog" +To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo +Subject: new-branch-with-a-patch +Date: Wed, 31 Oct 2018 17:27:52 +0100 +X-Mailer: MailMate (1.12r5523) +Message-ID: <7BE7C2E6-F0D9-4C85-9F55-B2A4BA01BFEC@gitlab.com> +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_=" + + +--=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_= + +This applies nicely to a branch freshly created from the root-ref + +The other attachments in this email are ignored + +--=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_= +Content-Disposition: attachment; + filename=0002-This-does-not-apply-to-the-feature-branch.patch +Content-Transfer-Encoding: quoted-printable + +=46rom 00c68c2b4f954370ce82a1162bc29c13f524897e Mon Sep 17 00:00:00 2001 +From: Patch user +Date: Mon, 22 Oct 2018 11:05:48 +0200 +Subject: [PATCH] This does not apply to the `feature` branch + +--- + files/ruby/feature.rb | 5 +++++ + 1 file changed, 5 insertions(+) + create mode 100644 files/ruby/feature.rb + +diff --git a/files/ruby/feature.rb b/files/ruby/feature.rb +new file mode 100644 +index 0000000..fef26e4 +--- /dev/null ++++ b/files/ruby/feature.rb +@@ -0,0 +1,5 @@ ++class Feature ++ def bar ++ puts 'foo' ++ end ++end +-- = + +2.19.1 + +--=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_= +Content-Disposition: attachment; filename=0001-A-commit-from-a-patch.patch +Content-Transfer-Encoding: quoted-printable + +=46rom 3fee0042e610fb3563e4379e316704cb1210f3de Mon Sep 17 00:00:00 2001 +From: Patch user +Date: Thu, 18 Oct 2018 13:40:35 +0200 +Subject: [PATCH] A commit from a patch + +--- + README | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/README b/README +index 3742e48..e40a3b9 100644 +--- a/README ++++ b/README +@@ -1 +1,3 @@ + Sample repo for testing gitlab features ++ ++This was applied in a patch! +-- = + +2.19.1 + + +--=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_= +Content-Disposition: attachment; filename=really-not-a-patch.png +Content-Type: image/png +Content-Transfer-Encoding: base64 + +iVBORw0KGgoAAAANSUhEUgAAAjMAAAAfCAYAAAASo0ymAAABfGlDQ1BJQ0MgUHJvZmlsZQAAKJFj +YGAqSSwoyGFhYGDIzSspCnJ3UoiIjFJgv8PAzcDDIMRgxSCemFxc4BgQ4MOAE3y7xsAIoi/rgsxK +8/x506a1fP4WNq+ZclYlOrj1gQF3SmpxMgMDIweQnZxSnJwLZOcA2TrJBUUlQPYMIFu3vKQAxD4B +ZIsUAR0IZN8BsdMh7A8gdhKYzcQCVhMS5AxkSwDZAkkQtgaInQ5hW4DYyRmJKUC2B8guiBvAgNPD +RcHcwFLXkYC7SQa5OaUwO0ChxZOaFxoMcgcQyzB4MLgwKDCYMxgwWDLoMjiWpFaUgBQ65xdUFmWm +Z5QoOAJDNlXBOT+3oLQktUhHwTMvWU9HwcjA0ACkDhRnEKM/B4FNZxQ7jxDLX8jAYKnMwMDcgxBL +msbAsH0PA4PEKYSYyjwGBn5rBoZt5woSixLhDmf8xkKIX5xmbARh8zgxMLDe+///sxoDA/skBoa/ +E////73o//+/i4H2A+PsQA4AJHdp4IxrEg8AAAGcaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8 +eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA1LjQu +MCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1y +ZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAg +ICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAg +ICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjU2MzwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAg +ICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj4zMTwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAg +IDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpZ5vHZAAAS+ElE +QVR4Ae2cC1QUV5rH/xnA1uZli02MiEHQqBMdTGB8ZIniA5xgfCTRycb1rBmdTHJGJ+toPBtzzGg2 +Rs3LdVTOYhKMcDgmDNHVNXKiGKJINCoqiXpUEFdB0CDKCtrSYp/Zr6q7muqmu+pWd7XgnFtH6Xrc ++h6/+92qr+69VQ9dqrvxd/CFE+AEOAFOgBPgBDiBB5TALx5Qu7nZnAAnwAlwApwAJ8AJiAR4MsMD +gRPgBDgBToAT4AQeaAI8mXmgq48bzwlwApwAJ8AJcAI8meExwAlwApwAJ8AJcAIPNAGezDzQ1ceN +5wQ4AU6AE+AEOIFgTwiamls87eb7OAFOgBPgBDgBToAT0I1ARHhXXWTxnhldMHIhnAAnwAlwApwA +J9BRBIL+vOjfl7srt969577L63Zj1VGU/liHHn1j0I0xNbI1X8TRo5V4yNQLEQbGk7xawH6go/Sy +W8hLBoqAL3EaKFtY5doaz6PkwCk8FN0H3f1oJx0d93r5wcpN13K2RhzbX4KKn4FH+vSAx65sXRX+ +YwgTYq5k72FUXqzChaoq1DQGI7Z3dwT6ai/oPbj/OJoNDyM6IsRvmL74odTe9LbPbwc7gQCDQZ9W +5VFK0co52FKp7OW0N9di6sAI1B//G7YU3kHvEb+Gyah8jnTUWncYWdm7kf7GWrz0eIS0O+C/HaVX +P8dsqPphL07dicFvxg6BQT/BnVSSfv76EqcdDcVafww5X1A7iRmGR/1oJ+pxrx9nT8z08sOT7IDv +s9YiPzcf10InImlkfy9tLrD8Au5jABRY6w8j56vdbZIFfslxCG/bE5A1Idazqc2EpUVj/cwhfuvw +xQ+l9qa3fX47eN8EWFC4cj4KKgdjadZiJATg5uUxUQ6JiEGYOQpm8X9bhiLtC4MRIcH2PCg4RBjv +Mml7YgkOFRFGhnrMpQKHt6P06ubRbZzIy8f2radxVzeZnVmQfv76FKcdjSa4i2hBiL/NRDXu9ePs +EZlefngUHuCdQcF0daOlRxcEeVUVYH5e9XbeA8aEF/HZZ5vw2Zo/wSyYqchPRz8csdbD70Zjt8kn +P5Tam8726Ugu4KJaxam4TWixBUaVx8tk6vx3kSrps57HqtdWosI8Ge9/8BylMV4WR0u32WwICvLe +7IWzjQnP4vPPn/UiKHC7O0qvfh4ZYOxB0lpCYb/N6Se5c0oKgL8a4rRjmNhATUhsQwblZsRsnnrc +B4Az9PeD2eH7XjAQ/O67E7orFG8Dxm5iMnhNd+n3T6BWP9Tb2/2zvTNpkgb9PCYdOhjKINcxf4Y6 +YJQSquoTxdiVl4eTtwWrYjDzzYVIGyg+0zjNrC3NQ27JFUREGGC1Av/04qsY0ad9f1PD6d3IzdmJ +k9cs4rlh5niMmTIL01PinLK0rHSE3trSTfikBMhIj8P3m5W5NJwrxqfr81AhsgP6Jk3GnDnP4VEp +c7ReRM7H2agjpytqBM+/wZqPz0CA2NJiQMYfX8eIXu05KjFqvnwCuwv34shPZ3DNofexpIl4/l+m +Y6BJ+13U7u9djEg0YR91L4sXL/MwzF/4KpJktjHp1eyvFee+244tW3ejWvTFiMeSnsZvZ09HQrir +LyxxqsRNOsbkBxXWEgdV1D7WZhfjlqAkNgWzUyRtvv2qxr1mzmx2sPqhGvcOdZarJ1CwaQv2VV4X +94TFDsfLf/ydS1wJB1iuG1rqw6He+0+A+Kn5a71cgjXritB7yh8oRmJl9jWh6JN1KL07DAvmP2vv +UaKjavIEAbpykVnEssoaB6zl3HU2nP4auTsO42ZECt6YPzFgQ12q7c3dMMe2N/tY6s2LSI+7Wfhp +iQO97fNotIadHoeZvJ3veluQSgk30Vps2ZiHq/1SkJ4UY99enYlz9lxEKmj/peLW+gqcPFWOmtuU +0bgttqvFWPxRPiUyQHLaRExKG47elgvYlV2KZreymjbvs96WhjpUV5YiK1OZS/PpfCxebU9kho4Z +h6eGRKH62E4sn7cOl5x4gtDFEEFJYATCRKe7wWAwiNuRkeHahvgc0Kr3rseuQ5QQ9R6MVOKcOiQG +Fcd2Y/XCNahy6mUnbPf3CAookemWNA6TxsQD18qxYckK/CirODa9Wvy1onTDIqzOFRKZKKRmTEb6 +mEdFX0ovOLI00Q2NcariOpsf1InGGAe1peuwQkxkYpA+dSKG3iil+TKlKlYwHFaMey2cGXRREVY/ +2OKebsRVuzFvyXoxkXlsFF0PxgzGrZojFFeLcLih7fGK9brBWh/tvPX4tQr9+bH4a+gRjZZrtdj3 +5X40ygy1XS7DlkMXUA26Vjj2s8gTivrMRabfl1XWOGAt525D7Q+b6H6yDScrDXj+n1MDlsg49Sq2 +N2cp54o3+1jrzSlIZYWVH2sc6G2fivlMhxl6ZpjkIPmltzAvvb9Y+Fdb3sZHRRdwsqYJA2mSsLTE +pMzCEnratF38Gr9/Z5u02+XXevOmuJ3+xgc0OdjRNTHzFTQ3W30OxI7SKziizMWC4vzdor8zaEJ1 +hshqFp7c/DY27C/H1oM1WDiWnrwMsXhp0WIqZ0PhX15BQcsYzKMnL+H27OvSf+p7WPVCNHo5ey5e +xIQ9H2LpF2dw4mITEmT1pkVH34w/4Z0ZT4in/Lr3Oiz/ohyFJeeROMkeG0x6NfhrOVeI7GOUNZvH +YcWqWYhxZNy/faEGV++1J6RcH+yeMvkhE6es9woKs8updBTmf/guknrS6uSn8Om/LcNBeT4mk8ey +qhr3Gjiz6ANY/WCMe+rf2/Gf+aQ6CnNXrUZKL3vlPjehGAvezkPWl98jef5ocS6L1uuGcn3IvDUM +wpLPN8l2yFZ158for3EQpowyYsOhYpRdnoE0R+/22ZLvROOmpSU75vcwypO5xMxFdo7vq6xxwFqu +zZKudOuoKs2iB4QjQOhwLP/gtbae7rZiuq6ptjeZNmX7tNebTLSHVe38lOPAP/vY35X24IrCLt2S +mcRf9XOq6ZtIs8iLahHipc/Aamt1lm2/Yp/auicnE5HPP4PEuBiYzCaEh0tjLu3PYN3TEXoVuViq +cbyGrDdPxNOy5CHp2QyE7f8UNxvc72RW2Mm1ihOA29+qWUlQfmR6BIaLJ1D4zQn878+30KVLF9xt +qGcX0K6k0J1jRMYEeyIjHH40JR19KZmpOHwSFkpmhBrUplfd35qfjguqkD57ijOREbaDwmNpsLP9 +olgf7Yt73aPND0BRr+UmLguaaHhxmJDICEtQLMZNiMfBHRfs2378VY57QbA6Zyb1rH6wxr3lOs6K +TeA6DnyTgzN37deGLtRHKw7FVV+B0Plrf0NG23VDsT6YnJUX0osfu79Dxv8GOLQNRSWVSBPe2rHV +YB9dc4HhGDHQca3UxM/uj75c5Iw8rDPHgdbrJA3Hf/UBVogqB2P5XymR8Tys4MEo/3eptzcV+3yo +N0WrWTnLhCjGgT/2hfbDw/7fymWWtq3qkMwIN7EYmGXzLIK62qentkJ7Dmbsn46Zo05Rd+kZFGyk +/w5bU//1Lcwa21/hjYI2p3xZ018vAxdqYOK3D4mXS1sLN6M3OVFRXUe3mUEuPTDSJCqX8j44fLqA +es8KhYsf5VKx8TCRIS3/d8cHSfJTTOguD1RbM3V5uy5a9ar5GxxiT+mMXdVCmaE+XE1V3GL3g0Gv +ozLND0e7xMG9VuHc+7OocWaygtUPKscU91KQmyktratDg8yIoQPiYe1pdvJib78M9SHTw7qqJz8w ++GtIGIHU0G2UwBSjlpIZ0/mDKCNj+05NRS/JaA38hIRWz+u4ZILir4Y4YIoXj8rOYN/RK5g98hGP +Rzt+pwf7NNUbgwesnEVRDHHgo33ig7jR5P0lIgZXlIqo3QGUznU91jZ87bpf61aQCWl/eBfjZjXh +Wn0dzh7dg62F5diXuxHJIz6ENPKkVaxq+UDpVeJCx8ShePfx+LvN4li4uW9vl0SmzYe7NODkx0Jv +qP2PkMiEplD36xxn96u16r/x2oqdfghuQYvwgCx1GXUJx2O0WUH+ifb6rNe7v9INv5U1b/YLnAON +L34o6XUcu3NbNrmIVAUbJZAOvQH/8c6ZSTWrH1SOKe4lZt3HYslb45RN0Np+JdnKUjUe1YcfWPyl +F55TnomnyfblKKtqRJ/9B8hWI9JGDmizWfKRSZ7jNOmcNin+rZE898ubU6B0zL2A+/WPtZxTMBA2 +6hW8P6Mr3l9I8602voeEuL86hyllxdhXJRvYz1AsqWgf6RIXLfWmpE2yXY2zXIZkg3yftC4d02Sf +EVP/YxOmSjIC8KtpArBe+g1B9ueYkGAPF2ubVbzxBRkj0CtuEFJnvI7X0qJItZib+2VCR+n1arQx +CoNC6ei1gzgpe+y8VLJXfBvIZHTPNe/Zh5munUKdkED7utju2S8w/eKciQxNtcS3+d/6KpHOE+ry +OkrLapwyGs+WoYK2zMMG2IcCNOtV9ze6n314c9fXB8QhB6dyetK0WKVW17ZXlzXNfqhoNXRFNBW5 +tX+fbPJ1E04d9n+ISdCsGPeiaeqcxWJqf1j9YI17Q6jIBZXf4Uf5TFfRDrf6DeB1Q81tUA+0+NTp +b7vU4i8ZlTBivPhCwPaNK7H5EA24xY7Hk455RaLNGuWp++lDCWNvDDXTeTdui30/LhJY44C1nEx4 +j2jqATA9gQULhCTYguwlmaj153Kg5IdMr7Cq3t7osztK9uldbz7wc3PJddMn+yy4dO4sTp87j0Z/ +7l2ulrhsud8tXQ66bFBW508s2JrP40BJFVqNIWi9Qm/R0HL820JEVhthae2G4eNHoyd1X53bthSr +C7siffpY/DLOhObKE8gvEl7JHIwwD7mPKEjhT0fpVTBJdsiM0S8Mw57ccmQtXoHrr05ExNVjyN4h +8IlCxuj+srLCagT6D6LErqYWGz7OwoRkupHTNWzI+AntXkF2O9F10xGM1afykEnjeKm/DMdPO3Kw +p1KYgeDfUpa7DDmWlzHIeAVbc4tFYRmjHU+LmvWq+2tKfg6TzKXYdSof81bWYm76UIS0/Izvs7ch +ir4wPduPL+d6JaHZD6+S7AeC4jBhagzKqN5XvJeFuVOScP2Hv2F7W16oIqD9Yda4t5+pzrm9Bg97 +mP1gjHuaN/Tiqyko21iKtQsXY9JLk9GPPlF/4+oZFO0oxZ0xC7H+ZZovQove1w0P3ins0osfu7+i +MT0T8UwsUFBzXZxDlJw+0rULXwM/Bef8PBSKfv1p7JkmK/9l5S2MGRBGHUiPY/KkJ+jxhzEOmMvJ +TBWzS/qca+IsLEg7h7VF5Vi6thhZi8Y5O45lpRlWlfygeyPj/c2pSMk+3euNlbPTOuUVX+yjN5Iz +V68BvaQcsC//sycz3UOc49OungoZhudUSz4B2Fp/kj5v7TqEUX1oJ3IOCdKiaLIoJTMU85GxifS0 +UYw9X+Vhj6QoNB6z//x7nyZxdZRee0+FOpeYsfPwhiUTH1F3ccHG/3J4HI+5y15Hon1mo0RB/H38 +hdcx7cYn2H7sCLZXHrEfG/o0JTPyySoup7TfoGD83bKXUf/OZpQV5tF/oUg80tMM2FN0hiZu+76Y +B0RR1/dm7HOImDTvPaRK3xLyQa+6vyZMX/U+IrPX0TyrUmTT6/D2JQZzI+XZL1ucOk5W/tHkB5ve +gdMWY279h8g+dATZmUK9RuGpUTQBmF61lb62rWyU61HWuJfOUucslVT+ZfWDNe57jpyDVcEmrMnc +iV1fbG5THhqDaU887Nxmv26w1YdTMOOKXvxY/bWbZcTwKSkoyBRiPh4TnnyknbXs8gLDhWayI2nu +Msy4uw4FdM3aVUkmmkORQcmMsLDGAWu5IMfVq6usVztx5kJMOruIHnjy8GXZUMxOFrqKtC7KfrC2 +N1b72OuNzQ9Wfqz3Lc32Ob6kLSQzkarzG9l8ci/10KW6G39339nU7D645l4i0Ns2WCy36UuoNljv +BcFkivCSSOltR0fppcze0kTdb9T3Rf/Ce5p8fHrQysOKxkZ7b4zR5J/Oqu1vY8UOA5Z+thRx5EvT +PRvN+aC30IRrZLtFP71y0VZLI+gNfhho+NJIyR119AV40d8PS2Oj+GhgECbKeWQXYJd0Es/qB3vc +E+uGJqpcA4KDDAj3CKfj2q9O2GRiWPyVFVdd1VueqkJNBVjjgLWcJuWdurC+9aY/P//tiwj3fwqJ +UIWdNJnp1NHFjfNAwJ7MAG9mvgvpzVAPxfguToAT4AQ4AU7ASUCvZIZ9mMmpmq9wAu0J3Gu1z9Bk +famovQS+hxPgBDgBToAT8I0A75nxjRs/y42ApaEGV24Go08CfYzP7Rjf5AQ4AU6AE+AEPBHQq2fG +YzLjSSHfxwlwApwAJ8AJcAKcQGck8IvOaBS3iRPgBDgBToAT4AQ4AVYCPJlhJcXLcQKcACfACXAC +nECnJMCTmU5ZLdwoToAT4AQ4AU6AE2AlwJMZVlK8HCfACXACnAAnwAl0SgI8memU1cKN4gQ4AU6A +E+AEOAFWAv8PxvdR0yK8jugAAAAASUVORK5CYII= + +--=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_=-- diff --git a/spec/fixtures/emails/merge_request_with_conflicting_patch.eml b/spec/fixtures/emails/merge_request_with_conflicting_patch.eml new file mode 100644 index 00000000000..ddfdfe9e24a --- /dev/null +++ b/spec/fixtures/emails/merge_request_with_conflicting_patch.eml @@ -0,0 +1,45 @@ +From: "Jake the Dog" +To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo +Subject: feature +Date: Wed, 31 Oct 2018 17:27:52 +0100 +X-Mailer: MailMate (1.12r5523) +Message-ID: <7BE7C2E6-F0D9-4C85-9F55-B2A4BA01BFEC@gitlab.com> +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_=" + +--=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_= + +This does not apply + +--=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_= +Content-Disposition: attachment; + filename=0002-This-does-not-apply-to-the-feature-branch.patch +Content-Transfer-Encoding: quoted-printable + +=46rom 00c68c2b4f954370ce82a1162bc29c13f524897e Mon Sep 17 00:00:00 2001 +From: Patch user +Date: Mon, 22 Oct 2018 11:05:48 +0200 +Subject: [PATCH] This does not apply to the `feature` branch + +--- + files/ruby/feature.rb | 5 +++++ + 1 file changed, 5 insertions(+) + create mode 100644 files/ruby/feature.rb + +diff --git a/files/ruby/feature.rb b/files/ruby/feature.rb +new file mode 100644 +index 0000000..fef26e4 +--- /dev/null ++++ b/files/ruby/feature.rb +@@ -0,0 +1,5 @@ ++class Feature ++ def bar ++ puts 'foo' ++ end ++end +-- = + +2.19.1 + +--=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_=-- diff --git a/spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml b/spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml new file mode 100644 index 00000000000..965658721cd --- /dev/null +++ b/spec/fixtures/emails/merge_request_with_patch_and_target_branch.eml @@ -0,0 +1,44 @@ +From: "Jake the Dog" +To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo +Subject: new-branch-with-a-patch +Date: Wed, 24 Oct 2018 16:39:49 +0200 +X-Mailer: MailMate (1.12r5523) +Message-ID: +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_=" + + +--=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_= + +This applies nicely to a branch freshly created from the root-ref + +The other attachments in this email are ignored + +/target_branch with-codeowners + + +--=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_= +Content-Disposition: attachment; filename=0001-A-commit-from-a-patch.patch +Content-Transfer-Encoding: quoted-printable + +=46rom 3fee0042e610fb3563e4379e316704cb1210f3de Mon Sep 17 00:00:00 2001 +From: Patch user +Date: Thu, 18 Oct 2018 13:40:35 +0200 +Subject: [PATCH] A commit from a patch + +--- + README | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/README b/README +index 3742e48..e40a3b9 100644 +--- a/README ++++ b/README +@@ -1 +1,3 @@ + Sample repo for testing gitlab features ++ ++This was applied in a patch! +-- = + +2.19.1 diff --git a/spec/fixtures/emails/valid_merge_request_with_patch.eml b/spec/fixtures/emails/valid_merge_request_with_patch.eml new file mode 100644 index 00000000000..143fa77d1fa --- /dev/null +++ b/spec/fixtures/emails/valid_merge_request_with_patch.eml @@ -0,0 +1,151 @@ +From: "Jake the Dog" +To: incoming+gitlabhq/gitlabhq+merge-request+auth_token@appmail.adventuretime.ooo +Subject: new-branch-with-a-patch +Date: Wed, 24 Oct 2018 16:39:49 +0200 +X-Mailer: MailMate (1.12r5523) +Message-ID: +MIME-Version: 1.0 +Content-Type: multipart/mixed; + boundary="=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_=" + + +--=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_= + +This applies nicely to a branch freshly created from the root-ref + +The other attachments in this email are ignored + + +--=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_= +Content-Disposition: attachment; filename=0001-A-commit-from-a-patch.patch +Content-Transfer-Encoding: quoted-printable + +=46rom 3fee0042e610fb3563e4379e316704cb1210f3de Mon Sep 17 00:00:00 2001 +From: Patch user +Date: Thu, 18 Oct 2018 13:40:35 +0200 +Subject: [PATCH] A commit from a patch + +--- + README | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/README b/README +index 3742e48..e40a3b9 100644 +--- a/README ++++ b/README +@@ -1 +1,3 @@ + Sample repo for testing gitlab features ++ ++This was applied in a patch! +-- = + +2.19.1 + + +--=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_= +Content-Disposition: attachment; filename=really-not-a-patch.png +Content-Type: image/png +Content-Transfer-Encoding: base64 + +iVBORw0KGgoAAAANSUhEUgAAAjMAAAAfCAYAAAASo0ymAAABfGlDQ1BJQ0MgUHJvZmlsZQAAKJFj +YGAqSSwoyGFhYGDIzSspCnJ3UoiIjFJgv8PAzcDDIMRgxSCemFxc4BgQ4MOAE3y7xsAIoi/rgsxK +8/x506a1fP4WNq+ZclYlOrj1gQF3SmpxMgMDIweQnZxSnJwLZOcA2TrJBUUlQPYMIFu3vKQAxD4B +ZIsUAR0IZN8BsdMh7A8gdhKYzcQCVhMS5AxkSwDZAkkQtgaInQ5hW4DYyRmJKUC2B8guiBvAgNPD +RcHcwFLXkYC7SQa5OaUwO0ChxZOaFxoMcgcQyzB4MLgwKDCYMxgwWDLoMjiWpFaUgBQ65xdUFmWm +Z5QoOAJDNlXBOT+3oLQktUhHwTMvWU9HwcjA0ACkDhRnEKM/B4FNZxQ7jxDLX8jAYKnMwMDcgxBL +msbAsH0PA4PEKYSYyjwGBn5rBoZt5woSixLhDmf8xkKIX5xmbARh8zgxMLDe+///sxoDA/skBoa/ +E////73o//+/i4H2A+PsQA4AJHdp4IxrEg8AAAGcaVRYdFhNTDpjb20uYWRvYmUueG1wAAAAAAA8 +eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJYTVAgQ29yZSA1LjQu +MCI+CiAgIDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1y +ZGYtc3ludGF4LW5zIyI+CiAgICAgIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiCiAgICAg +ICAgICAgIHhtbG5zOmV4aWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20vZXhpZi8xLjAvIj4KICAgICAg +ICAgPGV4aWY6UGl4ZWxYRGltZW5zaW9uPjU2MzwvZXhpZjpQaXhlbFhEaW1lbnNpb24+CiAgICAg +ICAgIDxleGlmOlBpeGVsWURpbWVuc2lvbj4zMTwvZXhpZjpQaXhlbFlEaW1lbnNpb24+CiAgICAg +IDwvcmRmOkRlc2NyaXB0aW9uPgogICA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgpZ5vHZAAAS+ElE +QVR4Ae2cC1QUV5rH/xnA1uZli02MiEHQqBMdTGB8ZIniA5xgfCTRycb1rBmdTHJGJ+toPBtzzGg2 +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 +--=_MailMate_D2C4B06A-4F8D-4AAB-B247-C507E35AAED6_=-- diff --git a/spec/fixtures/patchfiles/0001-A-commit-from-a-patch.patch b/spec/fixtures/patchfiles/0001-A-commit-from-a-patch.patch new file mode 100644 index 00000000000..cc38682a0ab --- /dev/null +++ b/spec/fixtures/patchfiles/0001-A-commit-from-a-patch.patch @@ -0,0 +1,19 @@ +From 3fee0042e610fb3563e4379e316704cb1210f3de Mon Sep 17 00:00:00 2001 +From: Patch User +Date: Thu, 18 Oct 2018 13:40:35 +0200 +Subject: [PATCH] A commit from a patch + +--- + README | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/README b/README +index 3742e48..e40a3b9 100644 +--- a/README ++++ b/README +@@ -1 +1,3 @@ + Sample repo for testing gitlab features ++ ++This was applied in a patch! +-- +2.19.1 diff --git a/spec/fixtures/patchfiles/0001-This-does-not-apply-to-the-feature-branch.patch b/spec/fixtures/patchfiles/0001-This-does-not-apply-to-the-feature-branch.patch new file mode 100644 index 00000000000..905002ae898 --- /dev/null +++ b/spec/fixtures/patchfiles/0001-This-does-not-apply-to-the-feature-branch.patch @@ -0,0 +1,23 @@ +From 00c68c2b4f954370ce82a1162bc29c13f524897e Mon Sep 17 00:00:00 2001 +From: Patch User +Date: Mon, 22 Oct 2018 11:05:48 +0200 +Subject: [PATCH] This does not apply to the `feature` branch + +--- + files/ruby/feature.rb | 5 +++++ + 1 file changed, 5 insertions(+) + create mode 100644 files/ruby/feature.rb + +diff --git a/files/ruby/feature.rb b/files/ruby/feature.rb +new file mode 100644 +index 0000000..fef26e4 +--- /dev/null ++++ b/files/ruby/feature.rb +@@ -0,0 +1,5 @@ ++class Feature ++ def bar ++ puts 'foo' ++ end ++end +-- +2.19.1 diff --git a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb index ace3104f36f..f276f1a8ddf 100644 --- a/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_merge_request_handler_spec.rb @@ -93,5 +93,74 @@ describe Gitlab::Email::Handler::CreateMergeRequestHandler do end end end + + context 'when the email contains patch attachments' do + let(:email_raw) { fixture_file("emails/valid_merge_request_with_patch.eml") } + + it 'creates the source branch and applies the patches' do + receiver.execute + + branch = project.repository.find_branch('new-branch-with-a-patch') + + expect(branch).not_to be_nil + expect(branch.dereferenced_target.message).to include('A commit from a patch') + end + + it 'creates the merge request' do + expect { receiver.execute } + .to change { project.merge_requests.where(source_branch: 'new-branch-with-a-patch').size }.by(1) + end + + it 'does not mention the patches in the created merge request' do + receiver.execute + + merge_request = project.merge_requests.find_by!(source_branch: 'new-branch-with-a-patch') + + expect(merge_request.description).not_to include('0001-A-commit-from-a-patch.patch') + end + + context 'when the patch could not be applied' do + let(:email_raw) { fixture_file("emails/merge_request_with_conflicting_patch.eml") } + + it 'raises an error' do + expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidAttachment) + end + end + + context 'when specifying the target branch using quick actions' do + let(:email_raw) { fixture_file('emails/merge_request_with_patch_and_target_branch.eml') } + + it 'creates the merge request with the correct target branch' do + receiver.execute + + merge_request = project.merge_requests.find_by!(source_branch: 'new-branch-with-a-patch') + + expect(merge_request.target_branch).to eq('with-codeowners') + end + + it 'based the merge request of the target_branch' do + receiver.execute + + merge_request = project.merge_requests.find_by!(source_branch: 'new-branch-with-a-patch') + + expect(merge_request.diff_base_commit).to eq(project.repository.commit('with-codeowners')) + end + end + end + end + + describe '#patch_attachments' do + let(:email_raw) { fixture_file('emails/merge_request_multiple_patches.eml') } + let(:mail) { Mail::Message.new(email_raw) } + subject(:handler) { described_class.new(mail, mail_key) } + + it 'orders attachments ending in `.patch` by name' do + expected_filenames = ["0001-A-commit-from-a-patch.patch", + "0002-This-does-not-apply-to-the-feature-branch.patch"] + + attachments = handler.__send__(:patch_attachments).map(&:filename) + + expect(attachments).to eq(expected_filenames) + end end end diff --git a/spec/lib/gitlab/git/patches/collection_spec.rb b/spec/lib/gitlab/git/patches/collection_spec.rb new file mode 100644 index 00000000000..080be141c59 --- /dev/null +++ b/spec/lib/gitlab/git/patches/collection_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::Git::Patches::Collection do + let(:patches_folder) { Rails.root.join('spec/fixtures/patchfiles') } + let(:patch_content1) do + File.read(File.join(patches_folder, "0001-This-does-not-apply-to-the-feature-branch.patch")) + end + let(:patch_content2) do + File.read(File.join(patches_folder, "0001-A-commit-from-a-patch.patch")) + end + + subject(:collection) { described_class.new([patch_content1, patch_content2]) } + + describe '#size' do + it 'combines the size of the patches' do + expect(collection.size).to eq(549.bytes + 424.bytes) + end + end + + describe '#valid_size?' do + it 'is not valid if the total size is bigger than 2MB' do + expect(collection).to receive(:size).and_return(2500.kilobytes) + + expect(collection).not_to be_valid_size + end + end +end diff --git a/spec/lib/gitlab/git/patches/commit_patches_spec.rb b/spec/lib/gitlab/git/patches/commit_patches_spec.rb new file mode 100644 index 00000000000..760112155ce --- /dev/null +++ b/spec/lib/gitlab/git/patches/commit_patches_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::Git::Patches::CommitPatches do + describe '#commit' do + let(:patches) do + patches_folder = Rails.root.join('spec/fixtures/patchfiles') + content_1 = File.read(File.join(patches_folder, "0001-This-does-not-apply-to-the-feature-branch.patch")) + content_2 = File.read(File.join(patches_folder, "0001-A-commit-from-a-patch.patch")) + + Gitlab::Git::Patches::Collection.new([content_1, content_2]) + end + let(:user) { build(:user) } + let(:branch_name) { 'branch-with-patches' } + let(:repository) { create(:project, :repository).repository } + + subject(:commit_patches) do + described_class.new(user, repository, branch_name, patches) + end + + it 'applies the patches' do + new_rev = commit_patches.commit + + expect(repository.commit(new_rev)).not_to be_nil + end + + it 'updates the branch cache' do + expect(repository).to receive(:after_create_branch) + + commit_patches.commit + end + + context 'when the repository does not exist' do + let(:repository) { create(:project).repository } + + it 'raises the correct error' do + expect { commit_patches.commit }.to raise_error(Gitlab::Git::Repository::NoRepository) + end + end + + context 'when the patch does not apply' do + let(:branch_name) { 'feature' } + + it 'raises the correct error' do + expect { commit_patches.commit }.to raise_error(Gitlab::Git::CommandError) + end + end + end +end diff --git a/spec/lib/gitlab/git/patches/patch_spec.rb b/spec/lib/gitlab/git/patches/patch_spec.rb new file mode 100644 index 00000000000..7466e853b65 --- /dev/null +++ b/spec/lib/gitlab/git/patches/patch_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Gitlab::Git::Patches::Patch do + let(:patches_folder) { Rails.root.join('spec/fixtures/patchfiles') } + let(:patch_content) do + File.read(File.join(patches_folder, "0001-This-does-not-apply-to-the-feature-branch.patch")) + end + let(:patch) { described_class.new(patch_content) } + + describe '#size' do + it 'is correct' do + expect(patch.size).to eq(549.bytes) + end + end +end diff --git a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb index eaf64e3c9b4..b37fe2686b6 100644 --- a/spec/lib/gitlab/gitaly_client/operation_service_spec.rb +++ b/spec/lib/gitlab/gitaly_client/operation_service_spec.rb @@ -335,4 +335,37 @@ describe Gitlab::GitalyClient::OperationService do end end end + + describe '#user_commit_patches' do + let(:patches_folder) { Rails.root.join('spec/fixtures/patchfiles') } + let(:patch_content) do + patch_names.map { |name| File.read(File.join(patches_folder, name)) }.join("\n") + end + let(:patch_names) { %w(0001-This-does-not-apply-to-the-feature-branch.patch) } + let(:branch_name) { 'branch-with-patches' } + + subject(:commit_patches) do + client.user_commit_patches(user, branch_name, patch_content) + end + + it 'applies the patch correctly' do + branch_update = commit_patches + + expect(branch_update).to be_branch_created + + commit = repository.commit(branch_update.newrev) + expect(commit.author_email).to eq('patchuser@gitlab.org') + expect(commit.committer_email).to eq(user.email) + expect(commit.message.chomp).to eq('This does not apply to the `feature` branch') + end + + context 'when the patch could not be applied' do + let(:patch_names) { %w(0001-This-does-not-apply-to-the-feature-branch.patch) } + let(:branch_name) { 'feature' } + + it 'raises the correct error' do + expect { commit_patches }.to raise_error(GRPC::FailedPrecondition) + end + end + end end diff --git a/spec/services/commits/commit_patch_service_spec.rb b/spec/services/commits/commit_patch_service_spec.rb new file mode 100644 index 00000000000..f4fcec2fbc2 --- /dev/null +++ b/spec/services/commits/commit_patch_service_spec.rb @@ -0,0 +1,92 @@ +# frozen_string_literal: true +require 'spec_helper' + +describe Commits::CommitPatchService do + describe '#execute' do + let(:patches) do + patches_folder = Rails.root.join('spec/fixtures/patchfiles') + content_1 = File.read(File.join(patches_folder, "0001-This-does-not-apply-to-the-feature-branch.patch")) + content_2 = File.read(File.join(patches_folder, "0001-A-commit-from-a-patch.patch")) + + [content_1, content_2] + end + let(:user) { project.creator } + let(:branch_name) { 'branch-with-patches' } + let(:project) { create(:project, :repository) } + let(:start_branch) { nil } + let(:params) { { branch_name: branch_name, patches: patches, start_branch: start_branch } } + + subject(:service) do + described_class.new(project, user, params) + end + + it 'returns a successful result' do + result = service.execute + + branch = project.repository.find_branch(branch_name) + + expect(result[:status]).to eq(:success) + expect(result[:result]).to eq(branch.target) + end + + it 'is based off HEAD when no start ref is passed' do + service.execute + + merge_base = project.repository.merge_base(project.repository.root_ref, branch_name) + + expect(merge_base).to eq(project.repository.commit('HEAD').sha) + end + + context 'when specifying a different start branch' do + let(:start_branch) { 'with-codeowners' } + + it 'is based of the correct branch' do + service.execute + + merge_base = project.repository.merge_base(start_branch, branch_name) + + expect(merge_base).to eq(project.repository.commit(start_branch).sha) + end + end + + shared_examples 'an error response' do |expected_message| + it 'returns the correct error' do + result = service.execute + + expect(result[:status]).to eq(:error) + expect(result[:message]).to match(expected_message) + end + end + + context 'when the user does not have access' do + let(:user) { create(:user) } + + it_behaves_like 'an error response', + 'You are not allowed to push into this branch' + end + + context 'when the patches are not valid' do + let(:patches) { "a" * 2.1.megabytes } + + it_behaves_like 'an error response', 'Patches are too big' + end + + context 'when the new branch name is invalid' do + let(:branch_name) { 'HEAD' } + + it_behaves_like 'an error response', 'Branch name is invalid' + end + + context 'when the patches do not apply' do + let(:branch_name) { 'feature' } + + it_behaves_like 'an error response', 'Patch failed at' + end + + context 'when specifying a non existent start branch' do + let(:start_branch) { 'does-not-exist' } + + it_behaves_like 'an error response', 'Invalid reference name' + end + end +end diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 9f1da7d9419..c9a668994eb 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -392,5 +392,13 @@ describe MergeRequests::BuildService do expect(merge_request.source_project).to eq(project) end end + + context 'when specifying target branch in the description' do + let(:description) { "A merge request targeting another branch\n\n/target_branch with-codeowners" } + + it 'sets the attribute from the quick actions' do + expect(merge_request.target_branch).to eq('with-codeowners') + end + end end end diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb index e4e77c667b3..045135255d6 100644 --- a/spec/workers/email_receiver_worker_spec.rb +++ b/spec/workers/email_receiver_worker_spec.rb @@ -46,6 +46,21 @@ describe EmailReceiverWorker, :mailer do should_not_email_anyone end end + + context 'when the error is Gitlab::Email::InvalidAttachment' do + let(:error) { Gitlab::Email::InvalidAttachment.new("Could not deal with that") } + + it 'reports the error to the sender' do + perform_enqueued_jobs do + described_class.new.perform(raw_message) + end + + email = ActionMailer::Base.deliveries.last + expect(email).not_to be_nil + expect(email.to).to eq(["jake@adventuretime.ooo"]) + expect(email.body.parts.last.to_s).to include("Could not deal with that") + end + end end end -- cgit v1.2.1 From c85a19f920da1b544bbfae344145503c25e71048 Mon Sep 17 00:00:00 2001 From: Bob Van Landuyt Date: Wed, 7 Nov 2018 13:33:42 +0100 Subject: Allow limiting quick actions to execute Sometimes we don't want to trigger any quick actions that cause side effects. For example when building a record to validate. This allows listing the quick actions that need to be performed. --- app/services/issuable_base_service.rb | 4 ++-- app/services/merge_requests/build_service.rb | 4 +++- app/services/quick_actions/interpret_service.rb | 4 ++-- lib/gitlab/quick_actions/extractor.rb | 14 +++++++++----- spec/lib/gitlab/quick_actions/extractor_spec.rb | 19 +++++++++++++++++++ spec/services/quick_actions/interpret_service_spec.rb | 9 +++++++++ 6 files changed, 44 insertions(+), 10 deletions(-) diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index c388913ae65..e32e262ac31 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -126,12 +126,12 @@ class IssuableBaseService < BaseService merge_quick_actions_into_params!(issuable) end - def merge_quick_actions_into_params!(issuable) + def merge_quick_actions_into_params!(issuable, only: nil) original_description = params.fetch(:description, issuable.description) description, command_params = QuickActions::InterpretService.new(project, current_user) - .execute(original_description, issuable) + .execute(original_description, issuable, only: only) # Avoid a description already set on an issuable to be overwritten by a nil params[:description] = description if description diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 6c9e566109a..6c69452e2ab 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -7,7 +7,9 @@ module MergeRequests def execute @params_issue_iid = params.delete(:issue_iid) self.merge_request = MergeRequest.new - merge_quick_actions_into_params!(merge_request) + # TODO: this should handle all quick actions that don't have side effects + # https://gitlab.com/gitlab-org/gitlab-ce/issues/53658 + merge_quick_actions_into_params!(merge_request, only: [:target_branch]) merge_request.assign_attributes(params) merge_request.author = current_user diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index eb431c36807..9c81de7e90e 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -23,13 +23,13 @@ module QuickActions # Takes a text and interprets the commands that are extracted from it. # Returns the content without commands, and hash of changes to be applied to a record. - def execute(content, issuable) + def execute(content, issuable, only: nil) return [content, {}] unless current_user.can?(:use_quick_actions) @issuable = issuable @updates = {} - content, commands = extractor.extract_commands(content) + content, commands = extractor.extract_commands(content, only: only) extract_updates(commands) [content, @updates] diff --git a/lib/gitlab/quick_actions/extractor.rb b/lib/gitlab/quick_actions/extractor.rb index 30c6806b68e..59f8dd889aa 100644 --- a/lib/gitlab/quick_actions/extractor.rb +++ b/lib/gitlab/quick_actions/extractor.rb @@ -29,7 +29,7 @@ module Gitlab # commands = extractor.extract_commands(msg) #=> [['labels', '~foo ~"bar baz"']] # msg #=> "hello\nworld" # ``` - def extract_commands(content) + def extract_commands(content, only: nil) return [content, []] unless content content = content.dup @@ -37,7 +37,7 @@ module Gitlab commands = [] content.delete!("\r") - content.gsub!(commands_regex) do + content.gsub!(commands_regex(only: only)) do if $~[:cmd] commands << [$~[:cmd].downcase, $~[:arg]].reject(&:blank?) '' @@ -60,8 +60,8 @@ module Gitlab # It looks something like: # # /^\/(?close|reopen|...)(?:( |$))(?[^\/\n]*)(?:\n|$)/ - def commands_regex - names = command_names.map(&:to_s) + def commands_regex(only:) + names = command_names(limit_to_commands: only).map(&:to_s) @commands_regex ||= %r{ (? @@ -133,10 +133,14 @@ module Gitlab [content, commands] end - def command_names + def command_names(limit_to_commands:) command_definitions.flat_map do |command| next if command.noop? + if limit_to_commands && (command.all_names & limit_to_commands).empty? + next + end + command.all_names end.compact end diff --git a/spec/lib/gitlab/quick_actions/extractor_spec.rb b/spec/lib/gitlab/quick_actions/extractor_spec.rb index 0166f6c2ee0..873bb359d6e 100644 --- a/spec/lib/gitlab/quick_actions/extractor_spec.rb +++ b/spec/lib/gitlab/quick_actions/extractor_spec.rb @@ -272,5 +272,24 @@ describe Gitlab::QuickActions::Extractor do expect(commands).to be_empty expect(msg).to eq expected end + + it 'limits to passed commands when they are passed' do + msg = <<~MSG.strip + Hello, we should only extract the commands passed + /reopen + /labels hello world + /power + MSG + expected_msg = <<~EXPECTED.strip + Hello, we should only extract the commands passed + /power + EXPECTED + expected_commands = [['reopen'], ['labels', 'hello world']] + + msg, commands = extractor.extract_commands(msg, only: [:open, :labels]) + + expect(commands).to eq(expected_commands) + expect(msg).to eq expected_msg + end end end diff --git a/spec/services/quick_actions/interpret_service_spec.rb b/spec/services/quick_actions/interpret_service_spec.rb index e513ee7ae44..5a7cafcb60f 100644 --- a/spec/services/quick_actions/interpret_service_spec.rb +++ b/spec/services/quick_actions/interpret_service_spec.rb @@ -1213,6 +1213,15 @@ describe QuickActions::InterpretService do end end end + + it 'limits to commands passed ' do + content = "/shrug\n/close" + + text, commands = service.execute(content, issue, only: [:shrug]) + + expect(commands).to be_empty + expect(text).to eq("#{described_class::SHRUG}\n/close") + end end describe '#explain' do -- cgit v1.2.1