Reduce startup git work for workspace snapshots
This commit is contained in:
@@ -723,8 +723,29 @@ export interface MergeFromBaseOptions {
|
||||
export interface CheckoutContext {
|
||||
paseoHome?: string;
|
||||
logger?: Pick<Logger, "trace">;
|
||||
facts?: CheckoutSnapshotFacts | null;
|
||||
}
|
||||
|
||||
export type CheckoutSnapshotFacts =
|
||||
| {
|
||||
isGit: false;
|
||||
}
|
||||
| {
|
||||
isGit: true;
|
||||
worktreeRoot: string;
|
||||
currentBranch: string | null;
|
||||
remoteUrl: string | null;
|
||||
paseoWorktree: PaseoWorktreeForCwd;
|
||||
storedBaseRef: string | null;
|
||||
resolvedBaseRef: string | null;
|
||||
mainRepoRoot: string | null;
|
||||
comparisonBaseRef: string | null;
|
||||
branchRemoteName: string | null;
|
||||
branchMergeRef: string | null;
|
||||
trackedOriginBranch: string | null;
|
||||
pullRequestLookupTarget: PullRequestStatusLookupTarget | null;
|
||||
};
|
||||
|
||||
function isGitError(error: unknown): boolean {
|
||||
if (!(error instanceof Error)) {
|
||||
return false;
|
||||
@@ -915,6 +936,7 @@ type PaseoWorktreeForCwd =
|
||||
async function getPaseoWorktreeForCwd(
|
||||
cwd: string,
|
||||
context?: CheckoutContext,
|
||||
knownWorktreeRoot?: string | null,
|
||||
): Promise<PaseoWorktreeForCwd> {
|
||||
// Fast-path reject: non-worktree paths do not need expensive ownership checks.
|
||||
if (!/[\\/]worktrees[\\/]/.test(cwd)) {
|
||||
@@ -928,7 +950,7 @@ async function getPaseoWorktreeForCwd(
|
||||
|
||||
return {
|
||||
isPaseoOwnedWorktree: true,
|
||||
worktreeRoot: (await getWorktreeRoot(cwd)) ?? cwd,
|
||||
worktreeRoot: knownWorktreeRoot ?? (await getWorktreeRoot(cwd)) ?? cwd,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -940,6 +962,9 @@ async function getStoredBaseRefForCwd(
|
||||
cwd: string,
|
||||
context?: CheckoutContext,
|
||||
): Promise<string | null> {
|
||||
if (context?.facts?.isGit) {
|
||||
return context.facts.storedBaseRef;
|
||||
}
|
||||
const paseoWorktree = await getPaseoWorktreeForCwd(cwd, context);
|
||||
if (!paseoWorktree.isPaseoOwnedWorktree) {
|
||||
return null;
|
||||
@@ -952,6 +977,9 @@ async function getResolvedBaseRefForCwd(
|
||||
cwd: string,
|
||||
context?: CheckoutContext,
|
||||
): Promise<string | null> {
|
||||
if (context?.facts?.isGit) {
|
||||
return context.facts.resolvedBaseRef;
|
||||
}
|
||||
const { resolvedBaseRef } = await resolveBaseRefForCwd(cwd, context);
|
||||
return resolvedBaseRef;
|
||||
}
|
||||
@@ -965,6 +993,12 @@ async function resolveBaseRefForCwd(
|
||||
cwd: string,
|
||||
context?: CheckoutContext,
|
||||
): Promise<BaseRefResolution> {
|
||||
if (context?.facts?.isGit) {
|
||||
return {
|
||||
storedBaseRef: context.facts.storedBaseRef,
|
||||
resolvedBaseRef: context.facts.resolvedBaseRef,
|
||||
};
|
||||
}
|
||||
const storedBaseRef = await getStoredBaseRefForCwd(cwd, context);
|
||||
return {
|
||||
storedBaseRef,
|
||||
@@ -1029,7 +1063,11 @@ function parseBranchMergeHeadRef(mergeRef: string | null): string | null {
|
||||
async function resolvePullRequestStatusLookupTarget(
|
||||
cwd: string,
|
||||
currentBranch: string,
|
||||
context?: CheckoutContext,
|
||||
): Promise<PullRequestStatusLookupTarget> {
|
||||
if (context?.facts?.isGit && context.facts.pullRequestLookupTarget) {
|
||||
return context.facts.pullRequestLookupTarget;
|
||||
}
|
||||
const remoteName = await getGitConfigValue(cwd, `branch.${currentBranch}.remote`);
|
||||
if (!remoteName?.startsWith("paseo-pr-")) {
|
||||
return { headRef: currentBranch };
|
||||
@@ -1251,7 +1289,13 @@ async function getAheadBehind(
|
||||
if (!normalizedBaseRef || !currentBranch || normalizedBaseRef === currentBranch) {
|
||||
return null;
|
||||
}
|
||||
const comparisonBaseRef = await resolveBestComparisonBaseRef(cwd, baseRef, context);
|
||||
const comparisonBaseRef =
|
||||
context?.facts?.isGit && context.facts.resolvedBaseRef === baseRef
|
||||
? context.facts.comparisonBaseRef
|
||||
: await resolveBestComparisonBaseRef(cwd, baseRef, context);
|
||||
if (!comparisonBaseRef) {
|
||||
return null;
|
||||
}
|
||||
const { stdout } = await runGitCommand(
|
||||
["rev-list", "--left-right", "--count", `${comparisonBaseRef}...${currentBranch}`],
|
||||
{ cwd, envOverlay: READ_ONLY_GIT_ENV, logger: context?.logger },
|
||||
@@ -1309,6 +1353,9 @@ async function getTrackedOriginBranch(
|
||||
currentBranch: string,
|
||||
context?: CheckoutContext,
|
||||
): Promise<string | null> {
|
||||
if (context?.facts?.isGit && context.facts.currentBranch === currentBranch) {
|
||||
return context.facts.trackedOriginBranch;
|
||||
}
|
||||
const remoteName = await getGitConfigValue(cwd, `branch.${currentBranch}.remote`, context);
|
||||
if (remoteName !== "origin") {
|
||||
return null;
|
||||
@@ -1358,7 +1405,7 @@ async function inspectCheckoutContext(
|
||||
const [currentBranch, remoteUrl, paseoWorktree] = await Promise.all([
|
||||
getCurrentBranch(cwd),
|
||||
getOriginRemoteUrl(cwd),
|
||||
getPaseoWorktreeForCwd(cwd, context),
|
||||
getPaseoWorktreeForCwd(cwd, context, root),
|
||||
]);
|
||||
|
||||
return {
|
||||
@@ -1375,6 +1422,108 @@ async function inspectCheckoutContext(
|
||||
}
|
||||
}
|
||||
|
||||
function buildPullRequestLookupTargetFromBranchConfig(input: {
|
||||
currentBranch: string;
|
||||
branchRemoteName: string | null;
|
||||
branchMergeRef: string | null;
|
||||
branchRemoteUrl: string | null;
|
||||
}): PullRequestStatusLookupTarget {
|
||||
if (!input.branchRemoteName?.startsWith("paseo-pr-")) {
|
||||
return { headRef: input.currentBranch };
|
||||
}
|
||||
|
||||
const trackedHeadRef = parseBranchMergeHeadRef(input.branchMergeRef);
|
||||
if (!trackedHeadRef) {
|
||||
return { headRef: input.currentBranch };
|
||||
}
|
||||
|
||||
const remoteRepo = input.branchRemoteUrl
|
||||
? parseGitHubRepoFromRemote(input.branchRemoteUrl)
|
||||
: null;
|
||||
const headRepositoryOwner = remoteRepo?.split("/")[0];
|
||||
return {
|
||||
headRef: trackedHeadRef,
|
||||
...(headRepositoryOwner ? { headRepositoryOwner } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export async function getCheckoutSnapshotFacts(
|
||||
cwd: string,
|
||||
context?: CheckoutContext,
|
||||
): Promise<CheckoutSnapshotFacts> {
|
||||
if (context?.facts) {
|
||||
return context.facts;
|
||||
}
|
||||
|
||||
const inspected = await inspectCheckoutContext(cwd, context);
|
||||
if (!inspected) {
|
||||
return { isGit: false };
|
||||
}
|
||||
|
||||
const storedBaseRef = inspected.paseoWorktree.isPaseoOwnedWorktree
|
||||
? readPaseoWorktreeBaseRef(inspected.paseoWorktree.worktreeRoot)
|
||||
: null;
|
||||
const resolvedBaseRef = storedBaseRef ?? (await resolveBaseRef(cwd));
|
||||
const mainRepoRoot = await getMainRepoRoot(cwd).catch(() => null);
|
||||
let comparisonBaseRef: string | null = null;
|
||||
if (
|
||||
resolvedBaseRef &&
|
||||
inspected.currentBranch &&
|
||||
normalizeLocalBranchRefName(resolvedBaseRef) !== inspected.currentBranch
|
||||
) {
|
||||
comparisonBaseRef = await resolveBestComparisonBaseRef(cwd, resolvedBaseRef, context).catch(
|
||||
() => null,
|
||||
);
|
||||
}
|
||||
|
||||
let branchRemoteName: string | null = null;
|
||||
let branchMergeRef: string | null = null;
|
||||
let branchRemoteUrl: string | null = null;
|
||||
if (inspected.remoteUrl && inspected.currentBranch) {
|
||||
branchRemoteName = await getGitConfigValue(
|
||||
cwd,
|
||||
`branch.${inspected.currentBranch}.remote`,
|
||||
context,
|
||||
);
|
||||
if (branchRemoteName) {
|
||||
branchMergeRef = await getGitConfigValue(
|
||||
cwd,
|
||||
`branch.${inspected.currentBranch}.merge`,
|
||||
context,
|
||||
);
|
||||
if (branchRemoteName.startsWith("paseo-pr-")) {
|
||||
branchRemoteUrl = await getGitConfigValue(cwd, `remote.${branchRemoteName}.url`, context);
|
||||
}
|
||||
}
|
||||
}
|
||||
const trackedOriginBranch =
|
||||
branchRemoteName === "origin" ? parseBranchMergeHeadRef(branchMergeRef) : null;
|
||||
const pullRequestLookupTarget = inspected.currentBranch
|
||||
? buildPullRequestLookupTargetFromBranchConfig({
|
||||
currentBranch: inspected.currentBranch,
|
||||
branchRemoteName,
|
||||
branchMergeRef,
|
||||
branchRemoteUrl,
|
||||
})
|
||||
: null;
|
||||
|
||||
return {
|
||||
isGit: true,
|
||||
worktreeRoot: inspected.worktreeRoot,
|
||||
currentBranch: inspected.currentBranch,
|
||||
remoteUrl: inspected.remoteUrl,
|
||||
paseoWorktree: inspected.paseoWorktree,
|
||||
storedBaseRef,
|
||||
resolvedBaseRef,
|
||||
mainRepoRoot,
|
||||
comparisonBaseRef,
|
||||
branchRemoteName,
|
||||
branchMergeRef,
|
||||
trackedOriginBranch,
|
||||
pullRequestLookupTarget,
|
||||
};
|
||||
}
|
||||
|
||||
const PER_FILE_DIFF_MAX_BYTES = 1024 * 1024; // 1MB
|
||||
const TOTAL_DIFF_MAX_BYTES = 2 * 1024 * 1024; // 2MB
|
||||
const UNTRACKED_BINARY_SNIFF_BYTES = 16 * 1024;
|
||||
@@ -1489,28 +1638,29 @@ export async function getCheckoutStatus(
|
||||
cwd: string,
|
||||
context?: CheckoutContext,
|
||||
): Promise<CheckoutStatusResult> {
|
||||
const inspected = await inspectCheckoutContext(cwd, context);
|
||||
if (!inspected) {
|
||||
const facts = await getCheckoutSnapshotFacts(cwd, context);
|
||||
if (!facts.isGit) {
|
||||
return { isGit: false };
|
||||
}
|
||||
|
||||
const worktreeRoot = inspected.worktreeRoot;
|
||||
const currentBranch = inspected.currentBranch;
|
||||
const remoteUrl = inspected.remoteUrl;
|
||||
const paseoWorktree = inspected.paseoWorktree;
|
||||
const worktreeRoot = facts.worktreeRoot;
|
||||
const currentBranch = facts.currentBranch;
|
||||
const remoteUrl = facts.remoteUrl;
|
||||
const paseoWorktree = facts.paseoWorktree;
|
||||
const isDirty = await isWorkingTreeDirty(cwd, context);
|
||||
const hasRemote = remoteUrl !== null;
|
||||
const { resolvedBaseRef: baseRef } = await resolveBaseRefForCwd(cwd, context);
|
||||
const mainRepoRoot = await getMainRepoRoot(cwd).catch(() => null);
|
||||
const baseRef = facts.resolvedBaseRef;
|
||||
const mainRepoRoot = facts.mainRepoRoot;
|
||||
const factsContext = { ...context, facts };
|
||||
const [aheadBehind, aheadOfOrigin, behindOfOrigin] = await Promise.all([
|
||||
baseRef && currentBranch
|
||||
? getAheadBehind(cwd, baseRef, currentBranch, context)
|
||||
? getAheadBehind(cwd, baseRef, currentBranch, factsContext)
|
||||
: Promise.resolve(null),
|
||||
hasRemote && currentBranch
|
||||
? getAheadOfOrigin(cwd, currentBranch, baseRef, context)
|
||||
? getAheadOfOrigin(cwd, currentBranch, baseRef, factsContext)
|
||||
: Promise.resolve(null),
|
||||
hasRemote && currentBranch
|
||||
? getBehindOfOrigin(cwd, currentBranch, context)
|
||||
? getBehindOfOrigin(cwd, currentBranch, factsContext)
|
||||
: Promise.resolve(null),
|
||||
]);
|
||||
|
||||
@@ -1616,30 +1766,29 @@ async function getCheckoutShortstatUncached(
|
||||
cwd: string,
|
||||
context?: CheckoutContext,
|
||||
): Promise<CheckoutShortstat | null> {
|
||||
try {
|
||||
await requireGitRepo(cwd);
|
||||
} catch {
|
||||
if (context?.facts?.isGit === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const localBaseRef = await getResolvedBaseRefForCwd(cwd, context);
|
||||
const currentBranch = await getCurrentBranch(cwd);
|
||||
|
||||
let comparisonRef: string;
|
||||
|
||||
if (currentBranch && localBaseRef && currentBranch !== localBaseRef) {
|
||||
if (!context?.facts?.isGit) {
|
||||
try {
|
||||
comparisonRef = await resolveBestComparisonBaseRef(cwd, localBaseRef);
|
||||
await requireGitRepo(cwd);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
} else if (currentBranch) {
|
||||
const hasOrigin = await doesGitRefExist(cwd, `refs/remotes/origin/${currentBranch}`);
|
||||
if (!hasOrigin) {
|
||||
return null;
|
||||
}
|
||||
comparisonRef = `origin/${currentBranch}`;
|
||||
} else {
|
||||
}
|
||||
|
||||
const facts = context?.facts;
|
||||
const localBaseRef = facts?.isGit
|
||||
? facts.resolvedBaseRef
|
||||
: await getResolvedBaseRefForCwd(cwd, context);
|
||||
const currentBranch = facts?.isGit ? facts.currentBranch : await getCurrentBranch(cwd);
|
||||
const comparisonRef = await resolveShortstatComparisonRef({
|
||||
cwd,
|
||||
currentBranch,
|
||||
localBaseRef,
|
||||
facts,
|
||||
});
|
||||
if (!comparisonRef) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1675,6 +1824,31 @@ async function getCheckoutShortstatUncached(
|
||||
}
|
||||
}
|
||||
|
||||
async function resolveShortstatComparisonRef(input: {
|
||||
cwd: string;
|
||||
currentBranch: string | null;
|
||||
localBaseRef: string | null;
|
||||
facts?: CheckoutSnapshotFacts | null;
|
||||
}): Promise<string | null> {
|
||||
const { cwd, currentBranch, localBaseRef, facts } = input;
|
||||
if (!currentBranch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (localBaseRef && currentBranch !== localBaseRef) {
|
||||
try {
|
||||
return facts?.isGit && facts.resolvedBaseRef === localBaseRef && facts.comparisonBaseRef
|
||||
? facts.comparisonBaseRef
|
||||
: await resolveBestComparisonBaseRef(cwd, localBaseRef);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const hasOrigin = await doesGitRefExist(cwd, `refs/remotes/origin/${currentBranch}`);
|
||||
return hasOrigin ? `origin/${currentBranch}` : null;
|
||||
}
|
||||
|
||||
function getOrLoadCheckoutShortstat(
|
||||
cwd: string,
|
||||
context?: CheckoutContext,
|
||||
@@ -2460,6 +2634,7 @@ export async function getPullRequestStatus(
|
||||
cwd: string,
|
||||
github: GitHubService = createGitHubService(),
|
||||
options?: CheckoutReadCacheOptions,
|
||||
context?: CheckoutContext,
|
||||
): Promise<PullRequestStatusResult> {
|
||||
const cacheKey = getPullRequestStatusCacheKey(cwd);
|
||||
if (!options?.force) {
|
||||
@@ -2474,7 +2649,7 @@ export async function getPullRequestStatus(
|
||||
}
|
||||
}
|
||||
|
||||
const lookup = getPullRequestStatusUncached(cwd, github, options)
|
||||
const lookup = getPullRequestStatusUncached(cwd, github, options, context)
|
||||
.then((status) => {
|
||||
pullRequestStatusCache.set(cacheKey, status);
|
||||
rememberPullRequestStatus(cacheKey, status);
|
||||
@@ -2501,9 +2676,18 @@ async function getPullRequestStatusUncached(
|
||||
cwd: string,
|
||||
github: GitHubService,
|
||||
options?: CheckoutReadCacheOptions,
|
||||
context?: CheckoutContext,
|
||||
): Promise<PullRequestStatusResult> {
|
||||
await requireGitRepo(cwd);
|
||||
const head = await getCurrentBranch(cwd);
|
||||
if (context?.facts?.isGit === false) {
|
||||
return {
|
||||
status: null,
|
||||
githubFeaturesEnabled: false,
|
||||
};
|
||||
}
|
||||
if (!context?.facts?.isGit) {
|
||||
await requireGitRepo(cwd);
|
||||
}
|
||||
const head = context?.facts?.isGit ? context.facts.currentBranch : await getCurrentBranch(cwd);
|
||||
if (!head) {
|
||||
return {
|
||||
status: null,
|
||||
@@ -2511,7 +2695,7 @@ async function getPullRequestStatusUncached(
|
||||
};
|
||||
}
|
||||
try {
|
||||
const lookupTarget = await resolvePullRequestStatusLookupTarget(cwd, head);
|
||||
const lookupTarget = await resolvePullRequestStatusLookupTarget(cwd, head, context);
|
||||
let status: GitHubCurrentPullRequestStatus | null;
|
||||
if (options?.force) {
|
||||
const reason = options.reason;
|
||||
|
||||
Reference in New Issue
Block a user