You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

396 lines
10 KiB

'use strict';
import {
CancellationToken,
commands,
ConfigurationChangeEvent,
ProgressLocation,
TreeItem,
TreeItemCollapsibleState,
window,
} from 'vscode';
import { configuration, RemotesViewConfig, ViewBranchesLayout, ViewFilesLayout } from '../configuration';
import { GlyphChars } from '../constants';
import { Container } from '../container';
import {
GitBranch,
GitBranchReference,
GitLogCommit,
GitReference,
GitRemote,
GitRevisionReference,
RepositoryChange,
RepositoryChangeComparisonMode,
RepositoryChangeEvent,
} from '../git/git';
import { GitUri } from '../git/gitUri';
import {
BranchNode,
BranchOrTagFolderNode,
RemoteNode,
RemotesNode,
RepositoryFolderNode,
RepositoryNode,
unknownGitUri,
ViewNode,
} from './nodes';
import { debug, gate, Strings } from '../system';
import { ViewBase } from './viewBase';
export class RemotesRepositoryNode extends RepositoryFolderNode<RemotesView, RemotesNode> {
async getChildren(): Promise<ViewNode[]> {
if (this.child == null) {
this.child = new RemotesNode(this.uri, this.view, this, this.repo);
}
return this.child.getChildren();
}
protected changed(e: RepositoryChangeEvent) {
return e.changed(
RepositoryChange.Config,
RepositoryChange.Remotes,
RepositoryChange.Unknown,
RepositoryChangeComparisonMode.Any,
);
}
}
export class RemotesViewNode extends ViewNode<RemotesView> {
protected splatted = true;
private children: RemotesRepositoryNode[] | undefined;
constructor(view: RemotesView) {
super(unknownGitUri, view);
}
async getChildren(): Promise<ViewNode[]> {
if (this.children == null) {
const repositories = await Container.git.getOrderedRepositories();
if (repositories.length === 0) {
this.view.message = 'No remotes could be found.';
return [];
}
this.view.message = undefined;
const splat = repositories.length === 1;
this.children = repositories.map(
r => new RemotesRepositoryNode(GitUri.fromRepoPath(r.path), this.view, this, r, splat),
);
}
if (this.children.length === 1) {
const [child] = this.children;
if (!child.repo.supportsChangeEvents) {
this.view.description = `${Strings.pad(GlyphChars.Warning, 0, 2)}Auto-refresh unavailable`;
}
const remotes = await child.repo.getRemotes();
if (remotes.length === 0) {
this.view.message = 'No remotes could be found.';
this.view.title = 'Remotes';
void child.ensureSubscription();
return [];
}
this.view.message = undefined;
this.view.title = `Remotes (${remotes.length})`;
return child.getChildren();
}
return this.children;
}
getTreeItem(): TreeItem {
const item = new TreeItem('Remotes', TreeItemCollapsibleState.Expanded);
return item;
}
async getSplattedChild() {
if (this.children == null) {
await this.getChildren();
}
return this.children?.length === 1 ? this.children[0] : undefined;
}
@gate()
@debug()
refresh(reset: boolean = false) {
if (reset && this.children != null) {
for (const child of this.children) {
child.dispose();
}
this.children = undefined;
}
}
}
export class RemotesView extends ViewBase<RemotesViewNode, RemotesViewConfig> {
protected readonly configKey = 'remotes';
constructor() {
super('gitlens.views.remotes', 'Remotes');
}
getRoot() {
return new RemotesViewNode(this);
}
protected registerCommands() {
void Container.viewCommands;
commands.registerCommand(
this.getQualifiedCommand('copy'),
() => commands.executeCommand('gitlens.views.copy', this.selection),
this,
);
commands.registerCommand(
this.getQualifiedCommand('refresh'),
async () => {
await Container.git.resetCaches('branches', 'remotes');
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('setShowBranchPullRequestOn'),
() => this.setShowBranchPullRequest(true),
this,
);
commands.registerCommand(
this.getQualifiedCommand('setShowBranchPullRequestOff'),
() => this.setShowBranchPullRequest(false),
this,
);
}
protected 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, 'integrations', 'enabled') &&
!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: 5,
canTraverse: n => {
if (n instanceof RemotesViewNode) return true;
if (n instanceof RemotesRepositoryNode || n instanceof BranchOrTagFolderNode) {
return n.id.startsWith(repoNodeId);
}
if (n instanceof RemoteNode) {
if (!n.id.startsWith(repoNodeId)) return false;
return n.remote.name === GitBranch.getRemote(branch.name);
}
return false;
},
token: token,
});
}
async findCommit(commit: GitLogCommit | { repoPath: string; ref: string }, token?: CancellationToken) {
const repoNodeId = RepositoryNode.getId(commit.repoPath);
// Get all the remote branches the commit is on
const branches = await Container.git.getCommitBranches(commit.repoPath, commit.ref, { remotes: true });
if (branches.length === 0) return undefined;
const remotes = branches.map(b => b.split('/', 1)[0]);
return this.findNode((n: any) => n.commit !== undefined && n.commit.ref === commit.ref, {
allowPaging: true,
maxDepth: 6,
canTraverse: n => {
if (n instanceof RemotesViewNode) return true;
if (n instanceof RemotesRepositoryNode || n instanceof BranchOrTagFolderNode) {
return n.id.startsWith(repoNodeId);
}
if (n instanceof RemoteNode) {
return n.id.startsWith(repoNodeId) && remotes.includes(n.remote.name);
}
if (n instanceof BranchNode) {
return n.id.startsWith(repoNodeId) && branches.includes(n.branch.name);
}
if (n instanceof RepositoryNode || n instanceof RemotesNode || n instanceof BranchOrTagFolderNode) {
return n.id.startsWith(repoNodeId);
}
return false;
},
token: token,
});
}
findRemote(remote: GitRemote, token?: CancellationToken) {
const repoNodeId = RepositoryNode.getId(remote.repoPath);
return this.findNode((n: any) => n.remote?.name === remote.name, {
allowPaging: true,
maxDepth: 2,
canTraverse: n => {
if (n instanceof RemotesViewNode) return true;
if (n instanceof RemotesRepositoryNode) {
return n.id.startsWith(repoNodeId);
}
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 })} 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 })} 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(() => '')
revealRemote(
remote: GitRemote,
options?: {
select?: boolean;
focus?: boolean;
expand?: boolean | number;
},
) {
return window.withProgress(
{
location: ProgressLocation.Notification,
title: `Revealing remote ${remote.name} in the side bar...`,
cancellable: true,
},
async (progress, token) => {
const node = await this.findRemote(remote, token);
if (node == null) return undefined;
await this.ensureRevealNode(node, options);
return node;
},
);
}
private setLayout(layout: ViewBranchesLayout) {
return configuration.updateEffective('views', this.configKey, 'branches', 'layout', layout);
}
private setFilesLayout(layout: ViewFilesLayout) {
return configuration.updateEffective('views', this.configKey, 'files', 'layout', layout);
}
private setShowAvatars(enabled: boolean) {
return configuration.updateEffective('views', this.configKey, 'avatars', enabled);
}
private async setShowBranchPullRequest(enabled: boolean) {
await configuration.updateEffective('views', this.configKey, 'pullRequests', 'showForBranches', enabled);
await configuration.updateEffective('views', this.configKey, 'pullRequests', 'enabled', enabled);
}
}