Skip to content

Commit 1902982

Browse files
authored
Merge pull request #613 from filipnavara/head-in-packed-refs
Fix resolving HEAD reference if it's a packed ref
2 parents 07964f8 + 311f477 commit 1902982

File tree

3 files changed

+75
-53
lines changed

3 files changed

+75
-53
lines changed

src/NerdBank.GitVersioning.Tests/GitContextTests.cs

+19
Original file line numberDiff line numberDiff line change
@@ -162,4 +162,23 @@ public void SelectDirectory_SubDir()
162162
Assert.Equal("sub", this.Context.RepoRelativeProjectDirectory);
163163
Assert.Equal(absolutePath, this.Context.AbsoluteProjectDirectory);
164164
}
165+
166+
[Fact]
167+
public void GetVersion_PackedHead()
168+
{
169+
using var expandedRepo = TestUtilities.ExtractRepoArchive("PackedHeadRef");
170+
this.Context = this.CreateGitContext(Path.Combine(expandedRepo.RepoPath));
171+
var oracle = new VersionOracle(this.Context);
172+
Assert.Equal("1.0.1", oracle.SimpleVersion.ToString());
173+
this.Context.TrySelectCommit("HEAD");
174+
Assert.Equal("1.0.1", oracle.SimpleVersion.ToString());
175+
}
176+
177+
[Fact]
178+
public void HeadCanonicalName_PackedHead()
179+
{
180+
using var expandedRepo = TestUtilities.ExtractRepoArchive("PackedHeadRef");
181+
this.Context = this.CreateGitContext(Path.Combine(expandedRepo.RepoPath));
182+
Assert.Equal("refs/heads/main", this.Context.HeadCanonicalName);
183+
}
165184
}
Binary file not shown.

src/NerdBank.GitVersioning/ManagedGit/GitRepository.cs

+56-53
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Linq;
99
using System.Runtime.InteropServices;
1010
using System.Text;
11+
using Validation;
1112

1213
namespace Nerdbank.GitVersioning.ManagedGit
1314
{
@@ -222,9 +223,7 @@ public object GetHeadAsReferenceOrSha()
222223
/// </returns>
223224
public GitObjectId GetHeadCommitSha()
224225
{
225-
var reference = this.GetHeadAsReferenceOrSha();
226-
var objectId = this.ResolveReference(reference);
227-
return objectId;
226+
return this.Lookup("HEAD") ?? GitObjectId.Empty;
228227
}
229228

230229
/// <summary>
@@ -280,16 +279,25 @@ public GitCommit GetCommit(GitObjectId sha, bool readAuthor = false)
280279
/// <returns>The object ID referenced by <paramref name="objectish"/> if found; otherwise <see langword="null"/>.</returns>
281280
public GitObjectId? Lookup(string objectish)
282281
{
282+
bool skipObjectIdLookup = false;
283+
283284
if (objectish == "HEAD")
284285
{
285-
return this.GetHeadCommitSha();
286+
var reference = this.GetHeadAsReferenceOrSha();
287+
if (reference is GitObjectId headObjectId)
288+
{
289+
return headObjectId;
290+
}
291+
292+
objectish = (string)reference;
286293
}
287294

288295
var possibleLooseFileMatches = new List<string>();
289296
if (objectish.StartsWith("refs/", StringComparison.Ordinal))
290297
{
291298
// Match on loose ref files by their canonical name.
292-
possibleLooseFileMatches.Add(Path.Combine(this.GitDirectory, objectish));
299+
possibleLooseFileMatches.Add(Path.Combine(this.CommonDirectory, objectish));
300+
skipObjectIdLookup = true;
293301
}
294302
else
295303
{
@@ -341,6 +349,11 @@ public GitCommit GetCommit(GitObjectId sha, bool readAuthor = false)
341349
}
342350
}
343351

352+
if (skipObjectIdLookup)
353+
{
354+
return null;
355+
}
356+
344357
if (objectish.Length == 40)
345358
{
346359
return GitObjectId.Parse(objectish);
@@ -372,25 +385,30 @@ public GitCommit GetCommit(GitObjectId sha, bool readAuthor = false)
372385
objectish += "0";
373386
}
374387

375-
var hex = ConvertHexStringToByteArray(objectish);
376-
377-
foreach (var pack in this.packs.Value.Span)
388+
if (objectish.Length <= 40 && objectish.Length % 2 == 0)
378389
{
379-
var objectId = pack.Lookup(hex, endsWithHalfByte);
380-
381-
// It's possible for the same object to be present in both the object database and the pack files,
382-
// or in multiple pack files.
383-
if (objectId != null && !possibleObjectIds.Contains(objectId.Value))
390+
Span<byte> decodedHex = stackalloc byte[objectish.Length / 2];
391+
if (TryConvertHexStringToByteArray(objectish, decodedHex))
384392
{
385-
if (possibleObjectIds.Count > 0)
386-
{
387-
// If objectish already resolved to at least one object which is different from the current
388-
// object id, objectish is not well-defined; so stop resolving and return null instead.
389-
return null;
390-
}
391-
else
393+
foreach (var pack in this.packs.Value.Span)
392394
{
393-
possibleObjectIds.Add(objectId.Value);
395+
var objectId = pack.Lookup(decodedHex, endsWithHalfByte);
396+
397+
// It's possible for the same object to be present in both the object database and the pack files,
398+
// or in multiple pack files.
399+
if (objectId != null && !possibleObjectIds.Contains(objectId.Value))
400+
{
401+
if (possibleObjectIds.Count > 0)
402+
{
403+
// If objectish already resolved to at least one object which is different from the current
404+
// object id, objectish is not well-defined; so stop resolving and return null instead.
405+
return null;
406+
}
407+
else
408+
{
409+
possibleObjectIds.Add(objectId.Value);
410+
}
411+
}
394412
}
395413
}
396414
}
@@ -610,33 +628,6 @@ private bool TryGetObjectByPath(GitObjectId sha, string objectType, [NotNullWhen
610628
return true;
611629
}
612630

613-
private GitObjectId ResolveReference(object reference)
614-
{
615-
if (reference is string)
616-
{
617-
if (!FileHelpers.TryOpen(Path.Combine(this.CommonDirectory, (string)reference), out FileStream? stream))
618-
{
619-
return GitObjectId.Empty;
620-
}
621-
622-
using (stream)
623-
{
624-
Span<byte> objectId = stackalloc byte[40];
625-
stream!.Read(objectId);
626-
627-
return GitObjectId.ParseHex(objectId);
628-
}
629-
}
630-
else if (reference is GitObjectId)
631-
{
632-
return (GitObjectId)reference;
633-
}
634-
else
635-
{
636-
throw new GitException();
637-
}
638-
}
639-
640631
private ReadOnlyMemory<GitPack> LoadPacks()
641632
{
642633
var packDirectory = Path.Combine(this.ObjectDirectory, "pack/");
@@ -687,22 +678,34 @@ private static string TrimEndingDirectorySeparator(string path)
687678
#endif
688679
}
689680

690-
private static byte[] ConvertHexStringToByteArray(string hexString)
681+
private static bool TryConvertHexStringToByteArray(string hexString, Span<byte> data)
691682
{
692683
// https://stackoverflow.com/questions/321370/how-can-i-convert-a-hex-string-to-a-byte-array
693684
if (hexString.Length % 2 != 0)
694685
{
695-
throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "The binary key cannot have an odd number of digits: {0}", hexString));
686+
data = null;
687+
return false;
696688
}
697689

698-
byte[] data = new byte[hexString.Length / 2];
690+
Requires.Argument(data.Length == hexString.Length / 2, nameof(data), "Length must be exactly half that of " + nameof(hexString) + ".");
699691
for (int index = 0; index < data.Length; index++)
700692
{
693+
#if NETCOREAPP3_1_OR_GREATER
694+
ReadOnlySpan<char> byteValue = hexString.AsSpan(index * 2, 2);
695+
if (!byte.TryParse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out data[index]))
696+
{
697+
return false;
698+
}
699+
#else
701700
string byteValue = hexString.Substring(index * 2, 2);
702-
data[index] = byte.Parse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture);
701+
if (!byte.TryParse(byteValue, NumberStyles.HexNumber, CultureInfo.InvariantCulture, out data[index]))
702+
{
703+
return false;
704+
}
705+
#endif
703706
}
704707

705-
return data;
708+
return true;
706709
}
707710

708711
/// <summary>

0 commit comments

Comments
 (0)