diff --git a/CHANGELOG.md b/CHANGELOG.md index 87b140f..0ca8370 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ --- ## Release Notes +### 1.1.0 + + - Adds new `gitlens.showQuickFileHistory` command to show the file history in a quick-pick list (palette) + - Adds new `gitlens.showQuickRepoHistory` command to show the repository history in a quick-pick list (palette) + - Adds `gitlens.showQuickFileHistory` option to the `gitlens.codeLens.recentChange.command`, `gitlens.codeLens.authors.command`, and `gitlens.statusBar.command` settings + - Removes `git.viewFileHistory` option from the `gitlens.codeLens.recentChange.command`, `gitlens.codeLens.authors.command`, and `gitlens.statusBar.command` settings + - Changes the `gitlens.statusBar.command` settings default to `gitlens.showQuickFileHistory` instead of `gitlens.toggleBlame` + +### 1.0.2 + + - Fixes [#16](https://github.com/eamodio/vscode-gitlens/issues/16) - incorrect 'Unable to find Git' message + ### 1.0.0 - Adds support for git history (log)! diff --git a/README.md b/README.md index 7745889..aa24863 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,6 @@ Provides Git CodeLens information (most recent commit, # of authors), on-demand ![GitLens preview](https://raw.githubusercontent.com/eamodio/vscode-git-codelens/master/images/preview-gitlens.gif) -## Requirements - -Must be using Git. - ## Extension Settings |Name | Description @@ -39,13 +35,13 @@ Must be using Git. |`gitlens.codeLens.locationCustomSymbols`|Specifies the set of document symbols to render active document CodeLens on. Must be a member of `SymbolKind` |`gitlens.codeLens.languageLocations`|Specifies where CodeLens will be rendered in the active document for the specified languages |`gitlens.codeLens.recentChange.enabled`|Specifies whether the recent change CodeLens is shown -|`gitlens.codeLens.recentChange.command`|Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension +|`gitlens.codeLens.recentChange.command`|Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickFileHistory` - shows a file history picker |`gitlens.codeLens.authors.enabled`|Specifies whether the authors CodeLens is shown -|`gitlens.codeLens.authors.command`|Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension +|`gitlens.codeLens.authors.command`|Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickFileHistory` - shows a file history picker |`gitlens.menus.fileDiff.enabled`|Specifies whether file-based diff commands will be added to the context menus |`gitlens.menus.lineDiff.enabled`|Specifies whether line-based diff commands will be added to the context menus |`gitlens.statusBar.enabled`|Specifies whether blame information is shown in the status bar -|`gitlens.statusBar.command`|"Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension" +|`gitlens.statusBar.command`|"Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickFileHistory` - shows a file history picker ## Known Issues diff --git a/package.json b/package.json index 2fd573e..607f331 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gitlens", - "version": "1.0.2", + "version": "1.1.0", "author": { "name": "Eric Amodio", "email": "eamodio@gmail.com" @@ -162,9 +162,9 @@ "gitlens.showBlameHistory", "gitlens.showFileHistory", "gitlens.diffWithPrevious", - "git.viewFileHistory" + "gitlens.showQuickFileHistory" ], - "description": "Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension" + "description": "Specifies the command executed when the recent change CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickFileHistory` - shows a file history picker" }, "gitlens.codeLens.authors.enabled": { "type": "boolean", @@ -179,9 +179,9 @@ "gitlens.showBlameHistory", "gitlens.showFileHistory", "gitlens.diffWithPrevious", - "git.viewFileHistory" + "gitlens.showQuickFileHistory" ], - "description": "Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension" + "description": "Specifies the command executed when the authors CodeLens is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickFileHistory` - shows a file history picker" }, "gitlens.statusBar.enabled": { "type": "boolean", @@ -190,16 +190,16 @@ }, "gitlens.statusBar.command": { "type": "string", - "default": "gitlens.toggleBlame", + "default": "gitlens.showQuickFileHistory", "enum": [ "gitlens.toggleBlame", "gitlens.showBlameHistory", "gitlens.showFileHistory", "gitlens.diffWithPrevious", "gitlens.toggleCodeLens", - "git.viewFileHistory" + "gitlens.showQuickFileHistory" ], - "description": "Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `git.viewFileHistory` - opens a file history picker, which requires the Git History (git log) extension" + "description": "Specifies the command executed when the blame status bar item is clicked. `gitlens.toggleBlame` - toggles blame annotations. `gitlens.showBlameHistory` - opens the blame history explorer. `gitlens.showFileHistory` - opens the file history explorer. `gitlens.diffWithPrevious` - compares the current checked-in file with the previous commit. `gitlens.showQuickFileHistory` - shows a file history picker" }, "gitlens.menus.fileDiff.enabled": { "type": "boolean", @@ -281,58 +281,83 @@ }, { "command": "gitlens.showBlameHistory", - "title": "Open Git Blame History", + "title": "Open Git Blame History Explorer", "category": "GitLens" }, { "command": "gitlens.showFileHistory", - "title": "Open Git File History", + "title": "Open File History Explorer", + "category": "GitLens" + }, + { + "command": "gitlens.showQuickFileHistory", + "title": "Show File History", + "category": "GitLens" + }, + { + "command": "gitlens.showQuickRepoHistory", + "title": "Show Repository History", "category": "GitLens" } ], "menus": { "explorer/context": [ + { + "command": "gitlens.showQuickFileHistory", + "when": "config.git.enabled", + "group": "2_gitlens-file@1.0" + }, { "command": "gitlens.diffWithPrevious", "alt": "gitlens.diffWithWorking", "when": "config.gitlens.menus.fileDiff.enabled && config.git.enabled", - "group": "2_gitlens-file" + "group": "2_gitlens-file@1.1" } ], "editor/title": [ { "command": "gitlens.toggleBlame", "when": "editorTextFocus && config.git.enabled", - "group": "2_gitlens-blame" + "group": "2_gitlens" + }, + { + "command": "gitlens.showQuickFileHistory", + "when": "editorTextFocus && config.git.enabled", + "group": "3_gitlens" } ], "editor/context": [ + { + "command": "gitlens.toggleBlame", + "when": "editorTextFocus && config.git.enabled", + "group": "2_gitlens" + }, + { + "command": "gitlens.showQuickFileHistory", + "when": "config.git.enabled", + "group": "3_gitlens@1.0" + }, { "command": "gitlens.diffLineWithWorking", "when": "editorTextFocus && config.gitlens.menus.lineDiff.enabled && config.git.enabled", - "group": "3_gitlens-line@1.0" + "group": "3_gitlens@1.1" }, { "command": "gitlens.diffLineWithPrevious", "when": "editorTextFocus && config.gitlens.menus.lineDiff.enabled && config.git.enabled", - "group": "3_gitlens-line@1.1" + "group": "3_gitlens@1.2" }, { "command": "gitlens.diffWithWorking", "alt": "gitlens.diffLineWithWorking", "when": "editorTextFocus && config.gitlens.menus.fileDiff.enabled && config.git.enabled", - "group": "3_gitlens-file@1.0" + "group": "3_gitlens@1.3" }, { "command": "gitlens.diffWithPrevious", "alt": "gitlens.diffLineWithPrevious", "when": "editorTextFocus && config.gitlens.menus.fileDiff.enabled && config.git.enabled", - "group": "3_gitlens-file@1.1" - }, - { - "command": "gitlens.toggleBlame", - "when": "editorTextFocus && config.git.enabled", - "group": "2_gitlens-blame" + "group": "3_gitlens@1.4" } ] }, @@ -360,16 +385,16 @@ "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.4.0", "lodash.once": "^4.1.1", - "moment": "^2.16.0", + "moment": "^2.17.0", "spawn-rx": "^2.0.6", - "tmp": "^0.0.30" + "tmp": "^0.0.31" }, "devDependencies": { "mocha": "^3.1.2", - "tslint": "^3.15.1", - "typescript": "^2.0.9", + "tslint": "^4.0.1", + "typescript": "^2.0.10", "vscode": "^1.0.3", - "@types/node": "^6.0.48", + "@types/node": "^6.0.50", "@types/mocha": "^2.2.33", "@types/tmp": "^0.0.31" }, diff --git a/src/blameStatusBarController.ts b/src/blameStatusBarController.ts index c6bcda8..50926fc 100644 --- a/src/blameStatusBarController.ts +++ b/src/blameStatusBarController.ts @@ -3,7 +3,6 @@ import { Objects } from './system'; import { Disposable, ExtensionContext, StatusBarAlignment, StatusBarItem, TextDocument, TextEditor, TextEditorSelectionChangeEvent, window, workspace } from 'vscode'; import { TextDocumentComparer } from './comparers'; import { IConfig, StatusBarCommand } from './configuration'; -import { WorkspaceState } from './constants'; import GitProvider, { GitCommit, GitUri, IGitBlame } from './gitProvider'; import * as moment from 'moment'; @@ -17,7 +16,7 @@ export default class BlameStatusBarController extends Disposable { private _uri: GitUri; private _useCaching: boolean; - constructor(private context: ExtensionContext, private git: GitProvider) { + constructor(context: ExtensionContext, private git: GitProvider) { super(() => this.dispose()); this._onConfigure(); @@ -50,11 +49,6 @@ export default class BlameStatusBarController extends Disposable { config.statusBar.command = StatusBarCommand.BlameAnnotate; } break; - case StatusBarCommand.GitViewHistory: - if (!this.context.workspaceState.get(WorkspaceState.HasGitHistoryExtension, false)) { - config.statusBar.command = StatusBarCommand.ShowBlameHistory; - } - break; } this._statusBarItem.command = config.statusBar.command; @@ -156,7 +150,7 @@ export default class BlameStatusBarController extends Disposable { case StatusBarCommand.ToggleCodeLens: this._statusBarItem.tooltip = 'Toggle Blame CodeLens'; break; - case StatusBarCommand.GitViewHistory: + case StatusBarCommand.ShowQuickFileHistory: this._statusBarItem.tooltip = 'View Git File History'; break; } diff --git a/src/commands/showFileHistory.ts b/src/commands/showFileHistory.ts index cf367a8..1102048 100644 --- a/src/commands/showFileHistory.ts +++ b/src/commands/showFileHistory.ts @@ -23,13 +23,13 @@ export default class ShowFileHistoryCommand extends EditorCommand { try { const locations = await this.git.getLogLocations(gitUri.fsPath, gitUri.sha, gitUri.repoPath, sha, line); - if (!locations) return window.showWarningMessage(`Unable to show history. File is probably not under source control`); + if (!locations) return window.showWarningMessage(`Unable to show file history. File is probably not under source control`); return commands.executeCommand(BuiltInCommands.ShowReferences, uri, position, locations); } catch (ex) { Logger.error('[GitLens.ShowFileHistoryCommand]', 'getLogLocations', ex); - return window.showErrorMessage(`Unable to show history. See output channel for more details`); + return window.showErrorMessage(`Unable to show file history. See output channel for more details`); } } } \ No newline at end of file diff --git a/src/commands/showQuickFileHistory.ts b/src/commands/showQuickFileHistory.ts new file mode 100644 index 0000000..bdc665a --- /dev/null +++ b/src/commands/showQuickFileHistory.ts @@ -0,0 +1,54 @@ +'use strict'; +import { Iterables } from '../system'; +import { commands, QuickPickItem, QuickPickOptions, TextEditor, TextEditorEdit, Uri, window } from 'vscode'; +import { EditorCommand } from './commands'; +import { Commands } from '../constants'; +import GitProvider, { GitCommit, GitUri } from '../gitProvider'; +import { Logger } from '../logger'; +import * as moment from 'moment'; + +class CommitQuickPickItem implements QuickPickItem { + label: string; + description: string; + detail: string; + + constructor(public commit: GitCommit) { + this.label = `${commit.author}, ${moment(commit.date).fromNow()}`; + this.description = `\u2022 ${commit.sha}`; + this.detail = commit.message; + } +} + +export default class ShowQuickFileHistoryCommand extends EditorCommand { + constructor(private git: GitProvider) { + super(Commands.ShowQuickFileHistory); + } + + async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri) { + if (!(uri instanceof Uri)) { + if (!editor.document) return undefined; + uri = editor.document.uri; + } + + const gitUri = GitUri.fromUri(uri); + + try { + const log = await this.git.getLogForFile(gitUri.fsPath, gitUri.sha, gitUri.repoPath); + if (!log) return window.showWarningMessage(`Unable to show file history. File is probably not under source control`); + + const items = Iterables.map(log.commits.values(), c => new CommitQuickPickItem(c)); + const commitPick = await window.showQuickPick(Array.from(items), { + matchOnDescription: true, + matchOnDetail: true + }); + + if (commitPick) { + return commands.executeCommand(Commands.DiffWithWorking, commitPick.commit.uri, commitPick.commit); + } + } + catch (ex) { + Logger.error('[GitLens.ShowQuickFileHistoryCommand]', 'getLogLocations', ex); + return window.showErrorMessage(`Unable to show file history. See output channel for more details`); + } + } +} \ No newline at end of file diff --git a/src/commands/showQuickRepoHistory.ts b/src/commands/showQuickRepoHistory.ts new file mode 100644 index 0000000..54c5cee --- /dev/null +++ b/src/commands/showQuickRepoHistory.ts @@ -0,0 +1,84 @@ +'use strict'; +import { Iterables } from '../system'; +import { commands, QuickPickItem, QuickPickOptions, TextEditor, TextEditorEdit, Uri, window } from 'vscode'; +import { EditorCommand } from './commands'; +import { Commands } from '../constants'; +import GitProvider, { GitCommit, GitUri } from '../gitProvider'; +import { Logger } from '../logger'; +import * as moment from 'moment'; +import * as path from 'path'; + +class CommitQuickPickItem implements QuickPickItem { + label: string; + description: string; + detail: string; + + constructor(public commit: GitCommit) { + this.label = `${commit.author}, ${moment(commit.date).fromNow()}`; + this.description = `\u2022 ${commit.sha} \u2014 ${commit.fileName}`; + this.detail = commit.message; + } +} + +class FileQuickPickItem implements QuickPickItem { + label: string; + description: string; + detail: string; + uri: GitUri; + + constructor(commit: GitCommit, public fileName: string) { + this.label = fileName; + this.uri = GitUri.fromUri(Uri.file(path.resolve(commit.repoPath, fileName))); + } +} + +export default class ShowQuickRepoHistoryCommand extends EditorCommand { + constructor(private git: GitProvider) { + super(Commands.ShowQuickRepoHistory); + } + + async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri) { + if (!(uri instanceof Uri)) { + if (!editor.document) return undefined; + uri = editor.document.uri; + } + + const gitUri = GitUri.fromUri(uri); + + let repoPath = gitUri.repoPath; + try { + if (!repoPath) { + repoPath = await this.git.getRepoPathFromFile(gitUri.fsPath); + } + + if (!repoPath) return window.showWarningMessage(`Unable to show repository history`); + + const log = await this.git.getLogForRepo(repoPath); + if (!log) return window.showWarningMessage(`Unable to show repository history`); + + const items = Iterables.map(log.commits.values(), c => new CommitQuickPickItem(c)); + const commitPick = await window.showQuickPick(Array.from(items), { + matchOnDescription: true, + matchOnDetail: true + }); + + if (commitPick) { + const items = commitPick.commit.fileName.split(', ').map(f => new FileQuickPickItem(commitPick.commit, f)); + const filePick = await window.showQuickPick(items, { + matchOnDescription: true, + matchOnDetail: true, + placeHolder: `${commitPick.commit.author}, ${moment(commitPick.commit.date).fromNow()} \u2022 ${commitPick.commit.sha}` + }); + + if (filePick) { + const commit = new GitCommit(commitPick.commit.repoPath, commitPick.commit.sha, filePick.fileName, commitPick.commit.author, commitPick.commit.date, commitPick.commit.message, undefined, undefined, commitPick.commit.previousSha); + commands.executeCommand(Commands.DiffWithWorking, filePick.uri, commit); + } + } + } + catch (ex) { + Logger.error('[GitLens.ShowQuickRepoHistoryCommand]', 'getLogLocations', ex); + return window.showErrorMessage(`Unable to show repository history. See output channel for more details`); + } + } +} \ No newline at end of file diff --git a/src/configuration.ts b/src/configuration.ts index 8035cfd..db2f133 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -16,13 +16,13 @@ export interface IBlameConfig { }; } -export type CodeLensCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.diffWithPrevious' | 'git.viewFileHistory'; +export type CodeLensCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.diffWithPrevious' | 'gitlens.showQuickFileHistory'; export const CodeLensCommand = { BlameAnnotate: Commands.ToggleBlame as CodeLensCommand, ShowBlameHistory: Commands.ShowBlameHistory as CodeLensCommand, ShowFileHistory: Commands.ShowFileHistory as CodeLensCommand, DiffWithPrevious: Commands.DiffWithPrevious as CodeLensCommand, - GitViewHistory: 'git.viewFileHistory' as CodeLensCommand + ShowQuickFileHistory: Commands.ShowQuickFileHistory as CodeLensCommand }; export type CodeLensLocation = 'all' | 'document+containers' | 'document' | 'custom' | 'none'; @@ -61,14 +61,14 @@ export interface ICodeLensesConfig { authors: ICodeLensConfig; } -export type StatusBarCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.toggleCodeLens' | 'gitlens.diffWithPrevious' | 'git.viewFileHistory'; +export type StatusBarCommand = 'gitlens.toggleBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.toggleCodeLens' | 'gitlens.diffWithPrevious' | 'gitlens.showQuickFileHistory'; export const StatusBarCommand = { BlameAnnotate: Commands.ToggleBlame as StatusBarCommand, ShowBlameHistory: Commands.ShowBlameHistory as StatusBarCommand, ShowFileHistory: Commands.ShowFileHistory as CodeLensCommand, DiffWithPrevious: Commands.DiffWithPrevious as StatusBarCommand, ToggleCodeLens: Commands.ToggleCodeLens as StatusBarCommand, - GitViewHistory: 'git.viewFileHistory' as StatusBarCommand + ShowQuickFileHistory: Commands.ShowQuickFileHistory as StatusBarCommand }; export interface IStatusBarConfig { diff --git a/src/constants.ts b/src/constants.ts index 4ecdff5..7e4566b 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -14,7 +14,7 @@ export const BuiltInCommands = { ToggleRenderWhitespace: 'editor.action.toggleRenderWhitespace' as BuiltInCommands }; -export type Commands = 'gitlens.diffWithPrevious' | 'gitlens.diffLineWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.diffLineWithWorking' | 'gitlens.showBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.toggleBlame' | 'gitlens.toggleCodeLens'; +export type Commands = 'gitlens.diffWithPrevious' | 'gitlens.diffLineWithPrevious' | 'gitlens.diffWithWorking' | 'gitlens.diffLineWithWorking' | 'gitlens.showBlame' | 'gitlens.showBlameHistory' | 'gitlens.showFileHistory' | 'gitlens.showQuickFileHistory' | 'gitlens.showQuickRepoHistory' | 'gitlens.toggleBlame' | 'gitlens.toggleCodeLens'; export const Commands = { DiffWithPrevious: 'gitlens.diffWithPrevious' as Commands, DiffLineWithPrevious: 'gitlens.diffLineWithPrevious' as Commands, @@ -23,6 +23,8 @@ export const Commands = { ShowBlame: 'gitlens.showBlame' as Commands, ShowBlameHistory: 'gitlens.showBlameHistory' as Commands, ShowFileHistory: 'gitlens.showFileHistory' as Commands, + ShowQuickFileHistory: 'gitlens.showQuickFileHistory' as Commands, + ShowQuickRepoHistory: 'gitlens.showQuickRepoHistory' as Commands, ToggleBlame: 'gitlens.toggleBlame' as Commands, ToggleCodeLens: 'gitlens.toggleCodeLens' as Commands }; @@ -33,8 +35,7 @@ export const DocumentSchemes = { Git: 'git' as DocumentSchemes }; -export type WorkspaceState = 'hasGitHistoryExtension' | 'repoPath'; +export type WorkspaceState = 'repoPath'; export const WorkspaceState = { - HasGitHistoryExtension: 'hasGitHistoryExtension' as WorkspaceState, RepoPath: 'repoPath' as WorkspaceState }; \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index c565c5b..a160687 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,5 +1,5 @@ 'use strict'; -import { ExtensionContext, extensions, languages, window, workspace } from 'vscode'; +import { ExtensionContext, languages, window, workspace } from 'vscode'; import BlameAnnotationController from './blameAnnotationController'; import BlameStatusBarController from './blameStatusBarController'; import DiffLineWithPreviousCommand from './commands/diffLineWithPrevious'; @@ -9,6 +9,8 @@ import DiffWithWorkingCommand from './commands/diffWithWorking'; import ShowBlameCommand from './commands/showBlame'; import ShowBlameHistoryCommand from './commands/showBlameHistory'; import ShowFileHistoryCommand from './commands/showFileHistory'; +import ShowQuickFileHistoryCommand from './commands/showQuickFileHistory'; +import ShowQuickRepoHistoryCommand from './commands/showQuickRepoHistory'; import ToggleBlameCommand from './commands/toggleBlame'; import ToggleCodeLensCommand from './commands/toggleCodeLens'; import { IAdvancedConfig } from './configuration'; @@ -45,7 +47,6 @@ export async function activate(context: ExtensionContext) { } context.workspaceState.update(WorkspaceState.RepoPath, repoPath); - context.workspaceState.update(WorkspaceState.HasGitHistoryExtension, extensions.getExtension('donjayamanne.githistory') !== undefined); const git = new GitProvider(context); context.subscriptions.push(git); @@ -68,6 +69,8 @@ export async function activate(context: ExtensionContext) { context.subscriptions.push(new ToggleBlameCommand(annotationController)); context.subscriptions.push(new ShowBlameHistoryCommand(git)); context.subscriptions.push(new ShowFileHistoryCommand(git)); + context.subscriptions.push(new ShowQuickFileHistoryCommand(git)); + context.subscriptions.push(new ShowQuickRepoHistoryCommand(git)); context.subscriptions.push(new ToggleCodeLensCommand(git)); } diff --git a/src/git/enrichers/logParserEnricher.ts b/src/git/enrichers/logParserEnricher.ts index 81e953b..c93b582 100644 --- a/src/git/enrichers/logParserEnricher.ts +++ b/src/git/enrichers/logParserEnricher.ts @@ -13,12 +13,13 @@ interface ILogEntry { committerDate?: string; fileName?: string; + fileNames?: string[]; summary?: string; } export class GitLogParserEnricher implements IGitEnricher { - private _parseEntries(data: string): ILogEntry[] { + private _parseEntries(data: string, isRepoPath: boolean): ILogEntry[] { if (!data) return undefined; const lines = data.split('\n'); @@ -65,14 +66,34 @@ export class GitLogParserEnricher implements IGitEnricher { break; case 'filename': - position += 2; - lineParts = lines[position].split(' '); - if (lineParts.length === 1) { - entry.fileName = lineParts[0]; + if (isRepoPath) { + position++; + while (++position < lines.length) { + lineParts = lines[position].split(' '); + if (/^[a-f0-9]{40}$/.test(lineParts[0])) { + position--; + break; + } + + if (entry.fileNames == null) { + entry.fileNames = [lineParts[0]]; + } + else { + entry.fileNames.push(lineParts[0]); + } + } + entry.fileName = entry.fileNames.join(', '); } else { - entry.fileName = lineParts[3].substring(2); - position += 4; + position += 2; + lineParts = lines[position].split(' '); + if (lineParts.length === 1) { + entry.fileName = lineParts[0]; + } + else { + entry.fileName = lineParts[3].substring(2); + position += 4; + } } entries.push(entry); @@ -87,8 +108,9 @@ export class GitLogParserEnricher implements IGitEnricher { return entries; } - enrich(data: string, fileName: string): IGitLog { - const entries = this._parseEntries(data); + enrich(data: string, fileNameOrRepoPath: string): IGitLog { + const isRepoPath = !path.extname(fileNameOrRepoPath); + const entries = this._parseEntries(data, isRepoPath); if (!entries) return undefined; const authors: Map = new Map(); @@ -98,13 +120,22 @@ export class GitLogParserEnricher implements IGitEnricher { let relativeFileName: string; let recentCommit: GitCommit; + if (isRepoPath) { + repoPath = fileNameOrRepoPath; + } + for (let i = 0, len = entries.length; i < len; i++) { const entry = entries[i]; - if (i === 0) { - // Try to get the repoPath from the most recent commit - repoPath = fileName.replace(`/${entry.fileName}`, ''); - relativeFileName = path.relative(repoPath, fileName).replace(/\\/g, '/'); + if (i === 0 || isRepoPath) { + if (isRepoPath) { + relativeFileName = entry.fileName; + } + else { + // Try to get the repoPath from the most recent commit + repoPath = fileNameOrRepoPath.replace(`/${entry.fileName}`, ''); + relativeFileName = path.relative(repoPath, fileNameOrRepoPath).replace(/\\/g, '/'); + } } let commit = commits.get(entry.sha); @@ -129,7 +160,9 @@ export class GitLogParserEnricher implements IGitEnricher { if (recentCommit) { recentCommit.previousSha = commit.sha; - recentCommit.previousFileName = commit.originalFileName || commit.fileName; + if (!isRepoPath) { + recentCommit.previousFileName = commit.originalFileName || commit.fileName; + } } recentCommit = commit; } diff --git a/src/git/git.ts b/src/git/git.ts index 700e518..849b3b6 100644 --- a/src/git/git.ts +++ b/src/git/git.ts @@ -89,6 +89,12 @@ export default class Git { return gitCommand(root, 'log', `--follow`, `--name-only`, `--no-merges`, `--date=iso8601-strict`, `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nsummary %s%nfilename ?`, file); } + static logMostRecent(fileName: string, repoPath?: string) { + const [file, root]: [string, string] = Git.splitPath(Git.normalizePath(fileName, repoPath)); + + return gitCommand(root, 'log', `-n1`, `--follow`, `--name-only`, `--no-merges`, `--date=iso8601-strict`, `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nsummary %s%nfilename ?`, file); + } + static logRange(fileName: string, start: number, end: number, sha?: string, repoPath?: string) { const [file, root]: [string, string] = Git.splitPath(Git.normalizePath(fileName), repoPath); @@ -98,6 +104,10 @@ export default class Git { return gitCommand(root, 'log', `--name-only`, `--no-merges`, `--date=iso8601-strict`, `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nsummary %s%nfilename ?`, `-L ${start},${end}:${file}`); } + static logRepo(repoPath: string) { + return gitCommand(repoPath, 'log', `--name-only`, `--no-merges`, `--date=iso8601-strict`, `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nsummary %s%nfilename ?`); + } + static getVersionedFile(fileName: string, repoPath: string, sha: string) { return new Promise((resolve, reject) => { Git.getVersionedFileText(fileName, repoPath, sha).then(data => { diff --git a/src/gitCodeLensProvider.ts b/src/gitCodeLensProvider.ts index 4655a5a..ecb48ce 100644 --- a/src/gitCodeLensProvider.ts +++ b/src/gitCodeLensProvider.ts @@ -1,7 +1,7 @@ 'use strict'; import { Functions, Iterables, Strings } from './system'; import { CancellationToken, CodeLens, CodeLensProvider, commands, DocumentSelector, ExtensionContext, Position, Range, SymbolInformation, SymbolKind, TextDocument, Uri, workspace } from 'vscode'; -import { BuiltInCommands, Commands, DocumentSchemes, WorkspaceState } from './constants'; +import { BuiltInCommands, Commands, DocumentSchemes } from './constants'; import { CodeLensCommand, CodeLensLocation, IConfig, ICodeLensLanguageLocation } from './configuration'; import GitProvider, { GitCommit, GitUri, IGitBlame, IGitBlameLines } from './gitProvider'; import * as moment from 'moment'; @@ -30,11 +30,9 @@ export default class GitCodeLensProvider implements CodeLensProvider { static selector: DocumentSelector = { scheme: DocumentSchemes.File }; private _config: IConfig; - private _hasGitHistoryExtension: boolean; constructor(context: ExtensionContext, private git: GitProvider) { this._config = workspace.getConfiguration('').get('gitlens'); - this._hasGitHistoryExtension = context.workspaceState.get(WorkspaceState.HasGitHistoryExtension, false); } async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise { @@ -197,7 +195,7 @@ export default class GitCodeLensProvider implements CodeLensProvider { case CodeLensCommand.ShowBlameHistory: return this._applyShowBlameHistoryCommand(title, lens, blame, recentCommit); case CodeLensCommand.ShowFileHistory: return this._applyShowFileHistoryCommand(title, lens, blame, recentCommit); case CodeLensCommand.DiffWithPrevious: return this._applyDiffWithPreviousCommand(title, lens, blame, recentCommit); - case CodeLensCommand.GitViewHistory: return this._applyGitHistoryCommand(title, lens, blame); + case CodeLensCommand.ShowQuickFileHistory: return this._applyShowQuickFileHistoryCommand(title, lens, blame); default: return lens; } } @@ -212,7 +210,7 @@ export default class GitCodeLensProvider implements CodeLensProvider { case CodeLensCommand.ShowBlameHistory: return this._applyShowBlameHistoryCommand(title, lens, blame); case CodeLensCommand.ShowFileHistory: return this._applyShowFileHistoryCommand(title, lens, blame); case CodeLensCommand.DiffWithPrevious: return this._applyDiffWithPreviousCommand(title, lens, blame); - case CodeLensCommand.GitViewHistory: return this._applyGitHistoryCommand(title, lens, blame); + case CodeLensCommand.ShowQuickFileHistory: return this._applyShowQuickFileHistoryCommand(title, lens, blame); default: return lens; } } @@ -280,12 +278,10 @@ export default class GitCodeLensProvider implements CodeLensProvider { return lens; } - _applyGitHistoryCommand(title: string, lens: T, blame: IGitBlameLines): T { - if (!this._hasGitHistoryExtension) return this._applyShowFileHistoryCommand(title, lens, blame); - + _applyShowQuickFileHistoryCommand(title: string, lens: T, blame: IGitBlameLines): T { lens.command = { title: title, - command: CodeLensCommand.GitViewHistory, + command: CodeLensCommand.ShowQuickFileHistory, arguments: [Uri.file(lens.uri.fsPath)] }; return lens; diff --git a/src/gitProvider.ts b/src/gitProvider.ts index 07e4595..3eebee9 100644 --- a/src/gitProvider.ts +++ b/src/gitProvider.ts @@ -167,6 +167,11 @@ export default class GitProvider extends Disposable { return Git.repoPath(cwd); } + async getRepoPathFromFile(fileName: string): Promise { + const log = await this.getMostRecentLogForFile(fileName); + return log && log.repoPath; + } + getBlameForFile(fileName: string, sha?: string, repoPath?: string): Promise { Logger.log(`getBlameForFile('${fileName}', ${sha}, ${repoPath})`); fileName = Git.normalizePath(fileName); @@ -342,6 +347,30 @@ export default class GitProvider extends Disposable { return locations; } + async getLogForRepo(repoPath: string): Promise { + Logger.log(`getLogForRepo('${repoPath}')`); + try { + const data = await Git.logRepo(repoPath); + return new GitLogParserEnricher().enrich(data, repoPath); + } + catch (ex) { + return undefined; + } + } + + async getMostRecentLogForFile(fileName: string): Promise { + Logger.log(`getMostRecentLogForFile('${fileName}')`); + fileName = Git.normalizePath(fileName); + + try { + const data = await Git.logMostRecent(fileName); + return new GitLogParserEnricher().enrich(data, fileName); + } + catch (ex) { + return undefined; + } + } + getLogForFile(fileName: string, sha?: string, repoPath?: string, range?: Range): Promise { Logger.log(`getLogForFile('${fileName}', ${sha}, ${repoPath}, ${range && `[${range.start.line}, ${range.end.line}]`})`); fileName = Git.normalizePath(fileName); @@ -371,7 +400,7 @@ export default class GitProvider extends Disposable { : Git.log(fileName, sha, repoPath)) .then(data => new GitLogParserEnricher().enrich(data, fileName)) .catch(ex => { - // Trap and cache expected blame errors + // Trap and cache expected log errors if (useCaching) { const msg = ex && ex.toString(); Logger.log(`Replace log cache with empty promise for '${cacheKey}'`); diff --git a/tslint.json b/tslint.json index b460771..3000bdc 100644 --- a/tslint.json +++ b/tslint.json @@ -17,7 +17,6 @@ "no-internal-module": true, "no-reference": true, "no-trailing-whitespace": true, - "no-unreachable": true, "no-unsafe-finally": true, "no-unused-expression": false, "no-unused-new": true,