<commit_before>'use strict';
import * as paths from 'path';
import { commands, env, TextDocumentShowOptions, Uri, window } from 'vscode';
import {
	Commands,
	DiffWithCommandArgs,
	DiffWithCommandArgsRevision,
	DiffWithPreviousCommandArgs,
	DiffWithWorkingCommandArgs,
	GitCommandsCommandArgs,
	openEditor,
	OpenFileInRemoteCommandArgs,
	OpenFileRevisionCommandArgs,
	OpenWorkingFileCommandArgs,
	openWorkspace
} from '../commands';
import { BuiltInCommands, CommandContext, setCommandContext } from '../constants';
import { Container } from '../container';
import { toGitLensFSUri } from '../git/fsProvider';
import { GitReference, GitService, GitUri } from '../git/gitService';
import {
	BranchNode,
	BranchTrackingStatusNode,
	CommitFileNode,
	CommitNode,
	CompareBranchNode,
	CompareResultsNode,
	ContributorNode,
	FileHistoryNode,
	FolderNode,
	LineHistoryNode,
	nodeSupportsPaging,
	PagerNode,
	RemoteNode,
	RepositoryNode,
	ResultsFileNode,
	ResultsFilesNode,
	StashFileNode,
	StashNode,
	StatusFileNode,
	TagNode,
	ViewNode,
	ViewRefFileNode,
	ViewRefNode,
	viewSupportsNodeDismissal
} from './nodes';
import { Strings } from '../system/string';
import { runGitCommandInTerminal } from '../terminal';

interface CompareSelectedInfo {
	ref: string;
	repoPath: string | undefined;
	uri?: Uri;
}

export class ViewCommands {
	constructor() {
		commands.registerCommand(
			'gitlens.views.copy',
			async (selection: ViewNode[]) => {
				if (selection.length === 0) return;

				const data = selection
					.filter(n => n.toClipboard !== undefined)
					.map(n => n.toClipboard!())
					.join(',');
				await env.clipboard.writeText(data);
			},
			this
		);
		commands.registerCommand(
			'gitlens.views.refreshNode',
			(node: ViewNode, reset?: boolean) => {
				if (reset === undefined && nodeSupportsPaging(node)) {
					node.maxCount = undefined;
					node.view.resetNodeLastMaxCount(node);
				}

				return node.view.refreshNode(node, reset === undefined ? true : reset);
			},
			this
		);
		commands.registerCommand(
			'gitlens.views.expandNode',
			(node: ViewNode) => node.view.reveal(node, { select: false, focus: false, expand: 3 }),
			this
		);
		commands.registerCommand(
			'gitlens.views.dismissNode',
			(node: ViewNode) => viewSupportsNodeDismissal(node.view) && node.view.dismissNode(node),
			this
		);
		commands.registerCommand('gitlens.views.executeNodeCallback', (fn: <R>() => Promise<R>) => fn(), this);
		commands.registerCommand('gitlens.views.showMoreChildren', (node: PagerNode) => node.showMore(), this);
		commands.registerCommand('gitlens.views.showAllChildren', (node: PagerNode) => node.showAll(), this);

		commands.registerCommand('gitlens.views.fetch', this.fetch, this);
		commands.registerCommand('gitlens.views.pull', this.pull, this);
		commands.registerCommand('gitlens.views.push', this.push, this);
		commands.registerCommand('gitlens.views.pushWithForce', n => this.push(n, true), this);
		commands.registerCommand('gitlens.views.closeRepository', this.closeRepository, this);

		commands.registerCommand('gitlens.views.setAsDefault', this.setAsDefault, this);
		commands.registerCommand('gitlens.views.unsetAsDefault', this.unsetAsDefault, this);

		commands.registerCommand('gitlens.views.openInTerminal', this.openInTerminal, this);
		commands.registerCommand('gitlens.views.star', this.star, this);
		commands.registerCommand('gitlens.views.unstar', this.unstar, this);

		commands.registerCommand('gitlens.views.exploreRepoRevision', this.exploreRepoRevision, this);

		commands.registerCommand('gitlens.views.contributor.addCoauthoredBy', this.contributorAddCoauthoredBy, this);
		commands.registerCommand('gitlens.views.contributor.copyToClipboard', this.contributorCopyToClipboard, this);

		commands.registerCommand('gitlens.views.openChanges', this.openChanges, this);
		commands.registerCommand('gitlens.views.openChangesWithWorking', this.openChangesWithWorking, this);
		commands.registerCommand('gitlens.views.openFile', this.openFile, this);
		commands.registerCommand('gitlens.views.openFileRevision', this.openFileRevision, this);
		commands.registerCommand('gitlens.views.openFileRevisionInRemote', this.openFileRevisionInRemote, this);
		commands.registerCommand('gitlens.views.openChangedFiles', this.openChangedFiles, this);
		commands.registerCommand('gitlens.views.openChangedFileChanges', this.openChangedFileChanges, this);
		commands.registerCommand(
			'gitlens.views.openChangedFileChangesWithWorking',
			this.openChangedFileChangesWithWorking,
			this
		);
		commands.registerCommand('gitlens.views.openChangedFileRevisions', this.openChangedFileRevisions, this);
		commands.registerCommand('gitlens.views.applyChanges', this.applyChanges, this);
		commands.registerCommand('gitlens.views.restore', this.restore, this);
		commands.registerCommand('gitlens.views.switchToBranch', this.switch, this);
		commands.registerCommand('gitlens.views.switchToCommit', this.switch, this);
		commands.registerCommand('gitlens.views.switchToTag', this.switch, this);
		commands.registerCommand('gitlens.views.addRemote', this.addRemote, this);
		commands.registerCommand('gitlens.views.pruneRemote', this.pruneRemote, this);

		commands.registerCommand('gitlens.views.stageDirectory', this.stageDirectory, this);
		commands.registerCommand('gitlens.views.stageFile', this.stageFile, this);
		commands.registerCommand('gitlens.views.unstageDirectory', this.unstageDirectory, this);
		commands.registerCommand('gitlens.views.unstageFile', this.unstageFile, this);

		commands.registerCommand('gitlens.views.compareAncestryWithWorking', this.compareAncestryWithWorking, this);
		commands.registerCommand('gitlens.views.compareWithHead', this.compareWithHead, this);
		commands.registerCommand('gitlens.views.compareWithRemote', this.compareWithRemote, this);
		commands.registerCommand('gitlens.views.compareWithSelected', this.compareWithSelected, this);
		commands.registerCommand('gitlens.views.selectForCompare', this.selectForCompare, this);
		commands.registerCommand('gitlens.views.compareFileWithSelected', this.compareFileWithSelected, this);
		commands.registerCommand('gitlens.views.selectFileForCompare', this.selectFileForCompare, this);
		commands.registerCommand('gitlens.views.compareWithWorking', this.compareWithWorking, this);

		commands.registerCommand('gitlens.views.setComparisonToTwoDot', n => this.setComparisonNotation(n, '..'), this);
		commands.registerCommand(
			'gitlens.views.setComparisonToThreeDot',
			n => this.setComparisonNotation(n, '...'),
			this
		);

		commands.registerCommand('gitlens.views.cherryPick', this.cherryPick, this);

		commands.registerCommand('gitlens.views.mergeBranchInto', this.merge, this);

		commands.registerCommand('gitlens.views.rebaseOntoBranch', this.rebase, this);
		commands.registerCommand('gitlens.views.rebaseOntoUpstream', this.rebaseToRemote, this);
		commands.registerCommand('gitlens.views.rebaseOntoCommit', this.rebase, this);

		commands.registerCommand('gitlens.views.reset', this.reset, this);
		commands.registerCommand('gitlens.views.revert', this.revert, this);

		commands.registerCommand('gitlens.views.terminalPushCommit', this.terminalPushCommit, this);

		commands.registerCommand('gitlens.views.terminalCreateBranch', this.terminalCreateBranch, this);
		commands.registerCommand('gitlens.views.terminalDeleteBranch', this.terminalDeleteBranch, this);
		commands.registerCommand('gitlens.views.terminalRemoveRemote', this.terminalRemoveRemote, this);
		commands.registerCommand('gitlens.views.terminalCreateTag', this.terminalCreateTag, this);
		commands.registerCommand('gitlens.views.terminalDeleteTag', this.terminalDeleteTag, this);
	}

	private async contributorAddCoauthoredBy(node: ContributorNode) {
		if (!(node instanceof ContributorNode)) return;

		const gitApi = await GitService.getBuiltInGitApi();
		if (gitApi === undefined) return;

		const repo = gitApi.repositories.find(
			r => Strings.normalizePath(r.rootUri.fsPath) === node.contributor.repoPath
		);
		if (repo === undefined) return;

		const coauthor = `${node.contributor.name}${node.contributor.email ? ` <${node.contributor.email}>` : ''}`;

		const message = repo.inputBox.value;
		if (message.includes(coauthor)) return;

		let newlines;
		if (message.includes('Co-authored-by: ')) {
			newlines = '\n';
		} else if (message.length !== 0 && message.endsWith('\n')) {
			newlines = '\n\n';
		} else {
			newlines = '\n\n\n';
		}

		repo.inputBox.value = `${message}${newlines}Co-authored-by: ${coauthor}`;
	}

	private async contributorCopyToClipboard(node: ContributorNode) {
		if (!(node instanceof ContributorNode)) return;

		await env.clipboard.writeText(
			`${node.contributor.name}${node.contributor.email ? ` <${node.contributor.email}>` : ''}`
		);
	}

	private fetch(node: RemoteNode | RepositoryNode) {
		if (node instanceof RemoteNode) return node.fetch();
		if (node instanceof RepositoryNode) {
			const args: GitCommandsCommandArgs = { command: 'fetch', state: { repos: [node.repo] } };
			return commands.executeCommand(Commands.GitCommands, args);
		}

		return undefined;
	}

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

		const args: GitCommandsCommandArgs = { command: 'pull', state: { repos: [node.repo] } };
		return commands.executeCommand(Commands.GitCommands, args);
	}

	private push(node: RepositoryNode | BranchTrackingStatusNode, force?: boolean) {
		if (node instanceof BranchTrackingStatusNode) {
			node = node.getParent() as RepositoryNode;
		}
		if (!(node instanceof RepositoryNode)) return undefined;

		const args: GitCommandsCommandArgs = { command: 'push', state: { repos: [node.repo] } };
		return commands.executeCommand(Commands.GitCommands, args);
	}

	private async applyChanges(node: ViewRefFileNode) {
		if (!(node instanceof ViewRefFileNode)) return;

		void (await this.openFile(node));

		if (node instanceof ResultsFileNode) {
			void (await Container.git.applyChangesToWorkingFile(node.uri, node.ref1, node.ref2));

			return;
		}

		if (node.uri.sha !== undefined && node.uri.sha !== 'HEAD') {
			void (await Container.git.applyChangesToWorkingFile(node.uri));
		}
	}

	private async cherryPick(node: CommitNode) {
		if (!(node instanceof CommitNode)) return undefined;

		const repo = await Container.git.getRepository(node.repoPath);

		const args: GitCommandsCommandArgs = {
			command: 'cherry-pick',
			state: { repo: repo!, references: [GitReference.create(node.ref)] }
		};
		return commands.executeCommand(Commands.GitCommands, args);
	}

	private async merge(node: BranchNode | TagNode) {
		if (!(node instanceof BranchNode) && !(node instanceof TagNode)) return undefined;

		const repo = await Container.git.getRepository(node.repoPath);

		const args: GitCommandsCommandArgs = {
			command: 'merge',
			state: { repo: repo!, reference: node instanceof BranchNode ? node.branch : node.tag }
		};
		return commands.executeCommand(Commands.GitCommands, args);
	}

	private async rebase(node: BranchNode | CommitNode | TagNode) {
		if (!(node instanceof BranchNode) && !(node instanceof CommitNode) && !(node instanceof TagNode)) {
			return undefined;
		}

		const repo = await Container.git.getRepository(node.repoPath);

		let args: GitCommandsCommandArgs;
		if (node instanceof CommitNode) {
			args = {
				command: 'rebase',
				state: {
					repo: repo!,
					reference: GitReference.create(node.ref)
				}
			};
		} else {
			args = {
				command: 'rebase',
				state: {
					repo: repo!,
					reference: node instanceof BranchNode ? node.branch : node.tag
				}
			};
		}
		return commands.executeCommand(Commands.GitCommands, args);
	}

	private async rebaseToRemote(node: BranchNode | BranchTrackingStatusNode) {
		if (!(node instanceof BranchNode) && !(node instanceof BranchTrackingStatusNode)) return undefined;

		const upstream = node instanceof BranchNode ? node.branch.tracking : node.status.upstream;
		if (upstream === undefined) return undefined;

		const repo = await Container.git.getRepository(node.repoPath);

		const args: GitCommandsCommandArgs = {
			command: 'rebase',
			state: { repo: repo!, reference: GitReference.create(upstream, { name: upstream, refType: 'branch' }) }
		};
		return commands.executeCommand(Commands.GitCommands, args);
	}

	private async reset(node: CommitNode) {
		if (!(node instanceof CommitNode)) return undefined;

		const repo = await Container.git.getRepository(node.repoPath);

		const args: GitCommandsCommandArgs = {
			command: 'reset',
			state: { repo: repo!, reference: GitReference.create(node.ref) }
		};
		return commands.executeCommand(Commands.GitCommands, args);
	}

	private async restore(node: ViewRefFileNode) {
		if (!(node instanceof ViewRefFileNode)) return undefined;

		return Container.git.checkout(node.repoPath, node.ref, { fileName: node.fileName });
	}

	private async revert(node: CommitNode) {
		if (!(node instanceof CommitNode)) return undefined;

		const repo = await Container.git.getRepository(node.repoPath);

		const args: GitCommandsCommandArgs = {
			command: 'revert',
			state: { repo: repo!, references: [GitReference.create(node.ref)] }
		};
		return commands.executeCommand(Commands.GitCommands, args);
	}

	private async switch(node: ViewRefNode) {
		if (!(node instanceof ViewRefNode)) return undefined;

		const repo = await Container.git.getRepository(node.repoPath);

		let args: GitCommandsCommandArgs;
		if (node instanceof BranchNode) {
			args = {
				command: 'switch',
				state: { repos: [repo!], reference: node.branch.current ? undefined : node.branch }
			};
		} else if (node instanceof TagNode) {
			args = { command: 'switch', state: { repos: [repo!], reference: node.tag } };
		} else {
			args = {
				command: 'switch',
				state: { repos: [repo!], reference: GitReference.create(node.ref) }
			};
		}

		return commands.executeCommand(Commands.GitCommands, args);
	}

	private async addRemote(node: RemoteNode) {
		const name = await window.showInputBox({
			prompt: 'Please provide a name for the remote',
			placeHolder: 'Remote name',
			value: undefined,
			ignoreFocusOut: true
		});
		if (name === undefined || name.length === 0) return undefined;

		const url = await window.showInputBox({
			prompt: 'Please provide the repository url for the remote',
			placeHolder: 'Remote repository url',
			value: undefined,
			ignoreFocusOut: true
		});
		if (url === undefined || url.length === 0) return undefined;

		void (await Container.git.addRemote(node.repo.path, name, url));
		void (await node.repo.fetch({ remote: name }));

		return name;
	}

	private async pruneRemote(node: RemoteNode) {
		const remoteName = node.remote.name;
		void (await Container.git.pruneRemote(node.repo.path, remoteName));

		return remoteName;
	}

	private closeRepository(node: RepositoryNode) {
		if (!(node instanceof RepositoryNode)) return;

		node.repo.closed = true;
	}

	private compareWithHead(node: ViewRefNode) {
		if (!(node instanceof ViewRefNode)) return undefined;

		return Container.compareView.compare(node.repoPath, node.ref, 'HEAD');
	}

	private compareWithRemote(node: BranchNode) {
		if (!(node instanceof BranchNode)) return undefined;
		if (!node.branch.tracking) return undefined;

		return Container.compareView.compare(node.repoPath, node.branch.tracking, node.ref);
	}

	private compareWithWorking(node: ViewRefNode) {
		if (!(node instanceof ViewRefNode)) return undefined;

		return Container.compareView.compare(node.repoPath, node.ref, '');
	}

	private async compareAncestryWithWorking(node: BranchNode) {
		if (!(node instanceof BranchNode)) return undefined;

		const branch = await Container.git.getBranch(node.repoPath);
		if (branch === undefined) return undefined;

		const commonAncestor = await Container.git.getMergeBase(node.repoPath, branch.ref, node.ref);
		if (commonAncestor === undefined) return undefined;

		return Container.compareView.compare(
			node.repoPath,
			{ ref: commonAncestor, label: `ancestry with ${node.ref} (${GitService.shortenSha(commonAncestor)})` },
			''
		);
	}

	private compareWithSelected(node: ViewRefNode) {
		if (!(node instanceof ViewRefNode)) return;

		Container.compareView.compareWithSelected(node.repoPath, node.ref);
	}

	private selectForCompare(node: ViewRefNode) {
		if (!(node instanceof ViewRefNode)) return;

		Container.compareView.selectForCompare(node.repoPath, node.ref);
	}

	private compareFileWithSelected(node: ViewRefFileNode) {
		if (this._selectedFile === undefined || !(node instanceof ViewRefFileNode) || node.ref === undefined) {
			return undefined;
		}

		if (this._selectedFile.repoPath !== node.repoPath) {
			this.selectFileForCompare(node);
			return undefined;
		}

		const selected = this._selectedFile;

		this._selectedFile = undefined;
		setCommandContext(CommandContext.ViewsCanCompareFile, false);

		const diffArgs: DiffWithCommandArgs = {
			repoPath: selected.repoPath,
			lhs: {
				sha: selected.ref,
				uri: selected.uri!
			},
			rhs: {
				sha: node.ref,
				uri: node.uri
			}
		};
		return commands.executeCommand(Commands.DiffWith, diffArgs);
	}

	private _selectedFile: CompareSelectedInfo | undefined;

	private selectFileForCompare(node: ViewRefFileNode) {
		if (!(node instanceof ViewRefFileNode) || node.ref === undefined) return;

		this._selectedFile = {
			ref: node.ref,
			repoPath: node.repoPath,
			uri: node.uri
		};
		setCommandContext(CommandContext.ViewsCanCompareFile, true);
	}

	private exploreRepoRevision(node: ViewRefNode, options: { openInNewWindow?: boolean } = {}) {
		if (!(node instanceof ViewRefNode)) return;

		const uri = toGitLensFSUri(node.ref, node.repoPath);
		const gitUri = GitUri.fromRevisionUri(uri);

		openWorkspace(uri, `${paths.basename(gitUri.repoPath!)} @ ${gitUri.shortSha}`, options);

		void commands.executeCommand(BuiltInCommands.FocusFilesExplorer);
	}

	private openChanges(node: ViewRefFileNode | StatusFileNode) {
		if (!(node instanceof ViewRefFileNode) && !(node instanceof StatusFileNode)) return undefined;

		const command = node.getCommand();
		if (command === undefined || command.arguments === undefined) return undefined;

		const [uri, args] = command.arguments as [Uri, DiffWithPreviousCommandArgs];
		args.showOptions!.preview = false;
		return commands.executeCommand(command.command, uri, args);
	}

	private openChangesWithWorking(node: ViewRefFileNode | StatusFileNode) {
		if (!(node instanceof ViewRefFileNode) && !(node instanceof StatusFileNode)) return undefined;

		const args: DiffWithWorkingCommandArgs = {
			showOptions: {
				preserveFocus: true,
				preview: false
			}
		};
		return commands.executeCommand(Commands.DiffWithWorking, node.uri, args);<commit_msg>Fixes issue w/ explore repo rev in latest vscode<commit_after>'use strict';
import * as paths from 'path';
import { commands, env, TextDocumentShowOptions, Uri, window } from 'vscode';
import {
	Commands,
	DiffWithCommandArgs,
	DiffWithCommandArgsRevision,
	DiffWithPreviousCommandArgs,
	DiffWithWorkingCommandArgs,
	GitCommandsCommandArgs,
	openEditor,
	OpenFileInRemoteCommandArgs,
	OpenFileRevisionCommandArgs,
	OpenWorkingFileCommandArgs,
	openWorkspace
} from '../commands';
import { BuiltInCommands, CommandContext, setCommandContext } from '../constants';
import { Container } from '../container';
import { toGitLensFSUri } from '../git/fsProvider';
import { GitReference, GitService, GitUri } from '../git/gitService';
import {
	BranchNode,
	BranchTrackingStatusNode,
	CommitFileNode,
	CommitNode,
	CompareBranchNode,
	CompareResultsNode,
	ContributorNode,
	FileHistoryNode,
	FolderNode,
	LineHistoryNode,
	nodeSupportsPaging,
	PagerNode,
	RemoteNode,
	RepositoryNode,
	ResultsFileNode,
	ResultsFilesNode,
	StashFileNode,
	StashNode,
	StatusFileNode,
	TagNode,
	ViewNode,
	ViewRefFileNode,
	ViewRefNode,
	viewSupportsNodeDismissal
} from './nodes';
import { Strings } from '../system/string';
import { runGitCommandInTerminal } from '../terminal';

interface CompareSelectedInfo {
	ref: string;
	repoPath: string | undefined;
	uri?: Uri;
}

export class ViewCommands {
	constructor() {
		commands.registerCommand(
			'gitlens.views.copy',
			async (selection: ViewNode[]) => {
				if (selection.length === 0) return;

				const data = selection
					.filter(n => n.toClipboard !== undefined)
					.map(n => n.toClipboard!())
					.join(',');
				await env.clipboard.writeText(data);
			},
			this
		);
		commands.registerCommand(
			'gitlens.views.refreshNode',
			(node: ViewNode, reset?: boolean) => {
				if (reset === undefined && nodeSupportsPaging(node)) {
					node.maxCount = undefined;
					node.view.resetNodeLastMaxCount(node);
				}

				return node.view.refreshNode(node, reset === undefined ? true : reset);
			},
			this
		);
		commands.registerCommand(
			'gitlens.views.expandNode',
			(node: ViewNode) => node.view.reveal(node, { select: false, focus: false, expand: 3 }),
			this
		);
		commands.registerCommand(
			'gitlens.views.dismissNode',
			(node: ViewNode) => viewSupportsNodeDismissal(node.view) && node.view.dismissNode(node),
			this
		);
		commands.registerCommand('gitlens.views.executeNodeCallback', (fn: <R>() => Promise<R>) => fn(), this);
		commands.registerCommand('gitlens.views.showMoreChildren', (node: PagerNode) => node.showMore(), this);
		commands.registerCommand('gitlens.views.showAllChildren', (node: PagerNode) => node.showAll(), this);

		commands.registerCommand('gitlens.views.fetch', this.fetch, this);
		commands.registerCommand('gitlens.views.pull', this.pull, this);
		commands.registerCommand('gitlens.views.push', this.push, this);
		commands.registerCommand('gitlens.views.pushWithForce', n => this.push(n, true), this);
		commands.registerCommand('gitlens.views.closeRepository', this.closeRepository, this);

		commands.registerCommand('gitlens.views.setAsDefault', this.setAsDefault, this);
		commands.registerCommand('gitlens.views.unsetAsDefault', this.unsetAsDefault, this);

		commands.registerCommand('gitlens.views.openInTerminal', this.openInTerminal, this);
		commands.registerCommand('gitlens.views.star', this.star, this);
		commands.registerCommand('gitlens.views.unstar', this.unstar, this);

		commands.registerCommand('gitlens.views.exploreRepoRevision', this.exploreRepoRevision, this);

		commands.registerCommand('gitlens.views.contributor.addCoauthoredBy', this.contributorAddCoauthoredBy, this);
		commands.registerCommand('gitlens.views.contributor.copyToClipboard', this.contributorCopyToClipboard, this);

		commands.registerCommand('gitlens.views.openChanges', this.openChanges, this);
		commands.registerCommand('gitlens.views.openChangesWithWorking', this.openChangesWithWorking, this);
		commands.registerCommand('gitlens.views.openFile', this.openFile, this);
		commands.registerCommand('gitlens.views.openFileRevision', this.openFileRevision, this);
		commands.registerCommand('gitlens.views.openFileRevisionInRemote', this.openFileRevisionInRemote, this);
		commands.registerCommand('gitlens.views.openChangedFiles', this.openChangedFiles, this);
		commands.registerCommand('gitlens.views.openChangedFileChanges', this.openChangedFileChanges, this);
		commands.registerCommand(
			'gitlens.views.openChangedFileChangesWithWorking',
			this.openChangedFileChangesWithWorking,
			this
		);
		commands.registerCommand('gitlens.views.openChangedFileRevisions', this.openChangedFileRevisions, this);
		commands.registerCommand('gitlens.views.applyChanges', this.applyChanges, this);
		commands.registerCommand('gitlens.views.restore', this.restore, this);
		commands.registerCommand('gitlens.views.switchToBranch', this.switch, this);
		commands.registerCommand('gitlens.views.switchToCommit', this.switch, this);
		commands.registerCommand('gitlens.views.switchToTag', this.switch, this);
		commands.registerCommand('gitlens.views.addRemote', this.addRemote, this);
		commands.registerCommand('gitlens.views.pruneRemote', this.pruneRemote, this);

		commands.registerCommand('gitlens.views.stageDirectory', this.stageDirectory, this);
		commands.registerCommand('gitlens.views.stageFile', this.stageFile, this);
		commands.registerCommand('gitlens.views.unstageDirectory', this.unstageDirectory, this);
		commands.registerCommand('gitlens.views.unstageFile', this.unstageFile, this);

		commands.registerCommand('gitlens.views.compareAncestryWithWorking', this.compareAncestryWithWorking, this);
		commands.registerCommand('gitlens.views.compareWithHead', this.compareWithHead, this);
		commands.registerCommand('gitlens.views.compareWithRemote', this.compareWithRemote, this);
		commands.registerCommand('gitlens.views.compareWithSelected', this.compareWithSelected, this);
		commands.registerCommand('gitlens.views.selectForCompare', this.selectForCompare, this);
		commands.registerCommand('gitlens.views.compareFileWithSelected', this.compareFileWithSelected, this);
		commands.registerCommand('gitlens.views.selectFileForCompare', this.selectFileForCompare, this);
		commands.registerCommand('gitlens.views.compareWithWorking', this.compareWithWorking, this);

		commands.registerCommand('gitlens.views.setComparisonToTwoDot', n => this.setComparisonNotation(n, '..'), this);
		commands.registerCommand(
			'gitlens.views.setComparisonToThreeDot',
			n => this.setComparisonNotation(n, '...'),
			this
		);

		commands.registerCommand('gitlens.views.cherryPick', this.cherryPick, this);

		commands.registerCommand('gitlens.views.mergeBranchInto', this.merge, this);

		commands.registerCommand('gitlens.views.rebaseOntoBranch', this.rebase, this);
		commands.registerCommand('gitlens.views.rebaseOntoUpstream', this.rebaseToRemote, this);
		commands.registerCommand('gitlens.views.rebaseOntoCommit', this.rebase, this);

		commands.registerCommand('gitlens.views.reset', this.reset, this);
		commands.registerCommand('gitlens.views.revert', this.revert, this);

		commands.registerCommand('gitlens.views.terminalPushCommit', this.terminalPushCommit, this);

		commands.registerCommand('gitlens.views.terminalCreateBranch', this.terminalCreateBranch, this);
		commands.registerCommand('gitlens.views.terminalDeleteBranch', this.terminalDeleteBranch, this);
		commands.registerCommand('gitlens.views.terminalRemoveRemote', this.terminalRemoveRemote, this);
		commands.registerCommand('gitlens.views.terminalCreateTag', this.terminalCreateTag, this);
		commands.registerCommand('gitlens.views.terminalDeleteTag', this.terminalDeleteTag, this);
	}

	private async contributorAddCoauthoredBy(node: ContributorNode) {
		if (!(node instanceof ContributorNode)) return;

		const gitApi = await GitService.getBuiltInGitApi();
		if (gitApi === undefined) return;

		const repo = gitApi.repositories.find(
			r => Strings.normalizePath(r.rootUri.fsPath) === node.contributor.repoPath
		);
		if (repo === undefined) return;

		const coauthor = `${node.contributor.name}${node.contributor.email ? ` <${node.contributor.email}>` : ''}`;

		const message = repo.inputBox.value;
		if (message.includes(coauthor)) return;

		let newlines;
		if (message.includes('Co-authored-by: ')) {
			newlines = '\n';
		} else if (message.length !== 0 && message.endsWith('\n')) {
			newlines = '\n\n';
		} else {
			newlines = '\n\n\n';
		}

		repo.inputBox.value = `${message}${newlines}Co-authored-by: ${coauthor}`;
	}

	private async contributorCopyToClipboard(node: ContributorNode) {
		if (!(node instanceof ContributorNode)) return;

		await env.clipboard.writeText(
			`${node.contributor.name}${node.contributor.email ? ` <${node.contributor.email}>` : ''}`
		);
	}

	private fetch(node: RemoteNode | RepositoryNode) {
		if (node instanceof RemoteNode) return node.fetch();
		if (node instanceof RepositoryNode) {
			const args: GitCommandsCommandArgs = { command: 'fetch', state: { repos: [node.repo] } };
			return commands.executeCommand(Commands.GitCommands, args);
		}

		return undefined;
	}

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

		const args: GitCommandsCommandArgs = { command: 'pull', state: { repos: [node.repo] } };
		return commands.executeCommand(Commands.GitCommands, args);
	}

	private push(node: RepositoryNode | BranchTrackingStatusNode, force?: boolean) {
		if (node instanceof BranchTrackingStatusNode) {
			node = node.getParent() as RepositoryNode;
		}
		if (!(node instanceof RepositoryNode)) return undefined;

		const args: GitCommandsCommandArgs = { command: 'push', state: { repos: [node.repo] } };
		return commands.executeCommand(Commands.GitCommands, args);
	}

	private async applyChanges(node: ViewRefFileNode) {
		if (!(node instanceof ViewRefFileNode)) return;

		void (await this.openFile(node));

		if (node instanceof ResultsFileNode) {
			void (await Container.git.applyChangesToWorkingFile(node.uri, node.ref1, node.ref2));

			return;
		}

		if (node.uri.sha !== undefined && node.uri.sha !== 'HEAD') {
			void (await Container.git.applyChangesToWorkingFile(node.uri));
		}
	}

	private async cherryPick(node: CommitNode) {
		if (!(node instanceof CommitNode)) return undefined;

		const repo = await Container.git.getRepository(node.repoPath);

		const args: GitCommandsCommandArgs = {
			command: 'cherry-pick',
			state: { repo: repo!, references: [GitReference.create(node.ref)] }
		};
		return commands.executeCommand(Commands.GitCommands, args);
	}

	private async merge(node: BranchNode | TagNode) {
		if (!(node instanceof BranchNode) && !(node instanceof TagNode)) return undefined;

		const repo = await Container.git.getRepository(node.repoPath);

		const args: GitCommandsCommandArgs = {
			command: 'merge',
			state: { repo: repo!, reference: node instanceof BranchNode ? node.branch : node.tag }
		};
		return commands.executeCommand(Commands.GitCommands, args);
	}

	private async rebase(node: BranchNode | CommitNode | TagNode) {
		if (!(node instanceof BranchNode) && !(node instanceof CommitNode) && !(node instanceof TagNode)) {
			return undefined;
		}

		const repo = await Container.git.getRepository(node.repoPath);

		let args: GitCommandsCommandArgs;
		if (node instanceof CommitNode) {
			args = {
				command: 'rebase',
				state: {
					repo: repo!,
					reference: GitReference.create(node.ref)
				}
			};
		} else {
			args = {
				command: 'rebase',
				state: {
					repo: repo!,
					reference: node instanceof BranchNode ? node.branch : node.tag
				}
			};
		}
		return commands.executeCommand(Commands.GitCommands, args);
	}

	private async rebaseToRemote(node: BranchNode | BranchTrackingStatusNode) {
		if (!(node instanceof BranchNode) && !(node instanceof BranchTrackingStatusNode)) return undefined;

		const upstream = node instanceof BranchNode ? node.branch.tracking : node.status.upstream;
		if (upstream === undefined) return undefined;

		const repo = await Container.git.getRepository(node.repoPath);

		const args: GitCommandsCommandArgs = {
			command: 'rebase',
			state: { repo: repo!, reference: GitReference.create(upstream, { name: upstream, refType: 'branch' }) }
		};
		return commands.executeCommand(Commands.GitCommands, args);
	}

	private async reset(node: CommitNode) {
		if (!(node instanceof CommitNode)) return undefined;

		const repo = await Container.git.getRepository(node.repoPath);

		const args: GitCommandsCommandArgs = {
			command: 'reset',
			state: { repo: repo!, reference: GitReference.create(node.ref) }
		};
		return commands.executeCommand(Commands.GitCommands, args);
	}

	private async restore(node: ViewRefFileNode) {
		if (!(node instanceof ViewRefFileNode)) return undefined;

		return Container.git.checkout(node.repoPath, node.ref, { fileName: node.fileName });
	}

	private async revert(node: CommitNode) {
		if (!(node instanceof CommitNode)) return undefined;

		const repo = await Container.git.getRepository(node.repoPath);

		const args: GitCommandsCommandArgs = {
			command: 'revert',
			state: { repo: repo!, references: [GitReference.create(node.ref)] }
		};
		return commands.executeCommand(Commands.GitCommands, args);
	}

	private async switch(node: ViewRefNode) {
		if (!(node instanceof ViewRefNode)) return undefined;

		const repo = await Container.git.getRepository(node.repoPath);

		let args: GitCommandsCommandArgs;
		if (node instanceof BranchNode) {
			args = {
				command: 'switch',
				state: { repos: [repo!], reference: node.branch.current ? undefined : node.branch }
			};
		} else if (node instanceof TagNode) {
			args = { command: 'switch', state: { repos: [repo!], reference: node.tag } };
		} else {
			args = {
				command: 'switch',
				state: { repos: [repo!], reference: GitReference.create(node.ref) }
			};
		}

		return commands.executeCommand(Commands.GitCommands, args);
	}

	private async addRemote(node: RemoteNode) {
		const name = await window.showInputBox({
			prompt: 'Please provide a name for the remote',
			placeHolder: 'Remote name',
			value: undefined,
			ignoreFocusOut: true
		});
		if (name === undefined || name.length === 0) return undefined;

		const url = await window.showInputBox({
			prompt: 'Please provide the repository url for the remote',
			placeHolder: 'Remote repository url',
			value: undefined,
			ignoreFocusOut: true
		});
		if (url === undefined || url.length === 0) return undefined;

		void (await Container.git.addRemote(node.repo.path, name, url));
		void (await node.repo.fetch({ remote: name }));

		return name;
	}

	private async pruneRemote(node: RemoteNode) {
		const remoteName = node.remote.name;
		void (await Container.git.pruneRemote(node.repo.path, remoteName));

		return remoteName;
	}

	private closeRepository(node: RepositoryNode) {
		if (!(node instanceof RepositoryNode)) return;

		node.repo.closed = true;
	}

	private compareWithHead(node: ViewRefNode) {
		if (!(node instanceof ViewRefNode)) return undefined;

		return Container.compareView.compare(node.repoPath, node.ref, 'HEAD');
	}

	private compareWithRemote(node: BranchNode) {
		if (!(node instanceof BranchNode)) return undefined;
		if (!node.branch.tracking) return undefined;

		return Container.compareView.compare(node.repoPath, node.branch.tracking, node.ref);
	}

	private compareWithWorking(node: ViewRefNode) {
		if (!(node instanceof ViewRefNode)) return undefined;

		return Container.compareView.compare(node.repoPath, node.ref, '');
	}

	private async compareAncestryWithWorking(node: BranchNode) {
		if (!(node instanceof BranchNode)) return undefined;

		const branch = await Container.git.getBranch(node.repoPath);
		if (branch === undefined) return undefined;

		const commonAncestor = await Container.git.getMergeBase(node.repoPath, branch.ref, node.ref);
		if (commonAncestor === undefined) return undefined;

		return Container.compareView.compare(
			node.repoPath,
			{ ref: commonAncestor, label: `ancestry with ${node.ref} (${GitService.shortenSha(commonAncestor)})` },
			''
		);
	}

	private compareWithSelected(node: ViewRefNode) {
		if (!(node instanceof ViewRefNode)) return;

		Container.compareView.compareWithSelected(node.repoPath, node.ref);
	}

	private selectForCompare(node: ViewRefNode) {
		if (!(node instanceof ViewRefNode)) return;

		Container.compareView.selectForCompare(node.repoPath, node.ref);
	}

	private compareFileWithSelected(node: ViewRefFileNode) {
		if (this._selectedFile === undefined || !(node instanceof ViewRefFileNode) || node.ref === undefined) {
			return undefined;
		}

		if (this._selectedFile.repoPath !== node.repoPath) {
			this.selectFileForCompare(node);
			return undefined;
		}

		const selected = this._selectedFile;

		this._selectedFile = undefined;
		setCommandContext(CommandContext.ViewsCanCompareFile, false);

		const diffArgs: DiffWithCommandArgs = {
			repoPath: selected.repoPath,
			lhs: {
				sha: selected.ref,
				uri: selected.uri!
			},
			rhs: {
				sha: node.ref,
				uri: node.uri
			}
		};
		return commands.executeCommand(Commands.DiffWith, diffArgs);
	}

	private _selectedFile: CompareSelectedInfo | undefined;

	private selectFileForCompare(node: ViewRefFileNode) {
		if (!(node instanceof ViewRefFileNode) || node.ref === undefined) return;

		this._selectedFile = {
			ref: node.ref,
			repoPath: node.repoPath,
			uri: node.uri
		};
		setCommandContext(CommandContext.ViewsCanCompareFile, true);
	}

	private exploreRepoRevision(node: ViewRefNode, options: { openInNewWindow?: boolean } = {}) {
		if (!(node instanceof ViewRefNode)) return;

		if (options == null) {
			options = {};
		}

		const uri = toGitLensFSUri(node.ref, node.repoPath);
		const gitUri = GitUri.fromRevisionUri(uri);

		openWorkspace(uri, `${paths.basename(gitUri.repoPath!)} @ ${gitUri.shortSha}`, options);

		void commands.executeCommand(BuiltInCommands.FocusFilesExplorer);
	}

	private openChanges(node: ViewRefFileNode | StatusFileNode) {
		if (!(node instanceof ViewRefFileNode) && !(node instanceof StatusFileNode)) return undefined;

		const command = node.getCommand();
		if (command === undefined || command.arguments === undefined) return undefined;

		const [uri, args] = command.arguments as [Uri, DiffWithPreviousCommandArgs];
		args.showOptions!.preview = false;
		return commands.executeCommand(command.command, uri, args);
	}

	private openChangesWithWorking(node: ViewRefFileNode | StatusFileNode) {
		if (!(node instanceof ViewRefFileNode) && !(node instanceof StatusFileNode)) return undefined;

		const args: DiffWithWorkingCommandArgs = {
			showOptions: {
				preserveFocus: true,
				preview: false
			}
		};
		return commands.executeCommand(Commands.DiffWithWorking, node.uri, args);
{'fraction_non_alphanumeric': 0.12777043110288283, 'fraction_numerical': 0.00037027241470510445, 'mean_word_length': 5.23840950338228, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '891891', 'n_tokens_mistral': 12501, 'n_tokens_neox': 11171, 'n_words': 2345}