-
Notifications
You must be signed in to change notification settings - Fork 0
/
create-obj.ts
115 lines (108 loc) · 3.12 KB
/
create-obj.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import * as compress from "https://deno.land/x/[email protected]/mod.ts";
const encoder = new TextEncoder();
const content = encoder.encode("hello");
const blobSHA = await writeObject(encodeBlobObject(content));
const tree = encodeTreeObject([
{ mode: "100644", filename: "test", hash: blobSHA },
]);
const treeSHA = await writeObject(tree);
const commit = encodeCommitObject({
tree: treeSHA,
author: {
name: "Moge",
email: "[email protected]",
date: new Date(),
},
committer: {
name: "Moge",
email: "[email protected]",
date: new Date(),
},
message: "test\n",
});
console.log(await writeObject(commit));
async function writeObject(obj: Uint8Array) {
const sha1 = await crypto.subtle.digest("SHA-1", obj);
const sha1str = [...new Uint8Array(sha1)]
.map((n) => n.toString(16).padStart(2, "0"))
.join("");
const dirPath = `./testrepo/.git/objects/${sha1str.slice(0, 2)}`;
const path = `${dirPath}/${sha1str.slice(2)}`;
const data = compress.deflate(obj);
await Deno.mkdir(dirPath, { recursive: true });
await Deno.writeFile(path, data, { create: true });
return sha1str;
}
function encodeObject(type: string, body: Uint8Array) {
const head = encoder.encode(`${type} ${body.length}`);
const dest = new Uint8Array(head.length + 1 + body.length);
let p = 0;
for (let i = 0; i < head.length; i++) dest[p++] = head[i];
p++;
for (let i = 0; i < body.length; i++) dest[p++] = body[i];
return dest;
}
function encodeBlobObject(content: Uint8Array) {
return encodeObject("blob", content);
}
function encodeTreeObject(
entries: ReadonlyArray<{ mode: string; filename: string; hash: string }>,
) {
const bufs = entries.map(({ mode, filename, hash }) => {
if (!/^[0-9a-f]{40}$/.test(hash)) {
throw new Error(`Invalid hash: ${hash}`);
}
const sha = new Uint8Array(20);
for (let i = 0; i < 20; i++) {
sha[i] = Number.parseInt(hash.slice(i * 2, i * 2 + 2), 16);
}
const header = encoder.encode(`${mode} ${filename}`);
const buf = new Uint8Array(header.length + 1 + 20);
let p = 0;
for (let i = 0; i < header.length; i++) buf[p++] = header[i];
p++;
for (let i = 0; i < sha.length; i++) buf[p++] = sha[i];
return buf;
});
const len = bufs.reduce((pre, cur) => pre + cur.length, 0);
const dest = new Uint8Array(len);
let p = 0;
for (const b of bufs) {
for (let i = 0; i < b.length; i++) dest[p++] = b[i];
}
return encodeObject("tree", dest);
}
type Committer = Readonly<{
name: string;
email: string;
date: Date;
}>;
function encodeCommitObject({
tree,
parent,
author,
committer,
message,
}: Readonly<{
tree: string;
parent?: string;
author?: Committer;
committer?: Committer;
message: string;
}>) {
let msg = "";
msg += `tree ${tree}\n`;
if (parent) msg += `parent ${parent}\n`;
if (author) {
msg += `author ${author.name} <${author.email}> ${
author.date.getTime() / 1000
} +0000\n`;
}
if (committer) {
msg += `committer ${committer.name} <${committer.email}> ${
committer.date.getTime() / 1000
} +0000\n`;
}
msg += `\n${message}`;
return encodeObject("commit", encoder.encode(msg));
}