From 6a4c7fb9759dbbf5ddaf0ebd00921d0f8045f355 Mon Sep 17 00:00:00 2001
From: Eelco Dolstra <edolstra@gmail.com>
Date: Mon, 8 Apr 2019 22:46:25 +0200
Subject: [PATCH] Add path flakeref variant

Unlike file://<path>, this allows the path to be a dirty Git tree, so

  nix build /path/to/flake:attr

is a convenient way to test building a local flake.
---
 src/libexpr/primops/fetchGit.cc |  4 ++--
 src/libexpr/primops/fetchGit.hh |  2 +-
 src/libexpr/primops/flake.cc    | 27 ++++++++++++++++++++++++++-
 src/libexpr/primops/flake.hh    |  1 +
 src/libexpr/primops/flakeref.cc | 13 +++++++++++++
 src/libexpr/primops/flakeref.hh |  7 ++++++-
 6 files changed, 49 insertions(+), 5 deletions(-)

diff --git a/src/libexpr/primops/fetchGit.cc b/src/libexpr/primops/fetchGit.cc
index bbf13c87b..391308224 100644
--- a/src/libexpr/primops/fetchGit.cc
+++ b/src/libexpr/primops/fetchGit.cc
@@ -170,7 +170,7 @@ GitInfo exportGit(ref<Store> store, const std::string & uri,
     json["uri"] = uri;
     json["name"] = name;
     json["rev"] = gitInfo.rev;
-    json["revCount"] = gitInfo.revCount;
+    json["revCount"] = *gitInfo.revCount;
 
     writeFile(storeLink, json.dump());
 
@@ -224,7 +224,7 @@ static void prim_fetchGit(EvalState & state, const Pos & pos, Value * * args, Va
     mkString(*state.allocAttr(v, state.sOutPath), gitInfo.storePath, PathSet({gitInfo.storePath}));
     mkString(*state.allocAttr(v, state.symbols.create("rev")), gitInfo.rev);
     mkString(*state.allocAttr(v, state.symbols.create("shortRev")), gitInfo.shortRev);
-    mkInt(*state.allocAttr(v, state.symbols.create("revCount")), gitInfo.revCount);
+    mkInt(*state.allocAttr(v, state.symbols.create("revCount")), gitInfo.revCount.value_or(0));
     v.attrs->sort();
 
     if (state.allowedPaths)
diff --git a/src/libexpr/primops/fetchGit.hh b/src/libexpr/primops/fetchGit.hh
index d7a0e165a..60c439426 100644
--- a/src/libexpr/primops/fetchGit.hh
+++ b/src/libexpr/primops/fetchGit.hh
@@ -11,7 +11,7 @@ struct GitInfo
     Path storePath;
     std::string rev;
     std::string shortRev;
-    uint64_t revCount = 0;
+    std::optional<uint64_t> revCount;
 };
 
 GitInfo exportGit(ref<Store> store, const std::string & uri,
diff --git a/src/libexpr/primops/flake.cc b/src/libexpr/primops/flake.cc
index dedd2f737..f068569a6 100644
--- a/src/libexpr/primops/flake.cc
+++ b/src/libexpr/primops/flake.cc
@@ -129,6 +129,7 @@ struct FlakeSourceInfo
 {
     Path storePath;
     std::optional<Hash> rev;
+    std::optional<uint64_t> revCount;
 };
 
 static FlakeSourceInfo fetchFlake(EvalState & state, const FlakeRef & flakeRef)
@@ -178,6 +179,18 @@ static FlakeSourceInfo fetchFlake(EvalState & state, const FlakeRef & flakeRef)
         FlakeSourceInfo info;
         info.storePath = gitInfo.storePath;
         info.rev = Hash(gitInfo.rev, htSHA1);
+        info.revCount = gitInfo.revCount;
+        return info;
+    }
+
+    else if (auto refData = std::get_if<FlakeRef::IsPath>(&directFlakeRef.data)) {
+        if (!pathExists(refData->path + "/.git"))
+            throw Error("flake '%s' does not reference a Git repository", refData->path);
+        auto gitInfo = exportGit(state.store, refData->path, {}, "", "source");
+        FlakeSourceInfo info;
+        info.storePath = gitInfo.storePath;
+        info.rev = Hash(gitInfo.rev, htSHA1);
+        info.revCount = gitInfo.revCount;
         return info;
     }
 
@@ -206,6 +219,8 @@ Flake getFlake(EvalState & state, const FlakeRef & flakeRef)
     }
 
     Flake flake(newFlakeRef);
+    flake.path = flakePath;
+    flake.revCount = sourceInfo.revCount;
 
     Value vInfo;
     state.evalFile(flakePath + "/flake.nix", vInfo); // FIXME: symlink attack
@@ -349,10 +364,20 @@ Value * makeFlakeValue(EvalState & state, const FlakeRef & flakeRef, bool impure
     for (auto & flake : flakes) {
         auto vFlake = state.allocAttr(*vResult, flake.second.id);
         if (topFlakeId == flake.second.id) vTop = vFlake;
-        state.mkAttrs(*vFlake, 2);
+
+        state.mkAttrs(*vFlake, 4);
+
         mkString(*state.allocAttr(*vFlake, state.sDescription), flake.second.description);
+
+        state.store->assertStorePath(flake.second.path);
+        mkString(*state.allocAttr(*vFlake, state.sOutPath), flake.second.path, {flake.second.path});
+
+        if (flake.second.revCount)
+            mkInt(*state.allocAttr(*vFlake, state.symbols.create("revCount")), *flake.second.revCount);
+
         auto vProvides = state.allocAttr(*vFlake, state.symbols.create("provides"));
         mkApp(*vProvides, *flake.second.vProvides, *vResult);
+
         vFlake->attrs->sort();
     }
 
diff --git a/src/libexpr/primops/flake.hh b/src/libexpr/primops/flake.hh
index df8cf9efb..aea4e8aa2 100644
--- a/src/libexpr/primops/flake.hh
+++ b/src/libexpr/primops/flake.hh
@@ -35,6 +35,7 @@ struct Flake
     FlakeRef ref;
     std::string description;
     Path path;
+    std::optional<uint64_t> revCount;
     std::vector<FlakeRef> requires;
     std::shared_ptr<FlakeRegistry> lockFile;
     Value * vProvides; // FIXME: gc
diff --git a/src/libexpr/primops/flakeref.cc b/src/libexpr/primops/flakeref.cc
index 8e7c1f8df..5f9a29260 100644
--- a/src/libexpr/primops/flakeref.cc
+++ b/src/libexpr/primops/flakeref.cc
@@ -106,6 +106,12 @@ FlakeRef::FlakeRef(const std::string & uri)
         data = d;
     }
 
+    else if (hasPrefix(uri, "/")) {
+        IsPath d;
+        d.path = canonPath(uri);
+        data = d;
+    }
+
     else
         throw Error("'%s' is not a valid flake reference", uri);
 }
@@ -135,6 +141,10 @@ std::string FlakeRef::to_string() const
             (refData->rev ? "&rev=" + refData->rev->to_string(Base16, false) : "");
     }
 
+    else if (auto refData = std::get_if<FlakeRef::IsPath>(&data)) {
+        return refData->path;
+    }
+
     else abort();
 }
 
@@ -149,6 +159,9 @@ bool FlakeRef::isImmutable() const
     else if (auto refData = std::get_if<FlakeRef::IsGit>(&data))
         return (bool) refData->rev;
 
+    else if (std::get_if<FlakeRef::IsPath>(&data))
+        return false;
+
     else abort();
 }
 
diff --git a/src/libexpr/primops/flakeref.hh b/src/libexpr/primops/flakeref.hh
index fb365e101..832d7dd03 100644
--- a/src/libexpr/primops/flakeref.hh
+++ b/src/libexpr/primops/flakeref.hh
@@ -122,9 +122,14 @@ struct FlakeRef
         std::optional<Hash> rev;
     };
 
+    struct IsPath
+    {
+        Path path;
+    };
+
     // Git, Tarball
 
-    std::variant<IsFlakeId, IsGitHub, IsGit> data;
+    std::variant<IsFlakeId, IsGitHub, IsGit, IsPath> data;
 
     // Parse a flake URI.
     FlakeRef(const std::string & uri);