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,119 @@
|
|
|
1
|
+
import { sha1 } from "../../../core/types.mjs";
|
|
2
|
+
import { hashObject } from "../../../core/hash-digest.mjs";
|
|
3
|
+
import { numberToObjectType } from "../../../pack/constants.mjs";
|
|
4
|
+
import { decodeObjectHeader } from "../../../pack/object-header.mjs";
|
|
5
|
+
import { decodeOfsDeltaOffset } from "../../../pack/ofs-delta-offset.mjs";
|
|
6
|
+
import { applyDelta } from "../../../pack/delta-apply.mjs";
|
|
7
|
+
import { parsePackHeader, readCompressedData } from "../../../pack/pack-reader-utils.mjs";
|
|
8
|
+
import { ReceivePackServiceError } from "./types.mjs";
|
|
9
|
+
//#region src/transport/server/receive-pack/unpack.ts
|
|
10
|
+
/**
|
|
11
|
+
* Push packfile 解包(raw-first)
|
|
12
|
+
*
|
|
13
|
+
* 将 receive-pack push 请求中的 packfile 解包,
|
|
14
|
+
* 将对象以 RawGitObject 形式摄入对象数据库,处理 ofs_delta 和 ref_delta。
|
|
15
|
+
*
|
|
16
|
+
* 所有对象按 canonical raw object 直接写入,不经过语义序列化/反序列化。
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* 将 push packfile 中的对象解包到对象数据库中
|
|
20
|
+
*
|
|
21
|
+
* 处理非 delta、ofs_delta 和 ref_delta 三种对象类型。
|
|
22
|
+
* 对于 ref_delta,如果 base 不在当前 packfile 中,则从已存在的数据库查找。
|
|
23
|
+
* 所有对象通过 db.ingest() 以 RawGitObject 形式直接写入。
|
|
24
|
+
*
|
|
25
|
+
* @param db - 对象数据库
|
|
26
|
+
* @param packfile - push 请求中的 packfile 数据
|
|
27
|
+
* @throws {ReceivePackServiceError} 当解包失败时
|
|
28
|
+
*/
|
|
29
|
+
function unpackPackfile(db, packfile) {
|
|
30
|
+
if (packfile.length < 32) throw new ReceivePackServiceError("Packfile too small to contain any objects");
|
|
31
|
+
const objectCount = parsePackHeader(packfile);
|
|
32
|
+
if (objectCount === 0) return;
|
|
33
|
+
const resolvedByOffset = /* @__PURE__ */ new Map();
|
|
34
|
+
const resolvedByHash = /* @__PURE__ */ new Map();
|
|
35
|
+
let offset = 12;
|
|
36
|
+
for (let i = 0; i < objectCount; i++) {
|
|
37
|
+
const objOffset = offset;
|
|
38
|
+
const [typeNum, , headerBytes] = decodeObjectHeader(packfile, offset);
|
|
39
|
+
offset += headerBytes;
|
|
40
|
+
if (typeNum === 6) {
|
|
41
|
+
const [negOffset, offsetBytes] = decodeOfsDeltaOffset(packfile, offset);
|
|
42
|
+
offset += offsetBytes;
|
|
43
|
+
const [deltaData, compressedBytes] = readCompressedData(packfile, offset);
|
|
44
|
+
offset += compressedBytes;
|
|
45
|
+
const baseOffset = objOffset - negOffset;
|
|
46
|
+
const base = resolvedByOffset.get(baseOffset);
|
|
47
|
+
if (!base) throw new ReceivePackServiceError(`ofs_delta base not found at offset ${baseOffset}`);
|
|
48
|
+
const resolvedData = applyDelta(base.data, deltaData);
|
|
49
|
+
const hash = hashObject(base.type, resolvedData);
|
|
50
|
+
const raw = {
|
|
51
|
+
hash,
|
|
52
|
+
type: base.type,
|
|
53
|
+
content: resolvedData
|
|
54
|
+
};
|
|
55
|
+
db.ingest(raw);
|
|
56
|
+
resolvedByOffset.set(objOffset, {
|
|
57
|
+
type: base.type,
|
|
58
|
+
data: resolvedData
|
|
59
|
+
});
|
|
60
|
+
resolvedByHash.set(hash, {
|
|
61
|
+
type: base.type,
|
|
62
|
+
data: resolvedData
|
|
63
|
+
});
|
|
64
|
+
} else if (typeNum === 7) {
|
|
65
|
+
const baseHash = packfile.subarray(offset, offset + 20).toString("hex");
|
|
66
|
+
offset += 20;
|
|
67
|
+
const [deltaData, compressedBytes] = readCompressedData(packfile, offset);
|
|
68
|
+
offset += compressedBytes;
|
|
69
|
+
let base;
|
|
70
|
+
const cachedBase = resolvedByHash.get(baseHash);
|
|
71
|
+
if (cachedBase) base = cachedBase;
|
|
72
|
+
else if (db.exists(sha1(baseHash))) {
|
|
73
|
+
const raw = db.read(sha1(baseHash));
|
|
74
|
+
base = {
|
|
75
|
+
type: raw.type,
|
|
76
|
+
data: raw.content
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (!base) throw new ReceivePackServiceError(`ref_delta base not found: ${baseHash}`);
|
|
80
|
+
const resolvedData = applyDelta(base.data, deltaData);
|
|
81
|
+
const resolvedHash = hashObject(base.type, resolvedData);
|
|
82
|
+
const raw = {
|
|
83
|
+
hash: resolvedHash,
|
|
84
|
+
type: base.type,
|
|
85
|
+
content: resolvedData
|
|
86
|
+
};
|
|
87
|
+
db.ingest(raw);
|
|
88
|
+
resolvedByOffset.set(objOffset, {
|
|
89
|
+
type: base.type,
|
|
90
|
+
data: resolvedData
|
|
91
|
+
});
|
|
92
|
+
resolvedByHash.set(resolvedHash, {
|
|
93
|
+
type: base.type,
|
|
94
|
+
data: resolvedData
|
|
95
|
+
});
|
|
96
|
+
} else {
|
|
97
|
+
const [compressedData, compressedBytes] = readCompressedData(packfile, offset);
|
|
98
|
+
offset += compressedBytes;
|
|
99
|
+
const type = numberToObjectType(typeNum);
|
|
100
|
+
const hash = hashObject(type, compressedData);
|
|
101
|
+
const raw = {
|
|
102
|
+
hash,
|
|
103
|
+
type,
|
|
104
|
+
content: compressedData
|
|
105
|
+
};
|
|
106
|
+
db.ingest(raw);
|
|
107
|
+
resolvedByOffset.set(objOffset, {
|
|
108
|
+
type,
|
|
109
|
+
data: compressedData
|
|
110
|
+
});
|
|
111
|
+
resolvedByHash.set(hash, {
|
|
112
|
+
type,
|
|
113
|
+
data: compressedData
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//#endregion
|
|
119
|
+
export { unpackPackfile };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
//#region src/transport/server/upload-pack/advertise.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* upload-pack 能力广告生成
|
|
4
|
+
*
|
|
5
|
+
* 生成 upload-pack 的 v2 能力广告。
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* 生成 v2 能力广告
|
|
9
|
+
*
|
|
10
|
+
* @returns 完整的 pkt-line 编码能力广告
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const response = advertiseUploadPack();
|
|
15
|
+
* // "000eversion 2\n000bls-refs\n...0000"
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
declare function advertiseUploadPack(): Buffer;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { advertiseUploadPack };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { encodeFlushPkt, encodePktLine } from "../../protocol/pkt-line.mjs";
|
|
2
|
+
import { SERVER_AGENT } from "./types.mjs";
|
|
3
|
+
//#region src/transport/server/upload-pack/advertise.ts
|
|
4
|
+
/**
|
|
5
|
+
* upload-pack 能力广告生成
|
|
6
|
+
*
|
|
7
|
+
* 生成 upload-pack 的 v2 能力广告。
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* 生成 v2 能力广告
|
|
11
|
+
*
|
|
12
|
+
* @returns 完整的 pkt-line 编码能力广告
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const response = advertiseUploadPack();
|
|
17
|
+
* // "000eversion 2\n000bls-refs\n...0000"
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
function advertiseUploadPack() {
|
|
21
|
+
const parts = [];
|
|
22
|
+
parts.push(encodePktLine("version 2\n"));
|
|
23
|
+
parts.push(encodePktLine("ls-refs\n"));
|
|
24
|
+
parts.push(encodePktLine("fetch=shallow ref-in-want filter\n"));
|
|
25
|
+
parts.push(encodePktLine(`agent=${SERVER_AGENT}\n`));
|
|
26
|
+
parts.push(encodeFlushPkt());
|
|
27
|
+
return Buffer.concat(parts);
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { advertiseUploadPack };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/transport/server/upload-pack/command.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* v2 命令请求解析
|
|
4
|
+
*
|
|
5
|
+
* 解析 v2 协议中 command/capability/args 三段式请求体。
|
|
6
|
+
*
|
|
7
|
+
* 请求格式:
|
|
8
|
+
* ```
|
|
9
|
+
* command=<name>\n
|
|
10
|
+
* <capability-list>
|
|
11
|
+
* 0001
|
|
12
|
+
* <command-args>
|
|
13
|
+
* 0000
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @see https://git-scm.com/docs/protocol-v2#_request
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* 解析后的命令请求
|
|
20
|
+
*/
|
|
21
|
+
interface ParsedCommandRequest {
|
|
22
|
+
/** 命令名称(如 "ls-refs"、"fetch") */
|
|
23
|
+
readonly command: string;
|
|
24
|
+
/** capability-list(command 行后的 pkt-line,至 delimiter 前) */
|
|
25
|
+
readonly capabilities: string[];
|
|
26
|
+
/** command-args(delimiter 后的 pkt-line,至 flush 前) */
|
|
27
|
+
readonly args: string[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 解析 v2 命令请求体
|
|
31
|
+
*
|
|
32
|
+
* @param body - 完整的请求体(pkt-line 编码)
|
|
33
|
+
* @returns 解析后的命令信息
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const cmd = parseCommandRequest(body);
|
|
38
|
+
* console.log(cmd.command); // "ls-refs"
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
declare function parseCommandRequest(body: Buffer): ParsedCommandRequest;
|
|
42
|
+
//#endregion
|
|
43
|
+
export { ParsedCommandRequest, parseCommandRequest };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { parsePktLines } from "../../protocol/pkt-line.mjs";
|
|
2
|
+
//#region src/transport/server/upload-pack/command.ts
|
|
3
|
+
/**
|
|
4
|
+
* v2 命令请求解析
|
|
5
|
+
*
|
|
6
|
+
* 解析 v2 协议中 command/capability/args 三段式请求体。
|
|
7
|
+
*
|
|
8
|
+
* 请求格式:
|
|
9
|
+
* ```
|
|
10
|
+
* command=<name>\n
|
|
11
|
+
* <capability-list>
|
|
12
|
+
* 0001
|
|
13
|
+
* <command-args>
|
|
14
|
+
* 0000
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* @see https://git-scm.com/docs/protocol-v2#_request
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* 解析 v2 命令请求体
|
|
21
|
+
*
|
|
22
|
+
* @param body - 完整的请求体(pkt-line 编码)
|
|
23
|
+
* @returns 解析后的命令信息
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const cmd = parseCommandRequest(body);
|
|
28
|
+
* console.log(cmd.command); // "ls-refs"
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
function parseCommandRequest(body) {
|
|
32
|
+
const pktLines = parsePktLines(body);
|
|
33
|
+
let command = "";
|
|
34
|
+
const capabilities = [];
|
|
35
|
+
const args = [];
|
|
36
|
+
let afterDelimiter = false;
|
|
37
|
+
for (const pkt of pktLines) {
|
|
38
|
+
if (pkt.type === "flush") break;
|
|
39
|
+
if (pkt.type === "delimiter") {
|
|
40
|
+
afterDelimiter = true;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (pkt.type !== "data") continue;
|
|
44
|
+
const text = pkt.payload.toString("utf-8").replace(/\n$/, "");
|
|
45
|
+
if (!afterDelimiter) if (text.startsWith("command=")) command = text.slice(8);
|
|
46
|
+
else capabilities.push(text);
|
|
47
|
+
else args.push(text);
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
command,
|
|
51
|
+
capabilities,
|
|
52
|
+
args
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { parseCommandRequest };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { SHA1 } from "../../../core/types.mjs";
|
|
2
|
+
import { RepositoryBackend } from "../../../backend/types.mjs";
|
|
3
|
+
|
|
4
|
+
//#region src/transport/server/upload-pack/fetch.d.ts
|
|
5
|
+
/** 从 fetch 请求 args 中解析的参数 */
|
|
6
|
+
interface FetchServerParams {
|
|
7
|
+
readonly wants: SHA1[];
|
|
8
|
+
readonly haves: SHA1[];
|
|
9
|
+
readonly wantRefs: string[];
|
|
10
|
+
readonly done: boolean;
|
|
11
|
+
readonly thinPack: boolean;
|
|
12
|
+
readonly noProgress: boolean;
|
|
13
|
+
readonly ofsDelta: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 从 args 中解析 fetch 参数
|
|
17
|
+
*
|
|
18
|
+
* @param args - fetch 命令的 args 列表
|
|
19
|
+
* @returns 结构化的 fetch 参数
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const params = parseFetchArgs(["want <oid>", "have <oid>", "done"]);
|
|
24
|
+
* // { wants: [<oid>], haves: [<oid>], done: true, ... }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
declare function parseFetchArgs(args: string[]): FetchServerParams;
|
|
28
|
+
/**
|
|
29
|
+
* 生成 v2 fetch 响应
|
|
30
|
+
*
|
|
31
|
+
* @param backend - 仓库后端
|
|
32
|
+
* @param params - 解析后的 fetch 参数
|
|
33
|
+
* @returns 完整的 v2 fetch 响应(pkt-line 编码)
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```ts
|
|
37
|
+
* const params = parseFetchArgs(["want <oid>", "done"]);
|
|
38
|
+
* const response = generateFetchResponse(backend, params);
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
declare function generateFetchResponse(backend: RepositoryBackend, params: FetchServerParams): Buffer;
|
|
42
|
+
//#endregion
|
|
43
|
+
export { FetchServerParams, generateFetchResponse, parseFetchArgs };
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { sha1 } from "../../../core/types.mjs";
|
|
2
|
+
import { resolveRefHash } from "../../../refs/resolve.mjs";
|
|
3
|
+
import { encodeDelimiterPkt, encodeFlushPkt, encodePktLine } from "../../protocol/pkt-line.mjs";
|
|
4
|
+
import { collectReachable } from "../../protocol/object-graph.mjs";
|
|
5
|
+
import { createPackWriter } from "../../../pack/pack-writer.mjs";
|
|
6
|
+
import { MAX_PKT_PAYLOAD, UploadPackServiceError } from "./types.mjs";
|
|
7
|
+
//#region src/transport/server/upload-pack/fetch.ts
|
|
8
|
+
/**
|
|
9
|
+
* v2 fetch 命令响应生成
|
|
10
|
+
*
|
|
11
|
+
* 处理 fetch 命令,计算对象集合、构建 packfile、生成 fetch 响应。
|
|
12
|
+
*
|
|
13
|
+
* @see https://git-scm.com/docs/protocol-v2#_fetch
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* 从 args 中解析 fetch 参数
|
|
17
|
+
*
|
|
18
|
+
* @param args - fetch 命令的 args 列表
|
|
19
|
+
* @returns 结构化的 fetch 参数
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```ts
|
|
23
|
+
* const params = parseFetchArgs(["want <oid>", "have <oid>", "done"]);
|
|
24
|
+
* // { wants: [<oid>], haves: [<oid>], done: true, ... }
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
function parseFetchArgs(args) {
|
|
28
|
+
const wants = [];
|
|
29
|
+
const haves = [];
|
|
30
|
+
const wantRefs = [];
|
|
31
|
+
let done = false;
|
|
32
|
+
let thinPack = false;
|
|
33
|
+
let noProgress = false;
|
|
34
|
+
let ofsDelta = false;
|
|
35
|
+
for (const arg of args) if (arg === "done") done = true;
|
|
36
|
+
else if (arg === "thin-pack") thinPack = true;
|
|
37
|
+
else if (arg === "no-progress") noProgress = true;
|
|
38
|
+
else if (arg === "ofs-delta") ofsDelta = true;
|
|
39
|
+
else if (arg.startsWith("want ")) wants.push(sha1(arg.slice(5).trim()));
|
|
40
|
+
else if (arg.startsWith("have ")) haves.push(sha1(arg.slice(5).trim()));
|
|
41
|
+
else if (arg.startsWith("want-ref ")) wantRefs.push(arg.slice(9).trim());
|
|
42
|
+
return {
|
|
43
|
+
wants,
|
|
44
|
+
haves,
|
|
45
|
+
wantRefs,
|
|
46
|
+
done,
|
|
47
|
+
thinPack,
|
|
48
|
+
noProgress,
|
|
49
|
+
ofsDelta
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 计算要打包的对象集合
|
|
54
|
+
*
|
|
55
|
+
* - 无 haves(clone):返回所有 want 对象及其可达对象
|
|
56
|
+
* - 有 haves(增量 fetch):返回 want 可达对象与 have 可达对象的差集
|
|
57
|
+
*/
|
|
58
|
+
function computeObjectsToPack(backend, params) {
|
|
59
|
+
if (params.haves.length === 0) return collectReachable(backend.objects, params.wants, "skip-commit-parents");
|
|
60
|
+
const wantReachable = collectReachable(backend.objects, params.wants, "skip-commit-parents");
|
|
61
|
+
const haveReachable = collectReachable(backend.objects, params.haves, "skip-commit-parents");
|
|
62
|
+
const result = /* @__PURE__ */ new Set();
|
|
63
|
+
for (const hash of wantReachable) if (!haveReachable.has(hash)) result.add(hash);
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* 将 packfile 数据以 side-band channel 1 格式分帧
|
|
68
|
+
*
|
|
69
|
+
* 每个 pkt-line 帧:<4字节长度><1字节channel><数据>
|
|
70
|
+
*/
|
|
71
|
+
function encodePackfileWithSideBand(packfile) {
|
|
72
|
+
const maxPayload = MAX_PKT_PAYLOAD - 1;
|
|
73
|
+
const frames = [];
|
|
74
|
+
let offset = 0;
|
|
75
|
+
while (offset < packfile.length) {
|
|
76
|
+
const chunkSize = Math.min(maxPayload, packfile.length - offset);
|
|
77
|
+
const frame = Buffer.alloc(1 + chunkSize);
|
|
78
|
+
frame[0] = 1;
|
|
79
|
+
packfile.copy(frame, 1, offset, offset + chunkSize);
|
|
80
|
+
frames.push(encodePktLine(frame));
|
|
81
|
+
offset += chunkSize;
|
|
82
|
+
}
|
|
83
|
+
return frames;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* 生成带 packfile 的 fetch 响应
|
|
87
|
+
*
|
|
88
|
+
* 响应结构遵循 protocol-v2(节之间以 delim-pkt 分隔,最后以 flush-pkt 收尾):
|
|
89
|
+
* ```
|
|
90
|
+
* [acknowledgments\n ACK...\n ready\n 0001] ← 仅协商命中 ready 时(无 done)
|
|
91
|
+
* [wanted-refs\n <oid> <refname>\n ... 0001] ← 仅当客户端使用 want-ref
|
|
92
|
+
* packfile\n
|
|
93
|
+
* <side-band 编码的 packfile 数据>
|
|
94
|
+
* 0000
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* 注意:
|
|
98
|
+
* - 当请求带 `done`(无 acknowledgments 节)时,section 不应以 delim-pkt 开头——
|
|
99
|
+
* 首节直接是 wanted-refs 或 packfile。早期实现错误地在 packfile 前加了 leading
|
|
100
|
+
* delim-pkt,导致 git CLI 报 `fatal: expected 'packfile'`。
|
|
101
|
+
* - 当协商阶段(无 done)服务端判定 ready 时,必须在 **同一响应** 中
|
|
102
|
+
* acknowledgments 节之后紧接 packfile,否则 git 报
|
|
103
|
+
* `fatal: expected packfile after 'ready'`。
|
|
104
|
+
*
|
|
105
|
+
* @param wantedRefs - want-ref 解析出的 refname→oid 映射(无则不发 wanted-refs 节)
|
|
106
|
+
* @param ackSection - 可选的 acknowledgments 节内容(不含分隔 delim);提供时会在其后补一个 delim-pkt
|
|
107
|
+
*/
|
|
108
|
+
function generatePackfileResponse(backend, params, wantedRefs, ackSection) {
|
|
109
|
+
const parts = [];
|
|
110
|
+
if (ackSection !== void 0) {
|
|
111
|
+
parts.push(ackSection);
|
|
112
|
+
parts.push(encodeDelimiterPkt());
|
|
113
|
+
}
|
|
114
|
+
if (wantedRefs.length > 0) {
|
|
115
|
+
parts.push(encodePktLine("wanted-refs\n"));
|
|
116
|
+
for (const { refname, oid } of wantedRefs) parts.push(encodePktLine(`${oid} ${refname}\n`));
|
|
117
|
+
parts.push(encodeDelimiterPkt());
|
|
118
|
+
}
|
|
119
|
+
const toPack = computeObjectsToPack(backend, params);
|
|
120
|
+
const writer = createPackWriter();
|
|
121
|
+
for (const hash of toPack) {
|
|
122
|
+
const raw = backend.objects.tryRead(hash);
|
|
123
|
+
if (raw) writer.addRaw(raw);
|
|
124
|
+
}
|
|
125
|
+
const packfile = writer.build();
|
|
126
|
+
parts.push(encodePktLine("packfile\n"));
|
|
127
|
+
if (packfile.length > 0) parts.push(...encodePackfileWithSideBand(packfile));
|
|
128
|
+
parts.push(encodeFlushPkt());
|
|
129
|
+
return Buffer.concat(parts);
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* 查找 wants 与 haves 间的共同对象
|
|
133
|
+
*
|
|
134
|
+
* 当前简化实现:返回所有在本地仓库中存在且出现在 haves 中的哈希。
|
|
135
|
+
* 更完善的实现应通过 commit 图 BFS 验证祖先关系。
|
|
136
|
+
*/
|
|
137
|
+
function findCommonObjects(backend, _wants, haves) {
|
|
138
|
+
const common = [];
|
|
139
|
+
for (const have of haves) if (backend.objects.exists(have)) common.push(have);
|
|
140
|
+
return {
|
|
141
|
+
common,
|
|
142
|
+
ready: common.length > 0
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 构建 acknowledgments 节内容(不含尾部 flush/delim)
|
|
147
|
+
*
|
|
148
|
+
* ```
|
|
149
|
+
* acknowledgments\n
|
|
150
|
+
* NAK\n
|
|
151
|
+
* --- 或 ---
|
|
152
|
+
* acknowledgments\n
|
|
153
|
+
* ACK <oid>\n
|
|
154
|
+
* ready\n
|
|
155
|
+
* ```
|
|
156
|
+
*
|
|
157
|
+
* @returns section 内容及是否 ready
|
|
158
|
+
*/
|
|
159
|
+
function buildAcknowledgmentsSection(backend, params) {
|
|
160
|
+
const parts = [];
|
|
161
|
+
parts.push(encodePktLine("acknowledgments\n"));
|
|
162
|
+
const { common, ready } = findCommonObjects(backend, params.wants, params.haves);
|
|
163
|
+
if (common.length > 0) {
|
|
164
|
+
for (const oid of common) parts.push(encodePktLine(`ACK ${oid}\n`));
|
|
165
|
+
if (ready) parts.push(encodePktLine("ready\n"));
|
|
166
|
+
} else parts.push(encodePktLine("NAK\n"));
|
|
167
|
+
return {
|
|
168
|
+
section: Buffer.concat(parts),
|
|
169
|
+
ready
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* 生成 v2 fetch 响应
|
|
174
|
+
*
|
|
175
|
+
* @param backend - 仓库后端
|
|
176
|
+
* @param params - 解析后的 fetch 参数
|
|
177
|
+
* @returns 完整的 v2 fetch 响应(pkt-line 编码)
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* const params = parseFetchArgs(["want <oid>", "done"]);
|
|
182
|
+
* const response = generateFetchResponse(backend, params);
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
function generateFetchResponse(backend, params) {
|
|
186
|
+
if (params.wants.length === 0 && params.wantRefs.length === 0) throw new UploadPackServiceError("fetch: no wants or want-refs specified");
|
|
187
|
+
for (const want of params.wants) if (!backend.objects.exists(want)) {
|
|
188
|
+
const parts = [];
|
|
189
|
+
parts.push(encodePktLine("packfile\n"));
|
|
190
|
+
parts.push(encodePktLine(Buffer.concat([Buffer.from([3]), Buffer.from(`want ${want} not found\n`)])));
|
|
191
|
+
parts.push(encodeFlushPkt());
|
|
192
|
+
return Buffer.concat(parts);
|
|
193
|
+
}
|
|
194
|
+
const effectiveWants = [...params.wants];
|
|
195
|
+
const wantedRefs = [];
|
|
196
|
+
for (const ref of params.wantRefs) {
|
|
197
|
+
const hash = resolveRefHash(backend.refs, ref);
|
|
198
|
+
if (hash !== null) {
|
|
199
|
+
effectiveWants.push(hash);
|
|
200
|
+
wantedRefs.push({
|
|
201
|
+
refname: ref,
|
|
202
|
+
oid: hash
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const effectiveParams = {
|
|
207
|
+
...params,
|
|
208
|
+
wants: effectiveWants
|
|
209
|
+
};
|
|
210
|
+
if (effectiveParams.wants.length === 0) throw new UploadPackServiceError("fetch: no wants resolved");
|
|
211
|
+
if (params.done) return generatePackfileResponse(backend, effectiveParams, wantedRefs);
|
|
212
|
+
const { section: ackSection, ready } = buildAcknowledgmentsSection(backend, effectiveParams);
|
|
213
|
+
if (ready) return generatePackfileResponse(backend, effectiveParams, wantedRefs, ackSection);
|
|
214
|
+
return Buffer.concat([ackSection, encodeFlushPkt()]);
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
217
|
+
export { generateFetchResponse, parseFetchArgs };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { advertiseUploadPack } from "./advertise.mjs";
|
|
2
|
+
import { ParsedCommandRequest, parseCommandRequest } from "./command.mjs";
|
|
3
|
+
import { LsRefsServerOptions, generateLsRefsResponse, parseLsRefsArgs } from "./ls-refs.mjs";
|
|
4
|
+
import { FetchServerParams, generateFetchResponse, parseFetchArgs } from "./fetch.mjs";
|
|
5
|
+
import { UploadPackServiceError } from "./types.mjs";
|
|
6
|
+
import { UploadPackService, createUploadPackService } from "./service.mjs";
|
|
7
|
+
export { type FetchServerParams, type LsRefsServerOptions, type ParsedCommandRequest, type UploadPackService, UploadPackServiceError, advertiseUploadPack, createUploadPackService, generateFetchResponse, generateLsRefsResponse, parseCommandRequest, parseFetchArgs, parseLsRefsArgs };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { UploadPackServiceError } from "./types.mjs";
|
|
2
|
+
import { advertiseUploadPack } from "./advertise.mjs";
|
|
3
|
+
import { parseCommandRequest } from "./command.mjs";
|
|
4
|
+
import { generateLsRefsResponse, parseLsRefsArgs } from "./ls-refs.mjs";
|
|
5
|
+
import { generateFetchResponse, parseFetchArgs } from "./fetch.mjs";
|
|
6
|
+
import { createUploadPackService } from "./service.mjs";
|
|
7
|
+
export { UploadPackServiceError, advertiseUploadPack, createUploadPackService, generateFetchResponse, generateLsRefsResponse, parseCommandRequest, parseFetchArgs, parseLsRefsArgs };
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { RepositoryBackend } from "../../../backend/types.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/transport/server/upload-pack/ls-refs.d.ts
|
|
4
|
+
/** ls-refs 请求中解析出的选项 */
|
|
5
|
+
interface LsRefsServerOptions {
|
|
6
|
+
readonly symrefs: boolean;
|
|
7
|
+
readonly peel: boolean;
|
|
8
|
+
readonly unborn: boolean;
|
|
9
|
+
readonly refPrefixes: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* 从 args 中解析 ls-refs 选项
|
|
13
|
+
*
|
|
14
|
+
* @param args - ls-refs 命令的 args 列表
|
|
15
|
+
* @returns 结构化的 ls-refs 选项
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```ts
|
|
19
|
+
* const opts = parseLsRefsArgs(["symrefs", "peel", "ref-prefix refs/heads/"]);
|
|
20
|
+
* // { symrefs: true, peel: true, unborn: false, refPrefixes: ["refs/heads/"] }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare function parseLsRefsArgs(args: string[]): LsRefsServerOptions;
|
|
24
|
+
/**
|
|
25
|
+
* 生成 ls-refs 响应
|
|
26
|
+
*
|
|
27
|
+
* @param backend - 仓库后端
|
|
28
|
+
* @param options - ls-refs 选项
|
|
29
|
+
* @returns pkt-line 编码的 ls-refs 响应
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const buf = generateLsRefsResponse(backend, { symrefs: true, peel: true, unborn: false, refPrefixes: [] });
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
declare function generateLsRefsResponse(backend: RepositoryBackend, options: LsRefsServerOptions): Buffer;
|
|
37
|
+
//#endregion
|
|
38
|
+
export { LsRefsServerOptions, generateLsRefsResponse, parseLsRefsArgs };
|