Revert "Carry local files and shared state into new worktrees (#2419)"
This reverts commit cbbf6c1684.
This commit is contained in:
@@ -4,7 +4,7 @@ import { existsSync, mkdirSync, realpathSync, rmSync, statSync } from "fs";
|
||||
import { copyFile, rm, stat } from "fs/promises";
|
||||
import { join, basename, dirname, isAbsolute, resolve, sep } from "path";
|
||||
import net from "node:net";
|
||||
import { createHash, randomUUID } from "node:crypto";
|
||||
import { createHash } from "node:crypto";
|
||||
import stripAnsi from "strip-ansi";
|
||||
import {
|
||||
buildStringCommandShellInvocation,
|
||||
@@ -36,11 +36,6 @@ import { createExternalProcessEnv } from "../server/paseo-env.js";
|
||||
import { parseGitRevParsePath, resolveGitRevParsePath } from "./git-rev-parse-path.js";
|
||||
import { validateBranchSlug } from "@getpaseo/protocol/branch-slug";
|
||||
import { expandTilde, getRealpathAwareRelativePath, isPathInsideRoot } from "./path.js";
|
||||
import {
|
||||
materializeWorktreeIncludePlan,
|
||||
readWorktreeIncludePlan,
|
||||
type WorktreeIncludeSummary,
|
||||
} from "./worktree-include.js";
|
||||
|
||||
export { slugify, validateBranchSlug } from "@getpaseo/protocol/branch-slug";
|
||||
|
||||
@@ -54,10 +49,6 @@ export interface WorktreeConfig {
|
||||
worktreePath: string;
|
||||
}
|
||||
|
||||
export interface CreatedWorktreeConfig extends WorktreeConfig {
|
||||
worktreeIncludeSummary: WorktreeIncludeSummary;
|
||||
}
|
||||
|
||||
export interface WorktreeRuntimeEnv {
|
||||
[key: string]: string;
|
||||
PASEO_SOURCE_CHECKOUT_PATH: string;
|
||||
@@ -1170,67 +1161,13 @@ export async function deletePaseoWorktree({
|
||||
}
|
||||
}
|
||||
|
||||
export interface RollbackCreatedPaseoWorktreeOptions extends DeletePaseoWorktreeOptions {
|
||||
createdBranchName?: string;
|
||||
expectedOid?: string;
|
||||
}
|
||||
|
||||
async function removeCreatedWorktreeBranch(options: {
|
||||
createdBranchName?: string;
|
||||
expectedOid?: string;
|
||||
cwd?: string | null;
|
||||
}): Promise<void> {
|
||||
if (!options.createdBranchName || !options.cwd) {
|
||||
return;
|
||||
}
|
||||
if (!(await localBranchExists(options.cwd, options.createdBranchName))) {
|
||||
return;
|
||||
}
|
||||
if (options.expectedOid) {
|
||||
await runGitCommand(
|
||||
["update-ref", "-d", `refs/heads/${options.createdBranchName}`, options.expectedOid],
|
||||
{ cwd: options.cwd },
|
||||
);
|
||||
} else {
|
||||
await runGitCommand(["branch", "--delete", "--force", options.createdBranchName], {
|
||||
cwd: options.cwd,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function rollbackCreatedWorktreeBranch(
|
||||
options: {
|
||||
createdBranchName?: string;
|
||||
expectedOid?: string;
|
||||
cwd: string;
|
||||
},
|
||||
cause: unknown,
|
||||
): Promise<never> {
|
||||
let cleanupError: unknown;
|
||||
try {
|
||||
await removeCreatedWorktreeBranch(options);
|
||||
} catch (error) {
|
||||
cleanupError = error;
|
||||
}
|
||||
if (cleanupError) {
|
||||
const failure = new Error(
|
||||
`${cause instanceof Error ? cause.message : "Worktree workflow failed"}; rollback also failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`,
|
||||
{ cause },
|
||||
);
|
||||
Object.assign(failure, { cleanupError });
|
||||
throw failure;
|
||||
}
|
||||
throw cause;
|
||||
}
|
||||
|
||||
export async function rollbackCreatedPaseoWorktree(
|
||||
options: RollbackCreatedPaseoWorktreeOptions,
|
||||
options: DeletePaseoWorktreeOptions,
|
||||
cause: unknown,
|
||||
): Promise<never> {
|
||||
let cleanupError: unknown;
|
||||
try {
|
||||
await deletePaseoWorktree(options);
|
||||
await removeCreatedWorktreeBranch(options);
|
||||
} catch (error) {
|
||||
cleanupError = error;
|
||||
}
|
||||
@@ -1296,100 +1233,50 @@ export const createWorktree = async ({
|
||||
runSetup,
|
||||
paseoHome,
|
||||
worktreesRoot,
|
||||
}: CreateWorktreeOptions): Promise<CreatedWorktreeConfig> => {
|
||||
}: CreateWorktreeOptions): Promise<WorktreeConfig> => {
|
||||
const sourcePlan = await resolveWorktreeSourcePlan({ cwd, source, desiredSlug: worktreeSlug });
|
||||
const { worktreeIncludePlan, worktreePath } = await (async () => {
|
||||
try {
|
||||
const paseoWorktreesBaseRoot = resolvePaseoWorktreesBaseRoot({ paseoHome, worktreesRoot });
|
||||
const paseoWorktreesRoot = await getPaseoWorktreesRoot(cwd, paseoHome, worktreesRoot);
|
||||
const includePlan = await readWorktreeIncludePlan({
|
||||
sourceRoot: cwd,
|
||||
excludedSourceRoots: [paseoWorktreesBaseRoot],
|
||||
});
|
||||
const requestedWorktreePath = join(paseoWorktreesRoot, worktreeSlug);
|
||||
mkdirSync(dirname(requestedWorktreePath), { recursive: true });
|
||||
let worktreePath = join(await getPaseoWorktreesRoot(cwd, paseoHome, worktreesRoot), worktreeSlug);
|
||||
mkdirSync(dirname(worktreePath), { recursive: true });
|
||||
|
||||
// Also handle worktree path collision
|
||||
let finalWorktreePath = requestedWorktreePath;
|
||||
let pathSuffix = 1;
|
||||
while (existsSync(finalWorktreePath)) {
|
||||
finalWorktreePath = `${requestedWorktreePath}-${pathSuffix}`;
|
||||
pathSuffix++;
|
||||
}
|
||||
|
||||
// Primitive owner for `git worktree add`; callers route through createWorktreeCore.
|
||||
await runGitCommand(["worktree", "add", finalWorktreePath, ...sourcePlan.addArguments], {
|
||||
cwd,
|
||||
timeout: 120_000,
|
||||
});
|
||||
|
||||
return {
|
||||
worktreeIncludePlan: includePlan,
|
||||
worktreePath: normalizePathForOwnership(finalWorktreePath),
|
||||
};
|
||||
} catch (error) {
|
||||
return rollbackCreatedWorktreeBranch(
|
||||
{
|
||||
cwd,
|
||||
createdBranchName: sourcePlan.createdBranchNameBeforeWorktreeAdd,
|
||||
expectedOid: sourcePlan.createdBranchOidBeforeWorktreeAdd,
|
||||
},
|
||||
error,
|
||||
);
|
||||
}
|
||||
})();
|
||||
|
||||
let worktreeIncludeSummary: WorktreeIncludeSummary = {
|
||||
materialized: 0,
|
||||
skipped: [...worktreeIncludePlan.skipped],
|
||||
};
|
||||
try {
|
||||
if (sourcePlan.pushRemote) {
|
||||
await configureWorktreePushRemote({
|
||||
cwd,
|
||||
branchName: sourcePlan.branchName,
|
||||
remote: sourcePlan.pushRemote,
|
||||
});
|
||||
}
|
||||
if (sourcePlan.trackingRemote) {
|
||||
await configureWorktreeTrackingRemote({
|
||||
cwd,
|
||||
branchName: sourcePlan.branchName,
|
||||
remote: sourcePlan.trackingRemote,
|
||||
});
|
||||
}
|
||||
|
||||
writePaseoWorktreeMetadata(worktreePath, {
|
||||
baseRefName: sourcePlan.metadataBaseRefName,
|
||||
...(sourcePlan.changeRequestLookupTarget
|
||||
? { changeRequestLookupTarget: sourcePlan.changeRequestLookupTarget }
|
||||
: {}),
|
||||
});
|
||||
|
||||
await seedPaseoConfigFile({ sourceCwd: cwd, targetCwd: worktreePath });
|
||||
const materialization = await materializeWorktreeIncludePlan({
|
||||
plan: worktreeIncludePlan,
|
||||
worktreeRoot: worktreePath,
|
||||
});
|
||||
worktreeIncludeSummary = {
|
||||
materialized: materialization.materialized,
|
||||
skipped: [...worktreeIncludePlan.skipped, ...materialization.skipped],
|
||||
};
|
||||
} catch (error) {
|
||||
await rollbackCreatedPaseoWorktree(
|
||||
{
|
||||
cwd,
|
||||
worktreePath,
|
||||
teardownCwds: [],
|
||||
paseoHome,
|
||||
worktreesBaseRoot: worktreesRoot,
|
||||
createdBranchName: sourcePlan.createdBranchName,
|
||||
expectedOid: sourcePlan.createdBranchOidBeforeWorktreeAdd,
|
||||
},
|
||||
error,
|
||||
);
|
||||
// Also handle worktree path collision
|
||||
let finalWorktreePath = worktreePath;
|
||||
let pathSuffix = 1;
|
||||
while (existsSync(finalWorktreePath)) {
|
||||
finalWorktreePath = `${worktreePath}-${pathSuffix}`;
|
||||
pathSuffix++;
|
||||
}
|
||||
|
||||
// Primitive owner for `git worktree add`; callers route through createWorktreeCore.
|
||||
await runGitCommand(["worktree", "add", finalWorktreePath, ...sourcePlan.addArguments], {
|
||||
cwd,
|
||||
timeout: 120_000,
|
||||
});
|
||||
worktreePath = normalizePathForOwnership(finalWorktreePath);
|
||||
|
||||
if (sourcePlan.pushRemote) {
|
||||
await configureWorktreePushRemote({
|
||||
cwd,
|
||||
branchName: sourcePlan.branchName,
|
||||
remote: sourcePlan.pushRemote,
|
||||
});
|
||||
}
|
||||
if (sourcePlan.trackingRemote) {
|
||||
await configureWorktreeTrackingRemote({
|
||||
cwd,
|
||||
branchName: sourcePlan.branchName,
|
||||
remote: sourcePlan.trackingRemote,
|
||||
});
|
||||
}
|
||||
|
||||
writePaseoWorktreeMetadata(worktreePath, {
|
||||
baseRefName: sourcePlan.metadataBaseRefName,
|
||||
...(sourcePlan.changeRequestLookupTarget
|
||||
? { changeRequestLookupTarget: sourcePlan.changeRequestLookupTarget }
|
||||
: {}),
|
||||
});
|
||||
|
||||
await seedPaseoConfigFile({ sourceCwd: cwd, targetCwd: worktreePath });
|
||||
|
||||
if (runSetup) {
|
||||
await runWorktreeSetupCommands({
|
||||
worktreePath,
|
||||
@@ -1400,7 +1287,6 @@ export const createWorktree = async ({
|
||||
|
||||
return {
|
||||
branchName: sourcePlan.branchName,
|
||||
worktreeIncludeSummary,
|
||||
worktreePath,
|
||||
};
|
||||
};
|
||||
@@ -1413,9 +1299,6 @@ interface ResolveWorktreeSourcePlanOptions {
|
||||
|
||||
interface WorktreeSourcePlan {
|
||||
branchName: string;
|
||||
createdBranchName?: string;
|
||||
createdBranchNameBeforeWorktreeAdd?: string;
|
||||
createdBranchOidBeforeWorktreeAdd?: string;
|
||||
metadataBaseRefName: string;
|
||||
changeRequestLookupTarget?: PaseoWorktreeChangeRequestLookupTarget;
|
||||
addArguments: string[];
|
||||
@@ -1431,11 +1314,6 @@ interface WorktreeSourcePlan {
|
||||
};
|
||||
}
|
||||
|
||||
type ChangeRequestWorktreeSource = Extract<
|
||||
WorktreeSource,
|
||||
{ kind: "checkout-change-request" | "checkout-github-pr" }
|
||||
>;
|
||||
|
||||
async function resolveWorktreeSourcePlan({
|
||||
cwd,
|
||||
source,
|
||||
@@ -1454,143 +1332,93 @@ async function resolveWorktreeSourcePlan({
|
||||
|
||||
return {
|
||||
branchName: newBranchName,
|
||||
createdBranchName: newBranchName,
|
||||
metadataBaseRefName: normalizedBaseBranch,
|
||||
addArguments: ["-b", newBranchName, "--no-track", base],
|
||||
};
|
||||
}
|
||||
case "checkout-branch":
|
||||
return resolveCheckoutBranchWorktreeSourcePlan({ cwd, branchName: source.branchName });
|
||||
case "checkout-change-request":
|
||||
case "checkout-github-pr":
|
||||
return resolveChangeRequestWorktreeSourcePlan({ cwd, source });
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveCheckoutBranchWorktreeSourcePlan(options: {
|
||||
branchName: string;
|
||||
cwd: string;
|
||||
}): Promise<WorktreeSourcePlan> {
|
||||
await validateExistingWorktreeBranchName(options.cwd, options.branchName);
|
||||
const needsFetch = !(await localBranchExists(options.cwd, options.branchName));
|
||||
let createdBranchOid: string | undefined;
|
||||
if (needsFetch) {
|
||||
try {
|
||||
createdBranchOid = await fetchNewLocalBranchAtomically({
|
||||
cwd: options.cwd,
|
||||
localBranchName: options.branchName,
|
||||
remoteName: "origin",
|
||||
remoteRef: `refs/heads/${options.branchName}`,
|
||||
});
|
||||
} catch {
|
||||
throw new UnknownBranchError({ branchName: options.branchName, cwd: options.cwd });
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
if (await isBranchCheckedOut(options.cwd, options.branchName)) {
|
||||
throw new BranchAlreadyCheckedOutError(options.branchName);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!needsFetch) {
|
||||
throw error;
|
||||
}
|
||||
return rollbackCreatedWorktreeBranch(
|
||||
{
|
||||
cwd: options.cwd,
|
||||
createdBranchName: options.branchName,
|
||||
expectedOid: createdBranchOid,
|
||||
},
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
branchName: options.branchName,
|
||||
...(needsFetch
|
||||
? {
|
||||
createdBranchName: options.branchName,
|
||||
createdBranchNameBeforeWorktreeAdd: options.branchName,
|
||||
createdBranchOidBeforeWorktreeAdd: createdBranchOid,
|
||||
case "checkout-branch": {
|
||||
await validateExistingWorktreeBranchName(cwd, source.branchName);
|
||||
if (!(await localBranchExists(cwd, source.branchName))) {
|
||||
try {
|
||||
await runGitCommand(["fetch", "origin", `${source.branchName}:${source.branchName}`], {
|
||||
cwd,
|
||||
timeout: 120_000,
|
||||
});
|
||||
} catch {
|
||||
throw new UnknownBranchError({ branchName: source.branchName, cwd });
|
||||
}
|
||||
: {}),
|
||||
metadataBaseRefName: options.branchName,
|
||||
addArguments: [options.branchName],
|
||||
};
|
||||
}
|
||||
|
||||
async function resolveChangeRequestWorktreeSourcePlan(options: {
|
||||
cwd: string;
|
||||
source: ChangeRequestWorktreeSource;
|
||||
}): Promise<WorktreeSourcePlan> {
|
||||
const { cwd, source } = options;
|
||||
const localBranchCandidate = source.localBranchName ?? source.headRef;
|
||||
await validateExistingWorktreeBranchName(cwd, localBranchCandidate);
|
||||
const localBranchName = await resolveUniqueLocalBranchName(cwd, localBranchCandidate);
|
||||
const normalizedBaseRefName = normalizeRequiredBaseBranch(source.baseRefName);
|
||||
const changeRequestNumber =
|
||||
source.kind === "checkout-github-pr" ? source.githubPrNumber : source.changeRequestNumber;
|
||||
|
||||
let createdBranchOid: string | undefined;
|
||||
try {
|
||||
createdBranchOid = await fetchWorktreeCheckoutRefs({
|
||||
cwd,
|
||||
localBranchName,
|
||||
checkoutRefs: source.checkoutRefs ?? [
|
||||
{ remoteName: "origin", remoteRef: `refs/pull/${changeRequestNumber}/head` },
|
||||
],
|
||||
});
|
||||
const shouldTrackOriginHead = source.trackOriginHead === true;
|
||||
const trackingRemote = shouldTrackOriginHead
|
||||
? await tryFetchWorktreeTrackingRemote({
|
||||
cwd,
|
||||
remoteName: "origin",
|
||||
headRef: source.headRef,
|
||||
})
|
||||
: undefined;
|
||||
const remotePlan: Pick<WorktreeSourcePlan, "pushRemote" | "trackingRemote"> = {};
|
||||
if (source.pushRemoteUrl) {
|
||||
const remoteName = `paseo-pr-${changeRequestNumber}`;
|
||||
remotePlan.pushRemote = {
|
||||
name: remoteName,
|
||||
url: source.pushRemoteUrl,
|
||||
headRef: source.headRef,
|
||||
track: true,
|
||||
};
|
||||
} else if (shouldTrackOriginHead && localBranchName !== source.headRef) {
|
||||
const originUrl = await getWorktreeRemotePushUrl(cwd, "origin");
|
||||
if (originUrl) {
|
||||
remotePlan.pushRemote = {
|
||||
name: `paseo-pr-${changeRequestNumber}`,
|
||||
url: originUrl,
|
||||
headRef: source.headRef,
|
||||
track: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (trackingRemote) {
|
||||
remotePlan.trackingRemote = trackingRemote;
|
||||
}
|
||||
if (await isBranchCheckedOut(cwd, source.branchName)) {
|
||||
throw new BranchAlreadyCheckedOutError(source.branchName);
|
||||
}
|
||||
|
||||
return {
|
||||
branchName: localBranchName,
|
||||
createdBranchName: localBranchName,
|
||||
createdBranchNameBeforeWorktreeAdd: localBranchName,
|
||||
createdBranchOidBeforeWorktreeAdd: createdBranchOid,
|
||||
metadataBaseRefName: normalizedBaseRefName,
|
||||
changeRequestLookupTarget: {
|
||||
headRef: source.headRef,
|
||||
...(source.headRepositoryOwner ? { headRepositoryOwner: source.headRepositoryOwner } : {}),
|
||||
changeRequestNumber,
|
||||
},
|
||||
addArguments: [localBranchName],
|
||||
...remotePlan,
|
||||
};
|
||||
} catch (error) {
|
||||
return rollbackCreatedWorktreeBranch(
|
||||
{ cwd, createdBranchName: localBranchName, expectedOid: createdBranchOid },
|
||||
error,
|
||||
);
|
||||
return {
|
||||
branchName: source.branchName,
|
||||
metadataBaseRefName: source.branchName,
|
||||
addArguments: [source.branchName],
|
||||
};
|
||||
}
|
||||
case "checkout-change-request":
|
||||
case "checkout-github-pr": {
|
||||
const localBranchCandidate = source.localBranchName ?? source.headRef;
|
||||
await validateExistingWorktreeBranchName(cwd, localBranchCandidate);
|
||||
const localBranchName = await resolveUniqueLocalBranchName(cwd, localBranchCandidate);
|
||||
const normalizedBaseRefName = normalizeRequiredBaseBranch(source.baseRefName);
|
||||
const changeRequestNumber =
|
||||
source.kind === "checkout-github-pr" ? source.githubPrNumber : source.changeRequestNumber;
|
||||
await fetchWorktreeCheckoutRefs({
|
||||
cwd,
|
||||
localBranchName,
|
||||
checkoutRefs: source.checkoutRefs ?? [
|
||||
{ remoteName: "origin", remoteRef: `refs/pull/${changeRequestNumber}/head` },
|
||||
],
|
||||
});
|
||||
const shouldTrackOriginHead = source.trackOriginHead === true;
|
||||
const trackingRemote = shouldTrackOriginHead
|
||||
? await tryFetchWorktreeTrackingRemote({
|
||||
cwd,
|
||||
remoteName: "origin",
|
||||
headRef: source.headRef,
|
||||
})
|
||||
: undefined;
|
||||
const remotePlan: Pick<WorktreeSourcePlan, "pushRemote" | "trackingRemote"> = {};
|
||||
if (source.pushRemoteUrl) {
|
||||
const remoteName = `paseo-pr-${changeRequestNumber}`;
|
||||
remotePlan.pushRemote = {
|
||||
name: remoteName,
|
||||
url: source.pushRemoteUrl,
|
||||
headRef: source.headRef,
|
||||
track: true,
|
||||
};
|
||||
} else if (shouldTrackOriginHead && localBranchName !== source.headRef) {
|
||||
const originUrl = await getWorktreeRemotePushUrl(cwd, "origin");
|
||||
if (originUrl) {
|
||||
remotePlan.pushRemote = {
|
||||
name: `paseo-pr-${changeRequestNumber}`,
|
||||
url: originUrl,
|
||||
headRef: source.headRef,
|
||||
track: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
if (trackingRemote) {
|
||||
remotePlan.trackingRemote = trackingRemote;
|
||||
}
|
||||
|
||||
return {
|
||||
branchName: localBranchName,
|
||||
metadataBaseRefName: normalizedBaseRefName,
|
||||
changeRequestLookupTarget: {
|
||||
headRef: source.headRef,
|
||||
...(source.headRepositoryOwner
|
||||
? { headRepositoryOwner: source.headRepositoryOwner }
|
||||
: {}),
|
||||
changeRequestNumber,
|
||||
},
|
||||
addArguments: [localBranchName],
|
||||
...remotePlan,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1643,22 +1471,27 @@ async function fetchWorktreeCheckoutRefs(options: {
|
||||
cwd: string;
|
||||
localBranchName: string;
|
||||
checkoutRefs: WorktreeCheckoutRef[];
|
||||
}): Promise<string> {
|
||||
}): Promise<void> {
|
||||
let lastResult:
|
||||
| Awaited<ReturnType<typeof runGitCommand>>
|
||||
| { stderr: string; stdout: string; exitCode: number | null }
|
||||
| null = null;
|
||||
for (const checkoutRef of options.checkoutRefs) {
|
||||
try {
|
||||
return await fetchNewLocalBranchAtomically({
|
||||
lastResult = await runGitCommand(
|
||||
[
|
||||
"fetch",
|
||||
checkoutRef.remoteName ?? "origin",
|
||||
`+${checkoutRef.remoteRef}:refs/heads/${options.localBranchName}`,
|
||||
"--force",
|
||||
],
|
||||
{
|
||||
cwd: options.cwd,
|
||||
localBranchName: options.localBranchName,
|
||||
remoteName: checkoutRef.remoteName ?? "origin",
|
||||
remoteRef: checkoutRef.remoteRef,
|
||||
});
|
||||
} catch (error) {
|
||||
lastResult =
|
||||
error instanceof Error ? { stderr: error.message, stdout: "", exitCode: 1 } : null;
|
||||
timeout: 120_000,
|
||||
acceptExitCodes: [0, 1, 128],
|
||||
},
|
||||
);
|
||||
if (lastResult.exitCode === 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const attemptedRefs = options.checkoutRefs
|
||||
@@ -1669,35 +1502,6 @@ async function fetchWorktreeCheckoutRefs(options: {
|
||||
);
|
||||
}
|
||||
|
||||
async function fetchNewLocalBranchAtomically(options: {
|
||||
cwd: string;
|
||||
localBranchName: string;
|
||||
remoteName: string;
|
||||
remoteRef: string;
|
||||
}): Promise<string> {
|
||||
const temporaryRef = `refs/paseo/worktree-fetch/${randomUUID()}`;
|
||||
try {
|
||||
await runGitCommand(
|
||||
["fetch", options.remoteName, `${options.remoteRef}:${temporaryRef}`, "--force"],
|
||||
{ cwd: options.cwd, timeout: 120_000 },
|
||||
);
|
||||
const { stdout } = await runGitCommand(["rev-parse", "--verify", temporaryRef], {
|
||||
cwd: options.cwd,
|
||||
});
|
||||
const oid = stdout.trim();
|
||||
const nullOid = "0".repeat(oid.length);
|
||||
await runGitCommand(["update-ref", `refs/heads/${options.localBranchName}`, oid, nullOid], {
|
||||
cwd: options.cwd,
|
||||
});
|
||||
return oid;
|
||||
} finally {
|
||||
await runGitCommand(["update-ref", "-d", temporaryRef], {
|
||||
cwd: options.cwd,
|
||||
acceptExitCodes: [0, 1, 128],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function tryFetchWorktreeTrackingRemote(options: {
|
||||
cwd: string;
|
||||
remoteName: string;
|
||||
|
||||
Reference in New Issue
Block a user