diff --git a/GitBranchStateCache.Tests/Tool/MirrorRootFlagTests.cs b/GitBranchStateCache.Tests/Tool/MirrorRootFlagTests.cs
new file mode 100644
index 0000000..bc0d21b
--- /dev/null
+++ b/GitBranchStateCache.Tests/Tool/MirrorRootFlagTests.cs
@@ -0,0 +1,40 @@
+// Copyright (c) 2023-2026 ktsu-dev contributors
+
+namespace ktsu.GitBranchStateCache.Tests.Tool;
+
+using ktsu.GitBranchStateCache.Tool;
+
+[TestClass]
+public class MirrorRootFlagTests
+{
+ [TestMethod]
+ public void MirrorRoot_RelativeDirectory_ResolvesAgainstTheWorkingDirectory()
+ {
+ // The options validator refuses a root that is not fully qualified, so passing
+ // `--mirror-root ./mirrors` through verbatim aborted startup with a validation stack trace.
+ string resolved = Program.ResolveMirrorRoot("./mirrors");
+
+ Assert.IsTrue(Path.IsPathFullyQualified(resolved), resolved);
+ Assert.AreEqual(Path.Combine(Environment.CurrentDirectory, "mirrors"), resolved);
+ }
+
+ [TestMethod]
+ public async Task MirrorRoot_RelativeDirectory_IsAcceptedByTheCommandLine()
+ {
+ // The invalid upstream stops the run after the flags are read and before a server starts, so a
+ // relative --mirror-root reaches the same one-line failure path as any other flag mistake.
+ int exitCode = await Program.Main(["--mirror-root", "./mirrors", "--upstream", "not-a-name-url-pair"]);
+
+ Assert.AreEqual(1, exitCode);
+ }
+
+ [TestMethod]
+ public void MirrorRoot_FullyQualifiedDirectory_IsKeptAsGiven()
+ {
+ string root = Path.Combine(
+ Path.GetPathRoot(Path.GetTempPath()) ?? Path.DirectorySeparatorChar.ToString(),
+ "gitbranchstatecache");
+
+ Assert.AreEqual(root, Program.ResolveMirrorRoot(root));
+ }
+}
diff --git a/GitBranchStateCache.Tool/Program.cs b/GitBranchStateCache.Tool/Program.cs
index bb5ef6f..811e29a 100644
--- a/GitBranchStateCache.Tool/Program.cs
+++ b/GitBranchStateCache.Tool/Program.cs
@@ -26,7 +26,7 @@ internal static class Program
/// The configuration key the --mirror-root flag overrides.
private const string MirrorRootKey = "GitBranchStateCache:MirrorRoot";
- private static async Task Main(string[] args)
+ internal static async Task Main(string[] args)
{
Option port = new("--port", "-p")
{
@@ -94,7 +94,7 @@ private static async Task Main(string[] args)
if (parseResult.GetValue(mirrorRoot) is string root_)
{
- overrides[MirrorRootKey] = root_;
+ overrides[MirrorRootKey] = ResolveMirrorRoot(root_);
}
if (parseResult.GetValue(gitExecutable) is string git)
@@ -266,6 +266,20 @@ private static bool TryApplyUpstreams(
return true;
}
+ ///
+ /// Resolves the --mirror-root flag against the working directory.
+ ///
+ ///
+ /// The options validator requires a fully qualified root, which keeps configuration files and
+ /// environment variables strict for container deployments. A relative directory is what someone
+ /// running the tool locally types, though, so the flag resolves it the way --config does
+ /// rather than letting startup fail with a validation stack trace. This is the same behaviour as
+ /// --store in ktsu.GitLfsCache.
+ ///
+ /// The directory as given on the command line.
+ /// The fully qualified directory.
+ internal static string ResolveMirrorRoot(string mirrorRoot) => Path.GetFullPath(mirrorRoot);
+
///
/// Groups every --allow flag by the upstream it names.
///