import {
|
|
CancellationToken,
|
|
commands,
|
|
ConfigurationChangeEvent,
|
|
Disposable,
|
|
ProgressLocation,
|
|
TreeItem,
|
|
TreeItemCollapsibleState,
|
|
window,
|
|
} from 'vscode';
|
|
import {
|
|
BranchesViewConfig,
|
|
configuration,
|
|
ViewBranchesLayout,
|
|
ViewFilesLayout,
|
|
ViewShowBranchComparison,
|
|
} from '../configuration';
|
|
import { Commands } from '../constants';
|
|
import { Container } from '../container';
|
|
import { GitUri } from '../git/gitUri';
|
|
import {
|
|
GitBranchReference,
|
|
GitCommit,
|
|
GitReference,
|
|
GitRevisionReference,
|
|
RepositoryChange,
|
|
RepositoryChangeComparisonMode,
|
|
RepositoryChangeEvent,
|
|
} from '../git/models';
|
|
import { executeCommand } from '../system/command';
|
|
import { gate } from '../system/decorators/gate';
|
|
import {
|
|
BranchesNode,
|
|
BranchNode,
|
|
BranchOrTagFolderNode,
|
|
RepositoriesSubscribeableNode,
|
|
RepositoryFolderNode,
|
|
RepositoryNode,
|
|
ViewNode,
|
|
} from './nodes';
|
|
import { ViewBase } from './viewBase';
|
|
|
|
export class BranchesRepositoryNode extends RepositoryFolderNode<BranchesView, BranchesNode> {
|
|
async getChildren(): Promise<ViewNode[]> {
|
|
if (this.child == null) {
|
|
this.child = new BranchesNode(this.uri, this.view, this, this.repo);
|
|
}
|
|
|
|
return this.child.getChildren();
|
|
}
|
|
|
|
protected changed(e: RepositoryChangeEvent) {
|
|
return e.changed(
|
|
RepositoryChange.Config,
|
|
RepositoryChange.Heads,
|
|
RepositoryChange.Index,
|
|
RepositoryChange.Remotes,
|
|
RepositoryChange.RemoteProviders,
|
|
RepositoryChange.Status,
|
|
RepositoryChange.Unknown,
|
|
RepositoryChangeComparisonMode.Any,
|
|
);
|
|
}
|
|
}
|
|
|
|
export class BranchesViewNode extends RepositoriesSubscribeableNode<BranchesView, BranchesRepositoryNode> {
|
|
async getChildren(): Promise<ViewNode[]> {
|
|
if (this.children == null) {
|
|
const repositories = this.view.container.git.openRepositories;
|
|
if (repositories.length === 0) {
|
|
this.view.message = 'No branches could be found.';
|
|
|
|
return [];
|
|
}
|
|
|
|
this.view.message = undefined;
|
|
|
|
const splat = repositories.length === 1;
|
|
this.children = repositories.map(
|
|
r => new BranchesRepositoryNode(GitUri.fromRepoPath(r.path), this.view, this, r, splat),
|
|
);
|
|
}
|
|
|
|
if (this.children.length === 1) {
|
|
const [child] = this.children;
|
|
|
|
const branches = await child.repo.getBranches({ filter: b => !b.remote });
|
|
if (branches.values.length === 0) {
|
|
this.view.message = 'No branches could be found.';
|
|
this.view.title = 'Branches';
|
|
|
|
void child.ensureSubscription();
|
|
|
|
return [];
|
|
}
|
|
|
|
this.view.message = undefined;
|
|
this.view.title = `Branches (${branches.values.length})`;
|
|
|
|
return child.getChildren();
|
|
}
|
|
|
|
return this.children;
|
|
}
|
|
|
|
getTreeItem(): TreeItem {
|
|
const item = new TreeItem('Branches', TreeItemCollapsibleState.Expanded);
|
|
return item;
|
|
}
|
|
}
|
|
|
|
export class BranchesView extends ViewBase<BranchesViewNode, BranchesViewConfig> {
|
|
protected readonly configKey = 'branches';
|
|
|
|
constructor(container: Container) {
|
|
super('gitlens.views.branches', 'Branches', container);
|
|
}
|
|
|
|
override get canReveal(): boolean {
|
|
return this.config.reveal || !configuration.get('views.repositories.showBranches');
|
|
}
|
|
|
|
protected getRoot() {
|
|
return new BranchesViewNode(this);
|
|
}
|
|
|
|
protected registerCommands(): Disposable[] {
|
|
void this.container.viewCommands;
|
|
|
|
return [
|
|
commands.registerCommand(
|
|
this.getQualifiedCommand('copy'),
|
|
() => executeCommand(Commands.ViewsCopy, this.selection),
|
|
this,
|
|
),
|
|
commands.registerCommand(
|
|
this.getQualifiedCommand('refresh'),
|
|
() => {
|
|
this.container.git.resetCaches('branches');
|
|
return this.refresh(true);
|
|
},
|
|
this,
|
|
),
|
|
commands.registerCommand(
|
|
this.getQualifiedCommand('setLayoutToList'),
|
|
() => this.setLayout(ViewBranchesLayout.List),
|
|
this,
|
|
),
|
|
commands.registerCommand(
|
|
this.getQualifiedCommand('setLayoutToTree'),
|
|
() => this.setLayout(ViewBranchesLayout.Tree),
|
|
this,
|
|
),
|
|
commands.registerCommand(
|
|
this.getQualifiedCommand('setFilesLayoutToAuto'),
|
|
() => this.setFilesLayout(ViewFilesLayout.Auto),
|
|
this,
|
|
),
|
|
commands.registerCommand(
|
|
this.getQualifiedCommand('setFilesLayoutToList'),
|
|
() => this.setFilesLayout(ViewFilesLayout.List),
|
|
this,
|
|
),
|
|
commands.registerCommand(
|
|
this.getQualifiedCommand('setFilesLayoutToTree'),
|
|
() => this.setFilesLayout(ViewFilesLayout.Tree),
|
|
this,
|
|
),
|
|
commands.registerCommand(
|
|
this.getQualifiedCommand('setShowAvatarsOn'),
|
|
() => this.setShowAvatars(true),
|
|
this,
|
|
),
|
|
commands.registerCommand(
|
|
this.getQualifiedCommand('setShowAvatarsOff'),
|
|
() => this.setShowAvatars(false),
|
|
this,
|
|
),
|
|
commands.registerCommand(
|
|
this.getQualifiedCommand('setShowBranchComparisonOn'),
|
|
() => this.setShowBranchComparison(true),
|
|
this,
|
|
),
|
|
commands.registerCommand(
|
|
this.getQualifiedCommand('setShowBranchComparisonOff'),
|
|
() => this.setShowBranchComparison(false),
|
|
this,
|
|
),
|
|
commands.registerCommand(
|
|
this.getQualifiedCommand('setShowBranchPullRequestOn'),
|
|
() => this.setShowBranchPullRequest(true),
|
|
this,
|
|
),
|
|
commands.registerCommand(
|
|
this.getQualifiedCommand('setShowBranchPullRequestOff'),
|
|
() => this.setShowBranchPullRequest(false),
|
|
this,
|
|
),
|
|
];
|
|
}
|
|
|
|
protected override filterConfigurationChanged(e: ConfigurationChangeEvent) {
|
|
const changed = super.filterConfigurationChanged(e);
|
|
if (
|
|
!changed &&
|
|
!configuration.changed(e, 'defaultDateFormat') &&
|
|
!configuration.changed(e, 'defaultDateShortFormat') &&
|
|
!configuration.changed(e, 'defaultDateSource') &&
|
|
!configuration.changed(e, 'defaultDateStyle') &&
|
|
!configuration.changed(e, 'defaultGravatarsStyle') &&
|
|
!configuration.changed(e, 'defaultTimeFormat') &&
|
|
!configuration.changed(e, 'sortBranchesBy')
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
findBranch(branch: GitBranchReference, token?: CancellationToken) {
|
|
if (branch.remote) return undefined;
|
|
|
|
const repoNodeId = RepositoryNode.getId(branch.repoPath);
|
|
|
|
return this.findNode((n: any) => n.branch?.ref === branch.ref, {
|
|
allowPaging: true,
|
|
maxDepth: 4,
|
|
canTraverse: n => {
|
|
if (n instanceof BranchesViewNode) return true;
|
|
|
|
if (n instanceof BranchesRepositoryNode || n instanceof BranchOrTagFolderNode) {
|
|
return n.id.startsWith(repoNodeId);
|
|
}
|
|
|
|
return false;
|
|
},
|
|
token: token,
|
|
});
|
|
}
|
|
|
|
async findCommit(commit: GitCommit | { repoPath: string; ref: string }, token?: CancellationToken) {
|
|
const repoNodeId = RepositoryNode.getId(commit.repoPath);
|
|
|
|
// Get all the branches the commit is on
|
|
const branches = await this.container.git.getCommitBranches(
|
|
commit.repoPath,
|
|
commit.ref,
|
|
GitCommit.is(commit) ? { commitDate: commit.committer.date } : undefined,
|
|
);
|
|
if (branches.length === 0) return undefined;
|
|
|
|
return this.findNode((n: any) => n.commit?.ref === commit.ref, {
|
|
allowPaging: true,
|
|
maxDepth: 5,
|
|
canTraverse: async n => {
|
|
if (n instanceof BranchesViewNode) return true;
|
|
|
|
if (n instanceof BranchesRepositoryNode || n instanceof BranchOrTagFolderNode) {
|
|
return n.id.startsWith(repoNodeId);
|
|
}
|
|
|
|
if (n instanceof BranchNode && branches.includes(n.branch.name)) {
|
|
await n.loadMore({ until: commit.ref });
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
token: token,
|
|
});
|
|
}
|
|
|
|
@gate(() => '')
|
|
revealBranch(
|
|
branch: GitBranchReference,
|
|
options?: {
|
|
select?: boolean;
|
|
focus?: boolean;
|
|
expand?: boolean | number;
|
|
},
|
|
) {
|
|
return window.withProgress(
|
|
{
|
|
location: ProgressLocation.Notification,
|
|
title: `Revealing ${GitReference.toString(branch, { icon: false, quoted: true })} in the side bar...`,
|
|
cancellable: true,
|
|
},
|
|
async (progress, token) => {
|
|
const node = await this.findBranch(branch, token);
|
|
if (node == null) return undefined;
|
|
|
|
await this.ensureRevealNode(node, options);
|
|
|
|
return node;
|
|
},
|
|
);
|
|
}
|
|
|
|
@gate(() => '')
|
|
async revealCommit(
|
|
commit: GitRevisionReference,
|
|
options?: {
|
|
select?: boolean;
|
|
focus?: boolean;
|
|
expand?: boolean | number;
|
|
},
|
|
) {
|
|
return window.withProgress(
|
|
{
|
|
location: ProgressLocation.Notification,
|
|
title: `Revealing ${GitReference.toString(commit, { icon: false, quoted: true })} in the side bar...`,
|
|
cancellable: true,
|
|
},
|
|
async (progress, token) => {
|
|
const node = await this.findCommit(commit, token);
|
|
if (node == null) return undefined;
|
|
|
|
await this.ensureRevealNode(node, options);
|
|
|
|
return node;
|
|
},
|
|
);
|
|
}
|
|
|
|
@gate(() => '')
|
|
async revealRepository(
|
|
repoPath: string,
|
|
options?: { select?: boolean; focus?: boolean; expand?: boolean | number },
|
|
) {
|
|
const node = await this.findNode(RepositoryFolderNode.getId(repoPath), {
|
|
maxDepth: 1,
|
|
canTraverse: n => n instanceof BranchesViewNode || n instanceof RepositoryFolderNode,
|
|
});
|
|
|
|
if (node !== undefined) {
|
|
await this.reveal(node, options);
|
|
}
|
|
|
|
return node;
|
|
}
|
|
|
|
private setLayout(layout: ViewBranchesLayout) {
|
|
return configuration.updateEffective(`views.${this.configKey}.branches.layout` as const, layout);
|
|
}
|
|
|
|
private setFilesLayout(layout: ViewFilesLayout) {
|
|
return configuration.updateEffective(`views.${this.configKey}.files.layout` as const, layout);
|
|
}
|
|
|
|
private setShowAvatars(enabled: boolean) {
|
|
return configuration.updateEffective(`views.${this.configKey}.avatars` as const, enabled);
|
|
}
|
|
|
|
private setShowBranchComparison(enabled: boolean) {
|
|
return configuration.updateEffective(
|
|
`views.${this.configKey}.showBranchComparison` as const,
|
|
enabled ? ViewShowBranchComparison.Branch : false,
|
|
);
|
|
}
|
|
|
|
private async setShowBranchPullRequest(enabled: boolean) {
|
|
await configuration.updateEffective(`views.${this.configKey}.pullRequests.showForBranches` as const, enabled);
|
|
await configuration.updateEffective(`views.${this.configKey}.pullRequests.enabled` as const, enabled);
|
|
}
|
|
}
|