Browse Source

Adds more multi-root support

main
Eric Amodio 7 years ago
parent
commit
1353643fb3
8 changed files with 239 additions and 152 deletions
  1. +46
    -46
      package.json
  2. +3
    -2
      src/constants.ts
  3. +29
    -10
      src/git/gitContextTracker.ts
  4. +1
    -0
      src/git/models/models.ts
  5. +106
    -0
      src/git/models/repository.ts
  6. +42
    -83
      src/gitService.ts
  7. +1
    -1
      src/views/repositoryNode.ts
  8. +11
    -10
      src/views/statusNode.ts

+ 46
- 46
package.json View File

@ -1243,31 +1243,31 @@
}, },
{ {
"command": "gitlens.diffWithBranch", "command": "gitlens.diffWithBranch",
"when": "gitlens:isTracked"
"when": "gitlens:activeIsTracked"
}, },
{ {
"command": "gitlens.diffWithNext", "command": "gitlens.diffWithNext",
"when": "gitlens:isTracked"
"when": "gitlens:activeIsTracked"
}, },
{ {
"command": "gitlens.diffWithPrevious", "command": "gitlens.diffWithPrevious",
"when": "gitlens:isTracked"
"when": "gitlens:activeIsTracked"
}, },
{ {
"command": "gitlens.diffLineWithPrevious", "command": "gitlens.diffLineWithPrevious",
"when": "gitlens:isBlameable"
"when": "gitlens:activeIsBlameable"
}, },
{ {
"command": "gitlens.diffWithRevision", "command": "gitlens.diffWithRevision",
"when": "gitlens:isTracked"
"when": "gitlens:activeIsTracked"
}, },
{ {
"command": "gitlens.diffWithWorking", "command": "gitlens.diffWithWorking",
"when": "gitlens:isTracked"
"when": "gitlens:activeIsTracked"
}, },
{ {
"command": "gitlens.diffLineWithWorking", "command": "gitlens.diffLineWithWorking",
"when": "gitlens:isBlameable"
"when": "gitlens:activeIsBlameable"
}, },
{ {
"command": "gitlens.externalDiffAll", "command": "gitlens.externalDiffAll",
@ -1275,15 +1275,15 @@
}, },
{ {
"command": "gitlens.showFileBlame", "command": "gitlens.showFileBlame",
"when": "gitlens:isBlameable"
"when": "gitlens:activeIsBlameable"
}, },
{ {
"command": "gitlens.showLineBlame", "command": "gitlens.showLineBlame",
"when": "gitlens:isBlameable"
"when": "gitlens:activeIsBlameable"
}, },
{ {
"command": "gitlens.toggleFileBlame", "command": "gitlens.toggleFileBlame",
"when": "gitlens:isBlameable"
"when": "gitlens:activeIsBlameable"
}, },
{ {
"command": "gitlens.clearFileAnnotations", "command": "gitlens.clearFileAnnotations",
@ -1295,15 +1295,15 @@
}, },
{ {
"command": "gitlens.toggleFileRecentChanges", "command": "gitlens.toggleFileRecentChanges",
"when": "gitlens:isTracked"
"when": "gitlens:activeIsTracked"
}, },
{ {
"command": "gitlens.toggleLineBlame", "command": "gitlens.toggleLineBlame",
"when": "gitlens:isBlameable"
"when": "gitlens:activeIsBlameable"
}, },
{ {
"command": "gitlens.toggleCodeLens", "command": "gitlens.toggleCodeLens",
"when": "gitlens:isTracked && gitlens:canToggleCodeLens"
"when": "gitlens:activeIsTracked && gitlens:canToggleCodeLens"
}, },
{ {
"command": "gitlens.showLastQuickPick", "command": "gitlens.showLastQuickPick",
@ -1311,15 +1311,15 @@
}, },
{ {
"command": "gitlens.showQuickCommitDetails", "command": "gitlens.showQuickCommitDetails",
"when": "gitlens:isBlameable"
"when": "gitlens:activeIsBlameable"
}, },
{ {
"command": "gitlens.showQuickCommitFileDetails", "command": "gitlens.showQuickCommitFileDetails",
"when": "gitlens:isBlameable"
"when": "gitlens:activeIsBlameable"
}, },
{ {
"command": "gitlens.showQuickFileHistory", "command": "gitlens.showQuickFileHistory",
"when": "gitlens:isTracked"
"when": "gitlens:activeIsTracked"
}, },
{ {
"command": "gitlens.showQuickBranchHistory", "command": "gitlens.showQuickBranchHistory",
@ -1339,11 +1339,11 @@
}, },
{ {
"command": "gitlens.copyShaToClipboard", "command": "gitlens.copyShaToClipboard",
"when": "gitlens:isBlameable"
"when": "gitlens:activeIsBlameable"
}, },
{ {
"command": "gitlens.copyMessageToClipboard", "command": "gitlens.copyMessageToClipboard",
"when": "gitlens:isBlameable"
"when": "gitlens:activeIsBlameable"
}, },
{ {
"command": "gitlens.closeUnchangedFiles", "command": "gitlens.closeUnchangedFiles",
@ -1355,23 +1355,23 @@
}, },
{ {
"command": "gitlens.openBranchesInRemote", "command": "gitlens.openBranchesInRemote",
"when": "gitlens:hasRemotes"
"when": "gitlens:activeHasRemotes"
}, },
{ {
"command": "gitlens.openBranchInRemote", "command": "gitlens.openBranchInRemote",
"when": "gitlens:hasRemotes"
"when": "gitlens:activeHasRemotes"
}, },
{ {
"command": "gitlens.openCommitInRemote", "command": "gitlens.openCommitInRemote",
"when": "gitlens:isBlameable && gitlens:hasRemotes"
"when": "gitlens:activeIsBlameable && gitlens:activeHasRemotes"
}, },
{ {
"command": "gitlens.openFileInRemote", "command": "gitlens.openFileInRemote",
"when": "gitlens:isTracked && gitlens:hasRemotes"
"when": "gitlens:activeIsTracked && gitlens:activeHasRemotes"
}, },
{ {
"command": "gitlens.openRepoInRemote", "command": "gitlens.openRepoInRemote",
"when": "gitlens:hasRemotes"
"when": "gitlens:activeHasRemotes"
}, },
{ {
"command": "gitlens.stashApply", "command": "gitlens.stashApply",
@ -1461,52 +1461,52 @@
"editor/context": [ "editor/context": [
{ {
"command": "gitlens.openFileInRemote", "command": "gitlens.openFileInRemote",
"when": "editorTextFocus && gitlens:isTracked && gitlens:hasRemotes && config.gitlens.advanced.menus.editorContext.remote",
"when": "editorTextFocus && gitlens:activeHasRemotes && config.gitlens.advanced.menus.editorContext.remote",
"group": "navigation@100" "group": "navigation@100"
}, },
{ {
"command": "gitlens.diffLineWithPrevious", "command": "gitlens.diffLineWithPrevious",
"when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.lineDiff",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.lineDiff",
"group": "1_gitlens@1" "group": "1_gitlens@1"
}, },
{ {
"command": "gitlens.diffLineWithWorking", "command": "gitlens.diffLineWithWorking",
"when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.lineDiff",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.lineDiff",
"group": "1_gitlens@2" "group": "1_gitlens@2"
}, },
{ {
"command": "gitlens.showQuickCommitFileDetails", "command": "gitlens.showQuickCommitFileDetails",
"when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.details",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.details",
"group": "1_gitlens@3" "group": "1_gitlens@3"
}, },
{ {
"command": "gitlens.diffWithPrevious", "command": "gitlens.diffWithPrevious",
"when": "editorTextFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorContext.fileDiff",
"when": "editorTextFocus && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorContext.fileDiff",
"group": "1_gitlens_1@1" "group": "1_gitlens_1@1"
}, },
{ {
"command": "gitlens.diffWithWorking", "command": "gitlens.diffWithWorking",
"when": "editorTextFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorContext.fileDiff",
"when": "editorTextFocus && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorContext.fileDiff",
"group": "1_gitlens_1@2" "group": "1_gitlens_1@2"
}, },
{ {
"command": "gitlens.showQuickFileHistory", "command": "gitlens.showQuickFileHistory",
"when": "gitlens:isTracked && config.gitlens.advanced.menus.editorContext.history",
"when": "gitlens:activeIsTracked && config.gitlens.advanced.menus.editorContext.history",
"group": "3_gitlens@1" "group": "3_gitlens@1"
}, },
{ {
"command": "gitlens.toggleFileBlame", "command": "gitlens.toggleFileBlame",
"when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.blame",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.blame",
"group": "3_gitlens@2" "group": "3_gitlens@2"
}, },
{ {
"command": "gitlens.copyShaToClipboard", "command": "gitlens.copyShaToClipboard",
"when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.copy",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.copy",
"group": "9_gitlens@1" "group": "9_gitlens@1"
}, },
{ {
"command": "gitlens.copyMessageToClipboard", "command": "gitlens.copyMessageToClipboard",
"when": "editorTextFocus && gitlens:isBlameable && config.gitlens.advanced.menus.editorContext.copy",
"when": "editorTextFocus && gitlens:activeIsBlameable && config.gitlens.advanced.menus.editorContext.copy",
"group": "9_gitlens@2" "group": "9_gitlens@2"
} }
], ],
@ -1514,7 +1514,7 @@
{ {
"command": "gitlens.toggleFileBlame", "command": "gitlens.toggleFileBlame",
"alt": "gitlens.toggleFileRecentChanges", "alt": "gitlens.toggleFileRecentChanges",
"when": "gitlens:isBlameable && !gitlens:annotationStatus && config.gitlens.advanced.menus.editorTitle.blame",
"when": "gitlens:activeIsBlameable && !gitlens:annotationStatus && config.gitlens.advanced.menus.editorTitle.blame",
"group": "navigation@100" "group": "navigation@100"
}, },
{ {
@ -1529,27 +1529,27 @@
}, },
{ {
"command": "gitlens.openFileInRemote", "command": "gitlens.openFileInRemote",
"when": "gitlens:enabled && gitlens:hasRemotes && config.gitlens.advanced.menus.editorTitle.remote",
"when": "gitlens:enabled && gitlens:activeHasRemotes && config.gitlens.advanced.menus.editorTitle.remote",
"group": "1_gitlens" "group": "1_gitlens"
}, },
{ {
"command": "gitlens.openRepoInRemote", "command": "gitlens.openRepoInRemote",
"when": "gitlens:enabled && gitlens:hasRemotes && config.gitlens.advanced.menus.editorTitle.remote",
"when": "gitlens:enabled && gitlens:activeHasRemotes && config.gitlens.advanced.menus.editorTitle.remote",
"group": "1_gitlens" "group": "1_gitlens"
}, },
{ {
"command": "gitlens.diffWithPrevious", "command": "gitlens.diffWithPrevious",
"when": "editorTextFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorTitle.fileDiff",
"when": "editorTextFocus && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorTitle.fileDiff",
"group": "2_gitlens" "group": "2_gitlens"
}, },
{ {
"command": "gitlens.diffWithWorking", "command": "gitlens.diffWithWorking",
"when": "editorTextFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorTitle.fileDiff",
"when": "editorTextFocus && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorTitle.fileDiff",
"group": "2_gitlens" "group": "2_gitlens"
}, },
{ {
"command": "gitlens.showQuickFileHistory", "command": "gitlens.showQuickFileHistory",
"when": "editorFocus && gitlens:isTracked && config.gitlens.advanced.menus.editorTitle.history",
"when": "editorFocus && gitlens:activeIsTracked && config.gitlens.advanced.menus.editorTitle.history",
"group": "2_gitlens_1" "group": "2_gitlens_1"
}, },
{ {
@ -1990,12 +1990,12 @@
{ {
"command": "gitlens.toggleFileBlame", "command": "gitlens.toggleFileBlame",
"key": "alt+b", "key": "alt+b",
"when": "editorTextFocus && gitlens:isBlameable"
"when": "editorTextFocus && gitlens:activeIsBlameable"
}, },
{ {
"command": "gitlens.toggleCodeLens", "command": "gitlens.toggleCodeLens",
"key": "shift+alt+b", "key": "shift+alt+b",
"when": "editorTextFocus && gitlens:isTracked && gitlens:canToggleCodeLens"
"when": "editorTextFocus && gitlens:activeIsTracked && gitlens:canToggleCodeLens"
}, },
{ {
"command": "gitlens.showLastQuickPick", "command": "gitlens.showLastQuickPick",
@ -2030,27 +2030,27 @@
{ {
"command": "gitlens.diffWithNext", "command": "gitlens.diffWithNext",
"key": "alt+.", "key": "alt+.",
"when": "editorTextFocus && gitlens:isTracked"
"when": "editorTextFocus && gitlens:activeIsTracked"
}, },
{ {
"command": "gitlens.diffLineWithPrevious", "command": "gitlens.diffLineWithPrevious",
"key": "shift+alt+,", "key": "shift+alt+,",
"when": "editorTextFocus && gitlens:isTracked"
"when": "editorTextFocus && gitlens:activeIsTracked"
}, },
{ {
"command": "gitlens.diffWithPrevious", "command": "gitlens.diffWithPrevious",
"key": "alt+,", "key": "alt+,",
"when": "editorTextFocus && gitlens:isTracked"
"when": "editorTextFocus && gitlens:activeIsTracked"
}, },
{ {
"command": "gitlens.diffLineWithWorking", "command": "gitlens.diffLineWithWorking",
"key": "alt+w", "key": "alt+w",
"when": "editorTextFocus && gitlens:isTracked"
"when": "editorTextFocus && gitlens:activeIsTracked"
}, },
{ {
"command": "gitlens.diffWithWorking", "command": "gitlens.diffWithWorking",
"key": "shift+alt+w", "key": "shift+alt+w",
"when": "editorTextFocus && gitlens:isTracked"
"when": "editorTextFocus && gitlens:activeIsTracked"
} }
], ],
"views": { "views": {

+ 3
- 2
src/constants.ts View File

@ -33,8 +33,9 @@ export enum CommandContext {
GitExplorerView = 'gitlens:gitExplorer:view', GitExplorerView = 'gitlens:gitExplorer:view',
HasRemotes = 'gitlens:hasRemotes', HasRemotes = 'gitlens:hasRemotes',
HasRepository = 'gitlens:hasRepository', HasRepository = 'gitlens:hasRepository',
IsBlameable = 'gitlens:isBlameable',
IsTracked = 'gitlens:isTracked',
ActiveHasRemotes = 'gitlens:activeHasRemotes',
ActiveIsBlameable = 'gitlens:activeIsBlameable',
ActiveFileIsTracked = 'gitlens:activeIsTracked',
Key = 'gitlens:key' Key = 'gitlens:key'
} }

+ 29
- 10
src/git/gitContextTracker.ts View File

@ -53,7 +53,8 @@ export class GitContextTracker extends Disposable {
} }
async _onRepoChanged(reasons: RepoChangedReasons[]) { async _onRepoChanged(reasons: RepoChangedReasons[]) {
if (!reasons.includes(RepoChangedReasons.Remotes)) return;
// TODO: Support multi-root
if (!reasons.includes(RepoChangedReasons.Remotes) && !reasons.includes(RepoChangedReasons.Repositories)) return;
const gitUri = this._editor === undefined ? undefined : await GitUri.fromUri(this._editor.document.uri, this.git); const gitUri = this._editor === undefined ? undefined : await GitUri.fromUri(this._editor.document.uri, this.git);
this._updateContextHasRemotes(gitUri); this._updateContextHasRemotes(gitUri);
@ -129,18 +130,36 @@ export class GitContextTracker extends Disposable {
private async _updateContextHasRemotes(uri: GitUri | undefined) { private async _updateContextHasRemotes(uri: GitUri | undefined) {
try { try {
let repoPath = this.git.repoPath;
const repositories = await this.git.getRepositories();
let hasRemotes = false;
if (uri !== undefined && this.git.isTrackable(uri)) { if (uri !== undefined && this.git.isTrackable(uri)) {
repoPath = uri.repoPath || this.git.repoPath;
const remotes = await this.git.getRemotes(uri.repoPath);
await setCommandContext(CommandContext.ActiveHasRemotes, remotes.length !== 0);
}
else {
if (repositories.length === 1) {
const remotes = await this.git.getRemotes(repositories[0].path);
hasRemotes = remotes.length !== 0;
await setCommandContext(CommandContext.ActiveHasRemotes, hasRemotes);
}
else {
await setCommandContext(CommandContext.ActiveHasRemotes, false);
}
} }
let hasRemotes = false;
if (repoPath) {
const remotes = await this.git.getRemotes(repoPath);
hasRemotes = remotes.length !== 0;
if (!hasRemotes) {
for (const repo of repositories) {
const remotes = await this.git.getRemotes(repo.path);
hasRemotes = remotes.length !== 0;
if (hasRemotes) break;
}
} }
setCommandContext(CommandContext.HasRemotes, hasRemotes);
await setCommandContext(CommandContext.HasRemotes, hasRemotes);
} }
catch (ex) { catch (ex) {
Logger.error(ex, 'GitEditorTracker._updateContextHasRemotes'); Logger.error(ex, 'GitEditorTracker._updateContextHasRemotes');
@ -150,7 +169,7 @@ export class GitContextTracker extends Disposable {
private async _updateEditorContext(uri: GitUri | undefined, editor: TextEditor | undefined) { private async _updateEditorContext(uri: GitUri | undefined, editor: TextEditor | undefined) {
try { try {
const tracked = uri === undefined ? false : await this.git.isTracked(uri); const tracked = uri === undefined ? false : await this.git.isTracked(uri);
setCommandContext(CommandContext.IsTracked, tracked);
setCommandContext(CommandContext.ActiveFileIsTracked, tracked);
let blameable = tracked && (editor !== undefined && editor.document !== undefined && !editor.document.isDirty); let blameable = tracked && (editor !== undefined && editor.document !== undefined && !editor.document.isDirty);
if (blameable) { if (blameable) {
@ -168,7 +187,7 @@ export class GitContextTracker extends Disposable {
if (!force && this._isBlameable === blameable) return; if (!force && this._isBlameable === blameable) return;
try { try {
setCommandContext(CommandContext.IsBlameable, blameable);
setCommandContext(CommandContext.ActiveIsBlameable, blameable);
this._onDidChangeBlameability.fire({ this._onDidChangeBlameability.fire({
blameable: blameable, blameable: blameable,
editor: this._editor editor: this._editor

+ 1
- 0
src/git/models/models.ts View File

@ -7,6 +7,7 @@ export * from './diff';
export * from './log'; export * from './log';
export * from './logCommit'; export * from './logCommit';
export * from './remote'; export * from './remote';
export * from './repository';
export * from './stash'; export * from './stash';
export * from './stashCommit'; export * from './stashCommit';
export * from './status'; export * from './status';

+ 106
- 0
src/git/models/repository.ts View File

@ -0,0 +1,106 @@
'use strict';
import { Functions } from '../../system';
import { Disposable, Event, EventEmitter, RelativePattern, Uri, workspace, WorkspaceFolder } from 'vscode';
export enum RepositoryStorage {
StatusNode = 'statusNode'
}
export class Repository extends Disposable {
private _onDidChangeFileSystem = new EventEmitter<Uri | undefined>();
get onDidChangeFileSystem(): Event<Uri | undefined> {
return this._onDidChangeFileSystem.event;
}
readonly index: number;
readonly name: string;
readonly storage: Map<string, any> = new Map();
private readonly _disposable: Disposable;
private _fsWatcherDisposable: Disposable | undefined;
private _pendingChanges: { repo: boolean, fs: boolean } = { repo: false, fs: false };
private _suspended: boolean;
constructor(
private readonly folder: WorkspaceFolder,
public readonly path: string,
readonly onRepoChanged: (uri: Uri) => void,
suspended: boolean
) {
super(() => this.dispose());
this.index = folder.index;
this.name = folder.name;
this._suspended = suspended;
const watcher = workspace.createFileSystemWatcher(new RelativePattern(folder, '**/.git/{index,HEAD,refs/stash,refs/heads/**,refs/remotes/**}'));
const subscriptions = [
watcher,
watcher.onDidChange(onRepoChanged),
watcher.onDidCreate(onRepoChanged),
watcher.onDidDelete(onRepoChanged)
];
this._disposable = Disposable.from(...subscriptions);
}
dispose() {
this.stopWatchingFileSystem();
// Clean up any disposables in storage
for (const item of this.storage.values()) {
if (item != null && typeof item.dispose === 'function') {
item.dispose();
}
}
this._disposable && this._disposable.dispose();
}
resume() {
if (!this._suspended) return;
this._suspended = false;
// If we've come back into focus and we are dirty, fire the change events
if (this._pendingChanges.fs) {
this._pendingChanges.fs = false;
this._onDidChangeFileSystem.fire();
}
}
startWatchingFileSystem() {
if (this._fsWatcherDisposable !== undefined) return;
const debouncedFn = Functions.debounce((uri: Uri) => this._onDidChangeFileSystem.fire(uri), 2500);
const fn = (uri: Uri) => {
// Ignore .git changes
if (/\.git/.test(uri.fsPath)) return;
if (this._suspended) {
this._pendingChanges.fs = true;
return;
}
debouncedFn(uri);
};
const watcher = workspace.createFileSystemWatcher(new RelativePattern(this.folder, `**`));
this._fsWatcherDisposable = Disposable.from(
watcher,
watcher.onDidChange(fn),
watcher.onDidCreate(fn),
watcher.onDidDelete(fn)
);
}
stopWatchingFileSystem() {
this._fsWatcherDisposable && this._fsWatcherDisposable.dispose();
this._fsWatcherDisposable = undefined;
}
suspend() {
this._suspended = true;
}
}

+ 42
- 83
src/gitService.ts View File

@ -1,10 +1,10 @@
'use strict'; 'use strict';
import { Functions, Iterables, Objects } from './system'; import { Functions, Iterables, Objects } from './system';
import { Disposable, Event, EventEmitter, FileSystemWatcher, Range, TextDocument, TextDocumentChangeEvent, TextEditor, Uri, window, WindowState, workspace, WorkspaceFoldersChangeEvent } from 'vscode';
import { Disposable, Event, EventEmitter, Range, TextDocument, TextDocumentChangeEvent, TextEditor, Uri, window, WindowState, workspace, WorkspaceFoldersChangeEvent } from 'vscode';
import { IConfig } from './configuration'; import { IConfig } from './configuration';
import { CommandContext, DocumentSchemes, ExtensionKey, setCommandContext } from './constants'; import { CommandContext, DocumentSchemes, ExtensionKey, setCommandContext } from './constants';
import { RemoteProviderFactory } from './git/remotes/factory'; import { RemoteProviderFactory } from './git/remotes/factory';
import { Git, GitAuthor, GitBlame, GitBlameCommit, GitBlameLine, GitBlameLines, GitBlameParser, GitBranch, GitBranchParser, GitCommit, GitCommitType, GitDiff, GitDiffChunkLine, GitDiffParser, GitDiffShortStat, GitLog, GitLogCommit, GitLogParser, GitRemote, GitRemoteParser, GitStash, GitStashParser, GitStatus, GitStatusFile, GitStatusParser, IGit, setDefaultEncoding } from './git/git';
import { Git, GitAuthor, GitBlame, GitBlameCommit, GitBlameLine, GitBlameLines, GitBlameParser, GitBranch, GitBranchParser, GitCommit, GitCommitType, GitDiff, GitDiffChunkLine, GitDiffParser, GitDiffShortStat, GitLog, GitLogCommit, GitLogParser, GitRemote, GitRemoteParser, GitStash, GitStashParser, GitStatus, GitStatusFile, GitStatusParser, IGit, Repository, setDefaultEncoding } from './git/git';
import { GitUri, IGitCommitInfo, IGitUriData } from './git/gitUri'; import { GitUri, IGitCommitInfo, IGitUriData } from './git/gitUri';
import { Logger } from './logger'; import { Logger } from './logger';
import * as fs from 'fs'; import * as fs from 'fs';
@ -51,12 +51,6 @@ interface CachedBlame extends CachedItem { }
interface CachedDiff extends CachedItem<GitDiff> { } interface CachedDiff extends CachedItem<GitDiff> { }
interface CachedLog extends CachedItem<GitLog> { } interface CachedLog extends CachedItem<GitLog> { }
export interface Repository {
readonly name: string;
readonly index: number;
readonly path: string;
}
enum RemoveCacheReason { enum RemoveCacheReason {
DocumentClosed, DocumentClosed,
DocumentSaved DocumentSaved
@ -94,11 +88,7 @@ export class GitService extends Disposable {
return this._onDidChangeGitCache.event; return this._onDidChangeGitCache.event;
} }
private _onDidChangeFileSystem = new EventEmitter<Uri | undefined>();
get onDidChangeFileSystem(): Event<Uri | undefined> {
return this._onDidChangeFileSystem.event;
}
// TODO: Support multi-root { repo, reasons }[]?
private _onDidChangeRepo = new EventEmitter<RepoChangedReasons[]>(); private _onDidChangeRepo = new EventEmitter<RepoChangedReasons[]>();
get onDidChangeRepo(): Event<RepoChangedReasons[]> { get onDidChangeRepo(): Event<RepoChangedReasons[]> {
return this._onDidChangeRepo.event; return this._onDidChangeRepo.event;
@ -106,14 +96,13 @@ export class GitService extends Disposable {
private _cacheDisposable: Disposable | undefined; private _cacheDisposable: Disposable | undefined;
private _disposable: Disposable | undefined; private _disposable: Disposable | undefined;
private _focused: boolean = true;
private _gitCache: Map<string, GitCacheEntry>; private _gitCache: Map<string, GitCacheEntry>;
private _pendingChanges: { repo: boolean } = { repo: false };
private _remotesCache: Map<string, GitRemote[]>; private _remotesCache: Map<string, GitRemote[]>;
private _repositories: Map<string, Repository | undefined>; private _repositories: Map<string, Repository | undefined>;
private _repositoriesPromise: Promise<void> | undefined; private _repositoriesPromise: Promise<void> | undefined;
private _repoWatcher: FileSystemWatcher | undefined;
private _suspended: boolean = false;
private _trackedCache: Map<string, boolean>; private _trackedCache: Map<string, boolean>;
private _unfocusedChanges: { repo: boolean, fs: boolean } = { repo: false, fs: false };
private _versionedUriCache: Map<string, UriCacheEntry>; private _versionedUriCache: Map<string, UriCacheEntry>;
constructor() { constructor() {
@ -138,10 +127,7 @@ export class GitService extends Disposable {
} }
dispose() { dispose() {
this.stopWatchingFileSystem();
this._repoWatcher && this._repoWatcher.dispose();
this._repoWatcher = undefined;
this._repositories.forEach(r => r && r.dispose());
this._disposable && this._disposable.dispose(); this._disposable && this._disposable.dispose();
@ -161,15 +147,6 @@ export class GitService extends Disposable {
return repo === undefined ? undefined : repo.path; return repo === undefined ? undefined : repo.path;
} }
public async getRepositories(): Promise<Repository[]> {
if (this._repositoriesPromise !== undefined) {
await this._repositoriesPromise;
this._repositoriesPromise = undefined;
}
return [...Iterables.filter(this._repositories.values(), r => r !== undefined) as Iterable<Repository>];
}
public get UseCaching() { public get UseCaching() {
return this.config.advanced.caching.enabled; return this.config.advanced.caching.enabled;
} }
@ -184,15 +161,10 @@ export class GitService extends Disposable {
if (cfg.advanced.caching.enabled) { if (cfg.advanced.caching.enabled) {
this._cacheDisposable && this._cacheDisposable.dispose(); this._cacheDisposable && this._cacheDisposable.dispose();
this._repoWatcher = this._repoWatcher || workspace.createFileSystemWatcher('**/.git/{index,HEAD,refs/stash,refs/heads/**,refs/remotes/**}');
const subscriptions: Disposable[] = [ const subscriptions: Disposable[] = [
workspace.onDidCloseTextDocument(d => this.removeCachedEntry(d, RemoveCacheReason.DocumentClosed)), workspace.onDidCloseTextDocument(d => this.removeCachedEntry(d, RemoveCacheReason.DocumentClosed)),
workspace.onDidChangeTextDocument(this.onTextDocumentChanged, this), workspace.onDidChangeTextDocument(this.onTextDocumentChanged, this),
workspace.onDidSaveTextDocument(d => this.removeCachedEntry(d, RemoveCacheReason.DocumentSaved)),
this._repoWatcher.onDidChange(this.onRepoChanged, this),
this._repoWatcher.onDidCreate(this.onRepoChanged, this),
this._repoWatcher.onDidDelete(this.onRepoChanged, this)
workspace.onDidSaveTextDocument(d => this.removeCachedEntry(d, RemoveCacheReason.DocumentSaved))
]; ];
this._cacheDisposable = Disposable.from(...subscriptions); this._cacheDisposable = Disposable.from(...subscriptions);
} }
@ -200,9 +172,6 @@ export class GitService extends Disposable {
this._cacheDisposable && this._cacheDisposable.dispose(); this._cacheDisposable && this._cacheDisposable.dispose();
this._cacheDisposable = undefined; this._cacheDisposable = undefined;
this._repoWatcher && this._repoWatcher.dispose();
this._repoWatcher = undefined;
this._gitCache.clear(); this._gitCache.clear();
} }
} }
@ -223,19 +192,22 @@ export class GitService extends Disposable {
} }
private onWindowStateChanged(e: WindowState) { private onWindowStateChanged(e: WindowState) {
const focusChanged = e.focused !== this._focused;
this._focused = e.focused;
if (e.focused) {
this._repositories.forEach(r => r && r.resume());
}
else {
this._repositories.forEach(r => r && r.suspend());
}
if (!focusChanged || !e.focused) return;
const suspended = !e.focused;
const changed = suspended !== this._suspended;
this._suspended = suspended;
// If we've come back into focus and we are dirty, fire the change events
if (this._unfocusedChanges.fs) {
this._unfocusedChanges.fs = false;
this._onDidChangeFileSystem.fire();
}
if (suspended || !changed) return;
if (this._unfocusedChanges.repo) {
this._unfocusedChanges.repo = false;
// If we've come back into focus and we are dirty, fire the change events
if (this._pendingChanges.repo) {
this._pendingChanges.repo = false;
this._fireRepoChangeDebounced!(); this._fireRepoChangeDebounced!();
} }
} }
@ -243,11 +215,9 @@ export class GitService extends Disposable {
private async onWorkspaceFoldersChanged(e?: WorkspaceFoldersChangeEvent) { private async onWorkspaceFoldersChanged(e?: WorkspaceFoldersChangeEvent) {
let initializing = false; let initializing = false;
if (e === undefined) { if (e === undefined) {
if (workspace.workspaceFolders === undefined) return;
initializing = true; initializing = true;
e = { e = {
added: workspace.workspaceFolders,
added: workspace.workspaceFolders || [],
removed: [] removed: []
} as WorkspaceFoldersChangeEvent; } as WorkspaceFoldersChangeEvent;
} }
@ -259,13 +229,21 @@ export class GitService extends Disposable {
const rp = await this.getRepoPathCore(fsPath, true); const rp = await this.getRepoPathCore(fsPath, true);
if (rp === undefined) { if (rp === undefined) {
Logger.log(`onWorkspaceFoldersChanged(${fsPath})`, 'No repository found'); Logger.log(`onWorkspaceFoldersChanged(${fsPath})`, 'No repository found');
this._repositories.set(fsPath, undefined);
}
else {
this._repositories.set(fsPath, new Repository(f, rp, this.onRepoChanged.bind(this), this._suspended));
} }
this._repositories.set(fsPath, { name: f.name, index: f.index, path: rp } as Repository);
} }
for (const f of e.removed) { for (const f of e.removed) {
if (f.uri.scheme !== DocumentSchemes.File) continue; if (f.uri.scheme !== DocumentSchemes.File) continue;
const repo = this._repositories.get(f.uri.fsPath);
if (repo !== undefined) {
repo.dispose();
}
this._repositories.delete(f.uri.fsPath); this._repositories.delete(f.uri.fsPath);
} }
@ -333,8 +311,8 @@ export class GitService extends Disposable {
this._repoChangedReasons.push(reason); this._repoChangedReasons.push(reason);
} }
if (!this._focused) {
this._unfocusedChanges.repo = true;
if (this._suspended) {
this._pendingChanges.repo = true;
return; return;
} }
@ -369,6 +347,15 @@ export class GitService extends Disposable {
} }
} }
public async getRepositories(): Promise<Repository[]> {
if (this._repositoriesPromise !== undefined) {
await this._repositoriesPromise;
this._repositoriesPromise = undefined;
}
return [...Iterables.filter(this._repositories.values(), r => r !== undefined) as Iterable<Repository>];
}
checkoutFile(uri: GitUri, sha?: string) { checkoutFile(uri: GitUri, sha?: string) {
sha = sha || uri.sha; sha = sha || uri.sha;
Logger.log(`checkoutFile('${uri.repoPath}', '${uri.fsPath}', ${sha})`); Logger.log(`checkoutFile('${uri.repoPath}', '${uri.fsPath}', ${sha})`);
@ -1111,36 +1098,8 @@ export class GitService extends Disposable {
return Git.difftool_dirDiff(repoPath, sha1, sha2); return Git.difftool_dirDiff(repoPath, sha1, sha2);
} }
private _fsWatcherDisposable: Disposable | undefined;
startWatchingFileSystem() {
if (this._fsWatcherDisposable !== undefined) return;
const debouncedFn = Functions.debounce((uri: Uri) => this._onDidChangeFileSystem.fire(uri), 2500);
const fn = (uri: Uri) => {
// Ignore .git changes
if (/\.git/.test(uri.fsPath)) return;
if (!this._focused) {
this._unfocusedChanges.fs = true;
return;
}
debouncedFn(uri);
};
const watcher = workspace.createFileSystemWatcher(`**`);
this._fsWatcherDisposable = Disposable.from(
watcher,
watcher.onDidChange(fn),
watcher.onDidCreate(fn),
watcher.onDidDelete(fn)
);
}
stopWatchingFileSystem() { stopWatchingFileSystem() {
this._fsWatcherDisposable && this._fsWatcherDisposable.dispose();
this._fsWatcherDisposable = undefined;
this._repositories.forEach(r => r && r.stopWatchingFileSystem());
} }
stashApply(repoPath: string, stashName: string, deleteAfter: boolean = false) { stashApply(repoPath: string, stashName: string, deleteAfter: boolean = false) {

+ 1
- 1
src/views/repositoryNode.ts View File

@ -24,7 +24,7 @@ export class RepositoryNode extends ExplorerNode {
async getChildren(): Promise<ExplorerNode[]> { async getChildren(): Promise<ExplorerNode[]> {
return [ return [
new StatusNode(this.uri, this.context, this.git),
new StatusNode(this.uri, this.repo, this.context, this.git),
new BranchesNode(this.uri, this.context, this.git), new BranchesNode(this.uri, this.context, this.git),
new RemotesNode(this.uri, this.context, this.git), new RemotesNode(this.uri, this.context, this.git),
new StashesNode(this.uri, this.context, this.git) new StashesNode(this.uri, this.context, this.git)

+ 11
- 10
src/views/statusNode.ts View File

@ -1,18 +1,17 @@
import { commands, Disposable, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
import { commands, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
import { WorkspaceState } from '../constants'; import { WorkspaceState } from '../constants';
import { ExplorerNode, ResourceType } from './explorerNode'; import { ExplorerNode, ResourceType } from './explorerNode';
import { GitService, GitStatus, GitUri } from '../gitService';
import { GitService, GitStatus, GitUri, Repository, RepositoryStorage } from '../gitService';
import { StatusFilesNode } from './statusFilesNode'; import { StatusFilesNode } from './statusFilesNode';
import { StatusUpstreamNode } from './statusUpstreamNode'; import { StatusUpstreamNode } from './statusUpstreamNode';
let _eventDisposable: Disposable | undefined;
export class StatusNode extends ExplorerNode { export class StatusNode extends ExplorerNode {
readonly resourceType: ResourceType = 'gitlens:status'; readonly resourceType: ResourceType = 'gitlens:status';
constructor( constructor(
uri: GitUri, uri: GitUri,
private repo: Repository,
protected readonly context: ExtensionContext, protected readonly context: ExtensionContext,
protected readonly git: GitService protected readonly git: GitService
) { ) {
@ -49,19 +48,21 @@ export class StatusNode extends ExplorerNode {
const status = await this.git.getStatusForRepo(this.uri.repoPath!); const status = await this.git.getStatusForRepo(this.uri.repoPath!);
if (status === undefined) return new TreeItem('No repo status'); if (status === undefined) return new TreeItem('No repo status');
if (_eventDisposable !== undefined) {
_eventDisposable.dispose();
_eventDisposable = undefined;
const subscription = this.repo.storage.get(RepositoryStorage.StatusNode);
if (subscription !== undefined) {
subscription.dispose();
this.repo.storage.delete(RepositoryStorage.StatusNode);
} }
if (this.includeWorkingTree) { if (this.includeWorkingTree) {
this._status = status; this._status = status;
if (this.git.config.gitExplorer.autoRefresh && this.context.workspaceState.get<boolean>(WorkspaceState.GitExplorerAutoRefresh, true)) { if (this.git.config.gitExplorer.autoRefresh && this.context.workspaceState.get<boolean>(WorkspaceState.GitExplorerAutoRefresh, true)) {
_eventDisposable = this.git.onDidChangeFileSystem(this.onFileSystemChanged, this);
this.context.subscriptions.push(_eventDisposable);
const subscription = this.repo.onDidChangeFileSystem(this.onFileSystemChanged, this);
this.repo.storage.set(RepositoryStorage.StatusNode, subscription);
this.context.subscriptions.push(subscription);
this.git.startWatchingFileSystem();
this.repo.startWatchingFileSystem();
} }
} }

Loading…
Cancel
Save