From 5e4d92d267c080bcb81168e37429bbb56bc39fb2 Mon Sep 17 00:00:00 2001
From: Nick Van den Broeck <nick.van.den.broeck666@gmail.com>
Date: Sun, 10 Mar 2019 07:05:05 +0100
Subject: [PATCH] Issue #15 is finished

---
 src/libexpr/primops/flake.cc    |  13 ++--
 src/libexpr/primops/flake.hh    |   5 ++
 src/libexpr/primops/flakeref.cc |  15 +++++
 src/libexpr/primops/flakeref.hh |   3 +-
 src/nix/command.hh              |   2 +-
 src/nix/flake.cc                | 113 +++++++++++++++++++++++++++++---
 6 files changed, 136 insertions(+), 15 deletions(-)

diff --git a/src/libexpr/primops/flake.cc b/src/libexpr/primops/flake.cc
index 48a036875..b74e0b4b7 100644
--- a/src/libexpr/primops/flake.cc
+++ b/src/libexpr/primops/flake.cc
@@ -12,9 +12,14 @@
 
 namespace nix {
 
+Path getUserRegistryPath()
+{
+    return getHome() + "/.config/nix/registry.json";
+}
+
 /* Read the registry or a lock file. (Currently they have an identical
    format. */
-static std::unique_ptr<FlakeRegistry> readRegistry(const Path & path)
+std::unique_ptr<FlakeRegistry> readRegistry(const Path & path)
 {
     auto registry = std::make_unique<FlakeRegistry>();
 
@@ -40,7 +45,7 @@ void writeRegistry(FlakeRegistry registry, Path path)
     json["version"] = 1;
     json["flakes"] = {};
     for (auto elem : registry.entries) {
-        json["flakes"][elem.first] = elem.second.ref.to_string();
+        json["flakes"][elem.first] = { {"uri", elem.second.ref.to_string()} };
     }
     writeFile(path, json.dump(4)); // The '4' is the number of spaces used in the indentation in the json file.
 }
@@ -183,8 +188,8 @@ Flake getFlake(EvalState & state, const FlakeRef & flakeRef)
     if (std::get_if<FlakeRef::IsGitHub>(&newFlakeRef.data)) {
         FlakeSourceInfo srcInfo = fetchFlake(state, newFlakeRef);
         if (srcInfo.rev) {
-            std::string uri = flakeRef.to_string();
-            newFlakeRef = FlakeRef(uri + "/" + srcInfo.rev->to_string());
+            std::string uri = flakeRef.baseRef().to_string();
+            newFlakeRef = FlakeRef(uri + "/" + srcInfo.rev->to_string(Base16, false));
         }
     }
 
diff --git a/src/libexpr/primops/flake.hh b/src/libexpr/primops/flake.hh
index b3a755311..4e49becc7 100644
--- a/src/libexpr/primops/flake.hh
+++ b/src/libexpr/primops/flake.hh
@@ -14,14 +14,19 @@ struct FlakeRegistry
     {
         FlakeRef ref;
         Entry(const FlakeRef & flakeRef) : ref(flakeRef) {};
+        Entry operator=(const Entry & entry) { return Entry(entry.ref); }
     };
     std::map<FlakeId, Entry> entries;
 };
 
+Path getUserRegistryPath();
+
 Value * makeFlakeRegistryValue(EvalState & state);
 
 Value * makeFlakeValue(EvalState & state, std::string flakeUri, Value & v);
 
+std::unique_ptr<FlakeRegistry> readRegistry(const Path &);
+
 void writeRegistry(FlakeRegistry, Path);
 
 struct Flake
diff --git a/src/libexpr/primops/flakeref.cc b/src/libexpr/primops/flakeref.cc
index a2700f102..8e7c1f8df 100644
--- a/src/libexpr/primops/flakeref.cc
+++ b/src/libexpr/primops/flakeref.cc
@@ -152,4 +152,19 @@ bool FlakeRef::isImmutable() const
     else abort();
 }
 
+FlakeRef FlakeRef::baseRef() const // Removes the ref and rev from a FlakeRef.
+{
+    FlakeRef result(*this);
+    if (auto refData = std::get_if<FlakeRef::IsGitHub>(&result.data)) {
+        refData->ref = std::nullopt;
+        refData->rev = std::nullopt;
+    } else if (auto refData = std::get_if<FlakeRef::IsGit>(&result.data)) {
+        refData->ref = std::nullopt;
+        refData->rev = std::nullopt;
+    } else if (auto refData = std::get_if<FlakeRef::IsGit>(&result.data)) {
+        refData->ref = std::nullopt;
+        refData->rev = std::nullopt;
+    }
+    return result;
+}
 }
diff --git a/src/libexpr/primops/flakeref.hh b/src/libexpr/primops/flakeref.hh
index 4d1756b49..fb365e101 100644
--- a/src/libexpr/primops/flakeref.hh
+++ b/src/libexpr/primops/flakeref.hh
@@ -153,6 +153,7 @@ struct FlakeRef
     /* Check whether this is an "immutable" flake reference, that is,
        one that contains a commit hash or content hash. */
     bool isImmutable() const;
-};
 
+    FlakeRef baseRef() const;
+};
 }
diff --git a/src/nix/command.hh b/src/nix/command.hh
index c58d5d56e..ffe64ccb7 100644
--- a/src/nix/command.hh
+++ b/src/nix/command.hh
@@ -44,7 +44,7 @@ struct GitRepoCommand : virtual Args
     }
 };
 
-struct FlakeCommand : virtual Args, StoreCommand, MixEvalArgs
+struct FlakeCommand : virtual Args
 {
     std::string flakeUri;
 
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index a5a1d34db..fda903944 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -50,17 +50,12 @@ struct CmdFlakeUpdate : StoreCommand, GitRepoCommand, MixEvalArgs
     {
         auto evalState = std::make_shared<EvalState>(searchPath, store);
 
-        if (flakeUri == "") flakeUri = absPath("./flake.nix");
-        int result = updateLockFile(*evalState, flakeUri);
-        if (result == 1) {
-            std::cout << "You can only update local flakes, not flakes on GitHub.\n";
-        } else if (result == 2) {
-            std::cout << "You can only update local flakes, not flakes through their FlakeId.\n";
-        }
+        if (gitPath == "") gitPath = absPath(".");
+        updateLockFile(*evalState, gitPath);
     }
 };
 
-struct CmdFlakeInfo : FlakeCommand, MixJSON
+struct CmdFlakeInfo : FlakeCommand, MixJSON, MixEvalArgs, StoreCommand
 {
     std::string name() override
     {
@@ -88,12 +83,112 @@ struct CmdFlakeInfo : FlakeCommand, MixJSON
     }
 };
 
+struct CmdFlakeAdd : MixEvalArgs, Command
+{
+    std::string flakeId;
+    std::string flakeUri;
+
+    std::string name() override
+    {
+        return "add";
+    }
+
+    std::string description() override
+    {
+        return "upsert flake in user flake registry";
+    }
+
+    CmdFlakeAdd()
+    {
+        expectArg("flake-id", &flakeId);
+        expectArg("flake-uri", &flakeUri);
+    }
+
+    void run() override
+    {
+        FlakeRef newFlakeRef(flakeUri);
+        Path userRegistryPath = getUserRegistryPath();
+        auto userRegistry = readRegistry(userRegistryPath);
+        FlakeRegistry::Entry entry(newFlakeRef);
+        userRegistry->entries.erase(flakeId);
+        userRegistry->entries.insert_or_assign(flakeId, newFlakeRef);
+        writeRegistry(*userRegistry, userRegistryPath);
+    }
+};
+
+struct CmdFlakeRemove : virtual Args, MixEvalArgs, Command
+{
+    std::string flakeId;
+
+    std::string name() override
+    {
+        return "remove";
+    }
+
+    std::string description() override
+    {
+        return "remove flake from user flake registry";
+    }
+
+    CmdFlakeRemove()
+    {
+        expectArg("flake-id", &flakeId);
+    }
+
+    void run() override
+    {
+        Path userRegistryPath = getUserRegistryPath();
+        auto userRegistry = readRegistry(userRegistryPath);
+        userRegistry->entries.erase(flakeId);
+        writeRegistry(*userRegistry, userRegistryPath);
+    }
+};
+
+struct CmdFlakePin : virtual Args, StoreCommand, MixEvalArgs
+{
+    std::string flakeId;
+
+    std::string name() override
+    {
+        return "pin";
+    }
+
+    std::string description() override
+    {
+        return "pin flake require in user flake registry";
+    }
+
+    CmdFlakePin()
+    {
+        expectArg("flake-id", &flakeId);
+    }
+
+    void run(nix::ref<nix::Store> store) override
+    {
+        auto evalState = std::make_shared<EvalState>(searchPath, store);
+
+        Path userRegistryPath = getUserRegistryPath();
+        FlakeRegistry userRegistry = *readRegistry(userRegistryPath);
+        auto it = userRegistry.entries.find(flakeId);
+        if (it != userRegistry.entries.end()) {
+            FlakeRef oldRef = it->second.ref;
+            it->second.ref = getFlake(*evalState, oldRef).ref;
+            // The 'ref' in 'flake' is immutable.
+            writeRegistry(userRegistry, userRegistryPath);
+        } else
+            throw Error("the flake identifier '%s' does not exist in the user registry", flakeId);
+    }
+};
+
 struct CmdFlake : virtual MultiCommand, virtual Command
 {
     CmdFlake()
         : MultiCommand({make_ref<CmdFlakeList>()
+            , make_ref<CmdFlakeUpdate>()
             , make_ref<CmdFlakeInfo>()
-            , make_ref<CmdFlakeUpdate>()})
+            , make_ref<CmdFlakeAdd>()
+            , make_ref<CmdFlakeRemove>()
+            , make_ref<CmdFlakePin>()})
     {
     }