Merge pull request #9653 from obsidiansystems/improve-parse-sink

Improve the `ParseSink` interface
This commit is contained in:
Robert Hensing 2024-01-23 01:04:57 +01:00 committed by GitHub
commit 08bf2846df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 383 additions and 245 deletions

View File

@ -441,7 +441,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
eagerly consume the entire stream it's given, past the eagerly consume the entire stream it's given, past the
length of the Nar. */ length of the Nar. */
TeeSource savedNARSource(from, saved); TeeSource savedNARSource(from, saved);
NullParseSink sink; /* just parse the NAR */ NullFileSystemObjectSink sink; /* just parse the NAR */
parseDump(sink, savedNARSource); parseDump(sink, savedNARSource);
} else { } else {
/* Incrementally parse the NAR file, stripping the /* Incrementally parse the NAR file, stripping the
@ -913,7 +913,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
source = std::make_unique<TunnelSource>(from, to); source = std::make_unique<TunnelSource>(from, to);
else { else {
TeeSource tee { from, saved }; TeeSource tee { from, saved };
NullParseSink ether; NullFileSystemObjectSink ether;
parseDump(ether, tee); parseDump(ether, tee);
source = std::make_unique<StringSource>(saved.s); source = std::make_unique<StringSource>(saved.s);
} }

View File

@ -65,7 +65,7 @@ StorePaths Store::importPaths(Source & source, CheckSigsFlag checkSigs)
/* Extract the NAR from the source. */ /* Extract the NAR from the source. */
StringSink saved; StringSink saved;
TeeSource tee { source, saved }; TeeSource tee { source, saved };
NullParseSink ether; NullFileSystemObjectSink ether;
parseDump(ether, tee); parseDump(ether, tee);
uint32_t magic = readInt(source); uint32_t magic = readInt(source);

View File

@ -1048,7 +1048,7 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
bool narRead = false; bool narRead = false;
Finally cleanup = [&]() { Finally cleanup = [&]() {
if (!narRead) { if (!narRead) {
NullParseSink sink; NullFileSystemObjectSink sink;
try { try {
parseDump(sink, source); parseDump(sink, source);
} catch (...) { } catch (...) {

View File

@ -19,6 +19,35 @@ struct NarMember
std::map<std::string, NarMember> children; std::map<std::string, NarMember> children;
}; };
struct NarMemberConstructor : CreateRegularFileSink
{
private:
NarMember & narMember;
uint64_t & pos;
public:
NarMemberConstructor(NarMember & nm, uint64_t & pos)
: narMember(nm), pos(pos)
{ }
void isExecutable() override
{
narMember.stat.isExecutable = true;
}
void preallocateContents(uint64_t size) override
{
narMember.stat.fileSize = size;
narMember.stat.narOffset = pos;
}
void operator () (std::string_view data) override
{ }
};
struct NarAccessor : public SourceAccessor struct NarAccessor : public SourceAccessor
{ {
std::optional<const std::string> nar; std::optional<const std::string> nar;
@ -27,7 +56,7 @@ struct NarAccessor : public SourceAccessor
NarMember root; NarMember root;
struct NarIndexer : ParseSink, Source struct NarIndexer : FileSystemObjectSink, Source
{ {
NarAccessor & acc; NarAccessor & acc;
Source & source; Source & source;
@ -42,7 +71,7 @@ struct NarAccessor : public SourceAccessor
: acc(acc), source(source) : acc(acc), source(source)
{ } { }
void createMember(const Path & path, NarMember member) NarMember & createMember(const Path & path, NarMember member)
{ {
size_t level = std::count(path.begin(), path.end(), '/'); size_t level = std::count(path.begin(), path.end(), '/');
while (parents.size() > level) parents.pop(); while (parents.size() > level) parents.pop();
@ -50,11 +79,14 @@ struct NarAccessor : public SourceAccessor
if (parents.empty()) { if (parents.empty()) {
acc.root = std::move(member); acc.root = std::move(member);
parents.push(&acc.root); parents.push(&acc.root);
return acc.root;
} else { } else {
if (parents.top()->stat.type != Type::tDirectory) if (parents.top()->stat.type != Type::tDirectory)
throw Error("NAR file missing parent directory of path '%s'", path); throw Error("NAR file missing parent directory of path '%s'", path);
auto result = parents.top()->children.emplace(baseNameOf(path), std::move(member)); auto result = parents.top()->children.emplace(baseNameOf(path), std::move(member));
parents.push(&result.first->second); auto & ref = result.first->second;
parents.push(&ref);
return ref;
} }
} }
@ -68,34 +100,18 @@ struct NarAccessor : public SourceAccessor
} }); } });
} }
void createRegularFile(const Path & path) override void createRegularFile(const Path & path, std::function<void(CreateRegularFileSink &)> func) override
{ {
createMember(path, NarMember{ .stat = { auto & nm = createMember(path, NarMember{ .stat = {
.type = Type::tRegular, .type = Type::tRegular,
.fileSize = 0, .fileSize = 0,
.isExecutable = false, .isExecutable = false,
.narOffset = 0 .narOffset = 0
} }); } });
NarMemberConstructor nmc { nm, pos };
func(nmc);
} }
void closeRegularFile() override
{ }
void isExecutable() override
{
parents.top()->stat.isExecutable = true;
}
void preallocateContents(uint64_t size) override
{
auto & st = parents.top()->stat;
st.fileSize = size;
st.narOffset = pos;
}
void receiveContents(std::string_view data) override
{ }
void createSymlink(const Path & path, const std::string & target) override void createSymlink(const Path & path, const std::string & target) override
{ {
createMember(path, createMember(path,

View File

@ -424,12 +424,12 @@ ValidPathInfo Store::addToStoreSlow(
information to narSink. */ information to narSink. */
TeeSource tapped { *fileSource, narSink }; TeeSource tapped { *fileSource, narSink };
NullParseSink blank; NullFileSystemObjectSink blank;
auto & parseSink = method.getFileIngestionMethod() == FileIngestionMethod::Flat auto & parseSink = method.getFileIngestionMethod() == FileIngestionMethod::Flat
? (ParseSink &) fileSink ? (FileSystemObjectSink &) fileSink
: method.getFileIngestionMethod() == FileIngestionMethod::Recursive : method.getFileIngestionMethod() == FileIngestionMethod::Recursive
? (ParseSink &) blank ? (FileSystemObjectSink &) blank
: (abort(), (ParseSink &)*(ParseSink *)nullptr); // handled both cases : (abort(), (FileSystemObjectSink &)*(FileSystemObjectSink *)nullptr); // handled both cases
/* The information that flows from tapped (besides being replicated in /* The information that flows from tapped (besides being replicated in
narSink), is now put in parseSink. */ narSink), is now put in parseSink. */

View File

@ -133,7 +133,7 @@ static SerialisationError badArchive(const std::string & s)
} }
static void parseContents(ParseSink & sink, Source & source, const Path & path) static void parseContents(CreateRegularFileSink & sink, Source & source)
{ {
uint64_t size = readLongLong(source); uint64_t size = readLongLong(source);
@ -147,7 +147,7 @@ static void parseContents(ParseSink & sink, Source & source, const Path & path)
auto n = buf.size(); auto n = buf.size();
if ((uint64_t)n > left) n = left; if ((uint64_t)n > left) n = left;
source(buf.data(), n); source(buf.data(), n);
sink.receiveContents({buf.data(), n}); sink({buf.data(), n});
left -= n; left -= n;
} }
@ -164,109 +164,121 @@ struct CaseInsensitiveCompare
}; };
static void parse(ParseSink & sink, Source & source, const Path & path) static void parse(FileSystemObjectSink & sink, Source & source, const Path & path)
{ {
std::string s; std::string s;
s = readString(source); s = readString(source);
if (s != "(") throw badArchive("expected open tag"); if (s != "(") throw badArchive("expected open tag");
enum { tpUnknown, tpRegular, tpDirectory, tpSymlink } type = tpUnknown;
std::map<Path, int, CaseInsensitiveCompare> names; std::map<Path, int, CaseInsensitiveCompare> names;
while (1) { auto getString = [&]() {
checkInterrupt(); checkInterrupt();
return readString(source);
};
s = readString(source); // For first iteration
s = getString();
while (1) {
if (s == ")") { if (s == ")") {
break; break;
} }
else if (s == "type") { else if (s == "type") {
if (type != tpUnknown) std::string t = getString();
throw badArchive("multiple type fields");
std::string t = readString(source);
if (t == "regular") { if (t == "regular") {
type = tpRegular; sink.createRegularFile(path, [&](auto & crf) {
sink.createRegularFile(path); while (1) {
s = getString();
if (s == "contents") {
parseContents(crf, source);
}
else if (s == "executable") {
auto s2 = getString();
if (s2 != "") throw badArchive("executable marker has non-empty value");
crf.isExecutable();
}
else break;
}
});
} }
else if (t == "directory") { else if (t == "directory") {
sink.createDirectory(path); sink.createDirectory(path);
type = tpDirectory;
while (1) {
s = getString();
if (s == "entry") {
std::string name, prevName;
s = getString();
if (s != "(") throw badArchive("expected open tag");
while (1) {
s = getString();
if (s == ")") {
break;
} else if (s == "name") {
name = getString();
if (name.empty() || name == "." || name == ".." || name.find('/') != std::string::npos || name.find((char) 0) != std::string::npos)
throw Error("NAR contains invalid file name '%1%'", name);
if (name <= prevName)
throw Error("NAR directory is not sorted");
prevName = name;
if (archiveSettings.useCaseHack) {
auto i = names.find(name);
if (i != names.end()) {
debug("case collision between '%1%' and '%2%'", i->first, name);
name += caseHackSuffix;
name += std::to_string(++i->second);
} else
names[name] = 0;
}
} else if (s == "node") {
if (name.empty()) throw badArchive("entry name missing");
parse(sink, source, path + "/" + name);
} else
throw badArchive("unknown field " + s);
}
}
else break;
}
} }
else if (t == "symlink") { else if (t == "symlink") {
type = tpSymlink; s = getString();
if (s != "target")
throw badArchive("expected 'target' got " + s);
std::string target = getString();
sink.createSymlink(path, target);
// for the next iteration
s = getString();
} }
else throw badArchive("unknown file type " + t); else throw badArchive("unknown file type " + t);
} }
else if (s == "contents" && type == tpRegular) {
parseContents(sink, source, path);
sink.closeRegularFile();
}
else if (s == "executable" && type == tpRegular) {
auto s = readString(source);
if (s != "") throw badArchive("executable marker has non-empty value");
sink.isExecutable();
}
else if (s == "entry" && type == tpDirectory) {
std::string name, prevName;
s = readString(source);
if (s != "(") throw badArchive("expected open tag");
while (1) {
checkInterrupt();
s = readString(source);
if (s == ")") {
break;
} else if (s == "name") {
name = readString(source);
if (name.empty() || name == "." || name == ".." || name.find('/') != std::string::npos || name.find((char) 0) != std::string::npos)
throw Error("NAR contains invalid file name '%1%'", name);
if (name <= prevName)
throw Error("NAR directory is not sorted");
prevName = name;
if (archiveSettings.useCaseHack) {
auto i = names.find(name);
if (i != names.end()) {
debug("case collision between '%1%' and '%2%'", i->first, name);
name += caseHackSuffix;
name += std::to_string(++i->second);
} else
names[name] = 0;
}
} else if (s == "node") {
if (name.empty()) throw badArchive("entry name missing");
parse(sink, source, path + "/" + name);
} else
throw badArchive("unknown field " + s);
}
}
else if (s == "target" && type == tpSymlink) {
std::string target = readString(source);
sink.createSymlink(path, target);
}
else else
throw badArchive("unknown field " + s); throw badArchive("unknown field " + s);
} }
} }
void parseDump(ParseSink & sink, Source & source) void parseDump(FileSystemObjectSink & sink, Source & source)
{ {
std::string version; std::string version;
try { try {
@ -294,7 +306,7 @@ void copyNAR(Source & source, Sink & sink)
// FIXME: if 'source' is the output of dumpPath() followed by EOF, // FIXME: if 'source' is the output of dumpPath() followed by EOF,
// we should just forward all data directly without parsing. // we should just forward all data directly without parsing.
NullParseSink parseSink; /* just parse the NAR */ NullFileSystemObjectSink parseSink; /* just parse the NAR */
TeeSource wrapper { source, sink }; TeeSource wrapper { source, sink };

View File

@ -73,7 +73,7 @@ time_t dumpPathAndGetMtime(const Path & path, Sink & sink,
*/ */
void dumpString(std::string_view s, Sink & sink); void dumpString(std::string_view s, Sink & sink);
void parseDump(ParseSink & sink, Source & source); void parseDump(FileSystemObjectSink & sink, Source & source);
void restorePath(const Path & path, Source & source); void restorePath(const Path & path, Source & source);

View File

@ -35,7 +35,7 @@ void dumpPath(
/** /**
* Restore a serialization of the given file system object. * Restore a serialization of the given file system object.
* *
* @TODO use an arbitrary `ParseSink`. * @TODO use an arbitrary `FileSystemObjectSink`.
*/ */
void restorePath( void restorePath(
const Path & path, const Path & path,

View File

@ -7,7 +7,7 @@ namespace nix {
void copyRecursive( void copyRecursive(
SourceAccessor & accessor, const CanonPath & from, SourceAccessor & accessor, const CanonPath & from,
ParseSink & sink, const Path & to) FileSystemObjectSink & sink, const Path & to)
{ {
auto stat = accessor.lstat(from); auto stat = accessor.lstat(from);
@ -19,16 +19,12 @@ void copyRecursive(
case SourceAccessor::tRegular: case SourceAccessor::tRegular:
{ {
sink.createRegularFile(to); sink.createRegularFile(to, [&](CreateRegularFileSink & crf) {
if (stat.isExecutable) if (stat.isExecutable)
sink.isExecutable(); crf.isExecutable();
LambdaSink sink2 { accessor.readFile(from, crf, [&](uint64_t size) {
[&](auto d) { crf.preallocateContents(size);
sink.receiveContents(d); });
}
};
accessor.readFile(from, sink2, [&](uint64_t size) {
sink.preallocateContents(size);
}); });
break; break;
} }
@ -71,20 +67,24 @@ void RestoreSink::createDirectory(const Path & path)
throw SysError("creating directory '%1%'", p); throw SysError("creating directory '%1%'", p);
}; };
void RestoreSink::createRegularFile(const Path & path) struct RestoreRegularFile : CreateRegularFileSink {
AutoCloseFD fd;
void operator () (std::string_view data) override;
void isExecutable() override;
void preallocateContents(uint64_t size) override;
};
void RestoreSink::createRegularFile(const Path & path, std::function<void(CreateRegularFileSink &)> func)
{ {
Path p = dstPath + path; Path p = dstPath + path;
fd = open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666); RestoreRegularFile crf;
if (!fd) throw SysError("creating file '%1%'", p); crf.fd = open(p.c_str(), O_CREAT | O_EXCL | O_WRONLY | O_CLOEXEC, 0666);
if (!crf.fd) throw SysError("creating file '%1%'", p);
func(crf);
} }
void RestoreSink::closeRegularFile() void RestoreRegularFile::isExecutable()
{
/* Call close explicitly to make sure the error is checked */
fd.close();
}
void RestoreSink::isExecutable()
{ {
struct stat st; struct stat st;
if (fstat(fd.get(), &st) == -1) if (fstat(fd.get(), &st) == -1)
@ -93,7 +93,7 @@ void RestoreSink::isExecutable()
throw SysError("fchmod"); throw SysError("fchmod");
} }
void RestoreSink::preallocateContents(uint64_t len) void RestoreRegularFile::preallocateContents(uint64_t len)
{ {
if (!restoreSinkSettings.preallocateContents) if (!restoreSinkSettings.preallocateContents)
return; return;
@ -111,7 +111,7 @@ void RestoreSink::preallocateContents(uint64_t len)
#endif #endif
} }
void RestoreSink::receiveContents(std::string_view data) void RestoreRegularFile::operator () (std::string_view data)
{ {
writeFull(fd.get(), data); writeFull(fd.get(), data);
} }
@ -122,4 +122,32 @@ void RestoreSink::createSymlink(const Path & path, const std::string & target)
nix::createSymlink(target, p); nix::createSymlink(target, p);
} }
void RegularFileSink::createRegularFile(const Path & path, std::function<void(CreateRegularFileSink &)> func)
{
struct CRF : CreateRegularFileSink {
RegularFileSink & back;
CRF(RegularFileSink & back) : back(back) {}
void operator () (std::string_view data) override
{
back.sink(data);
}
void isExecutable() override {}
} crf { *this };
func(crf);
}
void NullFileSystemObjectSink::createRegularFile(const Path & path, std::function<void(CreateRegularFileSink &)> func)
{
struct : CreateRegularFileSink {
void operator () (std::string_view data) override {}
void isExecutable() override {}
} crf;
// Even though `NullFileSystemObjectSink` doesn't do anything, it's important
// that we call the function, to e.g. advance the parser using this
// sink.
func(crf);
}
} }

View File

@ -9,18 +9,13 @@
namespace nix { namespace nix {
/** /**
* \todo Fix this API, it sucks. * Actions on an open regular file in the process of creating it.
*
* See `FileSystemObjectSink::createRegularFile`.
*/ */
struct ParseSink struct CreateRegularFileSink : Sink
{ {
virtual void createDirectory(const Path & path) = 0;
virtual void createRegularFile(const Path & path) = 0;
virtual void receiveContents(std::string_view data) = 0;
virtual void isExecutable() = 0; virtual void isExecutable() = 0;
virtual void closeRegularFile() = 0;
virtual void createSymlink(const Path & path, const std::string & target) = 0;
/** /**
* An optimization. By default, do nothing. * An optimization. By default, do nothing.
@ -28,46 +23,55 @@ struct ParseSink
virtual void preallocateContents(uint64_t size) { }; virtual void preallocateContents(uint64_t size) { };
}; };
struct FileSystemObjectSink
{
virtual void createDirectory(const Path & path) = 0;
/**
* This function in general is no re-entrant. Only one file can be
* written at a time.
*/
virtual void createRegularFile(
const Path & path,
std::function<void(CreateRegularFileSink &)>) = 0;
virtual void createSymlink(const Path & path, const std::string & target) = 0;
};
/** /**
* Recusively copy file system objects from the source into the sink. * Recursively copy file system objects from the source into the sink.
*/ */
void copyRecursive( void copyRecursive(
SourceAccessor & accessor, const CanonPath & sourcePath, SourceAccessor & accessor, const CanonPath & sourcePath,
ParseSink & sink, const Path & destPath); FileSystemObjectSink & sink, const Path & destPath);
/** /**
* Ignore everything and do nothing * Ignore everything and do nothing
*/ */
struct NullParseSink : ParseSink struct NullFileSystemObjectSink : FileSystemObjectSink
{ {
void createDirectory(const Path & path) override { } void createDirectory(const Path & path) override { }
void receiveContents(std::string_view data) override { }
void createSymlink(const Path & path, const std::string & target) override { } void createSymlink(const Path & path, const std::string & target) override { }
void createRegularFile(const Path & path) override { } void createRegularFile(
void closeRegularFile() override { } const Path & path,
void isExecutable() override { } std::function<void(CreateRegularFileSink &)>) override;
}; };
/** /**
* Write files at the given path * Write files at the given path
*/ */
struct RestoreSink : ParseSink struct RestoreSink : FileSystemObjectSink
{ {
Path dstPath; Path dstPath;
void createDirectory(const Path & path) override; void createDirectory(const Path & path) override;
void createRegularFile(const Path & path) override; void createRegularFile(
void receiveContents(std::string_view data) override; const Path & path,
void isExecutable() override; std::function<void(CreateRegularFileSink &)>) override;
void closeRegularFile() override;
void createSymlink(const Path & path, const std::string & target) override; void createSymlink(const Path & path, const std::string & target) override;
void preallocateContents(uint64_t size) override;
private:
AutoCloseFD fd;
}; };
/** /**
@ -75,7 +79,7 @@ private:
* `receiveContents` to the underlying `Sink`. For anything but a single * `receiveContents` to the underlying `Sink`. For anything but a single
* file, set `regular = true` so the caller can fail accordingly. * file, set `regular = true` so the caller can fail accordingly.
*/ */
struct RegularFileSink : ParseSink struct RegularFileSink : FileSystemObjectSink
{ {
bool regular = true; bool regular = true;
Sink & sink; Sink & sink;
@ -87,19 +91,14 @@ struct RegularFileSink : ParseSink
regular = false; regular = false;
} }
void receiveContents(std::string_view data) override
{
sink(data);
}
void createSymlink(const Path & path, const std::string & target) override void createSymlink(const Path & path, const std::string & target) override
{ {
regular = false; regular = false;
} }
void createRegularFile(const Path & path) override { } void createRegularFile(
void closeRegularFile() override { } const Path & path,
void isExecutable() override { } std::function<void(CreateRegularFileSink &)>) override;
}; };
} }

View File

@ -52,24 +52,22 @@ static std::string getString(Source & source, int n)
return v; return v;
} }
void parseBlob(
void parse( FileSystemObjectSink & sink,
ParseSink & sink,
const Path & sinkPath, const Path & sinkPath,
Source & source, Source & source,
std::function<SinkHook> hook, bool executable,
const ExperimentalFeatureSettings & xpSettings) const ExperimentalFeatureSettings & xpSettings)
{ {
xpSettings.require(Xp::GitHashing); xpSettings.require(Xp::GitHashing);
auto type = getString(source, 5); sink.createRegularFile(sinkPath, [&](auto & crf) {
if (executable)
if (type == "blob ") { crf.isExecutable();
sink.createRegularFile(sinkPath);
unsigned long long size = std::stoi(getStringUntil(source, 0)); unsigned long long size = std::stoi(getStringUntil(source, 0));
sink.preallocateContents(size); crf.preallocateContents(size);
unsigned long long left = size; unsigned long long left = size;
std::string buf; std::string buf;
@ -79,47 +77,91 @@ void parse(
checkInterrupt(); checkInterrupt();
buf.resize(std::min((unsigned long long)buf.capacity(), left)); buf.resize(std::min((unsigned long long)buf.capacity(), left));
source(buf); source(buf);
sink.receiveContents(buf); crf(buf);
left -= buf.size(); left -= buf.size();
} }
});
}
void parseTree(
FileSystemObjectSink & sink,
const Path & sinkPath,
Source & source,
std::function<SinkHook> hook,
const ExperimentalFeatureSettings & xpSettings)
{
unsigned long long size = std::stoi(getStringUntil(source, 0));
unsigned long long left = size;
sink.createDirectory(sinkPath);
while (left) {
std::string perms = getStringUntil(source, ' ');
left -= perms.size();
left -= 1;
RawMode rawMode = std::stoi(perms, 0, 8);
auto modeOpt = decodeMode(rawMode);
if (!modeOpt)
throw Error("Unknown Git permission: %o", perms);
auto mode = std::move(*modeOpt);
std::string name = getStringUntil(source, '\0');
left -= name.size();
left -= 1;
std::string hashs = getString(source, 20);
left -= 20;
Hash hash(HashAlgorithm::SHA1);
std::copy(hashs.begin(), hashs.end(), hash.hash);
hook(name, TreeEntry {
.mode = mode,
.hash = hash,
});
}
}
ObjectType parseObjectType(
Source & source,
const ExperimentalFeatureSettings & xpSettings)
{
xpSettings.require(Xp::GitHashing);
auto type = getString(source, 5);
if (type == "blob ") {
return ObjectType::Blob;
} else if (type == "tree ") { } else if (type == "tree ") {
unsigned long long size = std::stoi(getStringUntil(source, 0)); return ObjectType::Tree;
unsigned long long left = size;
sink.createDirectory(sinkPath);
while (left) {
std::string perms = getStringUntil(source, ' ');
left -= perms.size();
left -= 1;
RawMode rawMode = std::stoi(perms, 0, 8);
auto modeOpt = decodeMode(rawMode);
if (!modeOpt)
throw Error("Unknown Git permission: %o", perms);
auto mode = std::move(*modeOpt);
std::string name = getStringUntil(source, '\0');
left -= name.size();
left -= 1;
std::string hashs = getString(source, 20);
left -= 20;
Hash hash(HashAlgorithm::SHA1);
std::copy(hashs.begin(), hashs.end(), hash.hash);
hook(name, TreeEntry {
.mode = mode,
.hash = hash,
});
if (mode == Mode::Executable)
sink.isExecutable();
}
} else throw Error("input doesn't look like a Git object"); } else throw Error("input doesn't look like a Git object");
} }
void parse(
FileSystemObjectSink & sink,
const Path & sinkPath,
Source & source,
bool executable,
std::function<SinkHook> hook,
const ExperimentalFeatureSettings & xpSettings)
{
xpSettings.require(Xp::GitHashing);
auto type = parseObjectType(source, xpSettings);
switch (type) {
case ObjectType::Blob:
parseBlob(sink, sinkPath, source, executable, xpSettings);
break;
case ObjectType::Tree:
parseTree(sink, sinkPath, source, hook, xpSettings);
break;
default:
assert(false);
};
}
std::optional<Mode> convertMode(SourceAccessor::Type type) std::optional<Mode> convertMode(SourceAccessor::Type type)
{ {
@ -133,9 +175,9 @@ std::optional<Mode> convertMode(SourceAccessor::Type type)
} }
void restore(ParseSink & sink, Source & source, std::function<RestoreHook> hook) void restore(FileSystemObjectSink & sink, Source & source, std::function<RestoreHook> hook)
{ {
parse(sink, "", source, [&](Path name, TreeEntry entry) { parse(sink, "", source, false, [&](Path name, TreeEntry entry) {
auto [accessor, from] = hook(entry.hash); auto [accessor, from] = hook(entry.hash);
auto stat = accessor->lstat(from); auto stat = accessor->lstat(from);
auto gotOpt = convertMode(stat.type); auto gotOpt = convertMode(stat.type);

View File

@ -13,12 +13,19 @@
namespace nix::git { namespace nix::git {
enum struct ObjectType {
Blob,
Tree,
//Commit,
//Tag,
};
using RawMode = uint32_t; using RawMode = uint32_t;
enum struct Mode : RawMode { enum struct Mode : RawMode {
Directory = 0040000, Directory = 0040000,
Executable = 0100755,
Regular = 0100644, Regular = 0100644,
Executable = 0100755,
Symlink = 0120000, Symlink = 0120000,
}; };
@ -59,9 +66,34 @@ using Tree = std::map<std::string, TreeEntry>;
*/ */
using SinkHook = void(const Path & name, TreeEntry entry); using SinkHook = void(const Path & name, TreeEntry entry);
void parse( /**
ParseSink & sink, const Path & sinkPath, * Parse the "blob " or "tree " prefix.
*
* @throws if prefix not recognized
*/
ObjectType parseObjectType(
Source & source, Source & source,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
void parseBlob(
FileSystemObjectSink & sink, const Path & sinkPath,
Source & source,
bool executable,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
void parseTree(
FileSystemObjectSink & sink, const Path & sinkPath,
Source & source,
std::function<SinkHook> hook,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
/**
* Helper putting the previous three `parse*` functions together.
*/
void parse(
FileSystemObjectSink & sink, const Path & sinkPath,
Source & source,
bool executable,
std::function<SinkHook> hook, std::function<SinkHook> hook,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings); const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
@ -81,7 +113,7 @@ using RestoreHook = std::pair<SourceAccessor *, CanonPath>(Hash);
/** /**
* Wrapper around `parse` and `RestoreSink` * Wrapper around `parse` and `RestoreSink`
*/ */
void restore(ParseSink & sink, Source & source, std::function<RestoreHook> hook); void restore(FileSystemObjectSink & sink, Source & source, std::function<RestoreHook> hook);
/** /**
* Dumps a single file to a sink * Dumps a single file to a sink

View File

@ -134,36 +134,43 @@ void MemorySink::createDirectory(const Path & path)
throw Error("file '%s' is not a directory", path); throw Error("file '%s' is not a directory", path);
}; };
void MemorySink::createRegularFile(const Path & path) struct CreateMemoryRegularFile : CreateRegularFileSink {
File::Regular & regularFile;
CreateMemoryRegularFile(File::Regular & r)
: regularFile(r)
{ }
void operator () (std::string_view data) override;
void isExecutable() override;
void preallocateContents(uint64_t size) override;
};
void MemorySink::createRegularFile(const Path & path, std::function<void(CreateRegularFileSink &)> func)
{ {
auto * f = dst.open(CanonPath{path}, File { File::Regular {} }); auto * f = dst.open(CanonPath{path}, File { File::Regular {} });
if (!f) if (!f)
throw Error("file '%s' cannot be made because some parent file is not a directory", path); throw Error("file '%s' cannot be made because some parent file is not a directory", path);
if (!(r = std::get_if<File::Regular>(&f->raw))) if (auto * rp = std::get_if<File::Regular>(&f->raw)) {
CreateMemoryRegularFile crf { *rp };
func(crf);
} else
throw Error("file '%s' is not a regular file", path); throw Error("file '%s' is not a regular file", path);
} }
void MemorySink::closeRegularFile() void CreateMemoryRegularFile::isExecutable()
{ {
r = nullptr; regularFile.executable = true;
} }
void MemorySink::isExecutable() void CreateMemoryRegularFile::preallocateContents(uint64_t len)
{ {
assert(r); regularFile.contents.reserve(len);
r->executable = true;
} }
void MemorySink::preallocateContents(uint64_t len) void CreateMemoryRegularFile::operator () (std::string_view data)
{ {
assert(r); regularFile.contents += data;
r->contents.reserve(len);
}
void MemorySink::receiveContents(std::string_view data)
{
assert(r);
r->contents += data;
} }
void MemorySink::createSymlink(const Path & path, const std::string & target) void MemorySink::createSymlink(const Path & path, const std::string & target)

View File

@ -75,7 +75,7 @@ struct MemorySourceAccessor : virtual SourceAccessor
/** /**
* Write to a `MemorySourceAccessor` at the given path * Write to a `MemorySourceAccessor` at the given path
*/ */
struct MemorySink : ParseSink struct MemorySink : FileSystemObjectSink
{ {
MemorySourceAccessor & dst; MemorySourceAccessor & dst;
@ -83,17 +83,11 @@ struct MemorySink : ParseSink
void createDirectory(const Path & path) override; void createDirectory(const Path & path) override;
void createRegularFile(const Path & path) override; void createRegularFile(
void receiveContents(std::string_view data) override; const Path & path,
void isExecutable() override; std::function<void(CreateRegularFileSink &)>) override;
void closeRegularFile() override;
void createSymlink(const Path & path, const std::string & target) override; void createSymlink(const Path & path, const std::string & target) override;
void preallocateContents(uint64_t size) override;
private:
MemorySourceAccessor::File::Regular * r;
}; };
} }

View File

@ -66,7 +66,8 @@ TEST_F(GitTest, blob_read) {
StringSource in { encoded }; StringSource in { encoded };
StringSink out; StringSink out;
RegularFileSink out2 { out }; RegularFileSink out2 { out };
parse(out2, "", in, [](auto &, auto) {}, mockXpSettings); ASSERT_EQ(parseObjectType(in, mockXpSettings), ObjectType::Blob);
parseBlob(out2, "", in, false, mockXpSettings);
auto expected = readFile(goldenMaster("hello-world.bin")); auto expected = readFile(goldenMaster("hello-world.bin"));
@ -119,9 +120,10 @@ const static Tree tree = {
TEST_F(GitTest, tree_read) { TEST_F(GitTest, tree_read) {
readTest("tree.bin", [&](const auto & encoded) { readTest("tree.bin", [&](const auto & encoded) {
StringSource in { encoded }; StringSource in { encoded };
NullParseSink out; NullFileSystemObjectSink out;
Tree got; Tree got;
parse(out, "", in, [&](auto & name, auto entry) { ASSERT_EQ(parseObjectType(in, mockXpSettings), ObjectType::Tree);
parseTree(out, "", in, [&](auto & name, auto entry) {
auto name2 = name; auto name2 = name;
if (entry.mode == Mode::Directory) if (entry.mode == Mode::Directory)
name2 += '/'; name2 += '/';
@ -193,15 +195,21 @@ TEST_F(GitTest, both_roundrip) {
MemorySink sinkFiles2 { files2 }; MemorySink sinkFiles2 { files2 };
std::function<void(const Path, const Hash &)> mkSinkHook; std::function<void(const Path, const Hash &, bool)> mkSinkHook;
mkSinkHook = [&](const Path prefix, const Hash & hash) { mkSinkHook = [&](auto prefix, auto & hash, auto executable) {
StringSource in { cas[hash] }; StringSource in { cas[hash] };
parse(sinkFiles2, prefix, in, [&](const Path & name, const auto & entry) { parse(
mkSinkHook(prefix + "/" + name, entry.hash); sinkFiles2, prefix, in, executable,
}, mockXpSettings); [&](const Path & name, const auto & entry) {
mkSinkHook(
prefix + "/" + name,
entry.hash,
entry.mode == Mode::Executable);
},
mockXpSettings);
}; };
mkSinkHook("", root.hash); mkSinkHook("", root.hash, false);
ASSERT_EQ(files, files2); ASSERT_EQ(files, files2);
} }