Compare commits

...

5 Commits

Author SHA1 Message Date
John Ericson
fdfc8bf554 Make the git sink support bare files 2025-12-17 17:06:29 -05:00
John Ericson
4586223bab GitFileSystemObjectSink: Avoid the name in the root builder
It is not actually used, and I rather not keep such "fictitious value"
around.
2025-12-17 17:02:33 -05:00
John Ericson
a53317347d Deduplication in GitFileSystemObjectSink
A bunch of callers to `prepareDirs` were doing the same thing again and
again.
2025-12-17 17:01:39 -05:00
John Ericson
102c0bd9e3 Simplify Git FSO sink
`prepareDirs` always returns true.
2025-12-17 17:01:39 -05:00
John Ericson
1adc04f421 Combine the FileSystemObjectSink::createDirectory methods
I find the implementation easier to understand. And it enables the
refactors that come next.
2025-12-17 17:01:38 -05:00
12 changed files with 301 additions and 156 deletions

View File

@@ -70,12 +70,6 @@ TEST_F(GitUtilsTest, sink_basic)
auto repo = openRepo();
auto sink = repo->getFileSystemObjectSink();
// TODO/Question: It seems a little odd that we use the tarball-like convention of requiring a top-level directory
// here
// The sync method does not document this behavior, should probably renamed because it's not very
// general, and I can't imagine that "non-conventional" archives or any other source to be handled by
// this sink.
sink->createDirectory(CanonPath("foo-1.1"));
sink->createRegularFile(CanonPath("foo-1.1/hello"), [](CreateRegularFileSink & fileSink) {
@@ -91,7 +85,7 @@ TEST_F(GitUtilsTest, sink_basic)
// sink->createHardlink("foo-1.1/links/foo-2", CanonPath("foo-1.1/hello"));
auto result = repo->dereferenceSingletonDirectory(sink->flush());
auto result = repo->dereferenceSingletonDirectory(sink->flush().hash);
auto accessor = repo->getAccessor(result, {}, getRepoName());
auto entries = accessor->readDirectory(CanonPath::root);
ASSERT_EQ(entries.size(), 5u);
@@ -123,6 +117,51 @@ TEST_F(GitUtilsTest, sink_hardlink)
}
};
TEST_F(GitUtilsTest, sink_root_file)
{
auto repo = openRepo();
auto sink = repo->getFileSystemObjectSink();
sink->createRegularFile(
CanonPath::root, [](CreateRegularFileSink & fileSink) { writeString(fileSink, "hello world", false); });
auto result = sink->flush();
ASSERT_EQ(result.mode, nix::git::Mode::Regular);
auto accessor = repo->getAccessor(result.hash, false, getRepoName());
ASSERT_EQ(accessor->readFile(CanonPath::root), "hello world");
};
TEST_F(GitUtilsTest, sink_root_symlink)
{
auto repo = openRepo();
auto sink = repo->getFileSystemObjectSink();
sink->createSymlink(CanonPath::root, "target");
auto result = sink->flush();
ASSERT_EQ(result.mode, nix::git::Mode::Symlink);
auto accessor = repo->getAccessor(result.hash, false, getRepoName());
ASSERT_EQ(accessor->readLink(CanonPath::root), "target");
};
TEST_F(GitUtilsTest, sink_hardlink_to_root)
{
auto repo = openRepo();
auto sink = repo->getFileSystemObjectSink();
sink->createRegularFile(
CanonPath("hello"), [](CreateRegularFileSink & fileSink) { writeString(fileSink, "hello world", false); });
sink->createDirectory(CanonPath("subdir"));
sink->createHardlink(CanonPath("subdir/link"), CanonPath("hello"));
auto result = sink->flush();
ASSERT_EQ(result.mode, nix::git::Mode::Directory);
auto accessor = repo->getAccessor(result.hash, false, getRepoName());
ASSERT_EQ(accessor->readFile(CanonPath("hello")), "hello world");
ASSERT_EQ(accessor->readFile(CanonPath("subdir/link")), "hello world");
};
TEST_F(GitUtilsTest, peel_reference)
{
// Create a commit in the repo

View File

@@ -962,15 +962,15 @@ struct GitSourceAccessor : SourceAccessor
Blob getBlob(State & state, const CanonPath & path, bool expectSymlink)
{
if (!expectSymlink && git_object_type(state.root.get()) == GIT_OBJECT_BLOB)
return dupObject<Blob>((git_blob *) &*state.root);
auto notExpected = [&]() {
throw Error(expectSymlink ? "'%s' is not a symlink" : "'%s' is not a regular file", showPath(path));
};
if (path.isRoot())
if (path.isRoot()) {
if (git_object_type(state.root.get()) == GIT_OBJECT_BLOB)
return dupObject<Blob>((git_blob *) &*state.root);
notExpected();
}
auto entry = need(state, path);
@@ -1054,16 +1054,24 @@ struct GitExportIgnoreSourceAccessor : CachingFilteringSourceAccessor
}
};
struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
{
ref<GitRepoImpl> repo;
namespace {
struct DirEntry
{
git_oid oid;
git_filemode_t mode;
};
struct DirectoryState
{
struct PendingDir
{
std::string name;
TreeBuilder builder;
};
GitRepoImpl & repo;
TreeBuilder rootBuilder;
std::vector<PendingDir> pendingDirs;
/**
@@ -1078,30 +1086,9 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
*/
decltype(::git_odb_backend::refresh) packfileOdbRefresh = nullptr;
void pushBuilder(std::string name)
{
const git_tree_entry * entry;
Tree prevTree = nullptr;
if (!pendingDirs.empty() && (entry = git_treebuilder_get(pendingDirs.back().builder.get(), name.c_str()))) {
/* Clone a tree that we've already finished. This happens
if a tarball has directory entries that are not
contiguous. */
if (git_tree_entry_type(entry) != GIT_OBJECT_TREE)
throw Error("parent of '%s' is not a directory", name);
if (git_tree_entry_to_object((git_object **) (git_tree **) Setter(prevTree), *repo, entry))
throw Error("looking up parent of '%s': %s", name, git_error_last()->message);
}
git_treebuilder * b;
if (git_treebuilder_new(&b, *repo, prevTree.get()))
throw Error("creating a tree builder: %s", git_error_last()->message);
pendingDirs.push_back({.name = std::move(name), .builder = TreeBuilder(b)});
};
GitFileSystemObjectSinkImpl(ref<GitRepoImpl> repo)
DirectoryState(GitRepoImpl & repo)
: repo(repo)
, rootBuilder(makeBuilder())
{
/* Monkey-patching the pack backend to only read the pack directory
once. Otherwise it will do a readdir for each added oid when it's
@@ -1126,62 +1113,145 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
*/
packfileOdbRefresh = std::exchange(backend->refresh, nullptr);
}
pushBuilder("");
}
std::pair<git_oid, std::string> popBuilder()
{
assert(!pendingDirs.empty());
auto pending = std::move(pendingDirs.back());
git_oid oid;
if (git_treebuilder_write(&oid, pending.builder.get()))
throw Error("creating a tree object: %s", git_error_last()->message);
pendingDirs.pop_back();
return {oid, pending.name};
};
TreeBuilder makeBuilder(git_tree * prevTree = nullptr);
void addToTree(const std::string & name, const git_oid & oid, git_filemode_t mode)
{
assert(!pendingDirs.empty());
auto & pending = pendingDirs.back();
if (git_treebuilder_insert(nullptr, pending.builder.get(), name.c_str(), &oid, mode))
throw Error("adding a file to a tree builder: %s", git_error_last()->message);
};
std::vector<std::string> prepareDirs(const CanonPath & path);
void updateBuilders(std::span<const std::string> names);
std::pair<git_oid, std::string> popBuilder();
void pushBuilder(std::string name);
void addToTree(const std::string & name, const DirEntry & entry);
};
void updateBuilders(std::span<const std::string> names)
{
// Find the common prefix of pendingDirs and names.
size_t prefixLen = 0;
for (; prefixLen < names.size() && prefixLen + 1 < pendingDirs.size(); ++prefixLen)
if (names[prefixLen] != pendingDirs[prefixLen + 1].name)
break;
TreeBuilder DirectoryState::makeBuilder(git_tree * prevTree)
{
TreeBuilder builder;
if (git_treebuilder_new(Setter(builder), repo, prevTree))
throw Error("creating a tree builder: %s", git_error_last()->message);
return builder;
}
// Finish the builders that are not part of the common prefix.
for (auto n = pendingDirs.size(); n > prefixLen + 1; --n) {
auto [oid, name] = popBuilder();
addToTree(name, oid, GIT_FILEMODE_TREE);
git_oid finishBuilder(TreeBuilder & builder)
{
git_oid oid;
if (git_treebuilder_write(&oid, builder.get()))
throw Error("creating a tree object: %s", git_error_last()->message);
return oid;
}
void DirectoryState::pushBuilder(std::string name)
{
auto & parentBuilder = pendingDirs.empty() ? rootBuilder : pendingDirs.back().builder;
const git_tree_entry * entry;
Tree prevTree = nullptr;
if ((entry = git_treebuilder_get(parentBuilder.get(), name.c_str()))) {
/* Clone a tree that we've already finished. This happens
if a tarball has directory entries that are not
contiguous. */
if (git_tree_entry_type(entry) != GIT_OBJECT_TREE)
throw Error("parent of '%s' is not a directory", name);
if (git_tree_entry_to_object((git_object **) (git_tree **) Setter(prevTree), repo, entry))
throw Error("looking up parent of '%s': %s", name, git_error_last()->message);
}
pendingDirs.push_back({
.name = std::move(name),
.builder = makeBuilder(prevTree.get()),
});
}
std::pair<git_oid, std::string> DirectoryState::popBuilder()
{
assert(!pendingDirs.empty());
auto pending = std::move(pendingDirs.back());
pendingDirs.pop_back();
return {finishBuilder(pending.builder), pending.name};
}
void DirectoryState::addToTree(const std::string & name, const DirEntry & entry)
{
auto & builder = pendingDirs.empty() ? rootBuilder : pendingDirs.back().builder;
if (git_treebuilder_insert(nullptr, builder.get(), name.c_str(), &entry.oid, entry.mode))
throw Error("adding a file to a tree builder: %s", git_error_last()->message);
}
void DirectoryState::updateBuilders(std::span<const std::string> names)
{
// Find the common prefix of pendingDirs and names.
size_t prefixLen = 0;
for (; prefixLen < names.size() && prefixLen < pendingDirs.size(); ++prefixLen)
if (names[prefixLen] != pendingDirs[prefixLen].name)
break;
// Finish the builders that are not part of the common prefix.
for (auto n = pendingDirs.size(); n > prefixLen; --n) {
auto [oid, name] = popBuilder();
addToTree(name, DirEntry{.oid = oid, .mode = GIT_FILEMODE_TREE});
}
// Create builders for the new directories.
for (auto n = prefixLen; n < names.size(); ++n)
pushBuilder(names[n]);
}
std::vector<std::string> DirectoryState::prepareDirs(const CanonPath & path)
{
std::vector<std::string> pathComponents;
for (auto & c : path)
pathComponents.emplace_back(c);
updateBuilders(pathComponents);
return pathComponents;
}
struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
{
ref<GitRepoImpl> repo;
/**
* Root state:
* - nullopt: nothing created yet
* - DirEntry: root is a single non-directory object (file/symlink)
* - DirectoryState: root is a directory with builders
*/
std::optional<std::variant<DirEntry, DirectoryState>> root;
DirectoryState & ensureDirectoryState()
{
if (!root) {
root.emplace<DirectoryState>(*repo);
}
if (auto * dirState = std::get_if<DirectoryState>(&*root)) {
return *dirState;
}
throw Error("cannot create directory entry: root is not a directory");
}
// Create builders for the new directories.
for (auto n = prefixLen; n < names.size(); ++n)
pushBuilder(names[n]);
};
bool prepareDirs(const std::vector<std::string> & pathComponents, bool isDir)
GitFileSystemObjectSinkImpl(ref<GitRepoImpl> repo)
: repo(repo)
{
std::span<const std::string> pathComponents2{pathComponents};
}
updateBuilders(isDir ? pathComponents2 : pathComponents2.first(pathComponents2.size() - 1));
return true;
void addEntry(const CanonPath & path, const DirEntry & entry)
{
if (auto res = path.parentAndChild()) {
auto & [parent, child] = *res;
auto & dirState = ensureDirectoryState();
dirState.prepareDirs(parent);
dirState.addToTree(std::string(child), entry);
} else {
if (root)
throw Error("root already exists");
root = entry;
}
}
void createRegularFile(const CanonPath & path, std::function<void(CreateRegularFileSink &)> func) override
{
auto pathComponents = tokenizeString<std::vector<std::string>>(path.rel(), "/");
if (!prepareDirs(pathComponents, false))
return;
using WriteStream = std::unique_ptr<::git_writestream, decltype([](::git_writestream * stream) {
if (stream)
stream->free(stream);
@@ -1260,60 +1330,68 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
"creating a blob object for '%s' from in-memory buffer: %s", path, git_error_last()->message);
}
addToTree(*pathComponents.rbegin(), oid, crf.executable ? GIT_FILEMODE_BLOB_EXECUTABLE : GIT_FILEMODE_BLOB);
addEntry(
path,
DirEntry{
.oid = oid,
.mode = crf.executable ? GIT_FILEMODE_BLOB_EXECUTABLE : GIT_FILEMODE_BLOB,
});
}
void createDirectory(const CanonPath & path) override
void createDirectory(const CanonPath & path, std::optional<DirectoryCreatedCallback> callback = {}) override
{
auto pathComponents = tokenizeString<std::vector<std::string>>(path.rel(), "/");
(void) prepareDirs(pathComponents, true);
ensureDirectoryState().prepareDirs(path);
if (callback)
(*callback)(*this, path);
}
void createSymlink(const CanonPath & path, const std::string & target) override
{
auto pathComponents = tokenizeString<std::vector<std::string>>(path.rel(), "/");
if (!prepareDirs(pathComponents, false))
return;
git_oid oid;
if (git_blob_create_from_buffer(&oid, *repo, target.c_str(), target.size()))
throw Error("creating a blob object for tarball symlink member '%s': %s", path, git_error_last()->message);
addToTree(*pathComponents.rbegin(), oid, GIT_FILEMODE_LINK);
addEntry(
path,
DirEntry{
.oid = oid,
.mode = GIT_FILEMODE_LINK,
});
}
void createHardlink(const CanonPath & path, const CanonPath & target) override
{
std::vector<std::string> pathComponents;
for (auto & c : path)
pathComponents.emplace_back(c);
auto & dirState = ensureDirectoryState();
if (!prepareDirs(pathComponents, false))
return;
auto res = path.parentAndChild();
if (!res)
throw Error("hard link cannot be root file system object");
auto & [parent, name] = *res;
dirState.prepareDirs(parent);
// We can't just look up the path from the start of the root, since
// some parent directories may not have finished yet, so we compute
// a relative path that helps us find the right git_tree_builder or object.
auto relTarget = CanonPath(path).parent()->makeRelative(target);
auto relTarget = parent.makeRelative(target);
auto dir = pendingDirs.rbegin();
auto dir = dirState.pendingDirs.rbegin();
// For each ../ component at the start, go up one directory.
// CanonPath::makeRelative() always puts all .. elements at the start,
// so they're all handled by this loop:
std::string_view relTargetLeft(relTarget);
while (hasPrefix(relTargetLeft, "../")) {
if (dir == pendingDirs.rend())
if (dir == dirState.pendingDirs.rend())
throw Error("invalid hard link target '%s' for path '%s'", target, path);
++dir;
relTargetLeft = relTargetLeft.substr(3);
}
if (dir == pendingDirs.rend())
throw Error("invalid hard link target '%s' for path '%s'", target, path);
// Look up the remainder of the target, starting at the
// top-most `git_treebuilder`.
std::variant<git_treebuilder *, git_oid> curDir{dir->builder.get()};
std::variant<git_treebuilder *, git_oid> curDir{
dir == dirState.pendingDirs.rend() ? dirState.rootBuilder.get() : dir->builder.get()};
Object tree; // needed to keep `entry` alive
const git_tree_entry * entry = nullptr;
@@ -1333,26 +1411,41 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
assert(entry);
addToTree(*pathComponents.rbegin(), *git_tree_entry_id(entry), git_tree_entry_filemode(entry));
dirState.addToTree(
std::string(name),
DirEntry{.oid = *git_tree_entry_id(entry), .mode = git_tree_entry_filemode(entry)});
}
Hash flush() override
git::TreeEntry flush() override
{
updateBuilders({});
if (!root)
throw Error("nothing to flush");
auto [oid, _name] = popBuilder();
auto [oid, mode] = std::visit(
overloaded{
[](DirEntry & entry) -> std::pair<git_oid, git_filemode_t> {
return {entry.oid, entry.mode};
},
[](DirectoryState & dirState) -> std::pair<git_oid, git_filemode_t> {
dirState.updateBuilders({});
return {finishBuilder(dirState.rootBuilder), GIT_FILEMODE_TREE};
},
},
*root);
if (auto * backend = repo->packBackend) {
/* We are done writing blobs, can restore refresh functionality. */
backend->refresh = packfileOdbRefresh;
backend->refresh = dirState.packfileOdbRefresh;
}
repo->flush();
return toHash(oid);
return git::TreeEntry{.mode = static_cast<git::Mode>(mode), .hash = toHash(oid)};
}
};
}
ref<GitSourceAccessor> GitRepoImpl::getRawAccessor(const Hash & rev, const GitAccessorOptions & options)
{
auto self = ref<GitRepoImpl>(shared_from_this());

View File

@@ -321,12 +321,12 @@ struct GitArchiveInputScheme : InputScheme
auto tarballCache = settings.getTarballCache();
auto parseSink = tarballCache->getFileSystemObjectSink();
auto lastModified = unpackTarfileToSink(archive, *parseSink);
auto tree = parseSink->flush();
auto treeEntry = parseSink->flush();
act.reset();
TarballInfo tarballInfo{
.treeHash = tarballCache->dereferenceSingletonDirectory(tree), .lastModified = lastModified};
.treeHash = tarballCache->dereferenceSingletonDirectory(treeEntry.hash), .lastModified = lastModified};
cache->upsert(treeHashKey, Attrs{{"treeHash", tarballInfo.treeHash.gitRev()}});
cache->upsert(lastModifiedKey, Attrs{{"lastModified", (uint64_t) tarballInfo.lastModified}});

View File

@@ -2,6 +2,7 @@
#include "nix/fetchers/filtering-source-accessor.hh"
#include "nix/util/fs-sink.hh"
#include "nix/util/git.hh"
namespace nix {
@@ -17,9 +18,9 @@ struct Settings;
struct GitFileSystemObjectSink : ExtendedFileSystemObjectSink
{
/**
* Flush builder and return a final Git hash.
* Flush builder and return the final Git tree entry (hash and mode).
*/
virtual Hash flush() = 0;
virtual git::TreeEntry flush() = 0;
};
struct GitAccessorOptions

View File

@@ -182,7 +182,7 @@ static DownloadTarballResult downloadTarball_(
auto tarballCache = settings.getTarballCache();
auto parseSink = tarballCache->getFileSystemObjectSink();
auto lastModified = unpackTarfileToSink(archive, *parseSink);
auto tree = parseSink->flush();
auto treeEntry = parseSink->flush();
act.reset();
@@ -196,7 +196,7 @@ static DownloadTarballResult downloadTarball_(
infoAttrs = cached->value;
} else {
infoAttrs.insert_or_assign("etag", res->etag);
infoAttrs.insert_or_assign("treeHash", tarballCache->dereferenceSingletonDirectory(tree).gitRev());
infoAttrs.insert_or_assign("treeHash", tarballCache->dereferenceSingletonDirectory(treeEntry.hash).gitRev());
infoAttrs.insert_or_assign("lastModified", uint64_t(lastModified));
if (res->immutableUrl)
infoAttrs.insert_or_assign("immutableUrl", *res->immutableUrl);

View File

@@ -54,6 +54,17 @@ std::optional<CanonPath> CanonPath::parent() const
return CanonPath(unchecked_t(), path.substr(0, std::max((size_t) 1, path.rfind('/'))));
}
std::optional<std::pair<CanonPath, std::string_view>> CanonPath::parentAndChild() const
{
if (isRoot())
return std::nullopt;
auto slash = path.rfind('/');
return {{
CanonPath(unchecked_t(), path.substr(0, std::max((size_t) 1, slash))),
std::string_view{path}.substr(slash + 1)
}};
}
void CanonPath::pop()
{
assert(!isRoot());

View File

@@ -70,31 +70,7 @@ static std::filesystem::path append(const std::filesystem::path & src, const Can
return dst;
}
#ifndef _WIN32
void RestoreSink::createDirectory(const CanonPath & path, DirectoryCreatedCallback callback)
{
if (path.isRoot()) {
createDirectory(path);
callback(*this, path);
return;
}
createDirectory(path);
assert(dirFd); // If that's not true the above call must have thrown an exception.
RestoreSink dirSink{startFsync};
dirSink.dstPath = append(dstPath, path);
dirSink.dirFd =
unix::openFileEnsureBeneathNoSymlinks(dirFd.get(), path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (!dirSink.dirFd)
throw SysError("opening directory '%s'", dirSink.dstPath.string());
callback(dirSink, CanonPath::root);
}
#endif
void RestoreSink::createDirectory(const CanonPath & path)
void RestoreSink::createDirectory(const CanonPath & path, std::optional<DirectoryCreatedCallback> callback)
{
auto p = append(dstPath, path);
@@ -107,6 +83,16 @@ void RestoreSink::createDirectory(const CanonPath & path)
if (::mkdirat(dirFd.get(), path.rel_c_str(), 0777) == -1)
throw SysError("creating directory '%s'", p.string());
if (callback) {
RestoreSink dirSink{startFsync};
dirSink.dstPath =
unix::openFileEnsureBeneathNoSymlinks(dirFd.get(), path, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
if (!dirSink.dirFd)
throw SysError("opening directory '%s'", dirSink.dstPath.string());
(*callback)(dirSink, CanonPath::root);
}
return;
}
#endif
@@ -125,7 +111,10 @@ void RestoreSink::createDirectory(const CanonPath & path)
throw SysError("creating directory '%1%'", p.string());
}
#endif
};
if (callback)
(*callback)(*this, path);
}
struct RestoreRegularFile : CreateRegularFileSink
{

View File

@@ -238,6 +238,13 @@ public:
return ((std::string_view) path).substr(path.rfind('/') + 1);
}
/**
* Combination of `parent` and `baseName`.
*
* @note that because of the `std::string_view`, we are borrowing for the base name.
*/
std::optional<std::pair<CanonPath, std::string_view>> parentAndChild() const;
bool operator==(const CanonPath & x) const
{
return path == x.path;

View File

@@ -34,8 +34,6 @@ struct FileSystemObjectSink
{
virtual ~FileSystemObjectSink() = default;
virtual void createDirectory(const CanonPath & path) = 0;
using DirectoryCreatedCallback = std::function<void(FileSystemObjectSink & dirSink, const CanonPath & dirRelPath)>;
/**
@@ -47,11 +45,7 @@ struct FileSystemObjectSink
* freshly created directory. Use this when it's important to disallow any
* intermediate path components from being symlinks.
*/
virtual void createDirectory(const CanonPath & path, DirectoryCreatedCallback callback)
{
createDirectory(path);
callback(*this, path);
}
virtual void createDirectory(const CanonPath & path, std::optional<DirectoryCreatedCallback> callback = {}) = 0;
/**
* This function in general is no re-entrant. Only one file can be
@@ -86,7 +80,11 @@ void copyRecursive(
*/
struct NullFileSystemObjectSink : FileSystemObjectSink
{
void createDirectory(const CanonPath & path) override {}
void createDirectory(const CanonPath & path, std::optional<DirectoryCreatedCallback> callback = {}) override
{
if (callback)
(*callback)(*this, CanonPath::root);
}
void createSymlink(const CanonPath & path, const std::string & target) override {}
@@ -118,11 +116,7 @@ struct RestoreSink : FileSystemObjectSink
{
}
void createDirectory(const CanonPath & path) override;
#ifndef _WIN32
void createDirectory(const CanonPath & path, DirectoryCreatedCallback callback) override;
#endif
void createDirectory(const CanonPath & path, std::optional<DirectoryCreatedCallback> callback = {}) override;
void createRegularFile(const CanonPath & path, std::function<void(CreateRegularFileSink &)>) override;
@@ -144,9 +138,15 @@ struct RegularFileSink : FileSystemObjectSink
{
}
void createDirectory(const CanonPath & path) override
void createDirectory(const CanonPath & path, std::optional<DirectoryCreatedCallback> callback = {}) override
{
regular = false;
if (callback) {
NullFileSystemObjectSink s;
/* The children of the
directory that cannot exist also cannot exist */
(*callback)(s, CanonPath::root);
}
}
void createSymlink(const CanonPath & path, const std::string & target) override

View File

@@ -153,7 +153,7 @@ struct MemorySink : FileSystemObjectSink
{
}
void createDirectory(const CanonPath & path) override;
void createDirectory(const CanonPath & path, std::optional<DirectoryCreatedCallback> callback = {}) override;
void createRegularFile(const CanonPath & path, std::function<void(CreateRegularFileSink &)>) override;

View File

@@ -146,7 +146,7 @@ SourcePath MemorySourceAccessor::addFile(CanonPath path, std::string && contents
using File = MemorySourceAccessor::File;
void MemorySink::createDirectory(const CanonPath & path)
void MemorySink::createDirectory(const CanonPath & path, std::optional<DirectoryCreatedCallback> callback)
{
auto * f = dst.open(path, File{File::Directory{}});
if (!f)
@@ -154,7 +154,10 @@ void MemorySink::createDirectory(const CanonPath & path)
if (!std::holds_alternative<File::Directory>(f->raw))
throw Error("file '%s' is not a directory", path);
};
if (callback)
(*callback)(*this, path);
}
struct CreateMemoryRegularFile : CreateRegularFileSink
{

View File

@@ -98,11 +98,13 @@ struct NarAccessor : public SourceAccessor
}
}
void createDirectory(const CanonPath & path) override
void createDirectory(const CanonPath & path, std::optional<DirectoryCreatedCallback> callback = {}) override
{
createMember(
path,
NarMember{.stat = {.type = Type::tDirectory, .fileSize = 0, .isExecutable = false, .narOffset = 0}});
if (callback)
(*callback)(*this, path);
}
void createRegularFile(const CanonPath & path, std::function<void(CreateRegularFileSink &)> func) override