Skip to content

Commit

Permalink
Adds detailed tracking status to all branches in the repos view
Browse files Browse the repository at this point in the history
  • Loading branch information
eamodio committed Dec 11, 2018
1 parent f471073 commit cdc2241
Show file tree
Hide file tree
Showing 10 changed files with 84 additions and 38 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Expand Up @@ -8,6 +8,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p

### Added

- Adds more detailed branch tracking status (if available) to the **Branches** list in the _Repositories_ view
- **\* Commits Behind** — quickly see and explore the specific commits behind the upstream (i.e. commits that haven't been pulled)
- Only provided if the current branch is tracking a remote branch and is behind it
- **\* Commits Ahead** — quickly see and explore the specific commits ahead of the upstream (i.e. commits that haven't been pushed)
- Only provided if the current branch is tracking a remote branch and is ahead of it
- Adds control over the contributed menu commands to the Source Control side bar to the GitLens interactive settings editor (via the `gitlens.menus` setting)
- Adds Git extended regex support to commit searches

Expand Down
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -329,6 +329,10 @@ The repositories view provides the following features,
- An inline toolbar provides quick access to the _Checkout_, _Compare with Remote_ (if available), _Compare with HEAD_ (`alt-click` for _Compare with Working Tree_), and _Open Branch on Remote_ (if available) commands
- A context menu provides access to more common branch commands
- Each branch expands to list its revision (commit) history
- **\* Commits Behind** — quickly see and explore the specific commits behind the upstream (i.e. commits that haven't been pulled)
- Only provided if the current branch is tracking a remote branch and is behind it
- **\* Commits Ahead** — quickly see and explore the specific commits ahead of the upstream (i.e. commits that haven't been pushed)
- Only provided if the current branch is tracking a remote branch and is ahead of it
- An inline toolbar provides quick access to the _Compare with HEAD_ (`alt-click` for _Compare with Working Tree_), _Copy Commit ID to Clipboard_ (`alt-click` for _Copy Commit Message to Clipboard_), and _Open Commit on Remote_ (if available) commands
- A context menu provides access to more common revision (commit) commands
- Each revision (commit) expands to list its set of changed files, complete with status indicators for adds, changes, renames, and deletes
Expand Down
10 changes: 6 additions & 4 deletions src/git/models/branch.ts
Expand Up @@ -2,15 +2,17 @@
import { Git } from '../git';
import { GitStatus } from './status';

export interface GitTrackingState {
ahead: number;
behind: number;
}

export class GitBranch {
readonly detached: boolean;
readonly name: string;
readonly remote: boolean;
readonly tracking?: string;
readonly state: {
ahead: number;
behind: number;
};
readonly state: GitTrackingState;

constructor(
public readonly repoPath: string,
Expand Down
9 changes: 2 additions & 7 deletions src/git/models/status.ts
Expand Up @@ -3,14 +3,9 @@ import { Uri } from 'vscode';
import { GlyphChars } from '../../constants';
import { Strings } from '../../system';
import { GitUri } from '../gitUri';
import { GitBranch } from './branch';
import { GitBranch, GitTrackingState } from './branch';
import { GitFile, GitFileStatus } from './file';

export interface GitStatusUpstreamState {
ahead: number;
behind: number;
}

export class GitStatus {
readonly detached: boolean;

Expand All @@ -19,7 +14,7 @@ export class GitStatus {
public readonly branch: string,
public readonly sha: string,
public readonly files: GitStatusFile[],
public readonly state: GitStatusUpstreamState,
public readonly state: GitTrackingState,
public readonly upstream?: string
) {
this.detached = GitBranch.isDetached(branch);
Expand Down
2 changes: 1 addition & 1 deletion src/views/nodes.ts
Expand Up @@ -3,6 +3,7 @@
export * from './nodes/viewNode';
export * from './nodes/branchesNode';
export * from './nodes/branchNode';
export * from './nodes/branchTrackingStatusNode';
export * from './nodes/commitFileNode';
export * from './nodes/commitNode';
export * from './nodes/fileHistoryNode';
Expand All @@ -24,6 +25,5 @@ export * from './nodes/stashFileNode';
export * from './nodes/stashNode';
export * from './nodes/statusFileNode';
export * from './nodes/statusFilesNode';
export * from './nodes/statusUpstreamNode';
export * from './nodes/tagsNode';
export * from './nodes/tagNode';
33 changes: 27 additions & 6 deletions src/views/nodes/branchNode.ts
Expand Up @@ -6,6 +6,7 @@ import { Container } from '../../container';
import { GitBranch, GitUri } from '../../git/gitService';
import { Iterables } from '../../system';
import { RepositoriesView } from '../repositoriesView';
import { BranchTrackingStatusNode } from './branchTrackingStatusNode';
import { CommitNode } from './commitNode';
import { MessageNode, ShowMoreNode } from './common';
import { getBranchesAndTagTipsFn, insertDateMarkers } from './helpers';
Expand All @@ -22,15 +23,16 @@ export class BranchNode extends ViewRefNode<RepositoriesView> implements Pageabl
view: RepositoriesView,
parent: ViewNode,
public readonly branch: GitBranch,
private readonly _markCurrent: boolean = true
// Specifies that the node is shown as a root under the repository node
private readonly _root: boolean = false
) {
super(uri, view, parent);
}

get id(): string {
return `gitlens:repository(${this.branch.repoPath}):branch(${this.branch.name})${
return `gitlens:repository(${this.branch.repoPath}):${this._root ? 'root:' : ''}branch(${this.branch.name})${
this.branch.remote ? ':remote' : ''
}${this._markCurrent ? ':current' : ''}`;
}`;
}

get current(): boolean {
Expand All @@ -50,22 +52,40 @@ export class BranchNode extends ViewRefNode<RepositoriesView> implements Pageabl

async getChildren(): Promise<ViewNode[]> {
if (this._children === undefined) {
const children = [];
if (!this._root && this.branch.tracking) {
const status = {
ref: this.branch.ref,
repoPath: this.branch.repoPath,
state: this.branch.state,
upstream: this.branch.tracking
};

if (this.branch.state.behind) {
children.push(new BranchTrackingStatusNode(this.view, this, status, 'behind'));
}

if (this.branch.state.ahead) {
children.push(new BranchTrackingStatusNode(this.view, this, status, 'ahead'));
}
}

const log = await Container.git.getLog(this.uri.repoPath!, {
maxCount: this.maxCount || this.view.config.defaultItemLimit,
ref: this.ref
});
if (log === undefined) return [new MessageNode(this.view, this, 'No commits could be found.')];

const getBranchAndTagTips = await getBranchesAndTagTipsFn(this.uri.repoPath, this.branch.name);
const children = [
children.push(
...insertDateMarkers(
Iterables.map(
log.commits.values(),
c => new CommitNode(this.view, this, c, this.branch, getBranchAndTagTips)
),
this
)
];
);

if (log.truncated) {
children.push(new ShowMoreNode(this.view, this, 'Commits'));
Expand Down Expand Up @@ -104,7 +124,8 @@ export class BranchNode extends ViewRefNode<RepositoriesView> implements Pageabl
}

const item = new TreeItem(
`${this._markCurrent && this.current ? `${GlyphChars.Check} ${GlyphChars.Space}` : ''}${name}`,
// Hide the current branch checkmark when the node is displayed as a root under the repository node
`${!this._root && this.current ? `${GlyphChars.Check} ${GlyphChars.Space}` : ''}${name}`,
TreeItemCollapsibleState.Collapsed
);
item.id = this.id;
Expand Down
@@ -1,30 +1,40 @@
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { Container } from '../../container';
import { GitStatus, GitUri } from '../../git/gitService';
import { GitTrackingState, GitUri } from '../../git/gitService';
import { Iterables, Strings } from '../../system';
import { View } from '../viewBase';
import { CommitNode } from './commitNode';
import { ShowMoreNode } from './common';
import { insertDateMarkers } from './helpers';
import { RepositoryNode } from './repositoryNode';
import { PageableViewNode, ResourceType, ViewNode } from './viewNode';

export class StatusUpstreamNode extends ViewNode implements PageableViewNode {
export interface BranchTrackingStatus {
ref: string;
repoPath: string;
state: GitTrackingState;
upstream?: string;
}

export class BranchTrackingStatusNode extends ViewNode implements PageableViewNode {
readonly supportsPaging: boolean = true;
maxCount: number | undefined;

constructor(
view: View,
parent: RepositoryNode,
public readonly status: GitStatus,
public readonly direction: 'ahead' | 'behind'
parent: ViewNode,
public readonly status: BranchTrackingStatus,
public readonly direction: 'ahead' | 'behind',
// Specifies that the node is shown as a root under the repository node
private readonly _root: boolean = false
) {
super(GitUri.fromRepoPath(status.repoPath), view, parent);
}

get id(): string {
return `gitlens:repository(${this.status.repoPath}):status:upstream:${this.direction}`;
return `gitlens:repository(${this.status.repoPath}):${this._root ? 'root:' : ''}branch(${
this.status.ref
}):status:upstream:(${this.status.upstream}):${this.direction}`;
}

async getChildren(): Promise<ViewNode[]> {
Expand Down Expand Up @@ -77,7 +87,14 @@ export class StatusUpstreamNode extends ViewNode implements PageableViewNode {

const item = new TreeItem(label, TreeItemCollapsibleState.Collapsed);
item.id = this.id;
item.contextValue = ahead ? ResourceType.StatusAheadOfUpstream : ResourceType.StatusBehindUpstream;
if (this._root) {
item.contextValue = ahead ? ResourceType.StatusAheadOfUpstream : ResourceType.StatusBehindUpstream;
}
else {
item.contextValue = ahead
? ResourceType.BranchStatusAheadOfUpstream
: ResourceType.BranchStatusBehindUpstream;
}
item.tooltip = `${label}${ahead ? ' of ' : ''}${this.status.upstream}`;

const iconSuffix = ahead ? 'upload' : 'download';
Expand Down
8 changes: 4 additions & 4 deletions src/views/nodes/repositoryNode.ts
Expand Up @@ -15,11 +15,11 @@ import { Dates, debug, Functions, gate, log, Strings } from '../../system';
import { RepositoriesView } from '../repositoriesView';
import { BranchesNode } from './branchesNode';
import { BranchNode } from './branchNode';
import { BranchTrackingStatusNode } from './branchTrackingStatusNode';
import { MessageNode } from './common';
import { RemotesNode } from './remotesNode';
import { StashesNode } from './stashesNode';
import { StatusFilesNode } from './statusFilesNode';
import { StatusUpstreamNode } from './statusUpstreamNode';
import { TagsNode } from './tagsNode';
import { ResourceType, SubscribeableViewNode, ViewNode } from './viewNode';

Expand Down Expand Up @@ -59,14 +59,14 @@ export class RepositoryNode extends SubscribeableViewNode<RepositoriesView> {
status.state.behind,
status.detached
);
children.push(new BranchNode(this.uri, this.view, this, branch, false));
children.push(new BranchNode(this.uri, this.view, this, branch, true));

if (status.state.behind) {
children.push(new StatusUpstreamNode(this.view, this, status, 'behind'));
children.push(new BranchTrackingStatusNode(this.view, this, status, 'behind', true));
}

if (status.state.ahead) {
children.push(new StatusUpstreamNode(this.view, this, status, 'ahead'));
children.push(new BranchTrackingStatusNode(this.view, this, status, 'ahead', true));
}

if (status.state.ahead || (status.files.length !== 0 && this.includeWorkingTree)) {
Expand Down
4 changes: 3 additions & 1 deletion src/views/nodes/viewNode.ts
Expand Up @@ -9,9 +9,11 @@ export enum ResourceType {
ActiveFileHistory = 'gitlens:history:active:file',
ActiveLineHistory = 'gitlens:history:active:line',
Branch = 'gitlens:branch',
BranchWithTracking = 'gitlens:branch:tracking',
Branches = 'gitlens:branches',
BranchesWithRemotes = 'gitlens:branches:remotes',
BranchStatusAheadOfUpstream = 'gitlens:branch-status:upstream:ahead',
BranchStatusBehindUpstream = 'gitlens:branch-status:upstream:behind',
BranchWithTracking = 'gitlens:branch:tracking',
CurrentBranch = 'gitlens:branch:current',
CurrentBranchWithTracking = 'gitlens:branch:current:tracking',
RemoteBranch = 'gitlens:branch:remote',
Expand Down
14 changes: 7 additions & 7 deletions src/views/viewCommands.ts
Expand Up @@ -19,6 +19,7 @@ import { GitService, GitUri } from '../git/gitService';
import { Arrays } from '../system';
import {
BranchNode,
BranchTrackingStatusNode,
canDismissNode,
CommitFileNode,
CommitNode,
Expand All @@ -28,7 +29,6 @@ import {
StashFileNode,
StashNode,
StatusFileNode,
StatusUpstreamNode,
TagNode,
ViewNode,
ViewRefNode
Expand Down Expand Up @@ -134,17 +134,17 @@ export class ViewCommands implements Disposable {
return;
}

private pull(node: RepositoryNode | StatusUpstreamNode) {
if (node instanceof StatusUpstreamNode) {
private pull(node: RepositoryNode | BranchTrackingStatusNode) {
if (node instanceof BranchTrackingStatusNode) {
node = node.getParent() as RepositoryNode;
}
if (!(node instanceof RepositoryNode)) return;

return node.pull();
}

private push(node: RepositoryNode | StatusUpstreamNode, force?: boolean) {
if (node instanceof StatusUpstreamNode) {
private push(node: RepositoryNode | BranchTrackingStatusNode, force?: boolean) {
if (node instanceof BranchTrackingStatusNode) {
node = node.getParent() as RepositoryNode;
}
if (!(node instanceof RepositoryNode)) return;
Expand Down Expand Up @@ -502,13 +502,13 @@ export class ViewCommands implements Disposable {
this.sendTerminalCommand('rebase', `-i ${node.ref}`, node.repoPath);
}

terminalRebaseBranchToRemote(node: BranchNode | StatusUpstreamNode) {
terminalRebaseBranchToRemote(node: BranchNode | BranchTrackingStatusNode) {
if (node instanceof BranchNode) {
if (!node.branch.current || !node.branch.tracking) return;

this.sendTerminalCommand('rebase', `-i ${node.branch.tracking}`, node.repoPath);
}
else if (node instanceof StatusUpstreamNode) {
else if (node instanceof BranchTrackingStatusNode) {
this.sendTerminalCommand('rebase', `-i ${node.status.upstream}`, node.status.repoPath);
}
}
Expand Down

0 comments on commit cdc2241

Please sign in to comment.