Fix/local branch worktree base (#2328)

* Add automatic plus branch maintenance workflow

* Build plus artifacts for desktop and CLI/server

* Document and summarize plus artifact workflow

* Document Apple signing and pin plus workflow actions

* Disable Linux plus desktop artifact

* Publish plus CLI packages to GitHub Packages

* Fix GitHub Packages publish paths

* Fix plus mise install summary package name

* Disable macOS x64 plus desktop artifact

* Tag plus npm packages as latest for mise

* fix(worktrees): respect selected local base ref

* chore(ci): remove fork-only plus workflow

* fix(worktrees): preserve exact branch picker refs

---------

Co-authored-by: Matt Cowger <1929548+mcowger@users.noreply.github.com>
This commit is contained in:
Matt Cowger
2026-08-01 16:06:56 +00:00
committed by GitHub
co-authored by Matt Cowger
parent b0a5c09ace
commit 76abc50f82
12 changed files with 481 additions and 58 deletions
+55 -14
View File
@@ -231,6 +231,7 @@ function normalizeBranchSuggestionName(raw: string): string | null {
interface GitRef {
name: string;
committerDate: number;
oid: string;
}
export interface BranchSuggestion {
@@ -238,6 +239,8 @@ export interface BranchSuggestion {
committerDate: number;
hasLocal: boolean;
hasRemote: boolean;
localAhead?: number;
localBehind?: number;
}
async function listGitRefs(cwd: string, refPrefix: string): Promise<GitRef[]> {
@@ -245,7 +248,7 @@ async function listGitRefs(cwd: string, refPrefix: string): Promise<GitRef[]> {
[
"for-each-ref",
"--sort=-committerdate",
"--format=%(refname)%09%(committerdate:unix)",
"--format=%(refname)%09%(committerdate:unix)%09%(objectname)",
refPrefix,
],
{ cwd, envOverlay: READ_ONLY_GIT_ENV },
@@ -255,9 +258,9 @@ async function listGitRefs(cwd: string, refPrefix: string): Promise<GitRef[]> {
.map((line) => {
const trimmed = line.trim();
if (!trimmed) return null;
const [name, dateStr] = trimmed.split("\t");
if (!name) return null;
return { name, committerDate: Number(dateStr) || 0 };
const [name, dateStr, oid] = trimmed.split("\t");
if (!name || !oid) return null;
return { name, committerDate: Number(dateStr) || 0, oid };
})
.filter((ref): ref is GitRef => ref !== null);
}
@@ -266,6 +269,8 @@ interface BranchSuggestionMeta {
committerDate: number;
hasLocal: boolean;
hasRemote: boolean;
localOid?: string;
remoteOid?: string;
}
function sortBranchSuggestions(
@@ -304,7 +309,8 @@ export async function listBranchSuggestions(
const requestedLimit = options?.limit ?? 50;
const limit = Math.max(1, Math.min(200, requestedLimit));
const query = options?.query?.trim().toLowerCase() ?? "";
const rawQuery = options?.query?.trim().toLowerCase() ?? "";
const query = normalizeBranchSuggestionName(rawQuery) ?? rawQuery;
const [localRefs, remoteRefs] = await Promise.all([
listGitRefs(cwd, "refs/heads"),
@@ -320,6 +326,8 @@ export async function listBranchSuggestions(
branchMeta.set(normalized, {
hasLocal: true,
hasRemote: existing?.hasRemote ?? false,
localOid: ref.oid,
...(existing?.remoteOid ? { remoteOid: existing.remoteOid } : {}),
committerDate: Math.max(ref.committerDate, existing?.committerDate ?? 0),
});
}
@@ -332,12 +340,14 @@ export async function listBranchSuggestions(
branchMeta.set(normalized, {
hasLocal: false,
hasRemote: true,
remoteOid: ref.oid,
committerDate: ref.committerDate,
});
} else {
branchMeta.set(normalized, {
...existing,
hasRemote: true,
remoteOid: ref.oid,
committerDate: Math.max(ref.committerDate, existing.committerDate),
});
}
@@ -351,15 +361,46 @@ export async function listBranchSuggestions(
}
const ordered = sortBranchSuggestions(filteredNames, branchMeta, query);
return ordered.slice(0, limit).map((name) => {
const meta = branchMeta.get(name);
return {
name,
committerDate: meta?.committerDate ?? 0,
hasLocal: meta?.hasLocal ?? false,
hasRemote: meta?.hasRemote ?? false,
};
});
return Promise.all(
ordered.slice(0, limit).map(async (name): Promise<BranchSuggestion> => {
const meta = branchMeta.get(name);
const suggestion: BranchSuggestion = {
name,
committerDate: meta?.committerDate ?? 0,
hasLocal: meta?.hasLocal ?? false,
hasRemote: meta?.hasRemote ?? false,
};
if (!suggestion.hasLocal || !suggestion.hasRemote) {
return suggestion;
}
if (meta?.localOid && meta.localOid === meta.remoteOid) {
suggestion.localAhead = 0;
suggestion.localBehind = 0;
return suggestion;
}
try {
const { stdout } = await runGitCommand(
[
"rev-list",
"--left-right",
"--count",
`refs/heads/${name}...refs/remotes/origin/${name}`,
],
{ cwd, envOverlay: READ_ONLY_GIT_ENV },
);
const [localAhead, localBehind] = stdout.trim().split(/\s+/).map(Number);
if (Number.isFinite(localAhead) && Number.isFinite(localBehind)) {
suggestion.localAhead = localAhead;
suggestion.localBehind = localBehind;
}
} catch {
// A ref may disappear between listing and comparison. Keep the branch
// available without divergence metadata and let creation re-resolve it.
}
return suggestion;
}),
);
}
export interface LocalBranchCheckoutResolution {