Skip to content
This repository was archived by the owner on Feb 12, 2023. It is now read-only.

Commit edc6a3f

Browse files
committed
Dramatically improve perf of path normalization
1 parent 62283e6 commit edc6a3f

File tree

5 files changed

+179
-11
lines changed

5 files changed

+179
-11
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// --------------------------------------------------------------------------------------------------------------------
2+
// <copyright file="RepositoryExtensionFacts.cs" company="Andrew Arnott">
3+
// Copyright (c) 2016 Andrew Arnott. All rights reserved.
4+
// </copyright>
5+
// --------------------------------------------------------------------------------------------------------------------
6+
7+
namespace GitLink.Tests.Extensions
8+
{
9+
using System.IO;
10+
using GitTools.Git;
11+
using LibGit2Sharp;
12+
using NUnit.Framework;
13+
14+
[TestFixture]
15+
public class RepositoryExtensionFacts
16+
{
17+
private static readonly char[] PathSeparators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
18+
private Repository repo;
19+
20+
[SetUp]
21+
public void SetUp()
22+
{
23+
string repositoryDirectory = GitDirFinder.TreeWalkForGitDir(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location));
24+
repo = new Repository(repositoryDirectory);
25+
}
26+
27+
[Theory, Pairwise]
28+
public void NormalizeFileAtRoot(bool scrambleCase, bool absolutePath, bool emptySegments, bool forwardSlashes)
29+
{
30+
string expected = "LICENSE";
31+
string input = GetPathToTest(expected, scrambleCase, absolutePath, emptySegments, forwardSlashes);
32+
string actual = repo.GetNormalizedPath(input);
33+
Assert.AreEqual(expected, actual);
34+
}
35+
36+
[Theory, Pairwise]
37+
public void NormalizeFileOneDirDeep(bool scrambleCase, bool absolutePath, bool emptySegments, bool forwardSlashes)
38+
{
39+
string expected = "src/EnlistmentInfo.targets";
40+
string input = GetPathToTest(expected, scrambleCase, absolutePath, emptySegments, forwardSlashes);
41+
string actual = repo.GetNormalizedPath(input);
42+
Assert.AreEqual(expected, actual);
43+
}
44+
45+
[Theory, Pairwise]
46+
public void NormalizeFileTwoDirsDeep(bool scrambleCase, bool absolutePath, bool emptySegments, bool forwardSlashes)
47+
{
48+
string expected = "src/GitLink/Linker.cs";
49+
string input = GetPathToTest(expected, scrambleCase, absolutePath, emptySegments, forwardSlashes);
50+
string actual = repo.GetNormalizedPath(input);
51+
Assert.AreEqual(expected, actual);
52+
}
53+
54+
[Theory]
55+
public void NormalizeMissingFile([Values("T/N", "T\\N", "T", "T/n/C")]string path)
56+
{
57+
Assert.AreEqual(path, repo.GetNormalizedPath(path));
58+
}
59+
60+
private string GetPathToTest(string expected, bool scrambleCase, bool absolutePath, bool emptySegments, bool forwardSlashes)
61+
{
62+
string actual = expected;
63+
if (scrambleCase)
64+
{
65+
actual = actual.ToUpperInvariant();
66+
if (actual == expected)
67+
{
68+
actual = actual.ToLowerInvariant();
69+
}
70+
}
71+
72+
if (absolutePath)
73+
{
74+
actual = Path.Combine(repo.Info.WorkingDirectory, actual);
75+
}
76+
77+
if (emptySegments)
78+
{
79+
if (actual.IndexOfAny(PathSeparators) >= 0)
80+
{
81+
actual = actual.Replace(Path.DirectorySeparatorChar.ToString(), Path.DirectorySeparatorChar.ToString() + Path.DirectorySeparatorChar.ToString())
82+
.Replace(Path.AltDirectorySeparatorChar.ToString(), Path.AltDirectorySeparatorChar.ToString() + Path.AltDirectorySeparatorChar.ToString());
83+
}
84+
}
85+
86+
actual = forwardSlashes
87+
? actual.Replace('\\', '/')
88+
: actual.Replace('/', '\\');
89+
90+
return actual;
91+
}
92+
}
93+
}

src/GitLink.Tests/GitLink.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@
5656
<Otherwise />
5757
</Choose>
5858
<ItemGroup>
59+
<Compile Include="..\GitLink\Extensions\RepositoryExtensions.cs">
60+
<Link>Extensions\RepositoryExtensions.cs</Link>
61+
</Compile>
5962
<Compile Include="ApprovalTestsConfig.cs" />
63+
<Compile Include="Extensions\RepositoryExtensionFacts.cs" />
6064
<Compile Include="IntegrationTests\BitBucketIntegration.cs" />
6165
<Compile Include="IntegrationTests\GitHubIntegration.cs" />
6266
<Compile Include="IntegrationTests\IntegrationTestBase.cs" />
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// --------------------------------------------------------------------------------------------------------------------
2+
// <copyright file="RepositoryExtensions.cs" company="Andrew Arnott">
3+
// Copyright (c) 2016 Andrew Arnott. All rights reserved.
4+
// </copyright>
5+
// --------------------------------------------------------------------------------------------------------------------
6+
7+
namespace GitLink
8+
{
9+
using System;
10+
using System.IO;
11+
using System.Linq;
12+
using Catel;
13+
using Catel.Logging;
14+
using LibGit2Sharp;
15+
16+
internal static class RepositoryExtensions
17+
{
18+
private static readonly ILog Log = LogManager.GetCurrentClassLogger();
19+
private static readonly char[] PathSeparators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
20+
21+
internal static string GetNormalizedPath(this Repository repository, string path)
22+
{
23+
Argument.IsNotNull(nameof(repository), repository);
24+
Argument.IsNotNullOrEmpty(nameof(path), path);
25+
26+
string relativePath = GetRelativePath(path, repository.Info.WorkingDirectory);
27+
string[] relativePathSegments = relativePath.Split(PathSeparators, StringSplitOptions.RemoveEmptyEntries);
28+
var tree = repository.Commits.FirstOrDefault()?.Tree;
29+
if (tree == null)
30+
{
31+
// Throw an exception that will cause our caller to fallback to poor man's normalization.
32+
throw new RepositoryNotFoundException();
33+
}
34+
35+
for (int i = 0; i < relativePathSegments.Length; i++)
36+
{
37+
string segment = relativePathSegments[i];
38+
TreeEntry entry = tree[segment] ?? tree.FirstOrDefault(te => string.Equals(te.Name, segment, StringComparison.OrdinalIgnoreCase));
39+
40+
if (entry == null)
41+
{
42+
Log.Warning("Unable to find file in git.");
43+
return path;
44+
}
45+
46+
if (entry.TargetType == TreeEntryTargetType.Tree)
47+
{
48+
tree = (Tree)entry.Target;
49+
}
50+
else
51+
{
52+
if (i < relativePathSegments.Length - 1)
53+
{
54+
Log.Error("Found a file where we expected to find a directory.");
55+
return path;
56+
}
57+
}
58+
59+
relativePathSegments[i] = entry.Name;
60+
}
61+
62+
return string.Join("/", relativePathSegments);
63+
}
64+
65+
private static string GetRelativePath(string target, string relativeTo)
66+
{
67+
if (!Path.IsPathRooted(target))
68+
{
69+
// It is already relative.
70+
return target;
71+
}
72+
73+
target = string.Join(Path.DirectorySeparatorChar.ToString(), target.Split(PathSeparators, StringSplitOptions.RemoveEmptyEntries));
74+
75+
Uri baseUri = new Uri(relativeTo, UriKind.Absolute);
76+
Uri targetUri = new Uri(target, UriKind.Absolute);
77+
return baseUri.MakeRelativeUri(targetUri).ToString();
78+
}
79+
}
80+
}

src/GitLink/GitLink.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
<Compile Include="Extensions\BinaryReaderExtensions.cs" />
6363
<Compile Include="Extensions\PdbExtensions.cs" />
6464
<Compile Include="Exceptions\GitLinkException.cs" />
65+
<Compile Include="Extensions\RepositoryExtensions.cs" />
6566
<Compile Include="Helpers\PdbStrHelper.cs" />
6667
<Compile Include="Linker.cs" />
6768
<Compile Include="LinkMethod.cs" />

src/GitLink/Linker.cs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public static class Linker
9999
try
100100
{
101101
Repository repo = repository.Value;
102-
repoSourceFiles = sourceFiles.ToDictionary(e => e, e => GetNormalizedPath(e, repo));
102+
repoSourceFiles = sourceFiles.ToDictionary(e => e, e => repo.GetNormalizedPath(e));
103103
}
104104
catch (RepositoryNotFoundException)
105105
{
@@ -203,16 +203,6 @@ private static void CreateSrcSrv(string srcsrvFile, SrcSrvContext srcSrvContext)
203203
}
204204
}
205205

206-
private static string GetNormalizedPath(string path, Repository repository)
207-
{
208-
Argument.IsNotNull(nameof(repository), repository);
209-
Argument.IsNotNullOrEmpty(nameof(path), path);
210-
211-
string relativePath = Catel.IO.Path.GetRelativePath(path, repository.Info.WorkingDirectory);
212-
var repoFile = repository.Index.FirstOrDefault(e => string.Equals(e.Path, relativePath, StringComparison.OrdinalIgnoreCase));
213-
return repoFile?.Path;
214-
}
215-
216206
private static string GetNormalizedPath(string path, string gitRepoRootDir)
217207
{
218208
Argument.IsNotNullOrEmpty(nameof(path), path);

0 commit comments

Comments
 (0)