Skip to content

Commit

Permalink
Supported submodule repository redirection.
Browse files Browse the repository at this point in the history
  • Loading branch information
kekyo committed Nov 6, 2023
1 parent d71bc80 commit d199c32
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 35 deletions.
92 changes: 67 additions & 25 deletions GitReader.Core/Internal/RepositoryAccessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,31 +92,6 @@ public HashResults(Hash hash, string[] names)

internal static class RepositoryAccessor
{
public static string DetectLocalRepositoryPath(
string startPath)
{
var currentPath = Path.GetFullPath(startPath);

while (true)
{
var repositoryPath = Path.GetFileName(currentPath) != ".git" ?
Utilities.Combine(currentPath, ".git") : currentPath;

if (Directory.Exists(repositoryPath) &&
File.Exists(Path.Combine(repositoryPath, "config")))
{
return repositoryPath;
}

if (Path.GetPathRoot(currentPath) == currentPath)
{
throw new ArgumentException("Repository does not exist.");
}

currentPath = Utilities.GetDirectoryPath(currentPath);
}
}

#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
private static async ValueTask<Stream> OpenFileAsync(
string path, CancellationToken ct)
Expand Down Expand Up @@ -157,6 +132,73 @@ private static async Task<Stream> OpenFileAsync(
return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, 65536, true);
}

#if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP
public static async ValueTask<string> DetectLocalRepositoryPathAsync(
string startPath, CancellationToken ct)
#else
public static async Task<string> DetectLocalRepositoryPathAsync(
string startPath, CancellationToken ct)
#endif
{
var currentPath = Path.GetFullPath(startPath);

while (true)
{
ct.ThrowIfCancellationRequested();

var candidatePath = Path.GetFileName(currentPath) != ".git" ?
Utilities.Combine(currentPath, ".git") : currentPath;

if (Directory.Exists(candidatePath) &&
File.Exists(Path.Combine(candidatePath, "config")))
{
return candidatePath;
}

// Issue #11
if (File.Exists(candidatePath))
{
using var fs = await OpenFileAsync(candidatePath, ct);
var tr = new AsyncTextReader(fs);

while (true)
{
var line = await tr.ReadLineAsync(ct);
if (line == null)
{
break;
}

line = line.Trim();
if (line.StartsWith("gitdir:"))
{
// Resolve to full path (And normalize path directory separators)
var gitDirPath = line.Substring(7).TrimStart();
candidatePath = Utilities.ResolveRelativePath(currentPath, gitDirPath);

if (Directory.Exists(candidatePath) &&
File.Exists(Path.Combine(candidatePath, "config")))
{
return candidatePath;
}

break;
}
}

throw new ArgumentException(
"Repository does not exist. `.git` file exists. But failed to `gitdir` or specified directory is not exists.");
}

if (Path.GetPathRoot(currentPath) == currentPath)
{
throw new ArgumentException("Repository does not exist.");
}

currentPath = Utilities.GetDirectoryPath(currentPath);
}
}

public static string GetReferenceTypeName(ReferenceTypes type) =>
type switch
{
Expand Down
19 changes: 19 additions & 0 deletions GitReader.Core/Internal/Utilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,18 @@ internal static class Utilities
private const long UnixEpochTicks = DaysTo1970 * TicksPerDay;
private const long UnixEpochSeconds = UnixEpochTicks / TimeSpan.TicksPerSecond;

private static readonly bool isWindows =
#if NETSTANDARD1_6
!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("HOMEDRIVE"));
#else
Environment.OSVersion.Platform.ToString().Contains("Win");
#endif

private static readonly string homePath =
Path.GetFullPath(isWindows ?
$"{Environment.GetEnvironmentVariable("HOMEDRIVE") ?? "C:"}{Environment.GetEnvironmentVariable("HOMEPATH") ?? "\\"}" :
(Environment.GetEnvironmentVariable("HOME") ?? "/"));

public static DateTimeOffset FromUnixTimeSeconds(long seconds, TimeSpan offset)
{
var ticks = seconds * TimeSpan.TicksPerSecond + UnixEpochTicks;
Expand Down Expand Up @@ -692,4 +704,11 @@ public static string ToGitAuthorString(
Signature signature) =>
signature.MailAddress is { } mailAddress ?
$"{signature.Name} <{mailAddress}>" : signature.Name;

public static string ResolveRelativePath(string basePath, string path) =>
Path.GetFullPath(Path.IsPathRooted(path) ?
path :
path.StartsWith("~/") ?
Combine(homePath, path.Substring(2)) :
Combine(basePath, path));
}
6 changes: 3 additions & 3 deletions GitReader.Core/Primitive/PrimitiveRepositoryFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@ private static async Task<PrimitiveRepository> InternalOpenPrimitiveAsync(
}
}

public static Task<PrimitiveRepository> OpenPrimitiveAsync(
public static async Task<PrimitiveRepository> OpenPrimitiveAsync(
string path, CancellationToken ct)
{
var repositoryPath = RepositoryAccessor.DetectLocalRepositoryPath(path);
return InternalOpenPrimitiveAsync(repositoryPath, ct);
var repositoryPath = await RepositoryAccessor.DetectLocalRepositoryPathAsync(path, ct);
return await InternalOpenPrimitiveAsync(repositoryPath, ct);
}

//////////////////////////////////////////////////////////////////////////
Expand Down
7 changes: 4 additions & 3 deletions GitReader.Core/Structures/StructuredRepositoryFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,11 +204,12 @@ private static async Task<StructuredRepository> InternalOpenStructuredAsync(
throw;
}
}
public static Task<StructuredRepository> OpenStructuredAsync(

public static async Task<StructuredRepository> OpenStructuredAsync(
string path, CancellationToken ct)
{
var repositoryPath = RepositoryAccessor.DetectLocalRepositoryPath(path);
return InternalOpenStructuredAsync(repositoryPath, ct);
var repositoryPath = await RepositoryAccessor.DetectLocalRepositoryPathAsync(path, ct);
return await InternalOpenStructuredAsync(repositoryPath, ct);
}

//////////////////////////////////////////////////////////////////////////
Expand Down
26 changes: 22 additions & 4 deletions GitReader.Tests/Internal/RepositoryAccessorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,22 @@
////////////////////////////////////////////////////////////////////////////

using NUnit.Framework;
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Threading.Tasks;

namespace GitReader.Internal;

public sealed class RepositoryAccessorTests
{
[TestCase(1)]
[TestCase(2)]
public void DetectLocalRepositoryPath(int depth)
public async Task DetectLocalRepositoryPath(int depth)
{
var basePath = Path.GetFullPath(
RepositoryTestsSetUp.GetBasePath(
$"DetectLocalRepositoryPath1_{depth}"));
$"DetectLocalRepositoryPath_{depth}"));
var innerPath = Path.Combine(
basePath,
Path.Combine(Enumerable.Range(0, depth).Select(_ => "inner").ToArray()));
Expand All @@ -34,8 +34,26 @@ public void DetectLocalRepositoryPath(int depth)
Path.Combine("artifacts", "test1.zip"),
basePath);

var actual = RepositoryAccessor.DetectLocalRepositoryPath(innerPath);
var actual = await RepositoryAccessor.DetectLocalRepositoryPathAsync(innerPath, default);

Assert.AreEqual(Path.Combine(basePath, ".git"), actual);
}

[Test]
public async Task DetectLocalRepositoryPathFromDotGitFile()
{
var basePath = Path.GetFullPath(
RepositoryTestsSetUp.GetBasePath(
$"DetectLocalRepositoryPathFromDotGitFile"));

ZipFile.ExtractToDirectory(
Path.Combine("artifacts", "test5.zip"),
basePath);

var innerPath = Path.Combine(basePath, "GitReader");

var actual = await RepositoryAccessor.DetectLocalRepositoryPathAsync(innerPath, default);

Assert.AreEqual(Path.Combine(basePath, ".git", "modules", "GitReader"), actual);
}
}
Binary file added GitReader.Tests/artifacts/test5.zip
Binary file not shown.

0 comments on commit d199c32

Please sign in to comment.