summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue74
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue17
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js5
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb1
-rw-r--r--app/controllers/projects/merge_requests_controller.rb4
-rw-r--r--app/helpers/merge_requests_helper.rb9
-rw-r--r--app/models/merge_request.rb7
-rw-r--r--app/models/repository.rb8
-rw-r--r--app/serializers/merge_request_widget_entity.rb1
-rw-r--r--app/services/merge_requests/merge_service.rb17
-rw-r--r--app/services/merge_requests/squash_service.rb28
-rw-r--r--app/views/projects/merge_requests/show.html.haml2
-rw-r--r--app/views/shared/issuable/form/_merge_params.html.haml9
13 files changed, 152 insertions, 30 deletions
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue
index 926a3172412..875c3323dbb 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_squash_before_merge.vue
@@ -1,15 +1,63 @@
-/*
-The squash-before-merge button is EE only, but it's located right in the middle
-of the readyToMerge state component template.
-
-If we didn't declare this component in CE, we'd need to maintain a separate copy
-of the readyToMergeState template in EE, which is pretty big and likely to change.
-
-Instead, in CE, we declare the component, but it's hidden and is configured to do nothing.
-In EE, the configuration extends this object to add a functioning squash-before-merge
-button.
-*/
-
<script>
- export default {};
+import Icon from '~/vue_shared/components/icon.vue';
+import eventHub from '~/vue_merge_request_widget/event_hub';
+import tooltip from '~/vue_shared/directives/tooltip';
+
+export default {
+ components: {
+ Icon,
+ },
+ directives: {
+ tooltip,
+ },
+ props: {
+ mr: {
+ type: Object,
+ required: true,
+ },
+ isMergeButtonDisabled: {
+ type: Boolean,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ squashBeforeMerge: this.mr.squash,
+ };
+ },
+ methods: {
+ updateSquashModel() {
+ eventHub.$emit('MRWidgetUpdateSquash', this.squashBeforeMerge);
+ },
+ },
+};
</script>
+
+<template>
+ <div class="accept-control inline">
+ <label class="merge-param-checkbox">
+ <input
+ type="checkbox"
+ name="squash"
+ class="qa-squash-checkbox"
+ :disabled="isMergeButtonDisabled"
+ v-model="squashBeforeMerge"
+ @change="updateSquashModel"
+ />
+ {{ __('Squash commits') }}
+ </label>
+ <a
+ :href="mr.squashBeforeMergeHelpPath"
+ data-title="About this feature"
+ data-placement="bottom"
+ target="_blank"
+ rel="noopener noreferrer nofollow"
+ data-container="body"
+ v-tooltip
+ >
+ <icon
+ name="question-o"
+ />
+ </a>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index 1d1c8ebc179..3a194320bd8 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -6,11 +6,13 @@ import MergeRequest from '../../../merge_request';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub';
+import SquashBeforeMerge from './mr_widget_squash_before_merge.vue';
export default {
name: 'ReadyToMerge',
components: {
statusIcon,
+ 'squash-before-merge': SquashBeforeMerge,
},
props: {
mr: { type: Object, required: true },
@@ -101,6 +103,12 @@ export default {
return enableSquashBeforeMerge && commitsCount > 1;
},
},
+ created() {
+ eventHub.$on('MRWidgetUpdateSquash', this.handleUpdateSquash);
+ },
+ beforeDestroy() {
+ eventHub.$off('MRWidgetUpdateSquash', this.handleUpdateSquash);
+ },
methods: {
shouldShowMergeControls() {
return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText;
@@ -128,13 +136,9 @@ export default {
commit_message: this.commitMessage,
merge_when_pipeline_succeeds: this.setToMergeWhenPipelineSucceeds,
should_remove_source_branch: this.removeSourceBranch === true,
+ squash: this.mr.squash,
};
- // Only truthy in EE extension of this component
- if (this.setAdditionalParams) {
- this.setAdditionalParams(options);
- }
-
this.isMakingRequest = true;
this.service.merge(options)
.then(res => res.data)
@@ -154,6 +158,9 @@ export default {
new Flash('Something went wrong. Please try again.'); // eslint-disable-line
});
},
+ handleUpdateSquash(val) {
+ this.mr.squash = val;
+ },
initiateMergePolling() {
simplePoll((continuePolling, stopPolling) => {
this.handleMergePolling(continuePolling, stopPolling);
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index 83b7b054e6f..e5b7e1f1c68 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -15,6 +15,11 @@ export default class MergeRequestStore {
const currentUser = data.current_user;
const pipelineStatus = data.pipeline ? data.pipeline.details.status : null;
+ this.squash = data.squash;
+ this.squashBeforeMergeHelpPath = this.squashBeforeMergeHelpPath ||
+ data.squash_before_merge_help_path;
+ this.enableSquashBeforeMerge = this.enableSquashBeforeMerge || true;
+
this.title = data.title;
this.targetBranch = data.target_branch;
this.sourceBranch = data.source_branch;
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index 67d4ea2ca8f..29632bef7e5 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -24,6 +24,7 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
:source_branch,
:source_project_id,
:state_event,
+ :squash,
:target_branch,
:target_project_id,
:task_num,
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 62b739918e6..507a07c6e1b 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -253,7 +253,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
end
def merge_params_attributes
- [:should_remove_source_branch, :commit_message]
+ [:should_remove_source_branch, :commit_message, :squash]
end
def merge_when_pipeline_succeeds_active?
@@ -282,7 +282,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
return :sha_mismatch if params[:sha] != @merge_request.diff_head_sha
- @merge_request.update(merge_error: nil)
+ @merge_request.update(merge_error: nil, squash: merge_params.fetch(:squash, false))
if params[:merge_when_pipeline_succeeds].present?
return :failed unless @merge_request.actual_head_pipeline
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index c19c5b9cc82..74251c260f0 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -97,8 +97,9 @@ module MergeRequestsHelper
{
merge_when_pipeline_succeeds: true,
should_remove_source_branch: true,
- sha: merge_request.diff_head_sha
- }.merge(merge_params_ee(merge_request))
+ sha: merge_request.diff_head_sha,
+ squash: merge_request.squash
+ }
end
def tab_link_for(merge_request, tab, options = {}, &block)
@@ -149,8 +150,4 @@ module MergeRequestsHelper
current_user.fork_of(project)
end
end
-
- def merge_params_ee(merge_request)
- {}
- end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 9c4384a6e42..bc97fc3a5d9 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1140,4 +1140,11 @@ class MergeRequest < ActiveRecord::Base
maintainer_push_possible? &&
Ability.allowed?(user, :push_code, source_project)
end
+
+ def squash_in_progress?
+ # The source project can be deleted
+ return false unless source_project
+
+ source_project.repository.squash_in_progress?(id)
+ end
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 0e1bf11d7c0..6165808cd9a 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -957,6 +957,14 @@ class Repository
remote_branch: merge_request.target_branch)
end
+ def squash(user, merge_request)
+ raw.squash(user, merge_request.id, branch: merge_request.target_branch,
+ start_sha: merge_request.diff_start_sha,
+ end_sha: merge_request.diff_head_sha,
+ author: merge_request.author,
+ message: merge_request.title)
+ end
+
private
# TODO Generice finder, later split this on finders by Ref or Oid
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index d0165c148eb..141070aef45 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -10,6 +10,7 @@ class MergeRequestWidgetEntity < IssuableEntity
expose :merge_when_pipeline_succeeds
expose :source_branch
expose :source_project_id
+ expose :squash
expose :target_branch
expose :target_project_id
expose :allow_maintainer_to_push
diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb
index 2209a60a840..126da891c78 100644
--- a/app/services/merge_requests/merge_service.rb
+++ b/app/services/merge_requests/merge_service.rb
@@ -34,6 +34,19 @@ module MergeRequests
handle_merge_error(log_message: e.message, save_message_on_model: true)
end
+ def source
+ return merge_request.diff_head_sha unless merge_request.squash
+
+ squash_result = ::MergeRequests::SquashService.new(project, current_user, params).execute(merge_request)
+
+ case squash_result[:status]
+ when :success
+ squash_result[:squash_sha]
+ when :error
+ raise ::MergeRequests::MergeService::MergeError, squash_result[:message]
+ end
+ end
+
private
def error_check!
@@ -116,9 +129,5 @@ module MergeRequests
def merge_request_info
merge_request.to_reference(full: true)
end
-
- def source
- @source ||= @merge_request.diff_head_sha
- end
end
end
diff --git a/app/services/merge_requests/squash_service.rb b/app/services/merge_requests/squash_service.rb
new file mode 100644
index 00000000000..a40fb2786bd
--- /dev/null
+++ b/app/services/merge_requests/squash_service.rb
@@ -0,0 +1,28 @@
+module MergeRequests
+ class SquashService < MergeRequests::WorkingCopyBaseService
+ def execute(merge_request)
+ @merge_request = merge_request
+ @repository = target_project.repository
+
+ squash || error('Failed to squash. Should be done manually.')
+ end
+
+ def squash
+ if merge_request.commits_count < 2
+ return success(squash_sha: merge_request.diff_head_sha)
+ end
+
+ if merge_request.squash_in_progress?
+ return error('Squash task canceled: another squash is already in progress.')
+ end
+
+ squash_sha = repository.squash(current_user, merge_request)
+
+ success(squash_sha: squash_sha)
+ rescue => e
+ log_error("Failed to squash merge request #{merge_request.to_reference(full: true)}:")
+ log_error(e.message)
+ false
+ end
+ end
+end
diff --git a/app/views/projects/merge_requests/show.html.haml b/app/views/projects/merge_requests/show.html.haml
index aa3fb623e58..01e38ffee20 100644
--- a/app/views/projects/merge_requests/show.html.haml
+++ b/app/views/projects/merge_requests/show.html.haml
@@ -20,6 +20,8 @@
window.gl = window.gl || {};
window.gl.mrWidgetData = #{serialize_issuable(@merge_request, serializer: 'widget')}
+ window.gl.mrWidgetData.squash_before_merge_help_path = '#{help_page_path("user/project/merge_requests/squash_and_merge")}';
+
#js-vue-mr-widget.mr-widget
.content-block.content-block-small.emoji-list-container.js-noteable-awards
diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml
index 1df881e4102..90fbf19e843 100644
--- a/app/views/shared/issuable/form/_merge_params.html.haml
+++ b/app/views/shared/issuable/form/_merge_params.html.haml
@@ -15,3 +15,12 @@
= hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
= check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?
Remove source branch when merge request is accepted.
+
+.form-group
+ .col-sm-10.col-sm-offset-2
+ .checkbox
+ = label_tag 'merge_request[squash]' do
+ = hidden_field_tag 'merge_request[squash]', '0', id: nil
+ = check_box_tag 'merge_request[squash]', '1', issuable.squash
+ Squash commits when merge request is accepted.
+ = link_to 'About this feature', help_page_path('user/project/merge_requests/squash_and_merge')