Default new worktrees to the upstream branch (#2848)
* fix(workspaces): default new worktrees to upstream refs Use the daemon-resolved upstream as the untouched starting ref so unpushed local commits do not leak into new workspaces. Preserve exact base identity for fork remotes while keeping local-first behavior across version drift. * fix(workspaces): preserve exact base refs in git actions Resolve accepted display aliases back to the stored ref before comparisons and updates. Fail closed when a non-origin remote base has no explicit mutable merge target. * fix(workspaces): reject conflicting qualified base refs Display aliases remain accepted for compatibility, but a qualified ref carries remote identity and must not be canonicalized to a different stored upstream. * test(server): make upstream fixtures portable Keep the checkout-status fixture aligned with the optional upstream field, and select main explicitly when cloning a bare test remote whose default HEAD varies by platform. * fix(workspaces): preserve exact upstream refs Qualified remote refs now fail closed when pruned instead of switching lineage. Metadata accepts Git-valid upstream names rather than applying a narrower character whitelist. * fix(workspaces): preserve qualified local base refs Local tracking branches are exact upstream identities too. Keep heuristics only for legacy unqualified metadata so origin cannot replace a stored refs/heads base.
This commit is contained in:
@@ -30,6 +30,7 @@ import { parseGitRevParsePath, resolveGitRevParsePath } from "./git-rev-parse-pa
|
||||
import { runGitCommand } from "./run-git-command.js";
|
||||
import { isPaseoOwnedWorktreeCwd, resolvePaseoWorktreesBaseRoot } from "./worktree.js";
|
||||
import {
|
||||
branchNameFromRef,
|
||||
getPaseoWorktreeChangeRequestHintForBranch,
|
||||
type PaseoWorktreeMetadata,
|
||||
readPaseoWorktreeMetadata,
|
||||
@@ -786,6 +787,10 @@ export interface CheckoutStatusGitNonPaseo {
|
||||
isDirty: boolean;
|
||||
baseRef: string | null;
|
||||
aheadBehind: AheadBehind | null;
|
||||
// Remote-tracking ref for currentBranch, e.g. "refs/remotes/origin/main". Null when the
|
||||
// branch has no upstream or git could not resolve it. aheadOfOrigin/behindOfOrigin are
|
||||
// measured against exactly this ref.
|
||||
upstreamRef: string | null;
|
||||
aheadOfOrigin: number | null;
|
||||
behindOfOrigin: number | null;
|
||||
hasRemote: boolean;
|
||||
@@ -801,6 +806,7 @@ export interface CheckoutStatusGitPaseo {
|
||||
isDirty: boolean;
|
||||
baseRef: string;
|
||||
aheadBehind: AheadBehind | null;
|
||||
upstreamRef: string | null;
|
||||
aheadOfOrigin: number | null;
|
||||
behindOfOrigin: number | null;
|
||||
hasRemote: boolean;
|
||||
@@ -859,6 +865,7 @@ export type CheckoutSnapshotFacts =
|
||||
comparisonBaseRef: string | null;
|
||||
branchRemoteName: string | null;
|
||||
branchMergeRef: string | null;
|
||||
upstreamStatus: UpstreamStatus | null;
|
||||
pullRequestLookupTarget: PullRequestStatusLookupTarget | null;
|
||||
};
|
||||
|
||||
@@ -1153,8 +1160,14 @@ async function getPaseoWorktreeForCwd(
|
||||
};
|
||||
}
|
||||
|
||||
// Worktrees created before baseRef existed only stored the stripped name; it resolves
|
||||
// local-first, which is the base they were actually cut from.
|
||||
function storedBaseRefFromMetadata(metadata: PaseoWorktreeMetadata | null): string | null {
|
||||
return metadata?.baseRef ?? metadata?.baseRefName ?? null;
|
||||
}
|
||||
|
||||
function readPaseoWorktreeBaseRef(worktreeRoot: string): string | null {
|
||||
return readPaseoWorktreeMetadata(worktreeRoot)?.baseRefName ?? null;
|
||||
return storedBaseRefFromMetadata(readPaseoWorktreeMetadata(worktreeRoot));
|
||||
}
|
||||
|
||||
async function getStoredBaseRefForCwd(
|
||||
@@ -1205,12 +1218,37 @@ async function resolveBaseRefForCwd(
|
||||
};
|
||||
}
|
||||
|
||||
// The worktree stores the exact ref it was cut from ("refs/remotes/upstream/main") while
|
||||
// callers still send its display name ("main"). A different qualified ref retains its own
|
||||
// identity even when it has the same branch name.
|
||||
function isSameBaseRef(stored: string, requested: string): boolean {
|
||||
return stored === requested || branchNameFromRef(stored) === requested;
|
||||
}
|
||||
|
||||
// Names both refs rather than labelling either one correct: a caller's ref can be stale, but the
|
||||
// stored ref can equally be wrong, so the message states the two facts and leaves the diagnosis open.
|
||||
function baseRefMismatchError(refs: { stored: string; requested: string }): Error {
|
||||
return new Error(`Base ref mismatch: stored ${refs.stored}, requested ${refs.requested}`);
|
||||
}
|
||||
|
||||
function resolveOperationBaseRef(input: {
|
||||
storedBaseRef: string | null;
|
||||
resolvedBaseRef: string | null;
|
||||
requestedBaseRef?: string;
|
||||
}): string | null {
|
||||
if (
|
||||
input.storedBaseRef &&
|
||||
input.requestedBaseRef &&
|
||||
!isSameBaseRef(input.storedBaseRef, input.requestedBaseRef)
|
||||
) {
|
||||
throw baseRefMismatchError({
|
||||
stored: input.storedBaseRef,
|
||||
requested: input.requestedBaseRef,
|
||||
});
|
||||
}
|
||||
return input.storedBaseRef ?? input.requestedBaseRef ?? input.resolvedBaseRef;
|
||||
}
|
||||
|
||||
async function isWorkingTreeDirty(cwd: string, context?: CheckoutContext): Promise<boolean> {
|
||||
const { stdout } = await runGitCommand(["status", "--porcelain"], {
|
||||
cwd,
|
||||
@@ -1450,29 +1488,25 @@ async function resolveBaseRef(repoRoot: string): Promise<string | null> {
|
||||
return resolveRepositoryDefaultBranch(repoRoot);
|
||||
}
|
||||
|
||||
function normalizeLocalBranchRefName(input: string): string {
|
||||
if (input.startsWith("refs/remotes/origin/")) {
|
||||
return input.slice("refs/remotes/origin/".length);
|
||||
}
|
||||
if (input.startsWith("refs/heads/")) {
|
||||
return input.slice("refs/heads/".length);
|
||||
}
|
||||
if (input.startsWith("origin/")) {
|
||||
return input.slice("origin/".length);
|
||||
}
|
||||
return input;
|
||||
}
|
||||
|
||||
interface ComparisonBaseRefName {
|
||||
localName: string;
|
||||
originRef: string;
|
||||
}
|
||||
|
||||
function normalizeComparisonBaseRefName(input: string): ComparisonBaseRefName {
|
||||
const localName = normalizeLocalBranchRefName(input);
|
||||
const localName = branchNameFromRef(input);
|
||||
return { localName, originRef: `origin/${localName}` };
|
||||
}
|
||||
|
||||
function resolveMergeTargetBranch(baseRef: string): string {
|
||||
const remotePrefix = "refs/remotes/";
|
||||
const originPrefix = `${remotePrefix}origin/`;
|
||||
if (baseRef.startsWith(remotePrefix) && !baseRef.startsWith(originPrefix)) {
|
||||
throw new Error(`No local merge target is recorded for base ref ${baseRef}`);
|
||||
}
|
||||
return branchNameFromRef(baseRef);
|
||||
}
|
||||
|
||||
async function doesGitRefExist(
|
||||
cwd: string,
|
||||
fullRef: string,
|
||||
@@ -1487,11 +1521,23 @@ async function doesGitRefExist(
|
||||
return result.exitCode === 0;
|
||||
}
|
||||
|
||||
function isQualifiedBranchRef(ref: string): boolean {
|
||||
return ref.startsWith("refs/heads/") || ref.startsWith("refs/remotes/");
|
||||
}
|
||||
|
||||
async function resolveBestComparisonBaseRef(
|
||||
cwd: string,
|
||||
baseRef: string,
|
||||
context?: CheckoutContext,
|
||||
): Promise<string> {
|
||||
// A fully qualified branch ref names the exact commit stream to compare against. Bare names
|
||||
// keep going through the local-vs-origin heuristic below for legacy worktree metadata.
|
||||
if (isQualifiedBranchRef(baseRef)) {
|
||||
if (await doesGitRefExist(cwd, baseRef, context)) {
|
||||
return baseRef;
|
||||
}
|
||||
throw new Error(`Base ref not found: ${baseRef}`);
|
||||
}
|
||||
const normalized = normalizeComparisonBaseRefName(baseRef);
|
||||
const [hasLocal, hasOrigin] = await Promise.all([
|
||||
doesGitRefExist(cwd, `refs/heads/${normalized.localName}`, context),
|
||||
@@ -1512,7 +1558,14 @@ async function resolveBestComparisonBaseRef(
|
||||
throw new Error(`Base branch not found locally or on origin: ${refName}`);
|
||||
}
|
||||
|
||||
async function resolveMostAheadBaseRef(cwd: string, normalizedBaseRef: string): Promise<string> {
|
||||
async function resolveMostAheadBaseRef(cwd: string, baseRef: string): Promise<string> {
|
||||
if (isQualifiedBranchRef(baseRef)) {
|
||||
if (await doesGitRefExist(cwd, baseRef)) {
|
||||
return baseRef;
|
||||
}
|
||||
throw new Error(`Base ref not found: ${baseRef}`);
|
||||
}
|
||||
const normalizedBaseRef = branchNameFromRef(baseRef);
|
||||
const [hasLocal, hasOrigin] = await Promise.all([
|
||||
doesGitRefExist(cwd, `refs/heads/${normalizedBaseRef}`),
|
||||
doesGitRefExist(cwd, `refs/remotes/origin/${normalizedBaseRef}`),
|
||||
@@ -1551,7 +1604,7 @@ async function getAheadBehind(
|
||||
currentBranch: string,
|
||||
context?: CheckoutContext,
|
||||
): Promise<AheadBehind | null> {
|
||||
const normalizedBaseRef = normalizeLocalBranchRefName(baseRef);
|
||||
const normalizedBaseRef = branchNameFromRef(baseRef);
|
||||
if (!normalizedBaseRef || !currentBranch || normalizedBaseRef === currentBranch) {
|
||||
return null;
|
||||
}
|
||||
@@ -1575,48 +1628,32 @@ async function getAheadBehind(
|
||||
return { ahead, behind };
|
||||
}
|
||||
|
||||
async function getConfiguredUpstreamRef(
|
||||
cwd: string,
|
||||
currentBranch: string,
|
||||
context?: CheckoutContext,
|
||||
): Promise<string | null> {
|
||||
const remoteName =
|
||||
context?.facts?.isGit && context.facts.currentBranch === currentBranch
|
||||
? context.facts.branchRemoteName
|
||||
: await getGitConfigValue(cwd, `branch.${currentBranch}.remote`, context);
|
||||
if (!remoteName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const mergeRef =
|
||||
context?.facts?.isGit && context.facts.currentBranch === currentBranch
|
||||
? context.facts.branchMergeRef
|
||||
: await getGitConfigValue(cwd, `branch.${currentBranch}.merge`, context);
|
||||
const upstreamBranch = parseBranchMergeHeadRef(mergeRef);
|
||||
return upstreamBranch ? `${remoteName}/${upstreamBranch}` : null;
|
||||
interface UpstreamStatus {
|
||||
ref: string;
|
||||
aheadBehind: AheadBehind;
|
||||
}
|
||||
|
||||
async function getOriginAheadBehind(
|
||||
async function getUpstreamStatus(
|
||||
cwd: string,
|
||||
currentBranch: string,
|
||||
context?: CheckoutContext,
|
||||
): Promise<AheadBehind | null> {
|
||||
if (!currentBranch) {
|
||||
return null;
|
||||
}
|
||||
const upstreamRef = await getConfiguredUpstreamRef(cwd, currentBranch, context);
|
||||
if (!upstreamRef) {
|
||||
return null;
|
||||
}
|
||||
): Promise<UpstreamStatus | null> {
|
||||
try {
|
||||
const { stdout } = await runGitCommand(
|
||||
["rev-list", "--left-right", "--count", `${currentBranch}...${upstreamRef}`],
|
||||
[
|
||||
"for-each-ref",
|
||||
"--format=%(upstream)%00%(upstream:track,nobracket)",
|
||||
`refs/heads/${currentBranch}`,
|
||||
],
|
||||
{ cwd, envOverlay: READ_ONLY_GIT_ENV, logger: context?.logger },
|
||||
);
|
||||
const [aheadRaw, behindRaw] = stdout.trim().split(/\s+/);
|
||||
const ahead = Number.parseInt(aheadRaw ?? "", 10);
|
||||
const behind = Number.parseInt(behindRaw ?? "", 10);
|
||||
return Number.isNaN(ahead) || Number.isNaN(behind) ? null : { ahead, behind };
|
||||
const [ref = "", track = ""] = stdout.trim().split("\0", 2);
|
||||
if (!ref || track === "gone") {
|
||||
return null;
|
||||
}
|
||||
const ahead = Number.parseInt(track.match(/ahead (\d+)/)?.[1] ?? "0", 10);
|
||||
const behind = Number.parseInt(track.match(/behind (\d+)/)?.[1] ?? "0", 10);
|
||||
return { ref, aheadBehind: { ahead, behind } };
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
@@ -1674,9 +1711,7 @@ function buildPullRequestLookupTargetFromBranchConfig(
|
||||
const originRepo = parseRepositoryIdentityFromRemote(input.originRemoteUrl);
|
||||
const isSameRepo = areSameGitHubRepository(remoteRepo, originRepo);
|
||||
const headRepositoryOwner = remoteRepo && !isSameRepo ? remoteRepo.split("/")[0] : null;
|
||||
const normalizedBaseRef = input.resolvedBaseRef
|
||||
? normalizeLocalBranchRefName(input.resolvedBaseRef)
|
||||
: null;
|
||||
const normalizedBaseRef = input.resolvedBaseRef ? branchNameFromRef(input.resolvedBaseRef) : null;
|
||||
if (
|
||||
trackedHeadRef === normalizedBaseRef &&
|
||||
!doesLocalBranchNameIdentifyTrackedHead(
|
||||
@@ -1740,9 +1775,7 @@ function buildPullRequestLookupTargetFromPushConfig(
|
||||
: null;
|
||||
const isSameRepo = areSameGitHubRepository(remoteRepo, originRepo);
|
||||
const headRepositoryOwner = remoteRepo && !isSameRepo ? remoteRepo.split("/")[0] : null;
|
||||
const normalizedBaseRef = input.resolvedBaseRef
|
||||
? normalizeLocalBranchRefName(input.resolvedBaseRef)
|
||||
: null;
|
||||
const normalizedBaseRef = input.resolvedBaseRef ? branchNameFromRef(input.resolvedBaseRef) : null;
|
||||
if (pushedHeadRef === normalizedBaseRef && !headRepositoryOwner) {
|
||||
return null;
|
||||
}
|
||||
@@ -1855,7 +1888,7 @@ export async function getCheckoutSnapshotFacts(
|
||||
const paseoWorktreeMetadata = inspected.paseoWorktree.isPaseoOwnedWorktree
|
||||
? readPaseoWorktreeMetadata(inspected.paseoWorktree.worktreeRoot)
|
||||
: null;
|
||||
const storedBaseRef = paseoWorktreeMetadata?.baseRefName ?? null;
|
||||
const storedBaseRef = storedBaseRefFromMetadata(paseoWorktreeMetadata);
|
||||
const resolvedBaseRef = storedBaseRef ?? (await resolveBaseRef(cwd));
|
||||
const mainRepoRoot = await getMainRepoRootFromCommonDir(
|
||||
cwd,
|
||||
@@ -1866,7 +1899,7 @@ export async function getCheckoutSnapshotFacts(
|
||||
if (
|
||||
resolvedBaseRef &&
|
||||
inspected.currentBranch &&
|
||||
normalizeLocalBranchRefName(resolvedBaseRef) !== inspected.currentBranch
|
||||
branchNameFromRef(resolvedBaseRef) !== inspected.currentBranch
|
||||
) {
|
||||
comparisonBaseRef = await resolveBestComparisonBaseRef(cwd, resolvedBaseRef, context).catch(
|
||||
() => null,
|
||||
@@ -1876,6 +1909,9 @@ export async function getCheckoutSnapshotFacts(
|
||||
let branchRemoteName: string | null = null;
|
||||
let branchMergeRef: string | null = null;
|
||||
let branchRemoteUrl: string | null = null;
|
||||
const upstreamStatusPromise = inspected.currentBranch
|
||||
? getUpstreamStatus(cwd, inspected.currentBranch, context)
|
||||
: Promise.resolve(null);
|
||||
if (inspected.currentBranch) {
|
||||
branchRemoteName = await getGitConfigValue(
|
||||
cwd,
|
||||
@@ -1919,6 +1955,7 @@ export async function getCheckoutSnapshotFacts(
|
||||
pullRequestLookupTarget,
|
||||
context,
|
||||
);
|
||||
const upstreamStatus = await upstreamStatusPromise;
|
||||
|
||||
return {
|
||||
isGit: true,
|
||||
@@ -1934,6 +1971,7 @@ export async function getCheckoutSnapshotFacts(
|
||||
comparisonBaseRef,
|
||||
branchRemoteName,
|
||||
branchMergeRef,
|
||||
upstreamStatus,
|
||||
pullRequestLookupTarget,
|
||||
};
|
||||
}
|
||||
@@ -2097,16 +2135,18 @@ export async function getCheckoutStatus(
|
||||
const baseRef = facts.resolvedBaseRef;
|
||||
const mainRepoRoot = facts.mainRepoRoot;
|
||||
const factsContext = { ...context, facts };
|
||||
const [aheadBehind, originAheadBehind] = await Promise.all([
|
||||
const aheadBehind =
|
||||
baseRef && currentBranch
|
||||
? getAheadBehind(cwd, baseRef, currentBranch, factsContext)
|
||||
: Promise.resolve(null),
|
||||
hasRemote && currentBranch
|
||||
? getOriginAheadBehind(cwd, currentBranch, factsContext)
|
||||
: Promise.resolve(null),
|
||||
]);
|
||||
const aheadOfOrigin = originAheadBehind?.ahead ?? null;
|
||||
const behindOfOrigin = originAheadBehind?.behind ?? null;
|
||||
? await getAheadBehind(cwd, baseRef, currentBranch, factsContext)
|
||||
: null;
|
||||
const upstreamStatus = facts.upstreamStatus;
|
||||
// The wire carries the display name: clients label the base with it and send it back to
|
||||
// request diffs and merges. The exact ref stays in worktree.json and in facts, where the
|
||||
// comparisons and actions read it.
|
||||
const displayBaseRef = baseRef ? branchNameFromRef(baseRef) : null;
|
||||
const upstreamRef = upstreamStatus?.ref ?? null;
|
||||
const aheadOfOrigin = upstreamStatus?.aheadBehind.ahead ?? null;
|
||||
const behindOfOrigin = upstreamStatus?.aheadBehind.behind ?? null;
|
||||
|
||||
if (paseoWorktree.isPaseoOwnedWorktree && baseRef) {
|
||||
return {
|
||||
@@ -2115,8 +2155,9 @@ export async function getCheckoutStatus(
|
||||
mainRepoRoot: mainRepoRoot ?? worktreeRoot,
|
||||
currentBranch,
|
||||
isDirty,
|
||||
baseRef,
|
||||
baseRef: displayBaseRef ?? baseRef,
|
||||
aheadBehind,
|
||||
upstreamRef,
|
||||
aheadOfOrigin,
|
||||
behindOfOrigin,
|
||||
hasRemote,
|
||||
@@ -2132,8 +2173,9 @@ export async function getCheckoutStatus(
|
||||
mainRepoRoot && resolve(mainRepoRoot) !== resolve(worktreeRoot) ? mainRepoRoot : null,
|
||||
currentBranch,
|
||||
isDirty,
|
||||
baseRef,
|
||||
baseRef: displayBaseRef,
|
||||
aheadBehind,
|
||||
upstreamRef,
|
||||
aheadOfOrigin,
|
||||
behindOfOrigin,
|
||||
hasRemote,
|
||||
@@ -2345,11 +2387,18 @@ async function tryResolveCheckoutCommitsBaseRef(
|
||||
if (!baseRef) {
|
||||
return null;
|
||||
}
|
||||
const normalizedBaseRef = normalizeLocalBranchRefName(baseRef);
|
||||
const normalizedBaseRef = branchNameFromRef(baseRef);
|
||||
if (!normalizedBaseRef || normalizedBaseRef === currentBranch) {
|
||||
return null;
|
||||
}
|
||||
return resolveMostAheadBaseRef(cwd, normalizedBaseRef).catch(() => null);
|
||||
try {
|
||||
return await resolveMostAheadBaseRef(cwd, baseRef);
|
||||
} catch (error) {
|
||||
if (isQualifiedBranchRef(baseRef)) {
|
||||
throw error;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function listCheckoutCommits({
|
||||
@@ -2365,7 +2414,7 @@ export async function listCheckoutCommits({
|
||||
}
|
||||
|
||||
const { resolvedBaseRef } = await resolveBaseRefForCwd(cwd, context);
|
||||
const normalizedBaseRef = resolvedBaseRef ? normalizeLocalBranchRefName(resolvedBaseRef) : null;
|
||||
const normalizedBaseRef = resolvedBaseRef ? branchNameFromRef(resolvedBaseRef) : null;
|
||||
let comparisonBaseRef = await tryResolveCheckoutCommitsBaseRef(
|
||||
cwd,
|
||||
resolvedBaseRef,
|
||||
@@ -2991,13 +3040,14 @@ async function resolveCheckoutDiffRefs(
|
||||
return { baseRef: "HEAD", includeUntracked: true };
|
||||
}
|
||||
const { storedBaseRef, resolvedBaseRef } = await resolveBaseRefForCwd(cwd, context);
|
||||
const baseRef = compare.baseRef ?? resolvedBaseRef;
|
||||
const baseRef = resolveOperationBaseRef({
|
||||
storedBaseRef,
|
||||
resolvedBaseRef,
|
||||
requestedBaseRef: compare.baseRef,
|
||||
});
|
||||
if (!baseRef) {
|
||||
return null;
|
||||
}
|
||||
if (storedBaseRef && compare.baseRef && compare.baseRef !== storedBaseRef) {
|
||||
throw baseRefMismatchError({ stored: storedBaseRef, requested: compare.baseRef });
|
||||
}
|
||||
const bestBaseRef = await resolveBestComparisonBaseRef(cwd, baseRef);
|
||||
return {
|
||||
baseRef: (await tryResolveMergeBase(cwd, bestBaseRef)) ?? bestBaseRef,
|
||||
@@ -3209,18 +3259,18 @@ export async function mergeToBase(
|
||||
await requireGitRepo(cwd);
|
||||
const currentBranch = await getCurrentBranch(cwd);
|
||||
const { storedBaseRef, resolvedBaseRef } = await resolveBaseRefForCwd(cwd, context);
|
||||
const baseRef = options.baseRef ?? resolvedBaseRef;
|
||||
const baseRef = resolveOperationBaseRef({
|
||||
storedBaseRef,
|
||||
resolvedBaseRef,
|
||||
requestedBaseRef: options.baseRef,
|
||||
});
|
||||
if (!baseRef) {
|
||||
throw new Error("Unable to determine base branch for merge");
|
||||
}
|
||||
if (storedBaseRef && options.baseRef && options.baseRef !== storedBaseRef) {
|
||||
throw baseRefMismatchError({ stored: storedBaseRef, requested: options.baseRef });
|
||||
}
|
||||
if (!currentBranch) {
|
||||
throw new Error("Unable to determine current branch for merge");
|
||||
}
|
||||
let normalizedBaseRef = baseRef;
|
||||
normalizedBaseRef = normalizeLocalBranchRefName(normalizedBaseRef);
|
||||
const normalizedBaseRef = resolveMergeTargetBranch(baseRef);
|
||||
const currentWorktreeRoot = (await getWorktreeRoot(cwd, context)) ?? cwd;
|
||||
if (normalizedBaseRef === currentBranch) {
|
||||
return currentWorktreeRoot;
|
||||
@@ -3285,13 +3335,14 @@ export async function mergeFromBase(
|
||||
}
|
||||
|
||||
const { storedBaseRef, resolvedBaseRef } = await resolveBaseRefForCwd(cwd, context);
|
||||
const baseRef = options.baseRef ?? resolvedBaseRef;
|
||||
const baseRef = resolveOperationBaseRef({
|
||||
storedBaseRef,
|
||||
resolvedBaseRef,
|
||||
requestedBaseRef: options.baseRef,
|
||||
});
|
||||
if (!baseRef) {
|
||||
throw new Error("Unable to determine base branch for merge");
|
||||
}
|
||||
if (storedBaseRef && options.baseRef && options.baseRef !== storedBaseRef) {
|
||||
throw baseRefMismatchError({ stored: storedBaseRef, requested: options.baseRef });
|
||||
}
|
||||
|
||||
const requireCleanTarget = options.requireCleanTarget ?? true;
|
||||
if (requireCleanTarget) {
|
||||
@@ -3304,8 +3355,7 @@ export async function mergeFromBase(
|
||||
}
|
||||
}
|
||||
|
||||
const normalizedBaseRef = normalizeLocalBranchRefName(baseRef);
|
||||
const bestBaseRef = await resolveMostAheadBaseRef(cwd, normalizedBaseRef);
|
||||
const bestBaseRef = await resolveMostAheadBaseRef(cwd, baseRef);
|
||||
if (bestBaseRef === currentBranch) {
|
||||
return;
|
||||
}
|
||||
@@ -3619,17 +3669,18 @@ export async function createPullRequest(
|
||||
|
||||
const head = options.head ?? (await getCurrentBranch(cwd));
|
||||
const { storedBaseRef, resolvedBaseRef } = await resolveBaseRefForCwd(cwd, context);
|
||||
const base = options.base ?? resolvedBaseRef;
|
||||
const base = resolveOperationBaseRef({
|
||||
storedBaseRef,
|
||||
resolvedBaseRef,
|
||||
requestedBaseRef: options.base,
|
||||
});
|
||||
if (!head) {
|
||||
throw new Error("Unable to determine head branch for PR");
|
||||
}
|
||||
if (!base) {
|
||||
throw new Error("Unable to determine base branch for PR");
|
||||
}
|
||||
const normalizedBase = normalizeLocalBranchRefName(base);
|
||||
if (storedBaseRef && options.base && options.base !== storedBaseRef) {
|
||||
throw baseRefMismatchError({ stored: storedBaseRef, requested: options.base });
|
||||
}
|
||||
const normalizedBase = branchNameFromRef(base);
|
||||
|
||||
// The push deliberately happens before the adapter resolves the target
|
||||
// repository: slug resolution is adapter-internal (e.g. `gh repo view`, which
|
||||
|
||||
Reference in New Issue
Block a user