diff --git a/GitLfsCache.Tests/Tool/StoreFlagTests.cs b/GitLfsCache.Tests/Tool/StoreFlagTests.cs
new file mode 100644
index 0000000..e5a0944
--- /dev/null
+++ b/GitLfsCache.Tests/Tool/StoreFlagTests.cs
@@ -0,0 +1,40 @@
+// Copyright (c) 2023-2026 ktsu-dev contributors
+
+namespace ktsu.GitLfsCache.Tests.Tool;
+
+using ktsu.GitLfsCache.Tool;
+
+[TestClass]
+public class StoreFlagTests
+{
+ [TestMethod]
+ public void Store_RelativeDirectory_ResolvesAgainstTheWorkingDirectory()
+ {
+ // The options validator refuses a root that is not fully qualified, so passing `--store ./cache`
+ // through verbatim aborted startup with a validation stack trace.
+ string resolved = Program.ResolveStoreRoot("./cache");
+
+ Assert.IsTrue(Path.IsPathFullyQualified(resolved), resolved);
+ Assert.AreEqual(Path.Combine(Environment.CurrentDirectory, "cache"), resolved);
+ }
+
+ [TestMethod]
+ public async Task Store_RelativeDirectory_IsAcceptedByTheCommandLine()
+ {
+ // The invalid upstream stops the run after the flags are read and before a server starts, so a
+ // relative --store reaches the same one-line failure path as any other flag mistake.
+ int exitCode = await Program.Main(["--store", "./cache", "--upstream", "not-a-name-url-pair"]);
+
+ Assert.AreEqual(1, exitCode);
+ }
+
+ [TestMethod]
+ public void Store_FullyQualifiedDirectory_IsKeptAsGiven()
+ {
+ string root = Path.Combine(
+ Path.GetPathRoot(Path.GetTempPath()) ?? Path.DirectorySeparatorChar.ToString(),
+ "gitlfscache");
+
+ Assert.AreEqual(root, Program.ResolveStoreRoot(root));
+ }
+}
diff --git a/GitLfsCache.Tool/Program.cs b/GitLfsCache.Tool/Program.cs
index 94825bb..d7c0655 100644
--- a/GitLfsCache.Tool/Program.cs
+++ b/GitLfsCache.Tool/Program.cs
@@ -29,7 +29,7 @@ internal static class Program
/// The configuration key the --token-key flag overrides.
private const string TokenKeyKey = "GitLfsCache:TokenKeys:0";
- private static async Task Main(string[] args)
+ internal static async Task Main(string[] args)
{
Option port = new("--port", "-p")
{
@@ -110,7 +110,7 @@ private static async Task Main(string[] args)
if (parseResult.GetValue(store) is string storeRoot)
{
- overrides[StoreRootKey] = storeRoot;
+ overrides[StoreRootKey] = ResolveStoreRoot(storeRoot);
}
if (parseResult.GetValue(maxSize) is string budget)
@@ -285,6 +285,19 @@ private static bool TryApplyUpstreams(
return true;
}
+ ///
+ /// Resolves the --store 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.
+ ///
+ /// The directory as given on the command line.
+ /// The fully qualified directory.
+ internal static string ResolveStoreRoot(string storeRoot) => Path.GetFullPath(storeRoot);
+
///
/// Groups every --allow flag by the upstream it names.
///