Git - add open stash command (#201970)

* Initial implementation

* Add button to quick pick

* Improve stash picker

* Remove quick pick buttons
This commit is contained in:
Ladislau Szomoru 2024-01-08 09:48:51 +01:00 committed by GitHub
parent 0a90cb8a06
commit cfebdd863a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 96 additions and 11 deletions

View file

@ -674,6 +674,12 @@
"category": "Git",
"enablement": "!operationInProgress"
},
{
"command": "git.stashOpen",
"title": "%command.stashOpen%",
"category": "Git",
"enablement": "!operationInProgress"
},
{
"command": "git.timeline.openDiff",
"title": "%command.timelineOpenDiff%",
@ -1257,6 +1263,10 @@
"command": "git.openRepositoriesInParentFolders",
"when": "config.git.enabled && !git.missing && git.parentRepositoryCount != 0"
},
{
"command": "git.openStash",
"when": "config.git.enabled && !git.missing && config.multiDiffEditor.experimental.enabled"
},
{
"command": "git.viewChanges",
"when": "false"

View file

@ -104,6 +104,7 @@
"command.stashApplyLatest": "Apply Latest Stash",
"command.stashDrop": "Drop Stash...",
"command.stashDropAll": "Drop All Stashes...",
"command.stashOpen": "Open Stash...",
"command.timelineOpenDiff": "Open Changes",
"command.timelineOpenCommit": "Open Commit",
"command.timelineCopyCommitId": "Copy Commit ID",

View file

@ -3535,6 +3535,31 @@ export class CommandCenter {
await repository.dropStash();
}
@command('git.stashOpen', { repository: true })
async stashOpen(repository: Repository): Promise<void> {
const placeHolder = l10n.t('Pick a stash to open');
const stash = await this.pickStash(repository, placeHolder);
if (!stash) {
return;
}
const stashFiles = await repository.showStash(stash.index);
if (!stashFiles || stashFiles.length === 0) {
return;
}
const args: [Uri, Uri | undefined, Uri | undefined][] = [];
for (const file of stashFiles) {
const fileUri = Uri.file(path.join(repository.root, file));
args.push([fileUri, toGitUri(fileUri, `stash@{${stash.index}}`), fileUri]);
}
commands.executeCommand('vscode.changes', `Git Stash #${stash.index}: ${stash.description}`, args);
}
private async pickStash(repository: Repository, placeHolder: string): Promise<Stash | undefined> {
const stashes = await repository.getStashes();
@ -3543,9 +3568,10 @@ export class CommandCenter {
return;
}
const picks = stashes.map(stash => ({ label: `#${stash.index}: ${stash.description}`, description: '', details: '', stash }));
const picks = stashes.map(stash => ({ label: `#${stash.index}: ${stash.description}`, description: stash.branchName, stash }));
const result = await window.showQuickPick(picks, { placeHolder });
return result && result.stash;
return result?.stash;
}
@command('git.timeline.openDiff', { repository: false })

View file

@ -37,6 +37,7 @@ export interface IFileStatus {
export interface Stash {
index: number;
description: string;
branchName?: string;
}
interface MutableRemote extends Remote {
@ -964,6 +965,38 @@ export function parseLsFiles(raw: string): LsFilesElement[] {
.map(([, mode, object, stage, file]) => ({ mode, object, stage, file }));
}
function parseGitStashes(raw: string): Stash[] {
const result: Stash[] = [];
const regex = /^stash@{(\d+)}:(.+)$/;
const descriptionRegex = /(WIP\s)*on([^:]+):(.*)$/i;
for (const stash of raw.split('\n').filter(s => !!s)) {
// Extract index and description
const match = regex.exec(stash);
if (!match) {
continue;
}
const [, index, description] = match;
// Extract branch name from description
const descriptionMatch = descriptionRegex.exec(description);
if (!descriptionMatch) {
result.push({ index: parseInt(index), description: description.trim() });
continue;
}
const [, wip, branchName, message] = descriptionMatch;
result.push({
index: parseInt(index),
description: wip ? `WIP (${message.trim()})` : message.trim(),
branchName: branchName.trim()
});
}
return result;
}
export interface PullOptions {
unshallow?: boolean;
tags?: boolean;
@ -2114,6 +2147,24 @@ export class Repository {
}
}
async showStash(index: number): Promise<string[] | undefined> {
const args = ['stash', 'show', `stash@{${index}}`, '--name-only'];
try {
const result = await this.exec(args);
return result.stdout.trim()
.split('\n')
.filter(line => !!line);
} catch (err) {
if (/No stash found/.test(err.stderr || '')) {
return undefined;
}
throw err;
}
}
async getStatus(opts?: { limit?: number; ignoreSubmodules?: boolean; similarityThreshold?: number; untrackedChanges?: 'mixed' | 'separate' | 'hidden'; cancellationToken?: CancellationToken }): Promise<{ status: IFileStatus[]; statusLength: number; didHitLimit: boolean }> {
if (opts?.cancellationToken && opts?.cancellationToken.isCancellationRequested) {
throw new CancellationError();
@ -2385,14 +2436,7 @@ export class Repository {
async getStashes(): Promise<Stash[]> {
const result = await this.exec(['stash', 'list']);
const regex = /^stash@{(\d+)}:(.+)$/;
const rawStashes = result.stdout.trim().split('\n')
.filter(b => !!b)
.map(line => regex.exec(line) as RegExpExecArray)
.filter(g => !!g)
.map(([, index, description]: RegExpExecArray) => ({ index: parseInt(index), description }));
return rawStashes;
return parseGitStashes(result.stdout.trim());
}
async getRemotes(): Promise<Remote[]> {

View file

@ -1931,7 +1931,7 @@ export class Repository implements Disposable {
}
async getStashes(): Promise<Stash[]> {
return await this.repository.getStashes();
return this.run(Operation.Stash, () => this.repository.getStashes());
}
async createStash(message?: string, includeUntracked?: boolean, staged?: boolean): Promise<void> {
@ -1958,6 +1958,10 @@ export class Repository implements Disposable {
return await this.run(Operation.Stash, () => this.repository.applyStash(index));
}
async showStash(index: number): Promise<string[] | undefined> {
return await this.run(Operation.Stash, () => this.repository.showStash(index));
}
async getCommitTemplate(): Promise<string> {
return await this.run(Operation.GetCommitTemplate, async () => this.repository.getCommitTemplate());
}