diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9044b4e..99034e7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,11 +6,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
## [Unreleased]
-## [5.2.0-beta]
+## [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 file-based view of all the changed files in the working tree (enabled via `"gitlens.insiders": true`) and/or files in commits that haven't yet been pushed upstream
+ - 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
### Changed
- Changes the sorting (now alphabetical) of files shown in the `GitLens` custom view
diff --git a/README.md b/README.md
index 08018cc..5b2f479 100644
--- a/README.md
+++ b/README.md
@@ -126,13 +126,17 @@ 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 upstream tracking branch (if available), and its upstream status (if available)
+ - 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 indicator dots on the repository icon which denote the following:
- `None` - up-to-date with the upstream
- `Green` - ahead of the upstream
- `Red` - behind the upstream
- `Yellow` - both ahead of and behind the upstream
- - Provides additional nodes, if the current branch is not synchronized with the upstream, to quickly see and explore the specific commits ahead and/or behind the upstream
+ - Provides additional upstream status nodes, if the current branch is tracking a remote branch and
+ - 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
- Provides a context menu with `Open Repository in Remote`, and `Refresh` commands
- `Branches` node — provides a list of the local branches
@@ -357,6 +361,7 @@ GitLens is highly customizable and provides many configuration settings to allow
|`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
|`gitlens.gitExplorer.stashFormat`|Specifies the format of stashed 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.stashFileFormat`|Specifies the format of a stashed file in the `GitLens` custom view Available tokens ${file} - file name ${filePath} - file name and path ${path} - file path
+|`gitlens.gitExplorer.statusFileFormat`|Specifies the format of the status of a working or committed file in the `GitLens` custom view Available tokens ${file} - file name ${filePath} - file name and path ${path} - file path ${working} - optional indicator if the file is uncommitted
### Custom Remotes Settings
diff --git a/images/dark/icon-diff.svg b/images/dark/icon-diff.svg
new file mode 100644
index 0000000..055180d
--- /dev/null
+++ b/images/dark/icon-diff.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/images/light/icon-diff.svg b/images/light/icon-diff.svg
new file mode 100644
index 0000000..384d6c2
--- /dev/null
+++ b/images/light/icon-diff.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/package.json b/package.json
index 384916d..2e807e0 100644
--- a/package.json
+++ b/package.json
@@ -443,6 +443,11 @@
"default": "${filePath}",
"description": "Specifies the format of a stashed file in the `GitLens` custom view\nAvailable tokens\n ${file} - file name\n ${filePath} - file name and path\n ${path} - file path"
},
+ "gitlens.gitExplorer.statusFileFormat": {
+ "type": "string",
+ "default": "${working}${filePath}",
+ "description": "Specifies the format of the status of a working or committed file in the `GitLens` custom view\nAvailable tokens\n ${file} - file name\n ${filePath} - file name and path\n ${path} - file path\n ${working} - optional indicator if the file is uncommitted"
+ },
"gitlens.gitExplorer.view": {
"type": "string",
"default": "repository",
diff --git a/src/configuration.ts b/src/configuration.ts
index 935cc02..0e2cf2d 100644
--- a/src/configuration.ts
+++ b/src/configuration.ts
@@ -324,6 +324,7 @@ export interface IConfig {
commitFileFormat: string;
stashFormat: string;
stashFileFormat: string;
+ statusFileFormat: string;
// dateFormat: string | null;
};
diff --git a/src/constants.ts b/src/constants.ts
index 6e7cefe..9f433be 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -81,10 +81,12 @@ export type GlyphChars = '\u21a9' |
'\u21e8' |
'\u2191' |
'\u2197' |
+ '\u2217' |
'\u2713' |
'\u2014' |
'\u2022' |
'\u2026' |
+ '\u270E' |
'\u00a0' |
'\u200b';
export const GlyphChars = {
@@ -97,10 +99,12 @@ export const GlyphChars = {
ArrowRightHollow: '\u21e8' as GlyphChars,
ArrowUp: '\u2191' as GlyphChars,
ArrowUpRight: '\u2197' as GlyphChars,
+ Asterisk: '\u2217' as GlyphChars,
Check: '\u2713' as GlyphChars,
Dash: '\u2014' as GlyphChars,
Dot: '\u2022' as GlyphChars,
Ellipsis: '\u2026' as GlyphChars,
+ Pensil: '\u270E' as GlyphChars,
Space: '\u00a0' as GlyphChars,
ZeroWidthSpace: '\u200b' as GlyphChars
};
diff --git a/src/git/formatters/status.ts b/src/git/formatters/status.ts
index 4cd0393..f38f6c3 100644
--- a/src/git/formatters/status.ts
+++ b/src/git/formatters/status.ts
@@ -1,7 +1,8 @@
'use strict';
import { Strings } from '../../system';
+import { GlyphChars } from '../../constants';
import { Formatter, IFormatOptions } from './formatter';
-import { GitStatusFile, IGitStatusFile } from '../models/status';
+import { GitStatusFile, IGitStatusFile, IGitStatusFileWithCommit } from '../models/status';
import * as path from 'path';
export interface IStatusFormatOptions extends IFormatOptions {
@@ -29,6 +30,11 @@ export class StatusFileFormatter extends Formatter {
private onConfigurationChanged() {
const cfg = workspace.getConfiguration().get(ExtensionKey)!;
- if (!Objects.areEquivalent(cfg.gitExplorer, this._config && this._config.gitExplorer)) {
+ if (!Objects.areEquivalent(cfg.gitExplorer, this._config && this._config.gitExplorer) ||
+ !Objects.areEquivalent(cfg.insiders, this._config && this._config.insiders)) {
setTimeout(() => {
this._root = this.getRootNode(window.activeTextEditor);
this.refresh();
diff --git a/src/views/statusFileCommitsNode.ts b/src/views/statusFileCommitsNode.ts
index 0b0506c..49c3f6c 100644
--- a/src/views/statusFileCommitsNode.ts
+++ b/src/views/statusFileCommitsNode.ts
@@ -3,7 +3,7 @@ import { Command, ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } fr
import { Commands, DiffWithPreviousCommandArgs } from '../commands';
import { CommitFileNode, CommitFileNodeDisplayAs } from './commitFileNode';
import { ExplorerNode, ResourceType } from './explorerNode';
-import { getGitStatusIcon, GitBranch, GitLogCommit, GitService, GitUri, IGitStatusFile, StatusFileFormatter } from '../gitService';
+import { getGitStatusIcon, GitBranch, GitLogCommit, GitService, GitUri, IGitStatusFile, IGitStatusFileWithCommit, StatusFileFormatter } from '../gitService';
import * as path from 'path';
export class StatusFileCommitsNode extends ExplorerNode {
@@ -26,7 +26,7 @@ export class StatusFileCommitsNode extends ExplorerNode {
}
async getTreeItem(): Promise {
- const item = new TreeItem(StatusFileFormatter.fromTemplate(this.git.config.gitExplorer.commitFileFormat, this.status), TreeItemCollapsibleState.Collapsed);
+ const item = new TreeItem(this.label, TreeItemCollapsibleState.Collapsed);
item.contextValue = this.resourceType;
const icon = getGitStatusIcon(this.status.status);
@@ -41,9 +41,20 @@ export class StatusFileCommitsNode extends ExplorerNode {
item.command = this.getCommand();
}
+ // Only cache the label for a single refresh
+ this._label = undefined;
+
return item;
}
+ private _label: string | undefined;
+ get label() {
+ if (this._label === undefined) {
+ this._label = StatusFileFormatter.fromTemplate(this.git.config.gitExplorer.statusFileFormat, { ...this.status, commit: this.commit } as IGitStatusFileWithCommit);
+ }
+ return this._label;
+ }
+
get commit() {
return this.commits[0];
}
diff --git a/src/views/statusFilesNode.ts b/src/views/statusFilesNode.ts
index baf73c5..a665801 100644
--- a/src/views/statusFilesNode.ts
+++ b/src/views/statusFilesNode.ts
@@ -2,13 +2,9 @@
import { Arrays, Iterables, Objects } from '../system';
import { ExtensionContext, TreeItem, TreeItemCollapsibleState, Uri } from 'vscode';
import { ExplorerNode, ResourceType, ShowAllNode } from './explorerNode';
-import { GitBranch, GitLog, GitLogCommit, GitService, GitStatus, GitUri, IGitStatusFile } from '../gitService';
+import { GitBranch, GitLog, GitLogCommit, GitService, GitStatus, GitUri, IGitStatusFileWithCommit } from '../gitService';
import { StatusFileCommitsNode } from './statusFileCommitsNode';
-interface IGitStatusFileWithCommit extends IGitStatusFile {
- commit: GitLogCommit;
-}
-
export class StatusFilesNode extends ExplorerNode {
readonly resourceType: ResourceType = 'gitlens:status-files';
@@ -42,7 +38,7 @@ export class StatusFilesNode extends ExplorerNode {
statuses = [];
}
- if (this.status.files.length !== 0) {
+ if (this.status.files.length !== 0 && this.git.config.insiders) {
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;
}));
@@ -56,6 +52,8 @@ export class StatusFilesNode extends ExplorerNode {
statuses => new StatusFileCommitsNode(this.uri.repoPath!, statuses[statuses.length - 1], statuses.map(s => s.commit), this.context, this.git, this.branch))
];
+ children.sort((a: StatusFileCommitsNode, b: StatusFileCommitsNode) => (a.commit.isUncommitted ? -1 : 1) - (b.commit.isUncommitted ? -1 : 1) || a.label!.localeCompare(b.label!));
+
if (log !== undefined && log.truncated) {
children.push(new ShowAllNode('Show All Changes', this, this.context));
}
@@ -63,8 +61,17 @@ export class StatusFilesNode extends ExplorerNode {
}
async getTreeItem(): Promise {
- const item = new TreeItem(`Changed Files`, TreeItemCollapsibleState.Collapsed);
+ const stats = await this.git.getChangedFilesCount(this.status.repoPath, this.git.config.insiders ? this.status.upstream : this.range);
+ const files = (stats === undefined) ? 0 : stats.files;
+
+ const label = `${files} file${files > 1 ? 's' : ''} changed`; // ${this.status.upstream === undefined ? '' : ` (ahead of ${this.status.upstream})`}`;
+ const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed);
item.contextValue = this.resourceType;
+ item.iconPath = {
+ dark: this.context.asAbsolutePath(`images/dark/icon-diff.svg`),
+ light: this.context.asAbsolutePath(`images/light/icon-diff.svg`)
+ };
+
return item;
}
}
\ No newline at end of file
diff --git a/src/views/statusNode.ts b/src/views/statusNode.ts
index 4599f46..703c87f 100644
--- a/src/views/statusNode.ts
+++ b/src/views/statusNode.ts
@@ -30,11 +30,11 @@ export class StatusNode extends ExplorerNode {
children.push(new StatusUpstreamNode(status, 'ahead', this.context, this.git));
}
- if (status.files.length !== 0 || status.state.ahead && this.git.config.insiders) {
+ if (status.state.ahead || (status.files.length !== 0 && this.git.config.insiders)) {
const range = status.state.ahead
? `${status.upstream}..${status.branch}`
: undefined;
- children.splice(0, 0, new StatusFilesNode(status, range, this.context, this.git));
+ children.push(new StatusFilesNode(status, range, this.context, this.git));
}
return children;
@@ -45,14 +45,16 @@ export class StatusNode extends ExplorerNode {
if (status === undefined) return new TreeItem('No repo status');
let hasChildren = false;
+ const hasWorkingChanges = status.files.length !== 0 && this.git.config.insiders;
let label = '';
let iconSuffix = '';
if (status.upstream) {
if (!status.state.ahead && !status.state.behind) {
- label = `${status.branch} is up-to-date with ${status.upstream}`;
+ label = `${status.branch}${hasWorkingChanges ? ' has uncommitted changes and' : ''} is up-to-date with ${status.upstream}`;
}
else {
- label = `${status.branch} is not up-to-date with ${status.upstream}`;
+ label = `${status.branch}${hasWorkingChanges ? ' has uncommitted changes and' : ''} is not up-to-date with ${status.upstream}`;
+
hasChildren = true;
if (status.state.ahead && status.state.behind) {
iconSuffix = '-yellow';
@@ -66,14 +68,10 @@ export class StatusNode extends ExplorerNode {
}
}
else {
- label = `${status.branch} is up-to-date`;
+ label = `${status.branch} ${hasWorkingChanges ? 'has uncommitted changes' : 'is clean'}`;
}
- if (this.git.config.insiders) {
- hasChildren = hasChildren || status.files.length !== 0;
- }
-
- const item = new TreeItem(label, hasChildren ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.None);
+ const item = new TreeItem(label, (hasChildren || hasWorkingChanges) ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.None);
item.contextValue = this.resourceType;
item.iconPath = {
diff --git a/src/views/statusUpstreamNode.ts b/src/views/statusUpstreamNode.ts
index 3529b51..a94303a 100644
--- a/src/views/statusUpstreamNode.ts
+++ b/src/views/statusUpstreamNode.ts
@@ -43,8 +43,8 @@ export class StatusUpstreamNode extends ExplorerNode {
async getTreeItem(): Promise {
const label = this.direction === 'ahead'
- ? `${this.status.state.ahead} commit${this.status.state.ahead > 1 ? 's' : ''} ahead (local changes)` // of ${this.status.upstream}`
- : `${this.status.state.behind} commit${this.status.state.behind > 1 ? 's' : ''} behind (remote changes)`; // ${this.status.upstream}`;
+ ? `${this.status.state.ahead} commit${this.status.state.ahead > 1 ? 's' : ''} (ahead of ${this.status.upstream})`
+ : `${this.status.state.behind} commit${this.status.state.behind > 1 ? 's' : ''} (behind ${this.status.upstream})`;
const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed);
item.contextValue = this.resourceType;