summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEelco Dolstra <edolstra@gmail.com>2020-12-22 16:39:32 +0100
committerEelco Dolstra <edolstra@gmail.com>2020-12-22 16:39:32 +0100
commitf4a9fb67da5bc55221be451556710dfacb39eda0 (patch)
tree91c3d568e09772b714dba86fa66ebc9eea6eae74
parente27044216bf597710893e0366dbff60efbdaf0a6 (diff)
parent897ae235fc2cef0ce711470a7b620241d82a1b09 (diff)
Merge branch 'git-rev-error' of https://github.com/Ma27/nix into master
-rw-r--r--src/libexpr/primops/fetchTree.cc5
-rw-r--r--src/libfetchers/git.cc49
-rw-r--r--tests/fetchGit.sh13
3 files changed, 58 insertions, 9 deletions
diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc
index 6e7ddde8e..133299030 100644
--- a/src/libexpr/primops/fetchTree.cc
+++ b/src/libexpr/primops/fetchTree.cc
@@ -324,6 +324,11 @@ static RegisterPrimOp primop_fetchGit({
A Boolean parameter that specifies whether submodules should be
checked out. Defaults to `false`.
+ - allRefs
+ Whether to fetch all refs of the repository. With this argument being
+ true, it's possible to load a `rev` from *any* `ref` (by default only
+ `rev`s from the specified `ref` are supported).
+
Here are some examples of how to use `fetchGit`.
- To fetch a private repository over SSH:
diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc
index e7712c5fd..81c647f89 100644
--- a/src/libfetchers/git.cc
+++ b/src/libfetchers/git.cc
@@ -59,12 +59,13 @@ struct GitInputScheme : InputScheme
if (maybeGetStrAttr(attrs, "type") != "git") return {};
for (auto & [name, value] : attrs)
- if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "shallow" && name != "submodules" && name != "lastModified" && name != "revCount" && name != "narHash")
+ if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "shallow" && name != "submodules" && name != "lastModified" && name != "revCount" && name != "narHash" && name != "allRefs")
throw Error("unsupported Git input attribute '%s'", name);
parseURL(getStrAttr(attrs, "url"));
maybeGetBoolAttr(attrs, "shallow");
maybeGetBoolAttr(attrs, "submodules");
+ maybeGetBoolAttr(attrs, "allRefs");
if (auto ref = maybeGetStrAttr(attrs, "ref")) {
if (std::regex_search(*ref, badGitRefRegex))
@@ -169,10 +170,12 @@ struct GitInputScheme : InputScheme
bool shallow = maybeGetBoolAttr(input.attrs, "shallow").value_or(false);
bool submodules = maybeGetBoolAttr(input.attrs, "submodules").value_or(false);
+ bool allRefs = maybeGetBoolAttr(input.attrs, "allRefs").value_or(false);
std::string cacheType = "git";
if (shallow) cacheType += "-shallow";
if (submodules) cacheType += "-submodules";
+ if (allRefs) cacheType += "-all-refs";
auto getImmutableAttrs = [&]()
{
@@ -338,11 +341,15 @@ struct GitInputScheme : InputScheme
}
}
} else {
- /* If the local ref is older than ‘tarball-ttl’ seconds, do a
- git fetch to update the local ref to the remote ref. */
- struct stat st;
- doFetch = stat(localRefFile.c_str(), &st) != 0 ||
- (uint64_t) st.st_mtime + settings.tarballTtl <= (uint64_t) now;
+ if (allRefs) {
+ doFetch = true;
+ } else {
+ /* If the local ref is older than ‘tarball-ttl’ seconds, do a
+ git fetch to update the local ref to the remote ref. */
+ struct stat st;
+ doFetch = stat(localRefFile.c_str(), &st) != 0 ||
+ (uint64_t) st.st_mtime + settings.tarballTtl <= (uint64_t) now;
+ }
}
if (doFetch) {
@@ -352,9 +359,11 @@ struct GitInputScheme : InputScheme
// we're using --quiet for now. Should process its stderr.
try {
auto ref = input.getRef();
- auto fetchRef = ref->compare(0, 5, "refs/") == 0
- ? *ref
- : "refs/heads/" + *ref;
+ auto fetchRef = allRefs
+ ? "refs/*"
+ : ref->compare(0, 5, "refs/") == 0
+ ? *ref
+ : "refs/heads/" + *ref;
runProgram("git", true, { "-C", repoDir, "fetch", "--quiet", "--force", "--", actualUrl, fmt("%s:%s", fetchRef, fetchRef) });
} catch (Error & e) {
if (!pathExists(localRefFile)) throw;
@@ -392,6 +401,28 @@ struct GitInputScheme : InputScheme
AutoDelete delTmpDir(tmpDir, true);
PathFilter filter = defaultPathFilter;
+ RunOptions checkCommitOpts(
+ "git",
+ { "-C", repoDir, "cat-file", "commit", input.getRev()->gitRev() }
+ );
+ checkCommitOpts.searchPath = true;
+ checkCommitOpts.mergeStderrToStdout = true;
+
+ auto result = runProgram(checkCommitOpts);
+ if (WEXITSTATUS(result.first) == 128
+ && result.second.find("bad file") != std::string::npos
+ ) {
+ throw Error(
+ "Cannot find Git revision '%s' in ref '%s' of repository '%s'! "
+ "Please make sure that the " ANSI_BOLD "rev" ANSI_NORMAL " exists on the "
+ ANSI_BOLD "ref" ANSI_NORMAL " you've specified or add " ANSI_BOLD
+ "allRefs = true;" ANSI_NORMAL " to " ANSI_BOLD "fetchGit" ANSI_NORMAL ".",
+ input.getRev()->gitRev(),
+ *input.getRef(),
+ actualUrl
+ );
+ }
+
if (submodules) {
Path tmpGitDir = createTempDir();
AutoDelete delTmpGitDir(tmpGitDir, true);
diff --git a/tests/fetchGit.sh b/tests/fetchGit.sh
index 76390fa59..1e8963d76 100644
--- a/tests/fetchGit.sh
+++ b/tests/fetchGit.sh
@@ -41,6 +41,19 @@ export _NIX_FORCE_HTTP=1
path=$(nix eval --impure --raw --expr "(builtins.fetchGit file://$repo).outPath")
[[ $(cat $path/hello) = world ]]
+# Fetch a rev from another branch
+git -C $repo checkout -b devtest
+echo "different file" >> $TEST_ROOT/git/differentbranch
+git -C $repo add differentbranch
+git -C $repo commit -m 'Test2'
+git -C $repo checkout master
+devrev=$(git -C $repo rev-parse devtest)
+out=$(nix eval --impure --raw --expr "builtins.fetchGit { url = file://$repo; rev = \"$devrev\"; }" 2>&1) || status=$?
+[[ $status == 1 ]]
+[[ $out =~ 'Cannot find Git revision' ]]
+
+[[ $(nix eval --raw --expr "builtins.readFile (builtins.fetchGit { url = file://$repo; rev = \"$devrev\"; allRefs = true; } + \"/differentbranch\")") = 'different file' ]]
+
# In pure eval mode, fetchGit without a revision should fail.
[[ $(nix eval --impure --raw --expr "builtins.readFile (fetchGit file://$repo + \"/hello\")") = world ]]
(! nix eval --raw --expr "builtins.readFile (fetchGit file://$repo + \"/hello\")")