-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathrepo.d
159 lines (139 loc) · 3.58 KB
/
repo.d
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
module repo;
import std.algorithm;
import std.conv;
import std.exception;
import std.file;
import std.format;
import std.path;
import std.process;
import std.range;
import std.string;
import ae.utils.text;
class Repository
{
string path;
string[] argsPrefix;
this(string path)
{
enforce(path.exists, "Repository path does not exist");
this.path = path;
auto dotGit = path.buildPath(".git");
string[] workTreeArg = [`--work-tree=` ~ path];
if (!dotGit.exists && ["branches", "hooks", "info", "objects", "HEAD", "config"].all!(n => path.buildPath(n).exists)) // bare repository
{
dotGit = path;
workTreeArg = null;
}
else
if (dotGit.exists && dotGit.isFile) // submodule etc.
dotGit = path.buildPath(dotGit.readText().strip()[8..$]);
//path = path.replace(`\`, `/`);
this.argsPrefix = [`git`] ~ workTreeArg ~ [`--git-dir=` ~ dotGit];
}
void gitRun(string[] args...)
{
auto status = spawnProcess(argsPrefix ~ args).wait();
enforce(status == 0, "Git command %s failed with status %d".format(args, status));
}
string gitQuery(string[] args...)
{
auto result = execute(argsPrefix ~ args);
enforce(result.status == 0, "Git command %s failed with status %d".format(args, result.status));
return result.output;
}
// --------------------------------------------------------------------------------------------------------------
static struct History
{
Commit*[Hash] commits;
uint numCommits = 0;
Hash[string] refs;
}
History getHistory()
{
History history;
Commit* getCommit(Hash hash)
{
auto pcommit = hash in history.commits;
return pcommit ? *pcommit : (history.commits[hash] = new Commit(history.numCommits++, hash));
}
Commit* commit;
foreach (line; gitQuery([`log`, `--all`, `--pretty=raw`]).split("\n"))
{
if (!line.length)
continue;
if (line.startsWith("commit "))
{
auto hash = line[7..$].toCommitHash();
commit = getCommit(hash);
}
else
if (line.startsWith("tree "))
continue;
else
if (line.startsWith("parent "))
{
auto hash = line[7..$].toCommitHash();
auto parent = getCommit(hash);
commit.parents ~= parent;
parent.children ~= commit;
}
else
if (line.startsWith("author "))
commit.author = line[7..$];
else
if (line.startsWith("committer "))
{
commit.committer = line[10..$];
commit.time = line.split(" ")[$-2].to!int();
}
else
if (line.startsWith(" "))
commit.message ~= line[4..$];
else
if (line.startsWith("gpgsig ") || line.startsWith("mergetag "))
continue;
else
if (line.startsWith(" "))
continue; // continuation of gpgsig
else
enforce(false, "Unknown line in git log: %(%s%)".format([line]));
// commit.message[$-1] ~= line;
}
foreach (line; gitQuery([`show-ref`, `--dereference`]).splitLines())
{
auto h = line[0..40].toCommitHash();
if (h in history.commits)
history.refs[line[41..$]] = h;
}
return history;
}
}
alias ubyte[20] Hash;
struct Commit
{
uint id;
Hash hash;
uint time;
string author, committer;
string[] message;
Commit*[] parents, children;
}
Hash toCommitHash(string hash)
{
enforce(hash.length == 40, "Bad hash length");
ubyte[20] result;
foreach (i, ref b; result)
b = to!ubyte(hash[i*2..i*2+2], 16);
return result;
}
char[40] toString(in ref Hash hash)
{
//return format("%(%02x%)", hash[]);
char[40] result;
hash[].toLowerHex(result);
return result;
}
unittest
{
assert(toCommitHash("0123456789abcdef0123456789ABCDEF01234567") == [0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef, 0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67]);
}