local-review 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/dist/demo/lib/gitworld.js +224 -0
- package/dist/demo/world/api.js +2436 -0
- package/dist/demo/world/infra.js +189 -0
- package/dist/demo/world/people.js +16 -0
- package/dist/demo/world/review.js +578 -0
- package/dist/demo/world/sdk.js +376 -0
- package/dist/demo/world/web.js +657 -0
- package/dist/server/cli.js +28 -0
- package/dist/server/config.js +3 -0
- package/dist/server/examples.js +204 -0
- package/dist/server/index.js +17 -2
- package/dist/server/projects.js +2 -0
- package/dist/web/assets/{angular-html-DJ4eInGE.js → angular-html-qFVZP7wG.js} +1 -1
- package/dist/web/assets/{angular-ts-DBhgg2a7.js → angular-ts-DzOxtaC9.js} +1 -1
- package/dist/web/assets/{apl-TCQAu201.js → apl-0AThSj8r.js} +1 -1
- package/dist/web/assets/{astro-DVtFuwC2.js → astro-By53bk_I.js} +1 -1
- package/dist/web/assets/{blade-CMp-sZYg.js → blade-Dw_Nglq9.js} +1 -1
- package/dist/web/assets/{c-DgqspIM2.js → c-CQT-cQHo.js} +1 -1
- package/dist/web/assets/{chapel-NTLt7i8E.js → chapel-64o3Hib1.js} +1 -1
- package/dist/web/assets/{cobol-CJiWQrT4.js → cobol-COyB9oJ9.js} +1 -1
- package/dist/web/assets/{coffee-Bv_hE6wf.js → coffee-BGIfDQTv.js} +1 -1
- package/dist/web/assets/{cpp-CuUsyAvc.js → cpp-wi7ixCRa.js} +1 -1
- package/dist/web/assets/{crystal-BeLopiD_.js → crystal-ADRCp2kP.js} +1 -1
- package/dist/web/assets/{css-OngwBV3K.js → css-BH9tz7qt.js} +1 -1
- package/dist/web/assets/{edge-BFLildoE.js → edge-DFzphiQm.js} +1 -1
- package/dist/web/assets/{elixir-1Fs6FjVs.js → elixir-BUj3AoF_.js} +1 -1
- package/dist/web/assets/{elm-piMM67vF.js → elm-Bqgz9ryu.js} +1 -1
- package/dist/web/assets/{erb-JJ0sF8GL.js → erb-DHHWfqsN.js} +1 -1
- package/dist/web/assets/{git-rebase-CtqXywWe.js → git-rebase-C7iLD81B.js} +1 -1
- package/dist/web/assets/{glimmer-js-BPewWxGC.js → glimmer-js-BT_UjuD4.js} +1 -1
- package/dist/web/assets/{glimmer-ts-DyUQWB47.js → glimmer-ts-BpeqXZCf.js} +1 -1
- package/dist/web/assets/{glsl-D6tJoMaT.js → glsl-mPoRKPQX.js} +1 -1
- package/dist/web/assets/{graphql-E6u2hkWE.js → graphql-D5e-luGt.js} +1 -1
- package/dist/web/assets/{hack-Djq191-P.js → hack-Yj_0xAq9.js} +1 -1
- package/dist/web/assets/{haml-C0LqmZG3.js → haml-rCEvyqvs.js} +1 -1
- package/dist/web/assets/{handlebars-DwdxF6uN.js → handlebars-CZRXN7yP.js} +1 -1
- package/dist/web/assets/{html-B5Myt0fy.js → html-XqiqNW99.js} +1 -1
- package/dist/web/assets/{html-derivative-DTVrLAw3.js → html-derivative-nWC0aMV7.js} +1 -1
- package/dist/web/assets/{http-CFoh3cQf.js → http-C4fFBmNU.js} +1 -1
- package/dist/web/assets/{hurl-B_38naAo.js → hurl-DjZ_NyWj.js} +1 -1
- package/dist/web/assets/{index-CfxIojO0.js → index-BPfnOrNh.js} +22 -22
- package/dist/web/assets/{index-QywYYqTw.css → index-BplAAg-1.css} +1 -1
- package/dist/web/assets/{java-odnexk6r.js → java-B0zZG6CJ.js} +1 -1
- package/dist/web/assets/{javascript-DvFCJKws.js → javascript-CFawPmAZ.js} +1 -1
- package/dist/web/assets/{jinja-UeLt2DYu.js → jinja-qu2nA6S7.js} +1 -1
- package/dist/web/assets/{jison-HNaDYeqE.js → jison-DviWniCr.js} +1 -1
- package/dist/web/assets/{json-D57CnrjR.js → json-C0vF7y3r.js} +1 -1
- package/dist/web/assets/{jsx-o_5ZAydi.js → jsx-DQYIrdZV.js} +1 -1
- package/dist/web/assets/{julia-20UebbrD.js → julia-C_b0WNzR.js} +1 -1
- package/dist/web/assets/{just-DqnPCg-Q.js → just-NwM_1ruS.js} +1 -1
- package/dist/web/assets/{latex-CZhIB-LV.js → latex-As2zLFN8.js} +1 -1
- package/dist/web/assets/{liquid-BzouPsN0.js → liquid-By8qNhnm.js} +1 -1
- package/dist/web/assets/{lua-BXclv8Fm.js → lua-Dairqw_c.js} +1 -1
- package/dist/web/assets/{marko-M78_INlG.js → marko-C2UPN-L_.js} +1 -1
- package/dist/web/assets/{mdc-CBluOiY7.js → mdc-Eijzzwck.js} +1 -1
- package/dist/web/assets/{nginx-CZFFQFbT.js → nginx-BF8eLz9n.js} +1 -1
- package/dist/web/assets/{nim-17kKsGqk.js → nim-BlPfC-Lu.js} +1 -1
- package/dist/web/assets/{org-BZABHRQH.js → org-jZgCqYxI.js} +1 -1
- package/dist/web/assets/{perl-CMrAQ9Je.js → perl-BIZHUlMC.js} +1 -1
- package/dist/web/assets/{php-DwA3kETk.js → php-Bu0o-4mH.js} +1 -1
- package/dist/web/assets/{pug-D4fM85kA.js → pug-Benj2kck.js} +1 -1
- package/dist/web/assets/{qml-C3893RHi.js → qml-BHSeoMrW.js} +1 -1
- package/dist/web/assets/{r-DwJhLW77.js → r-CUvEVkuI.js} +1 -1
- package/dist/web/assets/{razor-_KkCNZ8V.js → razor-BwPBMIxs.js} +1 -1
- package/dist/web/assets/{regexp-BZ9NHg3b.js → regexp-_ifpjSOj.js} +1 -1
- package/dist/web/assets/{rst-gdK-1c4t.js → rst-Es4SE5IJ.js} +1 -1
- package/dist/web/assets/{ruby-DsmqYtlH.js → ruby-76YJjt1j.js} +1 -1
- package/dist/web/assets/{sas-BNGv1ScF.js → sas-BEWDsPnW.js} +1 -1
- package/dist/web/assets/{scss-DjYUXT73.js → scss-DgcF5VZK.js} +1 -1
- package/dist/web/assets/{shellscript-DBtV-eli.js → shellscript-Cty74sqM.js} +1 -1
- package/dist/web/assets/{shellsession-BKEV8Fnx.js → shellsession-BCfSy2gp.js} +1 -1
- package/dist/web/assets/{soy-DUapMxos.js → soy-dM_k5IdG.js} +1 -1
- package/dist/web/assets/{sql-B-UyqKkd.js → sql-Dr4UDgPO.js} +1 -1
- package/dist/web/assets/{stata-Xq3iZiMK.js → stata-COCyw4rB.js} +1 -1
- package/dist/web/assets/{surrealql-CzXacK8i.js → surrealql-Cc-YKbr5.js} +1 -1
- package/dist/web/assets/{svelte-CZaBEmPP.js → svelte-BBNgvYF5.js} +1 -1
- package/dist/web/assets/{templ-CxkGuUpr.js → templ-D8WXh8mG.js} +1 -1
- package/dist/web/assets/{tex-DP74JH6g.js → tex-BNsfM6-w.js} +1 -1
- package/dist/web/assets/{ts-tags-BAcrCE2Y.js → ts-tags-C5cp5d5e.js} +1 -1
- package/dist/web/assets/{tsx-Dw11YP6z.js → tsx-D0nBgoOJ.js} +1 -1
- package/dist/web/assets/{twig-BMVjBxD6.js → twig-Clef_vTO.js} +1 -1
- package/dist/web/assets/{typescript-DoBLlVdC.js → typescript-84qFVDd8.js} +1 -1
- package/dist/web/assets/{typst-w2u_vl4_.js → typst-kHrpsAej.js} +1 -1
- package/dist/web/assets/{vue-BmuHTv5p.js → vue-D_k3LQNp.js} +1 -1
- package/dist/web/assets/{vue-html-Dt0zSF4I.js → vue-html-Bclb5q-s.js} +1 -1
- package/dist/web/assets/{vue-vine-wvJ1BTex.js → vue-vine-BvY0ORZK.js} +1 -1
- package/dist/web/assets/{xml-CXftIBCQ.js → xml-BgfQ1YbS.js} +1 -1
- package/dist/web/assets/{xsl-BoHSOMz1.js → xsl-DEqH3rje.js} +1 -1
- package/dist/web/assets/{yaml-dGreldtD.js → yaml-BiWwl5JE.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +3 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# local-review
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#23](https://github.com/edspencer/local-review/pull/23) [`6af921a`](https://github.com/edspencer/local-review/commit/6af921a47a32e7d30324e2149730e007a4200d4e) Thanks [@edspencer](https://github.com/edspencer)! - A first-run welcome page, and one-click example projects:
|
|
8
|
+
|
|
9
|
+
- With no projects, the UI now shows a welcome page instead of a bare "No projects yet": what Local Review is, how to
|
|
10
|
+
create your first project (or a prompt to hand that to your coding agent), and links to the docs.
|
|
11
|
+
- **Add the example projects** (on that page, at the bottom of the project switcher, or `local-review examples`)
|
|
12
|
+
creates "Orchard", a made-up app reviewed across four repos, in about a second and with no network: stacked PRs,
|
|
13
|
+
review threads with an agent's replies, and a fully merged stack to click around. They're marked **Example**.
|
|
14
|
+
- **Remove examples** hides them again and deletes nothing: each example's `project.yaml` is renamed to a `.bak`, and
|
|
15
|
+
adding them again brings them back as you left them. A project of your own is never touched.
|
|
16
|
+
|
|
3
17
|
## 0.2.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// A tiny deterministic git history builder for the demo test bed. Commits are built in memory (each one a full
|
|
2
|
+
// snapshot of the tree) and written with one `git fast-import` per repo, with fixed authors and dates and git's
|
|
3
|
+
// config isolated from the machine's, so every run on every machine produces the same shas.
|
|
4
|
+
import { execFile, execFileSync } from 'node:child_process';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
/** The environment every git command here runs with: nothing from the machine's git config, no signing, no hooks. */
|
|
8
|
+
export function gitEnv() {
|
|
9
|
+
const env = {};
|
|
10
|
+
for (const [k, v] of Object.entries(process.env))
|
|
11
|
+
if (!k.startsWith('GIT_'))
|
|
12
|
+
env[k] = v;
|
|
13
|
+
return { ...env, GIT_CONFIG_GLOBAL: '/dev/null', GIT_CONFIG_NOSYSTEM: '1', GIT_TERMINAL_PROMPT: '0', LC_ALL: 'C', TZ: 'UTC' };
|
|
14
|
+
}
|
|
15
|
+
export function git(cwd, args, input) {
|
|
16
|
+
return execFileSync('git', args, { cwd, env: gitEnv(), input, encoding: 'utf8', maxBuffer: 64 << 20, stdio: ['pipe', 'pipe', 'pipe'] });
|
|
17
|
+
}
|
|
18
|
+
/** `git` without blocking: the app builds the example projects with this while it keeps serving. */
|
|
19
|
+
export function gitAsync(cwd, args, input) {
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const child = execFile('git', args, { cwd, env: gitEnv(), encoding: 'utf8', maxBuffer: 64 << 20 }, (err, stdout, stderr) => err ? reject(new Error(`git ${args[0]} failed in ${cwd}: ${stderr || err.message}`)) : resolve(stdout));
|
|
22
|
+
// commands that don't read stdin may exit before it's written (EPIPE): their exit status is what counts
|
|
23
|
+
child.stdin?.on('error', () => { });
|
|
24
|
+
child.stdin?.end(input ?? '');
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/** Replace exactly one occurrence of `from` (fails loudly if it isn't there, so content edits can't silently drift). */
|
|
28
|
+
export const replace = (from, to) => (old) => {
|
|
29
|
+
const i = old.indexOf(from);
|
|
30
|
+
if (i < 0)
|
|
31
|
+
throw new Error(`replace: text not found:\n${from}`);
|
|
32
|
+
if (old.indexOf(from, i + 1) >= 0)
|
|
33
|
+
throw new Error(`replace: text found more than once:\n${from}`);
|
|
34
|
+
return old.slice(0, i) + to + old.slice(i + from.length);
|
|
35
|
+
};
|
|
36
|
+
/** Several `replace`s in order. */
|
|
37
|
+
export const edits = (...pairs) => (old) => pairs.reduce((s, [a, b]) => replace(a, b)(s), old);
|
|
38
|
+
/** Insert `text` right after the (unique) `anchor`. */
|
|
39
|
+
export const after = (anchor, text) => replace(anchor, anchor + text);
|
|
40
|
+
/** Strip the common indentation of a template literal and its leading newline, keep one trailing newline. */
|
|
41
|
+
export function code(strings, ...vals) {
|
|
42
|
+
let s = strings.reduce((a, str, i) => a + str + (i < vals.length ? String(vals[i]) : ''), '');
|
|
43
|
+
s = s.replace(/^\n/, '').replace(/[ \t]+$/, '');
|
|
44
|
+
const lines = s.split('\n');
|
|
45
|
+
const indent = Math.min(...lines.filter((l) => l.trim()).map((l) => /^ */.exec(l)[0].length));
|
|
46
|
+
return lines.map((l) => l.slice(indent)).join('\n').replace(/\n*$/, '\n');
|
|
47
|
+
}
|
|
48
|
+
/** Go files and Makefiles are indented with tabs on disk; the content scripts write them with 4 spaces. */
|
|
49
|
+
export function onDisk(file, content) {
|
|
50
|
+
if (!/\.go$|(^|\/)Makefile$/.test(file))
|
|
51
|
+
return content;
|
|
52
|
+
return content.replace(/^((?: {4})+)/gm, (m) => '\t'.repeat(m.length / 4));
|
|
53
|
+
}
|
|
54
|
+
const unix = (iso) => Math.floor(new Date(iso).getTime() / 1000);
|
|
55
|
+
export class RepoBuilder {
|
|
56
|
+
name;
|
|
57
|
+
remoteUrl;
|
|
58
|
+
nextMark = 1;
|
|
59
|
+
commits = [];
|
|
60
|
+
byKey = new Map();
|
|
61
|
+
refs = new Map();
|
|
62
|
+
constructor(name, remoteUrl) {
|
|
63
|
+
this.name = name;
|
|
64
|
+
this.remoteUrl = remoteUrl;
|
|
65
|
+
}
|
|
66
|
+
/** A commit on `parent` (null: a root commit). `key` defaults to the subject. */
|
|
67
|
+
commit(parent, message, changes, o) {
|
|
68
|
+
const files = new Map(parent?.files ?? []);
|
|
69
|
+
if (o.merge)
|
|
70
|
+
for (const [p, c] of o.merge.files)
|
|
71
|
+
if (!files.has(p))
|
|
72
|
+
files.set(p, c);
|
|
73
|
+
for (const [p, ch] of Object.entries(changes)) {
|
|
74
|
+
if (ch === null) {
|
|
75
|
+
if (!files.delete(p))
|
|
76
|
+
throw new Error(`${this.name}: delete of missing file ${p}`);
|
|
77
|
+
}
|
|
78
|
+
else if (typeof ch === 'function') {
|
|
79
|
+
const old = files.get(p);
|
|
80
|
+
if (old === undefined)
|
|
81
|
+
throw new Error(`${this.name}: edit of missing file ${p} in "${message.split('\n')[0]}"`);
|
|
82
|
+
files.set(p, ch(old));
|
|
83
|
+
}
|
|
84
|
+
else
|
|
85
|
+
files.set(p, ch);
|
|
86
|
+
}
|
|
87
|
+
const subject = message.split('\n')[0];
|
|
88
|
+
const key = o.key ?? subject;
|
|
89
|
+
if (this.byKey.has(key))
|
|
90
|
+
throw new Error(`${this.name}: duplicate commit key ${key}`);
|
|
91
|
+
const c = {
|
|
92
|
+
mark: this.nextMark++,
|
|
93
|
+
key,
|
|
94
|
+
message: message.replace(/\n*$/, '\n'),
|
|
95
|
+
subject,
|
|
96
|
+
author: o.author,
|
|
97
|
+
authorAt: o.at,
|
|
98
|
+
committer: o.committer ?? o.author,
|
|
99
|
+
committedAt: o.committedAt ?? o.at,
|
|
100
|
+
parent,
|
|
101
|
+
merge: o.merge ?? null,
|
|
102
|
+
changes,
|
|
103
|
+
files,
|
|
104
|
+
};
|
|
105
|
+
this.commits.push(c);
|
|
106
|
+
this.byKey.set(key, c);
|
|
107
|
+
return c;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Replay commits onto `onto`, as `git rebase` would: same message, author and author date, a new committer
|
|
111
|
+
* date. Each copy takes over its original's key; the original stays reachable as `<key>@<tag>`.
|
|
112
|
+
*/
|
|
113
|
+
replay(list, onto, o) {
|
|
114
|
+
let base = onto;
|
|
115
|
+
return list.map((c) => {
|
|
116
|
+
const key = c.key;
|
|
117
|
+
this.rekey(c, `${key}@${o.tag}`);
|
|
118
|
+
// `override`: the copy's changes, when the rebase had to adapt the commit (a fixup squashed into it)
|
|
119
|
+
base = this.commit(base, c.message, o.override?.[key] ?? c.changes, { author: c.author, at: c.authorAt, committer: o.committer ?? c.committer, committedAt: o.at, key });
|
|
120
|
+
return base;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
rekey(c, key) {
|
|
124
|
+
if (this.byKey.has(key))
|
|
125
|
+
throw new Error(`${this.name}: duplicate commit key ${key}`);
|
|
126
|
+
this.byKey.delete(c.key);
|
|
127
|
+
c.key = key;
|
|
128
|
+
this.byKey.set(key, c);
|
|
129
|
+
}
|
|
130
|
+
get(key) {
|
|
131
|
+
const c = this.byKey.get(key);
|
|
132
|
+
if (!c)
|
|
133
|
+
throw new Error(`${this.name}: no commit ${key}`);
|
|
134
|
+
return c;
|
|
135
|
+
}
|
|
136
|
+
sha(key) {
|
|
137
|
+
const s = this.get(key).sha;
|
|
138
|
+
if (!s)
|
|
139
|
+
throw new Error(`${this.name}: not built yet`);
|
|
140
|
+
return s;
|
|
141
|
+
}
|
|
142
|
+
/** A file at a commit, as it is on disk. */
|
|
143
|
+
file(key, file) {
|
|
144
|
+
const s = this.get(key).files.get(file);
|
|
145
|
+
if (s === undefined)
|
|
146
|
+
throw new Error(`${this.name}: no ${file} at ${key}`);
|
|
147
|
+
return onDisk(file, s);
|
|
148
|
+
}
|
|
149
|
+
ref(name, c) {
|
|
150
|
+
this.refs.set(name.startsWith('refs/') ? name : `refs/heads/${name}`, c);
|
|
151
|
+
}
|
|
152
|
+
/** `git init`, one fast-import, then check out `checkout` (the work tree a developer would have). */
|
|
153
|
+
build(dir, checkout) {
|
|
154
|
+
const { steps, marks } = this.plan(dir, checkout);
|
|
155
|
+
for (const [args, input] of steps)
|
|
156
|
+
git(dir, args, input);
|
|
157
|
+
this.readMarks(marks);
|
|
158
|
+
}
|
|
159
|
+
/** The same as `build`, without blocking the event loop. */
|
|
160
|
+
async buildAsync(dir, checkout) {
|
|
161
|
+
const { steps, marks } = this.plan(dir, checkout);
|
|
162
|
+
for (const [args, input] of steps)
|
|
163
|
+
await gitAsync(dir, args, input);
|
|
164
|
+
this.readMarks(marks);
|
|
165
|
+
}
|
|
166
|
+
/** Create `dir` (it mustn't exist: never builds over anything) and list the git commands that fill it. */
|
|
167
|
+
plan(dir, checkout) {
|
|
168
|
+
if (fs.existsSync(dir))
|
|
169
|
+
throw new Error(`${dir} already exists`);
|
|
170
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
171
|
+
const chunks = [];
|
|
172
|
+
const w = (s) => chunks.push(Buffer.from(s, 'utf8'));
|
|
173
|
+
const data = (s) => {
|
|
174
|
+
w(`data ${Buffer.byteLength(s, 'utf8')}\n`);
|
|
175
|
+
w(s);
|
|
176
|
+
w('\n');
|
|
177
|
+
};
|
|
178
|
+
for (const c of this.commits) {
|
|
179
|
+
// every commit goes through one scratch ref, reset first so its parent is exactly what it says (none for a root)
|
|
180
|
+
w(`reset refs/lr-demo/build\ncommit refs/lr-demo/build\nmark :${c.mark}\n`);
|
|
181
|
+
w(`author ${c.author.name} <${c.author.email}> ${unix(c.authorAt)} +0000\n`);
|
|
182
|
+
w(`committer ${c.committer.name} <${c.committer.email}> ${unix(c.committedAt)} +0000\n`);
|
|
183
|
+
data(c.message);
|
|
184
|
+
if (c.parent)
|
|
185
|
+
w(`from :${c.parent.mark}\n`);
|
|
186
|
+
if (c.merge)
|
|
187
|
+
w(`merge :${c.merge.mark}\n`);
|
|
188
|
+
w('deleteall\n');
|
|
189
|
+
for (const p of [...c.files.keys()].sort()) {
|
|
190
|
+
w(`M 100644 inline ${p}\n`);
|
|
191
|
+
data(onDisk(p, c.files.get(p)));
|
|
192
|
+
}
|
|
193
|
+
w('\n');
|
|
194
|
+
}
|
|
195
|
+
for (const [name, c] of [...this.refs].sort(([a], [b]) => a.localeCompare(b)))
|
|
196
|
+
w(`reset ${name}\nfrom :${c.mark}\n\n`);
|
|
197
|
+
const marks = path.join(dir, '.git', 'lr-demo-marks');
|
|
198
|
+
return {
|
|
199
|
+
marks,
|
|
200
|
+
steps: [
|
|
201
|
+
[['init', '-q', '-b', 'main']],
|
|
202
|
+
[['config', 'core.hooksPath', '/dev/null']],
|
|
203
|
+
[['config', 'commit.gpgSign', 'false']],
|
|
204
|
+
[['config', 'gc.auto', '0']], // keep rewritten-away commits (anchors and moved branches point at them)
|
|
205
|
+
[['config', 'rebase.updateRefs', 'true']], // AGENTS.md "Branch tagging"
|
|
206
|
+
[['remote', 'add', 'origin', this.remoteUrl]],
|
|
207
|
+
[['fast-import', '--quiet', '--date-format=raw', `--export-marks=${marks}`], Buffer.concat(chunks).toString('utf8')],
|
|
208
|
+
[['update-ref', '-d', 'refs/lr-demo/build']],
|
|
209
|
+
[['checkout', '-q', '-f', checkout]],
|
|
210
|
+
],
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/** Every commit's sha, from fast-import's marks. The file stays in .git: nothing reads it, and this code never
|
|
214
|
+
* deletes a file (the app runs it to create the example projects, so test/fsguard.test.ts scans it like the server). */
|
|
215
|
+
readMarks(marks) {
|
|
216
|
+
const markShas = new Map();
|
|
217
|
+
for (const line of fs.readFileSync(marks, 'utf8').split('\n').filter(Boolean)) {
|
|
218
|
+
const [m, sha] = line.split(' ');
|
|
219
|
+
markShas.set(Number(m.slice(1)), sha);
|
|
220
|
+
}
|
|
221
|
+
for (const c of this.commits)
|
|
222
|
+
c.sha = markShas.get(c.mark);
|
|
223
|
+
}
|
|
224
|
+
}
|