forked from mdn/yari
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgit-history.ts
92 lines (86 loc) · 2.89 KB
/
git-history.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
import fs from "fs";
import path from "path";
import { execGit } from "../content";
import { CONTENT_ROOT } from "../libs/env";
function getFromGit(contentRoot = CONTENT_ROOT) {
// If `contentRoot` was a symlink, the `repoRoot` won't be. That'll make it
// impossible to compute the relative path for files within when we get
// output back from `git log ...`.
// So, always normalize to the real path.
const realContentRoot = fs.realpathSync(contentRoot);
const repoRoot = execGit(["rev-parse", "--show-toplevel"], {
cwd: realContentRoot,
});
const MARKER = "COMMIT:";
const DELIMITER = "_";
const output = execGit(
[
"log",
"--name-only",
"--no-decorate",
`--format=${MARKER}%H${DELIMITER}%cI${DELIMITER}%P`,
"--date-order",
"--reverse",
// "Separate the commits with NULs instead of with new newlines."
// So each line isn't, possibly, wrapped in "quotation marks".
// Now we just need to split the output, as a string, by \0.
"-z",
],
{
cwd: repoRoot,
},
repoRoot
);
const map = new Map();
let date = null;
let hash = null;
// Even if we specified the `-z` option to `git log ...` above, sometimes
// it seems `git log` prefers to use a newline character.
// At least as of git version 2.28.0 (Dec 2020). So let's split on both
// characters to be safe.
const parents = new Map();
for (const line of output.split(/\0|\n/)) {
if (line.startsWith(MARKER)) {
const data = line.replace(MARKER, "").split(DELIMITER);
hash = data[0];
date = new Date(data[1]);
if (data[2]) {
const split = data[2].split(" ");
if (split.length === 2) {
const [, parentHash] = split;
parents.set(parentHash, { modified: date, hash });
}
}
} else if (line) {
const relPath = path.relative(realContentRoot, path.join(repoRoot, line));
map.set(relPath, { modified: date, hash });
}
}
return [map, parents];
}
export function gather(contentRoots, previousFile = null) {
const map = new Map();
if (previousFile) {
const previous = JSON.parse(fs.readFileSync(previousFile, "utf-8"));
for (const [key, value] of Object.entries(previous)) {
map.set(key, value);
}
}
// Every key in this map is a path, relative to root.
for (const contentRoot of contentRoots) {
const [commits, parents] = getFromGit(contentRoot);
for (const [key, value] of commits) {
// Because CONTENT_*_ROOT isn't necessarily the same as the path relative to
// the git root. For example "../README.md" and since those aren't documents
// exclude them.
// We also only care about documents.
if (
!key.startsWith(".") &&
(key.endsWith("index.html") || key.endsWith("index.md"))
) {
map.set(key, Object.assign(value, { merged: parents.get(value.hash) }));
}
}
}
return map;
}