Skip to content

Commit 77fe64b

Browse files
committed
Fix handling of odd-length partial commit IDs
1 parent b5f1fc5 commit 77fe64b

File tree

5 files changed

+33
-12
lines changed

5 files changed

+33
-12
lines changed

src/NerdBank.GitVersioning.Tests/GitContextTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public void SelectCommitByPartialId(bool fromPack, bool oddLength)
8181
this.Context = this.CreateGitContext(this.RepoPath, null);
8282
}
8383

84-
Assert.True(this.Context.TrySelectCommit(this.Context.GitCommitId.Substring(0, oddLength ? 11 : 13)));
84+
Assert.True(this.Context.TrySelectCommit(this.Context.GitCommitId.Substring(0, oddLength ? 11 : 12)));
8585
Assert.Equal(this.LibGit2Repository.Head.Tip.Sha, this.Context.GitCommitId);
8686
}
8787

@@ -92,7 +92,7 @@ public void SelectCommitByPartialId(bool fromPack, bool oddLength)
9292
[InlineData(11)]
9393
public void GetShortUniqueCommitId(int length)
9494
{
95-
Skip.If(length < 7 && this.Context is Nerdbank.GitVersioning.LibGit2.LibGit2Context);
95+
Skip.If(length < 7 && this.Context is Nerdbank.GitVersioning.LibGit2.LibGit2Context, "LibGit2Sharp never returns commit IDs with fewer than 7 characters.");
9696
Assert.Equal(this.Context.GitCommitId.Substring(0, length), this.Context.GetShortUniqueCommitId(length));
9797
}
9898

src/NerdBank.GitVersioning/ManagedGit/GitPack.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,13 +130,14 @@ public GitPack(GetObjectFromRepositoryDelegate getObjectFromRepositoryDelegate,
130130
/// <param name="objectId">
131131
/// A partial object ID.
132132
/// </param>
133+
/// <param name="endsWithHalfByte"><inheritdoc cref="GitPackIndexReader.GetOffset(Span{byte}, bool)" path="/param[@name='endsWithHalfByte']"/></param>
133134
/// <returns>
134135
/// If found, a full object ID which matches the partial object ID.
135136
/// Otherwise, <see langword="false"/>.
136137
/// </returns>
137-
public GitObjectId? Lookup(Span<byte> objectId)
138+
public GitObjectId? Lookup(Span<byte> objectId, bool endsWithHalfByte = false)
138139
{
139-
(var _, var actualObjectId) = this.indexReader.Value.GetOffset(objectId);
140+
(var _, var actualObjectId) = this.indexReader.Value.GetOffset(objectId, endsWithHalfByte);
140141
return actualObjectId;
141142
}
142143

src/NerdBank.GitVersioning/ManagedGit/GitPackIndexMappedReader.cs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,21 +42,19 @@ public GitPackIndexMappedReader(FileStream stream)
4242
this.accessor.SafeMemoryMappedViewHandle.AcquirePointer(ref this.ptr);
4343
}
4444

45-
private Span<byte> Value
45+
private ReadOnlySpan<byte> Value
4646
{
4747
get
4848
{
49-
return new Span<byte>(this.ptr, (int)this.accessor.Capacity);
49+
return new ReadOnlySpan<byte>(this.ptr, (int)this.accessor.Capacity);
5050
}
5151
}
5252

5353
/// <inheritdoc/>
54-
public override (long?, GitObjectId?) GetOffset(Span<byte> objectName)
54+
public override (long?, GitObjectId?) GetOffset(Span<byte> objectName, bool endsWithHalfByte = false)
5555
{
5656
this.Initialize();
5757

58-
Span<byte> buffer = stackalloc byte[4];
59-
6058
var packStart = this.fanoutTable[objectName[0]];
6159
var packEnd = this.fanoutTable[objectName[0] + 1];
6260
var objectCount = this.fanoutTable[256];
@@ -82,7 +80,20 @@ public override (long?, GitObjectId?) GetOffset(Span<byte> objectName)
8280
{
8381
i = (packStart + packEnd) / 2;
8482

85-
order = table.Slice(20 * i, objectName.Length).SequenceCompareTo(objectName);
83+
ReadOnlySpan<byte> comparand = table.Slice(20 * i, objectName.Length);
84+
if (endsWithHalfByte)
85+
{
86+
// Copy out the value to be checked so we can zero out the last four bits,
87+
// so that it matches the last 4 bits of the objectName that isn't supposed to be compared.
88+
Span<byte> buffer = stackalloc byte[20];
89+
comparand.CopyTo(buffer);
90+
buffer[objectName.Length - 1] &= 0xf0;
91+
order = buffer.Slice(0, objectName.Length).SequenceCompareTo(objectName);
92+
}
93+
else
94+
{
95+
order = comparand.SequenceCompareTo(objectName);
96+
}
8697

8798
if (order < 0)
8899
{

src/NerdBank.GitVersioning/ManagedGit/GitPackIndexReader.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ public abstract class GitPackIndexReader : IDisposable
3737
/// <param name="objectId">
3838
/// A partial or full Git object id, in its binary representation.
3939
/// </param>
40+
/// <param name="endsWithHalfByte"><see langword="true"/> if <paramref name="objectId"/> ends with a byte whose last 4 bits are all zeros and not intended for inclusion in the search; <see langword="false"/> otherwise.</param>
4041
/// <returns>
4142
/// If found, the offset of the Git object in the index file; otherwise,
4243
/// <see langword="null"/>.
4344
/// </returns>
44-
public abstract (long?, GitObjectId?) GetOffset(Span<byte> objectId);
45+
public abstract (long?, GitObjectId?) GetOffset(Span<byte> objectId, bool endsWithHalfByte = false);
4546

4647
/// <inheritdoc/>
4748
public abstract void Dispose();

src/NerdBank.GitVersioning/ManagedGit/GitRepository.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,11 +403,19 @@ public GitCommit GetCommit(GitObjectId sha, bool readAuthor = false)
403403
}
404404

405405
// Search for _any_ object whose id starts with objectish in the packfile
406+
bool endsWithHalfByte = objectish.Length % 2 == 1;
407+
if (endsWithHalfByte)
408+
{
409+
// Add one more character so hex can be converted to bytes.
410+
// The bit length to be compared will not consider the last four bits.
411+
objectish += "0";
412+
}
413+
406414
var hex = ConvertHexStringToByteArray(objectish);
407415

408416
foreach (var pack in this.packs.Value)
409417
{
410-
var objectId = pack.Lookup(hex);
418+
var objectId = pack.Lookup(hex, endsWithHalfByte);
411419

412420
// It's possible for the same object to be present in both the object database and the pack files,
413421
// or in multiple pack files.

0 commit comments

Comments
 (0)