diff --git a/CHANGELOG.md b/CHANGELOG.md index 20fa788..dec8dc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p ## [Unreleased] ### Added +- Adds new branch history node under the **Repository Status** node in the *GitLens* explorer - Adds GitLab and Visual Studio Team Services icons to the remote nodes in the *GitLens* explorer — thanks to [PR #421](https://github.com/eamodio/vscode-gitlens/pull/421) by Maxim Pekurin ([@pmaxim25](https://github.com/pmaxim25)) ### Fixed diff --git a/README.md b/README.md index 6bbe667..eace2d0 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,8 @@ The repository view provides a full Git repository explorer, which has the follo - A context menu provides *Open Repository in Remote*, and *Refresh* commands - **Changed Files** — lists all the "working" changes - Expands to a file-based view of all changed files in the working tree ([optionally](#gitlens-explorer-settings "Jump to the GitLens explorer settings")) and/or all files in all commits ahead of the upstream + - **History (current-branch)** — lists the revision (commit) history of the current branch + - See the *Branches expand* section under **Branches** below for more details - **Branches** — lists the local branches - Indicates which branch is the current branch and [optionally](#gitlens-explorer-settings "Jump to the GitLens explorer settings") shows the remote tracking branch diff --git a/src/views/branchNode.ts b/src/views/branchNode.ts index 2be0d21..b36174e 100644 --- a/src/views/branchNode.ts +++ b/src/views/branchNode.ts @@ -16,7 +16,7 @@ export class BranchNode extends ExplorerRefNode { constructor( public readonly branch: GitBranch, uri: GitUri, - private readonly explorer: GitExplorer + protected readonly explorer: GitExplorer ) { super(uri); } @@ -32,6 +32,10 @@ export class BranchNode extends ExplorerRefNode { return GitBranch.isValid(branchName) && !this.current ? this.branch.getBasename() : branchName; } + get markCurrent(): boolean { + return true; + } + get ref(): string { return this.branch.name; } @@ -79,7 +83,7 @@ export class BranchNode extends ExplorerRefNode { } } - const item = new TreeItem(`${this.current ? `${GlyphChars.Check} ${GlyphChars.Space}` : ''}${name}`, TreeItemCollapsibleState.Collapsed); + const item = new TreeItem(`${this.markCurrent && this.current ? `${GlyphChars.Check} ${GlyphChars.Space}` : ''}${name}`, TreeItemCollapsibleState.Collapsed); item.tooltip = tooltip; if (this.branch.remote) { diff --git a/src/views/branchesNode.ts b/src/views/branchesNode.ts index 19e44ff..9896bf0 100644 --- a/src/views/branchesNode.ts +++ b/src/views/branchesNode.ts @@ -11,64 +11,63 @@ import { GitUri, Repository } from '../gitService'; export class BranchesNode extends ExplorerNode { - constructor( - uri: GitUri, - private readonly repo: Repository, - private readonly explorer: GitExplorer, - private readonly active: boolean = false - ) { - super(uri); - } - - get id(): string { - return `gitlens:repository(${this.repo.path})${this.active ? ':active' : ''}:branches`; - } + constructor( + uri: GitUri, + private readonly repo: Repository, + private readonly explorer: GitExplorer, + private readonly active: boolean = false + ) { + super(uri); + } - async getChildren(): Promise { - const branches = await this.repo.getBranches(); - if (branches === undefined) return []; + get id(): string { + return `gitlens:repository(${this.repo.path})${this.active ? ':active' : ''}:branches`; + } - branches.sort((a, b) => (a.current ? -1 : 1) - (b.current ? -1 : 1) || a.name.localeCompare(b.name)); + async getChildren(): Promise { + const branches = await this.repo.getBranches(); + if (branches === undefined) return []; - // filter local branches - const branchNodes = [...Iterables.filterMap(branches, b => b.remote ? undefined : new BranchNode(b, this.uri, this.explorer))]; - if (this.explorer.config.branches.layout === ExplorerBranchesLayout.List) return branchNodes; + branches.sort((a, b) => (a.current ? -1 : 1) - (b.current ? -1 : 1) || a.name.localeCompare(b.name)); - // Take out the current branch, since that should always be first and un-nested - const current = (branchNodes.length > 0 && branchNodes[0].current) - ? branchNodes.splice(0, 1)[0] - : undefined; + // filter local branches + const branchNodes = [...Iterables.filterMap(branches, b => b.remote ? undefined : new BranchNode(b, this.uri, this.explorer))]; + if (this.explorer.config.branches.layout === ExplorerBranchesLayout.List) return branchNodes; - const hierarchy = Arrays.makeHierarchical(branchNodes, - n => n.branch.isValid() ? n.branch.getName().split('/') : [n.branch.name], - (...paths: string[]) => paths.join('/'), - this.explorer.config.files.compact); + // Take out the current branch, since that should always be first and un-nested + const current = (branchNodes.length > 0 && branchNodes[0].current) + ? branchNodes.splice(0, 1)[0] + : undefined; - const root = new BranchOrTagFolderNode(this.repo.path, '', undefined, hierarchy, this.explorer); - const children = await root.getChildren() as (BranchOrTagFolderNode | BranchNode)[]; + const hierarchy = Arrays.makeHierarchical(branchNodes, + n => n.branch.isValid() ? n.branch.getName().split('/') : [n.branch.name], + (...paths: string[]) => paths.join('/'), + this.explorer.config.files.compact); - // If we found a current branch, insert it at the start - if (current !== undefined) { - children.splice(0, 0, current); - } + const root = new BranchOrTagFolderNode(this.repo.path, '', undefined, hierarchy, this.explorer); + const children = await root.getChildren() as (BranchOrTagFolderNode | BranchNode)[]; - return children; + // If we found a current branch, insert it at the start + if (current !== undefined) { + children.splice(0, 0, current); } - async getTreeItem(): Promise { - // HACK: Until https://github.com/Microsoft/vscode/issues/30918 is fixed - const item = new TreeItem(`Branches`, this.active ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.Collapsed); + return children; + } + + async getTreeItem(): Promise { + const item = new TreeItem(`Branches`, TreeItemCollapsibleState.Collapsed); - const remotes = await this.repo.getRemotes(); - item.contextValue = (remotes !== undefined && remotes.length > 0) - ? ResourceType.BranchesWithRemotes - : ResourceType.Branches; + const remotes = await this.repo.getRemotes(); + item.contextValue = (remotes !== undefined && remotes.length > 0) + ? ResourceType.BranchesWithRemotes + : ResourceType.Branches; - item.iconPath = { - dark: Container.context.asAbsolutePath('images/dark/icon-branch.svg'), - light: Container.context.asAbsolutePath('images/light/icon-branch.svg') - }; + item.iconPath = { + dark: Container.context.asAbsolutePath('images/dark/icon-branch.svg'), + light: Container.context.asAbsolutePath('images/light/icon-branch.svg') + }; - return item; - } + return item; } +} diff --git a/src/views/statusNode.ts b/src/views/statusNode.ts index 13a4e56..7e8b9ad 100644 --- a/src/views/statusNode.ts +++ b/src/views/statusNode.ts @@ -1,9 +1,10 @@ import { Disposable, TreeItem, TreeItemCollapsibleState } from 'vscode'; +import { BranchNode } from './branchNode'; import { GlyphChars } from '../constants'; import { Container } from '../container'; import { ExplorerNode, ResourceType } from './explorerNode'; import { GitExplorer } from './gitExplorer'; -import { GitUri, Repository, RepositoryFileSystemChangeEvent } from '../gitService'; +import { GitBranch, GitUri, Repository, RepositoryFileSystemChangeEvent } from '../gitService'; import { StatusFilesNode } from './statusFilesNode'; import { StatusUpstreamNode } from './statusUpstreamNode'; @@ -25,24 +26,33 @@ export class StatusNode extends ExplorerNode { async getChildren(): Promise { this.resetChildren(); + this.children = []; + const status = await this.repo.getStatus(); - if (status === undefined) return []; + if (status !== undefined) { + if (status.state.behind) { + this.children.push(new StatusUpstreamNode(status, 'behind', this.explorer, this.active)); + } - this.children = []; + if (status.state.ahead) { + this.children.push(new StatusUpstreamNode(status, 'ahead', this.explorer, this.active)); + } - if (status.state.behind) { - this.children.push(new StatusUpstreamNode(status, 'behind', this.explorer, this.active)); + if (status.state.ahead || (status.files.length !== 0 && this.includeWorkingTree)) { + const range = status.upstream + ? `${status.upstream}..${status.branch}` + : undefined; + this.children.push(new StatusFilesNode(status, range, this.explorer, this.active)); + } } - if (status.state.ahead) { - this.children.push(new StatusUpstreamNode(status, 'ahead', this.explorer, this.active)); - } + let branch = await this.repo.getBranch(); + if (branch !== undefined) { + if (status !== undefined) { + branch = new GitBranch(branch.repoPath, branch.name, branch.current, branch.sha, branch.tracking, status.state.ahead, status.state.behind); + } - if (status.state.ahead || (status.files.length !== 0 && this.includeWorkingTree)) { - const range = status.upstream - ? `${status.upstream}..${status.branch}` - : undefined; - this.children.push(new StatusFilesNode(status, range, this.explorer, this.active)); + this.children.push(new StatusBranchNode(branch, this.uri, this.explorer)); } return this.children; @@ -91,7 +101,6 @@ export class StatusNode extends ExplorerNode { } } - label = `${status.branch}${label === '' ? '' : ` ${GlyphChars.Space}${status.upstream ? GlyphChars.ArrowLeftRightLong : GlyphChars.Dash}${label}`}`; if (hasWorkingChanges) { tooltip += `\n\nHas uncommitted changes${status.getDiffStatus({ expand: true, prefix: `\n`, separator: '\n' })}`; } @@ -102,7 +111,7 @@ export class StatusNode extends ExplorerNode { state = this.active ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.Collapsed; } else { - state = TreeItemCollapsibleState.None; + state = TreeItemCollapsibleState.Collapsed; } const item = new TreeItem(`${status.branch}${label}`, state); @@ -133,4 +142,25 @@ export class StatusNode extends ExplorerNode { private async onFileSystemChanged(e: RepositoryFileSystemChangeEvent) { this.explorer.refreshNode(this); } -} \ No newline at end of file +} + +export class StatusBranchNode extends BranchNode { + + constructor( + branch: GitBranch, + uri: GitUri, + explorer: GitExplorer + ) { + super(branch, uri, explorer); + } + + get markCurrent() { + return false; + } + + async getTreeItem(): Promise { + const item = await super.getTreeItem(); + item.label = `History (${item.label})`; + return item; + } +}