mirror of
https://github.com/NixOS/nix
synced 2025-06-26 11:41:15 +02:00
Git fetcher: Improve submodule handling
Instead of making a complete copy of the repo, fetching the submodules, and writing the result to the store (which is all superexpensive), we now fetch the submodules recursively using the Git fetcher, and return a union accessor that "mounts" the accessors for the submodules on top of the root accessor.
This commit is contained in:
parent
ee36a44bf2
commit
d88106df24
6 changed files with 212 additions and 84 deletions
|
@ -1,11 +1,13 @@
|
|||
#include "git-utils.hh"
|
||||
#include "input-accessor.hh"
|
||||
#include "cache.hh"
|
||||
#include "finally.hh"
|
||||
|
||||
#include <boost/core/span.hpp>
|
||||
|
||||
#include <git2/blob.h>
|
||||
#include <git2/commit.h>
|
||||
#include <git2/config.h>
|
||||
#include <git2/describe.h>
|
||||
#include <git2/errors.h>
|
||||
#include <git2/global.h>
|
||||
|
@ -14,6 +16,7 @@
|
|||
#include <git2/remote.h>
|
||||
#include <git2/repository.h>
|
||||
#include <git2/status.h>
|
||||
#include <git2/submodule.h>
|
||||
#include <git2/tree.h>
|
||||
|
||||
#include <unordered_set>
|
||||
|
@ -63,6 +66,8 @@ typedef std::unique_ptr<git_reference, Deleter<git_reference_free>> Reference;
|
|||
typedef std::unique_ptr<git_describe_result, Deleter<git_describe_result_free>> DescribeResult;
|
||||
typedef std::unique_ptr<git_status_list, Deleter<git_status_list_free>> StatusList;
|
||||
typedef std::unique_ptr<git_remote, Deleter<git_remote_free>> Remote;
|
||||
typedef std::unique_ptr<git_config, Deleter<git_config_free>> GitConfig;
|
||||
typedef std::unique_ptr<git_config_iterator, Deleter<git_config_iterator_free>> ConfigIterator;
|
||||
|
||||
// A helper to ensure that we don't leak objects returned by libgit2.
|
||||
template<typename T>
|
||||
|
@ -256,6 +261,17 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
|
|||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::vector<Submodule> getSubmodules(const Hash & rev) override;
|
||||
|
||||
std::string resolveSubmoduleUrl(const std::string & url) override
|
||||
{
|
||||
git_buf buf = GIT_BUF_INIT;
|
||||
if (git_submodule_resolve_url(&buf, *this, url.c_str()))
|
||||
throw Error("resolving Git submodule URL '%s'", url);
|
||||
Finally cleanup = [&]() { git_buf_dispose(&buf); };
|
||||
return buf.ptr;
|
||||
}
|
||||
|
||||
bool hasObject(const Hash & oid_) override
|
||||
{
|
||||
auto oid = hashToOID(oid_);
|
||||
|
@ -400,6 +416,16 @@ struct GitInputAccessor : InputAccessor
|
|||
return readBlob(path, true);
|
||||
}
|
||||
|
||||
Hash getSubmoduleRev(const CanonPath & path)
|
||||
{
|
||||
auto entry = need(path);
|
||||
|
||||
if (git_tree_entry_type(entry) != GIT_OBJECT_COMMIT)
|
||||
throw Error("'%s' is not a submodule", showPath(path));
|
||||
|
||||
return toHash(*git_tree_entry_id(entry));
|
||||
}
|
||||
|
||||
std::map<CanonPath, TreeEntry> lookupCache;
|
||||
|
||||
/* Recursively look up 'path' relative to the root. */
|
||||
|
@ -495,4 +521,56 @@ ref<InputAccessor> GitRepoImpl::getAccessor(const Hash & rev)
|
|||
return make_ref<GitInputAccessor>(ref<GitRepoImpl>(shared_from_this()), rev);
|
||||
}
|
||||
|
||||
std::vector<GitRepoImpl::Submodule> GitRepoImpl::getSubmodules(const Hash & rev)
|
||||
{
|
||||
/* Read the .gitmodules files from this revision. */
|
||||
CanonPath modulesFile(".gitmodules");
|
||||
|
||||
auto accessor = getAccessor(rev);
|
||||
if (!accessor->pathExists(modulesFile)) return {};
|
||||
|
||||
/* Parse it. */
|
||||
auto configS = accessor->readFile(modulesFile);
|
||||
|
||||
auto [fdTemp, pathTemp] = createTempFile("nix-git-submodules");
|
||||
writeFull(fdTemp.get(), configS);
|
||||
|
||||
GitConfig config;
|
||||
if (git_config_open_ondisk(Setter(config), pathTemp.c_str()))
|
||||
throw Error("parsing .gitmodules file: %s", git_error_last()->message);
|
||||
|
||||
ConfigIterator it;
|
||||
if (git_config_iterator_glob_new(Setter(it), config.get(), "^submodule\\..*\\.(path|url|branch)$"))
|
||||
throw Error("iterating over .gitmodules: %s", git_error_last()->message);
|
||||
|
||||
std::map<std::string, std::string> entries;
|
||||
|
||||
while (true) {
|
||||
git_config_entry * entry = nullptr;
|
||||
if (auto err = git_config_next(&entry, it.get())) {
|
||||
if (err == GIT_ITEROVER) break;
|
||||
throw Error("iterating over .gitmodules: %s", git_error_last()->message);
|
||||
}
|
||||
entries.emplace(entry->name + 10, entry->value);
|
||||
}
|
||||
|
||||
std::vector<Submodule> result;
|
||||
|
||||
for (auto & [key, value] : entries) {
|
||||
if (!hasSuffix(key, ".path")) continue;
|
||||
std::string key2(key, 0, key.size() - 5);
|
||||
auto path = CanonPath(value);
|
||||
auto rev = accessor.dynamic_pointer_cast<GitInputAccessor>()->getSubmoduleRev(path);
|
||||
result.push_back(Submodule {
|
||||
.path = path,
|
||||
.url = entries[key2 + ".url"],
|
||||
.branch = entries[key2 + ".branch"],
|
||||
.rev = rev,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue