mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 04:09:28 +00:00
Git - refactor create/delete worktree and expose extension API (#278107)
* Git - refactor create/delete worktree and expose extension API * Pull request feedback * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -318,6 +318,14 @@ export class ApiRepository implements Repository {
|
|||||||
dropStash(index?: number): Promise<void> {
|
dropStash(index?: number): Promise<void> {
|
||||||
return this.#repository.dropStash(index);
|
return this.#repository.dropStash(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createWorktree(options?: { path?: string; commitish?: string; branch?: string }): Promise<string> {
|
||||||
|
return this.#repository.createWorktree(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteWorktree(path: string, options?: { force?: boolean }): Promise<void> {
|
||||||
|
return this.#repository.deleteWorktree(path, options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ApiGit implements Git {
|
export class ApiGit implements Git {
|
||||||
|
|||||||
3
extensions/git/src/api/git.d.ts
vendored
3
extensions/git/src/api/git.d.ts
vendored
@@ -289,6 +289,9 @@ export interface Repository {
|
|||||||
applyStash(index?: number): Promise<void>;
|
applyStash(index?: number): Promise<void>;
|
||||||
popStash(index?: number): Promise<void>;
|
popStash(index?: number): Promise<void>;
|
||||||
dropStash(index?: number): Promise<void>;
|
dropStash(index?: number): Promise<void>;
|
||||||
|
|
||||||
|
createWorktree(options?: { path?: string; commitish?: string; branch?: string }): Promise<string>;
|
||||||
|
deleteWorktree(path: string, options?: { force?: boolean }): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RemoteSource {
|
export interface RemoteSource {
|
||||||
|
|||||||
@@ -762,8 +762,6 @@ export class CommandCenter {
|
|||||||
private disposables: Disposable[];
|
private disposables: Disposable[];
|
||||||
private commandErrors = new CommandErrorOutputTextDocumentContentProvider();
|
private commandErrors = new CommandErrorOutputTextDocumentContentProvider();
|
||||||
|
|
||||||
private static readonly WORKTREE_ROOT_KEY = 'worktreeRoot';
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private git: Git,
|
private git: Git,
|
||||||
private model: Model,
|
private model: Model,
|
||||||
@@ -3500,119 +3498,47 @@ export class CommandCenter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@command('git.createWorktreeWithDefaults', { repository: true, repositoryFilter: ['repository'] })
|
@command('git.createWorktree', { repository: true, repositoryFilter: ['repository', 'submodule'] })
|
||||||
async createWorktreeWithDefaults(
|
|
||||||
repository: Repository,
|
|
||||||
commitish: string = 'HEAD'
|
|
||||||
): Promise<string | undefined> {
|
|
||||||
const config = workspace.getConfiguration('git');
|
|
||||||
const branchPrefix = config.get<string>('branchPrefix', '');
|
|
||||||
|
|
||||||
// Generate branch name if not provided
|
|
||||||
let branch = await this.generateRandomBranchName(repository, '-');
|
|
||||||
if (!branch) {
|
|
||||||
// Fallback to timestamp-based name if random generation fails
|
|
||||||
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
||||||
branch = `${branchPrefix}worktree-${timestamp}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure branch name starts with prefix if configured
|
|
||||||
if (branchPrefix && !branch.startsWith(branchPrefix)) {
|
|
||||||
branch = branchPrefix + branch;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create worktree name from branch name
|
|
||||||
const worktreeName = branch.startsWith(branchPrefix)
|
|
||||||
? branch.substring(branchPrefix.length).replace(/\//g, '-')
|
|
||||||
: branch.replace(/\//g, '-');
|
|
||||||
|
|
||||||
// Determine default worktree path
|
|
||||||
const defaultWorktreeRoot = this.globalState.get<string>(`${CommandCenter.WORKTREE_ROOT_KEY}:${repository.root}`);
|
|
||||||
const defaultWorktreePath = defaultWorktreeRoot
|
|
||||||
? path.join(defaultWorktreeRoot, worktreeName)
|
|
||||||
: path.join(path.dirname(repository.root), `${path.basename(repository.root)}.worktrees`, worktreeName);
|
|
||||||
|
|
||||||
// Check if worktree already exists at this path
|
|
||||||
const existingWorktree = repository.worktrees.find(worktree =>
|
|
||||||
pathEquals(path.normalize(worktree.path), path.normalize(defaultWorktreePath))
|
|
||||||
);
|
|
||||||
|
|
||||||
if (existingWorktree) {
|
|
||||||
// Generate unique path by appending a number
|
|
||||||
let counter = 1;
|
|
||||||
let uniquePath = `${defaultWorktreePath}-${counter}`;
|
|
||||||
while (repository.worktrees.some(wt => pathEquals(path.normalize(wt.path), path.normalize(uniquePath)))) {
|
|
||||||
counter++;
|
|
||||||
uniquePath = `${defaultWorktreePath}-${counter}`;
|
|
||||||
}
|
|
||||||
const finalWorktreePath = uniquePath;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await repository.addWorktree({ path: finalWorktreePath, branch, commitish });
|
|
||||||
|
|
||||||
// Update worktree root in global state
|
|
||||||
const worktreeRoot = path.dirname(finalWorktreePath);
|
|
||||||
if (worktreeRoot !== defaultWorktreeRoot) {
|
|
||||||
this.globalState.update(`${CommandCenter.WORKTREE_ROOT_KEY}:${repository.root}`, worktreeRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
return finalWorktreePath;
|
|
||||||
} catch (err) {
|
|
||||||
// Return undefined on failure
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await repository.addWorktree({ path: defaultWorktreePath, branch, commitish });
|
|
||||||
|
|
||||||
// Update worktree root in global state
|
|
||||||
const worktreeRoot = path.dirname(defaultWorktreePath);
|
|
||||||
if (worktreeRoot !== defaultWorktreeRoot) {
|
|
||||||
this.globalState.update(`${CommandCenter.WORKTREE_ROOT_KEY}:${repository.root}`, worktreeRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultWorktreePath;
|
|
||||||
} catch (err) {
|
|
||||||
// Return undefined on failure
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@command('git.createWorktree', { repository: true })
|
|
||||||
async createWorktree(repository?: Repository): Promise<void> {
|
async createWorktree(repository?: Repository): Promise<void> {
|
||||||
if (!repository) {
|
|
||||||
// Single repository/submodule/worktree
|
|
||||||
if (this.model.repositories.length === 1) {
|
|
||||||
repository = this.model.repositories[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!repository) {
|
|
||||||
// Single repository/submodule
|
|
||||||
const repositories = this.model.repositories
|
|
||||||
.filter(r => r.kind === 'repository' || r.kind === 'submodule');
|
|
||||||
|
|
||||||
if (repositories.length === 1) {
|
|
||||||
repository = repositories[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!repository) {
|
|
||||||
// Multiple repositories/submodules
|
|
||||||
repository = await this.model.pickRepository(['repository', 'submodule']);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!repository) {
|
if (!repository) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this._createWorktree(repository);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _createWorktree(repository: Repository): Promise<void> {
|
|
||||||
const config = workspace.getConfiguration('git');
|
const config = workspace.getConfiguration('git');
|
||||||
const branchPrefix = config.get<string>('branchPrefix')!;
|
const branchPrefix = config.get<string>('branchPrefix')!;
|
||||||
|
|
||||||
|
// Get commitish and branch for the new worktree
|
||||||
|
const worktreeDetails = await this.getWorktreeCommitishAndBranch(repository);
|
||||||
|
if (!worktreeDetails) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { commitish, branch } = worktreeDetails;
|
||||||
|
const worktreeName = ((branch ?? commitish).startsWith(branchPrefix)
|
||||||
|
? (branch ?? commitish).substring(branchPrefix.length).replace(/\//g, '-')
|
||||||
|
: (branch ?? commitish).replace(/\//g, '-'));
|
||||||
|
|
||||||
|
// Get path for the new worktree
|
||||||
|
const worktreePath = await this.getWorktreePath(repository, worktreeName);
|
||||||
|
if (!worktreePath) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await repository.createWorktree({ path: worktreePath, branch, commitish: commitish });
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof GitError && err.gitErrorCode === GitErrorCodes.WorktreeAlreadyExists) {
|
||||||
|
await this.handleWorktreeAlreadyExists(err);
|
||||||
|
} else if (err instanceof GitError && err.gitErrorCode === GitErrorCodes.WorktreeBranchAlreadyUsed) {
|
||||||
|
await this.handleWorktreeBranchAlreadyUsed(err);
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getWorktreeCommitishAndBranch(repository: Repository): Promise<{ commitish: string; branch: string | undefined } | undefined> {
|
||||||
|
const config = workspace.getConfiguration('git', Uri.file(repository.root));
|
||||||
const showRefDetails = config.get<boolean>('showReferenceDetails') === true;
|
const showRefDetails = config.get<boolean>('showReferenceDetails') === true;
|
||||||
|
|
||||||
const createBranch = new CreateBranchItem();
|
const createBranch = new CreateBranchItem();
|
||||||
@@ -3631,23 +3557,21 @@ export class CommandCenter {
|
|||||||
const choice = await this.pickRef(getBranchPicks(), placeHolder);
|
const choice = await this.pickRef(getBranchPicks(), placeHolder);
|
||||||
|
|
||||||
if (!choice) {
|
if (!choice) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let branch: string | undefined = undefined;
|
|
||||||
let commitish: string;
|
|
||||||
|
|
||||||
if (choice === createBranch) {
|
if (choice === createBranch) {
|
||||||
branch = await this.promptForBranchName(repository);
|
// Create new branch
|
||||||
|
const branch = await this.promptForBranchName(repository);
|
||||||
if (!branch) {
|
if (!branch) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
commitish = 'HEAD';
|
return { commitish: 'HEAD', branch };
|
||||||
} else {
|
} else {
|
||||||
|
// Existing reference
|
||||||
if (!(choice instanceof RefItem) || !choice.refName) {
|
if (!(choice instanceof RefItem) || !choice.refName) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (choice.refName === repository.HEAD?.name) {
|
if (choice.refName === repository.HEAD?.name) {
|
||||||
@@ -3656,15 +3580,14 @@ export class CommandCenter {
|
|||||||
const pick = await window.showWarningMessage(message, { modal: true }, createBranch);
|
const pick = await window.showWarningMessage(message, { modal: true }, createBranch);
|
||||||
|
|
||||||
if (pick === createBranch) {
|
if (pick === createBranch) {
|
||||||
branch = await this.promptForBranchName(repository);
|
const branch = await this.promptForBranchName(repository);
|
||||||
|
|
||||||
if (!branch) {
|
if (!branch) {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
commitish = 'HEAD';
|
return { commitish: 'HEAD', branch };
|
||||||
} else {
|
} else {
|
||||||
return;
|
return undefined;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Check whether the selected branch is checked out in an existing worktree
|
// Check whether the selected branch is checked out in an existing worktree
|
||||||
@@ -3674,17 +3597,14 @@ export class CommandCenter {
|
|||||||
await this.handleWorktreeConflict(worktree.path, message);
|
await this.handleWorktreeConflict(worktree.path, message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
commitish = choice.refName;
|
return { commitish: choice.refName, branch: undefined };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const worktreeName = ((branch ?? commitish).startsWith(branchPrefix)
|
private async getWorktreePath(repository: Repository, worktreeName: string): Promise<string | undefined> {
|
||||||
? (branch ?? commitish).substring(branchPrefix.length).replace(/\//g, '-')
|
|
||||||
: (branch ?? commitish).replace(/\//g, '-'));
|
|
||||||
|
|
||||||
// If user selects folder button, they manually select the worktree path through folder picker
|
|
||||||
const getWorktreePath = async (): Promise<string | undefined> => {
|
const getWorktreePath = async (): Promise<string | undefined> => {
|
||||||
const worktreeRoot = this.globalState.get<string>(`${CommandCenter.WORKTREE_ROOT_KEY}:${repository.root}`);
|
const worktreeRoot = this.globalState.get<string>(`${Repository.WORKTREE_ROOT_STORAGE_KEY}:${repository.root}`);
|
||||||
const defaultUri = worktreeRoot ? Uri.file(worktreeRoot) : Uri.file(path.dirname(repository.root));
|
const defaultUri = worktreeRoot ? Uri.file(worktreeRoot) : Uri.file(path.dirname(repository.root));
|
||||||
|
|
||||||
const uris = await window.showOpenDialog({
|
const uris = await window.showOpenDialog({
|
||||||
@@ -3720,7 +3640,7 @@ export class CommandCenter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Default worktree path is based on the last worktree location or a worktree folder for the repository
|
// Default worktree path is based on the last worktree location or a worktree folder for the repository
|
||||||
const defaultWorktreeRoot = this.globalState.get<string>(`${CommandCenter.WORKTREE_ROOT_KEY}:${repository.root}`);
|
const defaultWorktreeRoot = this.globalState.get<string>(`${Repository.WORKTREE_ROOT_STORAGE_KEY}:${repository.root}`);
|
||||||
const defaultWorktreePath = defaultWorktreeRoot
|
const defaultWorktreePath = defaultWorktreeRoot
|
||||||
? path.join(defaultWorktreeRoot, worktreeName)
|
? path.join(defaultWorktreeRoot, worktreeName)
|
||||||
: path.join(path.dirname(repository.root), `${path.basename(repository.root)}.worktrees`, worktreeName);
|
: path.join(path.dirname(repository.root), `${path.basename(repository.root)}.worktrees`, worktreeName);
|
||||||
@@ -3759,29 +3679,7 @@ export class CommandCenter {
|
|||||||
|
|
||||||
dispose(disposables);
|
dispose(disposables);
|
||||||
|
|
||||||
if (!worktreePath) {
|
return worktreePath;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await repository.addWorktree({ path: worktreePath, branch, commitish: commitish });
|
|
||||||
|
|
||||||
// Update worktree root in global state
|
|
||||||
const worktreeRoot = path.dirname(worktreePath);
|
|
||||||
if (worktreeRoot !== defaultWorktreeRoot) {
|
|
||||||
this.globalState.update(`${CommandCenter.WORKTREE_ROOT_KEY}:${repository.root}`, worktreeRoot);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof GitError && err.gitErrorCode === GitErrorCodes.WorktreeAlreadyExists) {
|
|
||||||
await this.handleWorktreeAlreadyExists(err);
|
|
||||||
} else if (err instanceof GitError && err.gitErrorCode === GitErrorCodes.WorktreeBranchAlreadyUsed) {
|
|
||||||
await this.handleWorktreeBranchAlreadyUsed(err);
|
|
||||||
} else {
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleWorktreeBranchAlreadyUsed(err: GitError): Promise<void> {
|
private async handleWorktreeBranchAlreadyUsed(err: GitError): Promise<void> {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import TelemetryReporter from '@vscode/extension-telemetry';
|
|||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import picomatch from 'picomatch';
|
import picomatch from 'picomatch';
|
||||||
|
import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator';
|
||||||
import { CancellationError, CancellationToken, CancellationTokenSource, Command, commands, Disposable, Event, EventEmitter, FileDecoration, FileType, l10n, LogLevel, LogOutputChannel, Memento, ProgressLocation, ProgressOptions, QuickDiffProvider, RelativePattern, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, TabInputNotebookDiff, TabInputTextDiff, TabInputTextMultiDiff, ThemeColor, ThemeIcon, Uri, window, workspace, WorkspaceEdit } from 'vscode';
|
import { CancellationError, CancellationToken, CancellationTokenSource, Command, commands, Disposable, Event, EventEmitter, FileDecoration, FileType, l10n, LogLevel, LogOutputChannel, Memento, ProgressLocation, ProgressOptions, QuickDiffProvider, RelativePattern, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, TabInputNotebookDiff, TabInputTextDiff, TabInputTextMultiDiff, ThemeColor, ThemeIcon, Uri, window, workspace, WorkspaceEdit } from 'vscode';
|
||||||
import { ActionButton } from './actionButton';
|
import { ActionButton } from './actionButton';
|
||||||
import { ApiRepository } from './api/api1';
|
import { ApiRepository } from './api/api1';
|
||||||
@@ -696,6 +697,7 @@ export interface IRepositoryResolver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Repository implements Disposable {
|
export class Repository implements Disposable {
|
||||||
|
static readonly WORKTREE_ROOT_STORAGE_KEY = 'worktreeRoot';
|
||||||
|
|
||||||
private _onDidChangeRepository = new EventEmitter<Uri>();
|
private _onDidChangeRepository = new EventEmitter<Uri>();
|
||||||
readonly onDidChangeRepository: Event<Uri> = this._onDidChangeRepository.event;
|
readonly onDidChangeRepository: Event<Uri> = this._onDidChangeRepository.event;
|
||||||
@@ -896,7 +898,7 @@ export class Repository implements Disposable {
|
|||||||
postCommitCommandsProviderRegistry: IPostCommitCommandsProviderRegistry,
|
postCommitCommandsProviderRegistry: IPostCommitCommandsProviderRegistry,
|
||||||
private readonly branchProtectionProviderRegistry: IBranchProtectionProviderRegistry,
|
private readonly branchProtectionProviderRegistry: IBranchProtectionProviderRegistry,
|
||||||
historyItemDetailProviderRegistry: ISourceControlHistoryItemDetailsProviderRegistry,
|
historyItemDetailProviderRegistry: ISourceControlHistoryItemDetailsProviderRegistry,
|
||||||
globalState: Memento,
|
private readonly globalState: Memento,
|
||||||
private readonly logger: LogOutputChannel,
|
private readonly logger: LogOutputChannel,
|
||||||
private telemetryReporter: TelemetryReporter,
|
private telemetryReporter: TelemetryReporter,
|
||||||
private readonly repositoryCache: RepositoryCache
|
private readonly repositoryCache: RepositoryCache
|
||||||
@@ -1797,8 +1799,57 @@ export class Repository implements Disposable {
|
|||||||
await this.run(Operation.DeleteTag, () => this.repository.deleteTag(name));
|
await this.run(Operation.DeleteTag, () => this.repository.deleteTag(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
async addWorktree(options: { path: string; commitish: string; branch?: string }): Promise<void> {
|
async createWorktree(options?: { path?: string; commitish?: string; branch?: string }): Promise<string> {
|
||||||
await this.run(Operation.Worktree, () => this.repository.addWorktree(options));
|
const defaultWorktreeRoot = this.globalState.get<string>(`${Repository.WORKTREE_ROOT_STORAGE_KEY}:${this.root}`);
|
||||||
|
let { path: worktreePath, commitish, branch } = options || {};
|
||||||
|
let worktreeName: string | undefined;
|
||||||
|
|
||||||
|
return await this.run(Operation.Worktree, async () => {
|
||||||
|
// Generate branch name if not provided
|
||||||
|
if (branch === undefined) {
|
||||||
|
const config = workspace.getConfiguration('git', Uri.file(this.root));
|
||||||
|
const branchPrefix = config.get<string>('branchPrefix', '');
|
||||||
|
|
||||||
|
let worktreeName = await this.getRandomBranchName();
|
||||||
|
if (!worktreeName) {
|
||||||
|
// Fallback to timestamp-based name if random generation fails
|
||||||
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
||||||
|
worktreeName = `worktree-${timestamp}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
branch = `${branchPrefix}${worktreeName}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate path if not provided
|
||||||
|
if (worktreePath === undefined) {
|
||||||
|
worktreePath = defaultWorktreeRoot
|
||||||
|
? path.join(defaultWorktreeRoot, worktreeName!)
|
||||||
|
: path.join(path.dirname(this.root), `${path.basename(this.root)}.worktrees`, worktreeName!);
|
||||||
|
|
||||||
|
// Ensure that the worktree path is unique
|
||||||
|
if (this.worktrees.some(worktree => pathEquals(path.normalize(worktree.path), path.normalize(worktreePath!)))) {
|
||||||
|
let counter = 1;
|
||||||
|
let uniqueWorktreePath = `${worktreePath}-${counter}`;
|
||||||
|
while (this.worktrees.some(wt => pathEquals(path.normalize(wt.path), path.normalize(uniqueWorktreePath)))) {
|
||||||
|
counter++;
|
||||||
|
uniqueWorktreePath = `${worktreePath}-${counter}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
worktreePath = uniqueWorktreePath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the worktree
|
||||||
|
await this.repository.addWorktree({ path: worktreePath!, commitish: commitish ?? 'HEAD', branch });
|
||||||
|
|
||||||
|
// Update worktree root in global state
|
||||||
|
const newWorktreeRoot = path.dirname(worktreePath!);
|
||||||
|
if (defaultWorktreeRoot && !pathEquals(newWorktreeRoot, defaultWorktreeRoot)) {
|
||||||
|
this.globalState.update(`${Repository.WORKTREE_ROOT_STORAGE_KEY}:${this.root}`, newWorktreeRoot);
|
||||||
|
}
|
||||||
|
|
||||||
|
return worktreePath!;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteWorktree(path: string, options?: { force?: boolean }): Promise<void> {
|
async deleteWorktree(path: string, options?: { force?: boolean }): Promise<void> {
|
||||||
@@ -2988,6 +3039,51 @@ export class Repository implements Disposable {
|
|||||||
return this.unpublishedCommits;
|
return this.unpublishedCommits;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getRandomBranchName(): Promise<string | undefined> {
|
||||||
|
const config = workspace.getConfiguration('git', Uri.file(this.root));
|
||||||
|
const branchRandomNameEnabled = config.get<boolean>('branchRandomName.enable', false);
|
||||||
|
if (!branchRandomNameEnabled) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dictionaries: string[][] = [];
|
||||||
|
const branchWhitespaceChar = config.get<string>('branchWhitespaceChar', '-');
|
||||||
|
const branchRandomNameDictionary = config.get<string[]>('branchRandomName.dictionary', ['adjectives', 'animals']);
|
||||||
|
|
||||||
|
for (const dictionary of branchRandomNameDictionary) {
|
||||||
|
if (dictionary.toLowerCase() === 'adjectives') {
|
||||||
|
dictionaries.push(adjectives);
|
||||||
|
} else if (dictionary.toLowerCase() === 'animals') {
|
||||||
|
dictionaries.push(animals);
|
||||||
|
} else if (dictionary.toLowerCase() === 'colors') {
|
||||||
|
dictionaries.push(colors);
|
||||||
|
} else if (dictionary.toLowerCase() === 'numbers') {
|
||||||
|
dictionaries.push(NumberDictionary.generate({ length: 3 }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dictionaries.length === 0) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5 attempts to generate a random branch name
|
||||||
|
for (let index = 0; index < 5; index++) {
|
||||||
|
const randomName = uniqueNamesGenerator({
|
||||||
|
dictionaries,
|
||||||
|
length: dictionaries.length,
|
||||||
|
separator: branchWhitespaceChar
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check for local ref conflict
|
||||||
|
const refs = await this.getRefs({ pattern: `refs/heads/${randomName}` });
|
||||||
|
if (refs.length === 0) {
|
||||||
|
return randomName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
this.disposables = dispose(this.disposables);
|
this.disposables = dispose(this.disposables);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user