@coderook/cli 0.31.12 → 0.32.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/.claude-plugin/plugin.json +1 -1
- package/README.md +73 -1
- package/dist/cbx/LICENSE +202 -0
- package/dist/cbx/NOTICE +8 -0
- package/dist/{desktop-app/src/main → cbx/src/core}/cbx.js +31 -0
- package/dist/{desktop-app/src/main → cbx/src/core}/download.js +62 -1
- package/dist/{desktop-app/src/main → cbx/src/core}/rules.js +2 -0
- package/dist/cbx/src/local/errors.js +23 -0
- package/dist/cbx/src/local/history.js +522 -0
- package/dist/cbx/src/local/merge.js +277 -0
- package/dist/cbx/src/local/merge_text.js +148 -0
- package/dist/cbx/src/local/remote.js +437 -0
- package/dist/cbx/src/local/repository.js +324 -0
- package/dist/cbx/src/local/snapshot.js +338 -0
- package/dist/cbx/src/local/store.js +258 -0
- package/dist/{desktop-app → cbx}/src/shared/safe_path.js +12 -0
- package/dist/cli/src/api.js +114 -2
- package/dist/cli/src/cli.js +198 -22
- package/dist/cli/src/diff_command.js +2 -2
- package/dist/cli/src/git_remote.js +6 -6
- package/dist/cli/src/licence_commands.js +3 -3
- package/dist/cli/src/local_commands.js +506 -0
- package/dist/cli/src/mcp.js +2 -2
- package/dist/cli/src/pages_command.js +625 -0
- package/dist/cli/src/project_commands.js +1 -0
- package/dist/cli/src/registry.js +1 -0
- package/dist/cli/src/runner.js +2 -2
- package/dist/cli/src/track_commands.js +1 -1
- package/package.json +2 -2
- package/skills/coderook/SKILL.md +41 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/compress.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/credentials.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/detect.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/faults.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/identify.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/ignore_templates.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/licence_texts.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/licences.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/profile.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/publish_name.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/retry.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/secret_patterns.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/solid.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/staging.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/tracks.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/transfer_progress.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/upload.js +0 -0
- /package/dist/{desktop-app/src/main → cbx/src/core}/worktree.js +0 -0
- /package/dist/{desktop-app → cbx}/src/shared/chunking.js +0 -0
- /package/dist/{desktop-app → cbx}/src/shared/compression_policy.js +0 -0
- /package/dist/{desktop-app → cbx}/src/shared/delta.js +0 -0
- /package/dist/{desktop-app → cbx}/src/shared/format.js +0 -0
- /package/dist/{desktop-app → cbx}/src/shared/merge_diff.js +0 -0
- /package/dist/{desktop-app → cbx}/src/shared/publication.js +0 -0
- /package/dist/{desktop-app → cbx}/src/shared/telemetry.js +0 -0
- /package/dist/{desktop-app → cbx}/src/shared/types.js +0 -0
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.NoRemote = exports.RemoteDiffers = exports.RemoteAhead = void 0;
|
|
7
|
+
exports.readRemote = readRemote;
|
|
8
|
+
exports.push = push;
|
|
9
|
+
exports.pull = pull;
|
|
10
|
+
/**
|
|
11
|
+
* CodeRook as a remote for a local history: `push` and `pull`.
|
|
12
|
+
*
|
|
13
|
+
* Each local save becomes one CodeRook save, and each CodeRook save one local
|
|
14
|
+
* save, and `.cbx/remote.json` remembers which is which. The two are only
|
|
15
|
+
* paired once their files are known to be identical, so a pair is a promise:
|
|
16
|
+
* the save here and the version there hold exactly the same bytes.
|
|
17
|
+
*
|
|
18
|
+
* Neither side is overwritten. A push onto a line CodeRook has moved on is
|
|
19
|
+
* refused, as git refuses one, and a pull onto local saves that were never
|
|
20
|
+
* pushed fetches CodeRook's saves and says so rather than merging, because
|
|
21
|
+
* merging a local history is not built yet.
|
|
22
|
+
*/
|
|
23
|
+
const promises_1 = require("node:fs/promises");
|
|
24
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
25
|
+
const download_js_1 = require("../core/download.js");
|
|
26
|
+
const tracks_js_1 = require("../core/tracks.js");
|
|
27
|
+
const upload_js_1 = require("../core/upload.js");
|
|
28
|
+
const history_js_1 = require("./history.js");
|
|
29
|
+
const snapshot_js_1 = require("./snapshot.js");
|
|
30
|
+
const merge_js_1 = require("./merge.js");
|
|
31
|
+
/** The service's limit on a save's message. */
|
|
32
|
+
const MESSAGE_LIMIT = 240;
|
|
33
|
+
const REMOTE_FILE = "remote.json";
|
|
34
|
+
async function readRemote(repository) {
|
|
35
|
+
try {
|
|
36
|
+
const raw = JSON.parse(await (0, promises_1.readFile)(node_path_1.default.join(repository.directory, REMOTE_FILE), "utf8"));
|
|
37
|
+
return { repositoryId: raw.repositoryId ?? null, pairs: raw.pairs ?? {} };
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return { repositoryId: null, pairs: {} };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
async function writeRemote(repository, record) {
|
|
44
|
+
const target = node_path_1.default.join(repository.directory, REMOTE_FILE);
|
|
45
|
+
await (0, promises_1.writeFile)(`${target}.partial`, JSON.stringify(record, null, 2));
|
|
46
|
+
await (0, promises_1.rename)(`${target}.partial`, target);
|
|
47
|
+
}
|
|
48
|
+
function servicesFor(credentials, given) {
|
|
49
|
+
return {
|
|
50
|
+
tracks: given?.tracks ?? new tracks_js_1.Tracks(credentials),
|
|
51
|
+
uploader: given?.uploader ?? new upload_js_1.Uploader(credentials),
|
|
52
|
+
downloader: given?.downloader ?? new download_js_1.Downloader(credentials),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/** CodeRook's line has saves this history does not: pull first. */
|
|
56
|
+
class RemoteAhead extends Error {
|
|
57
|
+
}
|
|
58
|
+
exports.RemoteAhead = RemoteAhead;
|
|
59
|
+
/** The version CodeRook made is not the save that was sent. */
|
|
60
|
+
class RemoteDiffers extends Error {
|
|
61
|
+
}
|
|
62
|
+
exports.RemoteDiffers = RemoteDiffers;
|
|
63
|
+
/** Nothing to pull from, or nothing on CodeRook under that name. */
|
|
64
|
+
class NoRemote extends Error {
|
|
65
|
+
}
|
|
66
|
+
exports.NoRemote = NoRemote;
|
|
67
|
+
function shortMessage(message) {
|
|
68
|
+
const flat = message.trim();
|
|
69
|
+
return flat.length <= MESSAGE_LIMIT ? flat : `${flat.slice(0, MESSAGE_LIMIT - 1)}…`;
|
|
70
|
+
}
|
|
71
|
+
function digestsOf(files) {
|
|
72
|
+
return Object.fromEntries([...files].map(([file, entry]) => [file, entry.sha256]));
|
|
73
|
+
}
|
|
74
|
+
function sameDigests(left, right) {
|
|
75
|
+
const keys = Object.keys(left);
|
|
76
|
+
return keys.length === Object.keys(right).length && keys.every((key) => left[key] === right[key]);
|
|
77
|
+
}
|
|
78
|
+
/** Saves from `tip` back along first parents, stopping at one `stop` accepts. */
|
|
79
|
+
async function unpairedChain(repository, tip, paired) {
|
|
80
|
+
const chain = [];
|
|
81
|
+
let at = await repository.readSave(tip);
|
|
82
|
+
while (at && !paired(at.id)) {
|
|
83
|
+
chain.push(at);
|
|
84
|
+
const parent = at.parents[0];
|
|
85
|
+
at = parent ? await repository.readSave(parent) : null;
|
|
86
|
+
}
|
|
87
|
+
return { chain: chain.reverse(), base: at };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* The saves from `to` (not included) up to `from`, oldest first, along the
|
|
91
|
+
* shortest run of parents; null when `to` is not an ancestor of `from`.
|
|
92
|
+
*/
|
|
93
|
+
async function routeTo(repository, from, to) {
|
|
94
|
+
const cameFrom = new Map([[from, null]]);
|
|
95
|
+
const queue = [from];
|
|
96
|
+
while (queue.length) {
|
|
97
|
+
const id = queue.shift();
|
|
98
|
+
if (id === to) {
|
|
99
|
+
const route = [];
|
|
100
|
+
let at = cameFrom.get(id) ?? null;
|
|
101
|
+
while (at) {
|
|
102
|
+
route.push(await repository.readSave(at));
|
|
103
|
+
at = cameFrom.get(at) ?? null;
|
|
104
|
+
}
|
|
105
|
+
return route;
|
|
106
|
+
}
|
|
107
|
+
for (const parent of (await repository.readSave(id)).parents) {
|
|
108
|
+
if (!cameFrom.has(parent)) {
|
|
109
|
+
cameFrom.set(parent, id);
|
|
110
|
+
queue.push(parent);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
async function push(repository, options) {
|
|
117
|
+
const services = servicesFor(options.credentials, options.services);
|
|
118
|
+
return repository.locked(async () => {
|
|
119
|
+
const line = await repository.currentLine();
|
|
120
|
+
const tip = await repository.head();
|
|
121
|
+
if (!tip)
|
|
122
|
+
throw new Error(`Nothing is saved on ${line} yet, so there is nothing to push.`);
|
|
123
|
+
if (await (0, merge_js_1.readMergeState)(repository)) {
|
|
124
|
+
throw new Error("A merge is waiting on conflicts. Finish it with cbx save first, then push.");
|
|
125
|
+
}
|
|
126
|
+
const remote = await readRemote(repository);
|
|
127
|
+
let repositoryId = remote.repositoryId ?? options.repositoryId ?? null;
|
|
128
|
+
/* Pairs from another project mean nothing to this one. */
|
|
129
|
+
if (options.repositoryId && remote.repositoryId && options.repositoryId !== remote.repositoryId) {
|
|
130
|
+
throw new Error("This history was pushed to a different CodeRook project from the one this folder is linked to.");
|
|
131
|
+
}
|
|
132
|
+
/*
|
|
133
|
+
Where CodeRook's line is now, asked before anything is sent.
|
|
134
|
+
|
|
135
|
+
Left to itself the service would take a push onto a line that has moved
|
|
136
|
+
and open a merge for it, which is right for the desktop app and wrong
|
|
137
|
+
here: that merge is not something this history can see or finish.
|
|
138
|
+
Refused first, the way git refuses a push that is not a fast-forward.
|
|
139
|
+
*/
|
|
140
|
+
const versionToSave = new Map(Object.entries(remote.pairs).map(([save, version]) => [version, save]));
|
|
141
|
+
const tracks = services.tracks;
|
|
142
|
+
const all = repositoryId ? await tracks.list(repositoryId) : [];
|
|
143
|
+
const track = all.find((one) => one.kind === "line" && one.name === line);
|
|
144
|
+
let chain;
|
|
145
|
+
let base;
|
|
146
|
+
let baseVersionId;
|
|
147
|
+
if (track?.headVersionId) {
|
|
148
|
+
const headSave = versionToSave.get(track.headVersionId);
|
|
149
|
+
if (!headSave) {
|
|
150
|
+
throw new RemoteAhead(`CodeRook's ${line} has saves this history does not. Run cbx pull, then push again. Nothing was sent.`);
|
|
151
|
+
}
|
|
152
|
+
if (headSave === tip)
|
|
153
|
+
return { repositoryId, line, pushed: [], head: null };
|
|
154
|
+
/*
|
|
155
|
+
The saves between CodeRook's head and this one, along whichever
|
|
156
|
+
parents reach it. After a pull that merged, that is the merge alone:
|
|
157
|
+
its first parent is local work CodeRook never had, its second is
|
|
158
|
+
CodeRook's head, and the merge already holds both.
|
|
159
|
+
*/
|
|
160
|
+
const route = await routeTo(repository, tip, headSave);
|
|
161
|
+
if (!route) {
|
|
162
|
+
throw new RemoteAhead((await (0, merge_js_1.isAncestor)(repository, tip, headSave))
|
|
163
|
+
? `CodeRook's ${line} is ahead of this history. Run cbx pull. Nothing was sent.`
|
|
164
|
+
: `CodeRook's ${line} and this history have both moved on. Run cbx pull to merge, then push. Nothing was sent.`);
|
|
165
|
+
}
|
|
166
|
+
chain = route;
|
|
167
|
+
base = await repository.readSave(headSave);
|
|
168
|
+
baseVersionId = track.headVersionId;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
({ chain, base } = await unpairedChain(repository, tip, (id) => id in remote.pairs));
|
|
172
|
+
if (!chain.length)
|
|
173
|
+
return { repositoryId, line, pushed: [], head: null };
|
|
174
|
+
baseVersionId = base ? remote.pairs[base.id] : null;
|
|
175
|
+
if (repositoryId && !track) {
|
|
176
|
+
if (baseVersionId) {
|
|
177
|
+
await tracks.create(repositoryId, line, baseVersionId);
|
|
178
|
+
}
|
|
179
|
+
else if (all.some((one) => one.headVersionId)) {
|
|
180
|
+
throw new RemoteAhead(`That CodeRook project already holds work this history does not. Pull one of its lines first. Nothing was sent.`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
else if (track) {
|
|
184
|
+
/* A line that exists and is empty: the first save goes up whole. */
|
|
185
|
+
base = null;
|
|
186
|
+
baseVersionId = null;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
let before = base
|
|
190
|
+
? (0, history_js_1.byPath)(await repository.readTree(base.tree))
|
|
191
|
+
: new Map();
|
|
192
|
+
let known = baseVersionId ? digestsOf(before) : undefined;
|
|
193
|
+
const scratch = node_path_1.default.join(repository.directory, "push");
|
|
194
|
+
const uploader = services.uploader;
|
|
195
|
+
const pushed = [];
|
|
196
|
+
let head = null;
|
|
197
|
+
try {
|
|
198
|
+
for (const [index, save] of chain.entries()) {
|
|
199
|
+
options.report?.({ kind: "save", index, total: chain.length, save });
|
|
200
|
+
const after = (0, history_js_1.byPath)(await repository.readTree(save.tree));
|
|
201
|
+
const changes = (0, history_js_1.compareTrees)(before, after);
|
|
202
|
+
const changed = [...changes.added, ...changes.modified];
|
|
203
|
+
if (!changed.length && !changes.deleted.length && baseVersionId) {
|
|
204
|
+
/* Holds what CodeRook already holds; nothing to make. */
|
|
205
|
+
remote.pairs[save.id] = baseVersionId;
|
|
206
|
+
await writeRemote(repository, { repositoryId, pairs: remote.pairs });
|
|
207
|
+
options.report?.({ kind: "unchanged", save });
|
|
208
|
+
before = after;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
await (0, promises_1.rm)(scratch, { recursive: true, force: true });
|
|
212
|
+
await (0, snapshot_js_1.writeEntriesInto)(repository, scratch, changed.map((file) => after.get(file)));
|
|
213
|
+
const request = {
|
|
214
|
+
localPath: scratch,
|
|
215
|
+
include: [...changed, ...changes.deleted],
|
|
216
|
+
deletions: changes.deleted,
|
|
217
|
+
message: shortMessage(save.message),
|
|
218
|
+
projectName: options.projectName,
|
|
219
|
+
repositoryId,
|
|
220
|
+
baseVersionId,
|
|
221
|
+
...(baseVersionId ? { expectedHeadVersionId: baseVersionId } : {}),
|
|
222
|
+
track: line,
|
|
223
|
+
/*
|
|
224
|
+
The save already applied the project's rules when it was made, and
|
|
225
|
+
the scratch tree holds only what changed, so its own rules would be
|
|
226
|
+
the wrong ones. The git push sends this for the same reason.
|
|
227
|
+
*/
|
|
228
|
+
allowIgnored: true,
|
|
229
|
+
...(options.allowSecrets ? { allowSecrets: true } : {}),
|
|
230
|
+
...(options.acknowledged ? { acknowledged: true } : {}),
|
|
231
|
+
...(known ? { known } : {}),
|
|
232
|
+
};
|
|
233
|
+
const report = (progress) => options.report?.({ kind: "upload", progress });
|
|
234
|
+
const plan = await uploader.plan(request, report);
|
|
235
|
+
const result = await uploader.execute(request, plan, report);
|
|
236
|
+
if (result.mergeTrack) {
|
|
237
|
+
throw new RemoteAhead(`Somebody saved to CodeRook's ${line} while this push was running, and it is waiting on a merge there. ` +
|
|
238
|
+
`${pushed.length} of ${chain.length} saves were pushed.`);
|
|
239
|
+
}
|
|
240
|
+
repositoryId = result.repositoryId;
|
|
241
|
+
const expected = digestsOf(after);
|
|
242
|
+
if (!sameDigests(result.manifest, expected)) {
|
|
243
|
+
await writeRemote(repository, { repositoryId, pairs: remote.pairs });
|
|
244
|
+
throw new RemoteDiffers(`CodeRook saved v${result.sequence}, but it does not hold the same files as ${save.id.slice(0, 10)}: ` +
|
|
245
|
+
`somebody else's work was merged into it. Run cbx pull to bring it here.`);
|
|
246
|
+
}
|
|
247
|
+
remote.pairs[save.id] = result.versionId;
|
|
248
|
+
await writeRemote(repository, { repositoryId, pairs: remote.pairs });
|
|
249
|
+
pushed.push({ save, versionId: result.versionId, sequence: result.sequence });
|
|
250
|
+
options.report?.({ kind: "pushed", save, versionId: result.versionId, sequence: result.sequence });
|
|
251
|
+
head = { versionId: result.versionId, sequence: result.sequence, manifest: result.manifest };
|
|
252
|
+
baseVersionId = result.versionId;
|
|
253
|
+
known = result.manifest;
|
|
254
|
+
before = after;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
await (0, promises_1.rm)(scratch, { recursive: true, force: true });
|
|
259
|
+
}
|
|
260
|
+
return { repositoryId, line, pushed, head };
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Bring CodeRook's saves on the current line into this history, and merge
|
|
265
|
+
* them with this line's own when both have moved on.
|
|
266
|
+
*
|
|
267
|
+
* Only files that differ from the save before are fetched; anything the
|
|
268
|
+
* history already holds by digest is reused. The folder then moves the way
|
|
269
|
+
* `switch` moves it: only files that changed are touched, and an unsaved edit
|
|
270
|
+
* to one of those stops it.
|
|
271
|
+
*/
|
|
272
|
+
async function pull(repository, options) {
|
|
273
|
+
const services = servicesFor(options.credentials, options.services);
|
|
274
|
+
return repository.locked(async () => {
|
|
275
|
+
const line = await repository.currentLine();
|
|
276
|
+
const remote = await readRemote(repository);
|
|
277
|
+
const repositoryId = remote.repositoryId ?? options.repositoryId ?? null;
|
|
278
|
+
if (!repositoryId) {
|
|
279
|
+
throw new NoRemote("This history has no CodeRook project to pull from. Push it first, or name one: cbx pull <project>.");
|
|
280
|
+
}
|
|
281
|
+
if (options.repositoryId && remote.repositoryId && options.repositoryId !== remote.repositoryId) {
|
|
282
|
+
throw new Error("This history belongs to a different CodeRook project.");
|
|
283
|
+
}
|
|
284
|
+
const track = (await services.tracks.list(repositoryId)).find((one) => one.kind === "line" && one.name === line);
|
|
285
|
+
if (!track)
|
|
286
|
+
throw new NoRemote(`CodeRook has no line called ${line} in that project.`);
|
|
287
|
+
const nothing = { line, imported: [], moved: false, written: [], removed: [], head: null };
|
|
288
|
+
if (!track.headVersionId)
|
|
289
|
+
return nothing;
|
|
290
|
+
const versionToSave = new Map(Object.entries(remote.pairs).map(([save, version]) => [version, save]));
|
|
291
|
+
const downloader = services.downloader;
|
|
292
|
+
const listed = await downloader.versions(repositoryId);
|
|
293
|
+
const byId = new Map(listed.map((version) => [version.id, version]));
|
|
294
|
+
const headVersion = byId.get(track.headVersionId);
|
|
295
|
+
const headSummary = headVersion ? { versionId: headVersion.id, sequence: headVersion.sequence } : null;
|
|
296
|
+
/* CodeRook's saves from its head back to one this history already has. */
|
|
297
|
+
const incoming = [];
|
|
298
|
+
let at = headVersion;
|
|
299
|
+
while (at && !versionToSave.has(at.id)) {
|
|
300
|
+
incoming.push(at);
|
|
301
|
+
const parent = at.parentVersionIds?.[0];
|
|
302
|
+
at = parent ? byId.get(parent) : undefined;
|
|
303
|
+
}
|
|
304
|
+
incoming.reverse();
|
|
305
|
+
const baseSaveId = at ? versionToSave.get(at.id) : null;
|
|
306
|
+
const localTip = await repository.head();
|
|
307
|
+
/* Import, whatever happens to the folder afterwards. */
|
|
308
|
+
const baseSave = baseSaveId ? await repository.readSave(baseSaveId) : null;
|
|
309
|
+
let before = baseSave
|
|
310
|
+
? (0, history_js_1.byPath)(await repository.readTree(baseSave.tree))
|
|
311
|
+
: new Map();
|
|
312
|
+
const byDigest = new Map();
|
|
313
|
+
for (const entry of before.values())
|
|
314
|
+
byDigest.set(entry.sha256, entry);
|
|
315
|
+
let parent = baseSave?.id ?? null;
|
|
316
|
+
const imported = [];
|
|
317
|
+
const scratch = node_path_1.default.join(repository.directory, "pull");
|
|
318
|
+
try {
|
|
319
|
+
for (const [index, version] of incoming.entries()) {
|
|
320
|
+
options.report?.({ kind: "version", index, total: incoming.length, version });
|
|
321
|
+
const files = await downloader.files(repositoryId, version.id);
|
|
322
|
+
const entries = [];
|
|
323
|
+
const fetch = [];
|
|
324
|
+
for (const file of files) {
|
|
325
|
+
const was = before.get(file.path);
|
|
326
|
+
const reuse = was?.sha256 === file.sha256 ? was : byDigest.get(file.sha256);
|
|
327
|
+
if (reuse) {
|
|
328
|
+
entries.push({ ...reuse, path: file.path, executable: was?.executable ?? reuse.executable });
|
|
329
|
+
}
|
|
330
|
+
else {
|
|
331
|
+
fetch.push(file);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
await downloader.collect(repositoryId, version.id, fetch, scratch, async (file, arrived) => {
|
|
335
|
+
options.report?.({ kind: "file", path: file.path });
|
|
336
|
+
let stored;
|
|
337
|
+
if ("bytes" in arrived) {
|
|
338
|
+
const digest = await repository.objects.put(arrived.bytes);
|
|
339
|
+
stored = { sha256: digest, chunks: arrived.bytes.length ? [digest] : [] };
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
stored = await (0, snapshot_js_1.storeFile)(repository, arrived.path, file.sourceSize);
|
|
343
|
+
}
|
|
344
|
+
if (stored.sha256 !== file.sha256)
|
|
345
|
+
throw new Error(`${file.path} did not arrive intact`);
|
|
346
|
+
const entry = {
|
|
347
|
+
path: file.path,
|
|
348
|
+
size: file.sourceSize,
|
|
349
|
+
sha256: stored.sha256,
|
|
350
|
+
executable: before.get(file.path)?.executable ?? false,
|
|
351
|
+
chunks: stored.chunks,
|
|
352
|
+
};
|
|
353
|
+
entries.push(entry);
|
|
354
|
+
byDigest.set(entry.sha256, entry);
|
|
355
|
+
});
|
|
356
|
+
await repository.objects.flush();
|
|
357
|
+
const tree = await repository.writeTree(entries);
|
|
358
|
+
const save = await repository.writeSave({
|
|
359
|
+
format: "cbx-save",
|
|
360
|
+
version: 1,
|
|
361
|
+
tree,
|
|
362
|
+
parents: parent ? [parent] : [],
|
|
363
|
+
line,
|
|
364
|
+
message: version.message || `CodeRook v${version.sequence}`,
|
|
365
|
+
author: version.authorName || "CodeRook",
|
|
366
|
+
created: version.createdAt || new Date(0).toISOString(),
|
|
367
|
+
});
|
|
368
|
+
remote.pairs[save.id] = version.id;
|
|
369
|
+
await writeRemote(repository, { repositoryId, pairs: remote.pairs });
|
|
370
|
+
imported.push(save);
|
|
371
|
+
parent = save.id;
|
|
372
|
+
before = (0, history_js_1.byPath)(await repository.readTree(tree));
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
finally {
|
|
376
|
+
await (0, promises_1.rm)(scratch, { recursive: true, force: true });
|
|
377
|
+
}
|
|
378
|
+
/*
|
|
379
|
+
Where CodeRook's line ends, in this history's terms. Decided from
|
|
380
|
+
ancestry rather than from what this call imported: a pull whose folder
|
|
381
|
+
could not move last time has nothing new to import, and must still move.
|
|
382
|
+
*/
|
|
383
|
+
const remoteTip = parent;
|
|
384
|
+
const stay = { line, imported, moved: false, written: [], removed: [], head: headSummary };
|
|
385
|
+
if (localTip === remoteTip)
|
|
386
|
+
return stay;
|
|
387
|
+
if (localTip && (await (0, merge_js_1.isAncestor)(repository, remoteTip, localTip))) {
|
|
388
|
+
/* This history is ahead of CodeRook: saves still to push, nothing to take. */
|
|
389
|
+
return stay;
|
|
390
|
+
}
|
|
391
|
+
if (localTip && !(await (0, merge_js_1.isAncestor)(repository, localTip, remoteTip))) {
|
|
392
|
+
/* Both sides moved on: merged, as git's pull merges. */
|
|
393
|
+
const outcome = await (0, merge_js_1.mergeInto)(repository, remoteTip, {
|
|
394
|
+
message: `Merge CodeRook's ${line}`,
|
|
395
|
+
author: await repository.author(),
|
|
396
|
+
label: `CodeRook's ${line}`,
|
|
397
|
+
});
|
|
398
|
+
if (outcome.kind === "conflicts") {
|
|
399
|
+
return {
|
|
400
|
+
...stay,
|
|
401
|
+
written: outcome.written,
|
|
402
|
+
removed: outcome.removed,
|
|
403
|
+
merged: { conflicts: outcome.state.conflicts },
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
if (outcome.kind === "up-to-date")
|
|
407
|
+
return stay;
|
|
408
|
+
return {
|
|
409
|
+
...stay,
|
|
410
|
+
moved: true,
|
|
411
|
+
written: outcome.written,
|
|
412
|
+
removed: outcome.removed,
|
|
413
|
+
merged: { save: outcome.save },
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
/* A fast-forward: move the folder as `switch` would, then the line. */
|
|
417
|
+
const { tree: savedTree } = await (0, history_js_1.headTree)(repository);
|
|
418
|
+
const target = await repository.readTree((await repository.readSave(remoteTip)).tree);
|
|
419
|
+
const reading = await (0, snapshot_js_1.readFolder)(repository, { store: false, previous: savedTree });
|
|
420
|
+
const saved = (0, history_js_1.byPath)(savedTree);
|
|
421
|
+
const wanted = (0, history_js_1.byPath)(target);
|
|
422
|
+
const touched = new Set([...saved.keys(), ...wanted.keys()].filter((file) => !(0, snapshot_js_1.sameFile)(saved.get(file), wanted.get(file))));
|
|
423
|
+
if (!options.force) {
|
|
424
|
+
const blocked = [
|
|
425
|
+
...(0, history_js_1.inTheWay)(reading.files, saved, wanted, touched),
|
|
426
|
+
...(await (0, history_js_1.hiddenInTheWay)(repository, reading.files, wanted, touched)),
|
|
427
|
+
].sort();
|
|
428
|
+
if (blocked.length) {
|
|
429
|
+
/* The saves are here; only the folder could not move. */
|
|
430
|
+
throw new history_js_1.UnsavedChanges(blocked, `Moving to what was pulled`);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
const done = await (0, snapshot_js_1.checkout)(repository, reading.files, target, (file) => touched.has(file));
|
|
434
|
+
await repository.setLineTip(line, remoteTip);
|
|
435
|
+
return { line, imported, moved: true, ...done, head: headSummary };
|
|
436
|
+
});
|
|
437
|
+
}
|