|
|
@ -9,7 +9,7 @@ import { GitBranch, GitLog, GitRevision, GitTrackingState } from '../../git/git' |
|
|
|
import { GitUri } from '../../git/gitUri'; |
|
|
|
import { insertDateMarkers } from './helpers'; |
|
|
|
import { RepositoriesView } from '../repositoriesView'; |
|
|
|
import { debug, gate, Iterables, memoize, Strings } from '../../system'; |
|
|
|
import { Dates, debug, gate, Iterables, memoize, Strings } from '../../system'; |
|
|
|
import { ViewsWithFiles } from '../viewBase'; |
|
|
|
import { ContextValues, PageableViewNode, ViewNode } from './viewNode'; |
|
|
|
|
|
|
@ -22,8 +22,14 @@ export interface BranchTrackingStatus { |
|
|
|
|
|
|
|
export class BranchTrackingStatusNode extends ViewNode<ViewsWithFiles> implements PageableViewNode { |
|
|
|
static key = ':status-branch:upstream'; |
|
|
|
static getId(repoPath: string, name: string, root: boolean, upstream: string, direction: string): string { |
|
|
|
return `${BranchNode.getId(repoPath, name, root)}${this.key}(${upstream}|${direction})`; |
|
|
|
static getId( |
|
|
|
repoPath: string, |
|
|
|
name: string, |
|
|
|
root: boolean, |
|
|
|
upstream: string | undefined, |
|
|
|
upstreamType: string, |
|
|
|
): string { |
|
|
|
return `${BranchNode.getId(repoPath, name, root)}${this.key}(${upstream ?? ''}|${upstreamType})`; |
|
|
|
} |
|
|
|
|
|
|
|
constructor( |
|
|
@ -31,28 +37,20 @@ export class BranchTrackingStatusNode extends ViewNode implement |
|
|
|
parent: ViewNode, |
|
|
|
public readonly branch: GitBranch, |
|
|
|
public readonly status: BranchTrackingStatus, |
|
|
|
public readonly direction: 'ahead' | 'behind', |
|
|
|
public readonly upstreamType: 'ahead' | 'behind' | 'same' | 'none', |
|
|
|
// 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 ahead(): boolean { |
|
|
|
return this.direction === 'ahead'; |
|
|
|
} |
|
|
|
|
|
|
|
get behind(): boolean { |
|
|
|
return this.direction === 'behind'; |
|
|
|
} |
|
|
|
|
|
|
|
get id(): string { |
|
|
|
return BranchTrackingStatusNode.getId( |
|
|
|
this.status.repoPath, |
|
|
|
this.status.ref, |
|
|
|
this.root, |
|
|
|
this.status.upstream!, |
|
|
|
this.direction, |
|
|
|
this.status.upstream, |
|
|
|
this.upstreamType, |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
@ -61,11 +59,13 @@ export class BranchTrackingStatusNode extends ViewNode implement |
|
|
|
} |
|
|
|
|
|
|
|
async getChildren(): Promise<ViewNode[]> { |
|
|
|
if (this.upstreamType === 'same' || this.upstreamType === 'none') return []; |
|
|
|
|
|
|
|
const log = await this.getLog(); |
|
|
|
if (log == null) return []; |
|
|
|
|
|
|
|
let children; |
|
|
|
if (this.ahead) { |
|
|
|
if (this.upstreamType === 'ahead') { |
|
|
|
// Since the last commit when we are looking 'ahead' can have no previous (because of the range given) -- look it up
|
|
|
|
const commits = [...log.commits.values()]; |
|
|
|
const commit = commits[commits.length - 1]; |
|
|
@ -97,14 +97,14 @@ export class BranchTrackingStatusNode extends ViewNode implement |
|
|
|
children.push(new ShowMoreNode(this.view, this, children[children.length - 1])); |
|
|
|
} |
|
|
|
|
|
|
|
if (!this.isReposView && this.status.upstream && this.ahead && this.status.state.ahead > 0) { |
|
|
|
if (!this.isReposView && this.status.upstream && this.upstreamType === 'ahead' && this.status.state.ahead > 0) { |
|
|
|
children.push( |
|
|
|
new BranchTrackingStatusFilesNode( |
|
|
|
this.view, |
|
|
|
this, |
|
|
|
this.branch, |
|
|
|
this.status as Required<BranchTrackingStatus>, |
|
|
|
this.direction, |
|
|
|
this.upstreamType, |
|
|
|
this.root, |
|
|
|
), |
|
|
|
); |
|
|
@ -113,29 +113,84 @@ export class BranchTrackingStatusNode extends ViewNode implement |
|
|
|
return children; |
|
|
|
} |
|
|
|
|
|
|
|
getTreeItem(): TreeItem { |
|
|
|
const ahead = this.ahead; |
|
|
|
let label = ahead |
|
|
|
? `${Strings.pluralize('commit', this.status.state.ahead)} ahead` |
|
|
|
: `${Strings.pluralize('commit', this.status.state.behind)} behind`; |
|
|
|
if (!this.isReposView) { |
|
|
|
label += `${ahead ? ' of ' : ' '}${this.status.upstream}`; |
|
|
|
async getTreeItem(): Promise<TreeItem> { |
|
|
|
let lastFetched = 0; |
|
|
|
|
|
|
|
if (this.root && !this.isReposView && this.upstreamType !== 'none') { |
|
|
|
const repo = await Container.git.getRepository(this.repoPath); |
|
|
|
lastFetched = (await repo?.getLastFetched()) ?? 0; |
|
|
|
} |
|
|
|
|
|
|
|
const item = new TreeItem( |
|
|
|
label, |
|
|
|
ahead && !this.isReposView ? TreeItemCollapsibleState.Expanded : TreeItemCollapsibleState.Collapsed, |
|
|
|
); |
|
|
|
item.id = this.id; |
|
|
|
if (this.root) { |
|
|
|
item.contextValue = ahead ? ContextValues.StatusAheadOfUpstream : ContextValues.StatusBehindUpstream; |
|
|
|
} else { |
|
|
|
item.contextValue = ahead |
|
|
|
? ContextValues.BranchStatusAheadOfUpstream |
|
|
|
: ContextValues.BranchStatusBehindUpstream; |
|
|
|
let label; |
|
|
|
let collapsibleState; |
|
|
|
let contextValue; |
|
|
|
let icon; |
|
|
|
let tooltip; |
|
|
|
switch (this.upstreamType) { |
|
|
|
case 'ahead': |
|
|
|
label = `${Strings.pluralize('commit', this.status.state.ahead)} ahead`; |
|
|
|
if (!this.isReposView) { |
|
|
|
label = `${this.root ? `${this.branch.name} is ` : ''}${label} ${this.status.upstream}`; |
|
|
|
} |
|
|
|
tooltip = `${label} of ${this.status.upstream}`; |
|
|
|
|
|
|
|
collapsibleState = !this.isReposView |
|
|
|
? TreeItemCollapsibleState.Expanded |
|
|
|
: TreeItemCollapsibleState.Collapsed; |
|
|
|
contextValue = this.root |
|
|
|
? ContextValues.StatusAheadOfUpstream |
|
|
|
: ContextValues.BranchStatusAheadOfUpstream; |
|
|
|
icon = 'cloud-upload'; |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case 'behind': |
|
|
|
label = `${Strings.pluralize('commit', this.status.state.behind)} behind`; |
|
|
|
if (!this.isReposView) { |
|
|
|
label = `${this.root ? `${this.branch.name} is ` : ''}${label} ${this.status.upstream}`; |
|
|
|
} |
|
|
|
tooltip = `${label} ${this.status.upstream}`; |
|
|
|
|
|
|
|
collapsibleState = TreeItemCollapsibleState.Collapsed; |
|
|
|
contextValue = this.root |
|
|
|
? ContextValues.StatusBehindUpstream |
|
|
|
: ContextValues.BranchStatusBehindUpstream; |
|
|
|
icon = 'cloud-download'; |
|
|
|
|
|
|
|
break; |
|
|
|
|
|
|
|
case 'same': |
|
|
|
label = `${this.branch.name} is up to date`; |
|
|
|
if (!this.isReposView) { |
|
|
|
label += ` with ${this.status.upstream}`; |
|
|
|
} |
|
|
|
tooltip = `${label} with ${this.status.upstream}`; |
|
|
|
|
|
|
|
collapsibleState = TreeItemCollapsibleState.None; |
|
|
|
contextValue = this.root ? ContextValues.StatusSameAsUpstream : undefined; |
|
|
|
icon = 'cloud'; |
|
|
|
|
|
|
|
break; |
|
|
|
case 'none': |
|
|
|
label = `${this.branch.name} hasn't yet been published`; |
|
|
|
tooltip = label; |
|
|
|
|
|
|
|
collapsibleState = TreeItemCollapsibleState.None; |
|
|
|
contextValue = this.root ? ContextValues.StatusNoUpstream : undefined; |
|
|
|
icon = 'cloud-upload'; |
|
|
|
|
|
|
|
break; |
|
|
|
} |
|
|
|
item.iconPath = new ThemeIcon(ahead ? 'cloud-upload' : 'cloud-download'); |
|
|
|
item.tooltip = `${label}${ahead ? ' of ' : ' '}${this.status.upstream}`; |
|
|
|
|
|
|
|
const item = new TreeItem(label, collapsibleState); |
|
|
|
item.id = this.id; |
|
|
|
item.contextValue = contextValue; |
|
|
|
item.description = lastFetched |
|
|
|
? `Last fetched ${Dates.getFormatter(new Date(lastFetched)).fromNow()}` |
|
|
|
: undefined; |
|
|
|
item.iconPath = new ThemeIcon(icon); |
|
|
|
item.tooltip = tooltip; |
|
|
|
|
|
|
|
return item; |
|
|
|
} |
|
|
|
|
|
|
@ -154,10 +209,13 @@ export class BranchTrackingStatusNode extends ViewNode implement |
|
|
|
|
|
|
|
private _log: GitLog | undefined; |
|
|
|
private async getLog() { |
|
|
|
if (this.upstreamType === 'same' || this.upstreamType === 'none') return undefined; |
|
|
|
|
|
|
|
if (this._log == null) { |
|
|
|
const range = this.ahead |
|
|
|
? GitRevision.createRange(this.status.upstream, this.status.ref) |
|
|
|
: GitRevision.createRange(this.status.ref, this.status.upstream); |
|
|
|
const range = |
|
|
|
this.upstreamType === 'ahead' |
|
|
|
? GitRevision.createRange(this.status.upstream, this.status.ref) |
|
|
|
: GitRevision.createRange(this.status.ref, this.status.upstream); |
|
|
|
|
|
|
|
this._log = await Container.git.getLog(this.uri.repoPath!, { |
|
|
|
limit: this.limit ?? this.view.config.defaultItemLimit, |
|
|
|