Commit 2a868635 authored by Fatih Acet's avatar Fatih Acet

Merge branch '32178-prevent-merge-on-sha-change' into 'master'

Add "SHA mismatch" state to the MR widget

Closes #32178

See merge request !11316
parents e012427c b6122aa9
export default {
name: 'MRWidgetSHAMismatch',
template: `
<div class="mr-widget-body">
<button
type="button"
class="btn btn-success btn-small"
disabled="true">
Merge
</button>
<span class="bold">
The source branch HEAD has recently changed. Please reload the page and review the changes before merging.
</span>
</div>
`,
};
...@@ -27,6 +27,7 @@ export { default as NothingToMergeState } from './components/states/mr_widget_no ...@@ -27,6 +27,7 @@ export { default as NothingToMergeState } from './components/states/mr_widget_no
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch'; export { default as MissingBranchState } from './components/states/mr_widget_missing_branch';
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed'; export { default as NotAllowedState } from './components/states/mr_widget_not_allowed';
export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge'; export { default as ReadyToMergeState } from './components/states/mr_widget_ready_to_merge';
export { default as SHAMismatchState } from './components/states/mr_widget_sha_mismatch';
export { default as UnresolvedDiscussionsState } from './components/states/mr_widget_unresolved_discussions'; export { default as UnresolvedDiscussionsState } from './components/states/mr_widget_unresolved_discussions';
export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked'; export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked';
export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed'; export { default as PipelineFailedState } from './components/states/mr_widget_pipeline_failed';
......
...@@ -16,6 +16,7 @@ import { ...@@ -16,6 +16,7 @@ import {
MissingBranchState, MissingBranchState,
NotAllowedState, NotAllowedState,
ReadyToMergeState, ReadyToMergeState,
SHAMismatchState,
UnresolvedDiscussionsState, UnresolvedDiscussionsState,
PipelineBlockedState, PipelineBlockedState,
PipelineFailedState, PipelineFailedState,
...@@ -203,6 +204,7 @@ export default { ...@@ -203,6 +204,7 @@ export default {
'mr-widget-not-allowed': NotAllowedState, 'mr-widget-not-allowed': NotAllowedState,
'mr-widget-missing-branch': MissingBranchState, 'mr-widget-missing-branch': MissingBranchState,
'mr-widget-ready-to-merge': ReadyToMergeState, 'mr-widget-ready-to-merge': ReadyToMergeState,
'mr-widget-sha-mismatch': SHAMismatchState,
'mr-widget-squash-before-merge': SquashBeforeMerge, 'mr-widget-squash-before-merge': SquashBeforeMerge,
'mr-widget-checking': CheckingState, 'mr-widget-checking': CheckingState,
'mr-widget-unresolved-discussions': UnresolvedDiscussionsState, 'mr-widget-unresolved-discussions': UnresolvedDiscussionsState,
......
...@@ -21,6 +21,8 @@ export default function deviseState(data) { ...@@ -21,6 +21,8 @@ export default function deviseState(data) {
return 'unresolvedDiscussions'; return 'unresolvedDiscussions';
} else if (this.isPipelineBlocked) { } else if (this.isPipelineBlocked) {
return 'pipelineBlocked'; return 'pipelineBlocked';
} else if (this.hasSHAChanged) {
return 'shaMismatch';
} else if (this.canBeMerged) { } else if (this.canBeMerged) {
return 'readyToMerge'; return 'readyToMerge';
} }
......
...@@ -4,6 +4,7 @@ import { getStateKey } from '../dependencies'; ...@@ -4,6 +4,7 @@ import { getStateKey } from '../dependencies';
export default class MergeRequestStore { export default class MergeRequestStore {
constructor(data) { constructor(data) {
this.startingSha = data.diff_head_sha;
this.setData(data); this.setData(data);
} }
...@@ -67,6 +68,7 @@ export default class MergeRequestStore { ...@@ -67,6 +68,7 @@ export default class MergeRequestStore {
this.canMerge = !!data.merge_path; this.canMerge = !!data.merge_path;
this.canCreateIssue = currentUser.can_create_issue || false; this.canCreateIssue = currentUser.can_create_issue || false;
this.canCancelAutomaticMerge = !!data.cancel_merge_when_pipeline_succeeds_path; this.canCancelAutomaticMerge = !!data.cancel_merge_when_pipeline_succeeds_path;
this.hasSHAChanged = this.sha !== this.startingSha;
this.canBeMerged = data.can_be_merged || false; this.canBeMerged = data.can_be_merged || false;
// Cherry-pick and Revert actions related // Cherry-pick and Revert actions related
......
...@@ -16,6 +16,7 @@ const stateToComponentMap = { ...@@ -16,6 +16,7 @@ const stateToComponentMap = {
mergeWhenPipelineSucceeds: 'mr-widget-merge-when-pipeline-succeeds', mergeWhenPipelineSucceeds: 'mr-widget-merge-when-pipeline-succeeds',
failedToMerge: 'mr-widget-failed-to-merge', failedToMerge: 'mr-widget-failed-to-merge',
autoMergeFailed: 'mr-widget-auto-merge-failed', autoMergeFailed: 'mr-widget-auto-merge-failed',
shaMismatch: 'mr-widget-sha-mismatch',
}; };
const statesToShowHelpWidget = [ const statesToShowHelpWidget = [
......
---
title: Add state to MR widget that prevent merges when branch changes after page load
merge_request: 11316
author:
import Vue from 'vue';
import shaMismatchComponent from '~/vue_merge_request_widget/components/states/mr_widget_sha_mismatch';
describe('MRWidgetSHAMismatch', () => {
describe('template', () => {
const Component = Vue.extend(shaMismatchComponent);
const vm = new Component({
el: document.createElement('div'),
});
it('should have correct elements', () => {
expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
expect(vm.$el.querySelector('button').getAttribute('disabled')).toBeTruthy();
expect(vm.$el.innerText).toContain('The source branch HEAD has recently changed. Please reload the page and review the changes before merging.');
});
});
});
...@@ -25,6 +25,9 @@ describe('getStateKey', () => { ...@@ -25,6 +25,9 @@ describe('getStateKey', () => {
context.canBeMerged = true; context.canBeMerged = true;
expect(bound()).toEqual('readyToMerge'); expect(bound()).toEqual('readyToMerge');
context.hasSHAChanged = true;
expect(bound()).toEqual('shaMismatch');
context.isPipelineBlocked = true; context.isPipelineBlocked = true;
expect(bound()).toEqual('pipelineBlocked'); expect(bound()).toEqual('pipelineBlocked');
......
import MergeRequestStore from '~/vue_merge_request_widget/stores/mr_widget_store';
import mockData from '../mock_data';
describe('MergeRequestStore', () => {
describe('setData', () => {
let store;
beforeEach(() => {
store = new MergeRequestStore(mockData);
});
it('should set hasSHAChanged when the diff SHA changes', () => {
store.setData({ ...mockData, diff_head_sha: 'a-different-string' });
expect(store.hasSHAChanged).toBe(true);
});
it('should not set hasSHAChanged when other data changes', () => {
store.setData({ ...mockData, work_in_progress: !mockData.work_in_progress });
expect(store.hasSHAChanged).toBe(false);
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment