Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/libexpr/primops/fetchTree.cc
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,11 @@ static RegisterPrimOp primop_fetchGit({
true, it's possible to load a `rev` from *any* `ref` (by default only
`rev`s from the specified `ref` are supported).

- date\
A `git` date specification which can specify an absolute date (e.g.
`2000-01-01`) or a date relative to the specified reference (e.g.
`1 week ago`)

Here are some examples of how to use `fetchGit`.

- To fetch a private repository over SSH:
Expand Down
7 changes: 7 additions & 0 deletions src/libfetchers/fetchers.cc
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,13 @@ std::optional<std::string> Input::getRef() const
return {};
}

std::optional<std::string> Input::getDate() const
{
if (auto s = maybeGetStrAttr(attrs, "date"))
return *s;
return {};
}

std::optional<Hash> Input::getRev() const
{
std::optional<Hash> hash = {};
Expand Down
1 change: 1 addition & 0 deletions src/libfetchers/fetchers.hh
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ public:
std::string getType() const;
std::optional<Hash> getNarHash() const;
std::optional<std::string> getRef() const;
std::optional<std::string> getDate() const;
std::optional<Hash> getRev() const;
std::optional<uint64_t> getRevCount() const;
std::optional<time_t> getLastModified() const;
Expand Down
42 changes: 33 additions & 9 deletions src/libfetchers/git.cc
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ 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" && name != "allRefs" && name != "name")
if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "shallow" && name != "submodules" && name != "lastModified" && name != "revCount" && name != "narHash" && name != "allRefs" && name != "name" && name != "date")
throw Error("unsupported Git input attribute '%s'", name);

parseURL(getStrAttr(attrs, "url"));
Expand Down Expand Up @@ -442,9 +442,9 @@ struct GitInputScheme : InputScheme
auto [isLocal, actualUrl_] = getActualUrl(input);
auto actualUrl = actualUrl_; // work around clang bug

/* If this is a local directory and no ref or revision is given,
/* If this is a local directory and no ref, revision, or date is given,
allow fetching directly from a dirty workdir. */
if (!input.getRef() && !input.getRev() && isLocal) {
if (!input.getRef() && !input.getRev() && !input.getDate() && isLocal) {
auto workdirInfo = getWorkdirInfo(input, actualUrl);
if (!workdirInfo.clean) {
return fetchFromWorkdir(store, input, actualUrl, workdirInfo);
Expand All @@ -470,11 +470,20 @@ struct GitInputScheme : InputScheme
unlockedAttrs.insert_or_assign("ref", *head);
}

if (!input.getRev())
input.attrs.insert_or_assign("rev",
Hash::parseAny(chomp(runProgram("git", true, { "-C", actualUrl, "--git-dir", gitDir, "rev-parse", *input.getRef() })), htSHA1).gitRev());

repoDir = actualUrl;

if (!input.getRev()) {
std::string rev = "";
if (input.getDate()) {
rev = chomp(runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "rev-list", "--before", *input.getDate(), "-1", *input.getRef() }));
if (rev == "") {
rev = chomp(runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "rev-list", "--max-parents=0", "-1", *input.getRef() }));
}
} else {
rev = chomp(runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "rev-parse", *input.getRef() }));
}
input.attrs.insert_or_assign("rev", Hash::parseAny(rev, htSHA1).gitRev());
}
} else {
const bool useHeadRef = !input.getRef();
if (useHeadRef) {
Expand All @@ -491,6 +500,10 @@ struct GitInputScheme : InputScheme
}
}

if (input.getDate()) {
unlockedAttrs.insert_or_assign("date", input.getDate().value());
}

if (auto res = getCache()->lookup(store, unlockedAttrs)) {
auto rev2 = Hash::parseAny(getStrAttr(res->first, "rev"), htSHA1);
if (!input.getRev() || input.getRev() == rev2) {
Expand Down Expand Up @@ -569,8 +582,19 @@ struct GitInputScheme : InputScheme
warn("could not update cached head '%s' for '%s'", *input.getRef(), actualUrl);
}

if (!input.getRev())
input.attrs.insert_or_assign("rev", Hash::parseAny(chomp(readFile(localRefFile)), htSHA1).gitRev());
std::string rev = "";
if (!input.getRev()) {
auto startingRevision = chomp(readFile(localRefFile));
if (input.getDate()) {
rev = chomp(runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "rev-list", "--before", *input.getDate(), "-1", startingRevision }));
if (rev == "") {
rev = chomp(runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "rev-list", "--max-parents=0", "-1", startingRevision }));
}
} else {
rev = startingRevision;
}
input.attrs.insert_or_assign("rev", Hash::parseAny(rev, htSHA1).gitRev());
}

// cache dir lock is removed at scope end; we will only use read-only operations on specific revisions in the remainder
}
Expand Down
17 changes: 17 additions & 0 deletions tests/fetchGit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,23 @@ rev_tag2=$(git -C $repo rev-parse refs/tags/tag2)
[[ $rev_tag2_nix = $rev_tag2 ]]
unset _NIX_FORCE_HTTP

# The date argument works for both local repos and "remote" repos, returning the
# first commit preceding the specified commit
timestamp="$(date '+%s')"
sleep 1
git -C $repo commit -m 'Bla6' --allow-empty

rev4_date=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; date = \"$timestamp\"; }).rev")
[[ $rev4 = $rev4_date ]]
export _NIX_FORCE_HTTP=1
rev4_date=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; date = \"$timestamp\"; }).rev")
[[ $rev4 = $rev4_date ]]
unset _NIX_FORCE_HTTP

# A date prior to the first commit returns the first commit instead of failing
rev1_date=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; date = \"1 year ago\"; }).rev")
[[ $rev1 = $rev1_date ]]

# should fail if there is no repo
rm -rf $repo/.git
(! nix eval --impure --raw --expr "(builtins.fetchGit \"file://$repo\").outPath")
Expand Down