From f7df845dfea2f3dd550ec7192761a107e7dfa04b Mon Sep 17 00:00:00 2001 From: Eric Amodio Date: Wed, 20 Sep 2017 01:36:21 -0400 Subject: [PATCH] Adds working tree status to custom view (insiders) Hides working changed files behind insiders flag Unhides Changed Files node from behind insiders flag Adds changed file count to Changed Files node label Adds icon to Changed Files node Adds upstream branch to upstream status nodes Sorts files in the Changed Files node --- CHANGELOG.md | 7 +++++-- README.md | 9 +++++++-- images/dark/icon-diff.svg | 4 ++++ images/light/icon-diff.svg | 4 ++++ package.json | 5 +++++ src/configuration.ts | 1 + src/constants.ts | 4 ++++ src/git/formatters/status.ts | 8 +++++++- src/git/models/status.ts | 5 +++++ src/views/gitExplorer.ts | 3 ++- src/views/statusFileCommitsNode.ts | 15 +++++++++++++-- src/views/statusFilesNode.ts | 21 ++++++++++++++------- src/views/statusNode.ts | 18 ++++++++---------- src/views/statusUpstreamNode.ts | 4 ++-- 14 files changed, 81 insertions(+), 27 deletions(-) create mode 100644 images/dark/icon-diff.svg create mode 100644 images/light/icon-diff.svg 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, ![GitLens Repository view](https://raw.githubusercontent.com/eamodio/vscode-gitlens/master/images/screenshot-git-custom-view-repository.png) - `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;