121 lines
3.6 KiB
TypeScript
121 lines
3.6 KiB
TypeScript
import { getGitBranches, getGitStatus } from '@/lib/gitApi';
|
|||
|
|
import type { CreateWorktreeArgs, ProjectRef } from '@/lib/worktrees/worktreeManager';
|
||
|
|
import { createWorktree } from '@/lib/worktrees/worktreeManager';
|
||
|
|
import { getRootBranch } from '@/lib/worktrees/worktreeStatus';
|
||
|
|
|
||
|
|
const parseTrackingRef = (tracking: string | null | undefined): { remote: string; branch: string } | null => {
|
||
|
|
const value = String(tracking || '').trim().replace(/^remotes\//, '');
|
||
|
|
if (!value) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
const separatorIndex = value.indexOf('/');
|
||
|
|
if (separatorIndex <= 0 || separatorIndex >= value.length - 1) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
remote: value.slice(0, separatorIndex),
|
||
|
|
branch: value.slice(separatorIndex + 1),
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
const normalizeBranchName = (value: string): string => {
|
||
|
|
return String(value || '')
|
||
|
|
.trim()
|
||
|
|
.replace(/^refs\/heads\//, '')
|
||
|
|
.replace(/^heads\//, '')
|
||
|
|
.replace(/^remotes\//, '');
|
||
|
|
};
|
||
|
|
|
||
|
|
const resolveLocalBranchName = (args: CreateWorktreeArgs): string => {
|
||
|
|
if (args.branchName) {
|
||
|
|
return normalizeBranchName(args.branchName);
|
||
|
|
}
|
||
|
|
if (args.mode === 'existing') {
|
||
|
|
return normalizeBranchName(args.existingBranch || args.preferredName || '');
|
||
|
|
}
|
||
|
|
return normalizeBranchName(args.preferredName || '');
|
||
|
|
};
|
||
|
|
|
||
|
|
export const resolveRootTrackingRemote = async (projectDirectory: string): Promise<string | null> => {
|
||
|
|
const rootBranch = await getRootBranch(projectDirectory);
|
||
|
|
|
||
|
|
try {
|
||
|
|
const branchState = await getGitBranches(projectDirectory);
|
||
|
|
const tracking = branchState.branches?.[rootBranch]?.tracking || null;
|
||
|
|
const parsed = parseTrackingRef(tracking);
|
||
|
|
if (parsed?.remote) {
|
||
|
|
return parsed.remote;
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
// ignore and fallback to status tracking
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
const status = await getGitStatus(projectDirectory);
|
||
|
|
const parsed = parseTrackingRef(status.tracking);
|
||
|
|
if (parsed?.remote) {
|
||
|
|
return parsed.remote;
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
// ignore
|
||
|
|
}
|
||
|
|
|
||
|
|
return null;
|
||
|
|
};
|
||
|
|
|
||
|
|
export const resolveWorktreeUpstreamDefaults = async (
|
||
|
|
projectDirectory: string,
|
||
|
|
localBranch: string
|
||
|
|
): Promise<{ setUpstream: true; upstreamRemote: string; upstreamBranch: string } | null> => {
|
||
|
|
const remote = await resolveRootTrackingRemote(projectDirectory);
|
||
|
|
const normalizedBranch = normalizeBranchName(localBranch);
|
||
|
|
if (!remote || !normalizedBranch) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
setUpstream: true,
|
||
|
|
upstreamRemote: remote,
|
||
|
|
upstreamBranch: normalizedBranch,
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
export const withWorktreeUpstreamDefaults = async (
|
||
|
|
projectDirectory: string,
|
||
|
|
args: CreateWorktreeArgs,
|
||
|
|
options?: { resolvedRootTrackingRemote?: string | null }
|
||
|
|
): Promise<CreateWorktreeArgs> => {
|
||
|
|
const localBranch = resolveLocalBranchName(args);
|
||
|
|
const resolvedRemote = options?.resolvedRootTrackingRemote;
|
||
|
|
const defaults = resolvedRemote === undefined
|
||
|
|
? await resolveWorktreeUpstreamDefaults(projectDirectory, localBranch)
|
||
|
|
: (resolvedRemote && normalizeBranchName(localBranch)
|
||
|
|
? {
|
||
|
|
setUpstream: true as const,
|
||
|
|
upstreamRemote: resolvedRemote,
|
||
|
|
upstreamBranch: normalizeBranchName(localBranch),
|
||
|
|
}
|
||
|
|
: null);
|
||
|
|
if (!defaults) {
|
||
|
|
return args;
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
...args,
|
||
|
|
setUpstream: args.setUpstream ?? defaults.setUpstream,
|
||
|
|
upstreamRemote: args.upstreamRemote || defaults.upstreamRemote,
|
||
|
|
upstreamBranch: args.upstreamBranch || defaults.upstreamBranch,
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
export const createWorktreeWithDefaults = async (
|
||
|
|
project: ProjectRef,
|
||
|
|
args: CreateWorktreeArgs,
|
||
|
|
options?: { resolvedRootTrackingRemote?: string | null }
|
||
|
|
) => {
|
||
|
|
const resolvedArgs = await withWorktreeUpstreamDefaults(project.path, args, options);
|
||
|
|
return createWorktree(project, resolvedArgs);
|
||
|
|
};
|