forked from kekyo/GitReader
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathRepositoryFacade.cs
345 lines (306 loc) · 11.9 KB
/
RepositoryFacade.cs
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
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
////////////////////////////////////////////////////////////////////////////
//
// GitReader - Lightweight Git local repository traversal library.
// Copyright (c) Kouji Matsui (@kozy_kekyo, @[email protected])
//
// Licensed under Apache-v2: https://opensource.org/licenses/Apache-2.0
//
////////////////////////////////////////////////////////////////////////////
using GitReader.Collections;
using GitReader.Internal;
using GitReader.Primitive;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace GitReader.Structures;
internal static class RepositoryFacade
{
private static async Task<Branch?> GetCurrentHeadAsync(
StructuredRepository repository,
CancellationToken ct)
{
if (await RepositoryAccessor.ReadHashAsync(
repository, "HEAD", ct) is { } results)
{
var commit = await RepositoryAccessor.ReadCommitAsync(
repository, results.Hash, ct);
return commit is { } c ?
new(results.Names.Last(), new(new(repository), c)) :
throw new InvalidDataException(
$"Could not find a commit: {results.Hash}");
}
else
{
return null;
}
}
//////////////////////////////////////////////////////////////////////////
private static async Task<ReadOnlyDictionary<string, Branch>> GetStructuredBranchesAsync(
StructuredRepository repository,
WeakReference rwr,
CancellationToken ct)
{
Debug.Assert(object.ReferenceEquals(rwr.Target, repository));
var references = await RepositoryAccessor.ReadReferencesAsync(
repository, ReferenceTypes.Branches, ct);
var entries = await Utilities.WhenAll(
references.Select(async reference =>
new
{
Name = reference.Name,
Head = await RepositoryAccessor.ReadCommitAsync(
repository, reference.Target, ct)
}));
return entries.
Where(entry => entry.Head.HasValue).
ToDictionary(
entry => entry.Name,
entry => new Branch(
entry.Name,
new Commit(rwr, entry.Head!.Value)));
}
private static async Task<ReadOnlyDictionary<string, Branch>> GetStructuredRemoteBranchesAsync(
StructuredRepository repository,
WeakReference rwr,
CancellationToken ct)
{
Debug.Assert(object.ReferenceEquals(rwr.Target, repository));
var references = await RepositoryAccessor.ReadReferencesAsync(
repository, ReferenceTypes.RemoteBranches, ct);
var entries = await Utilities.WhenAll(
references.Select(async reference =>
new
{
Name = reference.Name,
Head = await RepositoryAccessor.ReadCommitAsync(
repository, reference.Target, ct)
}));
return entries.
Where(entry => entry.Head.HasValue).
ToDictionary(
entry => entry.Name,
entry => new Branch(
entry.Name,
new Commit(rwr, entry.Head!.Value)));
}
private static async Task<ReadOnlyDictionary<string, Tag>> GetStructuredTagsAsync(
StructuredRepository repository,
WeakReference rwr,
CancellationToken ct)
{
var references = await RepositoryAccessor.ReadReferencesAsync(
repository, ReferenceTypes.Tags, ct);
var tags = await Utilities.WhenAll(
references.Select(async reference =>
{
// Tag object is read.
if (await RepositoryAccessor.ReadTagAsync(
repository, reference.Target, ct) is { } tag)
{
// TODO: Currently does not support any other tag types.
if (tag.Type == ObjectTypes.Commit)
{
// Target commit object is read.
if (await RepositoryAccessor.ReadCommitAsync(
repository, tag.Hash, ct) is { } commit)
{
return (Tag)new CommitTag(
tag.Hash, tag.Name, tag.Tagger, tag.Message,
new(rwr, commit));
}
}
}
else
{
// Target commit object is read.
if (await RepositoryAccessor.ReadCommitAsync(
repository, reference.Target, ct) is { } commit)
{
return (Tag)new CommitTag(
reference.Target, reference.Name, null, null,
new(rwr, commit));
}
}
return null;
}));
return tags.
Where(tag => tag != null).
DistinctBy(tag => tag!.Name).
ToDictionary(tag => tag!.Name, tag => tag!);
}
//////////////////////////////////////////////////////////////////////////
public static async Task<StructuredRepository> OpenStructuredAsync(
string path, CancellationToken ct)
{
var repositoryPath = Path.GetFileName(path) != ".git" ?
Utilities.Combine(path, ".git") : path;
if (!Directory.Exists(repositoryPath))
{
throw new ArgumentException("Repository does not exist.");
}
var repository = new StructuredRepository(repositoryPath);
try
{
// Read remote references from config file.
repository.remoteUrls =
await RepositoryAccessor.ReadRemoteReferencesAsync(repository, ct);
// Read FETCH_HEAD and packed-refs.
var (fhc1, fhc2) = await Utilities.Join(
RepositoryAccessor.ReadFetchHeadsAsync(repository, ct),
RepositoryAccessor.ReadPackedRefsAsync(repository, ct));
repository.referenceCache = fhc1.Combine(fhc2);
// Read all other requirements.
var rwr = new WeakReference(repository);
var (head, branches, remoteBranches, tags) = await Utilities.Join(
GetCurrentHeadAsync(repository, ct),
GetStructuredBranchesAsync(repository, rwr, ct),
GetStructuredRemoteBranchesAsync(repository, rwr, ct),
GetStructuredTagsAsync(repository, rwr, ct));
repository.head = head;
repository.branches = branches;
repository.remoteBranches = remoteBranches;
repository.tags = tags;
return repository;
}
catch
{
repository.Dispose();
throw;
}
}
//////////////////////////////////////////////////////////////////////////
public static async Task<Commit?> GetCommitDirectlyAsync(
StructuredRepository repository,
Hash hash,
CancellationToken ct)
{
var commit = await RepositoryAccessor.ReadCommitAsync(
repository, hash, ct);
return commit is { } c ?
new(new(repository), c) : null;
}
private static StructuredRepository GetRelatedRepository(
Commit commit)
{
if (commit.rwr.Target is not StructuredRepository repository ||
repository.objectAccessor == null)
{
throw new InvalidOperationException(
"The repository already discarded.");
}
return repository;
}
private static StructuredRepository GetRelatedRepository(
TreeBlobEntry entry)
{
if (entry.rwr.Target is not StructuredRepository repository ||
repository.objectAccessor == null)
{
throw new InvalidOperationException(
"The repository already discarded.");
}
return repository;
}
public static async Task<Commit?> GetPrimaryParentAsync(
Commit commit,
CancellationToken ct)
{
if (commit.parents.Length == 0)
{
return null;
}
var repository = GetRelatedRepository(commit);
var pc = await RepositoryAccessor.ReadCommitAsync(
repository, commit.parents[0], ct);
return pc is { } ?
new(commit.rwr, pc!.Value) :
throw new InvalidDataException(
$"Could not find a commit: {commit.parents[0]}");
}
public static Task<Commit[]> GetParentsAsync(
Commit commit,
CancellationToken ct)
{
var repository = GetRelatedRepository(commit);
return Utilities.WhenAll(
commit.parents.Select(async parent =>
{
var pc = await RepositoryAccessor.ReadCommitAsync(
repository, parent, ct);
return pc is { } ?
new Commit(commit.rwr, pc!.Value) :
throw new InvalidDataException(
$"Could not find a commit: {parent}");
}));
}
public static Branch[] GetRelatedBranches(Commit commit)
{
var repository = GetRelatedRepository(commit);
return repository.Branches.Values.
Collect(branch => branch.Head.Equals(commit) ? branch : null).
ToArray();
}
public static Branch[] GetRelatedRemoteBranches(Commit commit)
{
var repository = GetRelatedRepository(commit);
return repository.RemoteBranches.Values.
Collect(branch => branch.Head.Equals(commit) ? branch : null).
ToArray();
}
public static Tag[] GetRelatedTags(Commit commit)
{
var repository = GetRelatedRepository(commit);
return repository.Tags.Values.
Collect(tag =>
(tag is CommitTag &&
tag.Hash.Equals(commit.Hash)) ? tag : null).
ToArray();
}
public static async Task<TreeRoot> GetTreeAsync(
Commit commit,
CancellationToken ct)
{
var repository = GetRelatedRepository(commit);
var rootTree = await RepositoryAccessor.ReadTreeAsync(
repository, commit.treeRoot, ct);
// This is a rather aggressive algorithm that recursively and in parallel searches all entries
// in the tree and builds all elements.
async Task<TreeEntry[]> GetChildrenAsync(Primitive.PrimitiveTreeEntry[] entries) =>
(await Utilities.WhenAll(
entries.Collect(async entry =>
{
var modeFlags = (ModeFlags)((int)entry.Modes & 0x1ff);
switch (entry.SpecialModes)
{
case PrimitiveSpecialModes.Directory:
var tree = await RepositoryAccessor.ReadTreeAsync(
repository!, entry.Hash, ct);
var children = await GetChildrenAsync(tree.Children);
return (TreeEntry)new TreeDirectoryEntry(
entry.Hash, entry.Name, modeFlags, children);
case PrimitiveSpecialModes.Blob:
return new TreeBlobEntry(
entry.Hash, entry.Name, modeFlags, commit.rwr);
default:
return null!;
}
}))).
Where(entry => entry != null).
ToArray();
var children = await GetChildrenAsync(rootTree.Children);
return new(commit.Hash, children);
}
public static Task<Stream> OpenBlobAsync(
TreeBlobEntry entry,
CancellationToken ct)
{
var repository = GetRelatedRepository(entry);
return RepositoryAccessor.OpenBlobAsync(
repository, entry.Hash, ct);
}
}