Ver código fonte

Adds support for blame & log on compare files

Allows for deep navigation through git history
main
Eric Amodio 8 anos atrás
pai
commit
72ef5e2902
17 arquivos alterados com 136 adições e 63 exclusões
  1. +4
    -0
      CHANGELOG.md
  2. +1
    -1
      README.md
  3. +1
    -1
      package.json
  4. +21
    -16
      src/blameAnnotationController.ts
  5. +2
    -1
      src/blameAnnotationProvider.ts
  6. +2
    -2
      src/blameStatusBarController.ts
  7. +1
    -1
      src/commands/diffLineWithPrevious.ts
  8. +1
    -1
      src/commands/diffLineWithWorking.ts
  9. +1
    -1
      src/commands/diffWithPrevious.ts
  10. +2
    -2
      src/commands/diffWithWorking.ts
  11. +1
    -1
      src/commands/showBlameHistory.ts
  12. +1
    -1
      src/commands/showFileHistory.ts
  13. +1
    -1
      src/commands/showQuickFileHistory.ts
  14. +1
    -1
      src/commands/showQuickRepoHistory.ts
  15. +1
    -1
      src/gitCodeLensProvider.ts
  16. +94
    -31
      src/gitProvider.ts
  17. +1
    -1
      src/gitRevisionCodeLensProvider.ts

+ 4
- 0
CHANGELOG.md Ver arquivo

@ -1,6 +1,10 @@
---
## Release Notes
### 1.3.0
- Adds support for blame and history (log) on files opened via compare commands -- allows for deep navigation through git history
### 1.2.0
- Adds compare (working vs previous) options to repository history

+ 1
- 1
README.md Ver arquivo

@ -45,6 +45,6 @@ Provides Git CodeLens information (most recent commit, # of authors), on-demand
## Known Issues
- Content in the **history explorers** disappears after a bit: [vscode issue](https://github.com/Microsoft/vscode/issues/11360)
- 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)

+ 1
- 1
package.json Ver arquivo

@ -1,6 +1,6 @@
{
"name": "gitlens",
"version": "1.2.0",
"version": "1.3.0",
"author": {
"name": "Eric Amodio",
"email": "eamodio@gmail.com"

+ 21
- 16
src/blameAnnotationController.ts Ver arquivo

@ -44,9 +44,10 @@ export default class BlameAnnotationController extends Disposable {
}
async showBlameAnnotation(editor: TextEditor, shaOrLine?: string | number): Promise<boolean> {
if (!editor || !editor.document || editor.viewColumn === undefined) return false;
if (!editor || !editor.document) return false;
if (editor.viewColumn === undefined && !this.git.hasGitUriForFile(editor)) return false;
const currentProvider = this._annotationProviders.get(editor.viewColumn);
const currentProvider = this._annotationProviders.get(editor.viewColumn || -1);
if (currentProvider && TextEditorComparer.equals(currentProvider.editor, editor)) {
await currentProvider.setSelection(shaOrLine);
return true;
@ -56,7 +57,7 @@ export default class BlameAnnotationController extends Disposable {
if (!await provider.supportsBlame()) return false;
if (currentProvider) {
await this.clear(currentProvider.editor.viewColumn, false);
await this.clear(currentProvider.editor.viewColumn || -1, false);
}
if (!this._blameAnnotationsDisposable && this._annotationProviders.size === 0) {
@ -73,36 +74,38 @@ export default class BlameAnnotationController extends Disposable {
this._visibleColumns = this._getVisibleColumns(window.visibleTextEditors);
}
this._annotationProviders.set(editor.viewColumn, provider);
this._annotationProviders.set(editor.viewColumn || -1, provider);
return provider.provideBlameAnnotation(shaOrLine);
}
async toggleBlameAnnotation(editor: TextEditor, shaOrLine?: string | number): Promise<boolean> {
if (!editor || !editor.document || editor.viewColumn === undefined) return false;
if (!editor || !editor.document) return false;
if (editor.viewColumn === undefined && !this.git.hasGitUriForFile(editor)) return false;
let provider = this._annotationProviders.get(editor.viewColumn);
let provider = this._annotationProviders.get(editor.viewColumn || -1);
if (!provider) return this.showBlameAnnotation(editor, shaOrLine);
await this.clear(provider.editor.viewColumn);
await this.clear(provider.editor.viewColumn || -1);
return false;
}
private _getVisibleColumns(editors: TextEditor[]): Set<number> {
const set: Set<number> = new Set();
for (const e of editors) {
if (e.viewColumn === undefined) continue;
if (e.viewColumn === undefined && !this.git.hasGitUriForFile(e)) continue;
set.add(e.viewColumn);
}
return set;
}
private _onActiveTextEditorChanged(e: TextEditor) {
if (e.viewColumn === undefined || this._pendingWhitespaceToggles.size === 0) return;
if (this._pendingWhitespaceToggles.size === 0 || (e.viewColumn === undefined && !this.git.hasGitUriForFile(e))) return;
const viewColumn = e.viewColumn || -1;
if (this._pendingWhitespaceToggles.has(e.viewColumn)) {
Logger.log('ActiveTextEditorChanged:', `Remove pending whitespace toggle for column ${e.viewColumn}`);
this._pendingWhitespaceToggles.delete(e.viewColumn);
if (this._pendingWhitespaceToggles.has(viewColumn)) {
Logger.log('ActiveTextEditorChanged:', `Remove pending whitespace toggle for column ${viewColumn}`);
this._pendingWhitespaceToggles.delete(viewColumn);
// HACK: Until https://github.com/Microsoft/vscode/issues/11485 is fixed -- toggle whitespace back on
Logger.log('ActiveTextEditorChanged:', `Toggle whitespace rendering on`);
@ -137,8 +140,10 @@ export default class BlameAnnotationController extends Disposable {
private async _onTextEditorViewColumnChanged(e: TextEditorViewColumnChangeEvent) {
this._visibleColumns = this._getVisibleColumns(window.visibleTextEditors);
Logger.log('TextEditorViewColumnChanged:', `Clear blame annotations for column ${e.viewColumn}`);
await this.clear(e.viewColumn);
const viewColumn = e.viewColumn || -1;
Logger.log('TextEditorViewColumnChanged:', `Clear blame annotations for column ${viewColumn}`);
await this.clear(viewColumn);
for (const [key, p] of this._annotationProviders) {
if (!TextEditorComparer.equals(p.editor, e.textEditor)) continue;
@ -168,7 +173,7 @@ export default class BlameAnnotationController extends Disposable {
Logger.log('VisibleTextEditorsChanged:', `Clear blame annotations for column ${key}`);
const editor = window.activeTextEditor;
if (p.requiresRenderWhitespaceToggle && (editor && editor.viewColumn !== key)) {
if (p.requiresRenderWhitespaceToggle && (editor && (editor.viewColumn || -1) !== key)) {
this.clear(key, false);
if (!this._pendingWhitespaceToggleDisposable) {

+ 2
- 1
src/blameAnnotationProvider.ts Ver arquivo

@ -47,7 +47,8 @@ export class BlameAnnotationProvider extends Disposable {
}
this.document = this.editor.document;
this._uri = GitUri.fromUri(this.document.uri);
this._uri = GitUri.fromUri(this.document.uri, this.git);
this._blame = this.git.getBlameForFile(this._uri.fsPath, this._uri.sha, this._uri.repoPath);
this._config = workspace.getConfiguration('gitlens').get<IBlameConfig>('blame');

+ 2
- 2
src/blameStatusBarController.ts Ver arquivo

@ -71,13 +71,13 @@ export default class BlameStatusBarController extends Disposable {
}
private async _onActiveTextEditorChanged(e: TextEditor): Promise<void> {
if (!e || !e.document || e.document.isUntitled || e.viewColumn === undefined) {
if (!e || !e.document || e.document.isUntitled || (e.viewColumn === undefined && !this.git.hasGitUriForFile(e))) {
this.clear();
return;
}
this._document = e.document;
this._uri = GitUri.fromUri(this._document.uri);
this._uri = GitUri.fromUri(this._document.uri, this.git);
const maxLines = this._config.advanced.caching.statusBar.maxLines;
this._useCaching = this._config.advanced.caching.enabled && (maxLines <= 0 || this._document.lineCount <= maxLines);
if (this._useCaching) {

+ 1
- 1
src/commands/diffLineWithPrevious.ts Ver arquivo

@ -21,7 +21,7 @@ export default class DiffLineWithPreviousCommand extends EditorCommand {
line = line || editor.selection.active.line;
if (!commit || GitProvider.isUncommitted(commit.sha)) {
const gitUri = GitUri.fromUri(uri);
const gitUri = GitUri.fromUri(uri, this.git);
const blameline = line - gitUri.offset;
if (blameline < 0) return undefined;

+ 1
- 1
src/commands/diffLineWithWorking.ts Ver arquivo

@ -21,7 +21,7 @@ export default class DiffLineWithWorkingCommand extends EditorCommand {
line = line || editor.selection.active.line;
if (!commit || GitProvider.isUncommitted(commit.sha)) {
const gitUri = GitUri.fromUri(uri);
const gitUri = GitUri.fromUri(uri, this.git);
const blameline = line - gitUri.offset;
if (blameline < 0) return undefined;

+ 1
- 1
src/commands/diffWithPrevious.ts Ver arquivo

@ -29,7 +29,7 @@ export default class DiffWithPreviousCommand extends EditorCommand {
}
if (!commit || rangeOrLine instanceof Range) {
const gitUri = GitUri.fromUri(uri);
const gitUri = GitUri.fromUri(uri, this.git);
try {
const log = await this.git.getLogForFile(gitUri.fsPath, gitUri.sha, gitUri.repoPath, <Range>rangeOrLine);

+ 2
- 2
src/commands/diffWithWorking.ts Ver arquivo

@ -23,7 +23,7 @@ export default class DiffWithWorkingCommand extends EditorCommand {
line = line || editor.selection.active.line;
if (!commit || GitProvider.isUncommitted(commit.sha)) {
const gitUri = GitUri.fromUri(uri);
const gitUri = GitUri.fromUri(uri, this.git);
try {
const log = await this.git.getLogForFile(gitUri.fsPath, gitUri.sha, gitUri.repoPath);
@ -37,7 +37,7 @@ export default class DiffWithWorkingCommand extends EditorCommand {
}
}
const gitUri = GitUri.fromUri(uri);
const gitUri = GitUri.fromUri(uri, this.git);
try {
const compare = await this.git.getVersionedFile(commit.uri.fsPath, commit.repoPath, commit.sha);

+ 1
- 1
src/commands/showBlameHistory.ts Ver arquivo

@ -20,7 +20,7 @@ export default class ShowBlameHistoryCommand extends EditorCommand {
position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start;
}
const gitUri = GitUri.fromUri(uri);
const gitUri = GitUri.fromUri(uri, this.git);
try {
const locations = await this.git.getBlameLocations(gitUri.fsPath, range, gitUri.sha, gitUri.repoPath, sha, line);

+ 1
- 1
src/commands/showFileHistory.ts Ver arquivo

@ -19,7 +19,7 @@ export default class ShowFileHistoryCommand extends EditorCommand {
position = editor.document.validateRange(new Range(0, 0, 0, 1000000)).start;
}
const gitUri = GitUri.fromUri(uri);
const gitUri = GitUri.fromUri(uri, this.git);
try {
const locations = await this.git.getLogLocations(gitUri.fsPath, gitUri.sha, gitUri.repoPath, sha, line);

+ 1
- 1
src/commands/showQuickFileHistory.ts Ver arquivo

@ -19,7 +19,7 @@ export default class ShowQuickFileHistoryCommand extends EditorCommand {
uri = editor.document.uri;
}
const gitUri = GitUri.fromUri(uri);
const gitUri = GitUri.fromUri(uri, this.git);
try {
const log = await this.git.getLogForFile(gitUri.fsPath, gitUri.sha, gitUri.repoPath);

+ 1
- 1
src/commands/showQuickRepoHistory.ts Ver arquivo

@ -23,7 +23,7 @@ export default class ShowQuickRepoHistoryCommand extends Command {
try {
let repoPath: string;
if (uri instanceof Uri) {
const gitUri = GitUri.fromUri(uri);
const gitUri = GitUri.fromUri(uri, this.git);
repoPath = gitUri.repoPath;
if (!repoPath) {

+ 1
- 1
src/gitCodeLensProvider.ts Ver arquivo

@ -50,7 +50,7 @@ export default class GitCodeLensProvider implements CodeLensProvider {
if (languageLocations.location === CodeLensLocation.None) return lenses;
const gitUri = GitUri.fromUri(document.uri);
const gitUri = GitUri.fromUri(document.uri, this.git);
const blamePromise = this.git.getBlameForFile(gitUri.fsPath, gitUri.sha, gitUri.repoPath);
let blame: IGitBlame;

+ 94
- 31
src/gitProvider.ts Ver arquivo

@ -14,7 +14,11 @@ import * as path from 'path';
export { Git };
export * from './git/git';
class CacheEntry {
class UriCacheEntry {
constructor(public uri: GitUri) { }
}
class GitCacheEntry {
blame?: ICachedBlame;
log?: ICachedLog;
@ -39,8 +43,9 @@ enum RemoveCacheReason {
}
export default class GitProvider extends Disposable {
private _cache: Map<string, CacheEntry> | undefined;
private _gitCache: Map<string, GitCacheEntry> | undefined;
private _cacheDisposable: Disposable | undefined;
private _uriCache: Map<string, UriCacheEntry> | undefined;
private _config: IConfig;
private _disposable: Disposable;
@ -85,11 +90,16 @@ export default class GitProvider extends Disposable {
this._disposable && this._disposable.dispose();
this._codeLensProviderDisposable && this._codeLensProviderDisposable.dispose();
this._cacheDisposable && this._cacheDisposable.dispose();
this._cache && this._cache.clear();
this._uriCache && this._uriCache.clear();
this._gitCache && this._gitCache.clear();
}
public get UseUriCaching() {
return !!this._uriCache;
}
public get UseCaching() {
return !!this._cache;
public get UseGitCaching() {
return !!this._gitCache;
}
private _onConfigure() {
@ -112,7 +122,8 @@ export default class GitProvider extends Disposable {
if (advancedChanged) {
if (config.advanced.caching.enabled) {
// TODO: Cache needs to be cleared on file changes -- createFileSystemWatcher or timeout?
this._cache = new Map();
this._gitCache = new Map();
this._uriCache = new Map();
const disposables: Disposable[] = [];
@ -128,8 +139,12 @@ export default class GitProvider extends Disposable {
else {
this._cacheDisposable && this._cacheDisposable.dispose();
this._cacheDisposable = undefined;
this._cache && this._cache.clear();
this._cache = undefined;
this._uriCache && this._uriCache.clear();
this._uriCache = undefined;
this._gitCache && this._gitCache.clear();
this._gitCache = undefined;
}
}
@ -141,19 +156,26 @@ export default class GitProvider extends Disposable {
}
private _removeCachedEntry(document: TextDocument, reason: RemoveCacheReason) {
if (!this.UseCaching) return;
if (!this.UseGitCaching) return;
if (document.uri.scheme !== DocumentSchemes.File) return;
const fileName = Git.normalizePath(document.fileName);
const cacheKey = this._getCacheEntryKey(fileName);
if (reason === RemoveCacheReason.DocumentClosed) {
// Don't remove this from cache because at least for now DocumentClosed can't really be trusted
// It seems to fire when an editor is no longer visible (but the tab still is)
// if (this._fileCache.delete(cacheKey)) {
// Logger.log(`Clear uri cache entry for '${cacheKey}', reason=${RemoveCacheReason[reason]}`);
// }
// Don't remove broken blame on close (since otherwise we'll have to run the broken blame again)
const entry = this._cache.get(cacheKey);
const entry = this._gitCache.get(cacheKey);
if (entry && entry.hasErrors) return;
}
if (this._cache.delete(cacheKey)) {
if (this._gitCache.delete(cacheKey)) {
Logger.log(`Clear cache entry for '${cacheKey}', reason=${RemoveCacheReason[reason]}`);
// if (reason === RemoveCacheReason.DocumentSaved) {
@ -163,6 +185,32 @@ export default class GitProvider extends Disposable {
}
}
hasGitUriForFile(editor: TextEditor): boolean;
hasGitUriForFile(fileName: string): boolean;
hasGitUriForFile(fileNameOrEditor: string | TextEditor): boolean {
if (!this.UseUriCaching) return false;
let fileName: string;
if (typeof fileNameOrEditor === 'string') {
fileName = fileNameOrEditor;
}
else {
if (!fileNameOrEditor || !fileNameOrEditor.document || !fileNameOrEditor.document.uri) return false;
fileName = fileNameOrEditor.document.uri.fsPath;
}
const cacheKey = this._getCacheEntryKey(fileName);
return this._uriCache.has(cacheKey);
}
getGitUriForFile(fileName: string) {
if (!this.UseUriCaching) return undefined;
const cacheKey = this._getCacheEntryKey(fileName);
const entry = this._uriCache.get(cacheKey);
return entry && entry.uri;
}
getRepoPath(cwd: string): Promise<string> {
return Git.repoPath(cwd);
}
@ -176,17 +224,17 @@ export default class GitProvider extends Disposable {
Logger.log(`getBlameForFile('${fileName}', ${sha}, ${repoPath})`);
fileName = Git.normalizePath(fileName);
const useCaching = this.UseCaching && !sha;
const useCaching = this.UseGitCaching && !sha;
let cacheKey: string | undefined;
let entry: CacheEntry | undefined;
let entry: GitCacheEntry | undefined;
if (useCaching) {
cacheKey = this._getCacheEntryKey(fileName);
entry = this._cache.get(cacheKey);
entry = this._gitCache.get(cacheKey);
if (entry !== undefined && entry.blame !== undefined) return entry.blame.item;
if (entry === undefined) {
entry = new CacheEntry();
entry = new GitCacheEntry();
}
}
@ -210,7 +258,7 @@ export default class GitProvider extends Disposable {
errorMessage: msg
};
this._cache.set(cacheKey, entry);
this._gitCache.set(cacheKey, entry);
return <Promise<IGitBlame>>GitProvider.EmptyPromise;
}
return undefined;
@ -225,7 +273,7 @@ export default class GitProvider extends Disposable {
item: promise
};
this._cache.set(cacheKey, entry);
this._gitCache.set(cacheKey, entry);
}
return promise;
@ -234,7 +282,7 @@ export default class GitProvider extends Disposable {
async getBlameForLine(fileName: string, line: number, sha?: string, repoPath?: string): Promise<IGitBlameLine | undefined> {
Logger.log(`getBlameForLine('${fileName}', ${line}, ${sha}, ${repoPath})`);
if (this.UseCaching && !sha) {
if (this.UseGitCaching && !sha) {
const blame = await this.getBlameForFile(fileName);
const blameLine = blame && blame.lines[line];
if (!blameLine) return undefined;
@ -375,17 +423,17 @@ export default class GitProvider extends Disposable {
Logger.log(`getLogForFile('${fileName}', ${sha}, ${repoPath}, ${range && `[${range.start.line}, ${range.end.line}]`})`);
fileName = Git.normalizePath(fileName);
const useCaching = this.UseCaching && !range;
const useCaching = this.UseGitCaching && !range;
let cacheKey: string;
let entry: CacheEntry;
let entry: GitCacheEntry;
if (useCaching) {
cacheKey = this._getCacheEntryKey(fileName);
entry = this._cache.get(cacheKey);
entry = this._gitCache.get(cacheKey);
if (entry !== undefined && entry.log !== undefined) return entry.log.item;
if (entry === undefined) {
entry = new CacheEntry();
entry = new GitCacheEntry();
}
}
@ -411,7 +459,7 @@ export default class GitProvider extends Disposable {
errorMessage: msg
};
this._cache.set(cacheKey, entry);
this._gitCache.set(cacheKey, entry);
return <Promise<IGitLog>>GitProvider.EmptyPromise;
}
return undefined;
@ -426,7 +474,7 @@ export default class GitProvider extends Disposable {
item: promise
};
this._cache.set(cacheKey, entry);
this._gitCache.set(cacheKey, entry);
}
return promise;
@ -455,9 +503,16 @@ export default class GitProvider extends Disposable {
return locations;
}
getVersionedFile(fileName: string, repoPath: string, sha: string) {
async getVersionedFile(fileName: string, repoPath: string, sha: string) {
Logger.log(`getVersionedFile('${fileName}', ${repoPath}, ${sha})`);
return Git.getVersionedFile(fileName, repoPath, sha);
const file = await Git.getVersionedFile(fileName, repoPath, sha);
if (this.UseUriCaching) {
const cacheKey = this._getCacheEntryKey(file);
const entry = new UriCacheEntry(new GitUri(Uri.file(fileName), { sha, repoPath, fileName }));
this._uriCache.set(cacheKey, entry);
}
return file;
}
getVersionedFileText(fileName: string, repoPath: string, sha: string) {
@ -524,12 +579,19 @@ export default class GitProvider extends Disposable {
}
}
export interface IGitCommitInfo {
sha: string;
repoPath: string;
fileName: string;
originalFileName?: string;
}
export class GitUri extends Uri {
offset: number;
repoPath?: string | undefined;
sha?: string | undefined;
constructor(uri?: Uri, commit?: GitCommit) {
constructor(uri?: Uri, commit?: IGitCommitInfo) {
super();
if (!uri) return;
@ -561,11 +623,12 @@ export class GitUri extends Uri {
return Uri.file(this.fsPath);
}
static fromCommit(uri: Uri, commit: GitCommit) {
return new GitUri(uri, commit);
}
static fromUri(uri: Uri, git?: GitProvider) {
if (git) {
const gitUri = git.getGitUriForFile(uri.fsPath);
if (gitUri) return gitUri;
}
static fromUri(uri: Uri) {
return new GitUri(uri);
}
}

+ 1
- 1
src/gitRevisionCodeLensProvider.ts Ver arquivo

@ -22,7 +22,7 @@ export default class GitRevisionCodeLensProvider implements CodeLensProvider {
constructor(context: ExtensionContext, private git: GitProvider) { }
async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise<CodeLens[]> {
const gitUri = GitUri.fromUri(document.uri);
const gitUri = GitUri.fromUri(document.uri, this.git);
const lenses: CodeLens[] = [];

Carregando…
Cancelar
Salvar