nano-git 0.1.0 → 0.2.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.
Files changed (45) hide show
  1. package/README.md +58 -1
  2. package/dist/backend/sqlite.d.mts +44 -0
  3. package/dist/backend/sqlite.mjs +59 -0
  4. package/dist/core/errors.d.mts +71 -1
  5. package/dist/core/errors.mjs +99 -1
  6. package/dist/errors.d.mts +2 -2
  7. package/dist/errors.mjs +2 -2
  8. package/dist/index.d.mts +2 -1
  9. package/dist/log/index.d.mts +3 -0
  10. package/dist/log/index.mjs +2 -0
  11. package/dist/log/types.d.mts +71 -0
  12. package/dist/log/walk.d.mts +37 -0
  13. package/dist/log/walk.mjs +274 -0
  14. package/dist/odb/sqlite.d.mts +23 -0
  15. package/dist/odb/sqlite.mjs +83 -0
  16. package/dist/refs/shallow/sqlite.d.mts +23 -0
  17. package/dist/refs/shallow/sqlite.mjs +61 -0
  18. package/dist/refs/sqlite.d.mts +23 -0
  19. package/dist/refs/sqlite.mjs +135 -0
  20. package/dist/remote/http.d.mts +51 -0
  21. package/dist/remote/http.mjs +74 -0
  22. package/dist/remote/types.d.mts +20 -0
  23. package/dist/repository/import/import-session-types.d.mts +3 -16
  24. package/dist/repository/ops/object-operations.mjs +0 -5
  25. package/dist/repository/ops/object-types.d.mts +0 -21
  26. package/dist/repository/sqlite.d.mts +28 -0
  27. package/dist/repository/sqlite.mjs +45 -0
  28. package/dist/transport/upload-pack.d.mts +1 -1
  29. package/dist/transport/upload-pack.mjs +1 -1
  30. package/dist/workdir/change-log.mjs +63 -0
  31. package/dist/workdir/core.d.mts +166 -0
  32. package/dist/workdir/core.mjs +2 -0
  33. package/dist/workdir/ids.mjs +20 -0
  34. package/dist/workdir/memory-backend.mjs +21 -0
  35. package/dist/workdir/memory.d.mts +2 -0
  36. package/dist/workdir/memory.mjs +2 -0
  37. package/dist/workdir/nodes.mjs +112 -0
  38. package/dist/workdir/origin.mjs +55 -0
  39. package/dist/workdir/overlay.mjs +95 -0
  40. package/dist/workdir/path.mjs +70 -0
  41. package/dist/workdir/session-internal.mjs +141 -0
  42. package/dist/workdir/session.d.mts +18 -0
  43. package/dist/workdir/session.mjs +380 -0
  44. package/dist/workdir/write-tree.mjs +97 -0
  45. package/package.json +21 -3
@@ -0,0 +1,55 @@
1
+ import { VirtualOriginUnavailableError } from "../core/errors.mjs";
2
+ import { tryReadObject } from "../objects/raw.mjs";
3
+ //#region src/workdir/origin.ts
4
+ /**
5
+ * 从对象源读取 repo-backed origin(tree/blob)
6
+ *
7
+ * 仅依赖 ObjectSource,不绑定具体 ODB 后端实现。
8
+ */
9
+ /**
10
+ * 读取 tree 对象;缺失时抛 VirtualOriginUnavailableError
11
+ */
12
+ function readRepoTree(source, hash, path) {
13
+ const obj = tryReadObject(source, hash);
14
+ if (obj === void 0) throw new VirtualOriginUnavailableError(path, `Origin tree object missing: ${hash}`);
15
+ if (obj.type !== "tree") throw new VirtualOriginUnavailableError(path, `Expected tree at origin, got ${obj.type}`);
16
+ return obj;
17
+ }
18
+ /**
19
+ * 读取 blob 原始内容;缺失时抛 VirtualOriginUnavailableError
20
+ */
21
+ function readRepoBlobContent(source, hash, path) {
22
+ const obj = tryReadObject(source, hash);
23
+ if (obj === void 0) throw new VirtualOriginUnavailableError(path, `Origin blob object missing: ${hash}`);
24
+ if (obj.type !== "blob") throw new VirtualOriginUnavailableError(path, `Expected blob at origin, got ${obj.type}`);
25
+ return obj.content;
26
+ }
27
+ /**
28
+ * 根据 tree 条目构造节点 origin
29
+ */
30
+ function treeEntryToNodeOrigin(entry) {
31
+ if (entry.mode === "40000") return {
32
+ kind: "repo-tree",
33
+ hash: entry.hash
34
+ };
35
+ if (entry.mode === "100644" || entry.mode === "100755" || entry.mode === "120000") return {
36
+ kind: "repo-blob",
37
+ mode: entry.mode,
38
+ hash: entry.hash
39
+ };
40
+ return {
41
+ kind: "repo-blob",
42
+ mode: "100644",
43
+ hash: entry.hash
44
+ };
45
+ }
46
+ /**
47
+ * Git mode 转为 VirtualEntryKind
48
+ */
49
+ function modeToVirtualEntryKind(mode) {
50
+ if (mode === "40000") return "tree";
51
+ if (mode === "120000") return "symlink";
52
+ return "blob";
53
+ }
54
+ //#endregion
55
+ export { modeToVirtualEntryKind, readRepoBlobContent, readRepoTree, treeEntryToNodeOrigin };
@@ -0,0 +1,95 @@
1
+ //#region src/workdir/overlay.ts
2
+ /**
3
+ * 创建空目录 overlay
4
+ */
5
+ function createEmptyDirectoryOverlay() {
6
+ return {
7
+ addedEntries: /* @__PURE__ */ new Map(),
8
+ deletedNames: /* @__PURE__ */ new Set()
9
+ };
10
+ }
11
+ /**
12
+ * 克隆目录 overlay(深拷贝 Map/Set)
13
+ */
14
+ function cloneDirectoryOverlay(overlay) {
15
+ return {
16
+ addedEntries: new Map(overlay.addedEntries),
17
+ deletedNames: new Set(overlay.deletedNames)
18
+ };
19
+ }
20
+ /**
21
+ * 按 RFC 规则合成目录子项列表
22
+ *
23
+ * 1. 从 origin 去掉 deletedNames
24
+ * 2. addedEntries 覆盖同名 origin
25
+ * 3. 仅存在于 addedEntries 的条目追加
26
+ * 4. 按 Git tree 名称排序
27
+ */
28
+ function mergeDirectoryChildren(originChildren, overlay, addedEntryModes) {
29
+ const byName = /* @__PURE__ */ new Map();
30
+ for (const child of originChildren) {
31
+ if (overlay.deletedNames.has(child.name)) continue;
32
+ const overrideId = overlay.addedEntries.get(child.name);
33
+ if (overrideId !== void 0) {
34
+ const mode = addedEntryModes.get(child.name) ?? child.mode;
35
+ byName.set(child.name, {
36
+ name: child.name,
37
+ mode,
38
+ nodeId: overrideId
39
+ });
40
+ } else byName.set(child.name, {
41
+ name: child.name,
42
+ mode: child.mode,
43
+ nodeId: child.nodeId
44
+ });
45
+ }
46
+ for (const [name, nodeId] of overlay.addedEntries) {
47
+ if (byName.has(name)) continue;
48
+ const mode = addedEntryModes.get(name);
49
+ if (mode === void 0) throw new Error(`Missing mode for added directory entry '${name}'`);
50
+ byName.set(name, {
51
+ name,
52
+ mode,
53
+ nodeId
54
+ });
55
+ }
56
+ const merged = Array.from(byName.values());
57
+ merged.sort((a, b) => a.name.localeCompare(b.name));
58
+ return merged;
59
+ }
60
+ /**
61
+ * 在目录 overlay 中绑定或覆盖条目(create / modify / rename 目标 / copy 目标)
62
+ */
63
+ function overlayBindEntry(overlay, name, nodeId) {
64
+ const addedEntries = new Map(overlay.addedEntries);
65
+ const deletedNames = new Set(overlay.deletedNames);
66
+ addedEntries.set(name, nodeId);
67
+ deletedNames.delete(name);
68
+ return {
69
+ addedEntries,
70
+ deletedNames
71
+ };
72
+ }
73
+ /**
74
+ * 在目录 overlay 中删除条目(tombstone;不递归清理子节点存储)
75
+ */
76
+ function overlayTombstoneEntry(overlay, name) {
77
+ const addedEntries = new Map(overlay.addedEntries);
78
+ const deletedNames = new Set(overlay.deletedNames);
79
+ if (addedEntries.has(name)) addedEntries.delete(name);
80
+ else deletedNames.add(name);
81
+ return {
82
+ addedEntries,
83
+ deletedNames
84
+ };
85
+ }
86
+ /**
87
+ * 在同一目录内重命名:复用 nodeId,仅改绑定名
88
+ */
89
+ function overlayRenameEntry(overlay, fromName, toName, nodeId) {
90
+ let next = overlayTombstoneEntry(overlay, fromName);
91
+ next = overlayBindEntry(next, toName, nodeId);
92
+ return next;
93
+ }
94
+ //#endregion
95
+ export { cloneDirectoryOverlay, createEmptyDirectoryOverlay, mergeDirectoryChildren, overlayBindEntry, overlayRenameEntry, overlayTombstoneEntry };
@@ -0,0 +1,70 @@
1
+ /**
2
+ * 规范化 readdir 等 API 的目录路径参数
3
+ *
4
+ * `undefined` 与 `""` 均视为根目录。
5
+ */
6
+ function normalizeDirectoryPath(path) {
7
+ if (path === void 0 || path === "") return "";
8
+ assertValidVirtualPath(path);
9
+ return path;
10
+ }
11
+ /**
12
+ * 校验非根虚拟路径格式(用于文件/子路径操作)
13
+ *
14
+ * @throws 路径为空或格式非法时抛出 Error
15
+ */
16
+ function assertValidVirtualPath(path) {
17
+ if (path === "") throw new Error("Path must not be empty");
18
+ validateVirtualPathSegments(path);
19
+ }
20
+ /**
21
+ * 校验路径段规则(根路径 `""` 由调用方单独处理)
22
+ */
23
+ function validateVirtualPathSegments(path) {
24
+ if (path.startsWith("/")) throw new Error(`Path must not start with '/': ${path}`);
25
+ if (path.endsWith("/")) throw new Error(`Path must not end with '/': ${path}`);
26
+ if (path.includes("//")) throw new Error(`Path must not contain consecutive slashes: ${path}`);
27
+ for (const segment of path.split("/")) {
28
+ if (segment === "." || segment === "..") throw new Error(`Path must not contain '.' or '..': ${path}`);
29
+ if (segment === "") throw new Error(`Path must not contain empty segments: ${path}`);
30
+ }
31
+ }
32
+ /**
33
+ * 将路径拆分为段(不含空段)
34
+ *
35
+ * 调用前须保证路径已通过 `assertValidVirtualPath`。
36
+ */
37
+ function splitPathSegments(path) {
38
+ return path.split("/");
39
+ }
40
+ /**
41
+ * 返回父路径;单段路径返回 `null`(表示无父目录,父为根)
42
+ */
43
+ function parentPath(path) {
44
+ assertValidVirtualPath(path);
45
+ const lastSlash = path.lastIndexOf("/");
46
+ if (lastSlash === -1) return null;
47
+ return path.slice(0, lastSlash);
48
+ }
49
+ /**
50
+ * 返回路径最后一段(条目名)
51
+ */
52
+ function baseName(path) {
53
+ assertValidVirtualPath(path);
54
+ const lastSlash = path.lastIndexOf("/");
55
+ return lastSlash === -1 ? path : path.slice(lastSlash + 1);
56
+ }
57
+ /**
58
+ * 组合父路径与条目名
59
+ *
60
+ * @param parent - `null` 表示根下直接子项
61
+ */
62
+ function joinPath(parent, name) {
63
+ if (name === "" || name.includes("/")) throw new Error(`Invalid entry name: ${name}`);
64
+ if (parent === null) return name;
65
+ if (parent === "") return name;
66
+ assertValidVirtualPath(parent);
67
+ return `${parent}/${name}`;
68
+ }
69
+ //#endregion
70
+ export { assertValidVirtualPath, baseName, joinPath, normalizeDirectoryPath, parentPath, splitPathSegments };
@@ -0,0 +1,141 @@
1
+ import { VirtualNotDirectoryError } from "../core/errors.mjs";
2
+ import { VIRTUAL_ROOT_NODE_ID, originBackedNodeId } from "./ids.mjs";
3
+ import { mergeDirectoryChildren } from "./overlay.mjs";
4
+ import { readRepoTree, treeEntryToNodeOrigin } from "./origin.mjs";
5
+ import { assertValidVirtualPath, joinPath, splitPathSegments } from "./path.mjs";
6
+ //#region src/workdir/session-internal.ts
7
+ /**
8
+ * Virtual Workdir session 内部共享逻辑
9
+ *
10
+ * 供 session.ts 与 write-tree.ts 复用:
11
+ * - 路径解析(resolvePath)
12
+ * - 目录子项展开(listDirectoryChildren)
13
+ * - origin 节点懒注册(ensureNodeFromTreeEntry)
14
+ */
15
+ function getRootNode(state) {
16
+ const root = state.nodes.get(VIRTUAL_ROOT_NODE_ID);
17
+ if (root === void 0) throw new Error("Virtual workdir session is missing root node");
18
+ return root;
19
+ }
20
+ /**
21
+ * 从根目录沿路径分段解析到目标节点
22
+ */
23
+ function resolvePath(source, state, path) {
24
+ assertValidVirtualPath(path);
25
+ const segments = splitPathSegments(path);
26
+ let current = getRootNode(state);
27
+ let currentPath = "";
28
+ for (const segment of segments) {
29
+ if (current.state.kind !== "directory") return {
30
+ found: false,
31
+ node: null
32
+ };
33
+ const child = listDirectoryChildren(source, state, current, currentPath).find((c) => c.name === segment);
34
+ if (child === void 0) return {
35
+ found: false,
36
+ node: null
37
+ };
38
+ currentPath = joinPath(currentPath === "" ? null : currentPath, segment);
39
+ const childNode = state.nodes.get(child.nodeId);
40
+ if (childNode === void 0) return {
41
+ found: false,
42
+ node: null
43
+ };
44
+ current = childNode;
45
+ }
46
+ return {
47
+ found: true,
48
+ node: current
49
+ };
50
+ }
51
+ /**
52
+ * 根据父节点与子条目名解析子节点
53
+ */
54
+ function resolveChild(source, state, parentNode, parentPath, name) {
55
+ if (parentNode.state.kind !== "directory") return {
56
+ found: false,
57
+ node: null
58
+ };
59
+ const child = listDirectoryChildren(source, state, parentNode, parentPath).find((c) => c.name === name);
60
+ if (child === void 0) return {
61
+ found: false,
62
+ node: null
63
+ };
64
+ const childNode = state.nodes.get(child.nodeId);
65
+ if (childNode === void 0) return {
66
+ found: false,
67
+ node: null
68
+ };
69
+ return {
70
+ found: true,
71
+ node: childNode
72
+ };
73
+ }
74
+ /**
75
+ * 确保 origin 条目对应的 session 节点已懒注册
76
+ */
77
+ function ensureNodeFromTreeEntry(state, entry) {
78
+ const id = originBackedNodeId(entry.hash);
79
+ if (!state.nodes.has(id)) {
80
+ const origin = treeEntryToNodeOrigin(entry);
81
+ let nodeState;
82
+ if (entry.mode === "40000") nodeState = {
83
+ kind: "directory",
84
+ overlay: {
85
+ addedEntries: /* @__PURE__ */ new Map(),
86
+ deletedNames: /* @__PURE__ */ new Set()
87
+ }
88
+ };
89
+ else if (entry.mode === "120000") nodeState = {
90
+ kind: "symlink",
91
+ mode: "120000"
92
+ };
93
+ else nodeState = {
94
+ kind: "file",
95
+ mode: entry.mode === "100755" ? "100755" : "100644"
96
+ };
97
+ state.nodes.set(id, {
98
+ id,
99
+ origin,
100
+ state: nodeState
101
+ });
102
+ }
103
+ return id;
104
+ }
105
+ /**
106
+ * 展开目录的完整子项列表(origin + overlay 合成)
107
+ */
108
+ function listDirectoryChildren(source, state, dirNode, dirPath) {
109
+ if (dirNode.state.kind !== "directory") throw new VirtualNotDirectoryError(dirPath);
110
+ let originChildren = [];
111
+ if (dirNode.origin.kind === "repo-tree") originChildren = readRepoTree(source, dirNode.origin.hash, dirPath).entries.map((entry) => {
112
+ const childNodeId = ensureNodeFromTreeEntry(state, entry);
113
+ return {
114
+ name: entry.name,
115
+ mode: entry.mode,
116
+ nodeId: childNodeId
117
+ };
118
+ });
119
+ const addedModes = buildAddedModes(state, dirNode);
120
+ return mergeDirectoryChildren(originChildren, dirNode.state.overlay, addedModes);
121
+ }
122
+ /**
123
+ * 构建 overlay addedEntries 对应的 mode map
124
+ */
125
+ function buildAddedModes(state, dirNode) {
126
+ if (dirNode.state.kind !== "directory") return /* @__PURE__ */ new Map();
127
+ const addedModes = /* @__PURE__ */ new Map();
128
+ for (const name of dirNode.state.overlay.addedEntries.keys()) {
129
+ const nodeId = dirNode.state.overlay.addedEntries.get(name);
130
+ const node = state.nodes.get(nodeId);
131
+ if (node !== void 0) addedModes.set(name, sessionNodeMode(node));
132
+ }
133
+ return addedModes;
134
+ }
135
+ function sessionNodeMode(node) {
136
+ if (node.state.kind === "directory") return "40000";
137
+ if (node.state.kind === "symlink") return "120000";
138
+ return node.state.mode;
139
+ }
140
+ //#endregion
141
+ export { listDirectoryChildren, resolveChild, resolvePath };
@@ -0,0 +1,18 @@
1
+ import { ObjectDatabase } from "../core/types/odb.mjs";
2
+ import { CreateVirtualWorkdirSessionOptions, VirtualWorkdirSession } from "./core.mjs";
3
+
4
+ //#region src/workdir/session.d.ts
5
+ /**
6
+ * 基于 ObjectDatabase 创建 VirtualWorkdirSession
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * const repo = createMemoryRepository();
11
+ * const tree = repo.createTree([]);
12
+ * const session = createVirtualWorkdirSession(repo.objects, { baseTree: tree });
13
+ * expect(session.readdir()).toEqual([]);
14
+ * ```
15
+ */
16
+ declare function createVirtualWorkdirSession(source: ObjectDatabase, options: CreateVirtualWorkdirSessionOptions): VirtualWorkdirSession;
17
+ //#endregion
18
+ export { createVirtualWorkdirSession };