diff --git a/CHANGELOG.md b/CHANGELOG.md index e7697e4..7a2ffdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ --- ## Release Notes +### 1.4.0 + + - Adds `alt+h` shortcut for the `gitlens.showQuickFileHistory` command + - Adds `shift+alt+h` shortcut for the `gitlens.showQuickRepoHistory` command + - Adds `gitlens.advanced.maxQuickHistory` to limit the number of quick history entries to show (for better performance); Defaults to 200 + - Adds `gitlens.diffLineWithPrevious` as `alt` context menu item for `gitlens.diffWithPrevious` + - Adds `gitlens.diffLineWithWorking` as `alt` context menu item for `gitlens.diffWithWorking` + - Adds `gitlens.showFileHistory` as `alt` context menu item for `gitlens.showQuickFileHistory` + - Removes context menu for `gitlens.diffLineWithPrevious` -- since it is now the `alt` of `gitlens.diffWithPrevious` + - Removes context menu for `gitlens.diffLineWithWorking` -- since it is now the `alt` of `gitlens.diffWithWorking` + - Replaces `gitlens.menus.fileDiff.enabled` and `gitlens.menus.lineDiff.enabled` with `gitlens.menus.diff.enabled` -- since the switch between file and line diff is now controlled by the `alt` key + ### 1.3.1 - Renames `Diff` commands for better clarity diff --git a/README.md b/README.md index 5a1dc64..055524c 100644 --- a/README.md +++ b/README.md @@ -38,13 +38,11 @@ Provides Git CodeLens information (most recent commit, # of authors), on-demand |`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. `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.menus.diff.enabled`|Specifies whether 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. `gitlens.showQuickFileHistory` - shows a file history picker ## Known Issues -- Content in the **history explorers** disappears after a bit: [vscode issue](https://github.com/Microsoft/vscode/issues/16098) - CodeLens aren't updated properly after a file is saved: [vscode issue](https://github.com/Microsoft/vscode/issues/11546) - Visible whitespace causes issue with blame overlay (currently fixed with a hack, but infrequently and randomly fails): [vscode issue](https://github.com/Microsoft/vscode/issues/11485) diff --git a/package.json b/package.json index 414b049..d161c2a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gitlens", - "version": "1.3.1", + "version": "1.4.0", "author": { "name": "Eric Amodio", "email": "eamodio@gmail.com" @@ -201,15 +201,10 @@ ], "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": { + "gitlens.menus.diff.enabled": { "type": "boolean", "default": true, - "description": "Specifies whether file-based diff commands will be added to the context menus" - }, - "gitlens.menus.lineDiff.enabled": { - "type": "boolean", - "default": false, - "description": "Specifies whether line-based diff commands will be added to the context menus" + "description": "Specifies whether diff commands will be added to the context menus" }, "gitlens.advanced.caching.enabled": { "type": "boolean", @@ -231,6 +226,11 @@ "default": null, "description": "Specifies a git path to use" }, + "gitlens.advanced.maxQuickHistory": { + "type": "number", + "default": 200, + "description": "Specifies the maximum number of QuickPick history entries to show" + }, "gitlens.advanced.output.level": { "type": "string", "default": "silent", @@ -304,12 +304,12 @@ "explorer/context": [ { "command": "gitlens.diffWithWorking", - "when": "config.gitlens.menus.fileDiff.enabled && config.git.enabled", + "when": "config.gitlens.menus.diff.enabled && config.git.enabled", "group": "2_gitlens-file" }, { "command": "gitlens.diffWithPrevious", - "when": "config.gitlens.menus.fileDiff.enabled && config.git.enabled", + "when": "config.gitlens.menus.diff.enabled && config.git.enabled", "group": "2_gitlens-file" }, { @@ -321,12 +321,12 @@ "editor/title": [ { "command": "gitlens.diffWithWorking", - "when": "config.gitlens.menus.fileDiff.enabled && config.git.enabled", + "when": "config.gitlens.menus.diff.enabled && config.git.enabled", "group": "2_gitlens" }, { "command": "gitlens.diffWithPrevious", - "when": "config.gitlens.menus.fileDiff.enabled && config.git.enabled", + "when": "config.gitlens.menus.diff.enabled && config.git.enabled", "group": "2_gitlens" }, { @@ -344,27 +344,18 @@ { "command": "gitlens.diffWithWorking", "alt": "gitlens.diffLineWithWorking", - "when": "editorTextFocus && config.gitlens.menus.fileDiff.enabled && config.git.enabled", + "when": "editorTextFocus && config.gitlens.menus.diff.enabled && config.git.enabled", "group": "2_gitlens@1.0" }, - { - "command": "gitlens.diffLineWithWorking", - "when": "editorTextFocus && config.gitlens.menus.lineDiff.enabled && config.git.enabled", - "group": "2_gitlens@1.1" - }, { "command": "gitlens.diffWithPrevious", "alt": "gitlens.diffLineWithPrevious", - "when": "editorTextFocus && config.gitlens.menus.fileDiff.enabled && config.git.enabled", - "group": "2_gitlens@1.2" - }, - { - "command": "gitlens.diffLineWithPrevious", - "when": "editorTextFocus && config.gitlens.menus.lineDiff.enabled && config.git.enabled", - "group": "2_gitlens@1.3" + "when": "editorTextFocus && config.gitlens.menus.diff.enabled && config.git.enabled", + "group": "2_gitlens@1.1" }, { "command": "gitlens.showQuickFileHistory", + "alt": "gitlens.showFileHistory", "when": "config.git.enabled", "group": "3_gitlens" }, @@ -382,6 +373,17 @@ "mac": "alt+b", "when": "editorTextFocus" }, + { + "command": "gitlens.showQuickFileHistory", + "key": "alt+h", + "mac": "alt+h", + "when": "editorTextFocus" + }, + { + "command": "gitlens.showQuickRepoHistory", + "key": "shift+alt+h", + "mac": "shift+alt+h" + }, { "command": "gitlens.toggleCodeLens", "key": "alt+shift+b", @@ -407,17 +409,17 @@ "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.4.0", "lodash.once": "^4.1.1", - "moment": "^2.17.0", - "spawn-rx": "^2.0.6", + "moment": "^2.17.1", + "spawn-rx": "^2.0.7", "tmp": "^0.0.31" }, "devDependencies": { - "@types/node": "^0.0.2", - "@types/mocha": "^2.2.33", + "@types/node": "^6.0.55", + "@types/mocha": "^2.2.35", "@types/tmp": "^0.0.32", "mocha": "^3.2.0", - "tslint": "^4.0.2", - "typescript": "^2.0.10", + "tslint": "^4.2.0", + "typescript": "^2.1.4", "vscode": "^1.0.3" } } \ No newline at end of file diff --git a/src/commands/quickPickItems.ts b/src/commands/quickPickItems.ts index c93eff9..2223010 100644 --- a/src/commands/quickPickItems.ts +++ b/src/commands/quickPickItems.ts @@ -32,3 +32,15 @@ export class FileQuickPickItem implements QuickPickItem { this.uri = GitUri.fromUri(Uri.file(path.resolve(commit.repoPath, fileName))); } } + +export class ShowAllCommitsQuickPickItem implements QuickPickItem { + label: string; + description: string; + detail: string; + + constructor(maxItems: number) { + this.label = `Show All Commits`; + this.description = `\u2014 Currently only showing the first ${maxItems} commits`; + this.detail = `This may take a while`; + } +} \ No newline at end of file diff --git a/src/commands/showBlameHistory.ts b/src/commands/showBlameHistory.ts index 61cd2ee..684e43e 100644 --- a/src/commands/showBlameHistory.ts +++ b/src/commands/showBlameHistory.ts @@ -14,7 +14,9 @@ export default class ShowBlameHistoryCommand extends EditorCommand { if (!(uri instanceof Uri)) { if (!editor.document) return undefined; uri = editor.document.uri; + } + if (range == null || position == null) { // If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file) range = editor.document.validateRange(new Range(0, 0, 1000000, 1000000)); position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start; diff --git a/src/commands/showFileHistory.ts b/src/commands/showFileHistory.ts index d478b8d..b1a9f52 100644 --- a/src/commands/showFileHistory.ts +++ b/src/commands/showFileHistory.ts @@ -14,7 +14,9 @@ export default class ShowFileHistoryCommand extends EditorCommand { if (!(uri instanceof Uri)) { if (!editor.document) return undefined; uri = editor.document.uri; + } + if (position == null) { // If the command is executed manually -- treat it as a click on the root lens (i.e. show blame for the whole file) position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start; } diff --git a/src/commands/showQuickFileHistory.ts b/src/commands/showQuickFileHistory.ts index dd1a9ca..79455e2 100644 --- a/src/commands/showQuickFileHistory.ts +++ b/src/commands/showQuickFileHistory.ts @@ -1,11 +1,11 @@ 'use strict'; import { Iterables } from '../system'; -import { commands, QuickPickOptions, TextEditor, TextEditorEdit, Uri, window } from 'vscode'; +import { commands, QuickPickItem, QuickPickOptions, TextEditor, TextEditorEdit, Uri, window } from 'vscode'; import { EditorCommand } from './commands'; import { Commands } from '../constants'; import GitProvider, { GitUri } from '../gitProvider'; import { Logger } from '../logger'; -import { CommitQuickPickItem, CompareQuickPickItem } from './quickPickItems'; +import { CommitQuickPickItem, CompareQuickPickItem, ShowAllCommitsQuickPickItem } from './quickPickItems'; import * as moment from 'moment'; export default class ShowQuickFileHistoryCommand extends EditorCommand { @@ -13,7 +13,7 @@ export default class ShowQuickFileHistoryCommand extends EditorCommand { super(Commands.ShowQuickFileHistory); } - async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri) { + async execute(editor: TextEditor, edit: TextEditorEdit, uri?: Uri, maxCount?: number) { if (!(uri instanceof Uri)) { if (!editor.document) return undefined; uri = editor.document.uri; @@ -21,48 +21,62 @@ export default class ShowQuickFileHistoryCommand extends EditorCommand { const gitUri = GitUri.fromUri(uri, this.git); + if (maxCount == null) { + maxCount = this.git.config.advanced.maxQuickHistory; + } + try { - const log = await this.git.getLogForFile(gitUri.fsPath, gitUri.sha, gitUri.repoPath); + const log = await this.git.getLogForFile(gitUri.fsPath, gitUri.sha, gitUri.repoPath, undefined, maxCount); 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), { + const commits = Array.from(Iterables.map(log.commits.values(), c => new CommitQuickPickItem(c))) as QuickPickItem[]; + let placeHolderSuffix = ''; + if (maxCount !== 0 && commits.length === this.git.config.advanced.maxQuickHistory) { + placeHolderSuffix = ` \u2014 Only showing the first ${this.git.config.advanced.maxQuickHistory} commits`; + commits.push(new ShowAllCommitsQuickPickItem(this.git.config.advanced.maxQuickHistory)); + } + + const pick = await window.showQuickPick(commits, { matchOnDescription: true, matchOnDetail: true, - placeHolder: Iterables.first(log.commits.values()).fileName - }); + placeHolder: `${Iterables.first(log.commits.values()).fileName}${placeHolderSuffix}` + } as QuickPickOptions); - if (commitPick) { - const commit = commitPick.commit; + if (!pick) return undefined; + if (pick instanceof ShowAllCommitsQuickPickItem) { + return commands.executeCommand(Commands.ShowQuickFileHistory, uri, 0); + } - let command: Commands | undefined = Commands.DiffWithWorking; - if (commit.previousSha) { - const items: CompareQuickPickItem[] = [ - { - label: `Compare with Working Tree`, - description: `\u2022 ${commit.sha} $(git-compare) ${commit.fileName}`, - detail: null, - command: Commands.DiffWithWorking - }, - { - label: `Compare with Previous Commit`, - description: `\u2022 ${commit.previousSha} $(git-compare) ${commit.sha}`, - detail: null, - command: Commands.DiffWithPrevious - } - ]; + const commitPick = pick as CommitQuickPickItem; + const commit = commitPick.commit; - const comparePick = await window.showQuickPick(items, { - matchOnDescription: true, - placeHolder: `${commit.fileName} \u2022 ${commit.sha} \u2022 ${commit.author}, ${moment(commit.date).fromNow()}` - }); + let command: Commands | undefined = Commands.DiffWithWorking; + if (commit.previousSha) { + const items: CompareQuickPickItem[] = [ + { + label: `Compare with Working Tree`, + description: `\u2022 ${commit.sha} $(git-compare) ${commit.fileName}`, + detail: null, + command: Commands.DiffWithWorking + }, + { + label: `Compare with Previous Commit`, + description: `\u2022 ${commit.previousSha} $(git-compare) ${commit.sha}`, + detail: null, + command: Commands.DiffWithPrevious + } + ]; - command = comparePick ? comparePick.command : undefined; - } + const comparePick = await window.showQuickPick(items, { + matchOnDescription: true, + placeHolder: `${commit.fileName} \u2022 ${commit.sha} \u2022 ${commit.author}, ${moment(commit.date).fromNow()}` + } as QuickPickOptions); - if (command) { - return commands.executeCommand(command, commit.uri, commit); - } + command = comparePick ? comparePick.command : undefined; + } + + if (command) { + return commands.executeCommand(command, commit.uri, commit); } } catch (ex) { diff --git a/src/commands/showQuickRepoHistory.ts b/src/commands/showQuickRepoHistory.ts index 93189c2..109c006 100644 --- a/src/commands/showQuickRepoHistory.ts +++ b/src/commands/showQuickRepoHistory.ts @@ -1,18 +1,19 @@ 'use strict'; import { Iterables } from '../system'; -import { commands, QuickPickOptions, Uri, window } from 'vscode'; +import { commands, QuickPickItem, QuickPickOptions, Uri, window } from 'vscode'; import { Command } from './commands'; import { Commands } from '../constants'; import GitProvider, { GitUri } from '../gitProvider'; import { Logger } from '../logger'; -import { CommitQuickPickItem, CompareQuickPickItem, FileQuickPickItem } from './quickPickItems'; +import { CommitQuickPickItem, CompareQuickPickItem, FileQuickPickItem, ShowAllCommitsQuickPickItem } from './quickPickItems'; import * as moment from 'moment'; + export default class ShowQuickRepoHistoryCommand extends Command { constructor(private git: GitProvider, public repoPath: string) { super(Commands.ShowQuickRepoHistory); } - async execute(uri?: Uri) { + async execute(uri?: Uri, maxCount?: number) { if (!(uri instanceof Uri)) { const document = window.activeTextEditor && window.activeTextEditor.document; if (document) { @@ -20,6 +21,10 @@ export default class ShowQuickRepoHistoryCommand extends Command { } } + if (maxCount == null) { + maxCount = this.git.config.advanced.maxQuickHistory; + } + try { let repoPath: string; if (uri instanceof Uri) { @@ -37,57 +42,68 @@ export default class ShowQuickRepoHistoryCommand extends Command { if (!repoPath) return window.showWarningMessage(`Unable to show repository history`); - const log = await this.git.getLogForRepo(repoPath); + const log = await this.git.getLogForRepo(repoPath, maxCount); if (!log) return window.showWarningMessage(`Unable to show repository history`); - const items = Iterables.map(log.commits.values(), c => new CommitQuickPickItem(c, ` \u2014 ${c.fileName}`)); - const commitPick = await window.showQuickPick(Array.from(items), { + const commits = Array.from(Iterables.map(log.commits.values(), c => new CommitQuickPickItem(c, ` \u2014 ${c.fileName}`))) as QuickPickItem[]; + let placeHolder = ''; + if (maxCount !== 0 && commits.length === this.git.config.advanced.maxQuickHistory) { + placeHolder = `Only showing the first ${this.git.config.advanced.maxQuickHistory} commits`; + commits.push(new ShowAllCommitsQuickPickItem(this.git.config.advanced.maxQuickHistory)); + } + + const pick = await window.showQuickPick(commits, { matchOnDescription: true, - matchOnDetail: true - }); + matchOnDetail: true, + placeHolder: placeHolder + } as QuickPickOptions); - 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.sha} \u2022 ${commitPick.commit.author}, ${moment(commitPick.commit.date).fromNow()}` - }); + if (!pick) return undefined; + if (pick instanceof ShowAllCommitsQuickPickItem) { + return commands.executeCommand(Commands.ShowQuickRepoHistory, uri, 0); + } - if (filePick) { - const log = await this.git.getLogForFile(filePick.uri.fsPath); - if (!log) return window.showWarningMessage(`Unable to open diff`); + const commitPick = pick as CommitQuickPickItem; + const files = commitPick.commit.fileName.split(', ').map(f => new FileQuickPickItem(commitPick.commit, f)); + const filePick = await window.showQuickPick(files, { + matchOnDescription: true, + matchOnDetail: true, + placeHolder: `${commitPick.commit.sha} \u2022 ${commitPick.commit.author}, ${moment(commitPick.commit.date).fromNow()}` + } as QuickPickOptions); - const commit = Iterables.find(log.commits.values(), c => c.sha === commitPick.commit.sha); + if (filePick) { + const log = await this.git.getLogForFile(filePick.uri.fsPath); + if (!log) return window.showWarningMessage(`Unable to open diff`); - let command: Commands | undefined = Commands.DiffWithWorking; - if (commit.previousSha) { - const items: CompareQuickPickItem[] = [ - { - label: `Compare with Working Tree`, - description: `\u2022 ${commit.sha} $(git-compare) ${commit.fileName}`, - detail: null, - command: Commands.DiffWithWorking - }, - { - label: `Compare with Previous Commit`, - description: `\u2022 ${commit.previousSha} $(git-compare) ${commit.sha}`, - detail: null, - command: Commands.DiffWithPrevious - } - ]; + const commit = Iterables.find(log.commits.values(), c => c.sha === commitPick.commit.sha); - const comparePick = await window.showQuickPick(items, { - matchOnDescription: true, - placeHolder: `${commit.fileName} \u2022 ${commit.sha} \u2022 ${commit.author}, ${moment(commit.date).fromNow()}` - }); + let command: Commands | undefined = Commands.DiffWithWorking; + if (commit.previousSha) { + const items: CompareQuickPickItem[] = [ + { + label: `Compare with Working Tree`, + description: `\u2022 ${commit.sha} $(git-compare) ${commit.fileName}`, + detail: null, + command: Commands.DiffWithWorking + }, + { + label: `Compare with Previous Commit`, + description: `\u2022 ${commit.previousSha} $(git-compare) ${commit.sha}`, + detail: null, + command: Commands.DiffWithPrevious + } + ]; - command = comparePick ? comparePick.command : undefined; - } + const comparePick = await window.showQuickPick(items, { + matchOnDescription: true, + placeHolder: `${commit.fileName} \u2022 ${commit.sha} \u2022 ${commit.author}, ${moment(commit.date).fromNow()}` + } as QuickPickOptions); - if (command) { - return commands.executeCommand(command, commit.uri, commit); - } + command = comparePick ? comparePick.command : undefined; + } + + if (command) { + return commands.executeCommand(command, commit.uri, commit); } } diff --git a/src/configuration.ts b/src/configuration.ts index db2f133..bdd028a 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -92,6 +92,7 @@ export interface IAdvancedConfig { }; debug: boolean; git: string; + maxQuickHistory: number; output: { level: OutputLevel; }; diff --git a/src/git/git.ts b/src/git/git.ts index 849b3b6..c4b60e4 100644 --- a/src/git/git.ts +++ b/src/git/git.ts @@ -13,6 +13,8 @@ export * from './enrichers/logParserEnricher'; let git: IGit; const UncommittedRegex = /^[0]+$/; +const DefaultLogParams = [`log`, `--name-only`, `--no-merges`, `--date=iso8601-strict`, `--format=%H -%nauthor %an%nauthor-date %ai%ncommitter %cn%ncommitter-date %ci%nsummary %s%nfilename ?`]; + async function gitCommand(cwd: string, ...args: any[]) { try { const s = await spawnPromise(git.path, args, { cwd: cwd }); @@ -65,68 +67,84 @@ export default class Git { static blame(format: GitBlameFormat, fileName: string, sha?: string, repoPath?: string) { const [file, root]: [string, string] = Git.splitPath(Git.normalizePath(fileName), repoPath); + const params = [`blame`, `--root`, format]; if (sha) { - return gitCommand(root, 'blame', format, '--root', `${sha}^!`, '--', file); + params.push(`${sha}^!`); } - return gitCommand(root, 'blame', format, '--root', '--', file); + + return gitCommand(root, ...params, `--`, file); } static blameLines(format: GitBlameFormat, fileName: string, startLine: number, endLine: number, sha?: string, repoPath?: string) { const [file, root]: [string, string] = Git.splitPath(Git.normalizePath(fileName), repoPath); + const params = [`blame`, `--root`, format, `-L ${startLine},${endLine}`]; if (sha) { - return gitCommand(root, 'blame', `-L ${startLine},${endLine}`, format, '--root', `${sha}^!`, '--', file); + params.push(`${sha}^!`); } - return gitCommand(root, 'blame', `-L ${startLine},${endLine}`, format, '--root', '--', file); + + return gitCommand(root, ...params, `--`, file); } - static log(fileName: string, sha?: string, repoPath?: string) { + static log(fileName: string, sha?: string, repoPath?: string, maxCount?: number) { const [file, root]: [string, string] = Git.splitPath(Git.normalizePath(fileName), repoPath); - if (sha) { - 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 ?`, `origin..${sha}`, '--', file); + const params = [...DefaultLogParams, `--follow`]; + if (maxCount) { + params.push(`-n${maxCount}`); } - 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); + if (sha) { + params.push(`origin..${sha}`); + params.push(`--`); + } + + return gitCommand(root, ...params, 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) { + static logRange(fileName: string, start: number, end: number, sha?: string, repoPath?: string, maxCount?: number) { const [file, root]: [string, string] = Git.splitPath(Git.normalizePath(fileName), repoPath); - if (sha) { - 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 ?`, `origin..${sha}`, `-L ${start},${end}:${file}`); + const params = [...DefaultLogParams]; + if (maxCount) { + params.push(`-n${maxCount}`); } - 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}`); + if (sha) { + params.push(`--follow`); + params.push(`origin..${sha}`); + } + params.push(`-L ${start},${end}:${file}`); + + return gitCommand(root, ...params); } - 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 logRepo(repoPath: string, maxCount?: number) { + const params = [...DefaultLogParams]; + if (maxCount) { + params.push(`-n${maxCount}`); + } + return gitCommand(repoPath, ...params); } static getVersionedFile(fileName: string, repoPath: string, sha: string) { return new Promise((resolve, reject) => { Git.getVersionedFileText(fileName, repoPath, sha).then(data => { const ext = path.extname(fileName); - tmp.file({ prefix: `${path.basename(fileName, ext)}-${sha}__`, postfix: ext }, (err, destination, fd, cleanupCallback) => { - if (err) { - reject(err); - return; - } - - Logger.log(`getVersionedFile(${fileName}, ${repoPath}, ${sha}); destination=${destination}`); - fs.appendFile(destination, data, err => { + tmp.file({ prefix: `${path.basename(fileName, ext)}-${sha}__`, postfix: ext }, + (err, destination, fd, cleanupCallback) => { if (err) { reject(err); return; } - resolve(destination); + + Logger.log(`getVersionedFile(${fileName}, ${repoPath}, ${sha}); destination=${destination}`); + fs.appendFile(destination, data, err => { + if (err) { + reject(err); + return; + } + resolve(destination); + }); }); - }); }); }); } @@ -135,7 +153,7 @@ export default class Git { const [file, root] = Git.splitPath(Git.normalizePath(fileName), repoPath); sha = sha.replace('^', ''); - if (Git.isUncommitted(sha)) return new Promise((resolve, reject) => reject(new Error(`sha=${sha} is uncommitted`))); + if (Git.isUncommitted(sha)) return Promise.reject(new Error(`sha=${sha} is uncommitted`)); return gitCommand(root, 'show', `${sha}:./${file}`); } diff --git a/src/gitProvider.ts b/src/gitProvider.ts index 9def052..378c9e3 100644 --- a/src/gitProvider.ts +++ b/src/gitProvider.ts @@ -47,7 +47,7 @@ export default class GitProvider extends Disposable { private _cacheDisposable: Disposable | undefined; private _uriCache: Map | undefined; - private _config: IConfig; + config: IConfig; private _disposable: Disposable; private _codeLensProviderDisposable: Disposable | undefined; private _gitignore: Promise; @@ -105,8 +105,8 @@ export default class GitProvider extends Disposable { private _onConfigure() { const config = workspace.getConfiguration().get('gitlens'); - const codeLensChanged = !Objects.areEquivalent(config.codeLens, this._config && this._config.codeLens); - const advancedChanged = !Objects.areEquivalent(config.advanced, this._config && this._config.advanced); + const codeLensChanged = !Objects.areEquivalent(config.codeLens, this.config && this.config.codeLens); + const advancedChanged = !Objects.areEquivalent(config.advanced, this.config && this.config.advanced); if (codeLensChanged || advancedChanged) { Logger.log('CodeLens config changed; resetting CodeLens provider'); @@ -148,7 +148,7 @@ export default class GitProvider extends Disposable { } } - this._config = config; + this.config = config; } private _getCacheEntryKey(fileName: string) { @@ -216,7 +216,7 @@ export default class GitProvider extends Disposable { } async getRepoPathFromFile(fileName: string): Promise { - const log = await this.getMostRecentLogForFile(fileName); + const log = await this.getLogForFile(fileName, undefined, undefined, undefined, 1); return log && log.repoPath; } @@ -241,7 +241,7 @@ export default class GitProvider extends Disposable { const promise = this._gitignore.then(ignore => { if (ignore && !ignore.filter([fileName]).length) { Logger.log(`Skipping blame; '${fileName}' is gitignored`); - return >GitProvider.EmptyPromise; + return GitProvider.EmptyPromise as Promise; } return Git.blame(GitProvider.BlameFormat, fileName, sha, repoPath) @@ -252,14 +252,14 @@ export default class GitProvider extends Disposable { const msg = ex && ex.toString(); Logger.log(`Replace blame cache with empty promise for '${cacheKey}'`); - entry.blame = { + entry.blame = { //date: new Date(), item: GitProvider.EmptyPromise, errorMessage: msg - }; + } as ICachedBlame; this._gitCache.set(cacheKey, entry); - return >GitProvider.EmptyPromise; + return GitProvider.EmptyPromise as Promise; } return undefined; }); @@ -268,10 +268,10 @@ export default class GitProvider extends Disposable { if (useCaching) { Logger.log(`Add blame cache for '${cacheKey}'`); - entry.blame = { + entry.blame = { //date: new Date(), item: promise - }; + } as ICachedBlame; this._gitCache.set(cacheKey, entry); } @@ -288,11 +288,11 @@ export default class GitProvider extends Disposable { if (!blameLine) return undefined; const commit = blame.commits.get(blameLine.sha); - return { + return { author: Object.assign({}, blame.authors.get(commit.author), { lineCount: commit.lines.length }), commit: commit, line: blameLine - }; + } as IGitBlameLine; } fileName = Git.normalizePath(fileName); @@ -306,11 +306,11 @@ export default class GitProvider extends Disposable { if (repoPath) { commit.repoPath = repoPath; } - return { + return { author: Iterables.first(blame.authors.values()), commit: commit, line: blame.lines[line] - }; + } as IGitBlameLine; } catch (ex) { return undefined; @@ -364,12 +364,12 @@ export default class GitProvider extends Disposable { .sort((a, b) => b.lineCount - a.lineCount) .forEach(a => sortedAuthors.set(a.name, a)); - return { + return { authors: sortedAuthors, commits: commits, lines: lines, allLines: blame.lines - }; + } as IGitBlameLines; } async getBlameLocations(fileName: string, range: Range, sha?: string, repoPath?: string, selectedSha?: string, line?: number): Promise { @@ -395,10 +395,15 @@ export default class GitProvider extends Disposable { return locations; } - async getLogForRepo(repoPath: string): Promise { - Logger.log(`getLogForRepo('${repoPath}')`); + async getLogForRepo(repoPath: string, maxCount?: number): Promise { + Logger.log(`getLogForRepo('${repoPath}', ${maxCount})`); + + if (maxCount == null) { + maxCount = this.config.advanced.maxQuickHistory || 0; + } + try { - const data = await Git.logRepo(repoPath); + const data = await Git.logRepo(repoPath, maxCount); return new GitLogParserEnricher().enrich(data, repoPath, true); } catch (ex) { @@ -406,24 +411,11 @@ export default class GitProvider extends Disposable { } } - async getMostRecentLogForFile(fileName: string): Promise { - Logger.log(`getMostRecentLogForFile('${fileName}')`); + getLogForFile(fileName: string, sha?: string, repoPath?: string, range?: Range, maxCount?: number): Promise { + Logger.log(`getLogForFile('${fileName}', ${sha}, ${repoPath}, ${range && `[${range.start.line}, ${range.end.line}]`}, ${maxCount})`); 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); - - const useCaching = this.UseGitCaching && !range; + const useCaching = this.UseGitCaching && !range && !maxCount; let cacheKey: string; let entry: GitCacheEntry; @@ -440,12 +432,12 @@ export default class GitProvider extends Disposable { const promise = this._gitignore.then(ignore => { if (ignore && !ignore.filter([fileName]).length) { Logger.log(`Skipping log; '${fileName}' is gitignored`); - return >GitProvider.EmptyPromise; + return GitProvider.EmptyPromise as Promise; } return (range - ? Git.logRange(fileName, range.start.line + 1, range.end.line + 1, sha, repoPath) - : Git.log(fileName, sha, repoPath)) + ? Git.logRange(fileName, range.start.line + 1, range.end.line + 1, sha, repoPath, maxCount) + : Git.log(fileName, sha, repoPath, maxCount)) .then(data => new GitLogParserEnricher().enrich(data, fileName)) .catch(ex => { // Trap and cache expected log errors @@ -453,14 +445,14 @@ export default class GitProvider extends Disposable { const msg = ex && ex.toString(); Logger.log(`Replace log cache with empty promise for '${cacheKey}'`); - entry.log = { + entry.log = { //date: new Date(), item: GitProvider.EmptyPromise, errorMessage: msg - }; + } as ICachedLog; this._gitCache.set(cacheKey, entry); - return >GitProvider.EmptyPromise; + return GitProvider.EmptyPromise as Promise; } return undefined; }); @@ -469,10 +461,10 @@ export default class GitProvider extends Disposable { if (useCaching) { Logger.log(`Add log cache for '${cacheKey}'`); - entry.log = { + entry.log = { //date: new Date(), item: promise - }; + } as ICachedLog; this._gitCache.set(cacheKey, entry); } @@ -521,8 +513,8 @@ export default class GitProvider extends Disposable { } toggleCodeLens(editor: TextEditor) { - if (this._config.codeLens.visibility !== CodeLensVisibility.OnDemand || - (!this._config.codeLens.recentChange.enabled && !this._config.codeLens.authors.enabled)) return; + if (this.config.codeLens.visibility !== CodeLensVisibility.OnDemand || + (!this.config.codeLens.recentChange.enabled && !this.config.codeLens.authors.enabled)) return; Logger.log(`toggleCodeLens(${editor})`); @@ -595,7 +587,7 @@ export class GitUri extends Uri { super(); if (!uri) return; - const base = this; + const base = this as any; base._scheme = uri.scheme; base._authority = uri.authority; base._path = uri.path;