diff --git a/media/main.css b/media/main.css index 1cdba493..6946a809 100644 --- a/media/main.css +++ b/media/main.css @@ -481,6 +481,9 @@ svg.openFolderIcon, svg.closedFolderIcon, svg.fileIcon{ #dialog > table.dialogForm.multi input[type=checkbox]{ margin-left:0; } +#dialog > table.dialogForm.single input[type=checkbox], #dialog > table.dialogForm.multiCheckbox input[type=checkbox]{ + margin-right:5px; +} #dialog > table.dialogForm input[type=text], #dialog > table.dialogForm select{ padding:4px; box-sizing:border-box; diff --git a/src/dataSource.ts b/src/dataSource.ts index 5e75674e..e255231f 100644 --- a/src/dataSource.ts +++ b/src/dataSource.ts @@ -216,12 +216,24 @@ export class DataSource { return this.runGitCommand('branch -m ' + escapeRefName(oldName) + ' ' + escapeRefName(newName), repo); } - public mergeBranch(repo: string, branchName: string, createNewCommit: boolean) { - return this.runGitCommand('merge ' + escapeRefName(branchName) + (createNewCommit ? ' --no-ff' : ''), repo); + public async mergeBranch(repo: string, branchName: string, createNewCommit: boolean, squash: boolean) { + let mergeStatus = await this.runGitCommand('merge ' + escapeRefName(branchName) + (createNewCommit && !squash ? ' --no-ff' : '') + (squash ? ' --squash' : ''), repo); + if (mergeStatus === null && squash) { + if (await this.areStagedChanges(repo)) { + return this.runGitCommand('commit -m "Merge branch \'' + escapeRefName(branchName) + '\'"', repo); + } + } + return mergeStatus; } - public mergeCommit(repo: string, commitHash: string, createNewCommit: boolean) { - return this.runGitCommand('merge ' + commitHash + (createNewCommit ? ' --no-ff' : ''), repo); + public async mergeCommit(repo: string, commitHash: string, createNewCommit: boolean, squash: boolean) { + let mergeStatus = await this.runGitCommand('merge ' + commitHash + (createNewCommit && !squash ? ' --no-ff' : '') + (squash ? ' --squash' : ''), repo); + if (mergeStatus === null && squash) { + if (await this.areStagedChanges(repo)) { + return this.runGitCommand('commit -m "Merge commit \'' + commitHash + '\'"', repo); + } + } + return mergeStatus; } public cherrypickCommit(repo: string, commitHash: string, parentIndex: number) { @@ -299,6 +311,12 @@ export class DataSource { }); } + private areStagedChanges(repo: string) { + return new Promise(resolve => { + this.execGit('diff-index HEAD', repo, (err, stdout) => resolve(!err && stdout !== '')); + }); + } + private runGitCommand(command: string, repo: string) { return new Promise((resolve) => { this.execGit(command, repo, (err, stdout, stderr) => { diff --git a/src/gitGraphView.ts b/src/gitGraphView.ts index 74ff6a59..21b1cad9 100644 --- a/src/gitGraphView.ts +++ b/src/gitGraphView.ts @@ -187,13 +187,13 @@ export class GitGraphView { case 'mergeBranch': this.sendMessage({ command: 'mergeBranch', - status: await this.dataSource.mergeBranch(msg.repo, msg.branchName, msg.createNewCommit) + status: await this.dataSource.mergeBranch(msg.repo, msg.branchName, msg.createNewCommit, msg.squash) }); break; case 'mergeCommit': this.sendMessage({ command: 'mergeCommit', - status: await this.dataSource.mergeCommit(msg.repo, msg.commitHash, msg.createNewCommit) + status: await this.dataSource.mergeCommit(msg.repo, msg.commitHash, msg.createNewCommit, msg.squash) }); break; case 'pushTag': diff --git a/src/types.ts b/src/types.ts index c667121b..8f069a68 100644 --- a/src/types.ts +++ b/src/types.ts @@ -256,6 +256,7 @@ export interface RequestMergeBranch { repo: string; branchName: string; createNewCommit: boolean; + squash: boolean; } export interface ResponseMergeBranch { command: 'mergeBranch'; @@ -267,6 +268,7 @@ export interface RequestMergeCommit { repo: string; commitHash: string; createNewCommit: boolean; + squash: boolean; } export interface ResponseMergeCommit { command: 'mergeCommit'; diff --git a/web/main.ts b/web/main.ts index 8be5731b..64677890 100644 --- a/web/main.ts +++ b/web/main.ts @@ -399,8 +399,12 @@ class GitGraphView { { title: 'Merge into current branch' + ELLIPSIS, onClick: () => { - showCheckboxDialog('Are you sure you want to merge commit ' + abbrevCommit(hash) + ' into the current branch?', 'Create a new commit even if fast-forward is possible', true, 'Yes, merge', (createNewCommit) => { - sendMessage({ command: 'mergeCommit', repo: this.currentRepo, commitHash: hash, createNewCommit: createNewCommit }); + showFormDialog('Are you sure you want to merge commit ' + abbrevCommit(hash) + ' into the current branch?', [ + { type: 'checkbox', name: 'Create a new commit even if fast-forward is possible', value: true }, + { type: 'checkbox', name: 'Squash commits', value: false } + ], 'Yes, merge', values => { + showActionRunningDialog('Merging Commit'); + sendMessage({ command: 'mergeCommit', repo: this.currentRepo, commitHash: hash, createNewCommit: values[0] === 'checked', squash: values[1] === 'checked' }); }, null); } }, @@ -484,8 +488,12 @@ class GitGraphView { }, { title: 'Merge into current branch' + ELLIPSIS, onClick: () => { - showCheckboxDialog('Are you sure you want to merge branch ' + escapeHtml(refName) + ' into the current branch?', 'Create a new commit even if fast-forward is possible', true, 'Yes, merge', (createNewCommit) => { - sendMessage({ command: 'mergeBranch', repo: this.currentRepo, branchName: refName, createNewCommit: createNewCommit }); + showFormDialog('Are you sure you want to merge branch ' + escapeHtml(refName) + ' into the current branch?', [ + { type: 'checkbox', name: 'Create a new commit even if fast-forward is possible', value: true }, + { type: 'checkbox', name: 'Squash commits', value: false } + ], 'Yes, merge', values => { + showActionRunningDialog('Merging Branch'); + sendMessage({ command: 'mergeBranch', repo: this.currentRepo, branchName: refName, createNewCommit: values[0] === 'checked', squash: values[1] === 'checked' }); }, null); } } @@ -1030,11 +1038,22 @@ function showSelectDialog(message: string, defaultValue: string, options: { name showFormDialog(message, [{ type: 'select', name: '', options: options, default: defaultValue }], actionName, values => actioned(values[0]), sourceElem); } function showFormDialog(message: string, inputs: DialogInput[], actionName: string, actioned: (values: string[]) => void, sourceElem: HTMLElement | null) { - let textRefInput = -1, multiElementForm = inputs.length > 1; - let html = message + '
'; + let textRefInput = -1, multiElement = inputs.length > 1; + let multiCheckbox = multiElement; + + if (multiElement) { // If has multiple elements, then check if they are all checkboxes. If so, then the form is a checkbox multi + for (let i = 0; i < inputs.length; i++) { + if (inputs[i].type !== 'checkbox') { + multiCheckbox = false; + break; + } + } + } + + let html = message + '
'; for (let i = 0; i < inputs.length; i++) { let input = inputs[i]; - html += '' + (multiElementForm ? '' : '') + '' + (multiElement && !multiCheckbox ? '' : '') + ''; } html += '
' + input.name + ''; + html += '
' + input.name + ''; if (input.type === 'select') { html += ''; } else if (input.type === 'checkbox') { - html += ''; + html += ''; } else { html += ''; if (input.type === 'text-ref') textRefInput = i; @@ -1050,6 +1069,7 @@ function showFormDialog(message: string, inputs: DialogInput[], actionName: stri html += '
'; + showDialog(html, actionName, 'Cancel', () => { if (dialog.className === 'active noInput' || dialog.className === 'active inputInvalid') return; let values = [];