Public: anyone can read this; the crew can change it.

A fork's files page reads its drift from the parent under 3 ms when neither tip has moved #6317

opened this

Check · Review · Land · Live — Changes were asked on this head. Next: builder, changes were asked on this head.

A reviewer asked for changes Review

scout/forge:scout/6146-a-fork-s-files-page-reads-its-drift-from → main 6 commits fork

Review

Sign in to review this change.
2 files changed +77 −8
src/actions/forks.ts +21 −8
@@ -25,6 +25,7 @@ import { divergence } from "../git/branches.ts";
2525 import { runGit } from "../git/env.ts";
2626 import { isSafeRef } from "../git/guard.ts";
2727 import { resolveOid } from "../git/merge.ts";
28 import { readTips } from "../git/tips.ts";
2829 import { repoDir } from "../git/repos.ts";
2930 import { err, type Result } from "../kernel/result.ts";
3031 import { requireWriter, type AccessError } from "./access.ts";
@@ -97,14 +98,23 @@ export async function forkDrift(
9798 ): Promise<ForkDrift> {
9899 const forkDir = repoDir(app.reposRoot, fork.ownerName, fork.name);
99100 const parentDir = repoDir(app.reposRoot, parent.ownerName, parent.name);
100 const head = await resolveOid(forkDir, `refs/heads/${fork.defaultBranch}`);
101 const base = await resolveOid(
102 parentDir,
103 `refs/heads/${parent.defaultBranch}`,
104 );
101 const headRef = `refs/heads/${fork.defaultBranch}`;
102 const baseRef = `refs/heads/${parent.defaultBranch}`;
103 const head =
104 (await readTips(forkDir, [headRef])).get(headRef) ??
105 (await resolveOid(forkDir, headRef));
106 const base =
107 (await readTips(parentDir, [baseRef])).get(baseRef) ??
108 (await resolveOid(parentDir, baseRef));
105109 if (head === undefined || base === undefined)
106110 return { ahead: null, behind: null };
107 return divergence(forkDir, base, head);
111 const key = `${base}:${head}`;
112 let drift = standing.get(key);
113 if (drift === undefined) {
114 drift = await divergence(forkDir, base, head);
115 remember(key, drift);
116 }
117 return drift;
108118 }
109119
110120 // ---- ranking ----
@@ -157,6 +167,10 @@ export const nothingNewHeading = (n: number): string =>
157167 */
158168 const standing = new Map<string, ForkDrift>();
159169 const STANDING_MAX = 2_000;
170 const remember = (key: string, drift: ForkDrift): void => {
171 if (standing.size >= STANDING_MAX) standing.clear();
172 standing.set(key, drift);
173 };
160174
161175 async function tipAndTime(
162176 dir: string,
@@ -194,8 +208,7 @@ export async function driftOfForks(
194208 let drift = standing.get(key);
195209 if (drift === undefined) {
196210 drift = await divergence(dir, base, tip.oid);
197 if (standing.size >= STANDING_MAX) standing.clear();
198 standing.set(key, drift);
211 remember(key, drift);
199212 }
200213 return { fork, drift, pushedAt: tip.pushedAt };
201214 }),
test/fork-drift-cache.test.ts added +56
@@ -0,0 +1,56 @@
1 import { afterAll, beforeAll, expect, spyOn, test } from "bun:test";
2 import { mkdtemp, rm } from "node:fs/promises";
3 import { tmpdir } from "node:os";
4 import { join } from "node:path";
5 import { commitToBranch, createRepo, forkRepo } from "../src/actions/repos.ts";
6 import { forkDrift } from "../src/actions/forks.ts";
7 import { createApp, type App } from "../src/app.ts";
8 import { createUser, type User } from "../src/core/users.ts";
9 import type { Repo } from "../src/core/repos.ts";
10 import { promote } from "./helpers/members.ts";
11
12 const must = <T>(r: { ok: true; value: T } | { ok: false; error: unknown }): T => {
13 if (!r.ok) throw new Error(JSON.stringify(r.error));
14 return r.value;
15 };
16
17 let dataDir = "";
18 let app: App;
19 let alice: User;
20 let upstream: Repo;
21 let fork: Repo;
22
23 const commit = async (who: User, repo: Repo, name: string) =>
24 must(await commitToBranch(app, who, repo, { message: name, files: [{ path: `${name}.txt`, content: "x\n" }] }));
25
26 beforeAll(async () => {
27 dataDir = await mkdtemp(join(tmpdir(), "forge-drift-cache-"));
28 app = createApp({ dataDir, port: 0 });
29 alice = must(await createUser(app.db, { origin: "seed", trusted: true, username: "alice", email: "[email protected]", password: "a-good-passphrase", adminIfFirst: true }));
30 promote(app.db, alice);
31 const bob = must(await createUser(app.db, { origin: "seed", trusted: true, username: "bob", email: "[email protected]", password: "a-good-passphrase" }));
32 promote(app.db, bob);
33 upstream = must(await createRepo(app, alice, { name: "app", visibility: "public" }));
34 await commit(alice, upstream, "one");
35 fork = must(await forkRepo(app, bob, upstream));
36 await commit(bob, fork, "mine");
37 }, 60_000);
38
39 afterAll(async () => {
40 app.close();
41 await rm(dataDir, { recursive: true, force: true });
42 });
43
44 test("a warm read spawns no git; a parent push changes behind", async () => {
45 expect(await forkDrift(app, fork, upstream)).toEqual({ ahead: 1, behind: 0 });
46 const spawn = spyOn(Bun, "spawn");
47 try {
48 const warm = await forkDrift(app, fork, upstream);
49 expect(warm).toEqual({ ahead: 1, behind: 0 });
50 expect(spawn).not.toHaveBeenCalled();
51 } finally {
52 spawn.mockRestore();
53 }
54 await commit(alice, upstream, "two");
55 expect(await forkDrift(app, fork, upstream)).toEqual({ ahead: 1, behind: 1 });
56 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs