Skip to content

Commit

Permalink
Merge pull request #596 from qmfrederik/features/alternates
Browse files Browse the repository at this point in the history
Improve alternate parsing
  • Loading branch information
AArnott committed Apr 30, 2021
2 parents e3477ca + c311a3f commit 2363be0
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 9 deletions.
32 changes: 32 additions & 0 deletions src/NerdBank.GitVersioning.Tests/ManagedGit/GitRepositoryTests.cs
Expand Up @@ -286,6 +286,38 @@ public void GetMissingObjectByShaTest()
}
}

[Fact]
public void ParseAlternates_SingleValue_Test()
{
var alternates = GitRepository.ParseAlternates(Encoding.UTF8.GetBytes("/home/git/nbgv/.git/objects\n"));
Assert.Collection(
alternates,
a => Assert.Equal("/home/git/nbgv/.git/objects", a));
}

[Fact]
public void ParseAlternates_TwoValues_Test()
{
var alternates = GitRepository.ParseAlternates(Encoding.UTF8.GetBytes("/home/git/nbgv/.git/objects:../../clone/.git/objects\n"));
Assert.Collection(
alternates,
a => Assert.Equal("/home/git/nbgv/.git/objects", a),
a => Assert.Equal("../../clone/.git/objects", a));
}

[Fact]
public void ParseAlternates_PathWithColon_Test()
{
var alternates = GitRepository.ParseAlternates(
Encoding.UTF8.GetBytes("C:/Users/nbgv/objects:C:/Users/nbgv2/objects/:../../clone/.git/objects\n"),
2);
Assert.Collection(
alternates,
a => Assert.Equal("C:/Users/nbgv/objects", a),
a => Assert.Equal("C:/Users/nbgv2/objects/", a),
a => Assert.Equal("../../clone/.git/objects", a));
}

private static void AssertPath(string expected, string actual)
{
Assert.Equal(
Expand Down
59 changes: 50 additions & 9 deletions src/NerdBank.GitVersioning/ManagedGit/GitRepository.cs
Expand Up @@ -6,6 +6,7 @@
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;

namespace Nerdbank.GitVersioning.ManagedGit
Expand Down Expand Up @@ -110,16 +111,14 @@ public GitRepository(string workingDirectory, string gitDirectory, string common
var length = alternateStream!.Read(alternates);
alternates = alternates.Slice(0, length);

int index = 0;

while ((index = alternates.IndexOf((byte)':')) > 0)
foreach (var alternate in ParseAlternates(alternates))
{
var alternate = GetString(alternates.Slice(0, index));
alternate = Path.GetFullPath(Path.Combine(this.ObjectDirectory, alternate));

this.alternates.Add(GitRepository.Create(workingDirectory, gitDirectory, commonDirectory, alternate));

alternates = alternates.Slice(index + 1);
this.alternates.Add(
GitRepository.Create(
workingDirectory,
gitDirectory,
commonDirectory,
objectDirectory: Path.GetFullPath(Path.Combine(this.ObjectDirectory, alternate))));
}
}

Expand Down Expand Up @@ -722,5 +721,47 @@ public static unsafe string GetString(ReadOnlySpan<byte> bytes)
return Encoding.GetString(pBytes, bytes.Length);
}
}

/// <summary>
/// Parses the contents of the alternates file, and returns a list of (relative) paths to the alternate object directories.
/// </summary>
/// <param name="alternates">
/// The contents of the alternates files.
/// </param>
/// <returns>
/// A list of (relative) paths to the alternate object directories.
/// </returns>
public static List<string> ParseAlternates(ReadOnlySpan<byte> alternates)
=> ParseAlternates(alternates, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 2 : 0);

/// <summary>
/// Parses the contents of the alternates file, and returns a list of (relative) paths to the alternate object directories.
/// </summary>
/// <param name="alternates">
/// The contents of the alternates files.
/// </param>
/// <param name="skipCount">
/// The number of bytes to skip in the span when looking for a delimiter.
/// </param>
/// <returns>
/// A list of (relative) paths to the alternate object directories.
/// </returns>
public static List<string> ParseAlternates(ReadOnlySpan<byte> alternates, int skipCount)
{
List<string> values = new List<string>();

int index;

// The alternates path is colon (:)-separated. On Windows, there may be full paths, such as
// C:/Users/username/source/repos/nbgv/.git, which also contain a colon. Because the colon
// can only appear at the second position, we skip the first two characters (e.g. C:) on Windows.
while (alternates.Length > skipCount && (index = alternates.Slice(skipCount).IndexOfAny((byte)':', (byte)'\n')) > 0)
{
values.Add(GetString(alternates.Slice(0, skipCount + index)));
alternates = alternates.Slice(skipCount + index + 1);
}

return values;
}
}
}

0 comments on commit 2363be0

Please sign in to comment.