nano-git 0.3.2 → 0.3.3
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 +1 -1
- package/dist/core/types.d.mts +1 -1
- package/dist/objects/tree.d.mts +4 -0
- package/dist/objects/tree.mjs +53 -2
- package/dist/repository/tree/tree-patch.mjs +4 -4
- package/dist/repository/tree/tree-walk.mjs +1 -1
- package/dist/repository/tree/tree-writer.mjs +1 -1
- package/dist/workdir/change-index.mjs +1 -1
- package/dist/workdir/core.d.mts +1 -1
- package/dist/workdir/origin.mjs +2 -2
- package/dist/workdir/workdir-path.mjs +2 -2
- package/dist/workdir/workdir-transaction.mjs +1 -1
- package/dist/workdir/write-tree.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/core/types.d.mts
CHANGED
package/dist/objects/tree.d.mts
CHANGED
|
@@ -4,6 +4,8 @@ import { GitTree } from "../core/types.mjs";
|
|
|
4
4
|
/**
|
|
5
5
|
* 序列化 Tree 对象
|
|
6
6
|
*
|
|
7
|
+
* 写入时自动将规范 mode 转换为磁盘格式(如 "040000" → "40000")。
|
|
8
|
+
*
|
|
7
9
|
* @example
|
|
8
10
|
* ```ts
|
|
9
11
|
* const tree: GitTree = {
|
|
@@ -16,6 +18,8 @@ import { GitTree } from "../core/types.mjs";
|
|
|
16
18
|
declare function serializeTree(tree: GitTree): Buffer;
|
|
17
19
|
/**
|
|
18
20
|
* 反序列化 Tree 对象
|
|
21
|
+
*
|
|
22
|
+
* 读取时自动将磁盘 mode 转换为规范形式(如 "40000" → "040000")。
|
|
19
23
|
*/
|
|
20
24
|
declare function deserializeTree(content: Buffer): GitTree;
|
|
21
25
|
//#endregion
|
package/dist/objects/tree.mjs
CHANGED
|
@@ -9,10 +9,58 @@ import { sha1 } from "../core/types.mjs";
|
|
|
9
9
|
* - mode: 文件模式(如 "100644")
|
|
10
10
|
* - name: 文件名
|
|
11
11
|
* - hash: 20 字节的原始 SHA-1(不是十六进制字符串)
|
|
12
|
+
*
|
|
13
|
+
* mode 规范化说明:
|
|
14
|
+
* Git CLI 显示目录 mode 为 "040000"(6 位八进制,前导补零),但磁盘上存储为 "40000"(5 字节)。
|
|
15
|
+
* 本模块在序列化/反序列化边界做双向转换,内部统一使用规范形式("040000")。
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* 目录 mode 的磁盘格式(无前导零,5 字节)
|
|
12
19
|
*/
|
|
20
|
+
const DIR_MODE_ON_DISK = "40000";
|
|
21
|
+
/**
|
|
22
|
+
* 目录 mode 的规范格式(有前导零,6 字节,与 git cat-file -p 显示一致)
|
|
23
|
+
*/
|
|
24
|
+
const DIR_MODE_CANONICAL = "040000";
|
|
25
|
+
/**
|
|
26
|
+
* 将 mode 转换为规范形式(6 位八进制)
|
|
27
|
+
*
|
|
28
|
+
* 读取磁盘 tree 条目时调用:将 "40000" → "040000",其他 mode 不变。
|
|
29
|
+
*
|
|
30
|
+
* @param mode - 来自磁盘的 mode 字符串
|
|
31
|
+
* @returns 规范化的 mode 字符串
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* toCanonicalMode("40000") // => "040000"
|
|
36
|
+
* toCanonicalMode("100644") // => "100644"
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
function toCanonicalMode(mode) {
|
|
40
|
+
return mode === DIR_MODE_ON_DISK ? DIR_MODE_CANONICAL : mode;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* 将 mode 转换为磁盘格式(无多余前导零)
|
|
44
|
+
*
|
|
45
|
+
* 写入磁盘 tree 条目时调用:将 "040000" → "40000",其他 mode 不变。
|
|
46
|
+
*
|
|
47
|
+
* @param mode - 规范形式的 mode 字符串
|
|
48
|
+
* @returns 磁盘形式的 mode 字符串
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```ts
|
|
52
|
+
* toOnDiskMode("040000") // => "40000"
|
|
53
|
+
* toOnDiskMode("100644") // => "100644"
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
function toOnDiskMode(mode) {
|
|
57
|
+
return mode === DIR_MODE_CANONICAL ? DIR_MODE_ON_DISK : mode;
|
|
58
|
+
}
|
|
13
59
|
/**
|
|
14
60
|
* 序列化 Tree 对象
|
|
15
61
|
*
|
|
62
|
+
* 写入时自动将规范 mode 转换为磁盘格式(如 "040000" → "40000")。
|
|
63
|
+
*
|
|
16
64
|
* @example
|
|
17
65
|
* ```ts
|
|
18
66
|
* const tree: GitTree = {
|
|
@@ -25,7 +73,8 @@ import { sha1 } from "../core/types.mjs";
|
|
|
25
73
|
function serializeTree(tree) {
|
|
26
74
|
const buffers = [];
|
|
27
75
|
for (const entry of tree.entries) {
|
|
28
|
-
const
|
|
76
|
+
const mode = toOnDiskMode(entry.mode);
|
|
77
|
+
const entryHeader = Buffer.from(`${mode} ${entry.name}\0`, "utf-8");
|
|
29
78
|
const entryHash = Buffer.from(entry.hash, "hex");
|
|
30
79
|
if (entryHash.length !== 20) throw new InvalidObjectError(`invalid SHA-1 hash length: ${entryHash.length}`);
|
|
31
80
|
buffers.push(entryHeader, entryHash);
|
|
@@ -34,6 +83,8 @@ function serializeTree(tree) {
|
|
|
34
83
|
}
|
|
35
84
|
/**
|
|
36
85
|
* 反序列化 Tree 对象
|
|
86
|
+
*
|
|
87
|
+
* 读取时自动将磁盘 mode 转换为规范形式(如 "40000" → "040000")。
|
|
37
88
|
*/
|
|
38
89
|
function deserializeTree(content) {
|
|
39
90
|
const entries = [];
|
|
@@ -51,7 +102,7 @@ function deserializeTree(content) {
|
|
|
51
102
|
if (hashEnd > content.length) throw new InvalidObjectError("tree: truncated hash");
|
|
52
103
|
const hash = content.subarray(hashStart, hashEnd).toString("hex");
|
|
53
104
|
entries.push({
|
|
54
|
-
mode,
|
|
105
|
+
mode: toCanonicalMode(mode),
|
|
55
106
|
name,
|
|
56
107
|
hash: sha1(hash)
|
|
57
108
|
});
|
|
@@ -98,7 +98,7 @@ function processOpsSequentially(objects, rootHash, ops) {
|
|
|
98
98
|
* 在 tree 中查找路径对应的条目
|
|
99
99
|
*
|
|
100
100
|
* 沿路径依次读取 tree 对象,最后一段返回目标条目。
|
|
101
|
-
* 中间段必须为目录(mode "
|
|
101
|
+
* 中间段必须为目录(mode "040000"),否则抛出异常。
|
|
102
102
|
*/
|
|
103
103
|
function findEntryByPath(objects, treeHash, path) {
|
|
104
104
|
const segments = path.split("/");
|
|
@@ -109,7 +109,7 @@ function findEntryByPath(objects, treeHash, path) {
|
|
|
109
109
|
const entry = obj.entries.find((e) => e.name === segments[i]);
|
|
110
110
|
if (!entry) return null;
|
|
111
111
|
if (i === segments.length - 1) return entry;
|
|
112
|
-
if (entry.mode !== "
|
|
112
|
+
if (entry.mode !== "040000") throw new Error(`Cannot access '${segments.slice(0, i + 1).join("/")}': not a directory (mode: ${entry.mode})`);
|
|
113
113
|
currentHash = entry.hash;
|
|
114
114
|
}
|
|
115
115
|
return null;
|
|
@@ -174,7 +174,7 @@ function applyPatchRecursive(objects, treeHash, ops, prefix, upsertedPaths) {
|
|
|
174
174
|
for (const [name, childOps] of deeperOps) {
|
|
175
175
|
const existingEntry = existingEntries.find((e) => e.name === name);
|
|
176
176
|
const existingHash = existingEntry?.hash ?? null;
|
|
177
|
-
if (existingEntry !== void 0 && existingEntry.mode !== "
|
|
177
|
+
if (existingEntry !== void 0 && existingEntry.mode !== "040000") throw new Error(`Cannot access '${prefix}${name}': existing entry is not a directory (mode: ${existingEntry.mode})`);
|
|
178
178
|
if (existingHash === null) {
|
|
179
179
|
const hasUpsertInBatch = childOps.some((op) => op.op === "upsert") || childOps.some((op) => op.op === "delete" && upsertedPaths.has(`${prefix}${name}/${op.path}`));
|
|
180
180
|
if (childOps.every((op) => op.op === "delete") && !hasUpsertInBatch) {
|
|
@@ -185,7 +185,7 @@ function applyPatchRecursive(objects, treeHash, ops, prefix, upsertedPaths) {
|
|
|
185
185
|
const result = applyPatchRecursive(objects, existingHash, childOps, `${prefix}${name}/`, upsertedPaths);
|
|
186
186
|
writtenTrees.push(...result.written);
|
|
187
187
|
finalEntryMap.set(name, {
|
|
188
|
-
mode: "
|
|
188
|
+
mode: "040000",
|
|
189
189
|
name,
|
|
190
190
|
hash: result.hash
|
|
191
191
|
});
|
|
@@ -58,7 +58,7 @@ function walkTreeRecursive(objects, treeHash, prefix, result, visit) {
|
|
|
58
58
|
};
|
|
59
59
|
if (result) result.push(entryWithPath);
|
|
60
60
|
if (visit) visit(entryWithPath);
|
|
61
|
-
if (entry.mode === "
|
|
61
|
+
if (entry.mode === "040000") walkTreeRecursive(objects, entry.hash, path, result, visit);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
//#endregion
|
|
@@ -195,7 +195,7 @@ function snapshotBaseTree(source, treeHash, dirPath) {
|
|
|
195
195
|
const out = [];
|
|
196
196
|
for (const entry of tree.entries) {
|
|
197
197
|
const path = joinChildPath(dirPath, entry.name);
|
|
198
|
-
if (entry.mode === "
|
|
198
|
+
if (entry.mode === "040000") {
|
|
199
199
|
out.push(...snapshotBaseTree(source, entry.hash, path));
|
|
200
200
|
continue;
|
|
201
201
|
}
|
package/dist/workdir/core.d.mts
CHANGED
|
@@ -18,7 +18,7 @@ type VirtualEntryKind = "blob" | "tree" | "symlink";
|
|
|
18
18
|
interface VirtualEntryStat {
|
|
19
19
|
/** 条目种类 */
|
|
20
20
|
readonly kind: VirtualEntryKind;
|
|
21
|
-
/** Git 文件模式(如 "100644"、"100755"、"
|
|
21
|
+
/** Git 文件模式(如 "100644"、"100755"、"040000"、"120000") */
|
|
22
22
|
readonly mode: string;
|
|
23
23
|
/** 文件大小(对 blob 和 symlink 有效;目录返回 0) */
|
|
24
24
|
readonly size: number;
|
package/dist/workdir/origin.mjs
CHANGED
|
@@ -28,7 +28,7 @@ function readRepoBlobContent(source, hash, path) {
|
|
|
28
28
|
* 根据 tree 条目构造节点 origin
|
|
29
29
|
*/
|
|
30
30
|
function treeEntryToNodeOrigin(entry) {
|
|
31
|
-
if (entry.mode === "
|
|
31
|
+
if (entry.mode === "040000") return {
|
|
32
32
|
kind: "repo-tree",
|
|
33
33
|
hash: entry.hash
|
|
34
34
|
};
|
|
@@ -47,7 +47,7 @@ function treeEntryToNodeOrigin(entry) {
|
|
|
47
47
|
* Git mode 转为 VirtualEntryKind
|
|
48
48
|
*/
|
|
49
49
|
function modeToVirtualEntryKind(mode) {
|
|
50
|
-
if (mode === "
|
|
50
|
+
if (mode === "040000") return "tree";
|
|
51
51
|
if (mode === "120000") return "symlink";
|
|
52
52
|
return "blob";
|
|
53
53
|
}
|
|
@@ -272,7 +272,7 @@ function ensureNodeFromTreeEntry(state, entry) {
|
|
|
272
272
|
if (state.getNode(id) === null) {
|
|
273
273
|
const origin = treeEntryToNodeOrigin(entry);
|
|
274
274
|
let nodeState;
|
|
275
|
-
if (entry.mode === "
|
|
275
|
+
if (entry.mode === "040000") nodeState = {
|
|
276
276
|
kind: "directory",
|
|
277
277
|
overlay: {
|
|
278
278
|
addedEntries: /* @__PURE__ */ new Map(),
|
|
@@ -326,7 +326,7 @@ function buildAddedModes(state, dirNode) {
|
|
|
326
326
|
return addedModes;
|
|
327
327
|
}
|
|
328
328
|
function workdirNodeMode(node) {
|
|
329
|
-
if (node.state.kind === "directory") return "
|
|
329
|
+
if (node.state.kind === "directory") return "040000";
|
|
330
330
|
if (node.state.kind === "symlink") return "120000";
|
|
331
331
|
return node.state.mode;
|
|
332
332
|
}
|
|
@@ -101,7 +101,7 @@ function compileChildEntry(writeSource, readSource, state, child) {
|
|
|
101
101
|
const originHash = node.origin.kind === "repo-tree" ? node.origin.hash : null;
|
|
102
102
|
return {
|
|
103
103
|
entry: {
|
|
104
|
-
mode: "
|
|
104
|
+
mode: "040000",
|
|
105
105
|
name: child.name,
|
|
106
106
|
hash: newHash
|
|
107
107
|
},
|