diff --git a/src/libutil/git.cc b/src/libutil/git.cc index 1f44aa1e1f2..7e8f25b3e14 100644 --- a/src/libutil/git.cc +++ b/src/libutil/git.cc @@ -51,53 +51,74 @@ static void parse(ParseSink & sink, Source & source, const Path & path) { else { } } -// Internal version, returns the perm. -unsigned int dumpGitInternal(const Path & path, Sink & sink, PathFilter & filter) + +// TODO stream file into sink, rather than reading into vector +GitMode dumpGitBlob(const Path & path, const struct stat st, Sink & sink) +{ + 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()); + return st.st_mode & S_IXUSR + ? GitMode::Executable + : GitMode::Regular; +} + +GitMode dumpGitTree(const GitTree & entries, Sink & sink) { + std::string s1 = ""; + for (auto & i : entries) { + unsigned int mode; + switch (i.second.first) { + case GitMode::Directory: mode = 40000; break; + case GitMode::Executable: mode = 100755; break; + case GitMode::Regular: mode = 100644; break; + } + s1 += (format("%6d %s\0%s"s) % mode % i.first % i.second.second.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()); + return GitMode::Directory; +} + +// Returns the perm in addition +std::pair dumpGitHashInternal( + std::function()> makeHashSink, + const Path & path, PathFilter & filter) +{ + auto hashSink = makeHashSink(); struct stat st; + GitMode perm; 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()); - return st.st_mode & S_IXUSR - ? 100755 - : 100644; - } - - else if (S_ISDIR(st.st_mode)) { - std::string s1 = ""; - - std::map entries; + perm = dumpGitBlob(path, st, *hashSink); + } else if (S_ISDIR(st.st_mode)) { + GitTree entries; for (auto & i : readDirectory(path)) - entries[i.name] = i.name; - - for (auto & i : entries) - if (filter(path + "/" + i.first)) { - HashSink hashSink(htSHA1); - unsigned int perm = dumpGitInternal(path + "/" + i.first, hashSink, filter); - auto hash = hashSink.finish().first; - s1 += (format("%6d %s\0%s"s) % perm % i.first % hash.hash).str(); + if (filter(path + "/" + i.name)) { + entries[i.name] = dumpGitHashInternal(makeHashSink, path + "/" + i.name, filter); } - - 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()); - return 40000; + perm = dumpGitTree(entries, *hashSink); + } else { + throw Error(format("file '%1%' has an unsupported type") % path); } - else throw Error(format("file '%1%' has an unsupported type") % path); + auto hash = hashSink->finish().first; + return std::pair { perm, hash }; } -void dumpGit(const Path & path, Sink & sink, PathFilter & filter) +Hash dumpGitHash( + std::function()> makeHashSink, + const Path & path, PathFilter & filter) { - dumpGitInternal(path, sink, filter); + return dumpGitHashInternal(makeHashSink, path, filter).second; } } diff --git a/src/libutil/git.hh b/src/libutil/git.hh index f87df52d8cc..7b11f72be29 100644 --- a/src/libutil/git.hh +++ b/src/libutil/git.hh @@ -16,6 +16,20 @@ void restoreGit(const Path & path, Source & source); void parseGit(ParseSink & sink, Source & source); -void dumpGit(const Path & path, Sink & sink, PathFilter & filter = defaultPathFilter); +// Dumps a single file to a sink +GitMode dumpGitBlob(const Path & path, const struct stat st, Sink & sink); +typedef std::map> GitTree; + +// Dumps a representation of a git tree to a sink +GitMode dumpGitTree(const GitTree & entries, Sink & sink); + +// Recursively dumps path, hashing as we go +Hash dumpGitHash( + std::function()>, + const Path & path, + PathFilter & filter = defaultPathFilter); + +// N.B. There is no way to recursively dump to a sink, as that doesn't make +// sense with the git hash/data model where the information is Merklized. } diff --git a/src/nix/hash.cc b/src/nix/hash.cc index db3f00d677a..3a8c82da17b 100644 --- a/src/nix/hash.cc +++ b/src/nix/hash.cc @@ -36,14 +36,17 @@ struct CmdHash : Command std::string description() override { - const char* d; + const char *d; switch (mode) { case FileIngestionMethod::Flat: d = "print cryptographic hash of a regular file"; + break; case FileIngestionMethod::Recursive: d = "print cryptographic hash of the NAR serialisation of a path"; + break; case FileIngestionMethod::Git: d = "print cryptographic hash of the Git serialisation of a path"; + break; }; return d; } @@ -78,9 +81,7 @@ struct CmdHash : Command break; } case FileIngestionMethod::Git: - auto hashSink = makeHashSink(); - dumpGit(path, *hashSink); - h = hashSink->finish().first; + h = dumpGitHash(makeHashSink, path); break; }