Skip to content

Commit ce56a06

Browse files
authored
Merge pull request #638 from dotnet/fix637
Always generate commit ID component of version as big endian
2 parents bf2ade8 + de6ca29 commit ce56a06

File tree

6 files changed

+49
-51
lines changed

6 files changed

+49
-51
lines changed

src/NerdBank.GitVersioning.Tests/GitExtensionsTests.cs renamed to src/NerdBank.GitVersioning.Tests/LibGit2GitExtensionsTests.cs

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Buffers.Binary;
23
using System.Collections.Generic;
34
using System.Diagnostics;
45
using System.IO;
@@ -11,9 +12,9 @@
1112
using Xunit.Abstractions;
1213
using Version = System.Version;
1314

14-
public partial class GitExtensionsTests : RepoTestBase
15+
public class LibGit2GitExtensionsTests : RepoTestBase
1516
{
16-
public GitExtensionsTests(ITestOutputHelper Logger)
17+
public LibGit2GitExtensionsTests(ITestOutputHelper Logger)
1718
: base(Logger)
1819
{
1920
this.InitializeSourceControl();
@@ -166,22 +167,6 @@ public void GetVersionHeight_ProgressAndReset(string version1, string version2,
166167
Assert.Equal(!versionHeightReset, height2 > height1);
167168
}
168169

169-
[Fact]
170-
public void GetTruncatedCommitIdAsInteger_Roundtrip()
171-
{
172-
var firstCommit = this.LibGit2Repository.Commit("First", this.Signer, this.Signer, new CommitOptions { AllowEmptyCommit = true });
173-
var secondCommit = this.LibGit2Repository.Commit("Second", this.Signer, this.Signer, new CommitOptions { AllowEmptyCommit = true });
174-
175-
int id1 = firstCommit.GetTruncatedCommitIdAsInt32();
176-
int id2 = secondCommit.GetTruncatedCommitIdAsInt32();
177-
178-
this.Logger.WriteLine($"Commit {firstCommit.Id.Sha.Substring(0, 8)} as int: {id1}");
179-
this.Logger.WriteLine($"Commit {secondCommit.Id.Sha.Substring(0, 8)} as int: {id2}");
180-
181-
Assert.Equal(firstCommit, this.LibGit2Repository.GetCommitFromTruncatedIdInteger(id1));
182-
Assert.Equal(secondCommit, this.LibGit2Repository.GetCommitFromTruncatedIdInteger(id2));
183-
}
184-
185170
[Fact]
186171
public void GetIdAsVersion_ReadsMajorMinorFromVersionTxt()
187172
{
@@ -384,6 +369,19 @@ public void GetIdAsVersion_Roundtrip_UnstableOffset(int startingOffset, int offs
384369
}
385370
}
386371

372+
[Fact]
373+
public void GetCommitsFromVersion_MatchesOnEitherEndian()
374+
{
375+
this.InitializeSourceControl();
376+
Commit commit = this.WriteVersionFile(new VersionOptions { Version = SemanticVersion.Parse("1.2"), GitCommitIdShortAutoMinimum = 4 });
377+
378+
Version originalVersion = new VersionOracle(this.Context).Version;
379+
Version swappedEndian = new Version(originalVersion.Major, originalVersion.Minor, originalVersion.Build, BinaryPrimitives.ReverseEndianness((ushort)originalVersion.Revision));
380+
ushort twoBytesFromCommitId = checked((ushort)originalVersion.Revision);
381+
Assert.Contains(commit, LibGit2GitExtensions.GetCommitsFromVersion(this.Context, originalVersion));
382+
Assert.Contains(commit, LibGit2GitExtensions.GetCommitsFromVersion(this.Context, swappedEndian));
383+
}
384+
387385
[Fact]
388386
public void GetIdAsVersion_Roundtrip_WithSubdirectoryVersionFiles()
389387
{

src/NerdBank.GitVersioning.Tests/ManagedGit/GitObjectIdTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ public void AsUInt16Test()
9393
{
9494
// The hash code is the int32 representation of the first 4 bytes
9595
var objectId = GitObjectId.ParseHex(this.shaAsHexAsciiByteArray);
96-
Assert.Equal(0x914e, objectId.AsUInt16());
96+
Assert.Equal(0x4e91, objectId.AsUInt16());
9797
Assert.Equal(0, GitObjectId.Empty.GetHashCode());
9898
}
9999

src/NerdBank.GitVersioning.Tests/TestUtilities.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ internal static ExpandedRepo ExtractRepoArchive(string repoArchiveName)
7979
}
8080
}
8181

82+
internal static string ToHex(ushort number) => number.ToString("X");
83+
84+
internal static ushort FromHex(string hex) => ushort.Parse(hex, System.Globalization.NumberStyles.HexNumber);
85+
8286
internal class ExpandedRepo : IDisposable
8387
{
8488
internal ExpandedRepo(string repoPath)

src/NerdBank.GitVersioning.Tests/VersionOracleTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,6 +845,23 @@ public void GitCommitIdShort()
845845
}
846846
}
847847

848+
[Fact]
849+
public void GitCommidIdLeading16BitsDecodedWithBigEndian()
850+
{
851+
this.WriteVersionFile(new VersionOptions { Version = SemanticVersion.Parse("1.2"), GitCommitIdShortAutoMinimum = 4 });
852+
this.InitializeSourceControl();
853+
this.AddCommits(1);
854+
var oracle = new VersionOracle(this.Context);
855+
856+
string leadingFourChars = this.Context.GitCommitId.Substring(0, 4);
857+
ushort expectedNumber = TestUtilities.FromHex(leadingFourChars);
858+
ushort actualNumber = checked((ushort)oracle.Version.Revision);
859+
this.Logger.WriteLine("First two characters from commit ID in hex is {0}", leadingFourChars);
860+
this.Logger.WriteLine("First two characters, converted to a number is {0}", expectedNumber);
861+
this.Logger.WriteLine("Generated 16-bit ushort from commit ID is {0}, whose hex representation is {1}", actualNumber, TestUtilities.ToHex(actualNumber));
862+
Assert.Equal(expectedNumber, actualNumber);
863+
}
864+
848865
[Fact(Skip = "Slow test")]
849866
public void GetVersionHeight_VeryLongHistory()
850867
{

src/NerdBank.GitVersioning/LibGit2/LibGit2GitExtensions.cs

Lines changed: 10 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
namespace Nerdbank.GitVersioning.LibGit2
44
{
55
using System;
6+
using System.Buffers.Binary;
67
using System.Collections.Generic;
7-
using System.Globalization;
88
using System.IO;
99
using System.Linq;
1010
using System.Runtime.InteropServices;
@@ -97,18 +97,6 @@ public static int GetHeight(LibGit2Context context, Func<Commit, bool>? continue
9797
return GetCommitHeight(context.Commit, tracker, continueStepping);
9898
}
9999

100-
/// <summary>
101-
/// Takes the first 4 bytes of a commit ID (i.e. first 8 characters of its hex-encoded SHA)
102-
/// and returns them as an integer.
103-
/// </summary>
104-
/// <param name="commit">The commit to identify with an integer.</param>
105-
/// <returns>The integer which identifies a commit.</returns>
106-
public static int GetTruncatedCommitIdAsInt32(this Commit commit)
107-
{
108-
Requires.NotNull(commit, nameof(commit));
109-
return BitConverter.ToInt32(commit.Id.RawId, 0);
110-
}
111-
112100
/// <summary>
113101
/// Takes the first 2 bytes of a commit ID (i.e. first 4 characters of its hex-encoded SHA)
114102
/// and returns them as an 16-bit unsigned integer.
@@ -118,21 +106,7 @@ public static int GetTruncatedCommitIdAsInt32(this Commit commit)
118106
public static ushort GetTruncatedCommitIdAsUInt16(this Commit commit)
119107
{
120108
Requires.NotNull(commit, nameof(commit));
121-
return BitConverter.ToUInt16(commit.Id.RawId, 0);
122-
}
123-
124-
/// <summary>
125-
/// Looks up a commit by an integer that captures the first for bytes of its ID.
126-
/// </summary>
127-
/// <param name="repo">The repo to search for a matching commit.</param>
128-
/// <param name="truncatedId">The value returned from <see cref="GetTruncatedCommitIdAsInt32(Commit)"/>.</param>
129-
/// <returns>A matching commit.</returns>
130-
public static Commit GetCommitFromTruncatedIdInteger(this Repository repo, int truncatedId)
131-
{
132-
Requires.NotNull(repo, nameof(repo));
133-
134-
byte[] rawId = BitConverter.GetBytes(truncatedId);
135-
return repo.Lookup<Commit>(EncodeAsHex(rawId));
109+
return BinaryPrimitives.ReadUInt16BigEndian(commit.Id.RawId);
136110
}
137111

138112
/// <summary>
@@ -310,7 +284,11 @@ private static bool IsCommitIdMismatch(Version version, VersionOptions versionOp
310284
ushort objectIdLeadingValue = (ushort)expectedCommitIdLeadingValue;
311285
ushort objectIdMask = (ushort)(objectIdLeadingValue == MaximumBuildNumberOrRevisionComponent ? 0xfffe : 0xffff);
312286

313-
return !commit.Id.StartsWith(objectIdLeadingValue, objectIdMask);
287+
// Accept a big endian match or a little endian match.
288+
// Nerdbank.GitVersioning up to v3.4 would produce versions based on the endianness of the CPU it ran on (typically little endian).
289+
// Starting with v3.5, it deterministically used big endian. In order for `nbgv get-commits` to match on versions computed before and after the change,
290+
// we match on either endian setting.
291+
return !(commit.Id.StartsWith(objectIdLeadingValue, bigEndian: true, objectIdMask) || commit.Id.StartsWith(objectIdLeadingValue, bigEndian: false, objectIdMask));
314292
}
315293
}
316294

@@ -324,10 +302,11 @@ private static bool IsCommitIdMismatch(Version version, VersionOptions versionOp
324302
/// <param name="object">The object whose ID is to be tested.</param>
325303
/// <param name="leadingBytes">The leading 16-bits to be tested.</param>
326304
/// <param name="bitMask">The mask that indicates which bits should be compared.</param>
305+
/// <param name="bigEndian"><see langword="true"/> to read the first two bytes as big endian (v3.5+ behavior); <see langword="false"/> to use little endian (v3.4 and earlier behavior).</param>
327306
/// <returns><c>True</c> if the object's ID starts with <paramref name="leadingBytes"/> after applying the <paramref name="bitMask"/>.</returns>
328-
private static bool StartsWith(this ObjectId @object, ushort leadingBytes, ushort bitMask = 0xffff)
307+
private static bool StartsWith(this ObjectId @object, ushort leadingBytes, bool bigEndian, ushort bitMask = 0xffff)
329308
{
330-
ushort truncatedObjectId = BitConverter.ToUInt16(@object.RawId, 0);
309+
ushort truncatedObjectId = bigEndian ? BinaryPrimitives.ReadUInt16BigEndian(@object.RawId) : BinaryPrimitives.ReadUInt16LittleEndian(@object.RawId);
331310
return (truncatedObjectId & bitMask) == leadingBytes;
332311
}
333312

src/NerdBank.GitVersioning/ManagedGit/GitObjectId.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ public override bool Equals(object? obj)
167167
/// <returns>
168168
/// A <see cref="ushort"/> which represents the first two bytes of this <see cref="GitObjectId"/>.
169169
/// </returns>
170-
public ushort AsUInt16() => BinaryPrimitives.ReadUInt16LittleEndian(this.Value.Slice(0, 2));
170+
public ushort AsUInt16() => BinaryPrimitives.ReadUInt16BigEndian(this.Value.Slice(0, 2));
171171

172172
/// <summary>
173173
/// Returns the SHA1 hash of this object.

0 commit comments

Comments
 (0)