nano-git 0.2.3 → 0.3.1
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/README.md +6 -0
- package/dist/workdir/change-index-plan.mjs +76 -0
- package/dist/workdir/change-index.d.mts +21 -0
- package/dist/workdir/change-index.mjs +413 -0
- package/dist/workdir/core.d.mts +54 -22
- package/dist/workdir/directory-view.mjs +197 -0
- package/dist/workdir/dirty-dir-plan.mjs +73 -0
- package/dist/workdir/dirty-dir.d.mts +28 -0
- package/dist/workdir/dirty-dir.mjs +32 -0
- package/dist/workdir/file-backend.mjs +96 -10
- package/dist/workdir/memory-backend.mjs +31 -12
- package/dist/workdir/nodes.mjs +1 -7
- package/dist/workdir/session-internal.mjs +216 -9
- package/dist/workdir/session-transaction.mjs +115 -0
- package/dist/workdir/session.mjs +103 -230
- package/dist/workdir/sqlite-backend.mjs +149 -48
- package/dist/workdir/state-store.d.mts +19 -8
- package/dist/workdir/write-tree.mjs +104 -40
- package/package.json +160 -199
- package/dist/workdir/change-log.d.mts +0 -25
- package/dist/workdir/change-log.mjs +0 -69
|
@@ -7,8 +7,7 @@ import { Database } from "bun:sqlite";
|
|
|
7
7
|
/**
|
|
8
8
|
* Virtual Workdir SQLite backend
|
|
9
9
|
*/
|
|
10
|
-
const WORKDIR_SQLITE_SCHEMA_VERSION =
|
|
11
|
-
const WORKDIR_CHANGES_SESSION_ORDER_INDEX = "workdir_changes_session_id_id_idx";
|
|
10
|
+
const WORKDIR_SQLITE_SCHEMA_VERSION = 5;
|
|
12
11
|
/**
|
|
13
12
|
* 创建基于 SQLite 的 Virtual Workdir backend
|
|
14
13
|
*
|
|
@@ -95,14 +94,55 @@ function createSqliteVirtualWorkdirStateStore(db, sessionId) {
|
|
|
95
94
|
target = excluded.target,
|
|
96
95
|
directory_overlay = excluded.directory_overlay`);
|
|
97
96
|
const deleteNodeStmt = db.query("DELETE FROM workdir_nodes WHERE session_id = ? AND node_id = ?");
|
|
98
|
-
const listChangesStmt = db.query("SELECT op, path, old_path FROM workdir_changes WHERE session_id = ? ORDER BY id");
|
|
99
|
-
const insertChangeStmt = db.query("INSERT INTO workdir_changes (session_id, op, path, old_path) VALUES (?, ?, ?, ?)");
|
|
100
97
|
const clearNodesStmt = db.query("DELETE FROM workdir_nodes WHERE session_id = ?");
|
|
98
|
+
const listChangesStmt = db.query(`SELECT path, previous_kind, previous_mode, previous_hash, current_kind, current_mode, current_hash, source_kind, source_path
|
|
99
|
+
FROM workdir_changes
|
|
100
|
+
WHERE session_id = ?
|
|
101
|
+
ORDER BY path`);
|
|
102
|
+
const getChangeStmt = db.query(`SELECT path, previous_kind, previous_mode, previous_hash, current_kind, current_mode, current_hash, source_kind, source_path
|
|
103
|
+
FROM workdir_changes
|
|
104
|
+
WHERE session_id = ? AND path = ?`);
|
|
105
|
+
const upsertChangeStmt = db.query(`INSERT INTO workdir_changes (
|
|
106
|
+
session_id, path, previous_kind, previous_mode, previous_hash,
|
|
107
|
+
current_kind, current_mode, current_hash, source_kind, source_path
|
|
108
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
109
|
+
ON CONFLICT(session_id, path) DO UPDATE SET
|
|
110
|
+
previous_kind = excluded.previous_kind,
|
|
111
|
+
previous_mode = excluded.previous_mode,
|
|
112
|
+
previous_hash = excluded.previous_hash,
|
|
113
|
+
current_kind = excluded.current_kind,
|
|
114
|
+
current_mode = excluded.current_mode,
|
|
115
|
+
current_hash = excluded.current_hash,
|
|
116
|
+
source_kind = excluded.source_kind,
|
|
117
|
+
source_path = excluded.source_path`);
|
|
118
|
+
const deleteChangeStmt = db.query("DELETE FROM workdir_changes WHERE session_id = ? AND path = ?");
|
|
101
119
|
const clearChangesStmt = db.query("DELETE FROM workdir_changes WHERE session_id = ?");
|
|
120
|
+
const listDirtyDirsStmt = db.query(`SELECT path, is_dirty, dirty_entry_count, dirty_descendant_count, affected_names, current_tree_hash, hash_state
|
|
121
|
+
FROM workdir_dirty_dirs
|
|
122
|
+
WHERE session_id = ?
|
|
123
|
+
ORDER BY path`);
|
|
124
|
+
const getDirtyDirStmt = db.query(`SELECT path, is_dirty, dirty_entry_count, dirty_descendant_count, affected_names, current_tree_hash, hash_state
|
|
125
|
+
FROM workdir_dirty_dirs
|
|
126
|
+
WHERE session_id = ? AND path = ?`);
|
|
127
|
+
const upsertDirtyDirStmt = db.query(`INSERT INTO workdir_dirty_dirs (
|
|
128
|
+
session_id, path, is_dirty, dirty_entry_count, dirty_descendant_count,
|
|
129
|
+
affected_names, current_tree_hash, hash_state
|
|
130
|
+
)
|
|
131
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
132
|
+
ON CONFLICT(session_id, path) DO UPDATE SET
|
|
133
|
+
is_dirty = excluded.is_dirty,
|
|
134
|
+
dirty_entry_count = excluded.dirty_entry_count,
|
|
135
|
+
dirty_descendant_count = excluded.dirty_descendant_count,
|
|
136
|
+
affected_names = excluded.affected_names,
|
|
137
|
+
current_tree_hash = excluded.current_tree_hash,
|
|
138
|
+
hash_state = excluded.hash_state`);
|
|
139
|
+
const deleteDirtyDirStmt = db.query("DELETE FROM workdir_dirty_dirs WHERE session_id = ? AND path = ?");
|
|
140
|
+
const clearDirtyDirsStmt = db.query("DELETE FROM workdir_dirty_dirs WHERE session_id = ?");
|
|
102
141
|
const resetTx = db.transaction((baseTree) => {
|
|
103
142
|
upsertSessionStmt.run(sessionId, baseTree);
|
|
104
143
|
clearNodesStmt.run(sessionId);
|
|
105
144
|
clearChangesStmt.run(sessionId);
|
|
145
|
+
clearDirtyDirsStmt.run(sessionId);
|
|
106
146
|
writeNode(setNodeStmt, sessionId, createRootDirectoryNode(baseTree));
|
|
107
147
|
});
|
|
108
148
|
return {
|
|
@@ -129,14 +169,31 @@ function createSqliteVirtualWorkdirStateStore(db, sessionId) {
|
|
|
129
169
|
deleteNode(id) {
|
|
130
170
|
deleteNodeStmt.run(sessionId, id);
|
|
131
171
|
},
|
|
132
|
-
appendChange(record) {
|
|
133
|
-
insertChangeStmt.run(sessionId, record.op, getRecordPath(record), getRecordOldPath(record));
|
|
134
|
-
},
|
|
135
172
|
listChangeRecords() {
|
|
136
173
|
return listChangesStmt.all(sessionId).map(readChangeRecord);
|
|
137
174
|
},
|
|
138
|
-
|
|
139
|
-
|
|
175
|
+
getChangeRecord(path) {
|
|
176
|
+
const row = getChangeStmt.get(sessionId, path);
|
|
177
|
+
return row === null ? null : readChangeRecord(row);
|
|
178
|
+
},
|
|
179
|
+
setChangeRecord(record) {
|
|
180
|
+
upsertChangeStmt.run(sessionId, record.path, record.previous?.kind ?? null, record.previous?.mode ?? null, record.previous?.hash ?? null, record.current?.kind ?? null, record.current?.mode ?? null, record.current?.hash ?? null, record.source?.kind ?? null, record.source?.path ?? null);
|
|
181
|
+
},
|
|
182
|
+
deleteChangeRecord(path) {
|
|
183
|
+
deleteChangeStmt.run(sessionId, path);
|
|
184
|
+
},
|
|
185
|
+
listDirtyDirSummaries() {
|
|
186
|
+
return listDirtyDirsStmt.all(sessionId).map(readDirtyDirSummary);
|
|
187
|
+
},
|
|
188
|
+
getDirtyDirSummary(path) {
|
|
189
|
+
const row = getDirtyDirStmt.get(sessionId, path);
|
|
190
|
+
return row === null ? null : readDirtyDirSummary(row);
|
|
191
|
+
},
|
|
192
|
+
setDirtyDirSummary(summary) {
|
|
193
|
+
upsertDirtyDirStmt.run(sessionId, summary.path, summary.isDirty ? 1 : 0, summary.dirtyEntryCount, summary.dirtyDescendantCount, JSON.stringify(summary.affectedNames), summary.currentTreeHash, summary.hashState);
|
|
194
|
+
},
|
|
195
|
+
deleteDirtyDirSummary(path) {
|
|
196
|
+
deleteDirtyDirStmt.run(sessionId, path);
|
|
140
197
|
},
|
|
141
198
|
reset(baseTree) {
|
|
142
199
|
resetTx(baseTree);
|
|
@@ -169,16 +226,31 @@ function ensureSchema(db) {
|
|
|
169
226
|
`);
|
|
170
227
|
db.run(`
|
|
171
228
|
CREATE TABLE IF NOT EXISTS workdir_changes (
|
|
172
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
173
229
|
session_id TEXT NOT NULL,
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
230
|
+
path TEXT NOT NULL,
|
|
231
|
+
previous_kind TEXT,
|
|
232
|
+
previous_mode TEXT,
|
|
233
|
+
previous_hash TEXT,
|
|
234
|
+
current_kind TEXT,
|
|
235
|
+
current_mode TEXT,
|
|
236
|
+
current_hash TEXT,
|
|
237
|
+
source_kind TEXT,
|
|
238
|
+
source_path TEXT,
|
|
239
|
+
PRIMARY KEY (session_id, path)
|
|
177
240
|
)
|
|
178
241
|
`);
|
|
179
242
|
db.run(`
|
|
180
|
-
CREATE
|
|
181
|
-
|
|
243
|
+
CREATE TABLE IF NOT EXISTS workdir_dirty_dirs (
|
|
244
|
+
session_id TEXT NOT NULL,
|
|
245
|
+
path TEXT NOT NULL,
|
|
246
|
+
is_dirty INTEGER NOT NULL,
|
|
247
|
+
dirty_entry_count INTEGER NOT NULL,
|
|
248
|
+
dirty_descendant_count INTEGER NOT NULL,
|
|
249
|
+
affected_names TEXT NOT NULL,
|
|
250
|
+
current_tree_hash TEXT,
|
|
251
|
+
hash_state TEXT NOT NULL,
|
|
252
|
+
PRIMARY KEY (session_id, path)
|
|
253
|
+
)
|
|
182
254
|
`);
|
|
183
255
|
writeSchemaVersion(db, WORKDIR_SQLITE_SCHEMA_VERSION);
|
|
184
256
|
}
|
|
@@ -187,6 +259,7 @@ function hasSession(db, sessionId) {
|
|
|
187
259
|
}
|
|
188
260
|
function deleteSessionRows(db, sessionId) {
|
|
189
261
|
db.transaction(() => {
|
|
262
|
+
db.query("DELETE FROM workdir_dirty_dirs WHERE session_id = ?").run(sessionId);
|
|
190
263
|
db.query("DELETE FROM workdir_changes WHERE session_id = ?").run(sessionId);
|
|
191
264
|
db.query("DELETE FROM workdir_nodes WHERE session_id = ?").run(sessionId);
|
|
192
265
|
db.query("DELETE FROM workdir_sessions WHERE session_id = ?").run(sessionId);
|
|
@@ -286,6 +359,67 @@ function readNodeOrigin(row) {
|
|
|
286
359
|
}
|
|
287
360
|
throw new Error(`Invalid SQLite workdir node origin kind: ${row.origin_kind}`);
|
|
288
361
|
}
|
|
362
|
+
function readChangeRecord(row) {
|
|
363
|
+
return {
|
|
364
|
+
path: row.path,
|
|
365
|
+
previous: row.previous_kind === null || row.previous_mode === null || row.previous_hash === null ? null : {
|
|
366
|
+
kind: readDiffObjectKind(row.previous_kind),
|
|
367
|
+
mode: readDiffObjectMode(row.previous_mode),
|
|
368
|
+
hash: row.previous_hash
|
|
369
|
+
},
|
|
370
|
+
current: row.current_kind === null || row.current_mode === null || row.current_hash === null ? null : {
|
|
371
|
+
kind: readDiffObjectKind(row.current_kind),
|
|
372
|
+
mode: readDiffObjectMode(row.current_mode),
|
|
373
|
+
hash: row.current_hash
|
|
374
|
+
},
|
|
375
|
+
source: row.source_kind === null || row.source_path === null ? null : {
|
|
376
|
+
kind: readDiffSourceKind(row.source_kind),
|
|
377
|
+
path: row.source_path
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function readDiffObjectKind(raw) {
|
|
382
|
+
if (raw === "blob" || raw === "symlink") return raw;
|
|
383
|
+
throw new Error(`Invalid SQLite workdir diff object kind: ${raw}`);
|
|
384
|
+
}
|
|
385
|
+
function readDiffObjectMode(raw) {
|
|
386
|
+
if (raw === "100644" || raw === "100755" || raw === "120000") return raw;
|
|
387
|
+
throw new Error(`Invalid SQLite workdir diff object mode: ${raw}`);
|
|
388
|
+
}
|
|
389
|
+
function readDiffSourceKind(raw) {
|
|
390
|
+
if (raw === "rename" || raw === "copy") return raw;
|
|
391
|
+
throw new Error(`Invalid SQLite workdir diff source kind: ${raw}`);
|
|
392
|
+
}
|
|
393
|
+
function readDirtyDirSummary(row) {
|
|
394
|
+
const affectedNames = readDirtyDirAffectedNames(row.affected_names);
|
|
395
|
+
return {
|
|
396
|
+
path: row.path,
|
|
397
|
+
isDirty: row.is_dirty !== 0,
|
|
398
|
+
dirtyEntryCount: readDirtyDirCount(row.dirty_entry_count, "dirty_entry_count"),
|
|
399
|
+
dirtyDescendantCount: readDirtyDirCount(row.dirty_descendant_count, "dirty_descendant_count"),
|
|
400
|
+
affectedNames,
|
|
401
|
+
currentTreeHash: row.current_tree_hash === null ? null : sha1(row.current_tree_hash),
|
|
402
|
+
hashState: readDirtyDirHashState(row.hash_state)
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
function readDirtyDirCount(raw, field) {
|
|
406
|
+
if (!Number.isInteger(raw) || raw < 0) throw new Error(`Invalid SQLite workdir dirty dir ${field}: ${raw}`);
|
|
407
|
+
return raw;
|
|
408
|
+
}
|
|
409
|
+
function readDirtyDirAffectedNames(raw) {
|
|
410
|
+
let parsed;
|
|
411
|
+
try {
|
|
412
|
+
parsed = JSON.parse(raw);
|
|
413
|
+
} catch {
|
|
414
|
+
throw new Error("Invalid SQLite workdir dirty dir affected_names JSON");
|
|
415
|
+
}
|
|
416
|
+
if (!Array.isArray(parsed) || parsed.some((item) => typeof item !== "string")) throw new Error("Invalid SQLite workdir dirty dir affected_names payload");
|
|
417
|
+
return [...parsed].sort((left, right) => left.localeCompare(right));
|
|
418
|
+
}
|
|
419
|
+
function readDirtyDirHashState(raw) {
|
|
420
|
+
if (raw === "stale" || raw === "materialized") return raw;
|
|
421
|
+
throw new Error(`Invalid SQLite workdir dirty dir hash state: ${raw}`);
|
|
422
|
+
}
|
|
289
423
|
function readDirectoryOverlay(raw) {
|
|
290
424
|
if (raw === null) return {
|
|
291
425
|
addedEntries: /* @__PURE__ */ new Map(),
|
|
@@ -326,38 +460,5 @@ function isDirectoryOverlayPayload(value) {
|
|
|
326
460
|
const hasValidDeletedNames = maybe.deletedNames.every((name) => typeof name === "string");
|
|
327
461
|
return hasValidAddedEntries && hasValidDeletedNames;
|
|
328
462
|
}
|
|
329
|
-
function getRecordPath(record) {
|
|
330
|
-
switch (record.op) {
|
|
331
|
-
case "add":
|
|
332
|
-
case "modify":
|
|
333
|
-
case "delete":
|
|
334
|
-
case "revert": return record.path;
|
|
335
|
-
case "rename":
|
|
336
|
-
case "copy": return record.to;
|
|
337
|
-
default: return record;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
function getRecordOldPath(record) {
|
|
341
|
-
if (record.op === "rename" || record.op === "copy") return record.from;
|
|
342
|
-
return null;
|
|
343
|
-
}
|
|
344
|
-
function readChangeRecord(row) {
|
|
345
|
-
switch (row.op) {
|
|
346
|
-
case "add":
|
|
347
|
-
case "modify":
|
|
348
|
-
case "delete":
|
|
349
|
-
case "revert": return {
|
|
350
|
-
op: row.op,
|
|
351
|
-
path: row.path
|
|
352
|
-
};
|
|
353
|
-
case "rename":
|
|
354
|
-
case "copy": return {
|
|
355
|
-
op: row.op,
|
|
356
|
-
from: row.old_path,
|
|
357
|
-
to: row.path
|
|
358
|
-
};
|
|
359
|
-
default: throw new Error(`Unknown workdir change op: ${row.op}`);
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
463
|
//#endregion
|
|
363
464
|
export { createSqliteVirtualWorkdirBackend };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { SHA1 } from "../core/types.mjs";
|
|
2
|
-
import { InternalChangeRecord } from "./change-log.mjs";
|
|
3
2
|
import { NodeId } from "./ids.mjs";
|
|
3
|
+
import { NormalizedChangeRecord } from "./change-index.mjs";
|
|
4
|
+
import { DirtyDirSummary } from "./dirty-dir.mjs";
|
|
4
5
|
import { SessionNode } from "./nodes.mjs";
|
|
5
6
|
|
|
6
7
|
//#region src/workdir/state-store.d.ts
|
|
@@ -27,16 +28,26 @@ interface VirtualWorkdirStateStore {
|
|
|
27
28
|
setNode(node: SessionNode): void;
|
|
28
29
|
/** 删除节点 */
|
|
29
30
|
deleteNode(id: NodeId): void;
|
|
30
|
-
/**
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
|
|
31
|
+
/** 列出全部规范化变更记录 */
|
|
32
|
+
listChangeRecords(): readonly NormalizedChangeRecord[];
|
|
33
|
+
/** 按路径读取规范化变更记录 */
|
|
34
|
+
getChangeRecord(path: string): NormalizedChangeRecord | null;
|
|
35
|
+
/** 写入或覆盖规范化变更记录 */
|
|
36
|
+
setChangeRecord(record: NormalizedChangeRecord): void;
|
|
37
|
+
/** 删除规范化变更记录 */
|
|
38
|
+
deleteChangeRecord(path: string): void;
|
|
39
|
+
/** 列出全部脏目录摘要 */
|
|
40
|
+
listDirtyDirSummaries(): readonly DirtyDirSummary[];
|
|
41
|
+
/** 按目录路径读取脏目录摘要 */
|
|
42
|
+
getDirtyDirSummary(path: string): DirtyDirSummary | null;
|
|
43
|
+
/** 写入或覆盖脏目录摘要 */
|
|
44
|
+
setDirtyDirSummary(summary: DirtyDirSummary): void;
|
|
45
|
+
/** 删除脏目录摘要 */
|
|
46
|
+
deleteDirtyDirSummary(path: string): void;
|
|
36
47
|
/**
|
|
37
48
|
* 重置为新的基线 tree
|
|
38
49
|
*
|
|
39
|
-
*
|
|
50
|
+
* 需要同时清空节点状态,并重新建立根节点。
|
|
40
51
|
*/
|
|
41
52
|
reset(baseTree: SHA1): void;
|
|
42
53
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { writeObject } from "../objects/raw.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { readRepoTree } from "./origin.mjs";
|
|
3
3
|
import { listDirectoryChildren } from "./session-internal.mjs";
|
|
4
|
+
import { createNamedOriginChildLookup, observeListedDirectoryChild, observeNamedDirectoryChild, planAffectedDirectoryChildren } from "./directory-view.mjs";
|
|
5
|
+
import { materializeDirtyDirSummary } from "./dirty-dir.mjs";
|
|
4
6
|
//#region src/workdir/write-tree.ts
|
|
5
7
|
/**
|
|
6
8
|
* Virtual Workdir overlay -> tree 最小化编译
|
|
@@ -35,63 +37,125 @@ function writeTreeFromSession(source, state) {
|
|
|
35
37
|
*
|
|
36
38
|
* 返回新 tree 的 SHA-1(若目录无任何变化则直接复用 origin hash)。
|
|
37
39
|
*/
|
|
38
|
-
function compileDirectory(writeSource, readSource, state, dirNode) {
|
|
40
|
+
function compileDirectory(writeSource, readSource, state, dirNode, dirPath = "") {
|
|
39
41
|
if (dirNode.state.kind !== "directory") throw new Error("compileDirectory called on non-directory node");
|
|
40
|
-
const
|
|
42
|
+
const summary = state.getDirtyDirSummary(dirPath);
|
|
43
|
+
if (summary !== null && summary.hashState === "materialized" && summary.currentTreeHash !== null) return summary.currentTreeHash;
|
|
44
|
+
if (summary === null && dirNode.origin.kind === "repo-tree") return dirNode.origin.hash;
|
|
41
45
|
let anyChanged = false;
|
|
42
|
-
const newEntries =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
const newEntries = collectCompiledEntries(writeSource, readSource, state, dirNode, dirPath, summary, (changed) => {
|
|
47
|
+
if (changed) anyChanged = true;
|
|
48
|
+
});
|
|
49
|
+
if (summary !== null && (summary.dirtyEntryCount > 0 || summary.dirtyDescendantCount > 0)) anyChanged = true;
|
|
50
|
+
if (!anyChanged && dirNode.origin.kind === "repo-tree") {
|
|
51
|
+
state.setDirtyDirSummary(materializeDirtyDirSummary(summary, dirPath, dirNode.origin.hash));
|
|
52
|
+
return dirNode.origin.hash;
|
|
53
|
+
}
|
|
54
|
+
newEntries.sort((a, b) => a.name.localeCompare(b.name));
|
|
55
|
+
const treeHash = writeObject(writeSource, {
|
|
56
|
+
type: "tree",
|
|
57
|
+
entries: newEntries
|
|
58
|
+
});
|
|
59
|
+
state.setDirtyDirSummary(materializeDirtyDirSummary(summary, dirPath, treeHash));
|
|
60
|
+
return treeHash;
|
|
61
|
+
}
|
|
62
|
+
function collectCompiledEntries(writeSource, readSource, state, dirNode, dirPath, summary, markChanged) {
|
|
63
|
+
if (dirNode.state.kind !== "directory") throw new Error("collectCompiledEntries called on non-directory node");
|
|
64
|
+
if (dirNode.origin.kind !== "repo-tree" || summary === null) return listDirectoryChildren(readSource, state, dirNode, dirPath).flatMap((child) => {
|
|
65
|
+
const observedChild = observeListedDirectoryChild(state, dirPath, child);
|
|
66
|
+
if (observedChild === null) return [];
|
|
67
|
+
const entry = compileChildEntry(writeSource, readSource, state, observedChild);
|
|
68
|
+
if (entry === null) return [];
|
|
69
|
+
markChanged(entry.changed);
|
|
70
|
+
return [entry.entry];
|
|
71
|
+
});
|
|
72
|
+
const originLookup = createNamedOriginChildLookup(readRepoTree(readSource, dirNode.origin.hash, dirPath).entries);
|
|
73
|
+
const childPlan = planAffectedDirectoryChildren(originLookup, collectAffectedChildNames(summary));
|
|
74
|
+
const out = [];
|
|
75
|
+
for (const planEntry of childPlan) {
|
|
76
|
+
if (!planEntry.shouldCompile) {
|
|
77
|
+
if (planEntry.originEntry === null) throw new Error(`collectCompiledEntries: missing origin entry for '${planEntry.name}'`);
|
|
78
|
+
out.push(planEntry.originEntry);
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const compiled = compileNamedChildEntry(writeSource, readSource, state, dirNode, dirPath, planEntry.name, originLookup);
|
|
82
|
+
if (compiled === null) continue;
|
|
83
|
+
markChanged(compiled.changed);
|
|
84
|
+
out.push(compiled.entry);
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
function collectAffectedChildNames(summary) {
|
|
89
|
+
return new Set(summary.affectedNames);
|
|
90
|
+
}
|
|
91
|
+
function compileNamedChildEntry(writeSource, readSource, state, dirNode, dirPath, name, originLookup) {
|
|
92
|
+
if (dirNode.state.kind !== "directory") throw new Error("compileNamedChildEntry called on non-directory node");
|
|
93
|
+
const observedChild = observeNamedDirectoryChild(state, dirNode, dirPath, originLookup, name);
|
|
94
|
+
if (observedChild === null) return null;
|
|
95
|
+
return compileChildEntry(writeSource, readSource, state, observedChild);
|
|
96
|
+
}
|
|
97
|
+
function compileChildEntry(writeSource, readSource, state, child) {
|
|
98
|
+
const node = child.node;
|
|
99
|
+
if (node.state.kind === "directory") {
|
|
100
|
+
const newHash = compileDirectory(writeSource, readSource, state, node, child.path);
|
|
101
|
+
const originHash = node.origin.kind === "repo-tree" ? node.origin.hash : null;
|
|
102
|
+
return {
|
|
103
|
+
entry: {
|
|
50
104
|
mode: "40000",
|
|
51
105
|
name: child.name,
|
|
52
106
|
hash: newHash
|
|
107
|
+
},
|
|
108
|
+
changed: newHash !== originHash
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
if (node.state.kind === "file") {
|
|
112
|
+
if (node.state.content !== void 0) {
|
|
113
|
+
const hash = writeObject(writeSource, {
|
|
114
|
+
type: "blob",
|
|
115
|
+
content: node.state.content
|
|
53
116
|
});
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const hash = writeObject(writeSource, {
|
|
57
|
-
type: "blob",
|
|
58
|
-
content: node.state.content
|
|
59
|
-
});
|
|
60
|
-
anyChanged = true;
|
|
61
|
-
newEntries.push({
|
|
117
|
+
return {
|
|
118
|
+
entry: {
|
|
62
119
|
mode: node.state.mode,
|
|
63
120
|
name: child.name,
|
|
64
121
|
hash
|
|
65
|
-
}
|
|
66
|
-
|
|
122
|
+
},
|
|
123
|
+
changed: node.origin.kind !== "repo-blob" || hash !== node.origin.hash
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
if (node.origin.kind === "repo-blob") return {
|
|
127
|
+
entry: {
|
|
67
128
|
mode: node.state.mode,
|
|
68
129
|
name: child.name,
|
|
69
130
|
hash: node.origin.hash
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
131
|
+
},
|
|
132
|
+
changed: false
|
|
133
|
+
};
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
if (node.state.target !== void 0) {
|
|
137
|
+
const hash = writeObject(writeSource, {
|
|
138
|
+
type: "blob",
|
|
139
|
+
content: node.state.target
|
|
140
|
+
});
|
|
141
|
+
return {
|
|
142
|
+
entry: {
|
|
78
143
|
mode: "120000",
|
|
79
144
|
name: child.name,
|
|
80
145
|
hash
|
|
81
|
-
}
|
|
82
|
-
|
|
146
|
+
},
|
|
147
|
+
changed: node.origin.kind !== "repo-blob" || hash !== node.origin.hash
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (node.origin.kind === "repo-blob") return {
|
|
151
|
+
entry: {
|
|
83
152
|
mode: "120000",
|
|
84
153
|
name: child.name,
|
|
85
154
|
hash: node.origin.hash
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
newEntries.sort((a, b) => a.name.localeCompare(b.name));
|
|
91
|
-
return writeObject(writeSource, {
|
|
92
|
-
type: "tree",
|
|
93
|
-
entries: newEntries
|
|
94
|
-
});
|
|
155
|
+
},
|
|
156
|
+
changed: false
|
|
157
|
+
};
|
|
158
|
+
return null;
|
|
95
159
|
}
|
|
96
160
|
//#endregion
|
|
97
161
|
export { writeTreeFromSession };
|