Files
paseo/packages/server/src/utils/checkout-git-ref-derived.test.ts
T
Mohamed Boudra 05dc2022c8 Keep shared-worktree fetches proportional to ref changes (#3078)
* fix(server): bound shared-worktree fetch refreshes

* fix(server): preserve buffered git metadata events

* fix(server): cover remote ref selection changes

* fix(server): preserve narrow fetch echo routing

* fix(server): preserve ref updates during refresh

Keep normal refresh requeue semantics for watcher events that arrive while a ref-derived calculation is active, so a second same-ref movement cannot be mistaken for the original fetch echo.

* fix(server): narrow external fetch ref updates

Use authoritative fetch boundaries to separate watcher echoes from outside events, and retain remote ref names so packed-to-loose materialization from other Git clients stays narrow without weakening add/delete fallbacks.
2026-08-09 12:51:00 +02:00

186 lines
5.5 KiB
TypeScript

import { execFileSync } from "node:child_process";
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { afterEach, expect, test } from "vitest";
import { type CheckoutSnapshotFacts, getCheckoutRefDerivedState } from "./checkout-git.js";
import {
startGitCommandMetrics,
stopGitCommandMetrics,
waitForGitCommandMetricsIdle,
} from "./run-git-command.js";
const cleanupPaths: string[] = [];
afterEach(() => {
for (const path of cleanupPaths.splice(0)) {
rmSync(path, { recursive: true, force: true });
}
});
function git(cwd: string, args: string[]): string {
return execFileSync("git", args, {
cwd,
encoding: "utf8",
env: { ...process.env, GIT_CONFIG_NOSYSTEM: "1" },
}).trim();
}
function seedFeatureCheckout(): {
cwd: string;
facts: Extract<CheckoutSnapshotFacts, { isGit: true }>;
} {
const cwd = mkdtempSync(join(tmpdir(), "paseo-ref-derived-"));
cleanupPaths.push(cwd);
git(cwd, ["init", "-b", "main"]);
git(cwd, ["config", "user.email", "repro@example.com"]);
git(cwd, ["config", "user.name", "Repro"]);
writeFileSync(join(cwd, "base.txt"), "base\n");
git(cwd, ["add", "base.txt"]);
git(cwd, ["commit", "-m", "base"]);
const base = git(cwd, ["rev-parse", "HEAD"]);
git(cwd, ["update-ref", "refs/remotes/origin/main", base]);
git(cwd, ["switch", "--quiet", "-c", "feature"]);
writeFileSync(join(cwd, "feature.txt"), "feature\n");
git(cwd, ["add", "feature.txt"]);
git(cwd, ["commit", "-m", "feature"]);
return {
cwd,
facts: {
isGit: true,
worktreeRoot: cwd,
currentBranch: "feature",
remoteUrl: "https://example.com/repo.git",
absoluteGitDir: join(cwd, ".git"),
gitCommonDir: join(cwd, ".git"),
paseoWorktree: { isPaseoOwnedWorktree: false },
storedBaseRef: null,
resolvedBaseRef: "main",
mainRepoRoot: null,
comparisonBaseRef: "origin/main",
branchRemoteName: "origin",
branchMergeRef: "refs/heads/main",
upstreamStatus: {
ref: "refs/remotes/origin/main",
aheadBehind: { ahead: 0, behind: 0 },
},
pullRequestLookupTarget: { headRef: "feature" },
},
};
}
function seedMainCheckout(): {
cwd: string;
facts: Extract<CheckoutSnapshotFacts, { isGit: true }>;
} {
const cwd = mkdtempSync(join(tmpdir(), "paseo-ref-derived-main-"));
cleanupPaths.push(cwd);
git(cwd, ["init", "-b", "main"]);
git(cwd, ["config", "user.email", "repro@example.com"]);
git(cwd, ["config", "user.name", "Repro"]);
writeFileSync(join(cwd, "base.txt"), "base\n");
git(cwd, ["add", "base.txt"]);
git(cwd, ["commit", "-m", "base"]);
git(cwd, ["update-ref", "refs/remotes/origin/main", git(cwd, ["rev-parse", "HEAD"])]);
writeFileSync(join(cwd, "local.txt"), "local\n");
git(cwd, ["add", "local.txt"]);
git(cwd, ["commit", "-m", "local"]);
return {
cwd,
facts: {
isGit: true,
worktreeRoot: cwd,
currentBranch: "main",
remoteUrl: "https://example.com/repo.git",
absoluteGitDir: join(cwd, ".git"),
gitCommonDir: join(cwd, ".git"),
paseoWorktree: { isPaseoOwnedWorktree: false },
storedBaseRef: null,
resolvedBaseRef: "main",
mainRepoRoot: null,
comparisonBaseRef: null,
branchRemoteName: "origin",
branchMergeRef: "refs/heads/main",
upstreamStatus: {
ref: "refs/remotes/origin/main",
aheadBehind: { ahead: 1, behind: 0 },
},
pullRequestLookupTarget: { headRef: "main" },
},
};
}
test("one comparison refresh updates a shared base and upstream ref", async () => {
const { cwd, facts } = seedFeatureCheckout();
startGitCommandMetrics();
const result = await getCheckoutRefDerivedState(
cwd,
facts,
{ aheadBehind: { ahead: 0, behind: 0 }, diffStat: null },
new Set(["origin/main"]),
);
await waitForGitCommandMetricsIdle({ quietMs: 50, timeoutMs: 5_000 });
const metrics = stopGitCommandMetrics();
expect(result.aheadBehind).toEqual({ ahead: 1, behind: 0 });
expect(result.upstreamStatus).toEqual({
ref: "refs/remotes/origin/main",
aheadBehind: { ahead: 1, behind: 0 },
});
expect(result.diffStat).toEqual({ additions: 1, deletions: 0 });
expect(metrics.submissions.map((command) => command.args[0]).sort()).toEqual([
"diff",
"ls-files",
"merge-base",
"rev-list",
]);
});
test("an upstream move refreshes the main checkout shortstat", async () => {
const { cwd, facts } = seedMainCheckout();
git(cwd, ["update-ref", "refs/remotes/origin/main", git(cwd, ["rev-parse", "HEAD"])]);
const result = await getCheckoutRefDerivedState(
cwd,
facts,
{ aheadBehind: null, diffStat: { additions: 1, deletions: 0 } },
new Set(["origin/main"]),
);
expect(result).toEqual({
aheadBehind: null,
diffStat: null,
upstreamStatus: {
ref: "refs/remotes/origin/main",
aheadBehind: { ahead: 0, behind: 0 },
},
});
});
test("an origin move refreshes an untracked main checkout shortstat", async () => {
const seeded = seedMainCheckout();
const facts = {
...seeded.facts,
branchRemoteName: null,
branchMergeRef: null,
upstreamStatus: null,
};
git(seeded.cwd, [
"update-ref",
"refs/remotes/origin/main",
git(seeded.cwd, ["rev-parse", "HEAD"]),
]);
expect(
await getCheckoutRefDerivedState(
seeded.cwd,
facts,
{ aheadBehind: null, diffStat: { additions: 1, deletions: 0 } },
new Set(["origin/main"]),
),
).toEqual({ aheadBehind: null, diffStat: null, upstreamStatus: null });
});