From 0ee7e5e4255f57bc893f7214eba33f4ff6f02252 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Mon, 12 Nov 2018 00:55:47 -0500 Subject: [PATCH] Closes #535 - Support commit ids in compares --- src/commands/diffBranchWithBranch.ts | 8 +- src/commands/diffDirectory.ts | 5 +- src/commands/diffWithBranch.ts | 3 +- src/commands/openBranchInRemote.ts | 2 +- src/commands/openFileInRemote.ts | 3 +- src/commands/showQuickBranchHistory.ts | 2 +- src/git/git.ts | 12 +++ src/git/gitService.ts | 6 ++ src/quickpicks/branchesAndTagsQuickPick.ts | 118 +++++++++++++++++++++++++---- src/quickpicks/commonQuickPicks.ts | 14 +++- src/views/nodes/compareNode.ts | 12 +-- src/views/nodes/fileHistoryTrackerNode.ts | 3 +- src/views/nodes/lineHistoryTrackerNode.ts | 3 +- 13 files changed, 155 insertions(+), 36 deletions(-) diff --git a/src/commands/diffBranchWithBranch.ts b/src/commands/diffBranchWithBranch.ts index 1d3d14f..f1e232e 100644 --- a/src/commands/diffBranchWithBranch.ts +++ b/src/commands/diffBranchWithBranch.ts @@ -1,5 +1,5 @@ 'use strict'; -import { CancellationTokenSource, TextEditor, Uri } from 'vscode'; +import { TextEditor, Uri } from 'vscode'; import { GlyphChars } from '../constants'; import { Container } from '../container'; import { Logger } from '../logger'; @@ -66,12 +66,14 @@ export class DiffBranchWithBranchCommand extends ActiveEditorCommand { break; } - const pick = await new BranchesAndTagsQuickPick(repoPath).show(placeHolder); + const pick = await new BranchesAndTagsQuickPick(repoPath).show(placeHolder, { + allowCommitId: true + }); if (pick === undefined) return undefined; if (pick instanceof CommandQuickPickItem) return pick.execute(); - args.ref1 = pick.name; + args.ref1 = pick.ref; if (args.ref1 === undefined) return undefined; } diff --git a/src/commands/diffDirectory.ts b/src/commands/diffDirectory.ts index aaff77c..84936b3 100644 --- a/src/commands/diffDirectory.ts +++ b/src/commands/diffDirectory.ts @@ -62,13 +62,14 @@ export class DiffDirectoryCommand extends ActiveEditorCommand { args = { ...args }; const pick = await new BranchesAndTagsQuickPick(repoPath).show( - `Compare Working Tree with${GlyphChars.Ellipsis}` + `Compare Working Tree with${GlyphChars.Ellipsis}`, + { allowCommitId: true } ); if (pick === undefined) return undefined; if (pick instanceof CommandQuickPickItem) return pick.execute(); - args.ref1 = pick.name; + args.ref1 = pick.ref; if (args.ref1 === undefined) return undefined; } diff --git a/src/commands/diffWithBranch.ts b/src/commands/diffWithBranch.ts index 35d5965..c9ae275 100644 --- a/src/commands/diffWithBranch.ts +++ b/src/commands/diffWithBranch.ts @@ -38,6 +38,7 @@ export class DiffWithBranchCommand extends ActiveEditorCommand { const pick = await new BranchesAndTagsQuickPick(gitUri.repoPath).show( `Compare ${paths.basename(gitUri.fsPath)} with${GlyphChars.Ellipsis}`, { + allowCommitId: true, goBack: args.goBackCommand } ); @@ -45,7 +46,7 @@ export class DiffWithBranchCommand extends ActiveEditorCommand { if (pick instanceof CommandQuickPickItem) return pick.execute(); - const ref = pick.name; + const ref = pick.ref; if (ref === undefined) return undefined; let renamedUri: Uri | undefined; diff --git a/src/commands/openBranchInRemote.ts b/src/commands/openBranchInRemote.ts index b4d6d63..b7287ea 100644 --- a/src/commands/openBranchInRemote.ts +++ b/src/commands/openBranchInRemote.ts @@ -65,7 +65,7 @@ export class OpenBranchInRemoteCommand extends ActiveEditorCommand { ); if (pick === undefined || pick instanceof CommandQuickPickItem) return undefined; - args.branch = pick.item.name; + args.branch = pick.ref; } const remotes = await Container.git.getRemotes(repoPath); diff --git a/src/commands/openFileInRemote.ts b/src/commands/openFileInRemote.ts index 16de8da..315a11d 100644 --- a/src/commands/openFileInRemote.ts +++ b/src/commands/openFileInRemote.ts @@ -1,5 +1,4 @@ 'use strict'; -import { __await } from 'tslib'; import { commands, Range, TextEditor, Uri, window } from 'vscode'; import { GlyphChars } from '../constants'; import { Container } from '../container'; @@ -69,7 +68,7 @@ export class OpenFileInRemoteCommand extends ActiveEditorCommand { ); if (pick === undefined || pick instanceof CommandQuickPickItem) return undefined; - args.branch = pick.item.name; + args.branch = pick.ref; } else { args.branch = branch.name; diff --git a/src/commands/showQuickBranchHistory.ts b/src/commands/showQuickBranchHistory.ts index faecd70..ad73bcc 100644 --- a/src/commands/showQuickBranchHistory.ts +++ b/src/commands/showQuickBranchHistory.ts @@ -68,7 +68,7 @@ export class ShowQuickBranchHistoryCommand extends ActiveEditorCachedCommand { if (pick === undefined) return undefined; if (pick instanceof CommandQuickPickItem) return pick.execute(); - args.branch = pick.item.name; + args.branch = pick.ref; if (args.branch === undefined) return undefined; progressCancellation = BranchHistoryQuickPick.showProgress(args.branch); diff --git a/src/git/git.ts b/src/git/git.ts index 98e43f2..6e68ab4 100644 --- a/src/git/git.ts +++ b/src/git/git.ts @@ -549,6 +549,18 @@ export class Git { return data === '' ? undefined : data.trim(); } + static async cat_validate(repoPath: string, ref: string) { + if (Git.isUncommitted(ref)) return true; + + try { + await git({ cwd: repoPath, exceptionHandler: throwExceptionHandler }, 'cat-file', '-t', ref); + return true; + } + catch (ex) { + return false; + } + } + static async cat_file_validate(repoPath: string, fileName: string, ref: string) { if (Git.isUncommitted(ref)) return ref; diff --git a/src/git/gitService.ts b/src/git/gitService.ts index 288d4c0..7e9d032 100644 --- a/src/git/gitService.ts +++ b/src/git/gitService.ts @@ -2015,6 +2015,12 @@ export class GitService implements Disposable { return ensuredRef; } + async validateReference(repoPath: string, ref: string) { + Logger.log(`validateReference('${repoPath}', '${ref}'`); + + return await Git.cat_validate(repoPath, ref); + } + stageFile(repoPath: string, fileName: string): Promise; stageFile(repoPath: string, uri: Uri): Promise; stageFile(repoPath: string, fileNameOrUri: string | Uri): Promise { diff --git a/src/quickpicks/branchesAndTagsQuickPick.ts b/src/quickpicks/branchesAndTagsQuickPick.ts index c8e158f..e723e61 100644 --- a/src/quickpicks/branchesAndTagsQuickPick.ts +++ b/src/quickpicks/branchesAndTagsQuickPick.ts @@ -2,10 +2,38 @@ import { CancellationToken, CancellationTokenSource, QuickPickItem, QuickPickOptions, window } from 'vscode'; import { GlyphChars } from '../constants'; import { Container } from '../container'; -import { GitBranch, GitTag } from '../git/gitService'; +import { GitBranch, GitService, GitTag } from '../git/gitService'; import { Functions } from '../system'; import { CommandQuickPickItem, getQuickPickIgnoreFocusOut } from './commonQuickPicks'; +export class RefQuickPickItem implements QuickPickItem { + label: string; + description: string; + detail: string | undefined; + + constructor( + public readonly ref: string, + checked?: boolean + ) { + this.label = `${checked ? `$(check)${GlyphChars.Space}` : GlyphChars.Space.repeat(4)} ${GitService.shortenSha( + ref + )}`; + this.description = ''; + } + + get current() { + return false; + } + + get item() { + return undefined; + } + + get remote() { + return false; + } +} + export class BranchQuickPickItem implements QuickPickItem { label: string; description: string; @@ -20,8 +48,8 @@ export class BranchQuickPickItem implements QuickPickItem { this.description = branch.remote ? `${GlyphChars.Space.repeat(2)} remote branch` : branch.current - ? 'current branch' - : ''; + ? 'current branch' + : ''; } get current() { @@ -32,7 +60,7 @@ export class BranchQuickPickItem implements QuickPickItem { return this.branch; } - get name() { + get ref() { return this.branch.name; } @@ -62,7 +90,7 @@ export class TagQuickPickItem implements QuickPickItem { return this.tag; } - get name() { + get ref() { return this.tag.name; } @@ -71,9 +99,10 @@ export class TagQuickPickItem implements QuickPickItem { } } -export type BranchOrTagQuickPickItem = BranchQuickPickItem | TagQuickPickItem; +export type BranchAndTagQuickPickResult = BranchQuickPickItem | TagQuickPickItem | RefQuickPickItem; export interface BranchesAndTagsQuickPickOptions { + allowCommitId?: boolean; autoPick?: boolean; checked?: string; filters?: { @@ -92,7 +121,7 @@ export class BranchesAndTagsQuickPick { async show( placeHolder: string, options: BranchesAndTagsQuickPickOptions = {} - ): Promise { + ): Promise { const cancellation = new CancellationTokenSource(); let scope; @@ -113,14 +142,71 @@ export class BranchesAndTagsQuickPick { }); } - let pick = await window.showQuickPick( - items, - { - placeHolder: placeHolder, - ignoreFocusOut: getQuickPickIgnoreFocusOut() - } as QuickPickOptions, - cancellation.token - ); + let pick; + if (options.allowCommitId) { + placeHolder += `${GlyphChars.Space.repeat(3)}(use # to enter a commit id)`; + + const quickpick = window.createQuickPick(); + quickpick.busy = true; + quickpick.enabled = false; + quickpick.placeholder = placeHolder; + quickpick.ignoreFocusOut = getQuickPickIgnoreFocusOut(); + quickpick.show(); + + quickpick.items = await items; + quickpick.busy = false; + quickpick.enabled = true; + + pick = await new Promise(resolve => { + cancellation.token.onCancellationRequested(() => quickpick.hide()); + + quickpick.onDidHide(() => resolve(undefined)); + quickpick.onDidChangeValue(value => { + quickpick.title = + value && value.startsWith('#') + ? `Please enter a commit id (Press 'Enter' to confirm or 'Escape' to cancel)` + : undefined; + }); + quickpick.onDidAccept(async () => { + if (quickpick.selectedItems.length === 0) { + let ref = quickpick.value; + if (!ref || !ref.startsWith('#')) return; + + ref = ref.substr(1); + + quickpick.busy = true; + quickpick.enabled = false; + + if (await Container.git.validateReference(this.repoPath, ref)) { + resolve(new RefQuickPickItem(ref)); + } + else { + quickpick.title = 'You must enter a valid commit id'; + quickpick.busy = false; + quickpick.enabled = true; + return; + } + } + else { + resolve(quickpick.selectedItems[0]); + } + + quickpick.hide(); + }); + }); + + quickpick.dispose(); + } + else { + pick = await window.showQuickPick( + items, + { + placeHolder: placeHolder, + ignoreFocusOut: getQuickPickIgnoreFocusOut() + } as QuickPickOptions, + cancellation.token + ); + } if (pick === undefined && autoPick !== undefined) { pick = autoPick; @@ -173,7 +259,7 @@ export class BranchesAndTagsQuickPick { } } - const items: (BranchOrTagQuickPickItem | CommandQuickPickItem)[] = []; + const items: (BranchQuickPickItem | TagQuickPickItem | CommandQuickPickItem)[] = []; if (branches !== undefined) { const filter = diff --git a/src/quickpicks/commonQuickPicks.ts b/src/quickpicks/commonQuickPicks.ts index 44ecd3b..387d1eb 100644 --- a/src/quickpicks/commonQuickPicks.ts +++ b/src/quickpicks/commonQuickPicks.ts @@ -16,7 +16,12 @@ import { Container } from '../container'; import { GitLog, GitLogCommit, GitRepoSearchBy, GitStashCommit, GitUri } from '../git/gitService'; import { KeyMapping, Keys } from '../keyboard'; import { Functions, Strings } from '../system'; -import { BranchesAndTagsQuickPick, BranchQuickPickItem, TagQuickPickItem } from './branchesAndTagsQuickPick'; +import { + BranchesAndTagsQuickPick, + BranchQuickPickItem, + RefQuickPickItem, + TagQuickPickItem +} from './branchesAndTagsQuickPick'; export function getQuickPickIgnoreFocusOut() { return !configuration.get(configuration.name('advanced')('quickPick')('closeOnFocusOut').value); @@ -145,8 +150,11 @@ export class ChooseFromBranchesAndTagsQuickPickItem extends CommandQuickPickItem super(item, undefined, undefined); } - execute(): Promise { - return new BranchesAndTagsQuickPick(this.repoPath).show(this.placeHolder, { goBack: this._goBack }); + execute(): Promise { + return new BranchesAndTagsQuickPick(this.repoPath).show(this.placeHolder, { + allowCommitId: true, + goBack: this._goBack + }); } } diff --git a/src/views/nodes/compareNode.ts b/src/views/nodes/compareNode.ts index bc5488c..1b9edd9 100644 --- a/src/views/nodes/compareNode.ts +++ b/src/views/nodes/compareNode.ts @@ -118,17 +118,18 @@ export class CompareNode extends ViewNode { } else if (repoPath !== this._selectedRef.repoPath) { // If we don't have a matching repoPath, then start over - this.selectForCompare(repoPath, ref); + void this.selectForCompare(repoPath, ref); return; } if (ref === undefined) { const pick = await new BranchesAndTagsQuickPick(repoPath).show( - `Compare ${this.getRefName(this._selectedRef.ref)} with${GlyphChars.Ellipsis}` + `Compare ${this.getRefName(this._selectedRef.ref)} with${GlyphChars.Ellipsis}`, + { allowCommitId: true } ); if (pick === undefined || pick instanceof CommandQuickPickItem) return; - ref = pick.name; + ref = pick.ref; } const ref1 = this._selectedRef; @@ -151,11 +152,12 @@ export class CompareNode extends ViewNode { let autoCompare = false; if (ref === undefined) { const pick = await new BranchesAndTagsQuickPick(repoPath).show( - `Select branch or tag for compare${GlyphChars.Ellipsis}` + `Select branch or tag for compare${GlyphChars.Ellipsis}`, + { allowCommitId: true } ); if (pick === undefined || pick instanceof CommandQuickPickItem) return; - ref = pick.name; + ref = pick.ref; autoCompare = true; } diff --git a/src/views/nodes/fileHistoryTrackerNode.ts b/src/views/nodes/fileHistoryTrackerNode.ts index 8b05f0a..fa393a2 100644 --- a/src/views/nodes/fileHistoryTrackerNode.ts +++ b/src/views/nodes/fileHistoryTrackerNode.ts @@ -70,12 +70,13 @@ export class FileHistoryTrackerNode extends SubscribeableViewNode