diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8984edd..2ab7e57 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,13 +8,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
## [5.2.0-beta] - 2017-09-20
### Added
-- Adds working tree status (enabled via `"gitlens.insiders": true`) to the `Repository Status` node in the `GitLens` custom view
- Adds new `Changed Files` node to the `Repository Status` node of the `GitLens` custom view's `Repository View` -- closes [#139](https://github.com/eamodio/vscode-gitlens/issues/139)
- Provides a at-a-glance view of all "working" changes
- Expands to a file-based view of all changed files in the working tree (enabled via `"gitlens.insiders": true`) and/or all files in all commits ahead of the upstream
-- Adds `gitlens.gitExplorer.enabled` setting to specify whether or not to show the `GitLens` custom view - closes [#144](https://github.com/eamodio/vscode-gitlens/issues/144)
-- Adds `gitlens.gitExplorer.statusFileFormat` setting to the format of the status of a working or committed file in the `GitLens` custom view
+- Adds optional (on by default) working tree status information to the `Repository Status` node in the `GitLens` custom view
- Adds `auto` value to `gitlens.gitExplorer.view` setting - closes [#150](https://github.com/eamodio/vscode-gitlens/issues/150)
+- Adds `gitlens.gitExplorer.enabled` setting to specify whether or not to show the `GitLens` custom view - closes [#144](https://github.com/eamodio/vscode-gitlens/issues/144)
+- Adds `gitlens.gitExplorer.includeWorkingTree` setting to specify whether or not to include working tree files inside the `Repository Status` node of the `GitLens` custom view
+- Adds `gitlens.gitExplorer.statusFileFormat` setting to the format of the status of a working or committed file in the `GitLens` custom view
### Changed
- Changes the sorting (now alphabetical) of files shown in the `GitLens` custom view
diff --git a/README.md b/README.md
index f43a069..dc1edae 100644
--- a/README.md
+++ b/README.md
@@ -126,7 +126,7 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,

- `Repository Status` node — provides the status of the repository
- - Provides the name of the current branch, its working tree status (enabled via `"gitlens.insiders": true`), and its upstream tracking branch and status (if available)
+ - Provides the name of the current branch, [optionally](#gitlens-custom-view-settings) its working tree status, and its upstream tracking branch and status (if available)
- Provides indicator dots on the repository icon which denote the following:
- `None` - up-to-date with the upstream
- `Green` - ahead of the upstream
@@ -136,7 +136,7 @@ GitLens provides an unobtrusive blame annotation at the end of the current line,
- is behind the upstream — quickly see and explore the specific commits behind the upstream (i.e. commits that haven't been pulled)
- is ahead of the upstream — quickly see and explore the specific commits ahead of the upstream (i.e. commits that haven't been pushed)
- `Changed Files` node — provides a at-a-glance view of all "working" changes
- - Expands to a file-based view of all changed files in the working tree (enabled via `"gitlens.insiders": true`) and/or all files in all commits ahead of the upstream
+ - Expands to a file-based view of all changed files in the working tree ([optionally](#gitlens-custom-view-settings)) and/or all files in all commits ahead of the upstream
- Provides a context menu with `Open Repository in Remote`, and `Refresh` commands
- `Branches` node — provides a list of the local branches
@@ -356,6 +356,7 @@ GitLens is highly customizable and provides many configuration settings to allow
|-----|------------
|`gitlens.gitExplorer.enabled`|Specifies whether or not to show the `GitLens` custom view"
|`gitlens.gitExplorer.view`|Specifies the starting view (mode) of the `GitLens` custom view
`auto` - shows the last selected view, defaults to `repository`
`history` - shows the commit history of the active file
`repository` - shows a repository explorer"
+|`gitlens.gitExplorer.includeWorkingTree`|Specifies whether or not to include working tree files inside the `Repository Status` node of the `GitLens` custom view
|`gitlens.gitExplorer.showTrackingBranch`|Specifies whether or not to show the tracking branch when displaying local branches in the `GitLens` custom view"
|`gitlens.gitExplorer.commitFormat`|Specifies the format of committed changes in the `GitLens` custom view
Available tokens
${id} - commit id
${author} - commit author
${message} - commit message
${ago} - relative commit date (e.g. 1 day ago)
${date} - formatted commit date (format specified by `gitlens.statusBar.dateFormat`)
${authorAgo} - commit author, relative commit date
See https://github.com/eamodio/vscode-gitlens/wiki/Advanced-Formatting for advanced formatting
|`gitlens.gitExplorer.commitFileFormat`|Specifies the format of a committed file in the `GitLens` custom view
Available tokens
${file} - file name
${filePath} - file name and path
${path} - file path
diff --git a/package.json b/package.json
index d97a085..fa10788 100644
--- a/package.json
+++ b/package.json
@@ -428,6 +428,11 @@
"default": true,
"description": "Specifies whether or not to show the `GitLens` custom view"
},
+ "gitlens.gitExplorer.includeWorkingTree": {
+ "type": "boolean",
+ "default": true,
+ "description": "Specifies whether or not to include working tree files inside the `Repository Status` node of the `GitLens` custom view"
+ },
"gitlens.gitExplorer.showTrackingBranch": {
"type": "boolean",
"default": true,
diff --git a/src/configuration.ts b/src/configuration.ts
index 0e2cf2d..7de453f 100644
--- a/src/configuration.ts
+++ b/src/configuration.ts
@@ -319,6 +319,7 @@ export interface IConfig {
gitExplorer: {
enabled: boolean;
view: GitExplorerView;
+ includeWorkingTree: boolean;
showTrackingBranch: boolean;
commitFormat: string;
commitFileFormat: string;
diff --git a/src/git/git.ts b/src/git/git.ts
index bc65f1e..f7098c7 100644
--- a/src/git/git.ts
+++ b/src/git/git.ts
@@ -340,6 +340,14 @@ export class Git {
return gitCommand({ cwd: repoPath }, ...params, ...search);
}
+ static log_shortstat(repoPath: string, sha?: string) {
+ const params = [`log`, `--shortstat`, `--oneline`];
+ if (sha) {
+ params.push(sha);
+ }
+ return gitCommand({ cwd: repoPath }, ...params);
+ }
+
static async ls_files(repoPath: string, fileName: string): Promise {
try {
return await gitCommand({ cwd: repoPath, overrideErrorHandling: true }, 'ls-files', fileName);
diff --git a/src/gitService.ts b/src/gitService.ts
index b64cdbf..41b3598 100644
--- a/src/gitService.ts
+++ b/src/gitService.ts
@@ -86,6 +86,11 @@ export class GitService extends Disposable {
return this._onDidChangeGitCache.event;
}
+ private _onDidChangeFileSystem = new EventEmitter();
+ get onDidChangeFileSystem(): Event {
+ return this._onDidChangeFileSystem.event;
+ }
+
private _onDidChangeRepo = new EventEmitter();
get onDidChangeRepo(): Event {
return this._onDidChangeRepo.event;
@@ -121,14 +126,16 @@ export class GitService extends Disposable {
}
dispose() {
+ this.stopWatchingFileSystem();
+
+ this._repoWatcher && this._repoWatcher.dispose();
+ this._repoWatcher = undefined;
+
this._disposable && this._disposable.dispose();
this._cacheDisposable && this._cacheDisposable.dispose();
this._cacheDisposable = undefined;
- this._repoWatcher && this._repoWatcher.dispose();
- this._repoWatcher = undefined;
-
this._gitCache.clear();
this._remotesCache.clear();
this._uriCache.clear();
@@ -602,7 +609,8 @@ export class GitService extends Disposable {
}
async getChangedFilesCount(repoPath: string, sha?: string): Promise {
- return GitDiffParser.parseShortStat(await Git.diff_shortstat(repoPath, sha));
+ const data = await Git.diff_shortstat(repoPath, sha);
+ return GitDiffParser.parseShortStat(data);
}
async getConfig(key: string, repoPath?: string): Promise {
@@ -1034,6 +1042,33 @@ export class GitService extends Disposable {
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;
+
+ debouncedFn(uri);
+ };
+
+ const watcher = workspace.createFileSystemWatcher(`**`);
+ this._fsWatcherDisposable = Disposable.from(
+ watcher,
+ watcher.onDidChange(fn),
+ watcher.onDidCreate(fn),
+ watcher.onDidDelete(fn)
+ );
+ }
+
+ stopWatchingFileSystem() {
+ this._fsWatcherDisposable && this._fsWatcherDisposable.dispose();
+ this._fsWatcherDisposable = undefined;
+ }
+
stashApply(repoPath: string, stashName: string, deleteAfter: boolean = false) {
Logger.log(`stashApply('${repoPath}', ${stashName}, ${deleteAfter})`);
diff --git a/src/views/gitExplorer.ts b/src/views/gitExplorer.ts
index 8e83ba0..5154f71 100644
--- a/src/views/gitExplorer.ts
+++ b/src/views/gitExplorer.ts
@@ -81,14 +81,14 @@ export class GitExplorer implements TreeDataProvider {
}
private getRootNode(editor?: TextEditor): ExplorerNode | undefined {
- const uri = new GitUri(Uri.file(this.git.repoPath), { repoPath: this.git.repoPath, fileName: this.git.repoPath });
-
switch (this._view) {
- case GitExplorerView.History: return this.getHistoryNode(editor || window.activeTextEditor);
- case GitExplorerView.Repository: return new RepositoryNode(uri, this.context, this.git);
- }
+ case GitExplorerView.History:
+ return this.getHistoryNode(editor || window.activeTextEditor);
- return undefined;
+ default:
+ const uri = new GitUri(Uri.file(this.git.repoPath), { repoPath: this.git.repoPath, fileName: this.git.repoPath });
+ return new RepositoryNode(uri, this.context, this.git);
+ }
}
private getHistoryNode(editor: TextEditor | undefined): ExplorerNode | undefined {
@@ -114,11 +114,7 @@ export class GitExplorer implements TreeDataProvider {
private onConfigurationChanged() {
const cfg = workspace.getConfiguration().get(ExtensionKey)!;
- let changed = false;
- if (!Objects.areEquivalent(cfg.gitExplorer, this._config && this._config.gitExplorer) ||
- !Objects.areEquivalent(cfg.insiders, this._config && this._config.insiders)) {
- changed = true;
- }
+ const changed = !Objects.areEquivalent(cfg.gitExplorer, this._config && this._config.gitExplorer);
this._config = cfg;
@@ -165,6 +161,10 @@ export class GitExplorer implements TreeDataProvider {
this._view = view;
setCommandContext(CommandContext.GitExplorerView, this._view);
+
+ if (view !== GitExplorerView.Repository) {
+ this.git.stopWatchingFileSystem();
+ }
}
switchTo(view: GitExplorerView) {
diff --git a/src/views/statusFilesNode.ts b/src/views/statusFilesNode.ts
index 6a7d627..6de7780 100644
--- a/src/views/statusFilesNode.ts
+++ b/src/views/statusFilesNode.ts
@@ -38,7 +38,7 @@ export class StatusFilesNode extends ExplorerNode {
statuses = [];
}
- if (this.status.files.length !== 0 && this.git.config.insiders) {
+ if (this.status.files.length !== 0 && this.includeWorkingTree) {
statuses.splice(0, 0, ...this.status.files.map(s => {
return { ...s, commit: new GitLogCommit('file', this.status.repoPath, GitService.uncommittedSha, s.fileName, 'You', new Date(), '', s.status, [s], s.originalFileName, 'HEAD', s.fileName) } as IGitStatusFileWithCommit;
}));
@@ -61,12 +61,13 @@ export class StatusFilesNode extends ExplorerNode {
}
async getTreeItem(): Promise {
- // Start with any untracked files, since they won't be included in the next call
- let files = (this.status.files === undefined) ? 0 : this.status.files.filter(s => s.status === '?').length;
+ let files = (this.status.files !== undefined && this.includeWorkingTree) ? this.status.files.length : 0;
- const stats = await this.git.getChangedFilesCount(this.status.repoPath, this.git.config.insiders ? this.status.upstream : this.range);
- if (stats !== undefined) {
- files += stats.files;
+ if (this.status.upstream !== undefined) {
+ const stats = await this.git.getChangedFilesCount(this.status.repoPath, `${this.status.upstream}...`);
+ if (stats !== undefined) {
+ files += stats.files;
+ }
}
const label = `${files} file${files > 1 ? 's' : ''} changed`; // ${this.status.upstream === undefined ? '' : ` (ahead of ${this.status.upstream})`}`;
@@ -79,4 +80,9 @@ export class StatusFilesNode extends ExplorerNode {
return item;
}
+
+ private get includeWorkingTree(): boolean {
+ return this.git.config.gitExplorer.includeWorkingTree;
+ }
+
}
\ No newline at end of file
diff --git a/src/views/statusNode.ts b/src/views/statusNode.ts
index 703c87f..10209ac 100644
--- a/src/views/statusNode.ts
+++ b/src/views/statusNode.ts
@@ -1,9 +1,11 @@
-import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode';
+import { commands, Disposable, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
import { ExplorerNode, ResourceType } from './explorerNode';
-import { GitService, GitUri } from '../gitService';
+import { GitService, GitStatus, GitUri } from '../gitService';
import { StatusFilesNode } from './statusFilesNode';
import { StatusUpstreamNode } from './statusUpstreamNode';
+let _eventDisposable: Disposable | undefined;
+
export class StatusNode extends ExplorerNode {
readonly resourceType: ResourceType = 'gitlens:status';
@@ -30,8 +32,8 @@ export class StatusNode extends ExplorerNode {
children.push(new StatusUpstreamNode(status, 'ahead', this.context, this.git));
}
- if (status.state.ahead || (status.files.length !== 0 && this.git.config.insiders)) {
- const range = status.state.ahead
+ if (status.state.ahead || (status.files.length !== 0 && this.includeWorkingTree)) {
+ const range = status.upstream
? `${status.upstream}..${status.branch}`
: undefined;
children.push(new StatusFilesNode(status, range, this.context, this.git));
@@ -40,12 +42,26 @@ export class StatusNode extends ExplorerNode {
return children;
}
- async getTreeItem(): Promise {
+ private _status: GitStatus | undefined;
+
+ async getTreeItem(): Promise < TreeItem > {
const status = await this.git.getStatusForRepo(this.uri.repoPath!);
if (status === undefined) return new TreeItem('No repo status');
+ if (_eventDisposable !== undefined) {
+ _eventDisposable.dispose();
+ _eventDisposable = undefined;
+ }
+
+ if (this.includeWorkingTree) {
+ this._status = status;
+
+ _eventDisposable = this.git.onDidChangeFileSystem(this.onFileSystemChanged, this);
+ this.git.startWatchingFileSystem();
+ }
+
let hasChildren = false;
- const hasWorkingChanges = status.files.length !== 0 && this.git.config.insiders;
+ const hasWorkingChanges = status.files.length !== 0 && this.includeWorkingTree;
let label = '';
let iconSuffix = '';
if (status.upstream) {
@@ -68,7 +84,7 @@ export class StatusNode extends ExplorerNode {
}
}
else {
- label = `${status.branch} ${hasWorkingChanges ? 'has uncommitted changes' : 'is clean'}`;
+ label = `${status.branch} ${hasWorkingChanges ? 'has uncommitted changes' : this.includeWorkingTree ? 'has no changes' : 'has nothing to commit'}`;
}
const item = new TreeItem(label, (hasChildren || hasWorkingChanges) ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.None);
@@ -81,4 +97,21 @@ export class StatusNode extends ExplorerNode {
return item;
}
+
+ private get includeWorkingTree(): boolean {
+ return this.git.config.gitExplorer.includeWorkingTree;
+ }
+
+ private async onFileSystemChanged(uri: Uri) {
+ const status = await this.git.getStatusForRepo(this.uri.repoPath!);
+
+ // If we haven't changed from having some working changes to none or vice versa then just refresh the node
+ // This is because of https://github.com/Microsoft/vscode/issues/34789
+ if (this._status !== undefined && status !== undefined &&
+ ((this._status.files.length === status.files.length) || (this._status.files.length > 0 && status.files.length > 0))) {
+ commands.executeCommand('gitlens.gitExplorer.refreshNode', this);
+ }
+
+ commands.executeCommand('gitlens.gitExplorer.refresh');
+ }
}
\ No newline at end of file