From 332b2c3b91ffb83b644a4e6cc78de0bfac777a58 Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Fri, 17 Mar 2017 14:12:09 -0400 Subject: [PATCH] Renames GitProvider to GitService --- src/blameActiveLineController.ts | 4 +- src/blameAnnotationController.ts | 4 +- src/blameAnnotationFormatter.ts | 2 +- src/blameAnnotationProvider.ts | 4 +- src/blameabilityTracker.ts | 4 +- src/commands/closeUnchangedFiles.ts | 4 +- src/commands/copyMessageToClipboard.ts | 4 +- src/commands/copyShaToClipboard.ts | 4 +- src/commands/diffDirectory.ts | 4 +- src/commands/diffLineWithPrevious.ts | 6 +- src/commands/diffLineWithWorking.ts | 6 +- src/commands/diffWithNext.ts | 4 +- src/commands/diffWithPrevious.ts | 4 +- src/commands/diffWithWorking.ts | 6 +- src/commands/openChangedFiles.ts | 4 +- src/commands/showBlameHistory.ts | 4 +- src/commands/showFileHistory.ts | 4 +- src/commands/showQuickCommitDetails.ts | 4 +- src/commands/showQuickCommitFileDetails.ts | 4 +- src/commands/showQuickFileHistory.ts | 4 +- src/commands/showQuickRepoHistory.ts | 4 +- src/commands/showQuickRepoStatus.ts | 4 +- src/commands/toggleCodeLens.ts | 4 +- src/extension.ts | 4 +- src/git/gitUri.ts | 6 +- src/gitCodeLensProvider.ts | 4 +- src/gitContentProvider.ts | 6 +- src/gitProvider.ts | 718 ----------------------------- src/gitRevisionCodeLensProvider.ts | 10 +- src/gitService.ts | 718 +++++++++++++++++++++++++++++ src/quickPicks/commitDetails.ts | 6 +- src/quickPicks/commitFileDetails.ts | 6 +- src/quickPicks/fileHistory.ts | 2 +- src/quickPicks/gitQuickPicks.ts | 4 +- src/quickPicks/repoHistory.ts | 2 +- src/quickPicks/repoStatus.ts | 2 +- 36 files changed, 792 insertions(+), 792 deletions(-) delete mode 100644 src/gitProvider.ts create mode 100644 src/gitService.ts diff --git a/src/blameActiveLineController.ts b/src/blameActiveLineController.ts index 7af6e36..18297c4 100644 --- a/src/blameActiveLineController.ts +++ b/src/blameActiveLineController.ts @@ -7,7 +7,7 @@ import { BlameAnnotationFormat, BlameAnnotationFormatter } from './blameAnnotati import { TextEditorComparer } from './comparers'; import { IBlameConfig, IConfig, StatusBarCommand } from './configuration'; import { DocumentSchemes } from './constants'; -import { GitCommit, GitProvider, GitUri, IGitBlame, IGitCommitLine } from './gitProvider'; +import { GitCommit, GitService, GitUri, IGitBlame, IGitCommitLine } from './gitService'; import * as moment from 'moment'; const activeLineDecoration: TextEditorDecorationType = window.createTextEditorDecorationType({ @@ -30,7 +30,7 @@ export class BlameActiveLineController extends Disposable { private _uri: GitUri; private _useCaching: boolean; - constructor(context: ExtensionContext, private git: GitProvider, private blameabilityTracker: BlameabilityTracker, private annotationController: BlameAnnotationController) { + constructor(context: ExtensionContext, private git: GitService, private blameabilityTracker: BlameabilityTracker, private annotationController: BlameAnnotationController) { super(() => this.dispose()); this._updateBlameDebounced = Functions.debounce(this._updateBlame, 50); diff --git a/src/blameAnnotationController.ts b/src/blameAnnotationController.ts index d12696c..f779f6d 100644 --- a/src/blameAnnotationController.ts +++ b/src/blameAnnotationController.ts @@ -5,7 +5,7 @@ import { BlameabilityChangeEvent, BlameabilityTracker } from './blameabilityTrac import { BlameAnnotationProvider } from './blameAnnotationProvider'; import { TextDocumentComparer, TextEditorComparer } from './comparers'; import { IBlameConfig } from './configuration'; -import { GitProvider, GitUri } from './gitProvider'; +import { GitService, GitUri } from './gitService'; import { Logger } from './logger'; import { WhitespaceController } from './whitespaceController'; @@ -34,7 +34,7 @@ export class BlameAnnotationController extends Disposable { private _disposable: Disposable; private _whitespaceController: WhitespaceController | undefined; - constructor(private context: ExtensionContext, private git: GitProvider, private blameabilityTracker: BlameabilityTracker) { + constructor(private context: ExtensionContext, private git: GitService, private blameabilityTracker: BlameabilityTracker) { super(() => this.dispose()); this._onConfigurationChanged(); diff --git a/src/blameAnnotationFormatter.ts b/src/blameAnnotationFormatter.ts index 973d1f0..5b440e0 100644 --- a/src/blameAnnotationFormatter.ts +++ b/src/blameAnnotationFormatter.ts @@ -1,6 +1,6 @@ 'use strict'; import { IBlameConfig } from './configuration'; -import { GitCommit, IGitCommitLine } from './gitProvider'; +import { GitCommit, IGitCommitLine } from './gitService'; import * as moment from 'moment'; export const defaultAbsoluteDateLength = 10; diff --git a/src/blameAnnotationProvider.ts b/src/blameAnnotationProvider.ts index 70ac41f..85cdbea 100644 --- a/src/blameAnnotationProvider.ts +++ b/src/blameAnnotationProvider.ts @@ -5,7 +5,7 @@ import { BlameAnnotationFormat, BlameAnnotationFormatter, cssIndent, defaultAuth import { BlameDecorations } from './blameAnnotationController'; import { TextDocumentComparer } from './comparers'; import { BlameAnnotationStyle, IBlameConfig } from './configuration'; -import { GitProvider, GitUri, IGitBlame } from './gitProvider'; +import { GitService, GitUri, IGitBlame } from './gitService'; import { WhitespaceController } from './whitespaceController'; export class BlameAnnotationProvider extends Disposable { @@ -16,7 +16,7 @@ export class BlameAnnotationProvider extends Disposable { private _config: IBlameConfig; private _disposable: Disposable; - constructor(context: ExtensionContext, private git: GitProvider, private whitespaceController: WhitespaceController | undefined, public editor: TextEditor, private uri: GitUri) { + constructor(context: ExtensionContext, private git: GitService, private whitespaceController: WhitespaceController | undefined, public editor: TextEditor, private uri: GitUri) { super(() => this.dispose()); this.document = this.editor.document; diff --git a/src/blameabilityTracker.ts b/src/blameabilityTracker.ts index 230312f..500daf4 100644 --- a/src/blameabilityTracker.ts +++ b/src/blameabilityTracker.ts @@ -2,7 +2,7 @@ import { Disposable, Event, EventEmitter, TextDocument, TextDocumentChangeEvent, TextEditor, window, workspace } from 'vscode'; import { CommandContext, setCommandContext } from './commands'; import { TextDocumentComparer } from './comparers'; -import { GitProvider } from './gitProvider'; +import { GitService } from './gitService'; export interface BlameabilityChangeEvent { blameable: boolean; @@ -21,7 +21,7 @@ export class BlameabilityTracker extends Disposable { private _editor: TextEditor; private _isBlameable: boolean; - constructor(private git: GitProvider) { + constructor(private git: GitService) { super(() => this.dispose()); const subscriptions: Disposable[] = []; diff --git a/src/commands/closeUnchangedFiles.ts b/src/commands/closeUnchangedFiles.ts index aaa08ee..8c64b30 100644 --- a/src/commands/closeUnchangedFiles.ts +++ b/src/commands/closeUnchangedFiles.ts @@ -3,13 +3,13 @@ import { TextEditor, Uri, window } from 'vscode'; import { ActiveEditorTracker } from '../activeEditorTracker'; import { ActiveEditorCommand, Commands } from './commands'; import { TextEditorComparer, UriComparer } from '../comparers'; -import { GitProvider } from '../gitProvider'; +import { GitService } from '../gitService'; import { Logger } from '../logger'; import * as path from 'path'; export class CloseUnchangedFilesCommand extends ActiveEditorCommand { - constructor(private git: GitProvider, private repoPath: string) { + constructor(private git: GitService, private repoPath: string) { super(Commands.CloseUnchangedFiles); } diff --git a/src/commands/copyMessageToClipboard.ts b/src/commands/copyMessageToClipboard.ts index 8728298..d412302 100644 --- a/src/commands/copyMessageToClipboard.ts +++ b/src/commands/copyMessageToClipboard.ts @@ -2,13 +2,13 @@ import { Iterables } from '../system'; import { TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from './commands'; -import { GitProvider, GitUri } from '../gitProvider'; +import { GitService, GitUri } from '../gitService'; import { Logger } from '../logger'; import { copy } from 'copy-paste'; export class CopyMessageToClipboardCommand extends ActiveEditorCommand { - constructor(private git: GitProvider, private repoPath: string) { + constructor(private git: GitService, private repoPath: string) { super(Commands.CopyMessageToClipboard); } diff --git a/src/commands/copyShaToClipboard.ts b/src/commands/copyShaToClipboard.ts index 75327de..417cce1 100644 --- a/src/commands/copyShaToClipboard.ts +++ b/src/commands/copyShaToClipboard.ts @@ -2,13 +2,13 @@ import { Iterables } from '../system'; import { TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from './commands'; -import { GitProvider, GitUri } from '../gitProvider'; +import { GitService, GitUri } from '../gitService'; import { Logger } from '../logger'; import { copy } from 'copy-paste'; export class CopyShaToClipboardCommand extends ActiveEditorCommand { - constructor(private git: GitProvider, private repoPath: string) { + constructor(private git: GitService, private repoPath: string) { super(Commands.CopyShaToClipboard); } diff --git a/src/commands/diffDirectory.ts b/src/commands/diffDirectory.ts index 1cb093f..10ff4ac 100644 --- a/src/commands/diffDirectory.ts +++ b/src/commands/diffDirectory.ts @@ -1,12 +1,12 @@ 'use strict'; import { TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from './commands'; -import { GitProvider } from '../gitProvider'; +import { GitService } from '../gitService'; import { Logger } from '../logger'; export class DiffDirectoryCommand extends ActiveEditorCommand { - constructor(private git: GitProvider, private repoPath: string) { + constructor(private git: GitService, private repoPath: string) { super(Commands.DiffDirectory); } diff --git a/src/commands/diffLineWithPrevious.ts b/src/commands/diffLineWithPrevious.ts index f866552..8415e67 100644 --- a/src/commands/diffLineWithPrevious.ts +++ b/src/commands/diffLineWithPrevious.ts @@ -2,13 +2,13 @@ import { commands, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from './commands'; import { BuiltInCommands } from '../constants'; -import { GitCommit, GitProvider, GitUri } from '../gitProvider'; +import { GitCommit, GitService, GitUri } from '../gitService'; import { Logger } from '../logger'; import * as path from 'path'; export class DiffLineWithPreviousCommand extends ActiveEditorCommand { - constructor(private git: GitProvider) { + constructor(private git: GitService) { super(Commands.DiffLineWithPrevious); } @@ -23,7 +23,7 @@ export class DiffLineWithPreviousCommand extends ActiveEditorCommand { const gitUri = await GitUri.fromUri(uri, this.git); line = line || (editor && editor.selection.active.line) || gitUri.offset; - if (!commit || GitProvider.isUncommitted(commit.sha)) { + if (!commit || GitService.isUncommitted(commit.sha)) { if (editor && editor.document && editor.document.isDirty) return undefined; const blameline = line - gitUri.offset; diff --git a/src/commands/diffLineWithWorking.ts b/src/commands/diffLineWithWorking.ts index 4b522b8..d7387e1 100644 --- a/src/commands/diffLineWithWorking.ts +++ b/src/commands/diffLineWithWorking.ts @@ -1,12 +1,12 @@ 'use strict'; import { commands, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from './commands'; -import { GitCommit, GitProvider, GitUri } from '../gitProvider'; +import { GitCommit, GitService, GitUri } from '../gitService'; import { Logger } from '../logger'; export class DiffLineWithWorkingCommand extends ActiveEditorCommand { - constructor(private git: GitProvider) { + constructor(private git: GitService) { super(Commands.DiffLineWithWorking); } @@ -21,7 +21,7 @@ export class DiffLineWithWorkingCommand extends ActiveEditorCommand { const gitUri = await GitUri.fromUri(uri, this.git); line = line || (editor && editor.selection.active.line) || gitUri.offset; - if (!commit || GitProvider.isUncommitted(commit.sha)) { + if (!commit || GitService.isUncommitted(commit.sha)) { if (editor && editor.document && editor.document.isDirty) return undefined; const blameline = line - gitUri.offset; diff --git a/src/commands/diffWithNext.ts b/src/commands/diffWithNext.ts index 49f10dd..4506b72 100644 --- a/src/commands/diffWithNext.ts +++ b/src/commands/diffWithNext.ts @@ -3,14 +3,14 @@ import { Iterables } from '../system'; import { commands, Range, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from './commands'; import { BuiltInCommands } from '../constants'; -import { GitLogCommit, GitProvider, GitUri } from '../gitProvider'; +import { GitLogCommit, GitService, GitUri } from '../gitService'; import { Logger } from '../logger'; // import * as moment from 'moment'; import * as path from 'path'; export class DiffWithNextCommand extends ActiveEditorCommand { - constructor(private git: GitProvider) { + constructor(private git: GitService) { super(Commands.DiffWithNext); } diff --git a/src/commands/diffWithPrevious.ts b/src/commands/diffWithPrevious.ts index 802255c..4ca6bbe 100644 --- a/src/commands/diffWithPrevious.ts +++ b/src/commands/diffWithPrevious.ts @@ -3,14 +3,14 @@ import { Iterables } from '../system'; import { commands, Range, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from './commands'; import { BuiltInCommands } from '../constants'; -import { GitCommit, GitProvider, GitUri } from '../gitProvider'; +import { GitCommit, GitService, GitUri } from '../gitService'; import { Logger } from '../logger'; import * as moment from 'moment'; import * as path from 'path'; export class DiffWithPreviousCommand extends ActiveEditorCommand { - constructor(private git: GitProvider) { + constructor(private git: GitService) { super(Commands.DiffWithPrevious); } diff --git a/src/commands/diffWithWorking.ts b/src/commands/diffWithWorking.ts index c371cb2..7b6c6c5 100644 --- a/src/commands/diffWithWorking.ts +++ b/src/commands/diffWithWorking.ts @@ -3,13 +3,13 @@ import { Iterables } from '../system'; import { commands, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from './commands'; import { BuiltInCommands } from '../constants'; -import { GitCommit, GitProvider, GitUri } from '../gitProvider'; +import { GitCommit, GitService, GitUri } from '../gitService'; import { Logger } from '../logger'; import * as path from 'path'; export class DiffWithWorkingCommand extends ActiveEditorCommand { - constructor(private git: GitProvider) { + constructor(private git: GitService) { super(Commands.DiffWithWorking); } @@ -23,7 +23,7 @@ export class DiffWithWorkingCommand extends ActiveEditorCommand { line = line || (editor && editor.selection.active.line) || 0; - if (!commit || GitProvider.isUncommitted(commit.sha)) { + if (!commit || GitService.isUncommitted(commit.sha)) { const gitUri = await GitUri.fromUri(uri, this.git); try { diff --git a/src/commands/openChangedFiles.ts b/src/commands/openChangedFiles.ts index b42defd..126bb28 100644 --- a/src/commands/openChangedFiles.ts +++ b/src/commands/openChangedFiles.ts @@ -1,13 +1,13 @@ 'use strict'; import { TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands, openEditor } from './commands'; -import { GitProvider } from '../gitProvider'; +import { GitService } from '../gitService'; import { Logger } from '../logger'; import * as path from 'path'; export class OpenChangedFilesCommand extends ActiveEditorCommand { - constructor(private git: GitProvider, private repoPath: string) { + constructor(private git: GitService, private repoPath: string) { super(Commands.OpenChangedFiles); } diff --git a/src/commands/showBlameHistory.ts b/src/commands/showBlameHistory.ts index d9efbac..93a9ba3 100644 --- a/src/commands/showBlameHistory.ts +++ b/src/commands/showBlameHistory.ts @@ -2,12 +2,12 @@ import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode'; import { Commands, EditorCommand } from './commands'; import { BuiltInCommands } from '../constants'; -import { GitProvider, GitUri } from '../gitProvider'; +import { GitService, GitUri } from '../gitService'; import { Logger } from '../logger'; export class ShowBlameHistoryCommand extends EditorCommand { - constructor(private git: GitProvider) { + constructor(private git: GitService) { super(Commands.ShowBlameHistory); } diff --git a/src/commands/showFileHistory.ts b/src/commands/showFileHistory.ts index 585ea00..38fb35a 100644 --- a/src/commands/showFileHistory.ts +++ b/src/commands/showFileHistory.ts @@ -2,12 +2,12 @@ import { commands, Position, Range, TextEditor, TextEditorEdit, Uri, window } from 'vscode'; import { Commands, EditorCommand } from './commands'; import { BuiltInCommands } from '../constants'; -import { GitProvider, GitUri } from '../gitProvider'; +import { GitService, GitUri } from '../gitService'; import { Logger } from '../logger'; export class ShowFileHistoryCommand extends EditorCommand { - constructor(private git: GitProvider) { + constructor(private git: GitService) { super(Commands.ShowFileHistory); } diff --git a/src/commands/showQuickCommitDetails.ts b/src/commands/showQuickCommitDetails.ts index 87d3b26..47e0407 100644 --- a/src/commands/showQuickCommitDetails.ts +++ b/src/commands/showQuickCommitDetails.ts @@ -1,13 +1,13 @@ 'use strict'; import { commands, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from './commands'; -import { GitCommit, GitLogCommit, GitProvider, GitUri, IGitLog } from '../gitProvider'; +import { GitCommit, GitLogCommit, GitService, GitUri, IGitLog } from '../gitService'; import { Logger } from '../logger'; import { CommandQuickPickItem, CommitDetailsQuickPick, CommitWithFileStatusQuickPickItem } from '../quickPicks'; export class ShowQuickCommitDetailsCommand extends ActiveEditorCommand { - constructor(private git: GitProvider, private repoPath: string) { + constructor(private git: GitService, private repoPath: string) { super(Commands.ShowQuickCommitDetails); } diff --git a/src/commands/showQuickCommitFileDetails.ts b/src/commands/showQuickCommitFileDetails.ts index b7a186f..0678857 100644 --- a/src/commands/showQuickCommitFileDetails.ts +++ b/src/commands/showQuickCommitFileDetails.ts @@ -1,14 +1,14 @@ 'use strict'; import { TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from './commands'; -import { GitCommit, GitLogCommit, GitProvider, GitUri, IGitLog } from '../gitProvider'; +import { GitCommit, GitLogCommit, GitService, GitUri, IGitLog } from '../gitService'; import { Logger } from '../logger'; import { CommandQuickPickItem, CommitFileDetailsQuickPick } from '../quickPicks'; import * as path from 'path'; export class ShowQuickCommitFileDetailsCommand extends ActiveEditorCommand { - constructor(private git: GitProvider) { + constructor(private git: GitService) { super(Commands.ShowQuickCommitFileDetails); } diff --git a/src/commands/showQuickFileHistory.ts b/src/commands/showQuickFileHistory.ts index f1f24a0..9a39d6a 100644 --- a/src/commands/showQuickFileHistory.ts +++ b/src/commands/showQuickFileHistory.ts @@ -1,14 +1,14 @@ 'use strict'; import { commands, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from '../commands'; -import { GitProvider, GitUri, IGitLog } from '../gitProvider'; +import { GitService, GitUri, IGitLog } from '../gitService'; import { Logger } from '../logger'; import { CommandQuickPickItem, FileHistoryQuickPick } from '../quickPicks'; import * as path from 'path'; export class ShowQuickFileHistoryCommand extends ActiveEditorCommand { - constructor(private git: GitProvider) { + constructor(private git: GitService) { super(Commands.ShowQuickFileHistory); } diff --git a/src/commands/showQuickRepoHistory.ts b/src/commands/showQuickRepoHistory.ts index ba7ce5c..f89d21d 100644 --- a/src/commands/showQuickRepoHistory.ts +++ b/src/commands/showQuickRepoHistory.ts @@ -1,13 +1,13 @@ 'use strict'; import { commands, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from '../commands'; -import { GitProvider, GitUri, IGitLog } from '../gitProvider'; +import { GitService, GitUri, IGitLog } from '../gitService'; import { Logger } from '../logger'; import { CommandQuickPickItem, RepoHistoryQuickPick } from '../quickPicks'; export class ShowQuickRepoHistoryCommand extends ActiveEditorCommand { - constructor(private git: GitProvider, private repoPath: string) { + constructor(private git: GitService, private repoPath: string) { super(Commands.ShowQuickRepoHistory); } diff --git a/src/commands/showQuickRepoStatus.ts b/src/commands/showQuickRepoStatus.ts index 3c3d0b1..c561ee2 100644 --- a/src/commands/showQuickRepoStatus.ts +++ b/src/commands/showQuickRepoStatus.ts @@ -1,13 +1,13 @@ 'use strict'; import { TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from './commands'; -import { GitProvider } from '../gitProvider'; +import { GitService } from '../gitService'; import { Logger } from '../logger'; import { CommandQuickPickItem, RepoStatusQuickPick } from '../quickPicks'; export class ShowQuickRepoStatusCommand extends ActiveEditorCommand { - constructor(private git: GitProvider, private repoPath: string) { + constructor(private git: GitService, private repoPath: string) { super(Commands.ShowQuickRepoStatus); } diff --git a/src/commands/toggleCodeLens.ts b/src/commands/toggleCodeLens.ts index 4026062..6e36344 100644 --- a/src/commands/toggleCodeLens.ts +++ b/src/commands/toggleCodeLens.ts @@ -1,11 +1,11 @@ 'use strict'; import { TextEditor, TextEditorEdit } from 'vscode'; import { Commands, EditorCommand } from './commands'; -import { GitProvider } from '../gitProvider'; +import { GitService } from '../gitService'; export class ToggleCodeLensCommand extends EditorCommand { - constructor(private git: GitProvider) { + constructor(private git: GitService) { super(Commands.ToggleCodeLens); } diff --git a/src/extension.ts b/src/extension.ts index 5ec9a66..df3b620 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -16,7 +16,7 @@ import { Keyboard } from './commands'; import { IAdvancedConfig, IBlameConfig } from './configuration'; import { WorkspaceState } from './constants'; import { GitContentProvider } from './gitContentProvider'; -import { Git, GitProvider } from './gitProvider'; +import { Git, GitService } from './gitService'; import { GitRevisionCodeLensProvider } from './gitRevisionCodeLensProvider'; import { Logger } from './logger'; @@ -70,7 +70,7 @@ export async function activate(context: ExtensionContext) { context.workspaceState.update(WorkspaceState.RepoPath, repoPath); - const git = new GitProvider(context); + const git = new GitService(context); context.subscriptions.push(git); const blameabilityTracker = new BlameabilityTracker(git); diff --git a/src/git/gitUri.ts b/src/git/gitUri.ts index bb07d8b..6e5d000 100644 --- a/src/git/gitUri.ts +++ b/src/git/gitUri.ts @@ -2,7 +2,7 @@ import { Iterables } from '../system'; import { Uri } from 'vscode'; import { DocumentSchemes } from '../constants'; -import { Git, GitProvider } from '../gitProvider'; +import { Git, GitService } from '../gitService'; import * as path from 'path'; export class GitUri extends Uri { @@ -26,7 +26,7 @@ export class GitUri extends Uri { this.offset = 0; if (uri.scheme === DocumentSchemes.GitLensGit) { - const data = GitProvider.fromGitContentUri(uri); + const data = GitService.fromGitContentUri(uri); base._fsPath = path.resolve(data.repoPath, data.originalFileName || data.fileName); this.offset = (data.decoration && data.decoration.split('\n').length) || 0; @@ -69,7 +69,7 @@ export class GitUri extends Uri { : `${path.basename(this.fsPath)}${separator}${directory}`; } - static async fromUri(uri: Uri, git: GitProvider) { + static async fromUri(uri: Uri, git: GitService) { if (uri instanceof GitUri) return uri; const gitUri = git.getGitUriForFile(uri.fsPath); diff --git a/src/gitCodeLensProvider.ts b/src/gitCodeLensProvider.ts index a720d35..be294f5 100644 --- a/src/gitCodeLensProvider.ts +++ b/src/gitCodeLensProvider.ts @@ -4,7 +4,7 @@ import { CancellationToken, CodeLens, CodeLensProvider, commands, DocumentSelect import { Commands } from './commands'; import { BuiltInCommands, DocumentSchemes } from './constants'; import { CodeLensCommand, CodeLensLocation, IConfig, ICodeLensLanguageLocation } from './configuration'; -import { GitCommit, GitProvider, GitUri, IGitBlame, IGitBlameLines } from './gitProvider'; +import { GitCommit, GitService, GitUri, IGitBlame, IGitBlameLines } from './gitService'; import { Logger } from './logger'; import * as moment from 'moment'; @@ -42,7 +42,7 @@ export default class GitCodeLensProvider implements CodeLensProvider { private _config: IConfig; private _documentIsDirty: boolean; - constructor(context: ExtensionContext, private git: GitProvider) { + constructor(context: ExtensionContext, private git: GitService) { this._config = workspace.getConfiguration('').get('gitlens'); } diff --git a/src/gitContentProvider.ts b/src/gitContentProvider.ts index 55c6cc6..8a7eb24 100644 --- a/src/gitContentProvider.ts +++ b/src/gitContentProvider.ts @@ -1,7 +1,7 @@ 'use strict'; import { ExtensionContext, TextDocumentContentProvider, Uri, window } from 'vscode'; import { DocumentSchemes } from './constants'; -import { GitProvider } from './gitProvider'; +import { GitService } from './gitService'; import { Logger } from './logger'; import * as path from 'path'; @@ -9,10 +9,10 @@ export class GitContentProvider implements TextDocumentContentProvider { static scheme = DocumentSchemes.GitLensGit; - constructor(context: ExtensionContext, private git: GitProvider) { } + constructor(context: ExtensionContext, private git: GitService) { } async provideTextDocumentContent(uri: Uri): Promise { - const data = GitProvider.fromGitContentUri(uri); + const data = GitService.fromGitContentUri(uri); const fileName = data.originalFileName || data.fileName; try { let text = await this.git.getVersionedFileText(fileName, data.repoPath, data.sha) as string; diff --git a/src/gitProvider.ts b/src/gitProvider.ts deleted file mode 100644 index cde0e63..0000000 --- a/src/gitProvider.ts +++ /dev/null @@ -1,718 +0,0 @@ -'use strict'; -import { Iterables, Objects } from './system'; -import { Disposable, Event, EventEmitter, ExtensionContext, FileSystemWatcher, languages, Location, Position, Range, TextDocument, TextEditor, Uri, workspace } from 'vscode'; -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 { IGitUriData, GitUri } from './git/gitUri'; -import GitCodeLensProvider from './gitCodeLensProvider'; -import { Logger } from './logger'; -import * as fs from 'fs'; -import * as ignore from 'ignore'; -import * as moment from 'moment'; -import * as path from 'path'; - -export { getGitStatusIcon } from './git/gitEnrichment'; -export { Git, GitUri }; -export * from './git/git'; - -class UriCacheEntry { - - constructor(public uri: GitUri) { } -} - -class GitCacheEntry { - - blame?: ICachedBlame; - log?: ICachedLog; - - get hasErrors() { - return !!((this.blame && this.blame.errorMessage) || (this.log && this.log.errorMessage)); - } -} - -interface ICachedItem { - //date: Date; - item: Promise; - errorMessage?: string; -} - -interface ICachedBlame extends ICachedItem { } -interface ICachedLog extends ICachedItem { } - -enum RemoveCacheReason { - DocumentClosed, - DocumentSaved -} - -export class GitProvider extends Disposable { - - private _onDidChangeGitCacheEmitter = new EventEmitter(); - get onDidChangeGitCache(): Event { - return this._onDidChangeGitCacheEmitter.event; - } - - private _onDidBlameFailEmitter = new EventEmitter(); - get onDidBlameFail(): Event { - return this._onDidBlameFailEmitter.event; - } - - public repoPath: string; - - private _gitCache: Map | undefined; - private _cacheDisposable: Disposable | undefined; - private _uriCache: Map | undefined; - - config: IConfig; - private _codeLensProvider: GitCodeLensProvider | undefined; - private _codeLensProviderDisposable: Disposable | undefined; - private _disposable: Disposable; - private _fsWatcher: FileSystemWatcher; - private _gitignore: Promise; - - static EmptyPromise: Promise = Promise.resolve(undefined); - static BlameFormat = GitBlameFormat.incremental; - - constructor(private context: ExtensionContext) { - super(() => this.dispose()); - - this.repoPath = context.workspaceState.get(WorkspaceState.RepoPath) as string; - - this._onConfigurationChanged(); - - const subscriptions: Disposable[] = []; - - subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this)); - - this._disposable = Disposable.from(...subscriptions); - } - - dispose() { - this._disposable && this._disposable.dispose(); - - this._codeLensProviderDisposable && this._codeLensProviderDisposable.dispose(); - this._codeLensProviderDisposable = undefined; - this._codeLensProvider = undefined; - - this._cacheDisposable && this._cacheDisposable.dispose(); - this._cacheDisposable = undefined; - - this._fsWatcher && this._fsWatcher.dispose(); - this._fsWatcher = undefined; - - this._gitCache && this._gitCache.clear(); - this._gitCache = undefined; - this._uriCache && this._uriCache.clear(); - this._uriCache = undefined; - } - - public getBlameability(fileName: string): boolean { - if (!this.UseGitCaching) return true; - - const cacheKey = this.getCacheEntryKey(Git.normalizePath(fileName)); - const entry = this._gitCache.get(cacheKey); - return !(entry && entry.hasErrors); - } - - public get UseUriCaching() { - return !!this._uriCache; - } - - public get UseGitCaching() { - return !!this._gitCache; - } - - private _onConfigurationChanged() { - 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); - - if (codeLensChanged || advancedChanged) { - Logger.log('CodeLens config changed; resetting CodeLens provider'); - if (config.codeLens.visibility === CodeLensVisibility.Auto && (config.codeLens.recentChange.enabled || config.codeLens.authors.enabled)) { - if (this._codeLensProvider) { - this._codeLensProvider.reset(); - } - else { - this._codeLensProvider = new GitCodeLensProvider(this.context, this); - this._codeLensProviderDisposable = languages.registerCodeLensProvider(GitCodeLensProvider.selector, this._codeLensProvider); - } - } - else { - this._codeLensProviderDisposable && this._codeLensProviderDisposable.dispose(); - this._codeLensProviderDisposable = undefined; - this._codeLensProvider = undefined; - } - - setCommandContext(CommandContext.CanToggleCodeLens, config.codeLens.visibility === CodeLensVisibility.OnDemand && (config.codeLens.recentChange.enabled || config.codeLens.authors.enabled)); - } - - if (advancedChanged) { - if (config.advanced.caching.enabled) { - this._gitCache = new Map(); - this._uriCache = new Map(); - - this._cacheDisposable && this._cacheDisposable.dispose(); - - this._fsWatcher = this._fsWatcher || workspace.createFileSystemWatcher('**/.git/index', true, false, true); - - const disposables: Disposable[] = []; - - disposables.push(workspace.onDidCloseTextDocument(d => this._removeCachedEntry(d, RemoveCacheReason.DocumentClosed))); - disposables.push(workspace.onDidSaveTextDocument(d => this._removeCachedEntry(d, RemoveCacheReason.DocumentSaved))); - disposables.push(this._fsWatcher.onDidChange(this._onGitChanged, this)); - - this._cacheDisposable = Disposable.from(...disposables); - } - else { - this._cacheDisposable && this._cacheDisposable.dispose(); - this._cacheDisposable = undefined; - - this._fsWatcher && this._fsWatcher.dispose(); - this._fsWatcher = undefined; - - this._gitCache && this._gitCache.clear(); - this._gitCache = undefined; - - this._uriCache && this._uriCache.clear(); - this._uriCache = undefined; - } - - this._gitignore = new Promise((resolve, reject) => { - if (!config.advanced.gitignore.enabled) { - resolve(undefined); - return; - } - - const gitignorePath = path.join(this.repoPath, '.gitignore'); - fs.exists(gitignorePath, e => { - if (e) { - fs.readFile(gitignorePath, 'utf8', (err, data) => { - if (!err) { - resolve(ignore().add(data)); - return; - } - resolve(undefined); - }); - return; - } - resolve(undefined); - }); - }); - } - - this.config = config; - } - - getCacheEntryKey(fileName: string) { - return fileName.toLowerCase(); - } - - private _onGitChanged() { - this._gitCache && this._gitCache.clear(); - - this._onDidChangeGitCacheEmitter.fire(); - this._codeLensProvider && this._codeLensProvider.reset(); - } - - private _removeCachedEntry(document: TextDocument, reason: RemoveCacheReason) { - 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.DocumentSaved) { - // Don't remove broken blame on save (since otherwise we'll have to run the broken blame again) - const entry = this._gitCache.get(cacheKey); - if (entry && entry.hasErrors) return; - } - - if (this._gitCache.delete(cacheKey)) { - Logger.log(`Clear cache entry for '${cacheKey}', reason=${RemoveCacheReason[reason]}`); - - if (reason === RemoveCacheReason.DocumentSaved) { - this._onDidChangeGitCacheEmitter.fire(); - - // Refresh the codelenses with the updated blame - this._codeLensProvider && this._codeLensProvider.reset(); - } - } - } - - 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); - } - - async findMostRecentCommitForFile(fileName: string, sha?: string): Promise { - const exists = await new Promise((resolve, reject) => fs.exists(fileName, e => resolve(e))); - if (exists) return null; - - return undefined; - - // TODO: Get this to work -- for some reason a reverse log won't return the renamed file - // Not sure how else to figure this out - - // let log: IGitLog; - // let commit: GitCommit; - // while (true) { - // // Go backward from the current commit to head to find the latest filename - // log = await this.getLogForFile(fileName, sha, undefined, undefined, undefined, true); - // if (!log) break; - - // commit = Iterables.first(log.commits.values()); - // sha = commit.sha; - // fileName = commit.fileName; - // } - - // return commit; - } - - 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 { - return Git.repoPath(cwd); - } - - async getRepoPathFromUri(uri?: Uri, fallbackRepoPath?: string): Promise { - if (!(uri instanceof Uri)) return fallbackRepoPath; - - const gitUri = await GitUri.fromUri(uri, this); - if (gitUri.repoPath) return gitUri.repoPath; - - return (await this.getRepoPathFromFile(gitUri.fsPath)) || fallbackRepoPath; - } - - async getRepoPathFromFile(fileName: string): Promise { - const log = await this.getLogForFile(fileName, undefined, undefined, undefined, 1); - return log && log.repoPath; - } - - getBlameForFile(fileName: string, sha?: string, repoPath?: string): Promise { - Logger.log(`getBlameForFile('${fileName}', ${sha}, '${repoPath}')`); - fileName = Git.normalizePath(fileName); - - const useCaching = this.UseGitCaching && !sha; - - let cacheKey: string | undefined; - let entry: GitCacheEntry | undefined; - if (useCaching) { - cacheKey = this.getCacheEntryKey(fileName); - entry = this._gitCache.get(cacheKey); - - if (entry !== undefined && entry.blame !== undefined) return entry.blame.item; - if (entry === undefined) { - entry = new GitCacheEntry(); - } - } - - const promise = this._gitignore.then(ignore => { - if (ignore && !ignore.filter([fileName]).length) { - Logger.log(`Skipping blame; '${fileName}' is gitignored`); - if (cacheKey) { - this._onDidBlameFailEmitter.fire(cacheKey); - } - return GitProvider.EmptyPromise as Promise; - } - - return Git.blame(GitProvider.BlameFormat, fileName, sha, repoPath) - .then(data => new GitBlameParserEnricher(GitProvider.BlameFormat).enrich(data, fileName)) - .catch(ex => { - // Trap and cache expected blame errors - if (useCaching) { - const msg = ex && ex.toString(); - Logger.log(`Replace blame cache with empty promise for '${cacheKey}'`); - - entry.blame = { - //date: new Date(), - item: GitProvider.EmptyPromise, - errorMessage: msg - } as ICachedBlame; - - this._onDidBlameFailEmitter.fire(cacheKey); - this._gitCache.set(cacheKey, entry); - return GitProvider.EmptyPromise as Promise; - } - return undefined; - }); - }); - - if (useCaching) { - Logger.log(`Add blame cache for '${cacheKey}'`); - - entry.blame = { - //date: new Date(), - item: promise - } as ICachedBlame; - - this._gitCache.set(cacheKey, entry); - } - - return promise; - } - - async getBlameForLine(fileName: string, line: number, sha?: string, repoPath?: string): Promise { - Logger.log(`getBlameForLine('${fileName}', ${line}, ${sha}, '${repoPath}')`); - - if (this.UseGitCaching && !sha) { - const blame = await this.getBlameForFile(fileName); - const blameLine = blame && blame.lines[line]; - if (!blameLine) return undefined; - - const commit = blame.commits.get(blameLine.sha); - return { - author: Object.assign({}, blame.authors.get(commit.author), { lineCount: commit.lines.length }), - commit: commit, - line: blameLine - } as IGitBlameLine; - } - - fileName = Git.normalizePath(fileName); - - try { - const data = await Git.blameLines(GitProvider.BlameFormat, fileName, line + 1, line + 1, sha, repoPath); - const blame = new GitBlameParserEnricher(GitProvider.BlameFormat).enrich(data, fileName); - if (!blame) return undefined; - - const commit = Iterables.first(blame.commits.values()); - if (repoPath) { - commit.repoPath = repoPath; - } - return { - author: Iterables.first(blame.authors.values()), - commit: commit, - line: blame.lines[line] - } as IGitBlameLine; - } - catch (ex) { - return undefined; - } - } - - async getBlameForRange(fileName: string, range: Range, sha?: string, repoPath?: string): Promise { - Logger.log(`getBlameForRange('${fileName}', [${range.start.line}, ${range.end.line}], ${sha}, '${repoPath}')`); - const blame = await this.getBlameForFile(fileName, sha, repoPath); - if (!blame) return undefined; - - return this.getBlameForRangeSync(blame, fileName, range, sha, repoPath); - } - - getBlameForRangeSync(blame: IGitBlame, fileName: string, range: Range, sha?: string, repoPath?: string): IGitBlameLines | undefined { - Logger.log(`getBlameForRangeSync('${fileName}', [${range.start.line}, ${range.end.line}], ${sha}, '${repoPath}')`); - - if (!blame.lines.length) return Object.assign({ allLines: blame.lines }, blame); - - if (range.start.line === 0 && range.end.line === blame.lines.length - 1) { - return Object.assign({ allLines: blame.lines }, blame); - } - - const lines = blame.lines.slice(range.start.line, range.end.line + 1); - const shas: Set = new Set(); - lines.forEach(l => shas.add(l.sha)); - - const authors: Map = new Map(); - const commits: Map = new Map(); - blame.commits.forEach(c => { - if (!shas.has(c.sha)) return; - - const commit: GitCommit = new GitCommit(c.repoPath, c.sha, c.fileName, c.author, c.date, c.message, - c.lines.filter(l => l.line >= range.start.line && l.line <= range.end.line), c.originalFileName, c.previousSha, c.previousFileName); - commits.set(c.sha, commit); - - let author = authors.get(commit.author); - if (!author) { - author = { - name: commit.author, - lineCount: 0 - }; - authors.set(author.name, author); - } - - author.lineCount += commit.lines.length; - }); - - const sortedAuthors: Map = new Map(); - Array.from(authors.values()) - .sort((a, b) => b.lineCount - a.lineCount) - .forEach(a => sortedAuthors.set(a.name, a)); - - 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 { - Logger.log(`getBlameLocations('${fileName}', [${range.start.line}, ${range.end.line}], ${sha}, '${repoPath}')`); - - const blame = await this.getBlameForRange(fileName, range, sha, repoPath); - if (!blame) return undefined; - - const commitCount = blame.commits.size; - - const locations: Array = []; - Iterables.forEach(blame.commits.values(), (c, i) => { - if (c.isUncommitted) return; - - const decoration = `\u2937 ${c.author}, ${moment(c.date).format('MMMM Do, YYYY h:MMa')}`; - const uri = GitProvider.toReferenceGitContentUri(c, i + 1, commitCount, c.originalFileName, decoration); - locations.push(new Location(uri, new Position(0, 0))); - if (c.sha === selectedSha) { - locations.push(new Location(uri, new Position(line + 1, 0))); - } - }); - - return locations; - } - - async getLogForRepo(repoPath: string, sha?: string, maxCount?: number, reverse: boolean = false): Promise { - Logger.log(`getLogForRepo('${repoPath}', ${maxCount})`); - - if (maxCount == null) { - maxCount = this.config.advanced.maxQuickHistory || 0; - } - - try { - const data = await Git.logRepo(repoPath, sha, maxCount, reverse); - return new GitLogParserEnricher().enrich(data, 'repo', repoPath, maxCount, true, reverse); - } - catch (ex) { - return undefined; - } - } - - getLogForFile(fileName: string, sha?: string, repoPath?: string, range?: Range, maxCount?: number, reverse: boolean = false): Promise { - Logger.log(`getLogForFile('${fileName}', ${sha}, '${repoPath}', ${range && `[${range.start.line}, ${range.end.line}]`}, ${maxCount}, ${reverse})`); - fileName = Git.normalizePath(fileName); - - const useCaching = this.UseGitCaching && !sha && !range && !maxCount; - - let cacheKey: string; - let entry: GitCacheEntry; - if (useCaching) { - cacheKey = this.getCacheEntryKey(fileName); - entry = this._gitCache.get(cacheKey); - - if (entry !== undefined && entry.log !== undefined) return entry.log.item; - if (entry === undefined) { - entry = new GitCacheEntry(); - } - } - - const promise = this._gitignore.then(ignore => { - if (ignore && !ignore.filter([fileName]).length) { - Logger.log(`Skipping log; '${fileName}' is gitignored`); - return GitProvider.EmptyPromise as Promise; - } - - return (range - ? Git.logRange(fileName, range.start.line + 1, range.end.line + 1, sha, repoPath, maxCount) - : Git.log(fileName, sha, repoPath, maxCount, reverse)) - .then(data => new GitLogParserEnricher().enrich(data, 'file', repoPath || fileName, maxCount, !!repoPath, reverse)) - .catch(ex => { - // Trap and cache expected log errors - if (useCaching) { - const msg = ex && ex.toString(); - Logger.log(`Replace log cache with empty promise for '${cacheKey}'`); - - entry.log = { - //date: new Date(), - item: GitProvider.EmptyPromise, - errorMessage: msg - } as ICachedLog; - - this._gitCache.set(cacheKey, entry); - return GitProvider.EmptyPromise as Promise; - } - return undefined; - }); - }); - - if (useCaching) { - Logger.log(`Add log cache for '${cacheKey}'`); - - entry.log = { - //date: new Date(), - item: promise - } as ICachedLog; - - this._gitCache.set(cacheKey, entry); - } - - return promise; - } - - async getLogLocations(fileName: string, sha?: string, repoPath?: string, selectedSha?: string, line?: number): Promise { - Logger.log(`getLogLocations('${fileName}', ${sha}, '${repoPath}', ${selectedSha}, ${line})`); - - const log = await this.getLogForFile(fileName, sha, repoPath); - if (!log) return undefined; - - const commitCount = log.commits.size; - - const locations: Array = []; - Iterables.forEach(log.commits.values(), (c, i) => { - if (c.isUncommitted) return; - - const decoration = `\u2937 ${c.author}, ${moment(c.date).format('MMMM Do, YYYY h:MMa')}`; - const uri = GitProvider.toReferenceGitContentUri(c, i + 1, commitCount, c.originalFileName, decoration); - locations.push(new Location(uri, new Position(0, 0))); - if (c.sha === selectedSha) { - locations.push(new Location(uri, new Position(line + 1, 0))); - } - }); - - return locations; - } - - async getStatusForFile(fileName: string, repoPath: string): Promise { - Logger.log(`getStatusForFile('${fileName}', '${repoPath}')`); - const status = await Git.statusFile(fileName, repoPath); - return status && status.trim().length && new GitFileStatusItem(repoPath, status); - } - - async getStatusesForRepo(repoPath: string): Promise { - Logger.log(`getStatusForRepo('${repoPath}')`); - const statuses = (await Git.statusRepo(repoPath)).split('\n').filter(_ => !!_); - return statuses.map(_ => new GitFileStatusItem(repoPath, _)); - } - - async isFileUncommitted(fileName: string, repoPath: string): Promise { - Logger.log(`isFileUncommitted('${fileName}', '${repoPath}')`); - const status = await this.getStatusForFile(fileName, repoPath); - return !!status; - } - - async getVersionedFile(fileName: string, repoPath: string, sha: string) { - Logger.log(`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) { - Logger.log(`getVersionedFileText('${fileName}', '${repoPath}', ${sha})`); - return Git.getVersionedFileText(fileName, repoPath, sha); - } - - isEditorBlameable(editor: TextEditor): boolean { - return (editor.viewColumn !== undefined || - editor.document.uri.scheme === DocumentSchemes.File || - editor.document.uri.scheme === DocumentSchemes.Git || - this.hasGitUriForFile(editor)); - } - - openDirectoryDiff(repoPath: string, sha1: string, sha2?: string) { - Logger.log(`openDirectoryDiff('${repoPath}', ${sha1}, ${sha2})`); - return Git.diffDir(repoPath, sha1, sha2); - } - - toggleCodeLens(editor: TextEditor) { - if (this.config.codeLens.visibility !== CodeLensVisibility.OnDemand || - (!this.config.codeLens.recentChange.enabled && !this.config.codeLens.authors.enabled)) return; - - Logger.log(`toggleCodeLens(${editor})`); - - if (this._codeLensProviderDisposable) { - this._codeLensProviderDisposable.dispose(); - this._codeLensProviderDisposable = undefined; - return; - } - - this._codeLensProviderDisposable = languages.registerCodeLensProvider(GitCodeLensProvider.selector, new GitCodeLensProvider(this.context, this)); - } - - static isUncommitted(sha: string) { - return Git.isUncommitted(sha); - } - - static fromGitContentUri(uri: Uri): IGitUriData { - if (uri.scheme !== DocumentSchemes.GitLensGit) throw new Error(`fromGitUri(uri=${uri}) invalid scheme`); - return GitProvider._fromGitContentUri(uri); - } - - private static _fromGitContentUri(uri: Uri): T { - return JSON.parse(uri.query) as T; - } - - static toGitContentUri(sha: string, fileName: string, repoPath: string, originalFileName: string): Uri; - static toGitContentUri(commit: GitCommit): Uri; - static toGitContentUri(shaOrcommit: string | GitCommit, fileName?: string, repoPath?: string, originalFileName?: string): Uri { - let data: IGitUriData; - if (typeof shaOrcommit === 'string') { - data = GitProvider._toGitUriData({ - sha: shaOrcommit, - fileName: fileName, - repoPath: repoPath, - originalFileName: originalFileName - }); - } - else { - data = GitProvider._toGitUriData(shaOrcommit, undefined, shaOrcommit.originalFileName); - fileName = shaOrcommit.fileName; - } - - const extension = path.extname(fileName); - return Uri.parse(`${DocumentSchemes.GitLensGit}:${path.basename(fileName, extension)}:${data.sha}${extension}?${JSON.stringify(data)}`); - } - - static toReferenceGitContentUri(commit: GitCommit, index: number, commitCount: number, originalFileName?: string, decoration?: string): Uri { - return GitProvider._toReferenceGitContentUri(commit, DocumentSchemes.GitLensGit, commitCount, GitProvider._toGitUriData(commit, index, originalFileName, decoration)); - } - - private static _toReferenceGitContentUri(commit: GitCommit, scheme: DocumentSchemes, commitCount: number, data: IGitUriData) { - const pad = (n: number) => ('0000000' + n).slice(-('' + commitCount).length); - const ext = path.extname(data.fileName); - const uriPath = `${path.relative(commit.repoPath, data.fileName.slice(0, -ext.length))}/${commit.sha}${ext}`; - - let message = commit.message; - if (message.length > 50) { - message = message.substring(0, 49) + '\u2026'; - } - - // NOTE: Need to specify an index here, since I can't control the sort order -- just alphabetic or by file location - return Uri.parse(`${scheme}:${pad(data.index)} \u2022 ${encodeURIComponent(message)} \u2022 ${moment(commit.date).format('MMM D, YYYY hh:MMa')} \u2022 ${encodeURIComponent(uriPath)}?${JSON.stringify(data)}`); - } - - private static _toGitUriData(commit: IGitUriData, index?: number, originalFileName?: string, decoration?: string): T { - const fileName = Git.normalizePath(path.resolve(commit.repoPath, commit.fileName)); - const data = { repoPath: commit.repoPath, fileName: fileName, sha: commit.sha, index: index } as T; - if (originalFileName) { - data.originalFileName = Git.normalizePath(path.resolve(commit.repoPath, originalFileName)); - } - if (decoration) { - data.decoration = decoration; - } - return data; - } -} \ No newline at end of file diff --git a/src/gitRevisionCodeLensProvider.ts b/src/gitRevisionCodeLensProvider.ts index 7671f8c..b92cdc8 100644 --- a/src/gitRevisionCodeLensProvider.ts +++ b/src/gitRevisionCodeLensProvider.ts @@ -3,18 +3,18 @@ import { Iterables } from './system'; import { CancellationToken, CodeLens, CodeLensProvider, DocumentSelector, ExtensionContext, Range, TextDocument, Uri } from 'vscode'; import { Commands } from './commands'; import { DocumentSchemes } from './constants'; -import { GitCommit, GitProvider, GitUri } from './gitProvider'; +import { GitCommit, GitService, GitUri } from './gitService'; export class GitDiffWithWorkingCodeLens extends CodeLens { - constructor(git: GitProvider, public fileName: string, public commit: GitCommit, range: Range) { + constructor(git: GitService, public fileName: string, public commit: GitCommit, range: Range) { super(range); } } export class GitDiffWithPreviousCodeLens extends CodeLens { - constructor(git: GitProvider, public fileName: string, public commit: GitCommit, range: Range) { + constructor(git: GitService, public fileName: string, public commit: GitCommit, range: Range) { super(range); } } @@ -23,10 +23,10 @@ export class GitRevisionCodeLensProvider implements CodeLensProvider { static selector: DocumentSelector = { scheme: DocumentSchemes.GitLensGit }; - constructor(context: ExtensionContext, private git: GitProvider) { } + constructor(context: ExtensionContext, private git: GitService) { } async provideCodeLenses(document: TextDocument, token: CancellationToken): Promise { - const data = GitProvider.fromGitContentUri(document.uri); + const data = GitService.fromGitContentUri(document.uri); const gitUri = new GitUri(Uri.file(data.fileName), data); const lenses: CodeLens[] = []; diff --git a/src/gitService.ts b/src/gitService.ts new file mode 100644 index 0000000..cd2fd02 --- /dev/null +++ b/src/gitService.ts @@ -0,0 +1,718 @@ +'use strict'; +import { Iterables, Objects } from './system'; +import { Disposable, Event, EventEmitter, ExtensionContext, FileSystemWatcher, languages, Location, Position, Range, TextDocument, TextEditor, Uri, workspace } from 'vscode'; +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 { IGitUriData, GitUri } from './git/gitUri'; +import GitCodeLensProvider from './gitCodeLensProvider'; +import { Logger } from './logger'; +import * as fs from 'fs'; +import * as ignore from 'ignore'; +import * as moment from 'moment'; +import * as path from 'path'; + +export { getGitStatusIcon } from './git/gitEnrichment'; +export { Git, GitUri }; +export * from './git/git'; + +class UriCacheEntry { + + constructor(public uri: GitUri) { } +} + +class GitCacheEntry { + + blame?: ICachedBlame; + log?: ICachedLog; + + get hasErrors() { + return !!((this.blame && this.blame.errorMessage) || (this.log && this.log.errorMessage)); + } +} + +interface ICachedItem { + //date: Date; + item: Promise; + errorMessage?: string; +} + +interface ICachedBlame extends ICachedItem { } +interface ICachedLog extends ICachedItem { } + +enum RemoveCacheReason { + DocumentClosed, + DocumentSaved +} + +export class GitService extends Disposable { + + private _onDidChangeGitCacheEmitter = new EventEmitter(); + get onDidChangeGitCache(): Event { + return this._onDidChangeGitCacheEmitter.event; + } + + private _onDidBlameFailEmitter = new EventEmitter(); + get onDidBlameFail(): Event { + return this._onDidBlameFailEmitter.event; + } + + public repoPath: string; + + private _gitCache: Map | undefined; + private _cacheDisposable: Disposable | undefined; + private _uriCache: Map | undefined; + + config: IConfig; + private _codeLensProvider: GitCodeLensProvider | undefined; + private _codeLensProviderDisposable: Disposable | undefined; + private _disposable: Disposable; + private _fsWatcher: FileSystemWatcher; + private _gitignore: Promise; + + static EmptyPromise: Promise = Promise.resolve(undefined); + static BlameFormat = GitBlameFormat.incremental; + + constructor(private context: ExtensionContext) { + super(() => this.dispose()); + + this.repoPath = context.workspaceState.get(WorkspaceState.RepoPath) as string; + + this._onConfigurationChanged(); + + const subscriptions: Disposable[] = []; + + subscriptions.push(workspace.onDidChangeConfiguration(this._onConfigurationChanged, this)); + + this._disposable = Disposable.from(...subscriptions); + } + + dispose() { + this._disposable && this._disposable.dispose(); + + this._codeLensProviderDisposable && this._codeLensProviderDisposable.dispose(); + this._codeLensProviderDisposable = undefined; + this._codeLensProvider = undefined; + + this._cacheDisposable && this._cacheDisposable.dispose(); + this._cacheDisposable = undefined; + + this._fsWatcher && this._fsWatcher.dispose(); + this._fsWatcher = undefined; + + this._gitCache && this._gitCache.clear(); + this._gitCache = undefined; + this._uriCache && this._uriCache.clear(); + this._uriCache = undefined; + } + + public getBlameability(fileName: string): boolean { + if (!this.UseGitCaching) return true; + + const cacheKey = this.getCacheEntryKey(Git.normalizePath(fileName)); + const entry = this._gitCache.get(cacheKey); + return !(entry && entry.hasErrors); + } + + public get UseUriCaching() { + return !!this._uriCache; + } + + public get UseGitCaching() { + return !!this._gitCache; + } + + private _onConfigurationChanged() { + 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); + + if (codeLensChanged || advancedChanged) { + Logger.log('CodeLens config changed; resetting CodeLens provider'); + if (config.codeLens.visibility === CodeLensVisibility.Auto && (config.codeLens.recentChange.enabled || config.codeLens.authors.enabled)) { + if (this._codeLensProvider) { + this._codeLensProvider.reset(); + } + else { + this._codeLensProvider = new GitCodeLensProvider(this.context, this); + this._codeLensProviderDisposable = languages.registerCodeLensProvider(GitCodeLensProvider.selector, this._codeLensProvider); + } + } + else { + this._codeLensProviderDisposable && this._codeLensProviderDisposable.dispose(); + this._codeLensProviderDisposable = undefined; + this._codeLensProvider = undefined; + } + + setCommandContext(CommandContext.CanToggleCodeLens, config.codeLens.visibility === CodeLensVisibility.OnDemand && (config.codeLens.recentChange.enabled || config.codeLens.authors.enabled)); + } + + if (advancedChanged) { + if (config.advanced.caching.enabled) { + this._gitCache = new Map(); + this._uriCache = new Map(); + + this._cacheDisposable && this._cacheDisposable.dispose(); + + this._fsWatcher = this._fsWatcher || workspace.createFileSystemWatcher('**/.git/index', true, false, true); + + const disposables: Disposable[] = []; + + disposables.push(workspace.onDidCloseTextDocument(d => this._removeCachedEntry(d, RemoveCacheReason.DocumentClosed))); + disposables.push(workspace.onDidSaveTextDocument(d => this._removeCachedEntry(d, RemoveCacheReason.DocumentSaved))); + disposables.push(this._fsWatcher.onDidChange(this._onGitChanged, this)); + + this._cacheDisposable = Disposable.from(...disposables); + } + else { + this._cacheDisposable && this._cacheDisposable.dispose(); + this._cacheDisposable = undefined; + + this._fsWatcher && this._fsWatcher.dispose(); + this._fsWatcher = undefined; + + this._gitCache && this._gitCache.clear(); + this._gitCache = undefined; + + this._uriCache && this._uriCache.clear(); + this._uriCache = undefined; + } + + this._gitignore = new Promise((resolve, reject) => { + if (!config.advanced.gitignore.enabled) { + resolve(undefined); + return; + } + + const gitignorePath = path.join(this.repoPath, '.gitignore'); + fs.exists(gitignorePath, e => { + if (e) { + fs.readFile(gitignorePath, 'utf8', (err, data) => { + if (!err) { + resolve(ignore().add(data)); + return; + } + resolve(undefined); + }); + return; + } + resolve(undefined); + }); + }); + } + + this.config = config; + } + + getCacheEntryKey(fileName: string) { + return fileName.toLowerCase(); + } + + private _onGitChanged() { + this._gitCache && this._gitCache.clear(); + + this._onDidChangeGitCacheEmitter.fire(); + this._codeLensProvider && this._codeLensProvider.reset(); + } + + private _removeCachedEntry(document: TextDocument, reason: RemoveCacheReason) { + 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.DocumentSaved) { + // Don't remove broken blame on save (since otherwise we'll have to run the broken blame again) + const entry = this._gitCache.get(cacheKey); + if (entry && entry.hasErrors) return; + } + + if (this._gitCache.delete(cacheKey)) { + Logger.log(`Clear cache entry for '${cacheKey}', reason=${RemoveCacheReason[reason]}`); + + if (reason === RemoveCacheReason.DocumentSaved) { + this._onDidChangeGitCacheEmitter.fire(); + + // Refresh the codelenses with the updated blame + this._codeLensProvider && this._codeLensProvider.reset(); + } + } + } + + 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); + } + + async findMostRecentCommitForFile(fileName: string, sha?: string): Promise { + const exists = await new Promise((resolve, reject) => fs.exists(fileName, e => resolve(e))); + if (exists) return null; + + return undefined; + + // TODO: Get this to work -- for some reason a reverse log won't return the renamed file + // Not sure how else to figure this out + + // let log: IGitLog; + // let commit: GitCommit; + // while (true) { + // // Go backward from the current commit to head to find the latest filename + // log = await this.getLogForFile(fileName, sha, undefined, undefined, undefined, true); + // if (!log) break; + + // commit = Iterables.first(log.commits.values()); + // sha = commit.sha; + // fileName = commit.fileName; + // } + + // return commit; + } + + 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 { + return Git.repoPath(cwd); + } + + async getRepoPathFromUri(uri?: Uri, fallbackRepoPath?: string): Promise { + if (!(uri instanceof Uri)) return fallbackRepoPath; + + const gitUri = await GitUri.fromUri(uri, this); + if (gitUri.repoPath) return gitUri.repoPath; + + return (await this.getRepoPathFromFile(gitUri.fsPath)) || fallbackRepoPath; + } + + async getRepoPathFromFile(fileName: string): Promise { + const log = await this.getLogForFile(fileName, undefined, undefined, undefined, 1); + return log && log.repoPath; + } + + getBlameForFile(fileName: string, sha?: string, repoPath?: string): Promise { + Logger.log(`getBlameForFile('${fileName}', ${sha}, '${repoPath}')`); + fileName = Git.normalizePath(fileName); + + const useCaching = this.UseGitCaching && !sha; + + let cacheKey: string | undefined; + let entry: GitCacheEntry | undefined; + if (useCaching) { + cacheKey = this.getCacheEntryKey(fileName); + entry = this._gitCache.get(cacheKey); + + if (entry !== undefined && entry.blame !== undefined) return entry.blame.item; + if (entry === undefined) { + entry = new GitCacheEntry(); + } + } + + const promise = this._gitignore.then(ignore => { + if (ignore && !ignore.filter([fileName]).length) { + Logger.log(`Skipping blame; '${fileName}' is gitignored`); + if (cacheKey) { + this._onDidBlameFailEmitter.fire(cacheKey); + } + return GitService.EmptyPromise as Promise; + } + + return Git.blame(GitService.BlameFormat, fileName, sha, repoPath) + .then(data => new GitBlameParserEnricher(GitService.BlameFormat).enrich(data, fileName)) + .catch(ex => { + // Trap and cache expected blame errors + if (useCaching) { + const msg = ex && ex.toString(); + Logger.log(`Replace blame cache with empty promise for '${cacheKey}'`); + + entry.blame = { + //date: new Date(), + item: GitService.EmptyPromise, + errorMessage: msg + } as ICachedBlame; + + this._onDidBlameFailEmitter.fire(cacheKey); + this._gitCache.set(cacheKey, entry); + return GitService.EmptyPromise as Promise; + } + return undefined; + }); + }); + + if (useCaching) { + Logger.log(`Add blame cache for '${cacheKey}'`); + + entry.blame = { + //date: new Date(), + item: promise + } as ICachedBlame; + + this._gitCache.set(cacheKey, entry); + } + + return promise; + } + + async getBlameForLine(fileName: string, line: number, sha?: string, repoPath?: string): Promise { + Logger.log(`getBlameForLine('${fileName}', ${line}, ${sha}, '${repoPath}')`); + + if (this.UseGitCaching && !sha) { + const blame = await this.getBlameForFile(fileName); + const blameLine = blame && blame.lines[line]; + if (!blameLine) return undefined; + + const commit = blame.commits.get(blameLine.sha); + return { + author: Object.assign({}, blame.authors.get(commit.author), { lineCount: commit.lines.length }), + commit: commit, + line: blameLine + } as IGitBlameLine; + } + + fileName = Git.normalizePath(fileName); + + try { + const data = await Git.blameLines(GitService.BlameFormat, fileName, line + 1, line + 1, sha, repoPath); + const blame = new GitBlameParserEnricher(GitService.BlameFormat).enrich(data, fileName); + if (!blame) return undefined; + + const commit = Iterables.first(blame.commits.values()); + if (repoPath) { + commit.repoPath = repoPath; + } + return { + author: Iterables.first(blame.authors.values()), + commit: commit, + line: blame.lines[line] + } as IGitBlameLine; + } + catch (ex) { + return undefined; + } + } + + async getBlameForRange(fileName: string, range: Range, sha?: string, repoPath?: string): Promise { + Logger.log(`getBlameForRange('${fileName}', [${range.start.line}, ${range.end.line}], ${sha}, '${repoPath}')`); + const blame = await this.getBlameForFile(fileName, sha, repoPath); + if (!blame) return undefined; + + return this.getBlameForRangeSync(blame, fileName, range, sha, repoPath); + } + + getBlameForRangeSync(blame: IGitBlame, fileName: string, range: Range, sha?: string, repoPath?: string): IGitBlameLines | undefined { + Logger.log(`getBlameForRangeSync('${fileName}', [${range.start.line}, ${range.end.line}], ${sha}, '${repoPath}')`); + + if (!blame.lines.length) return Object.assign({ allLines: blame.lines }, blame); + + if (range.start.line === 0 && range.end.line === blame.lines.length - 1) { + return Object.assign({ allLines: blame.lines }, blame); + } + + const lines = blame.lines.slice(range.start.line, range.end.line + 1); + const shas: Set = new Set(); + lines.forEach(l => shas.add(l.sha)); + + const authors: Map = new Map(); + const commits: Map = new Map(); + blame.commits.forEach(c => { + if (!shas.has(c.sha)) return; + + const commit: GitCommit = new GitCommit(c.repoPath, c.sha, c.fileName, c.author, c.date, c.message, + c.lines.filter(l => l.line >= range.start.line && l.line <= range.end.line), c.originalFileName, c.previousSha, c.previousFileName); + commits.set(c.sha, commit); + + let author = authors.get(commit.author); + if (!author) { + author = { + name: commit.author, + lineCount: 0 + }; + authors.set(author.name, author); + } + + author.lineCount += commit.lines.length; + }); + + const sortedAuthors: Map = new Map(); + Array.from(authors.values()) + .sort((a, b) => b.lineCount - a.lineCount) + .forEach(a => sortedAuthors.set(a.name, a)); + + 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 { + Logger.log(`getBlameLocations('${fileName}', [${range.start.line}, ${range.end.line}], ${sha}, '${repoPath}')`); + + const blame = await this.getBlameForRange(fileName, range, sha, repoPath); + if (!blame) return undefined; + + const commitCount = blame.commits.size; + + const locations: Array = []; + Iterables.forEach(blame.commits.values(), (c, i) => { + if (c.isUncommitted) return; + + const decoration = `\u2937 ${c.author}, ${moment(c.date).format('MMMM Do, YYYY h:MMa')}`; + const uri = GitService.toReferenceGitContentUri(c, i + 1, commitCount, c.originalFileName, decoration); + locations.push(new Location(uri, new Position(0, 0))); + if (c.sha === selectedSha) { + locations.push(new Location(uri, new Position(line + 1, 0))); + } + }); + + return locations; + } + + async getLogForRepo(repoPath: string, sha?: string, maxCount?: number, reverse: boolean = false): Promise { + Logger.log(`getLogForRepo('${repoPath}', ${maxCount})`); + + if (maxCount == null) { + maxCount = this.config.advanced.maxQuickHistory || 0; + } + + try { + const data = await Git.logRepo(repoPath, sha, maxCount, reverse); + return new GitLogParserEnricher().enrich(data, 'repo', repoPath, maxCount, true, reverse); + } + catch (ex) { + return undefined; + } + } + + getLogForFile(fileName: string, sha?: string, repoPath?: string, range?: Range, maxCount?: number, reverse: boolean = false): Promise { + Logger.log(`getLogForFile('${fileName}', ${sha}, '${repoPath}', ${range && `[${range.start.line}, ${range.end.line}]`}, ${maxCount}, ${reverse})`); + fileName = Git.normalizePath(fileName); + + const useCaching = this.UseGitCaching && !sha && !range && !maxCount; + + let cacheKey: string; + let entry: GitCacheEntry; + if (useCaching) { + cacheKey = this.getCacheEntryKey(fileName); + entry = this._gitCache.get(cacheKey); + + if (entry !== undefined && entry.log !== undefined) return entry.log.item; + if (entry === undefined) { + entry = new GitCacheEntry(); + } + } + + const promise = this._gitignore.then(ignore => { + if (ignore && !ignore.filter([fileName]).length) { + Logger.log(`Skipping log; '${fileName}' is gitignored`); + return GitService.EmptyPromise as Promise; + } + + return (range + ? Git.logRange(fileName, range.start.line + 1, range.end.line + 1, sha, repoPath, maxCount) + : Git.log(fileName, sha, repoPath, maxCount, reverse)) + .then(data => new GitLogParserEnricher().enrich(data, 'file', repoPath || fileName, maxCount, !!repoPath, reverse)) + .catch(ex => { + // Trap and cache expected log errors + if (useCaching) { + const msg = ex && ex.toString(); + Logger.log(`Replace log cache with empty promise for '${cacheKey}'`); + + entry.log = { + //date: new Date(), + item: GitService.EmptyPromise, + errorMessage: msg + } as ICachedLog; + + this._gitCache.set(cacheKey, entry); + return GitService.EmptyPromise as Promise; + } + return undefined; + }); + }); + + if (useCaching) { + Logger.log(`Add log cache for '${cacheKey}'`); + + entry.log = { + //date: new Date(), + item: promise + } as ICachedLog; + + this._gitCache.set(cacheKey, entry); + } + + return promise; + } + + async getLogLocations(fileName: string, sha?: string, repoPath?: string, selectedSha?: string, line?: number): Promise { + Logger.log(`getLogLocations('${fileName}', ${sha}, '${repoPath}', ${selectedSha}, ${line})`); + + const log = await this.getLogForFile(fileName, sha, repoPath); + if (!log) return undefined; + + const commitCount = log.commits.size; + + const locations: Array = []; + Iterables.forEach(log.commits.values(), (c, i) => { + if (c.isUncommitted) return; + + const decoration = `\u2937 ${c.author}, ${moment(c.date).format('MMMM Do, YYYY h:MMa')}`; + const uri = GitService.toReferenceGitContentUri(c, i + 1, commitCount, c.originalFileName, decoration); + locations.push(new Location(uri, new Position(0, 0))); + if (c.sha === selectedSha) { + locations.push(new Location(uri, new Position(line + 1, 0))); + } + }); + + return locations; + } + + async getStatusForFile(fileName: string, repoPath: string): Promise { + Logger.log(`getStatusForFile('${fileName}', '${repoPath}')`); + const status = await Git.statusFile(fileName, repoPath); + return status && status.trim().length && new GitFileStatusItem(repoPath, status); + } + + async getStatusesForRepo(repoPath: string): Promise { + Logger.log(`getStatusForRepo('${repoPath}')`); + const statuses = (await Git.statusRepo(repoPath)).split('\n').filter(_ => !!_); + return statuses.map(_ => new GitFileStatusItem(repoPath, _)); + } + + async isFileUncommitted(fileName: string, repoPath: string): Promise { + Logger.log(`isFileUncommitted('${fileName}', '${repoPath}')`); + const status = await this.getStatusForFile(fileName, repoPath); + return !!status; + } + + async getVersionedFile(fileName: string, repoPath: string, sha: string) { + Logger.log(`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) { + Logger.log(`getVersionedFileText('${fileName}', '${repoPath}', ${sha})`); + return Git.getVersionedFileText(fileName, repoPath, sha); + } + + isEditorBlameable(editor: TextEditor): boolean { + return (editor.viewColumn !== undefined || + editor.document.uri.scheme === DocumentSchemes.File || + editor.document.uri.scheme === DocumentSchemes.Git || + this.hasGitUriForFile(editor)); + } + + openDirectoryDiff(repoPath: string, sha1: string, sha2?: string) { + Logger.log(`openDirectoryDiff('${repoPath}', ${sha1}, ${sha2})`); + return Git.diffDir(repoPath, sha1, sha2); + } + + toggleCodeLens(editor: TextEditor) { + if (this.config.codeLens.visibility !== CodeLensVisibility.OnDemand || + (!this.config.codeLens.recentChange.enabled && !this.config.codeLens.authors.enabled)) return; + + Logger.log(`toggleCodeLens(${editor})`); + + if (this._codeLensProviderDisposable) { + this._codeLensProviderDisposable.dispose(); + this._codeLensProviderDisposable = undefined; + return; + } + + this._codeLensProviderDisposable = languages.registerCodeLensProvider(GitCodeLensProvider.selector, new GitCodeLensProvider(this.context, this)); + } + + static isUncommitted(sha: string) { + return Git.isUncommitted(sha); + } + + static fromGitContentUri(uri: Uri): IGitUriData { + if (uri.scheme !== DocumentSchemes.GitLensGit) throw new Error(`fromGitUri(uri=${uri}) invalid scheme`); + return GitService._fromGitContentUri(uri); + } + + private static _fromGitContentUri(uri: Uri): T { + return JSON.parse(uri.query) as T; + } + + static toGitContentUri(sha: string, fileName: string, repoPath: string, originalFileName: string): Uri; + static toGitContentUri(commit: GitCommit): Uri; + static toGitContentUri(shaOrcommit: string | GitCommit, fileName?: string, repoPath?: string, originalFileName?: string): Uri { + let data: IGitUriData; + if (typeof shaOrcommit === 'string') { + data = GitService._toGitUriData({ + sha: shaOrcommit, + fileName: fileName, + repoPath: repoPath, + originalFileName: originalFileName + }); + } + else { + data = GitService._toGitUriData(shaOrcommit, undefined, shaOrcommit.originalFileName); + fileName = shaOrcommit.fileName; + } + + const extension = path.extname(fileName); + return Uri.parse(`${DocumentSchemes.GitLensGit}:${path.basename(fileName, extension)}:${data.sha}${extension}?${JSON.stringify(data)}`); + } + + static toReferenceGitContentUri(commit: GitCommit, index: number, commitCount: number, originalFileName?: string, decoration?: string): Uri { + return GitService._toReferenceGitContentUri(commit, DocumentSchemes.GitLensGit, commitCount, GitService._toGitUriData(commit, index, originalFileName, decoration)); + } + + private static _toReferenceGitContentUri(commit: GitCommit, scheme: DocumentSchemes, commitCount: number, data: IGitUriData) { + const pad = (n: number) => ('0000000' + n).slice(-('' + commitCount).length); + const ext = path.extname(data.fileName); + const uriPath = `${path.relative(commit.repoPath, data.fileName.slice(0, -ext.length))}/${commit.sha}${ext}`; + + let message = commit.message; + if (message.length > 50) { + message = message.substring(0, 49) + '\u2026'; + } + + // NOTE: Need to specify an index here, since I can't control the sort order -- just alphabetic or by file location + return Uri.parse(`${scheme}:${pad(data.index)} \u2022 ${encodeURIComponent(message)} \u2022 ${moment(commit.date).format('MMM D, YYYY hh:MMa')} \u2022 ${encodeURIComponent(uriPath)}?${JSON.stringify(data)}`); + } + + private static _toGitUriData(commit: IGitUriData, index?: number, originalFileName?: string, decoration?: string): T { + const fileName = Git.normalizePath(path.resolve(commit.repoPath, commit.fileName)); + const data = { repoPath: commit.repoPath, fileName: fileName, sha: commit.sha, index: index } as T; + if (originalFileName) { + data.originalFileName = Git.normalizePath(path.resolve(commit.repoPath, originalFileName)); + } + if (decoration) { + data.decoration = decoration; + } + return data; + } +} \ No newline at end of file diff --git a/src/quickPicks/commitDetails.ts b/src/quickPicks/commitDetails.ts index 852403e..019a511 100644 --- a/src/quickPicks/commitDetails.ts +++ b/src/quickPicks/commitDetails.ts @@ -2,7 +2,7 @@ import { Iterables } from '../system'; import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode'; import { Commands, Keyboard, KeyNoopCommand } from '../commands'; -import { GitLogCommit, GitProvider, IGitLog } from '../gitProvider'; +import { GitLogCommit, GitService, IGitLog } from '../gitService'; import { CommitWithFileStatusQuickPickItem } from './gitQuickPicks'; import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, KeyCommandQuickPickItem, OpenFilesCommandQuickPickItem } from './quickPicks'; import * as moment from 'moment'; @@ -12,7 +12,7 @@ export class OpenCommitFilesCommandQuickPickItem extends OpenFilesCommandQuickPi constructor(commit: GitLogCommit, item?: QuickPickItem) { const repoPath = commit.repoPath; - const uris = commit.fileStatuses.map(_ => GitProvider.toGitContentUri(commit.sha, _.fileName, repoPath, commit.originalFileName)); + const uris = commit.fileStatuses.map(_ => GitService.toGitContentUri(commit.sha, _.fileName, repoPath, commit.originalFileName)); super(uris, item || { label: `$(file-symlink-file) Open Changed Files`, description: `\u00a0 \u2014 \u00a0\u00a0 in \u00a0$(git-commit) ${commit.shortSha}` @@ -36,7 +36,7 @@ export class OpenCommitWorkingTreeFilesCommandQuickPickItem extends OpenFilesCom export class CommitDetailsQuickPick { - static async show(git: GitProvider, commit: GitLogCommit, uri: Uri, goBackCommand?: CommandQuickPickItem, repoLog?: IGitLog): Promise { + static async show(git: GitService, commit: GitLogCommit, uri: Uri, goBackCommand?: CommandQuickPickItem, repoLog?: IGitLog): Promise { const items: (CommitWithFileStatusQuickPickItem | CommandQuickPickItem)[] = commit.fileStatuses.map(fs => new CommitWithFileStatusQuickPickItem(commit, fs.fileName, fs.status)); let index = 0; diff --git a/src/quickPicks/commitFileDetails.ts b/src/quickPicks/commitFileDetails.ts index 0219cda..54d2d06 100644 --- a/src/quickPicks/commitFileDetails.ts +++ b/src/quickPicks/commitFileDetails.ts @@ -2,7 +2,7 @@ import { Iterables } from '../system'; import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode'; import { Commands, Keyboard, KeyNoopCommand } from '../commands'; -import { GitCommit, GitLogCommit, GitProvider, GitUri, IGitLog } from '../gitProvider'; +import { GitCommit, GitLogCommit, GitService, GitUri, IGitLog } from '../gitService'; import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, KeyCommandQuickPickItem, OpenFileCommandQuickPickItem } from './quickPicks'; import * as moment from 'moment'; import * as path from 'path'; @@ -10,7 +10,7 @@ import * as path from 'path'; export class OpenCommitFileCommandQuickPickItem extends OpenFileCommandQuickPickItem { constructor(commit: GitCommit, item?: QuickPickItem) { - const uri = GitProvider.toGitContentUri(commit); + const uri = GitService.toGitContentUri(commit); super(uri, item || { label: `$(file-symlink-file) Open File`, description: `\u00a0 \u2014 \u00a0\u00a0 ${path.basename(commit.fileName)} in \u00a0$(git-commit) ${commit.shortSha}` @@ -31,7 +31,7 @@ export class OpenCommitWorkingTreeFileCommandQuickPickItem extends OpenFileComma export class CommitFileDetailsQuickPick { - static async show(git: GitProvider, commit: GitLogCommit, workingFileName: string, uri: Uri, goBackCommand?: CommandQuickPickItem, currentCommand?: CommandQuickPickItem, options: { showFileHistory?: boolean } = {}, fileLog?: IGitLog): Promise { + static async show(git: GitService, commit: GitLogCommit, workingFileName: string, uri: Uri, goBackCommand?: CommandQuickPickItem, currentCommand?: CommandQuickPickItem, options: { showFileHistory?: boolean } = {}, fileLog?: IGitLog): Promise { const items: CommandQuickPickItem[] = []; const workingName = (workingFileName && path.basename(workingFileName)) || path.basename(commit.fileName); diff --git a/src/quickPicks/fileHistory.ts b/src/quickPicks/fileHistory.ts index dfc770d..956a3b4 100644 --- a/src/quickPicks/fileHistory.ts +++ b/src/quickPicks/fileHistory.ts @@ -2,7 +2,7 @@ import { Iterables } from '../system'; import { CancellationTokenSource, QuickPickOptions, Uri, window } from 'vscode'; import { Commands, Keyboard, KeyNoopCommand } from '../commands'; -import { GitUri, IGitLog } from '../gitProvider'; +import { GitUri, IGitLog } from '../gitService'; import { CommitQuickPickItem } from './gitQuickPicks'; import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, showQuickPickProgress } from './quickPicks'; import * as path from 'path'; diff --git a/src/quickPicks/gitQuickPicks.ts b/src/quickPicks/gitQuickPicks.ts index 521b843..40936f0 100644 --- a/src/quickPicks/gitQuickPicks.ts +++ b/src/quickPicks/gitQuickPicks.ts @@ -1,6 +1,6 @@ 'use strict'; import { QuickPickItem, Uri } from 'vscode'; -import { getGitStatusIcon, GitCommit, GitFileStatus, GitProvider, GitUri } from '../gitProvider'; +import { getGitStatusIcon, GitCommit, GitFileStatus, GitService, GitUri } from '../gitService'; import { OpenFileCommandQuickPickItem } from './quickPicks'; import * as moment from 'moment'; import * as path from 'path'; @@ -34,7 +34,7 @@ export class CommitWithFileStatusQuickPickItem extends OpenFileCommandQuickPickI directory = undefined; } - super(GitProvider.toGitContentUri(commit.sha, fileName, commit.repoPath, commit.originalFileName), { + super(GitService.toGitContentUri(commit.sha, fileName, commit.repoPath, commit.originalFileName), { label: `\u00a0\u00a0\u00a0\u00a0${icon}\u00a0\u00a0 ${path.basename(fileName)}`, description: directory }); diff --git a/src/quickPicks/repoHistory.ts b/src/quickPicks/repoHistory.ts index d4a936d..78b7a1a 100644 --- a/src/quickPicks/repoHistory.ts +++ b/src/quickPicks/repoHistory.ts @@ -2,7 +2,7 @@ import { Iterables } from '../system'; import { CancellationTokenSource, QuickPickOptions, Uri, window } from 'vscode'; import { Commands, Keyboard, KeyNoopCommand } from '../commands'; -import { GitUri, IGitLog } from '../gitProvider'; +import { GitUri, IGitLog } from '../gitService'; import { CommitQuickPickItem } from './gitQuickPicks'; import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, showQuickPickProgress } from './quickPicks'; diff --git a/src/quickPicks/repoStatus.ts b/src/quickPicks/repoStatus.ts index a7d8820..a8d8d91 100644 --- a/src/quickPicks/repoStatus.ts +++ b/src/quickPicks/repoStatus.ts @@ -2,7 +2,7 @@ import { Iterables } from '../system'; import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode'; import { Commands, Keyboard } from '../commands'; -import { getGitStatusIcon, GitFileStatusItem } from '../gitProvider'; +import { getGitStatusIcon, GitFileStatusItem } from '../gitService'; import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, OpenFileCommandQuickPickItem } from './quickPicks'; import * as path from 'path';