Commit 407fd2ed authored by Natalia Tepluhina's avatar Natalia Tepluhina Committed by Phil Hughes

Resolve "Override squash commit message"

parent 232ae06a
<script>
export default {
props: {
value: {
type: String,
required: true,
},
label: {
type: String,
required: true,
},
inputId: {
type: String,
required: true,
},
},
};
</script>
<template>
<li>
<div class="commit-message-editor">
<div class="d-flex flex-wrap align-items-center justify-content-between">
<label class="col-form-label" :for="inputId">
<strong>{{ label }}</strong>
</label>
<slot name="header"></slot>
</div>
<textarea
:id="inputId"
:value="value"
class="form-control js-gfm-input append-bottom-default commit-message-edit"
required="required"
rows="7"
@input="$emit('input', $event.target.value)"
></textarea>
<slot name="checkbox"></slot>
</div>
</li>
</template>
<script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
export default {
components: {
GlDropdown,
GlDropdownItem,
},
props: {
commits: {
type: Array,
required: true,
default: () => [],
},
},
};
</script>
<template>
<div>
<gl-dropdown
right
no-caret
text="Use an existing commit message"
variant="link"
class="mr-commit-dropdown"
>
<gl-dropdown-item
v-for="commit in commits"
:key="commit.short_id"
class="text-nowrap text-truncate"
@click="$emit('input', commit.message)"
>
<span class="monospace mr-2">{{ commit.short_id }}</span> {{ commit.title }}
</gl-dropdown-item>
</gl-dropdown>
</div>
</template>
<script>
import { GlButton } from '@gitlab/ui';
import _ from 'underscore';
import { __, n__, sprintf, s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
export default {
components: {
Icon,
GlButton,
},
props: {
isSquashEnabled: {
type: Boolean,
required: true,
},
commitsCount: {
type: Number,
required: false,
default: 0,
},
targetBranch: {
type: String,
required: true,
},
},
data() {
return {
expanded: false,
};
},
computed: {
collapseIcon() {
return this.expanded ? 'chevron-down' : 'chevron-right';
},
commitsCountMessage() {
return n__(__('%d commit'), __('%d commits'), this.isSquashEnabled ? 1 : this.commitsCount);
},
modifyLinkMessage() {
return this.isSquashEnabled ? __('Modify commit messages') : __('Modify merge commit');
},
ariaLabel() {
return this.expanded ? __('Collapse') : __('Expand');
},
message() {
return sprintf(
s__(
'mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}.',
),
{
commitCount: `<strong class="commits-count-message">${this.commitsCountMessage}</strong>`,
mergeCommitCount: `<strong>${s__('mrWidgetCommitsAdded|1 merge commit')}</strong>`,
targetBranch: `<span class="label-branch">${_.escape(this.targetBranch)}</span>`,
},
false,
);
},
},
methods: {
toggle() {
this.expanded = !this.expanded;
},
},
};
</script>
<template>
<div>
<div
class="js-mr-widget-commits-count mr-widget-extension clickable d-flex align-items-center px-3 py-2"
@click="toggle()"
>
<gl-button
:aria-label="ariaLabel"
variant="blank"
class="commit-edit-toggle mr-2"
@click.stop="toggle()"
>
<icon :name="collapseIcon" :size="16" />
</gl-button>
<span v-if="expanded">{{ __('Collapse') }}</span>
<span v-else>
<span v-html="message"></span>
<gl-button variant="link" class="modify-message-button">
{{ modifyLinkMessage }}
</gl-button>
</span>
</div>
<div v-show="expanded"><slot></slot></div>
</div>
</template>
......@@ -315,7 +315,7 @@ export default {
:endpoint="mr.testResultsPath"
/>
<div class="mr-widget-section">
<div class="mr-widget-section p-0">
<component :is="componentName" :mr="mr" :service="service" />
<section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
......
......@@ -42,6 +42,8 @@ export default class MergeRequestStore {
this.mergePipeline = data.merge_pipeline || {};
this.deployments = this.deployments || data.deployments || [];
this.postMergeDeployments = this.postMergeDeployments || [];
this.commits = data.commits_without_merge_commits || [];
this.squashCommitMessage = data.default_squash_commit_message;
this.initRebase(data);
if (data.issues_links) {
......
......@@ -391,6 +391,11 @@ img.emoji {
.flex-no-shrink { flex-shrink: 0; }
.ws-initial { white-space: initial; }
.overflow-auto { overflow: auto; }
.d-flex-center {
display: flex;
align-items: center;
justify-content: center;
}
/** COMMON SIZING CLASSES **/
.w-0 { width: 0; }
......@@ -402,6 +407,10 @@ img.emoji {
.min-height-0 { min-height: 0; }
.w-3 { width: #{3 * $grid-size}; }
.h-3 { width: #{3 * $grid-size}; }
/** COMMON SPACING CLASSES **/
.gl-pl-0 { padding-left: 0; }
.gl-pl-1 { padding-left: #{0.5 * $grid-size}; }
......
......@@ -243,6 +243,7 @@ $gl-padding-8: 8px;
$gl-padding: 16px;
$gl-padding-24: 24px;
$gl-padding-32: 32px;
$gl-padding-50: 50px;
$gl-col-padding: 15px;
$gl-input-padding: 10px;
$gl-vert-padding: 6px;
......
......@@ -38,9 +38,7 @@
}
.mr-widget-section {
.media {
align-items: center;
}
border-radius: $border-radius-default $border-radius-default 0 0;
.code-text {
flex: 1;
......@@ -56,6 +54,11 @@
.mr-widget-extension {
border-top: 1px solid $border-color;
background-color: $gray-light;
&.clickable:hover {
background-color: $gl-gray-200;
cursor: pointer;
}
}
.mr-widget-workflow {
......@@ -78,6 +81,7 @@
border-top: 0;
}
.mr-widget-body,
.mr-widget-section,
.mr-widget-content,
.mr-widget-footer {
......@@ -87,11 +91,38 @@
.mr-state-widget {
color: $gl-text-color;
.commit-message-edit {
border-radius: $border-radius-default;
}
.mr-widget-section,
.mr-widget-footer {
border-top: solid 1px $border-color;
}
.mr-fast-forward-message {
padding-left: $gl-padding-50;
padding-bottom: $gl-padding;
}
.commits-list {
> li {
padding: $gl-padding;
@include media-breakpoint-up(md) {
padding-left: $gl-padding-50;
}
}
}
.mr-commit-dropdown {
.dropdown-menu {
@include media-breakpoint-up(md) {
width: 150%;
}
}
}
.mr-widget-footer {
padding: 0;
}
......@@ -405,7 +436,7 @@
}
.mr-widget-help {
padding: 10px 16px 10px 48px;
padding: 10px 16px 10px $gl-padding-50;
font-style: italic;
}
......@@ -423,10 +454,6 @@
}
}
.mr-widget-body-controls {
flex-wrap: wrap;
}
.mr_source_commit,
.mr_target_commit {
margin-bottom: 0;
......
---
title: Default squash commit message is now selected from the longest commit when
squashing merge requests
merge_request: 24518
author:
type: changed
......@@ -23,11 +23,14 @@ The squashed commit's commit message will be either:
- Taken from the first multi-line commit message in the merge.
- The merge request's title if no multi-line commit message is found.
Note that the squashed commit is still followed by a merge commit,
as the merge method for this example repository uses a merge commit.
Squashing also works with the fast-forward merge strategy, see
[squashing and fast-forward merge](#squash-and-fast-forward-merge) for more
details.
It can be customized before merging a merge request.
![A squash commit message editor](img/squash_mr_message.png)
NOTE: **Note:**
The squashed commit in this example is followed by a merge commit, as the merge method for this example repository uses a merge commit.
Squashing also works with the fast-forward merge strategy, see [squashing and fast-forward merge](#squash-and-fast-forward-merge) for more details.
## Use cases
......@@ -60,7 +63,7 @@ This can then be overridden at the time of accepting the merge request:
The squashed commit has the following metadata:
- Message: the message of the squash commit.
- Message: the message of the squash commit, or a customized message.
- Author: the author of the merge request.
- Committer: the user who initiated the squash.
......
......@@ -32,6 +32,9 @@ msgid_plural "%d commits behind"
msgstr[0] ""
msgstr[1] ""
msgid "%d commits"
msgstr ""
msgid "%d exporter"
msgid_plural "%d exporters"
msgstr[0] ""
......@@ -2531,6 +2534,9 @@ msgstr ""
msgid "Delete list"
msgstr ""
msgid "Delete source branch"
msgstr ""
msgid "Delete this attachment"
msgstr ""
......@@ -3256,6 +3262,9 @@ msgstr ""
msgid "Failure"
msgstr ""
msgid "Fast-forward merge without a merge commit"
msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
......@@ -3905,6 +3914,9 @@ msgstr ""
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
msgid "Include merge request description"
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
......@@ -4437,9 +4449,18 @@ msgstr ""
msgid "Merge Requests"
msgstr ""
msgid "Merge commit message"
msgstr ""
msgid "Merge events"
msgstr ""
msgid "Merge immediately"
msgstr ""
msgid "Merge in progress"
msgstr ""
msgid "Merge request"
msgstr ""
......@@ -4449,6 +4470,9 @@ msgstr ""
msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
msgstr ""
msgid "Merge when pipeline succeeds"
msgstr ""
msgid "MergeRequests|Add a reply"
msgstr ""
......@@ -4605,6 +4629,12 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
msgid "Modify commit messages"
msgstr ""
msgid "Modify merge commit"
msgstr ""
msgid "Monitor your errors by integrating with Sentry"
msgstr ""
......@@ -6622,6 +6652,9 @@ msgstr ""
msgid "Something went wrong while closing the %{issuable}. Please try again later"
msgstr ""
msgid "Something went wrong while deleting the source branch. Please try again."
msgstr ""
msgid "Something went wrong while fetching comments. Please try again."
msgstr ""
......@@ -6634,6 +6667,9 @@ msgstr ""
msgid "Something went wrong while fetching the registry list."
msgstr ""
msgid "Something went wrong while merging this merge request. Please try again."
msgstr ""
msgid "Something went wrong while reopening the %{issuable}. Please try again later"
msgstr ""
......@@ -6778,6 +6814,9 @@ msgstr ""
msgid "Specify the following URL during the Runner setup:"
msgstr ""
msgid "Squash commit message"
msgstr ""
msgid "Squash commits"
msgstr ""
......@@ -8294,6 +8333,9 @@ msgstr ""
msgid "You can only edit files when you are on a branch"
msgstr ""
msgid "You can only merge once the items above are resolved"
msgstr ""
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
......@@ -8588,6 +8630,12 @@ msgstr[1] ""
msgid "missing"
msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
msgstr ""
......
......@@ -80,8 +80,8 @@ describe 'User accepts a merge request', :js do
end
it 'accepts a merge request' do
click_button('Modify commit message')
fill_in('Commit message', with: 'wow such merge')
find('.js-mr-widget-commits-count').click
fill_in('merge-message-edit', with: 'wow such merge')
click_button('Merge')
......
......@@ -13,7 +13,7 @@ describe 'Merge request < User customizes merge commit message', :js do
description: "Description\n\nclosing #{issue_1.to_reference}, #{issue_2.to_reference}"
)
end
let(:textbox) { page.find(:css, '.js-commit-message', visible: false) }
let(:textbox) { page.find(:css, '#merge-message-edit', visible: false) }
let(:default_message) do
[
"Merge branch 'feature' into 'master'",
......@@ -38,16 +38,16 @@ describe 'Merge request < User customizes merge commit message', :js do
end
it 'toggles commit message between message with description and without description' do
expect(page).not_to have_selector('.js-commit-message')
click_button "Modify commit message"
expect(page).not_to have_selector('#merge-message-edit')
first('.js-mr-widget-commits-count').click
expect(textbox).to be_visible
expect(textbox.value).to eq(default_message)
click_link "Include description in commit message"
check('Include merge request description')
expect(textbox.value).to eq(message_with_description)
click_link "Don't include description in commit message"
uncheck('Include merge request description')
expect(textbox.value).to eq(default_message)
end
......
import { createLocalVue, shallowMount } from '@vue/test-utils';
import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue';
const localVue = createLocalVue();
const testCommitMessage = 'Test commit message';
const testLabel = 'Test label';
const testInputId = 'test-input-id';
describe('Commits edit component', () => {
let wrapper;
const createComponent = (slots = {}) => {
wrapper = shallowMount(localVue.extend(CommitEdit), {
localVue,
sync: false,
propsData: {
value: testCommitMessage,
label: testLabel,
inputId: testInputId,
},
slots: {
...slots,
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
const findTextarea = () => wrapper.find('.form-control');
it('has a correct label', () => {
const labelElement = wrapper.find('.col-form-label');
expect(labelElement.text()).toBe(testLabel);
});
describe('textarea', () => {
it('has a correct ID', () => {
expect(findTextarea().attributes('id')).toBe(testInputId);
});
it('has a correct value', () => {
expect(findTextarea().element.value).toBe(testCommitMessage);
});
it('emits an input event and receives changed value', () => {
const changedCommitMessage = 'Changed commit message';
findTextarea().element.value = changedCommitMessage;
findTextarea().trigger('input');
expect(wrapper.emitted().input[0]).toEqual([changedCommitMessage]);
expect(findTextarea().element.value).toBe(changedCommitMessage);
});
});
describe('when slots are present', () => {
beforeEach(() => {
createComponent({
header: `<div class="test-header">${testCommitMessage}</div>`,
checkbox: `<label slot="checkbox" class="test-checkbox">${testLabel}</label >`,
});
});
it('renders header slot correctly', () => {
const headerSlotElement = wrapper.find('.test-header');
expect(headerSlotElement.exists()).toBe(true);
expect(headerSlotElement.text()).toBe(testCommitMessage);
});
it('renders checkbox slot correctly', () => {
const checkboxSlotElement = wrapper.find('.test-checkbox');
expect(checkboxSlotElement.exists()).toBe(true);
expect(checkboxSlotElement.text()).toBe(testLabel);
});
});
});
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { GlDropdownItem } from '@gitlab/ui';
import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue';
const localVue = createLocalVue();
const commits = [
{
title: 'Commit 1',
short_id: '78d5b7',
message: 'Update test.txt',
},
{
title: 'Commit 2',
short_id: '34cbe28b',
message: 'Fixed test',
},
{
title: 'Commit 3',
short_id: 'fa42932a',
message: 'Added changelog',
},
];
describe('Commits message dropdown component', () => {
let wrapper;
const createComponent = () => {
wrapper = shallowMount(localVue.extend(CommitMessageDropdown), {
localVue,
sync: false,
propsData: {
commits,
},
});
};
beforeEach(() => {
createComponent();
});
afterEach(() => {
wrapper.destroy();
});
const findDropdownElements = () => wrapper.findAll(GlDropdownItem);
const findFirstDropdownElement = () => findDropdownElements().at(0);
it('should have 3 elements in dropdown list', () => {
expect(findDropdownElements().length).toBe(3);
});
it('should have correct message for the first dropdown list element', () => {
expect(findFirstDropdownElement().text()).toBe('78d5b7 Commit 1');
});
it('should emit a commit title on selecting commit', () => {
findFirstDropdownElement().vm.$emit('click');
expect(wrapper.emitted().input[0]).toEqual(['Update test.txt']);
});
});
import { createLocalVue, shallowMount } from '@vue/test-utils';
import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue';
import Icon from '~/vue_shared/components/icon.vue';
const localVue = createLocalVue();
describe('Commits header component', () => {
let wrapper;
const createComponent = props => {
wrapper = shallowMount(localVue.extend(CommitsHeader), {
localVue,
sync: false,
propsData: {
isSquashEnabled: false,
targetBranch: 'master',
commitsCount: 5,
...props,
},
});
};
afterEach(() => {
wrapper.destroy();