Files
paseo/packages/app/src/git/checkout-status-cache.ts
T
Mohamed BoudraandTianyu Yang 3ed57e9dd7 Change a worktree workspace base from Changes (#8642)
Change a worktree workspace's base from the Changes pane. The choice persists and diffs, counts, commits and Git actions follow it.

Supersedes #6872.

Co-authored-by: Tianyu Yang <yangtianyu.cl@bytedance.com>
2026-11-05 22:30:14 +07:00

115 lines
3.7 KiB
TypeScript

import type { QueryClient } from "@tanstack/react-query";
import type { CheckoutStatusResponse, CheckoutStatusUpdate } from "@getpaseo/protocol/messages";
import equal from "fast-deep-equal/es6";
import {
checkoutCommitsQueryKey,
checkoutPrStatusQueryKey,
checkoutStatusQueryKey,
invalidatePrPaneTimelineForCheckout,
} from "@/git/query-keys";
import { type CheckoutPrStatusPayload, normalizeCheckoutPrStatusPayload } from "@/git/pr-status";
import { resetDraftAgentCommandsForCheckout } from "@/agent-commands/cache";
export type CheckoutStatusPayload = CheckoutStatusResponse["payload"];
export type { CheckoutPrStatusPayload } from "@/git/pr-status";
export interface CheckoutStatusClient {
getCheckoutStatus: (cwd: string) => Promise<CheckoutStatusPayload>;
}
export async function fetchCheckoutStatus({
client,
cwd,
}: {
client: CheckoutStatusClient;
serverId: string;
cwd: string;
}): Promise<CheckoutStatusPayload> {
const payload = normalizeCheckoutBaseRef(await client.getCheckoutStatus(cwd));
return payload;
}
export async function ensureCheckoutStatus({
queryClient,
client,
serverId,
cwd,
}: {
queryClient: QueryClient;
client: CheckoutStatusClient;
serverId: string;
cwd: string;
}): Promise<CheckoutStatusPayload> {
return await queryClient.fetchQuery({
queryKey: checkoutStatusQueryKey(serverId, cwd),
queryFn: () => fetchCheckoutStatus({ client, serverId, cwd }),
staleTime: Infinity,
});
}
export function applyCheckoutStatusUpdateFromEvent({
queryClient,
serverId,
message,
}: {
queryClient: QueryClient;
serverId: string;
message: CheckoutStatusUpdate;
}): void {
const payload = normalizeCheckoutBaseRef(message.payload);
const prStatus = payload.prStatus
? normalizeCheckoutPrStatusPayload(payload.prStatus)
: undefined;
const cachePayload = prStatus ? { ...payload, prStatus } : payload;
const previousStatus = queryClient.getQueryData<CheckoutStatusPayload>(
checkoutStatusQueryKey(serverId, payload.cwd),
);
queryClient.setQueryData(checkoutStatusQueryKey(serverId, payload.cwd), cachePayload);
if (previousStatus?.currentBranch !== payload.currentBranch) {
void resetDraftAgentCommandsForCheckout(queryClient, { serverId, cwd: payload.cwd });
}
void queryClient.invalidateQueries({
queryKey: checkoutCommitsQueryKey(serverId, payload.cwd),
});
if (!prStatus) {
return;
}
const previous = queryClient.getQueryData<CheckoutPrStatusPayload>(
checkoutPrStatusQueryKey(serverId, prStatus.cwd),
);
queryClient.setQueryData(checkoutPrStatusQueryKey(serverId, prStatus.cwd), prStatus);
// The PR activity timeline has no push channel; mark it stale when the pushed PR status
// meaningfully changed. Active panes refetch immediately, evicted ones on next mount.
if (hasPrStatusChanged(previous, prStatus)) {
void invalidatePrPaneTimelineForCheckout(queryClient, { serverId, cwd: prStatus.cwd });
}
}
function normalizeCheckoutBaseRef<T extends CheckoutStatusPayload>(payload: T): T {
// COMPAT(workspaceBaseBranch): hosts <= v0.11.2 only send the display ref; remove after 2027-10-14.
return !payload.isGit || payload.baseRefExact === undefined
? payload
: { ...payload, baseRef: payload.baseRefExact };
}
// requestId changes on every emission and carries no PR state.
function prStatusWithoutVolatileFields(
prStatus: CheckoutPrStatusPayload,
): Omit<CheckoutPrStatusPayload, "requestId"> {
const { requestId: _requestId, ...rest } = prStatus;
return rest;
}
function hasPrStatusChanged(
previous: CheckoutPrStatusPayload | undefined,
next: CheckoutPrStatusPayload,
): boolean {
if (!previous) {
return true;
}
return !equal(prStatusWithoutVolatileFields(previous), prStatusWithoutVolatileFields(next));
}