diff --git a/src/Verlite.Core/Command.cs b/src/Verlite.Core/Command.cs
index cc0e9a1..6d1a2b0 100644
--- a/src/Verlite.Core/Command.cs
+++ b/src/Verlite.Core/Command.cs
@@ -7,7 +7,44 @@
namespace Verlite
{
///
- /// The command class
+ /// An interface to run commands.
+ ///
+ public interface ICommandRunner
+ {
+ ///
+ /// Asynchronously execute a command.
+ ///
+ /// The working directory in which to start the executable.
+ /// The command to execute.
+ /// Arguments to pass to the command.
+ /// The enviornment variables to start the process with.
+ /// Thrown if the process returns a non-zero exit code.
+ /// A task that completes upon the process exiting, containing the standard out and error streams.
+ Task<(string stdout, string stderr)> Run(
+ string directory,
+ string command,
+ string[] args,
+ IDictionary? envVars = null);
+ }
+
+ ///
+ /// Run commands using
+ ///
+ public class SystemCommandRunner : ICommandRunner
+ {
+ ///
+ public async Task<(string stdout, string stderr)> Run(
+ string directory,
+ string command,
+ string[] args,
+ IDictionary? envVars = null)
+ {
+ return await Command.Run(directory, command, args, envVars);
+ }
+ }
+
+ ///
+ /// A class for executing commands.
///
public static class Command
{
diff --git a/src/Verlite.Core/GitRepoInspector.cs b/src/Verlite.Core/GitRepoInspector.cs
index f5cbd41..4549349 100644
--- a/src/Verlite.Core/GitRepoInspector.cs
+++ b/src/Verlite.Core/GitRepoInspector.cs
@@ -59,14 +59,20 @@ public sealed class GitRepoInspector : IRepoInspector
///
/// The path of the Git repository.
/// A logger for diagnostics.
+ /// A command runner to use. Defaults to if null is given.
/// Thrown if the path is not a Git repository.
/// A task containing the Git repo inspector.
- public static async Task FromPath(string path, ILogger? log = null)
+ public static async Task FromPath(string path, ILogger? log = null, ICommandRunner? commandRunner = null)
{
+ commandRunner ??= new SystemCommandRunner();
+
try
{
- var (root, _) = await Command.Run(path, "git", new string[] { "rev-parse", "--show-toplevel" });
- var ret = new GitRepoInspector(root, log);
+ var (root, _) = await commandRunner.Run(path, "git", new string[] { "rev-parse", "--show-toplevel" });
+ var ret = new GitRepoInspector(
+ root,
+ log,
+ commandRunner);
await ret.CacheParents();
return ret;
}
@@ -77,6 +83,7 @@ public static async Task FromPath(string path, ILogger? log =
}
private ILogger? Log { get; }
+ private ICommandRunner CommandRunner { get; }
///
/// Can the Git repository be deepened to fetch commits not in the local repository.
///
@@ -88,13 +95,14 @@ public static async Task FromPath(string path, ILogger? log =
private Dictionary CachedParents { get; } = new();
private (int depth, bool shallow, Commit deepest)? FetchDepth { get; set; }
- private GitRepoInspector(string root, ILogger? log)
+ private GitRepoInspector(string root, ILogger? log, ICommandRunner commandRunner)
{
Root = root;
Log = log;
+ CommandRunner = commandRunner;
}
- private Task<(string stdout, string stderr)> Git(params string[] args) => Command.Run(Root, "git", args);
+ private Task<(string stdout, string stderr)> Git(params string[] args) => CommandRunner.Run(Root, "git", args);
///
public async Task GetHead()
@@ -161,14 +169,14 @@ private GitRepoInspector(string root, ILogger? log)
if (commitObj is null)
{
Log?.Verbatim($"MeasureDepth() -> (depth: {depth}, shallow: true)");
- return (depth: depth, shallow: true, deepestCommit: deepest);
+ return (depth, shallow: true, deepestCommit: deepest);
}
Commit? parent = ParseCommitObjectParent(commitObj);
if (parent is null)
{
Log?.Verbatim($"MeasureDepth() -> (depth: {depth}, shallow: false)");
- return (depth: depth, shallow: false, deepestCommit: current);
+ return (depth, shallow: false, deepestCommit: current);
}
depth++;
diff --git a/src/Verlite.Core/PublicAPI.Shipped.txt b/src/Verlite.Core/PublicAPI.Shipped.txt
index cd593d3..745e140 100644
--- a/src/Verlite.Core/PublicAPI.Shipped.txt
+++ b/src/Verlite.Core/PublicAPI.Shipped.txt
@@ -13,7 +13,7 @@ override Verlite.TaggedVersion.GetHashCode() -> int
static Verlite.Command.Run(string! directory, string! command, string![]! args, System.Collections.Generic.IDictionary? envVars = null) -> System.Threading.Tasks.Task<(string! stdout, string! stderr)>!
static Verlite.Commit.operator !=(Verlite.Commit left, Verlite.Commit right) -> bool
static Verlite.Commit.operator ==(Verlite.Commit left, Verlite.Commit right) -> bool
-static Verlite.GitRepoInspector.FromPath(string! path, Verlite.ILogger? log = null) -> System.Threading.Tasks.Task!
+static Verlite.GitRepoInspector.FromPath(string! path, Verlite.ILogger? log = null, Verlite.ICommandRunner? commandRunner = null) -> System.Threading.Tasks.Task!
static Verlite.HeightCalculator.FromRepository(Verlite.IRepoInspector! repo, string! tagPrefix, bool queryRemoteTags, Verlite.ILogger? log = null) -> System.Threading.Tasks.Task<(int height, Verlite.TaggedVersion?)>!
static Verlite.SemVer.ComparePrerelease(string! left, string! right) -> int
static Verlite.SemVer.IsValidIdentifierCharacter(char input) -> bool
@@ -59,6 +59,8 @@ Verlite.GitRepoInspector.GetParent(Verlite.Commit commit) -> System.Threading.Ta
Verlite.GitRepoInspector.GetTags(Verlite.QueryTarget queryTarget) -> System.Threading.Tasks.Task!
Verlite.GitRepoInspector.Root.get -> string!
Verlite.HeightCalculator
+Verlite.ICommandRunner
+Verlite.ICommandRunner.Run(string! directory, string! command, string![]! args, System.Collections.Generic.IDictionary? envVars = null) -> System.Threading.Tasks.Task<(string! stdout, string! stderr)>!
Verlite.ILogger
Verlite.ILogger.Normal(string! message) -> void
Verlite.ILogger.Verbatim(string! message) -> void
@@ -94,6 +96,9 @@ Verlite.SemVer.Prerelease.get -> string?
Verlite.SemVer.Prerelease.set -> void
Verlite.SemVer.SemVer() -> void
Verlite.SemVer.SemVer(int major, int minor, int patch, string? prerelease = null, string? buildMetadata = null) -> void
+Verlite.SystemCommandRunner
+Verlite.SystemCommandRunner.Run(string! directory, string! command, string![]! args, System.Collections.Generic.IDictionary? envVars = null) -> System.Threading.Tasks.Task<(string! stdout, string! stderr)>!
+Verlite.SystemCommandRunner.SystemCommandRunner() -> void
Verlite.Tag
Verlite.Tag.Equals(Verlite.Tag other) -> bool
Verlite.Tag.Name.get -> string!
diff --git a/tests/UnitTests/GitRepoInspectorTests.cs b/tests/UnitTests/GitRepoInspectorTests.cs
index c38bf21..78b6da5 100644
--- a/tests/UnitTests/GitRepoInspectorTests.cs
+++ b/tests/UnitTests/GitRepoInspectorTests.cs
@@ -53,9 +53,12 @@ public GitTestDirectory()
Directory.CreateDirectory(RootPath);
}
- public Task MakeInspector()
+ public Task MakeInspector(ICommandRunner? commandRunner = null)
{
- return GitRepoInspector.FromPath(RootPath);
+ return GitRepoInspector.FromPath(
+ path: RootPath,
+ log: null,
+ commandRunner);
}
public void Dispose()
@@ -391,5 +394,30 @@ public async Task FetchingTagInDeepCloneDoesNotMakeShallow()
var deeperParent = await repo.GetParent(deeperTag.PointsTo);
deeperParent.Should().Be(new Commit("b2000fc1f1d2e5f816cfa51a4ad8764048f22f0a"));
}
+
+ [Fact]
+ public async Task ShallowGitFetchFromCommitCanFallBack()
+ {
+ await TestRepo.Git("init");
+ await TestRepo.Git("commit", "--allow-empty", "-m", "first");
+ await TestRepo.Git("tag", "tag-one");
+ await TestRepo.Git("commit", "--allow-empty", "-m", "second");
+ await TestRepo.Git("tag", "tag-two");
+ await TestRepo.Git("commit", "--allow-empty", "-m", "third");
+
+ using var clone = new GitTestDirectory();
+ await clone.Git("clone", TestRepo.RootUri, ".", "--branch", "master", "--depth", "1");
+
+ var repo = await clone.MakeInspector(
+ new MockCommandRunnerWithOldRemoteGitVersion(
+ new SystemCommandRunner()));
+
+ repo.CanDeepen = true;
+ var head = await repo.GetHead();
+ var parent = await repo.GetParent(head.Value);
+ var parentsParent = await repo.GetParent(parent.Value);
+
+ parentsParent.Should().Be(new Commit("b2000fc1f1d2e5f816cfa51a4ad8764048f22f0a"));
+ }
}
}
diff --git a/tests/UnitTests/Mocks/MockCommandRunnerWithOldRemoteGitVersion.cs b/tests/UnitTests/Mocks/MockCommandRunnerWithOldRemoteGitVersion.cs
new file mode 100644
index 0000000..5309fdf
--- /dev/null
+++ b/tests/UnitTests/Mocks/MockCommandRunnerWithOldRemoteGitVersion.cs
@@ -0,0 +1,33 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+using Verlite;
+
+namespace UnitTests
+{
+ public sealed class MockCommandRunnerWithOldRemoteGitVersion : ICommandRunner
+ {
+ private ICommandRunner BaseRunner { get; }
+ public MockCommandRunnerWithOldRemoteGitVersion(ICommandRunner baseRunner)
+ {
+ BaseRunner = baseRunner;
+ }
+
+ Task<(string stdout, string stderr)> ICommandRunner.Run(
+ string directory,
+ string command,
+ string[] args,
+ IDictionary? envVars)
+ {
+ string? firstArg = args.Length > 0 ? args[0] : null;
+
+ return (command, firstArg, args) switch
+ {
+ ("git", "fetch", _) when args.Contains("origin") =>
+ throw new CommandException(128, "", "error: Server does not allow request for unadvertised object a1b2c3"),
+ _ => BaseRunner.Run(directory, command, args, envVars),
+ };
+ }
+ }
+}
diff --git a/tests/UnitTests/Helpers/MockRepoInspector.cs b/tests/UnitTests/Mocks/MockRepoInspector.cs
similarity index 100%
rename from tests/UnitTests/Helpers/MockRepoInspector.cs
rename to tests/UnitTests/Mocks/MockRepoInspector.cs