@coderook/cli 0.31.13 → 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.
Files changed (55) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +43 -1
  3. package/dist/cbx/LICENSE +202 -0
  4. package/dist/cbx/NOTICE +8 -0
  5. package/dist/{desktop-app/src/main → cbx/src/core}/cbx.js +31 -0
  6. package/dist/{desktop-app/src/main → cbx/src/core}/download.js +62 -1
  7. package/dist/{desktop-app/src/main → cbx/src/core}/rules.js +2 -0
  8. package/dist/cbx/src/local/errors.js +23 -0
  9. package/dist/cbx/src/local/history.js +522 -0
  10. package/dist/cbx/src/local/merge.js +277 -0
  11. package/dist/cbx/src/local/merge_text.js +148 -0
  12. package/dist/cbx/src/local/remote.js +437 -0
  13. package/dist/cbx/src/local/repository.js +324 -0
  14. package/dist/cbx/src/local/snapshot.js +338 -0
  15. package/dist/cbx/src/local/store.js +258 -0
  16. package/dist/{desktop-app → cbx}/src/shared/safe_path.js +12 -0
  17. package/dist/cli/src/api.js +1 -1
  18. package/dist/cli/src/cli.js +164 -22
  19. package/dist/cli/src/diff_command.js +2 -2
  20. package/dist/cli/src/git_remote.js +6 -6
  21. package/dist/cli/src/licence_commands.js +3 -3
  22. package/dist/cli/src/local_commands.js +506 -0
  23. package/dist/cli/src/mcp.js +2 -2
  24. package/dist/cli/src/pages_command.js +2 -2
  25. package/dist/cli/src/registry.js +1 -0
  26. package/dist/cli/src/runner.js +2 -2
  27. package/dist/cli/src/track_commands.js +1 -1
  28. package/package.json +2 -2
  29. package/skills/coderook/SKILL.md +24 -0
  30. /package/dist/{desktop-app/src/main → cbx/src/core}/compress.js +0 -0
  31. /package/dist/{desktop-app/src/main → cbx/src/core}/credentials.js +0 -0
  32. /package/dist/{desktop-app/src/main → cbx/src/core}/detect.js +0 -0
  33. /package/dist/{desktop-app/src/main → cbx/src/core}/faults.js +0 -0
  34. /package/dist/{desktop-app/src/main → cbx/src/core}/identify.js +0 -0
  35. /package/dist/{desktop-app/src/main → cbx/src/core}/ignore_templates.js +0 -0
  36. /package/dist/{desktop-app/src/main → cbx/src/core}/licence_texts.js +0 -0
  37. /package/dist/{desktop-app/src/main → cbx/src/core}/licences.js +0 -0
  38. /package/dist/{desktop-app/src/main → cbx/src/core}/profile.js +0 -0
  39. /package/dist/{desktop-app/src/main → cbx/src/core}/publish_name.js +0 -0
  40. /package/dist/{desktop-app/src/main → cbx/src/core}/retry.js +0 -0
  41. /package/dist/{desktop-app/src/main → cbx/src/core}/secret_patterns.js +0 -0
  42. /package/dist/{desktop-app/src/main → cbx/src/core}/solid.js +0 -0
  43. /package/dist/{desktop-app/src/main → cbx/src/core}/staging.js +0 -0
  44. /package/dist/{desktop-app/src/main → cbx/src/core}/tracks.js +0 -0
  45. /package/dist/{desktop-app/src/main → cbx/src/core}/transfer_progress.js +0 -0
  46. /package/dist/{desktop-app/src/main → cbx/src/core}/upload.js +0 -0
  47. /package/dist/{desktop-app/src/main → cbx/src/core}/worktree.js +0 -0
  48. /package/dist/{desktop-app → cbx}/src/shared/chunking.js +0 -0
  49. /package/dist/{desktop-app → cbx}/src/shared/compression_policy.js +0 -0
  50. /package/dist/{desktop-app → cbx}/src/shared/delta.js +0 -0
  51. /package/dist/{desktop-app → cbx}/src/shared/format.js +0 -0
  52. /package/dist/{desktop-app → cbx}/src/shared/merge_diff.js +0 -0
  53. /package/dist/{desktop-app → cbx}/src/shared/publication.js +0 -0
  54. /package/dist/{desktop-app → cbx}/src/shared/telemetry.js +0 -0
  55. /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
+ }