about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2017-07-27T16·08+0200
committerEelco Dolstra <edolstra@gmail.com>2017-07-27T16·08+0200
commit7480f4f9a478d294eaa095c101df7c0543c1fd71 (patch)
tree0e4c3a5cac785ccc8f7d78beffeaea32b27744fd
parent9f64cb89cbbd0cd0540ad99e3578b6cecd385a81 (diff)
builtins.fetchgit: Support specifying commit hashes
This adds an argument "rev" specififying the Git commit hash. The
existing argument "rev" is renamed to "ref". The default value for
"ref" is "master". When specifying a hash, it's necessary to specify a
ref since we're not cloning the entire repository but only fetching a
specific ref.

Example usage:

  builtins.fetchgit {
    url = https://github.com/NixOS/nixpkgs.git;
    ref = "release-16.03";
    rev = "c1c0484041ab6f9c6858c8ade80a8477c9ae4442";
  };
-rw-r--r--src/libexpr/primops/fetchgit.cc34
-rw-r--r--src/libexpr/primops/fetchgit.hh4
2 files changed, 29 insertions, 9 deletions
diff --git a/src/libexpr/primops/fetchgit.cc b/src/libexpr/primops/fetchgit.cc
index 3ab2644c8b04..1e01a452b7e7 100644
--- a/src/libexpr/primops/fetchgit.cc
+++ b/src/libexpr/primops/fetchgit.cc
@@ -6,13 +6,27 @@
 
 #include <sys/time.h>
 
+#include <regex>
+
 namespace nix {
 
-Path exportGit(ref<Store> store, const std::string & uri, const std::string & rev)
+Path exportGit(ref<Store> store, const std::string & uri,
+    const std::string & ref, const std::string & rev)
 {
     if (!isUri(uri))
         throw EvalError(format("‘%s’ is not a valid URI") % uri);
 
+    if (rev != "") {
+        std::regex revRegex("^[0-9a-fA-F]{40}$");
+        if (!std::regex_match(rev, revRegex))
+            throw Error("invalid Git revision ‘%s’", rev);
+    }
+
+    // FIXME: too restrictive, but better safe than sorry.
+    std::regex refRegex("^[0-9a-zA-Z][0-9a-zA-Z.-]+$");
+    if (!std::regex_match(ref, refRegex))
+        throw Error("invalid Git ref ‘%s’", ref);
+
     Path cacheDir = getCacheDir() + "/nix/git";
 
     if (!pathExists(cacheDir)) {
@@ -22,7 +36,7 @@ Path exportGit(ref<Store> store, const std::string & uri, const std::string & re
 
     //Activity act(*logger, lvlInfo, format("fetching Git repository ‘%s’") % uri);
 
-    std::string localRef = hashString(htSHA256, fmt("%s-%s", uri, rev)).to_string(Base32, false);
+    std::string localRef = hashString(htSHA256, fmt("%s-%s", uri, ref)).to_string(Base32, false);
 
     Path localRefFile = cacheDir + "/refs/heads/" + localRef;
 
@@ -33,7 +47,7 @@ Path exportGit(ref<Store> store, const std::string & uri, const std::string & re
     if (stat(localRefFile.c_str(), &st) != 0 ||
         st.st_mtime < now - settings.tarballTtl)
     {
-        runProgram("git", true, { "-C", cacheDir, "fetch", "--force", uri, rev + ":" + localRef });
+        runProgram("git", true, { "-C", cacheDir, "fetch", "--force", uri, ref + ":" + localRef });
 
         struct timeval times[2];
         times[0].tv_sec = now;
@@ -44,7 +58,9 @@ Path exportGit(ref<Store> store, const std::string & uri, const std::string & re
         utimes(localRefFile.c_str(), times);
     }
 
-    std::string commitHash = chomp(readFile(localRefFile));
+    // FIXME: check whether rev is an ancestor of ref.
+    std::string commitHash =
+        rev != "" ? rev : chomp(readFile(localRefFile));
 
     printTalkative("using revision %s of repo ‘%s’", uri, commitHash);
 
@@ -81,7 +97,8 @@ static void prim_fetchgit(EvalState & state, const Pos & pos, Value * * args, Va
     if (state.restricted) throw Error("‘fetchgit’ is not allowed in restricted mode");
 
     std::string url;
-    std::string rev = "master";
+    std::string ref = "master";
+    std::string rev;
 
     state.forceValue(*args[0]);
 
@@ -95,7 +112,10 @@ static void prim_fetchgit(EvalState & state, const Pos & pos, Value * * args, Va
                 PathSet context;
                 url = state.coerceToString(*attr.pos, *attr.value, context, false, false);
                 if (hasPrefix(url, "/")) url = "file://" + url;
-            } else if (name == "rev")
+            }
+            else if (name == "ref")
+                ref = state.forceStringNoCtx(*attr.value, *attr.pos);
+            else if (name == "rev")
                 rev = state.forceStringNoCtx(*attr.value, *attr.pos);
             else
                 throw EvalError("unsupported argument ‘%s’ to ‘fetchgit’, at %s", attr.name, *attr.pos);
@@ -107,7 +127,7 @@ static void prim_fetchgit(EvalState & state, const Pos & pos, Value * * args, Va
     } else
         url = state.forceStringNoCtx(*args[0], pos);
 
-    Path storePath = exportGit(state.store, url, rev);
+    Path storePath = exportGit(state.store, url, ref, rev);
 
     mkString(v, storePath, PathSet({storePath}));
 }
diff --git a/src/libexpr/primops/fetchgit.hh b/src/libexpr/primops/fetchgit.hh
index 6ffb21a96daa..ff228f3b3c6a 100644
--- a/src/libexpr/primops/fetchgit.hh
+++ b/src/libexpr/primops/fetchgit.hh
@@ -8,7 +8,7 @@ namespace nix {
 
 class Store;
 
-Path exportGit(ref<Store> store,
-    const std::string & uri, const std::string & rev);
+Path exportGit(ref<Store> store, const std::string & uri,
+    const std::string & ref, const std::string & rev = "");
 
 }