diff --git a/src/NerdBank.GitVersioning.Tests/ManagedGit/GitRepositoryTests.cs b/src/NerdBank.GitVersioning.Tests/ManagedGit/GitRepositoryTests.cs index fb8a3ba0..82174ee9 100644 --- a/src/NerdBank.GitVersioning.Tests/ManagedGit/GitRepositoryTests.cs +++ b/src/NerdBank.GitVersioning.Tests/ManagedGit/GitRepositoryTests.cs @@ -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( diff --git a/src/NerdBank.GitVersioning/ManagedGit/GitRepository.cs b/src/NerdBank.GitVersioning/ManagedGit/GitRepository.cs index 227e2b89..d6d829d2 100644 --- a/src/NerdBank.GitVersioning/ManagedGit/GitRepository.cs +++ b/src/NerdBank.GitVersioning/ManagedGit/GitRepository.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Runtime.InteropServices; using System.Text; namespace Nerdbank.GitVersioning.ManagedGit @@ -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)))); } } @@ -714,5 +713,47 @@ public static unsafe string GetString(ReadOnlySpan bytes) return Encoding.GetString(pBytes, bytes.Length); } } + + /// + /// Parses the contents of the alternates file, and returns a list of (relative) paths to the alternate object directories. + /// + /// + /// The contents of the alternates files. + /// + /// + /// A list of (relative) paths to the alternate object directories. + /// + public static List ParseAlternates(ReadOnlySpan alternates) + => ParseAlternates(alternates, RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? 2 : 0); + + /// + /// Parses the contents of the alternates file, and returns a list of (relative) paths to the alternate object directories. + /// + /// + /// The contents of the alternates files. + /// + /// + /// The number of bytes to skip in the span when looking for a delimiter. + /// + /// + /// A list of (relative) paths to the alternate object directories. + /// + public static List ParseAlternates(ReadOnlySpan alternates, int skipCount) + { + List values = new List(); + + 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; + } } }