diff --git a/package.json b/package.json index 4603016..855e01f 100644 --- a/package.json +++ b/package.json @@ -497,6 +497,11 @@ "command": "gitlens.stashApply", "title": "Apply Stashed Changes", "category": "GitLens" + }, + { + "command": "gitlens.stashSave", + "title": "Stash Changes", + "category": "GitLens" } ], "menus": { @@ -608,6 +613,10 @@ { "command": "gitlens.stashApply", "when": "gitlens:enabled && config.gitlens.insiders" + }, + { + "command": "gitlens.stashSave", + "when": "gitlens:enabled && config.gitlens.insiders" } ], "explorer/context": [ diff --git a/src/commands.ts b/src/commands.ts index 2300cd8..8536d91 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -30,5 +30,6 @@ export * from './commands/showQuickRepoStatus'; export * from './commands/showQuickStashList'; export * from './commands/stashApply'; export * from './commands/stashDelete'; +export * from './commands/stashSave'; export * from './commands/toggleBlame'; export * from './commands/toggleCodeLens'; \ No newline at end of file diff --git a/src/commands/common.ts b/src/commands/common.ts index 2fd6469..9b56fbb 100644 --- a/src/commands/common.ts +++ b/src/commands/common.ts @@ -41,6 +41,7 @@ export const Commands = { ShowQuickStashList: 'gitlens.showQuickStashList' as Commands, StashApply: 'gitlens.stashApply' as Commands, StashDelete: 'gitlens.stashDelete' as Commands, + StashSave: 'gitlens.stashSave' as Commands, ToggleBlame: 'gitlens.toggleBlame' as Commands, ToggleCodeLens: 'gitlens.toggleCodeLens' as Commands }; diff --git a/src/commands/showQuickStashList.ts b/src/commands/showQuickStashList.ts index 21adc1b..70049e5 100644 --- a/src/commands/showQuickStashList.ts +++ b/src/commands/showQuickStashList.ts @@ -21,7 +21,7 @@ export class ShowQuickStashListCommand extends ActiveEditorCachedCommand { if (!repoPath) return window.showWarningMessage(`Unable to show stashed changes`); const stash = await this.git.getStashList(repoPath); - const pick = await StashListQuickPick.show(stash, undefined, goBackCommand); + const pick = await StashListQuickPick.show(this.git, stash, 'list', goBackCommand); if (!pick) return undefined; if (pick instanceof CommandQuickPickItem) { diff --git a/src/commands/stashApply.ts b/src/commands/stashApply.ts index a2ff957..fecfef9 100644 --- a/src/commands/stashApply.ts +++ b/src/commands/stashApply.ts @@ -18,7 +18,7 @@ export class StashApplyCommand extends Command { const stash = await this.git.getStashList(this.git.repoPath); if (!stash) return window.showInformationMessage(`There are no stashed changes`); - const pick = await StashListQuickPick.show(stash, 'Apply stashed changes to your working tree\u2026'); + const pick = await StashListQuickPick.show(this.git, stash, 'apply'); if (!pick || !(pick instanceof CommitQuickPickItem)) return undefined; stashItem = pick.commit as GitStashCommit; diff --git a/src/commands/stashSave.ts b/src/commands/stashSave.ts new file mode 100644 index 0000000..d5f85d2 --- /dev/null +++ b/src/commands/stashSave.ts @@ -0,0 +1,32 @@ +'use strict'; +import { InputBoxOptions, window } from 'vscode'; +import { GitService } from '../gitService'; +import { Command, Commands } from './common'; +import { Logger } from '../logger'; + +export class StashSaveCommand extends Command { + + constructor(private git: GitService) { + super(Commands.StashSave); + } + + async execute(message?: string, unstagedOnly: boolean = false) { + if (!this.git.config.insiders) return undefined; + + try { + if (message == null) { + message = await window.showInputBox({ + prompt: `Please provide a stash message`, + placeHolder: `Stash message` + } as InputBoxOptions); + if (message === undefined) return undefined; + } + + return await this.git.stashSave(this.git.repoPath, message, unstagedOnly); + } + catch (ex) { + Logger.error(ex, 'StashSaveCommand'); + return window.showErrorMessage(`Unable to save stash. See output channel for more details`); + } + } +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 530354c..1069274 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -14,7 +14,7 @@ import { ShowBlameCommand, ToggleBlameCommand } from './commands'; import { ShowBlameHistoryCommand, ShowFileHistoryCommand } from './commands'; import { ShowLastQuickPickCommand, ShowQuickBranchHistoryCommand, ShowQuickCurrentBranchHistoryCommand, ShowQuickCommitDetailsCommand, ShowQuickCommitFileDetailsCommand, ShowQuickFileHistoryCommand } from './commands'; import { ShowQuickRepoStatusCommand, ShowQuickStashListCommand } from './commands'; -import { StashApplyCommand, StashDeleteCommand } from './commands'; +import { StashApplyCommand, StashDeleteCommand, StashSaveCommand } from './commands'; import { ToggleCodeLensCommand } from './commands'; import { Keyboard } from './commands'; import { IConfig } from './configuration'; @@ -120,6 +120,7 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(new ShowQuickStashListCommand(git)); context.subscriptions.push(new StashApplyCommand(git)); context.subscriptions.push(new StashDeleteCommand(git)); + context.subscriptions.push(new StashSaveCommand(git)); context.subscriptions.push(new ToggleCodeLensCommand(git)); Telemetry.trackEvent('initialized', Objects.flatten(config, 'config', true)); diff --git a/src/git/git.ts b/src/git/git.ts index 4473060..9193a00 100644 --- a/src/git/git.ts +++ b/src/git/git.ts @@ -250,6 +250,17 @@ export class Git { return gitCommand(repoPath, ...defaultStashParams); } + static stash_save(repoPath: string, message?: string, unstagedOnly: boolean = false) { + const params = [`stash`, `save`, `--include-untracked`]; + if (unstagedOnly) { + params.push(`--keep-index`); + } + if (message) { + params.push(message); + } + return gitCommand(repoPath, ...params); + } + static status(repoPath: string, porcelainVersion: number = 1): Promise { const porcelain = porcelainVersion >= 2 ? `--porcelain=v${porcelainVersion}` : '--porcelain'; return gitCommand(repoPath, 'status', porcelain, '--branch'); diff --git a/src/gitService.ts b/src/gitService.ts index cb71fd9..cf5de47 100644 --- a/src/gitService.ts +++ b/src/gitService.ts @@ -757,6 +757,12 @@ export class GitService extends Disposable { return Git.stash_delete(repoPath, stashName); } + stashSave(repoPath: string, message?: string, unstagedOnly: boolean = false) { + Logger.log(`stashSave('${repoPath}', ${message}, ${unstagedOnly})`); + + return Git.stash_save(repoPath, message, unstagedOnly); + } + toggleCodeLens(editor: TextEditor) { if (this.config.codeLens.visibility !== CodeLensVisibility.OnDemand || (!this.config.codeLens.recentChange.enabled && !this.config.codeLens.authors.enabled)) return; diff --git a/src/quickPicks/commitDetails.ts b/src/quickPicks/commitDetails.ts index dfc2a0b..cd622d7 100644 --- a/src/quickPicks/commitDetails.ts +++ b/src/quickPicks/commitDetails.ts @@ -78,7 +78,7 @@ export class CommitDetailsQuickPick { if (stash && git.config.insiders) { items.splice(index++, 0, new CommandQuickPickItem({ - label: `$(repo-forked) Apply Stashed Changes`, + label: `$(git-pull-request) Apply Stashed Changes`, description: `\u00a0 \u2014 \u00a0\u00a0 ${commit.message}` }, Commands.StashApply, [commit as GitStashCommit, true, false])); diff --git a/src/quickPicks/stashList.ts b/src/quickPicks/stashList.ts index dcf1f68..6558bb6 100644 --- a/src/quickPicks/stashList.ts +++ b/src/quickPicks/stashList.ts @@ -1,15 +1,27 @@ 'use strict'; import { Iterables } from '../system'; import { QuickPickOptions, window } from 'vscode'; -import { Keyboard } from '../commands'; -import { IGitStash } from '../gitService'; +import { Commands, Keyboard } from '../commands'; +import { GitService, IGitStash } from '../gitService'; import { CommandQuickPickItem, CommitQuickPickItem, getQuickPickIgnoreFocusOut } from '../quickPicks'; export class StashListQuickPick { - static async show(stash: IGitStash, placeHolder?: string, goBackCommand?: CommandQuickPickItem): Promise { + static async show(git: GitService, stash: IGitStash, mode: 'list' | 'apply', goBackCommand?: CommandQuickPickItem): Promise { const items = ((stash && Array.from(Iterables.map(stash.commits.values(), c => new CommitQuickPickItem(c)))) || []) as (CommitQuickPickItem | CommandQuickPickItem)[]; + if (mode === 'list' && git.config.insiders) { + items.splice(0, 0, new CommandQuickPickItem({ + label: `$(repo-push) Stash Unstaged Changes`, + description: `\u00a0 \u2014 \u00a0\u00a0 stashes only unstaged changes` + }, Commands.StashSave, [undefined, true])); + + items.splice(0, 0, new CommandQuickPickItem({ + label: `$(repo-push) Stash Changes`, + description: `\u00a0 \u2014 \u00a0\u00a0 stashes all changes` + }, Commands.StashSave)); + } + if (goBackCommand) { items.splice(0, 0, goBackCommand); } @@ -18,7 +30,9 @@ export class StashListQuickPick { const pick = await window.showQuickPick(items, { matchOnDescription: true, - placeHolder: placeHolder || `stashed changes \u2014 search by message, filename, or sha`, + placeHolder: mode === 'apply' + ? `Apply stashed changes to your working tree\u2026` + : `stashed changes \u2014 search by message, filename, or sha`, ignoreFocusOut: getQuickPickIgnoreFocusOut() // onDidSelectItem: (item: QuickPickItem) => { // scope.setKeyCommand('right', item);