nano-git 0.3.4 → 0.3.5
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/dist/workdir/change-index.d.mts +1 -1
- package/dist/workdir/change-index.mjs +8 -8
- package/dist/workdir/core.d.mts +8 -7
- package/dist/workdir/file-backend.mjs +12 -1
- package/dist/workdir/overlay.mjs +2 -2
- package/dist/workdir/sqlite-backend.mjs +3 -1
- package/dist/workdir/workdir-path.mjs +1 -1
- package/dist/workdir/workdir.mjs +13 -5
- package/package.json +1 -1
|
@@ -14,7 +14,7 @@ interface NormalizedChangeRecord {
|
|
|
14
14
|
readonly previous: VirtualDiffObject | null;
|
|
15
15
|
/** 变更后对象 */
|
|
16
16
|
readonly current: VirtualDiffObject | null;
|
|
17
|
-
/**
|
|
17
|
+
/** move/copy 来源 */
|
|
18
18
|
readonly source: VirtualDiffSource | null;
|
|
19
19
|
}
|
|
20
20
|
//#endregion
|
|
@@ -76,7 +76,7 @@ function rebuildNormalizedChangeIndex(source, state, cache) {
|
|
|
76
76
|
previous: renameFrom.previous,
|
|
77
77
|
current: addRecord.current,
|
|
78
78
|
source: {
|
|
79
|
-
kind: "
|
|
79
|
+
kind: "move",
|
|
80
80
|
path: renameFrom.path
|
|
81
81
|
}
|
|
82
82
|
});
|
|
@@ -155,17 +155,17 @@ function refreshChangeRecordForPath(source, state, path, cache) {
|
|
|
155
155
|
state.setChangeRecord(nextRecord);
|
|
156
156
|
}
|
|
157
157
|
/**
|
|
158
|
-
* 将单一路径的变更记录折叠为
|
|
158
|
+
* 将单一路径的变更记录折叠为 move 目标路径。
|
|
159
159
|
*
|
|
160
|
-
* 仅适用于叶子节点
|
|
160
|
+
* 仅适用于叶子节点 move;
|
|
161
161
|
* 目录及无法判定来源的情况应由调用方回退到全量重建。
|
|
162
162
|
*/
|
|
163
163
|
function rewriteChangeRecordForRename(source, state, from, to, cache) {
|
|
164
164
|
const previousRecord = state.getChangeRecord(from);
|
|
165
165
|
const currentTarget = snapshotCurrentEntryAtPath(source, state, to, cache);
|
|
166
|
-
if (currentTarget === null) throw new Error(`Cannot rewrite
|
|
166
|
+
if (currentTarget === null) throw new Error(`Cannot rewrite move change record for missing path: ${to}`);
|
|
167
167
|
const nextRecord = computeRenameRecordForPath(source, state, from, to, previousRecord, currentTarget);
|
|
168
|
-
if (nextRecord === null) throw new Error(`Cannot rewrite
|
|
168
|
+
if (nextRecord === null) throw new Error(`Cannot rewrite move change record from '${from}' to '${to}'`);
|
|
169
169
|
state.deleteChangeRecord(from);
|
|
170
170
|
state.setChangeRecord(nextRecord);
|
|
171
171
|
}
|
|
@@ -279,7 +279,7 @@ function computeRenameRecordForPath(source, state, from, to, previousRecord, cur
|
|
|
279
279
|
if (derivedFromPrevious !== void 0) return derivedFromPrevious;
|
|
280
280
|
const baseEntry = baseSnapshotEntryAtPath(source, state.readBaseTree(), from);
|
|
281
281
|
if (baseEntry === null) return null;
|
|
282
|
-
return createNormalizedChangeRecord(to, baseEntry.object, currentTarget.object, createDiffSource("
|
|
282
|
+
return createNormalizedChangeRecord(to, baseEntry.object, currentTarget.object, createDiffSource("move", from));
|
|
283
283
|
}
|
|
284
284
|
function deriveCopySource(from, sourceRecord, source, state) {
|
|
285
285
|
const fromRecordSource = sourceRecord?.source;
|
|
@@ -288,7 +288,7 @@ function deriveCopySource(from, sourceRecord, source, state) {
|
|
|
288
288
|
return null;
|
|
289
289
|
}
|
|
290
290
|
function preserveLineageRecordForPath(path, previousRecord, currentEntry) {
|
|
291
|
-
if (previousRecord?.source?.kind === "
|
|
291
|
+
if (previousRecord?.source?.kind === "move" && previousRecord.previous !== null) {
|
|
292
292
|
if (currentEntry === null) return null;
|
|
293
293
|
return createNormalizedChangeRecord(path, previousRecord.previous, currentEntry.object, previousRecord.source);
|
|
294
294
|
}
|
|
@@ -302,7 +302,7 @@ function deriveRenameRecordFromPreviousRecord(from, to, previousRecord, currentT
|
|
|
302
302
|
if (previousRecord.current === null) return null;
|
|
303
303
|
if (previousRecord.source !== null) return createNormalizedChangeRecord(to, previousRecord.previous, currentTarget.object, previousRecord.source);
|
|
304
304
|
if (previousRecord.previous === null) return createNormalizedChangeRecord(to, null, currentTarget.object);
|
|
305
|
-
return createNormalizedChangeRecord(to, previousRecord.previous, currentTarget.object, createDiffSource("
|
|
305
|
+
return createNormalizedChangeRecord(to, previousRecord.previous, currentTarget.object, createDiffSource("move", from));
|
|
306
306
|
}
|
|
307
307
|
function snapshotCurrentEntryAtPath(source, state, path, cache) {
|
|
308
308
|
const resolved = resolveCurrentLeafAtPath(source, state, path);
|
package/dist/workdir/core.d.mts
CHANGED
|
@@ -50,11 +50,11 @@ interface VirtualDiffObject {
|
|
|
50
50
|
readonly hash: SHA1;
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
*
|
|
53
|
+
* move/copy 来源描述
|
|
54
54
|
*/
|
|
55
55
|
interface VirtualDiffSource {
|
|
56
56
|
/** 来源类型 */
|
|
57
|
-
readonly kind: "
|
|
57
|
+
readonly kind: "move" | "copy";
|
|
58
58
|
/** 来源路径 */
|
|
59
59
|
readonly path: string;
|
|
60
60
|
}
|
|
@@ -77,7 +77,7 @@ interface VirtualDiffChanges {
|
|
|
77
77
|
type VirtualDiffEntry = {
|
|
78
78
|
/** 新建路径 */readonly kind: "create"; /** 当前路径 */
|
|
79
79
|
readonly path: string; /** 当前对象 */
|
|
80
|
-
readonly current: VirtualDiffObject; /**
|
|
80
|
+
readonly current: VirtualDiffObject; /** move/copy 的来源 */
|
|
81
81
|
readonly source?: VirtualDiffSource;
|
|
82
82
|
} | {
|
|
83
83
|
/** 删除路径 */readonly kind: "remove"; /** 当前路径 */
|
|
@@ -88,7 +88,7 @@ type VirtualDiffEntry = {
|
|
|
88
88
|
readonly path: string; /** 更新前对象 */
|
|
89
89
|
readonly previous: VirtualDiffObject; /** 更新后对象 */
|
|
90
90
|
readonly current: VirtualDiffObject; /** 变化维度 */
|
|
91
|
-
readonly changes: VirtualDiffChanges; /**
|
|
91
|
+
readonly changes: VirtualDiffChanges; /** move/copy 的来源 */
|
|
92
92
|
readonly source?: VirtualDiffSource;
|
|
93
93
|
};
|
|
94
94
|
/**
|
|
@@ -158,12 +158,13 @@ interface VirtualWorkdir {
|
|
|
158
158
|
readonly force?: boolean;
|
|
159
159
|
}): void;
|
|
160
160
|
/**
|
|
161
|
-
*
|
|
161
|
+
* 移动路径(可跨目录树)
|
|
162
162
|
*
|
|
163
163
|
* 只做路径重绑定,不退化为 delete + write。
|
|
164
|
-
*
|
|
164
|
+
* 目标父目录不存在时会自动创建中间目录。
|
|
165
|
+
* 目录移动后,子项保持懒加载。
|
|
165
166
|
*/
|
|
166
|
-
|
|
167
|
+
move(from: string, to: string): void;
|
|
167
168
|
/**
|
|
168
169
|
* 复制路径
|
|
169
170
|
*
|
|
@@ -372,8 +372,19 @@ function restoreChangeRecord(record) {
|
|
|
372
372
|
...record.current,
|
|
373
373
|
hash: record.current.hash
|
|
374
374
|
},
|
|
375
|
-
source: record.source
|
|
375
|
+
source: record.source === null ? null : readFileDiffSource(record.source)
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
function readFileDiffSource(source) {
|
|
379
|
+
if (source.kind === "copy") return {
|
|
380
|
+
kind: "copy",
|
|
381
|
+
path: source.path
|
|
382
|
+
};
|
|
383
|
+
if (source.kind === "move" || source.kind === "rename") return {
|
|
384
|
+
kind: "move",
|
|
385
|
+
path: source.path
|
|
376
386
|
};
|
|
387
|
+
throw new Error(`Invalid file workdir diff source kind: ${String(source.kind)}`);
|
|
377
388
|
}
|
|
378
389
|
function serializeDirtyDirSummary(summary) {
|
|
379
390
|
return {
|
package/dist/workdir/overlay.mjs
CHANGED
|
@@ -58,7 +58,7 @@ function mergeDirectoryChildren(originChildren, overlay, addedEntryModes) {
|
|
|
58
58
|
return merged;
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
|
-
* 在目录 overlay 中绑定或覆盖条目(create / modify /
|
|
61
|
+
* 在目录 overlay 中绑定或覆盖条目(create / modify / move 目标 / copy 目标)
|
|
62
62
|
*/
|
|
63
63
|
function overlayBindEntry(overlay, name, nodeId) {
|
|
64
64
|
const addedEntries = new Map(overlay.addedEntries);
|
|
@@ -84,7 +84,7 @@ function overlayTombstoneEntry(overlay, name) {
|
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
86
|
/**
|
|
87
|
-
*
|
|
87
|
+
* 在同一目录内 move:复用 nodeId,仅改绑定名
|
|
88
88
|
*/
|
|
89
89
|
function overlayRenameEntry(overlay, fromName, toName, nodeId) {
|
|
90
90
|
let next = overlayTombstoneEntry(overlay, fromName);
|
|
@@ -381,8 +381,10 @@ function readDiffObjectMode(raw) {
|
|
|
381
381
|
if (raw === "100644" || raw === "100755" || raw === "120000") return raw;
|
|
382
382
|
throw new Error(`Invalid SQLite workdir diff object mode: ${raw}`);
|
|
383
383
|
}
|
|
384
|
+
/** 解析 diff 来源种类;旧版 `rename` 读入时规范为 `move`。 */
|
|
384
385
|
function readDiffSourceKind(raw) {
|
|
385
|
-
if (raw === "
|
|
386
|
+
if (raw === "move" || raw === "copy") return raw;
|
|
387
|
+
if (raw === "rename") return "move";
|
|
386
388
|
throw new Error(`Invalid SQLite workdir diff source kind: ${raw}`);
|
|
387
389
|
}
|
|
388
390
|
function readDirtyDirSummary(row) {
|
package/dist/workdir/workdir.mjs
CHANGED
|
@@ -2,7 +2,7 @@ import { VirtualNotDirectoryError, VirtualNotFileError, VirtualNotSymlinkError,
|
|
|
2
2
|
import { createNodeId } from "./ids.mjs";
|
|
3
3
|
import { modeToVirtualEntryKind, readRepoBlobContent } from "./origin.mjs";
|
|
4
4
|
import { overlayBindEntry, overlayRenameEntry, overlayTombstoneEntry } from "./overlay.mjs";
|
|
5
|
-
import { assertValidVirtualPath, normalizeDirectoryPath, splitPathSegments } from "./path.mjs";
|
|
5
|
+
import { assertValidVirtualPath, normalizeDirectoryPath, parentPath, splitPathSegments } from "./path.mjs";
|
|
6
6
|
import { getDirectoryChildrenView, getRootNode, requireExistingWriteTarget, requireMissingWriteTarget, resolveLeafWriteTarget, resolvePath, resolveWriteTransfer } from "./workdir-path.mjs";
|
|
7
7
|
import { createChangeIndexPlanner } from "./change-index-plan.mjs";
|
|
8
8
|
import { computeVirtualDiff, rebuildNormalizedChangeIndex, refreshChangeRecordForPath, replaceChangeRecords, rewriteChangeRecordForRename, writeChangeRecordForCopy } from "./change-index.mjs";
|
|
@@ -15,7 +15,7 @@ import { writeTreeFromSession } from "./write-tree.mjs";
|
|
|
15
15
|
/**
|
|
16
16
|
* VirtualWorkdir 行为编排
|
|
17
17
|
*
|
|
18
|
-
* Phase 5:完整文件/目录
|
|
18
|
+
* Phase 5:完整文件/目录 move 与 copy 语义。
|
|
19
19
|
*/
|
|
20
20
|
/**
|
|
21
21
|
* 基于 ObjectDatabase 创建 VirtualWorkdir
|
|
@@ -237,21 +237,29 @@ function openVirtualWorkdir(source, state) {
|
|
|
237
237
|
updateParentOverlay(state, target.parentNode.id, overlayTombstoneEntry(target.parentNode.state.overlay, target.name));
|
|
238
238
|
});
|
|
239
239
|
},
|
|
240
|
-
|
|
240
|
+
move(from, to) {
|
|
241
241
|
runInWriteTransaction(state, () => {
|
|
242
242
|
changeIndexPlanner.apply(changeIndexPlanner.planRewriteForRename(from, to));
|
|
243
|
-
|
|
243
|
+
const dirtyPaths = [from, to];
|
|
244
|
+
const toParent = parentPath(to);
|
|
245
|
+
if (toParent !== null) {
|
|
246
|
+
const segments = splitPathSegments(toParent);
|
|
247
|
+
for (let i = 0; i < segments.length; i++) dirtyPaths.push(segments.slice(0, i + 1).join("/"));
|
|
248
|
+
}
|
|
249
|
+
dirtyDirPlanner.rebuild(dirtyPaths);
|
|
244
250
|
}, invalidateDiffCaches, () => {
|
|
245
251
|
assertValidVirtualPath(from);
|
|
246
252
|
assertValidVirtualPath(to);
|
|
247
253
|
if (from === to) return;
|
|
254
|
+
const toParent = parentPath(to);
|
|
255
|
+
if (toParent !== null) mkdirRecursive(toParent);
|
|
248
256
|
const { from: fromTarget, to: toTarget } = resolveWriteTransfer(source, state, from, to);
|
|
249
257
|
const sourceNode = fromTarget.existing.node;
|
|
250
258
|
if (toTarget.existing !== null) throw new VirtualPathAlreadyExistsError(to);
|
|
251
259
|
if (sourceNode.state.kind === "directory") {
|
|
252
260
|
const toPath = to;
|
|
253
261
|
const fromPath = from;
|
|
254
|
-
if (toPath.startsWith(fromPath + "/") || toPath === fromPath) throw new Error(`Cannot
|
|
262
|
+
if (toPath.startsWith(fromPath + "/") || toPath === fromPath) throw new Error(`Cannot move '${from}' to '${to}': destination is a subdirectory of source`);
|
|
255
263
|
}
|
|
256
264
|
if (fromTarget.parentNode.id === toTarget.parentNode.id) updateParentOverlay(state, fromTarget.parentNode.id, overlayRenameEntry(fromTarget.parentNode.state.overlay, fromTarget.name, toTarget.name, sourceNode.id));
|
|
257
265
|
else {
|