diff --git a/src/config.ts b/src/config.ts index 8d777ca8..2cacdafe 100644 --- a/src/config.ts +++ b/src/config.ts @@ -82,7 +82,7 @@ class Config { const userConfig = this.config.get('contextMenuActionsVisibility', {}); const config: ContextMenuActionsVisibility = { branch: { checkout: true, rename: true, delete: true, merge: true, rebase: true, push: true, viewIssue: true, createPullRequest: true, createArchive: true, selectInBranchesDropdown: true, unselectInBranchesDropdown: true, copyName: true }, - commit: { addTag: true, createBranch: true, checkout: true, cherrypick: true, revert: true, drop: true, merge: true, rebase: true, reset: true, copyHash: true, copySubject: true }, + commit: { addTag: true, createBranch: true, checkout: true, cherrypick: true, commitFixup: true, revert: true, drop: true, merge: true, rebase: true, reset: true, copyHash: true, copySubject: true }, commitDetailsViewFile: { viewDiff: true, viewFileAtThisRevision: true, viewDiffWithWorkingFile: true, openFile: true, markAsReviewed: true, markAsNotReviewed: true, resetFileToThisRevision: true, copyAbsoluteFilePath: true, copyRelativeFilePath: true }, remoteBranch: { checkout: true, delete: true, fetch: true, merge: true, pull: true, viewIssue: true, createPullRequest: true, createArchive: true, selectInBranchesDropdown: true, unselectInBranchesDropdown: true, copyName: true }, stash: { apply: true, createBranch: true, pop: true, drop: true, copyName: true, copyHash: true }, @@ -218,7 +218,8 @@ class Config { }, rebase: { ignoreDate: !!this.config.get('dialog.rebase.ignoreDate', true), - interactive: !!this.config.get('dialog.rebase.launchInteractiveRebase', false) + interactive: !!this.config.get('dialog.rebase.launchInteractiveRebase', false), + autosquash: !!this.config.get('dialog.rebase.autosquash', false) }, resetCommit: { mode: resetCommitMode === 'Soft' ? GitResetMode.Soft : (resetCommitMode === 'Hard' ? GitResetMode.Hard : GitResetMode.Mixed) diff --git a/src/dataSource.ts b/src/dataSource.ts index 10a3429f..c364e9df 100644 --- a/src/dataSource.ts +++ b/src/dataSource.ts @@ -1001,13 +1001,17 @@ export class DataSource extends Disposable { * @param actionOn Is the rebase on a branch or commit. * @param ignoreDate Is `--ignore-date` enabled. * @param interactive Should the rebase be performed interactively. + * @param autosquash Is `--autosquash` enabled. * @returns The ErrorInfo from the executed command. */ - public rebase(repo: string, obj: string, actionOn: RebaseActionOn, ignoreDate: boolean, interactive: boolean) { + public rebase(repo: string, obj: string, actionOn: RebaseActionOn, ignoreDate: boolean, interactive: boolean, autosquash: boolean) { if (interactive) { return this.openGitTerminal( repo, - 'rebase --interactive ' + (getConfig().signCommits ? '-S ' : '') + (actionOn === RebaseActionOn.Branch ? obj.replace(/'/g, '"\'"') : obj), + 'rebase --interactive ' + + (autosquash ? '--autosquash ' : '') + + (getConfig().signCommits ? '-S ' : '') + + (actionOn === RebaseActionOn.Branch ? obj.replace(/'/g, '"\'"') : obj), 'Rebase on "' + (actionOn === RebaseActionOn.Branch ? obj : abbrevCommit(obj)) + '"' ); } else { @@ -1092,6 +1096,17 @@ export class DataSource extends Disposable { return this.runGitCommand(args, repo); } + /** + * Commit the stashed files to fixup a commit. + * @param repo The path of the repository. + * @param commitHash The hash of the commit to drop. + * @returns The ErrorInfo from the executed command. + */ + public commitFixup(repo: string, commitHash: string) { + const args = ['commit', '--fixup=' + commitHash]; + return this.runGitCommand(args, repo); + } + /** * Reset the current branch to a specified commit. * @param repo The path of the repository. diff --git a/src/gitGraphView.ts b/src/gitGraphView.ts index 1acba6ff..ed68b07c 100644 --- a/src/gitGraphView.ts +++ b/src/gitGraphView.ts @@ -251,6 +251,13 @@ export class GitGraphView extends Disposable { refresh: msg.refresh }); break; + case 'commitFixup': + this.sendMessage({ + command: 'commitFixup', + commitHash: msg.commitHash, + error: await this.dataSource.commitFixup(msg.repo, msg.commitHash) + }); + break; case 'compareCommits': this.sendMessage({ command: 'compareCommits', @@ -528,7 +535,7 @@ export class GitGraphView extends Disposable { command: 'rebase', actionOn: msg.actionOn, interactive: msg.interactive, - error: await this.dataSource.rebase(msg.repo, msg.obj, msg.actionOn, msg.ignoreDate, msg.interactive) + error: await this.dataSource.rebase(msg.repo, msg.obj, msg.actionOn, msg.ignoreDate, msg.interactive, msg.autosquash) }); break; case 'renameBranch': diff --git a/src/types.ts b/src/types.ts index c543fa9f..7712c9b7 100644 --- a/src/types.ts +++ b/src/types.ts @@ -361,6 +361,7 @@ export interface ContextMenuActionsVisibility { readonly createBranch: boolean; readonly checkout: boolean; readonly cherrypick: boolean; + readonly commitFixup: boolean; readonly revert: boolean; readonly drop: boolean; readonly merge: boolean; @@ -496,6 +497,7 @@ export interface DialogDefaults { readonly rebase: { readonly ignoreDate: boolean, readonly interactive: boolean + readonly autosquash: boolean }; readonly resetCommit: { readonly mode: GitResetMode @@ -686,6 +688,17 @@ export interface RequestCommitDetails extends RepoRequest { readonly avatarEmail: string | null; // string => fetch avatar with the given email, null => don't fetch avatar readonly refresh: boolean; } + +export interface ResponseCommitFixup extends ResponseWithErrorInfo { + readonly command: 'commitFixup'; + readonly commitHash: string; +} + +export interface RequestCommitFixup extends RepoRequest { + readonly command: 'commitFixup'; + readonly commitHash: string; +} + export interface ResponseCommitDetails extends ResponseWithErrorInfo { readonly command: 'commitDetails'; readonly commitDetails: GitCommitDetails | null; @@ -1095,6 +1108,7 @@ export interface RequestRebase extends RepoRequest { readonly actionOn: RebaseActionOn; readonly ignoreDate: boolean; readonly interactive: boolean; + readonly autosquash: boolean; } export interface ResponseRebase extends ResponseWithErrorInfo { readonly command: 'rebase'; @@ -1257,6 +1271,7 @@ export type RequestMessage = | RequestCherrypickCommit | RequestCleanUntrackedFiles | RequestCommitDetails + | RequestCommitFixup | RequestCompareCommits | RequestCopyFilePath | RequestCopyToClipboard @@ -1322,6 +1337,7 @@ export type ResponseMessage = | ResponseCleanUntrackedFiles | ResponseCompareCommits | ResponseCommitDetails + | ResponseCommitFixup | ResponseCopyFilePath | ResponseCopyToClipboard | ResponseCreateArchive diff --git a/web/main.ts b/web/main.ts index 29c23c82..17afe5b6 100644 --- a/web/main.ts +++ b/web/main.ts @@ -1108,6 +1108,10 @@ class GitGraphView { title: 'Create Branch' + ELLIPSIS, visible: visibility.createBranch, onClick: () => this.createBranchAction(hash, '', this.config.dialogDefaults.createBranch.checkout, target) + }, { + title: 'Fixup this Commit', + visible: visibility.commitFixup, + onClick: () => this.commitFixupAction(hash) } ], [ { @@ -1688,13 +1692,19 @@ class GitGraphView { private rebaseAction(obj: string, name: string, actionOn: GG.RebaseActionOn, target: DialogTarget & (CommitTarget | RefTarget)) { dialog.showForm('Are you sure you want to rebase ' + (this.gitBranchHead !== null ? '' + escapeHtml(this.gitBranchHead) + ' (the current branch)' : 'the current branch') + ' on ' + actionOn.toLowerCase() + ' ' + escapeHtml(name) + '?', [ { type: DialogInputType.Checkbox, name: 'Launch Interactive Rebase in new Terminal', value: this.config.dialogDefaults.rebase.interactive }, + { type: DialogInputType.Checkbox, name: 'Auto-squash', value: this.config.dialogDefaults.rebase.autosquash, info: 'Only applicable to an interactive rebase.' }, { type: DialogInputType.Checkbox, name: 'Ignore Date', value: this.config.dialogDefaults.rebase.ignoreDate, info: 'Only applicable to a non-interactive rebase.' } ], 'Yes, rebase', (values) => { let interactive = values[0]; - runAction({ command: 'rebase', repo: this.currentRepo, obj: obj, actionOn: actionOn, ignoreDate: values[1], interactive: interactive }, interactive ? 'Launching Interactive Rebase' : 'Rebasing on ' + actionOn); + let autosquash = interactive && values[1]; + let ignoreDate = !interactive && values[2]; + runAction({ command: 'rebase', repo: this.currentRepo, obj: obj, actionOn: actionOn, ignoreDate: ignoreDate, interactive: interactive, autosquash: autosquash }, interactive ? 'Launching Interactive Rebase' : 'Rebasing on ' + actionOn); }, target); } + private commitFixupAction(hash: string) { + runAction({ command: 'commitFixup', repo: this.currentRepo, commitHash: hash }, 'Commiting Fixup'); + } /* Table Utils */ @@ -3229,6 +3239,9 @@ window.addEventListener('load', () => { dialog.showError('Unable to load Commit Details', msg.error, null, null); } break; + case 'commitFixup': + refreshOrDisplayError(msg.error, 'Unable to Commit'); + break; case 'compareCommits': if (msg.error === null) { gitGraph.showCommitComparison(msg.commitHash, msg.compareWithHash, msg.fileChanges, gitGraph.createFileTree(msg.fileChanges, msg.codeReview), msg.codeReview, msg.codeReview !== null ? msg.codeReview.lastViewedFile : null, msg.refresh);