From 73bbbc1d5f1452adddd7fc69b46d9cf77243195f Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Sat, 18 Mar 2017 02:01:25 -0400 Subject: [PATCH] Adds compare with branch command Adds branches quick pick --- package.json | 9 +++++++ src/commands.ts | 1 + src/commands/commands.ts | 3 ++- src/commands/diffWithBranch.ts | 47 ++++++++++++++++++++++++++++++++++ src/extension.ts | 3 ++- src/git/git.ts | 10 ++++++-- src/git/gitEnrichment.ts | 28 ++++++++++++++++++++ src/gitService.ts | 10 +++++++- src/quickPicks.ts | 1 + src/quickPicks/branches.ts | 42 ++++++++++++++++++++++++++++++ 10 files changed, 149 insertions(+), 5 deletions(-) create mode 100644 src/commands/diffWithBranch.ts create mode 100644 src/quickPicks/branches.ts diff --git a/package.json b/package.json index 73be0cc..fd42d35 100644 --- a/package.json +++ b/package.json @@ -359,6 +359,11 @@ "title": "Directory Compare", "category": "GitLens" }, + { + "command": "gitlens.diffWithBranch", + "title": "Compare with...", + "category": "GitLens" + }, { "command": "gitlens.diffWithNext", "title": "Compare with Next Commit", @@ -465,6 +470,10 @@ "command": "gitlens.diffDirectory", "when": "gitlens:enabled" }, + { + "command": "gitlens.diffWithBranch", + "when": "gitlens:enabled" + }, { "command": "gitlens.diffWithNext", "when": "gitlens:enabled" diff --git a/src/commands.ts b/src/commands.ts index af1520d..6a6f151 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -11,6 +11,7 @@ export { CopyShaToClipboardCommand } from './commands/copyShaToClipboard'; export { DiffDirectoryCommand } from './commands/diffDirectory'; export { DiffLineWithPreviousCommand } from './commands/diffLineWithPrevious'; export { DiffLineWithWorkingCommand } from './commands/diffLineWithWorking'; +export { DiffWithBranchCommand } from './commands/diffWithBranch'; export { DiffWithNextCommand } from './commands/diffWithNext'; export { DiffWithPreviousCommand } from './commands/diffWithPrevious'; export { DiffWithWorkingCommand } from './commands/diffWithWorking'; diff --git a/src/commands/commands.ts b/src/commands/commands.ts index 688fd8c..254719c 100644 --- a/src/commands/commands.ts +++ b/src/commands/commands.ts @@ -2,12 +2,13 @@ import { commands, Disposable, TextEditor, TextEditorEdit, Uri, window, workspace } from 'vscode'; import { BuiltInCommands } from '../constants'; -export type Commands = 'gitlens.closeUnchangedFiles' | 'gitlens.copyMessageToClipboard' | 'gitlens.copyShaToClipboard' | 'gitlens.diffDirectory' | 'gitlens.diffWithNext' | 'gitlens.diffWithPrevious' | 'gitlens.diffLineWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.diffLineWithWorking' | 'gitlens.openChangedFiles' | 'gitlens.showBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.showQuickCommitDetails' | 'gitlens.showQuickCommitFileDetails' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory' | 'gitlens.showQuickRepoStatus' | 'gitlens.toggleBlame' | 'gitlens.toggleCodeLens'; +export type Commands = 'gitlens.closeUnchangedFiles' | 'gitlens.copyMessageToClipboard' | 'gitlens.copyShaToClipboard' | 'gitlens.diffDirectory' | 'gitlens.diffWithBranch' | 'gitlens.diffWithNext' | 'gitlens.diffWithPrevious' | 'gitlens.diffLineWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.diffLineWithWorking' | 'gitlens.openChangedFiles' | 'gitlens.showBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.showQuickCommitDetails' | 'gitlens.showQuickCommitFileDetails' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory' | 'gitlens.showQuickRepoStatus' | 'gitlens.toggleBlame' | 'gitlens.toggleCodeLens'; export const Commands = { CloseUnchangedFiles: 'gitlens.closeUnchangedFiles' as Commands, CopyMessageToClipboard: 'gitlens.copyMessageToClipboard' as Commands, CopyShaToClipboard: 'gitlens.copyShaToClipboard' as Commands, DiffDirectory: 'gitlens.diffDirectory' as Commands, + DiffWithBranch: 'gitlens.diffWithBranch' as Commands, DiffWithNext: 'gitlens.diffWithNext' as Commands, DiffWithPrevious: 'gitlens.diffWithPrevious' as Commands, DiffLineWithPrevious: 'gitlens.diffLineWithPrevious' as Commands, diff --git a/src/commands/diffWithBranch.ts b/src/commands/diffWithBranch.ts new file mode 100644 index 0000000..5b97b3d --- /dev/null +++ b/src/commands/diffWithBranch.ts @@ -0,0 +1,47 @@ +'use strict'; +import { commands, TextEditor, Uri, window } from 'vscode'; +import { ActiveEditorCommand, Commands } from './commands'; +import { BuiltInCommands } from '../constants'; +import { GitService, GitUri } from '../gitService'; +import { Logger } from '../logger'; +import { CommandQuickPickItem, BranchesQuickPick } from '../quickPicks'; +import * as path from 'path'; + +export class DiffWithBranchCommand extends ActiveEditorCommand { + + constructor(private git: GitService) { + super(Commands.DiffWithBranch); + } + + async execute(editor: TextEditor, uri?: Uri, goBackCommand?: CommandQuickPickItem): Promise { + if (!(uri instanceof Uri)) { + if (!editor || !editor.document) return undefined; + uri = editor.document.uri; + } + + const line = (editor && editor.selection.active.line) || 0; + + const gitUri = await GitUri.fromUri(uri, this.git); + + const branches = await this.git.getBranches(gitUri.repoPath); + const pick = await BranchesQuickPick.show(branches, gitUri, goBackCommand); + if (!pick) return undefined; + + if (pick instanceof CommandQuickPickItem) { + return pick.execute(); + } + + const branch = pick.branch.name; + if (!branch) return undefined; + + try { + const compare = await this.git.getVersionedFile(gitUri.repoPath, gitUri.fsPath, branch); + await commands.executeCommand(BuiltInCommands.Diff, Uri.file(compare), gitUri.fileUri(), `${path.basename(gitUri.fsPath)} (${branch}) ↔ ${path.basename(gitUri.fsPath)}`); + return await commands.executeCommand(BuiltInCommands.RevealLine, { lineNumber: line, at: 'center' }); + } + catch (ex) { + Logger.error('[GitLens.DiffWithBranchCommand]', 'getVersionedFile', ex); + return window.showErrorMessage(`Unable to open diff. See output channel for more details`); + } + } +} \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 4be48f3..4357ecd 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -7,7 +7,7 @@ import { configureCssCharacters } from './blameAnnotationFormatter'; import { CommandContext, setCommandContext } from './commands'; import { CloseUnchangedFilesCommand, OpenChangedFilesCommand } from './commands'; import { CopyMessageToClipboardCommand, CopyShaToClipboardCommand } from './commands'; -import { DiffDirectoryCommand, DiffLineWithPreviousCommand, DiffLineWithWorkingCommand, DiffWithNextCommand, DiffWithPreviousCommand, DiffWithWorkingCommand} from './commands'; +import { DiffDirectoryCommand, DiffLineWithPreviousCommand, DiffLineWithWorkingCommand, DiffWithBranchCommand, DiffWithNextCommand, DiffWithPreviousCommand, DiffWithWorkingCommand} from './commands'; import { ShowBlameCommand, ToggleBlameCommand } from './commands'; import { ShowBlameHistoryCommand, ShowFileHistoryCommand } from './commands'; import { ShowQuickCommitDetailsCommand, ShowQuickCommitFileDetailsCommand, ShowQuickFileHistoryCommand, ShowQuickRepoHistoryCommand, ShowQuickRepoStatusCommand} from './commands'; @@ -95,6 +95,7 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(new DiffDirectoryCommand(git, repoPath)); context.subscriptions.push(new DiffLineWithPreviousCommand(git)); context.subscriptions.push(new DiffLineWithWorkingCommand(git)); + context.subscriptions.push(new DiffWithBranchCommand(git)); context.subscriptions.push(new DiffWithNextCommand(git)); context.subscriptions.push(new DiffWithPreviousCommand(git)); context.subscriptions.push(new DiffWithWorkingCommand(git)); diff --git a/src/git/git.ts b/src/git/git.ts index 8db0440..72bb700 100644 --- a/src/git/git.ts +++ b/src/git/git.ts @@ -57,7 +57,7 @@ export class Git { return data; } - static async getVersionedFile(fileName: string, repoPath: string, branchOrSha: string) { + static async getVersionedFile(repoPath: string, fileName: string, branchOrSha: string) { const data = await Git.show(repoPath, fileName, branchOrSha); const suffix = Git.isSha(branchOrSha) ? branchOrSha.substring(0, 8) : branchOrSha; @@ -70,7 +70,7 @@ export class Git { return; } - Logger.log(`getVersionedFile(${fileName}, ${repoPath}, ${branchOrSha}); destination=${destination}`); + Logger.log(`getVersionedFile('${repoPath}', '${fileName}', ${branchOrSha}); destination=${destination}`); fs.appendFile(destination, data, err => { if (err) { reject(err); @@ -127,6 +127,12 @@ export class Git { return gitCommand(root, ...params, `--`, file); } + static branch(repoPath: string) { + const params = [`branch`, `-a`]; + + return gitCommand(repoPath, ...params); + } + static diff_nameStatus(repoPath: string, sha1?: string, sha2?: string) { const params = [`diff`, `--name-status`, `-M`]; if (sha1) { diff --git a/src/git/gitEnrichment.ts b/src/git/gitEnrichment.ts index 8a9d6d2..981b67b 100644 --- a/src/git/gitEnrichment.ts +++ b/src/git/gitEnrichment.ts @@ -209,4 +209,32 @@ const statusOcticonsMap = { }; export function getGitStatusIcon(status: GitFileStatus, missing: string = '\u00a0\u00a0\u00a0\u00a0'): string { return statusOcticonsMap[status] || missing; +} + +export class GitBranch { + + current: boolean; + name: string; + remote: boolean; + + constructor(branch: string) { + branch = branch.trim(); + + if (branch.startsWith('* ')) { + branch = branch.substring(2); + this.current = true; + } + + if (branch.startsWith('remotes/')) { + branch = branch.substring(8); + this.remote = true; + } + + const index = branch.indexOf(' '); + if (index !== -1) { + branch = branch.substring(0, index); + } + + this.name = branch; + } } \ No newline at end of file diff --git a/src/gitService.ts b/src/gitService.ts index 453c7e8..441236b 100644 --- a/src/gitService.ts +++ b/src/gitService.ts @@ -4,7 +4,7 @@ import { Disposable, Event, EventEmitter, ExtensionContext, FileSystemWatcher, l import { CommandContext, setCommandContext } from './commands'; import { CodeLensVisibility, IConfig } from './configuration'; import { DocumentSchemes, WorkspaceState } from './constants'; -import { Git, GitBlameParserEnricher, GitBlameFormat, GitCommit, GitFileStatusItem, GitLogParserEnricher, IGitAuthor, IGitBlame, IGitBlameLine, IGitBlameLines, IGitLog } from './git/git'; +import { Git, GitBlameParserEnricher, GitBlameFormat, GitBranch, GitCommit, GitFileStatusItem, GitLogParserEnricher, IGitAuthor, IGitBlame, IGitBlameLine, IGitBlameLines, IGitLog } from './git/git'; import { IGitUriData, GitUri } from './git/gitUri'; import GitCodeLensProvider from './gitCodeLensProvider'; import { Logger } from './logger'; @@ -491,6 +491,14 @@ export class GitService extends Disposable { return locations; } + async getBranches(repoPath: string): Promise { + Logger.log(`getBranches('${repoPath}')`); + + const data = await Git.branch(repoPath); + const branches = data.split('\n').filter(_ => !!_).map(_ => new GitBranch(_)); + return branches; + } + async getLogForRepo(repoPath: string, sha?: string, maxCount?: number, reverse: boolean = false): Promise { Logger.log(`getLogForRepo('${repoPath}', ${maxCount})`); diff --git a/src/quickPicks.ts b/src/quickPicks.ts index 6e4795a..4fe00e7 100644 --- a/src/quickPicks.ts +++ b/src/quickPicks.ts @@ -1,4 +1,5 @@ 'use strict'; +export { BranchQuickPickItem, BranchesQuickPick } from './quickPicks/branches'; export { CommandQuickPickItem, OpenFileCommandQuickPickItem, OpenFilesCommandQuickPickItem, showQuickPickProgress } from './quickPicks/quickPicks'; export { CommitQuickPickItem, CommitWithFileStatusQuickPickItem } from './quickPicks/gitQuickPicks'; export { OpenCommitFilesCommandQuickPickItem, OpenCommitWorkingTreeFilesCommandQuickPickItem, CommitDetailsQuickPick } from './quickPicks/commitDetails'; diff --git a/src/quickPicks/branches.ts b/src/quickPicks/branches.ts new file mode 100644 index 0000000..6a17272 --- /dev/null +++ b/src/quickPicks/branches.ts @@ -0,0 +1,42 @@ +'use strict'; +import { QuickPickItem, QuickPickOptions, window } from 'vscode'; +import { GitBranch, GitUri } from '../gitService'; +import { CommandQuickPickItem, getQuickPickIgnoreFocusOut } from './quickPicks'; +import * as path from 'path'; + +export class BranchQuickPickItem implements QuickPickItem { + + label: string; + description: string; + detail: string; + + constructor(public branch: GitBranch) { + this.label = `${branch.current ? '$(check)\u00a0' : '\u00a0\u00a0\u00a0\u00a0'} ${branch.name}`; + this.description = branch.remote ? '\u00a0\u00a0 remote branch' : null; + } +} + +export class BranchesQuickPick { + + static async show(branches: GitBranch[], uri: GitUri, goBackCommand?: CommandQuickPickItem): Promise { + + const items = branches.map(_ => new BranchQuickPickItem(_)) as (BranchQuickPickItem | CommandQuickPickItem)[]; + + if (goBackCommand) { + items.splice(0, 0, goBackCommand); + } + + // const scope = await Keyboard.instance.beginScope({ left: goBackCommand }); + + const pick = await window.showQuickPick(items, + { + placeHolder: `Compare ${path.basename(uri.fsPath)} to \u2026`, + ignoreFocusOut: getQuickPickIgnoreFocusOut() + } as QuickPickOptions); + if (!pick) return undefined; + + // await scope.dispose(); + + return pick; + } +} \ No newline at end of file