nano-git 0.1.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.
- package/README.md +407 -0
- package/dist/backend/file-backend.d.mts +26 -0
- package/dist/backend/file-backend.mjs +83 -0
- package/dist/backend/file.d.mts +2 -0
- package/dist/backend/file.mjs +2 -0
- package/dist/backend/index.d.mts +2 -0
- package/dist/backend/index.mjs +1 -0
- package/dist/backend/memory-backend.d.mts +25 -0
- package/dist/backend/memory-backend.mjs +33 -0
- package/dist/backend/memory.d.mts +2 -0
- package/dist/backend/memory.mjs +2 -0
- package/dist/backend/types.d.mts +90 -0
- package/dist/core/errors.d.mts +124 -0
- package/dist/core/errors.mjs +168 -0
- package/dist/core/hash-digest.d.mts +35 -0
- package/dist/core/hash-digest.mjs +50 -0
- package/dist/core/hash-file.d.mts +20 -0
- package/dist/core/hash-file.mjs +27 -0
- package/dist/core/hash-path.d.mts +17 -0
- package/dist/core/hash-path.mjs +35 -0
- package/dist/core/types/odb.d.mts +63 -0
- package/dist/core/types/refs.d.mts +140 -0
- package/dist/core/types/refs.mjs +19 -0
- package/dist/core/types/shallow.d.mts +52 -0
- package/dist/core/types.d.mts +154 -0
- package/dist/core/types.mjs +43 -0
- package/dist/errors.d.mts +2 -0
- package/dist/errors.mjs +2 -0
- package/dist/hash-file.d.mts +2 -0
- package/dist/hash-file.mjs +2 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.mjs +14 -0
- package/dist/objects/author.d.mts +25 -0
- package/dist/objects/author.mjs +45 -0
- package/dist/objects/blob.d.mts +15 -0
- package/dist/objects/blob.mjs +20 -0
- package/dist/objects/codec.d.mts +46 -0
- package/dist/objects/codec.mjs +84 -0
- package/dist/objects/commit.d.mts +26 -0
- package/dist/objects/commit.mjs +160 -0
- package/dist/objects/index.d.mts +8 -0
- package/dist/objects/index.mjs +8 -0
- package/dist/objects/raw.d.mts +85 -0
- package/dist/objects/raw.mjs +111 -0
- package/dist/objects/tag.d.mts +26 -0
- package/dist/objects/tag.mjs +148 -0
- package/dist/objects/tree.d.mts +22 -0
- package/dist/objects/tree.mjs +66 -0
- package/dist/odb/file-utils.mjs +136 -0
- package/dist/odb/file.d.mts +22 -0
- package/dist/odb/file.mjs +66 -0
- package/dist/odb/memory.d.mts +22 -0
- package/dist/odb/memory.mjs +54 -0
- package/dist/pack/composite-store.d.mts +76 -0
- package/dist/pack/composite-store.mjs +133 -0
- package/dist/pack/constants.mjs +48 -0
- package/dist/pack/crc32.mjs +38 -0
- package/dist/pack/delta-apply.d.mts +21 -0
- package/dist/pack/delta-apply.mjs +71 -0
- package/dist/pack/delta-create.d.mts +28 -0
- package/dist/pack/delta-create.mjs +151 -0
- package/dist/pack/index.d.mts +16 -0
- package/dist/pack/index.mjs +13 -0
- package/dist/pack/object-header.d.mts +36 -0
- package/dist/pack/object-header.mjs +72 -0
- package/dist/pack/ofs-delta-offset.d.mts +35 -0
- package/dist/pack/ofs-delta-offset.mjs +59 -0
- package/dist/pack/pack-builder-types.d.mts +19 -0
- package/dist/pack/pack-builder.d.mts +52 -0
- package/dist/pack/pack-builder.mjs +80 -0
- package/dist/pack/pack-encoding.mjs +76 -0
- package/dist/pack/pack-index-reader.d.mts +57 -0
- package/dist/pack/pack-index-reader.mjs +90 -0
- package/dist/pack/pack-index-types.d.mts +16 -0
- package/dist/pack/pack-index-writer.d.mts +45 -0
- package/dist/pack/pack-index-writer.mjs +106 -0
- package/dist/pack/pack-reader-resolver.mjs +104 -0
- package/dist/pack/pack-reader-types.d.mts +31 -0
- package/dist/pack/pack-reader-types.mjs +22 -0
- package/dist/pack/pack-reader-utils.mjs +61 -0
- package/dist/pack/pack-reader.d.mts +81 -0
- package/dist/pack/pack-reader.mjs +171 -0
- package/dist/pack/pack-store-loader.mjs +79 -0
- package/dist/pack/pack-store-types.d.mts +16 -0
- package/dist/pack/pack-store.d.mts +55 -0
- package/dist/pack/pack-store.mjs +114 -0
- package/dist/pack/pack-writer.mjs +96 -0
- package/dist/pack/varint.d.mts +34 -0
- package/dist/pack/varint.mjs +57 -0
- package/dist/refs/file.d.mts +6 -0
- package/dist/refs/file.mjs +222 -0
- package/dist/refs/fs-utils.mjs +30 -0
- package/dist/refs/memory.d.mts +14 -0
- package/dist/refs/memory.mjs +104 -0
- package/dist/refs/names.d.mts +57 -0
- package/dist/refs/names.mjs +80 -0
- package/dist/refs/resolve.d.mts +33 -0
- package/dist/refs/resolve.mjs +55 -0
- package/dist/refs/shallow/file.d.mts +17 -0
- package/dist/refs/shallow/file.mjs +61 -0
- package/dist/refs/shallow/memory.d.mts +18 -0
- package/dist/refs/shallow/memory.mjs +33 -0
- package/dist/repository/core.d.mts +3 -0
- package/dist/repository/core.mjs +2 -0
- package/dist/repository/create.d.mts +22 -0
- package/dist/repository/create.mjs +43 -0
- package/dist/repository/file.d.mts +43 -0
- package/dist/repository/file.mjs +82 -0
- package/dist/repository/import/import-glob.mjs +44 -0
- package/dist/repository/import/import-plan-builder.mjs +625 -0
- package/dist/repository/import/import-session-types.d.mts +280 -0
- package/dist/repository/import/import-session.mjs +96 -0
- package/dist/repository/import/import-view.mjs +133 -0
- package/dist/repository/memory.d.mts +17 -0
- package/dist/repository/memory.mjs +33 -0
- package/dist/repository/ops/fetch-operations.mjs +20 -0
- package/dist/repository/ops/fetch-types.d.mts +82 -0
- package/dist/repository/ops/fetch-url.mjs +82 -0
- package/dist/repository/ops/fs-object-operations.mjs +31 -0
- package/dist/repository/ops/maintenance-operations.mjs +62 -0
- package/dist/repository/ops/maintenance-types.d.mts +42 -0
- package/dist/repository/ops/object-operations.mjs +65 -0
- package/dist/repository/ops/object-types.d.mts +109 -0
- package/dist/repository/ops/push-operations.mjs +16 -0
- package/dist/repository/ops/push-resolution.mjs +17 -0
- package/dist/repository/ops/push-types.d.mts +72 -0
- package/dist/repository/ops/push-url.mjs +45 -0
- package/dist/repository/ops/reachability.mjs +60 -0
- package/dist/repository/ops/ref-operations.mjs +86 -0
- package/dist/repository/ops/ref-types.d.mts +70 -0
- package/dist/repository/tree/tree-patch.d.mts +64 -0
- package/dist/repository/tree/tree-patch.mjs +268 -0
- package/dist/repository/tree/tree-walk.d.mts +46 -0
- package/dist/repository/tree/tree-walk.mjs +65 -0
- package/dist/repository/tree/tree-writer.mjs +68 -0
- package/dist/repository/types.d.mts +36 -0
- package/dist/sha1.d.mts +4 -0
- package/dist/sha1.mjs +4 -0
- package/dist/transport/client/receive-pack/http.d.mts +33 -0
- package/dist/transport/client/receive-pack/http.mjs +99 -0
- package/dist/transport/client/receive-pack/push-error.d.mts +23 -0
- package/dist/transport/client/receive-pack/push-error.mjs +32 -0
- package/dist/transport/client/receive-pack/push-pack-plan.d.mts +28 -0
- package/dist/transport/client/receive-pack/push-pack-plan.mjs +60 -0
- package/dist/transport/client/receive-pack/push-policy.d.mts +19 -0
- package/dist/transport/client/receive-pack/push-policy.mjs +64 -0
- package/dist/transport/client/receive-pack/push-ref-plan.d.mts +45 -0
- package/dist/transport/client/receive-pack/push-ref-plan.mjs +108 -0
- package/dist/transport/client/receive-pack/push-report.d.mts +28 -0
- package/dist/transport/client/receive-pack/push-report.mjs +84 -0
- package/dist/transport/client/receive-pack/push-request-plan.mjs +52 -0
- package/dist/transport/client/receive-pack/push.d.mts +32 -0
- package/dist/transport/client/receive-pack/push.mjs +97 -0
- package/dist/transport/client/receive-pack/request.d.mts +39 -0
- package/dist/transport/client/receive-pack/request.mjs +46 -0
- package/dist/transport/client/receive-pack/response.d.mts +26 -0
- package/dist/transport/client/receive-pack/response.mjs +52 -0
- package/dist/transport/client/receive-pack/result.d.mts +34 -0
- package/dist/transport/client/receive-pack/result.mjs +100 -0
- package/dist/transport/client/upload-pack/capability-advertisement.d.mts +56 -0
- package/dist/transport/client/upload-pack/capability-advertisement.mjs +130 -0
- package/dist/transport/client/upload-pack/fetch.d.mts +109 -0
- package/dist/transport/client/upload-pack/fetch.mjs +392 -0
- package/dist/transport/client/upload-pack/http.d.mts +29 -0
- package/dist/transport/client/upload-pack/http.mjs +79 -0
- package/dist/transport/client/upload-pack/ls-refs.d.mts +75 -0
- package/dist/transport/client/upload-pack/ls-refs.mjs +150 -0
- package/dist/transport/client/upload-pack/object-info.d.mts +65 -0
- package/dist/transport/client/upload-pack/object-info.mjs +111 -0
- package/dist/transport/client/upload-pack/types.d.mts +153 -0
- package/dist/transport/http/index.d.mts +3 -0
- package/dist/transport/http/index.mjs +2 -0
- package/dist/transport/http/smart-http.d.mts +46 -0
- package/dist/transport/http/smart-http.mjs +176 -0
- package/dist/transport/http/types.d.mts +27 -0
- package/dist/transport/index.d.mts +9 -0
- package/dist/transport/index.mjs +8 -0
- package/dist/transport/protocol/object-graph.d.mts +63 -0
- package/dist/transport/protocol/object-graph.mjs +149 -0
- package/dist/transport/protocol/pkt-line.d.mts +109 -0
- package/dist/transport/protocol/pkt-line.mjs +195 -0
- package/dist/transport/protocol/ref-advertisement.mjs +185 -0
- package/dist/transport/protocol/ref-collection.d.mts +38 -0
- package/dist/transport/protocol/ref-collection.mjs +63 -0
- package/dist/transport/protocol/ref-match.d.mts +39 -0
- package/dist/transport/protocol/ref-match.mjs +42 -0
- package/dist/transport/protocol/refspec.d.mts +44 -0
- package/dist/transport/protocol/refspec.mjs +79 -0
- package/dist/transport/protocol/side-band.d.mts +65 -0
- package/dist/transport/protocol/side-band.mjs +142 -0
- package/dist/transport/protocol/transport-capabilities.mjs +46 -0
- package/dist/transport/protocol/types.d.mts +148 -0
- package/dist/transport/protocol/update-refs.d.mts +68 -0
- package/dist/transport/protocol/update-refs.mjs +126 -0
- package/dist/transport/receive-pack.d.mts +11 -0
- package/dist/transport/receive-pack.mjs +11 -0
- package/dist/transport/server/receive-pack/advertise.d.mts +28 -0
- package/dist/transport/server/receive-pack/advertise.mjs +88 -0
- package/dist/transport/server/receive-pack/handler.d.mts +30 -0
- package/dist/transport/server/receive-pack/handler.mjs +156 -0
- package/dist/transport/server/receive-pack/index.d.mts +6 -0
- package/dist/transport/server/receive-pack/index.mjs +6 -0
- package/dist/transport/server/receive-pack/parse.d.mts +17 -0
- package/dist/transport/server/receive-pack/parse.mjs +80 -0
- package/dist/transport/server/receive-pack/report-status.mjs +64 -0
- package/dist/transport/server/receive-pack/service.d.mts +41 -0
- package/dist/transport/server/receive-pack/service.mjs +39 -0
- package/dist/transport/server/receive-pack/types.d.mts +56 -0
- package/dist/transport/server/receive-pack/types.mjs +25 -0
- package/dist/transport/server/receive-pack/unpack.mjs +119 -0
- package/dist/transport/server/upload-pack/advertise.d.mts +20 -0
- package/dist/transport/server/upload-pack/advertise.mjs +30 -0
- package/dist/transport/server/upload-pack/command.d.mts +43 -0
- package/dist/transport/server/upload-pack/command.mjs +56 -0
- package/dist/transport/server/upload-pack/fetch.d.mts +43 -0
- package/dist/transport/server/upload-pack/fetch.mjs +217 -0
- package/dist/transport/server/upload-pack/index.d.mts +7 -0
- package/dist/transport/server/upload-pack/index.mjs +7 -0
- package/dist/transport/server/upload-pack/ls-refs.d.mts +38 -0
- package/dist/transport/server/upload-pack/ls-refs.mjs +113 -0
- package/dist/transport/server/upload-pack/service.d.mts +40 -0
- package/dist/transport/server/upload-pack/service.mjs +51 -0
- package/dist/transport/server/upload-pack/types.d.mts +11 -0
- package/dist/transport/server/upload-pack/types.mjs +21 -0
- package/dist/transport/upload-pack.d.mts +7 -0
- package/dist/transport/upload-pack.mjs +6 -0
- package/package.json +98 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { InvalidObjectError } from "../core/errors.mjs";
|
|
2
|
+
import { sha1 } from "../core/types.mjs";
|
|
3
|
+
import { formatAuthor, parseAuthor } from "./author.mjs";
|
|
4
|
+
//#region src/objects/commit.ts
|
|
5
|
+
/**
|
|
6
|
+
* Commit 对象序列化/反序列化
|
|
7
|
+
*
|
|
8
|
+
* Commit 的文本格式:
|
|
9
|
+
* ```
|
|
10
|
+
* tree <hash>
|
|
11
|
+
* parent <hash>
|
|
12
|
+
* parent <hash>
|
|
13
|
+
* author <name> <email> <timestamp> <timezone>
|
|
14
|
+
* committer <name> <email> <timestamp> <timezone>
|
|
15
|
+
*
|
|
16
|
+
* <message>
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
const BUILTIN_COMMIT_HEADERS = /* @__PURE__ */ new Set([
|
|
20
|
+
"tree",
|
|
21
|
+
"parent",
|
|
22
|
+
"author",
|
|
23
|
+
"committer",
|
|
24
|
+
"encoding",
|
|
25
|
+
"gpgsig",
|
|
26
|
+
"mergetag"
|
|
27
|
+
]);
|
|
28
|
+
function validateExtraHeaderName(name) {
|
|
29
|
+
if (name.length === 0 || /\s/.test(name) || name.includes("\0")) throw new InvalidObjectError(`invalid commit extra header name: ${name}`);
|
|
30
|
+
if (BUILTIN_COMMIT_HEADERS.has(name)) throw new InvalidObjectError(`commit extra header "${name}" 与内建字段冲突,请改用对应的专用字段。`);
|
|
31
|
+
}
|
|
32
|
+
function parseCommitHeaders(headerText) {
|
|
33
|
+
if (headerText.length === 0) return [];
|
|
34
|
+
const rawLines = headerText.split("\n");
|
|
35
|
+
const headers = [];
|
|
36
|
+
let current;
|
|
37
|
+
for (const line of rawLines) {
|
|
38
|
+
if (line.startsWith(" ")) {
|
|
39
|
+
if (!current) throw new InvalidObjectError("invalid commit header continuation without base header");
|
|
40
|
+
current.value += `\n${line.slice(1)}`;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const spaceIndex = line.indexOf(" ");
|
|
44
|
+
if (spaceIndex === -1) throw new InvalidObjectError(`invalid commit header: ${line}`);
|
|
45
|
+
current = {
|
|
46
|
+
name: line.slice(0, spaceIndex),
|
|
47
|
+
value: line.slice(spaceIndex + 1)
|
|
48
|
+
};
|
|
49
|
+
headers.push(current);
|
|
50
|
+
}
|
|
51
|
+
return headers;
|
|
52
|
+
}
|
|
53
|
+
function applyParsedHeader(state, header) {
|
|
54
|
+
switch (header.name) {
|
|
55
|
+
case "tree":
|
|
56
|
+
if (state.tree !== void 0) throw new InvalidObjectError("commit has multiple tree headers");
|
|
57
|
+
state.tree = sha1(header.value);
|
|
58
|
+
return;
|
|
59
|
+
case "parent":
|
|
60
|
+
state.parents.push(sha1(header.value));
|
|
61
|
+
return;
|
|
62
|
+
case "author":
|
|
63
|
+
if (state.author !== void 0) throw new InvalidObjectError("commit has multiple author headers");
|
|
64
|
+
state.author = parseAuthor(header.value);
|
|
65
|
+
return;
|
|
66
|
+
case "committer":
|
|
67
|
+
if (state.committer !== void 0) throw new InvalidObjectError("commit has multiple committer headers");
|
|
68
|
+
state.committer = parseAuthor(header.value);
|
|
69
|
+
return;
|
|
70
|
+
case "encoding":
|
|
71
|
+
if (state.encoding !== void 0) throw new InvalidObjectError("commit has multiple encoding headers");
|
|
72
|
+
state.encoding = header.value;
|
|
73
|
+
return;
|
|
74
|
+
case "gpgsig":
|
|
75
|
+
if (state.gpgsig !== void 0) throw new InvalidObjectError("commit has multiple gpgsig headers");
|
|
76
|
+
state.gpgsig = header.value;
|
|
77
|
+
return;
|
|
78
|
+
case "mergetag":
|
|
79
|
+
state.mergetag.push(header.value);
|
|
80
|
+
return;
|
|
81
|
+
default: state.extraHeaders.push({
|
|
82
|
+
name: header.name,
|
|
83
|
+
value: header.value
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function encodeCommitHeader(name, value) {
|
|
88
|
+
const parts = value.split("\n");
|
|
89
|
+
const encoded = [`${name} ${parts[0] ?? ""}`];
|
|
90
|
+
for (let i = 1; i < parts.length; i++) encoded.push(` ${parts[i] ?? ""}`);
|
|
91
|
+
return encoded;
|
|
92
|
+
}
|
|
93
|
+
function buildCommitHeaderLines(commit) {
|
|
94
|
+
const lines = [];
|
|
95
|
+
lines.push(`tree ${commit.tree}`);
|
|
96
|
+
for (const parent of commit.parents) lines.push(`parent ${parent}`);
|
|
97
|
+
lines.push(`author ${formatAuthor(commit.author)}`);
|
|
98
|
+
lines.push(`committer ${formatAuthor(commit.committer)}`);
|
|
99
|
+
if (commit.encoding !== void 0) lines.push(...encodeCommitHeader("encoding", commit.encoding));
|
|
100
|
+
if (commit.gpgsig !== void 0) lines.push(...encodeCommitHeader("gpgsig", commit.gpgsig));
|
|
101
|
+
for (const tag of commit.mergetag ?? []) lines.push(...encodeCommitHeader("mergetag", tag));
|
|
102
|
+
for (const header of commit.extraHeaders ?? []) {
|
|
103
|
+
validateExtraHeaderName(header.name);
|
|
104
|
+
lines.push(...encodeCommitHeader(header.name, header.value));
|
|
105
|
+
}
|
|
106
|
+
return lines;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* 序列化 Commit 对象
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```ts
|
|
113
|
+
* const commit: GitCommit = {
|
|
114
|
+
* type: "commit",
|
|
115
|
+
* tree: sha1("abc..."),
|
|
116
|
+
* parents: [],
|
|
117
|
+
* author: { name: "John", email: "j@e.com", timestamp: 123, timezone: "+0800" },
|
|
118
|
+
* committer: { name: "John", email: "j@e.com", timestamp: 123, timezone: "+0800" },
|
|
119
|
+
* message: "Initial commit",
|
|
120
|
+
* };
|
|
121
|
+
* const buf = serializeCommit(commit);
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
function serializeCommit(commit) {
|
|
125
|
+
const headerLines = buildCommitHeaderLines(commit);
|
|
126
|
+
return Buffer.from(`${headerLines.join("\n")}\n\n${commit.message.replace(/\n+$/, "")}\n`, "utf-8");
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* 反序列化 Commit 对象
|
|
130
|
+
*/
|
|
131
|
+
function deserializeCommit(content) {
|
|
132
|
+
const text = content.toString("utf-8");
|
|
133
|
+
const separatorIndex = text.indexOf("\n\n");
|
|
134
|
+
const headerText = separatorIndex === -1 ? text : text.slice(0, separatorIndex);
|
|
135
|
+
const rawMessage = separatorIndex === -1 ? "" : text.slice(separatorIndex + 2);
|
|
136
|
+
const parsedHeaders = parseCommitHeaders(headerText);
|
|
137
|
+
const state = {
|
|
138
|
+
parents: [],
|
|
139
|
+
mergetag: [],
|
|
140
|
+
extraHeaders: []
|
|
141
|
+
};
|
|
142
|
+
for (const header of parsedHeaders) applyParsedHeader(state, header);
|
|
143
|
+
if (!state.tree) throw new InvalidObjectError("commit missing tree");
|
|
144
|
+
if (!state.author) throw new InvalidObjectError("commit missing author");
|
|
145
|
+
if (!state.committer) throw new InvalidObjectError("commit missing committer");
|
|
146
|
+
return {
|
|
147
|
+
type: "commit",
|
|
148
|
+
tree: state.tree,
|
|
149
|
+
parents: state.parents,
|
|
150
|
+
author: state.author,
|
|
151
|
+
committer: state.committer,
|
|
152
|
+
encoding: state.encoding,
|
|
153
|
+
gpgsig: state.gpgsig,
|
|
154
|
+
mergetag: state.mergetag.length > 0 ? state.mergetag : void 0,
|
|
155
|
+
extraHeaders: state.extraHeaders.length > 0 ? state.extraHeaders : void 0,
|
|
156
|
+
message: rawMessage.replace(/\n$/, "")
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
//#endregion
|
|
160
|
+
export { deserializeCommit, serializeCommit };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { deserializeBlob, serializeBlob } from "./blob.mjs";
|
|
2
|
+
import { deserializeTree, serializeTree } from "./tree.mjs";
|
|
3
|
+
import { deserializeCommit, serializeCommit } from "./commit.mjs";
|
|
4
|
+
import { deserializeTag, serializeTag } from "./tag.mjs";
|
|
5
|
+
import { formatAuthor, parseAuthor } from "./author.mjs";
|
|
6
|
+
import { deserialize, deserializeContent, serialize, serializeContent } from "./codec.mjs";
|
|
7
|
+
import { decodeObject, encodeObject, readObject, tryReadObject, writeObject } from "./raw.mjs";
|
|
8
|
+
export { decodeObject, deserialize, deserializeBlob, deserializeCommit, deserializeContent, deserializeTag, deserializeTree, encodeObject, formatAuthor, parseAuthor, readObject, serialize, serializeBlob, serializeCommit, serializeContent, serializeTag, serializeTree, tryReadObject, writeObject };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { deserializeBlob, serializeBlob } from "./blob.mjs";
|
|
2
|
+
import { deserializeTree, serializeTree } from "./tree.mjs";
|
|
3
|
+
import { formatAuthor, parseAuthor } from "./author.mjs";
|
|
4
|
+
import { deserializeCommit, serializeCommit } from "./commit.mjs";
|
|
5
|
+
import { deserializeTag, serializeTag } from "./tag.mjs";
|
|
6
|
+
import { deserialize, deserializeContent, serialize, serializeContent } from "./codec.mjs";
|
|
7
|
+
import { decodeObject, encodeObject, readObject, tryReadObject, writeObject } from "./raw.mjs";
|
|
8
|
+
export { decodeObject, deserialize, deserializeBlob, deserializeCommit, deserializeContent, deserializeTag, deserializeTree, encodeObject, formatAuthor, parseAuthor, readObject, serialize, serializeBlob, serializeCommit, serializeContent, serializeTag, serializeTree, tryReadObject, writeObject };
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { GitObject, RawGitObject, SHA1 } from "../core/types.mjs";
|
|
2
|
+
import { ObjectDatabase, ObjectSource } from "../core/types/odb.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/objects/raw.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* 将语义对象编码为 RawGitObject
|
|
7
|
+
*
|
|
8
|
+
* 内部复用 serializeContent() 计算内容字节,
|
|
9
|
+
* 并用 hashObject() 计算 canonical SHA-1。
|
|
10
|
+
*
|
|
11
|
+
* @param obj - 语义层 Git 对象
|
|
12
|
+
* @returns 可用于 ODB ingest 的原始对象
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const blob: GitBlob = { type: "blob", content: Buffer.from("hello") };
|
|
17
|
+
* const raw = encodeObject(blob);
|
|
18
|
+
* console.log(raw.hash); // => SHA-1
|
|
19
|
+
* console.log(raw.content); // => Buffer("hello")
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
declare function encodeObject(obj: GitObject): RawGitObject;
|
|
23
|
+
/**
|
|
24
|
+
* 将 RawGitObject 解码为语义对象
|
|
25
|
+
*
|
|
26
|
+
* 内部复用 deserializeContent() 解析对象内容。
|
|
27
|
+
*
|
|
28
|
+
* @param raw - 来自 ODB 的原始对象
|
|
29
|
+
* @returns 语义层 Git 对象(含类型特定的字段)
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const raw: RawGitObject = { hash, type: "blob", content: Buffer.from("hello") };
|
|
34
|
+
* const obj = decodeObject(raw);
|
|
35
|
+
* console.log(obj.type); // => "blob"
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
declare function decodeObject(raw: RawGitObject): GitObject;
|
|
39
|
+
/**
|
|
40
|
+
* 将语义对象写入对象数据库
|
|
41
|
+
*
|
|
42
|
+
* 先 encode 再 ingest,返回计算得到的哈希。
|
|
43
|
+
*
|
|
44
|
+
* @param db - 对象数据库
|
|
45
|
+
* @param obj - 语义层 Git 对象
|
|
46
|
+
* @returns 对象的 SHA-1 哈希
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const hash = writeObject(db, { type: "blob", content: Buffer.from("hello") });
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
declare function writeObject(db: ObjectDatabase, obj: GitObject): SHA1;
|
|
54
|
+
/**
|
|
55
|
+
* 从对象源读取并解码语义对象
|
|
56
|
+
*
|
|
57
|
+
* @param source - 只读对象源
|
|
58
|
+
* @param hash - 对象哈希
|
|
59
|
+
* @returns 语义层 Git 对象
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* const obj = readObject(source, hash);
|
|
64
|
+
* if (obj.type === "commit") console.log(obj.message);
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
declare function readObject(source: ObjectSource, hash: SHA1): GitObject;
|
|
68
|
+
/**
|
|
69
|
+
* 尝试从对象源读取并解码语义对象
|
|
70
|
+
*
|
|
71
|
+
* 对象不存在时返回 undefined,不抛出异常。
|
|
72
|
+
*
|
|
73
|
+
* @param source - 只读对象源
|
|
74
|
+
* @param hash - 对象哈希
|
|
75
|
+
* @returns 语义层 Git 对象,或 undefined
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```ts
|
|
79
|
+
* const obj = tryReadObject(source, hash);
|
|
80
|
+
* if (obj) { ... }
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
declare function tryReadObject(source: ObjectSource, hash: SHA1): GitObject | undefined;
|
|
84
|
+
//#endregion
|
|
85
|
+
export { decodeObject, encodeObject, readObject, tryReadObject, writeObject };
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { hashObject } from "../core/hash-digest.mjs";
|
|
2
|
+
import { deserializeContent, serializeContent } from "./codec.mjs";
|
|
3
|
+
//#region src/objects/raw.ts
|
|
4
|
+
/**
|
|
5
|
+
* 语义层 <-> Raw 对象转换 helper
|
|
6
|
+
*
|
|
7
|
+
* ODB 的真实边界是 RawGitObject,不是 GitObject。
|
|
8
|
+
* 本模块提供两者之间的双向转换工具,以及便捷的读写封装。
|
|
9
|
+
*
|
|
10
|
+
* 创建路径:GitObject → encodeObject() → RawGitObject → db.ingest()
|
|
11
|
+
* 读取路径:source.read() → RawGitObject → decodeObject() → GitObject
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* 将语义对象编码为 RawGitObject
|
|
15
|
+
*
|
|
16
|
+
* 内部复用 serializeContent() 计算内容字节,
|
|
17
|
+
* 并用 hashObject() 计算 canonical SHA-1。
|
|
18
|
+
*
|
|
19
|
+
* @param obj - 语义层 Git 对象
|
|
20
|
+
* @returns 可用于 ODB ingest 的原始对象
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const blob: GitBlob = { type: "blob", content: Buffer.from("hello") };
|
|
25
|
+
* const raw = encodeObject(blob);
|
|
26
|
+
* console.log(raw.hash); // => SHA-1
|
|
27
|
+
* console.log(raw.content); // => Buffer("hello")
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
function encodeObject(obj) {
|
|
31
|
+
const content = serializeContent(obj);
|
|
32
|
+
return {
|
|
33
|
+
hash: hashObject(obj.type, content),
|
|
34
|
+
type: obj.type,
|
|
35
|
+
content
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 将 RawGitObject 解码为语义对象
|
|
40
|
+
*
|
|
41
|
+
* 内部复用 deserializeContent() 解析对象内容。
|
|
42
|
+
*
|
|
43
|
+
* @param raw - 来自 ODB 的原始对象
|
|
44
|
+
* @returns 语义层 Git 对象(含类型特定的字段)
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```ts
|
|
48
|
+
* const raw: RawGitObject = { hash, type: "blob", content: Buffer.from("hello") };
|
|
49
|
+
* const obj = decodeObject(raw);
|
|
50
|
+
* console.log(obj.type); // => "blob"
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
function decodeObject(raw) {
|
|
54
|
+
return deserializeContent(raw.type, raw.content);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 将语义对象写入对象数据库
|
|
58
|
+
*
|
|
59
|
+
* 先 encode 再 ingest,返回计算得到的哈希。
|
|
60
|
+
*
|
|
61
|
+
* @param db - 对象数据库
|
|
62
|
+
* @param obj - 语义层 Git 对象
|
|
63
|
+
* @returns 对象的 SHA-1 哈希
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```ts
|
|
67
|
+
* const hash = writeObject(db, { type: "blob", content: Buffer.from("hello") });
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
function writeObject(db, obj) {
|
|
71
|
+
const raw = encodeObject(obj);
|
|
72
|
+
db.ingest(raw);
|
|
73
|
+
return raw.hash;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 从对象源读取并解码语义对象
|
|
77
|
+
*
|
|
78
|
+
* @param source - 只读对象源
|
|
79
|
+
* @param hash - 对象哈希
|
|
80
|
+
* @returns 语义层 Git 对象
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* const obj = readObject(source, hash);
|
|
85
|
+
* if (obj.type === "commit") console.log(obj.message);
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
function readObject(source, hash) {
|
|
89
|
+
return decodeObject(source.read(hash));
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 尝试从对象源读取并解码语义对象
|
|
93
|
+
*
|
|
94
|
+
* 对象不存在时返回 undefined,不抛出异常。
|
|
95
|
+
*
|
|
96
|
+
* @param source - 只读对象源
|
|
97
|
+
* @param hash - 对象哈希
|
|
98
|
+
* @returns 语义层 Git 对象,或 undefined
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* const obj = tryReadObject(source, hash);
|
|
103
|
+
* if (obj) { ... }
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
function tryReadObject(source, hash) {
|
|
107
|
+
const raw = source.tryRead(hash);
|
|
108
|
+
return raw ? decodeObject(raw) : void 0;
|
|
109
|
+
}
|
|
110
|
+
//#endregion
|
|
111
|
+
export { decodeObject, encodeObject, readObject, tryReadObject, writeObject };
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { GitTag } from "../core/types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/objects/tag.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* 序列化 Tag 对象
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const tag: GitTag = {
|
|
10
|
+
* type: "tag",
|
|
11
|
+
* object: sha1("abc..."),
|
|
12
|
+
* objectType: "commit",
|
|
13
|
+
* tag: "v1.0.0",
|
|
14
|
+
* tagger: { name: "John", email: "j@e.com", timestamp: 123, timezone: "+0800" },
|
|
15
|
+
* message: "Release v1.0.0",
|
|
16
|
+
* };
|
|
17
|
+
* const buf = serializeTag(tag);
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
declare function serializeTag(tag: GitTag): Buffer;
|
|
21
|
+
/**
|
|
22
|
+
* 反序列化 Tag 对象
|
|
23
|
+
*/
|
|
24
|
+
declare function deserializeTag(content: Buffer): GitTag;
|
|
25
|
+
//#endregion
|
|
26
|
+
export { deserializeTag, serializeTag };
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { InvalidObjectError } from "../core/errors.mjs";
|
|
2
|
+
import { assertObjectType, sha1 } from "../core/types.mjs";
|
|
3
|
+
import { formatAuthor, parseAuthor } from "./author.mjs";
|
|
4
|
+
//#region src/objects/tag.ts
|
|
5
|
+
/**
|
|
6
|
+
* Tag 对象序列化/反序列化
|
|
7
|
+
*
|
|
8
|
+
* Tag 的文本格式:
|
|
9
|
+
* ```
|
|
10
|
+
* object <hash>
|
|
11
|
+
* type <type>
|
|
12
|
+
* tag <name>
|
|
13
|
+
* tagger <name> <email> <timestamp> <timezone>
|
|
14
|
+
*
|
|
15
|
+
* <message>
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
const BUILTIN_TAG_HEADERS = /* @__PURE__ */ new Set([
|
|
19
|
+
"object",
|
|
20
|
+
"type",
|
|
21
|
+
"tag",
|
|
22
|
+
"tagger",
|
|
23
|
+
"gpgsig"
|
|
24
|
+
]);
|
|
25
|
+
function validateExtraHeaderName(name) {
|
|
26
|
+
if (name.length === 0 || /\s/.test(name) || name.includes("\0")) throw new InvalidObjectError(`invalid tag extra header name: ${name}`);
|
|
27
|
+
if (BUILTIN_TAG_HEADERS.has(name)) throw new InvalidObjectError(`tag extra header "${name}" 与内建字段冲突,请改用对应的专用字段。`);
|
|
28
|
+
}
|
|
29
|
+
function parseTagHeaders(headerText) {
|
|
30
|
+
if (headerText.length === 0) return [];
|
|
31
|
+
const rawLines = headerText.split("\n");
|
|
32
|
+
const headers = [];
|
|
33
|
+
let current;
|
|
34
|
+
for (const line of rawLines) {
|
|
35
|
+
if (line.startsWith(" ")) {
|
|
36
|
+
if (!current) throw new InvalidObjectError("invalid tag header continuation without base header");
|
|
37
|
+
current.value += `\n${line.slice(1)}`;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const spaceIndex = line.indexOf(" ");
|
|
41
|
+
if (spaceIndex === -1) throw new InvalidObjectError(`invalid tag header: ${line}`);
|
|
42
|
+
current = {
|
|
43
|
+
name: line.slice(0, spaceIndex),
|
|
44
|
+
value: line.slice(spaceIndex + 1)
|
|
45
|
+
};
|
|
46
|
+
headers.push(current);
|
|
47
|
+
}
|
|
48
|
+
return headers;
|
|
49
|
+
}
|
|
50
|
+
function applyParsedHeader(state, header) {
|
|
51
|
+
switch (header.name) {
|
|
52
|
+
case "object":
|
|
53
|
+
if (state.object !== void 0) throw new InvalidObjectError("tag has multiple object headers");
|
|
54
|
+
state.object = sha1(header.value);
|
|
55
|
+
return;
|
|
56
|
+
case "type":
|
|
57
|
+
if (state.objectType !== void 0) throw new InvalidObjectError("tag has multiple type headers");
|
|
58
|
+
state.objectType = assertObjectType(header.value);
|
|
59
|
+
return;
|
|
60
|
+
case "tag":
|
|
61
|
+
if (state.tag !== void 0) throw new InvalidObjectError("tag has multiple tag headers");
|
|
62
|
+
state.tag = header.value;
|
|
63
|
+
return;
|
|
64
|
+
case "tagger":
|
|
65
|
+
if (state.tagger !== void 0) throw new InvalidObjectError("tag has multiple tagger headers");
|
|
66
|
+
state.tagger = parseAuthor(header.value);
|
|
67
|
+
return;
|
|
68
|
+
case "gpgsig":
|
|
69
|
+
if (state.gpgsig !== void 0) throw new InvalidObjectError("tag has multiple gpgsig headers");
|
|
70
|
+
state.gpgsig = header.value;
|
|
71
|
+
return;
|
|
72
|
+
default: state.extraHeaders.push({
|
|
73
|
+
name: header.name,
|
|
74
|
+
value: header.value
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function encodeTagHeader(name, value) {
|
|
79
|
+
const parts = value.split("\n");
|
|
80
|
+
const encoded = [`${name} ${parts[0] ?? ""}`];
|
|
81
|
+
for (let i = 1; i < parts.length; i++) encoded.push(` ${parts[i] ?? ""}`);
|
|
82
|
+
return encoded;
|
|
83
|
+
}
|
|
84
|
+
function buildTagHeaderLines(tag) {
|
|
85
|
+
const lines = [];
|
|
86
|
+
lines.push(`object ${tag.object}`);
|
|
87
|
+
lines.push(`type ${tag.objectType}`);
|
|
88
|
+
lines.push(`tag ${tag.tag}`);
|
|
89
|
+
lines.push(`tagger ${formatAuthor(tag.tagger)}`);
|
|
90
|
+
if (tag.gpgsig !== void 0) lines.push(...encodeTagHeader("gpgsig", tag.gpgsig));
|
|
91
|
+
for (const header of tag.extraHeaders ?? []) {
|
|
92
|
+
validateExtraHeaderName(header.name);
|
|
93
|
+
lines.push(...encodeTagHeader(header.name, header.value));
|
|
94
|
+
}
|
|
95
|
+
return lines;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* 序列化 Tag 对象
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```ts
|
|
102
|
+
* const tag: GitTag = {
|
|
103
|
+
* type: "tag",
|
|
104
|
+
* object: sha1("abc..."),
|
|
105
|
+
* objectType: "commit",
|
|
106
|
+
* tag: "v1.0.0",
|
|
107
|
+
* tagger: { name: "John", email: "j@e.com", timestamp: 123, timezone: "+0800" },
|
|
108
|
+
* message: "Release v1.0.0",
|
|
109
|
+
* };
|
|
110
|
+
* const buf = serializeTag(tag);
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
function serializeTag(tag) {
|
|
114
|
+
const lines = [
|
|
115
|
+
...buildTagHeaderLines(tag),
|
|
116
|
+
"",
|
|
117
|
+
tag.message.replace(/\n+$/, "")
|
|
118
|
+
];
|
|
119
|
+
return Buffer.from(lines.join("\n") + "\n", "utf-8");
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* 反序列化 Tag 对象
|
|
123
|
+
*/
|
|
124
|
+
function deserializeTag(content) {
|
|
125
|
+
const text = content.toString("utf-8");
|
|
126
|
+
const headerEnd = text.indexOf("\n\n");
|
|
127
|
+
if (headerEnd === -1) throw new InvalidObjectError("tag missing header/body separator");
|
|
128
|
+
const parsedHeaders = parseTagHeaders(text.slice(0, headerEnd));
|
|
129
|
+
const state = { extraHeaders: [] };
|
|
130
|
+
for (const header of parsedHeaders) applyParsedHeader(state, header);
|
|
131
|
+
if (!state.object) throw new InvalidObjectError("tag missing object");
|
|
132
|
+
if (!state.objectType) throw new InvalidObjectError("tag missing type");
|
|
133
|
+
if (!state.tag) throw new InvalidObjectError("tag missing tag name");
|
|
134
|
+
if (!state.tagger) throw new InvalidObjectError("tag missing tagger");
|
|
135
|
+
const message = text.slice(headerEnd + 2).replace(/\n$/, "");
|
|
136
|
+
return {
|
|
137
|
+
type: "tag",
|
|
138
|
+
object: state.object,
|
|
139
|
+
objectType: state.objectType,
|
|
140
|
+
tag: state.tag,
|
|
141
|
+
tagger: state.tagger,
|
|
142
|
+
gpgsig: state.gpgsig,
|
|
143
|
+
extraHeaders: state.extraHeaders.length > 0 ? state.extraHeaders : void 0,
|
|
144
|
+
message
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
//#endregion
|
|
148
|
+
export { deserializeTag, serializeTag };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { GitTree } from "../core/types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/objects/tree.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* 序列化 Tree 对象
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const tree: GitTree = {
|
|
10
|
+
* type: "tree",
|
|
11
|
+
* entries: [{ mode: "100644", name: "README.md", hash: sha1("abc...") }],
|
|
12
|
+
* };
|
|
13
|
+
* const buf = serializeTree(tree);
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
declare function serializeTree(tree: GitTree): Buffer;
|
|
17
|
+
/**
|
|
18
|
+
* 反序列化 Tree 对象
|
|
19
|
+
*/
|
|
20
|
+
declare function deserializeTree(content: Buffer): GitTree;
|
|
21
|
+
//#endregion
|
|
22
|
+
export { deserializeTree, serializeTree };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { InvalidObjectError } from "../core/errors.mjs";
|
|
2
|
+
import { sha1 } from "../core/types.mjs";
|
|
3
|
+
//#region src/objects/tree.ts
|
|
4
|
+
/**
|
|
5
|
+
* Tree 对象序列化/反序列化
|
|
6
|
+
*
|
|
7
|
+
* Tree 对象存储目录结构,使用二进制格式:
|
|
8
|
+
* 每个条目: "<mode> <name>\0<20-byte-hash>"
|
|
9
|
+
* - mode: 文件模式(如 "100644")
|
|
10
|
+
* - name: 文件名
|
|
11
|
+
* - hash: 20 字节的原始 SHA-1(不是十六进制字符串)
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* 序列化 Tree 对象
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* const tree: GitTree = {
|
|
19
|
+
* type: "tree",
|
|
20
|
+
* entries: [{ mode: "100644", name: "README.md", hash: sha1("abc...") }],
|
|
21
|
+
* };
|
|
22
|
+
* const buf = serializeTree(tree);
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
function serializeTree(tree) {
|
|
26
|
+
const buffers = [];
|
|
27
|
+
for (const entry of tree.entries) {
|
|
28
|
+
const entryHeader = Buffer.from(`${entry.mode} ${entry.name}\0`, "utf-8");
|
|
29
|
+
const entryHash = Buffer.from(entry.hash, "hex");
|
|
30
|
+
if (entryHash.length !== 20) throw new InvalidObjectError(`invalid SHA-1 hash length: ${entryHash.length}`);
|
|
31
|
+
buffers.push(entryHeader, entryHash);
|
|
32
|
+
}
|
|
33
|
+
return Buffer.concat(buffers);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* 反序列化 Tree 对象
|
|
37
|
+
*/
|
|
38
|
+
function deserializeTree(content) {
|
|
39
|
+
const entries = [];
|
|
40
|
+
let offset = 0;
|
|
41
|
+
while (offset < content.length) {
|
|
42
|
+
const nullIndex = content.indexOf(0, offset);
|
|
43
|
+
if (nullIndex === -1) throw new InvalidObjectError("tree: missing null byte");
|
|
44
|
+
const entryHeader = content.subarray(offset, nullIndex).toString("utf-8");
|
|
45
|
+
const spaceIndex = entryHeader.indexOf(" ");
|
|
46
|
+
if (spaceIndex === -1) throw new InvalidObjectError(`invalid tree entry: ${entryHeader}`);
|
|
47
|
+
const mode = entryHeader.slice(0, spaceIndex);
|
|
48
|
+
const name = entryHeader.slice(spaceIndex + 1);
|
|
49
|
+
const hashStart = nullIndex + 1;
|
|
50
|
+
const hashEnd = hashStart + 20;
|
|
51
|
+
if (hashEnd > content.length) throw new InvalidObjectError("tree: truncated hash");
|
|
52
|
+
const hash = content.subarray(hashStart, hashEnd).toString("hex");
|
|
53
|
+
entries.push({
|
|
54
|
+
mode,
|
|
55
|
+
name,
|
|
56
|
+
hash: sha1(hash)
|
|
57
|
+
});
|
|
58
|
+
offset = hashEnd;
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
type: "tree",
|
|
62
|
+
entries
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//#endregion
|
|
66
|
+
export { deserializeTree, serializeTree };
|