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 aa65b16a3c3f9559e964d96d2ab8f41bad88dc21..65e523715e23444afbe00d8304f9042e2efe336c 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
@@ -3,8 +3,9 @@ import _ from 'underscore';
import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg';
import simplePoll from '~/lib/utils/simple_poll';
-import { __ } from '~/locale';
+import { __, sprintf } from '~/locale';
import readyToMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/ready_to_merge';
+import { GlIcon } from '@gitlab/ui';
import MergeRequest from '../../../merge_request';
import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests';
import Flash from '../../../flash';
@@ -24,6 +25,7 @@ export default {
CommitsHeader,
CommitEdit,
CommitMessageDropdown,
+ GlIcon,
},
mixins: [readyToMergeMixin],
props: {
@@ -111,6 +113,18 @@ export default {
shouldShowMergeEdit() {
return !this.mr.ffOnlyEnabled;
},
+ shaMismatchLink() {
+ const href = this.mr.mergeRequestDiffsPath;
+
+ return sprintf(
+ __('New changes were added. %{linkStart}Reload the page to review them%{linkEnd}'),
+ {
+ linkStart: ``,
+ linkEnd: '',
+ },
+ false,
+ );
+ },
},
methods: {
updateMergeCommitMessage(includeDescription) {
@@ -123,7 +137,7 @@ export default {
}
const options = {
- sha: this.mr.sha,
+ sha: this.mr.latestSHA || this.mr.sha,
commit_message: this.commitMessage,
auto_merge_strategy: useAutoMerge ? this.mr.preferredAutoMergeStrategy : undefined,
should_remove_source_branch: this.removeSourceBranch === true,
@@ -314,6 +328,10 @@ export default {
+
+
+
+
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index edd21a81f8bfee9859a84b3d67359051b9d31052..9686cadea770f31be3b9697afde20d7d877c3ae9 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -24,7 +24,6 @@ import NothingToMergeState from './components/states/nothing_to_merge.vue';
import MissingBranchState from './components/states/mr_widget_missing_branch.vue';
import NotAllowedState from './components/states/mr_widget_not_allowed.vue';
import ReadyToMergeState from './components/states/ready_to_merge.vue';
-import ShaMismatchState from './components/states/sha_mismatch.vue';
import UnresolvedDiscussionsState from './components/states/unresolved_discussions.vue';
import PipelineBlockedState from './components/states/mr_widget_pipeline_blocked.vue';
import PipelineFailedState from './components/states/pipeline_failed.vue';
@@ -61,7 +60,7 @@ export default {
'mr-widget-not-allowed': NotAllowedState,
'mr-widget-missing-branch': MissingBranchState,
'mr-widget-ready-to-merge': ReadyToMergeState,
- 'sha-mismatch': ShaMismatchState,
+ 'sha-mismatch': ReadyToMergeState,
'mr-widget-checking': CheckingState,
'mr-widget-unresolved-discussions': UnresolvedDiscussionsState,
'mr-widget-pipeline-blocked': PipelineBlockedState,
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 7e5b8f14b96deef9ee8f80a2329cfb0f71630e16..e0eb8de29dbb57a5a7c08d4996f18e8508cc07d4 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
@@ -48,6 +48,7 @@ export default class MergeRequestStore {
this.commits = data.commits_without_merge_commits || [];
this.squashCommitMessage = data.default_squash_commit_message;
this.rebaseInProgress = data.rebase_in_progress;
+ this.mergeRequestDiffsPath = data.diffs_path;
if (data.issues_links) {
const links = data.issues_links;
@@ -81,6 +82,7 @@ export default class MergeRequestStore {
this.isOpen = data.state === 'opened';
this.hasMergeableDiscussionsState = data.mergeable_discussions_state === false;
this.isSHAMismatch = this.sha !== data.diff_head_sha;
+ this.latestSHA = data.diff_head_sha;
this.canBeMerged = data.can_be_merged || false;
this.isMergeAllowed = data.mergeable || false;
this.mergeOngoing = data.merge_ongoing;
diff --git a/app/serializers/merge_request_poll_cached_widget_entity.rb b/app/serializers/merge_request_poll_cached_widget_entity.rb
index c7dc8c43a9ab8b17feff64e26e945822b58f375d..2f8eb6650e8ba5428b8028efd7927f6f0ddd1dda 100644
--- a/app/serializers/merge_request_poll_cached_widget_entity.rb
+++ b/app/serializers/merge_request_poll_cached_widget_entity.rb
@@ -70,6 +70,10 @@ class MergeRequestPollCachedWidgetEntity < IssuableEntity
presenter(merge_request).source_branch_with_namespace_link
end
+ expose :diffs_path do |merge_request|
+ diffs_project_merge_request_path(merge_request.project, merge_request)
+ end
+
private
delegate :current_user, to: :request
diff --git a/changelogs/unreleased/31236-auto-update-merge-widget.yml b/changelogs/unreleased/31236-auto-update-merge-widget.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b84e3e2c15b1edbff6f53c9824c3bfa8ce57f850
--- /dev/null
+++ b/changelogs/unreleased/31236-auto-update-merge-widget.yml
@@ -0,0 +1,5 @@
+---
+title: Allow merge without refresh when new commits are pushed
+merge_request: 19725
+author:
+type: changed
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 5f8c553bc80e0ad8179229c970691eae3ae8b754..12a38c3b32d67b4118ed3203e1b007b00373ba1e 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -11404,6 +11404,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New changes were added. %{linkStart}Reload the page to review them%{linkEnd}"
+msgstr ""
+
msgid "New deploy key"
msgstr ""
diff --git a/spec/fixtures/api/schemas/entities/merge_request_poll_cached_widget.json b/spec/fixtures/api/schemas/entities/merge_request_poll_cached_widget.json
index e8959307767c62d362feacdd7fc83a68c0f0d0ad..b40b71d2cd65410f27b85be5eea3ad7c53ff1038 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_poll_cached_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_poll_cached_widget.json
@@ -26,6 +26,7 @@
"has_conflicts": { "type": "boolean" },
"can_be_merged": { "type": "boolean" },
"remove_source_branch": { "type": ["boolean", "null"] },
+ "diffs_path": { "type": "string" },
"source_branch_exists": { "type": "boolean" },
"branch_missing": { "type": "boolean" },
"commits_without_merge_commits": { "type": "array" },
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index 2bb2319cc608cb1b600c314d50d422053e82311b..3a4fdaf9a208df865298d7cb38ebb8d814978777 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -938,4 +938,31 @@ describe('ReadyToMerge', () => {
expect(customVm.$el.querySelector('.js-modify-commit-message-button')).toBeNull();
});
});
+
+ describe('with a mismatched SHA', () => {
+ const findMismatchShaBlock = () => vm.$el.querySelector('.js-sha-mismatch');
+
+ beforeEach(() => {
+ vm = createComponent({
+ mr: {
+ isSHAMismatch: true,
+ mergeRequestDiffsPath: '/merge_requests/1/diffs',
+ },
+ });
+ });
+
+ it('displays a warning message', () => {
+ expect(findMismatchShaBlock()).toExist();
+ });
+
+ it('warns the user to refresh to review', () => {
+ expect(findMismatchShaBlock().textContent.trim()).toBe(
+ 'New changes were added. Reload the page to review them',
+ );
+ });
+
+ it('displays link to the diffs tab', () => {
+ expect(findMismatchShaBlock().querySelector('a').href).toContain(vm.mr.mergeRequestDiffsPath);
+ });
+ });
});