diff --git a/src/libutil/git.cc b/src/libutil/git.cc new file mode 100644 index 00000000000..b26226256e9 --- /dev/null +++ b/src/libutil/git.cc @@ -0,0 +1,65 @@ +#include "archive.hh" +#include "hash.hh" +#include "util.hh" +#include "config.hh" + +using namespace std::string_literals; + +namespace nix { + +void dumpGit(const Path & path, Sink & sink, PathFilter & filter) +{ + struct stat st; + if (lstat(path.c_str(), &st)) + throw SysError(format("getting attributes of path '%1%'") % path); + + if (S_ISREG(st.st_mode)) { + auto s = (format("blob %d\0%s"s) % std::to_string(st.st_size) % readFile(path)).str(); + + vector v; + std::copy(s.begin(), s.end(), std::back_inserter(v)); + sink(v.data(), v.size()); + } + + else if (S_ISDIR(st.st_mode)) { + std::string s1 = ""; + + std::map entries; + for (auto & i : readDirectory(path)) + entries[i.name] = i.name; + + for (auto & i : entries) + if (filter(path + "/" + i.first)) { + HashSink hashSink(htSHA1); + dumpGit(path + "/" + i.first, hashSink, filter); + auto hash = hashSink.finish().first; + + struct stat st2; + if (lstat((path + "/" + i.first).c_str(), &st2)) + throw SysError(format("getting attributes of path '%1%'") % (path + "/" + i.first)); + + unsigned int perm; + if (S_ISDIR(st2.st_mode)) + perm = 40000; + else if (S_ISREG(st2.st_mode)) { + if (st2.st_mode & S_IXUSR) + perm = 100755; + else + perm = 100644; + } else + throw Error(format("file '%1%' has an unsupported type") % (path + "/" + i.first)); + + s1 += (format("%6d %s\0%s"s) % perm % i.first % hash.hash).str(); + } + + std::string s2 = (format("tree %d\0%s"s) % s1.size() % s1).str(); + + vector v; + std::copy(s2.begin(), s2.end(), std::back_inserter(v)); + sink(v.data(), v.size()); + } + + else throw Error(format("file '%1%' has an unsupported type") % path); +} + +} diff --git a/src/libutil/git.hh b/src/libutil/git.hh new file mode 100644 index 00000000000..e2d978aafae --- /dev/null +++ b/src/libutil/git.hh @@ -0,0 +1,10 @@ +#pragma once + +#include "types.hh" +#include "serialise.hh" + +namespace nix { + +void dumpGit(const Path & path, Sink & sink, PathFilter & filter = defaultPathFilter); + +} diff --git a/src/nix/hash.cc b/src/nix/hash.cc index 366314227c8..cce81ba0bf6 100644 --- a/src/nix/hash.cc +++ b/src/nix/hash.cc @@ -4,12 +4,13 @@ #include "shared.hh" #include "references.hh" #include "archive.hh" +#include "git.hh" using namespace nix; struct CmdHash : Command { - enum Mode { mFile, mPath }; + enum Mode { mFile, mPath, mGit }; Mode mode; Base base = SRI; bool truncate = false; @@ -36,9 +37,12 @@ struct CmdHash : Command std::string description() override { - return mode == mFile - ? "print cryptographic hash of a regular file" - : "print cryptographic hash of the NAR serialisation of a path"; + switch (mode) { + case mFile: return "print cryptographic hash of a regular file"; + case mPath: return "print cryptographic hash of the NAR serialisation of a path"; + case mGit: return "print cryptographic hash of the Git serialisation of a path"; + } + throw; } Category category() override { return catUtility; } @@ -55,6 +59,8 @@ struct CmdHash : Command if (mode == mFile) readFile(path, *hashSink); + else if (mode == mGit) + dumpGit(path, *hashSink); else dumpPath(path, *hashSink); @@ -67,6 +73,7 @@ struct CmdHash : Command static RegisterCommand r1("hash-file", [](){ return make_ref(CmdHash::mFile); }); static RegisterCommand r2("hash-path", [](){ return make_ref(CmdHash::mPath); }); +static RegisterCommand r3("hash-git", [](){ return make_ref(CmdHash::mGit); }); struct CmdToBase : Command { @@ -98,10 +105,10 @@ struct CmdToBase : Command } }; -static RegisterCommand r3("to-base16", [](){ return make_ref(Base16); }); -static RegisterCommand r4("to-base32", [](){ return make_ref(Base32); }); -static RegisterCommand r5("to-base64", [](){ return make_ref(Base64); }); -static RegisterCommand r6("to-sri", [](){ return make_ref(SRI); }); +static RegisterCommand r4("to-base16", [](){ return make_ref(Base16); }); +static RegisterCommand r5("to-base32", [](){ return make_ref(Base32); }); +static RegisterCommand r6("to-base64", [](){ return make_ref(Base64); }); +static RegisterCommand r7("to-sri", [](){ return make_ref(SRI); }); /* Legacy nix-hash command. */ static int compatNixHash(int argc, char * * argv) diff --git a/tests/hash.sh b/tests/hash.sh index 4cfc9790101..33b5bef7595 100644 --- a/tests/hash.sh +++ b/tests/hash.sh @@ -85,3 +85,18 @@ try3() { try3 sha1 "800d59cfcd3c05e900cb4e214be48f6b886a08df" "vw46m23bizj4n8afrc0fj19wrp7mj3c0" "gA1Zz808BekAy04hS+SPa4hqCN8=" try3 sha256 "ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad" "1b8m03r63zqhnjf7l5wnldhh7c134ap5vpj0850ymkq1iyzicy5s" "ungWv48Bz+pBQUDeXa4iI7ADYaOWF3qctBD/YfIAFa0=" try3 sha512 "204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445" "12k9jiq29iyqm03swfsgiw5mlqs173qazm3n7daz43infy12pyrcdf30fkk3qwv4yl2ick8yipc2mqnlh48xsvvxl60lbx8vp38yji0" "IEqPxt2oLwoM7XvrjgikFlfBbvRosiioJ5vjMacDwzWW/RXBOxsH+aodO+pXeJygMa2Fx6cd1wNU7GMSOMo0RQ==" + +# Git. +try4 () { + hash=$(nix hash-git --base16 --type sha1 $TEST_ROOT/hash-path) + if test "$hash" != "$1"; then + echo "git hash, expected $1, got $hash" + exit 1 + fi +} + +rm -rf $TEST_ROOT/hash-path +mkdir $TEST_ROOT/hash-path +echo "Hello World" > $TEST_ROOT/hash-path/hello + +try4 "117c62a8c5e01758bd284126a6af69deab9dbbe2"