Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions web/lib/city.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,12 @@ export async function finishBuild(ticketToken: string, planToken: string): Promi
maxRepos: 60,
});
} catch {
// nothing to draw. Repos whose replays failed have commits (rust-lang/rust: 34,368 in the year, too big at
// replay time though under the size cap), so "no commits" would be false: name them. The page has the reasons.
const failed = missing.filter(r => r.c > 0).sort((a, b) => b.c - a.c);
if (failed.length) {
throw new HttpError(422, `${failed[0].n} could not be replayed`, { status: 'repo_failed', skipped: failed.slice(0, 5).map(r => ({ name: r.n, commits: r.c })) });
}
throw new HttpError(422, p.kind === 'user' ? 'no public commits on default branches in the last year' : 'no commits in the last year', { status: 'empty' });
}
const v = Date.now();
Expand Down
4 changes: 3 additions & 1 deletion web/lib/routes/gallery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
import { type GalleryEntry, readGallery, readMeta } from '../city.js';
import { handle, json } from '../http.js';

const FEATURED = ['torvalds', 'sindresorhus', 'vercel', 'react/react', 'denoland/deno', 'gaearon', 'karpathy', 'rust-lang/rust', 'withastro', 'antfu'];
// every one must be a city the hosted builder can replay, or its slot stays empty: rust-lang/rust, under the size
// cap, still outgrows the builder's memory at replay time
const FEATURED = ['torvalds', 'sindresorhus', 'vercel', 'react/react', 'denoland/deno', 'gaearon', 'karpathy', 'golang/go', 'withastro', 'antfu'];

// a city built under a repo's old name (facebook/react, now react/react) is a second city of the same repo: the
// list shows the one under the name GitHub uses now
Expand Down
18 changes: 17 additions & 1 deletion web/public/s/city.js
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,14 @@
let meta;
try {
meta = await call('/api/build/finish', { ticket: st.ticket, plan: st.plan });
} catch (e) { clearInterval(clock); return fail(e); }
} catch (e) {
clearInterval(clock);
// nothing drawable because replays failed: each failed repo's own reason is the one its row showed
if (e.info && e.info.status === 'repo_failed') {
for (const s of e.info.skipped || []) { const j = jobs.find(x => x.name === s.name && x.s === 'bad'); if (j) s.reason = j.note; }
}
return fail(e);
}
clearInterval(clock);
if (history.replaceState) history.replaceState(null, '', pagePath() + location.hash);
await open(meta);
Expand Down Expand Up @@ -238,6 +245,15 @@
for now the hosted builder takes repos up to about 1.5 GB.</p>
<div class="cc-actions">${owner ? `<a class="cc-primary" href="/${esc(owner)}">See ${esc(owner)}'s city</a>` : ''}<a href="/explore">Explore other cities</a></div>${foot}`);
}
if (s === 'repo_failed') {
tellParent('missing');
const bad = (info.skipped || [])[0] || {};
const owner = isRepo ? CC.target.split('/')[0] : '';
const heavy = /too big|too long/.test(bad.reason || ''); // not for a fetch that failed or a busy builder
return show(`${head('commit.city', CC.target)}<p class="cc-sub"><b>${esc(bad.name || CC.target)}</b> could not be replayed${bad.reason ? `: ${esc(bad.reason)}` : ''}.
${bad.commits ? `It has ${fmt(bad.commits)} commits from the last year${heavy ? ', more than one hosted build can replay' : ''}.` : ''}</p>
<div class="cc-actions"><button class="cc-primary" id="cc-retry">Try again</button>${owner ? `<a href="/${esc(owner)}">See ${esc(owner)}'s city</a>` : ''}<a href="/explore">Explore other cities</a></div>${foot}`), wireRetry();
}
if (s === 'empty') {
const whole = isRepo && !CC.all;
return show(`${head('commit.city', CC.target)}<p class="cc-sub">${esc(e.message)}.
Expand Down
22 changes: 22 additions & 0 deletions web/test/bundle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { finishBuild, readGallery, REPLAY_GEN, repoPath, usableGen } from '../li
import { aggregate, budgetCaps, bundle, mergeEvents, mineOf, type Payload } from '../lib/engine/bundle';
import { replayRepo } from '../lib/engine/repo';
import type { RepoRecord } from '../lib/engine/replay';
import { HttpError } from '../lib/http';
import { sign } from '../lib/secret';
import { getJSON, putJSON, setStore, type Store, StoreConflict } from '../lib/store';
import { localTransport, Repo, REPO_ROOT, sh, tmpdir } from './helpers';
Expand Down Expand Up @@ -151,6 +152,27 @@ test('finish step: records of another version are never drawn; meta and gallery
assert.equal((await getJSON<{ v: number }>('meta/old-me.json'))?.v, meta.v); // the name the build was asked for points here
});

test('finish step: a city whose replays all failed names them, instead of claiming no commits', async () => {
setStore(new MemStore());
const start = T0 + 2 * DAY, head = 'c'.repeat(40);
const plan = (r: object[]) => sign({
k: 'rust-lang/rust', kind: 'repo', mode: 'all', s: start, n: T0 + 30 * DAY,
p: { login: 'rust-lang/rust', name: 'rust-lang/rust', avatar: '', url: '', createdAt: '' }, c: null, x: 0, o: [0, 0], r,
}, 600);
const ticket = sign({ k: 'rust-lang/rust', t: 'rust-lang/rust', s: start, w: false, r: [['rust-lang/rust', head, 0, 1_021_559]] }, 600);
// its replay failed, so there is no record: 34,368 commits are not "no commits"
await assert.rejects(finishBuild(ticket, plan([{ n: 'rust-lang/rust', f: false, c: 34_368, h: head }])), (e: unknown) => {
assert.ok(e instanceof HttpError);
assert.equal(e.status, 422);
assert.equal(e.extra.status, 'repo_failed');
assert.equal(e.message, 'rust-lang/rust could not be replayed');
assert.deepEqual(e.extra.skipped, [{ name: 'rust-lang/rust', commits: 34_368 }]);
return true;
});
// nothing failed and nothing to draw: that one is empty
await assert.rejects(finishBuild(ticket, plan([])), (e: unknown) => e instanceof HttpError && e.extra.status === 'empty' && e.message === 'no commits in the last year');
});

test('a generation-1 record stays in use only where the merged-branch fix cannot change it', async () => {
setStore(new MemStore());
const r = makeRepo(path.join(tmpdir(), 'gen'), 1);
Expand Down
Loading