Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 199 additions & 0 deletions src/libfetchers-tests/git.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
#include "nix/store/store-open.hh"
#include "nix/fetchers/fetch-settings.hh"
#include "nix/fetchers/fetchers.hh"
#include "nix/fetchers/git-utils.hh"

#include <git2.h>
#include <gtest/gtest.h>

#include <filesystem>

namespace {

template<class T, void (*F)(T *)>
struct Deleter
{
void operator()(T * p) const
{
if (p)
F(p);
}
};

template<class T, void (*F)(T *)>
using Handle = std::unique_ptr<T, Deleter<T, F>>;

using RepoHandle = Handle<git_repository, git_repository_free>;
using IndexHandle = Handle<git_index, git_index_free>;
using TreeHandle = Handle<git_tree, git_tree_free>;
using SigHandle = Handle<git_signature, git_signature_free>;
using RefHandle = Handle<git_reference, git_reference_free>;
using CommitHandle = Handle<git_commit, git_commit_free>;
using SubmoduleHandle = Handle<git_submodule, git_submodule_free>;

#define CHECK_LIBGIT(expr) ASSERT_TRUE((expr) >= 0) << git_error_last()

static void commitAll(git_repository * repo, const char * msg)
{
IndexHandle idx;
{
git_index * raw = nullptr;
CHECK_LIBGIT(git_repository_index(&raw, repo));
idx.reset(raw);
}
CHECK_LIBGIT(git_index_add_all(idx.get(), nullptr, 0, nullptr, nullptr));
CHECK_LIBGIT(git_index_write(idx.get()));

git_oid treeId{};
CHECK_LIBGIT(git_index_write_tree(&treeId, idx.get()));
TreeHandle tree;
{
git_tree * raw = nullptr;
CHECK_LIBGIT(git_tree_lookup(&raw, repo, &treeId));
tree.reset(raw);
}

SigHandle sig;
{
git_signature * raw = nullptr;
CHECK_LIBGIT(git_signature_now(&raw, "you", "you@example.com"));
sig.reset(raw);
}

git_oid commitId{};
if (git_repository_is_empty(repo) == 1) {
CHECK_LIBGIT(git_commit_create_v(&commitId, repo, "HEAD", sig.get(), sig.get(), nullptr, msg, tree.get(), 0));
CHECK_LIBGIT(git_reference_create(nullptr, repo, "refs/heads/main", &commitId, true, nullptr));
CHECK_LIBGIT(git_repository_set_head(repo, "refs/heads/main"));
} else {
RefHandle head;
{
git_reference * raw = nullptr;
CHECK_LIBGIT(git_repository_head(&raw, repo));
head.reset(raw);
}
CommitHandle parent;
{
git_commit * raw = nullptr;
CHECK_LIBGIT(git_commit_lookup(&raw, repo, git_reference_target(head.get())));
parent.reset(raw);
}
const git_commit * parents[] = {parent.get()};
CHECK_LIBGIT(git_commit_create(
&commitId,
repo,
"HEAD",
sig.get(),
sig.get(),
/*message_encoding=*/nullptr,
msg,
tree.get(),
/*parent_count=*/1,
&parents[0]));
}
}

} // namespace

using namespace nix;

class GitTest : public ::testing::Test
{
std::unique_ptr<AutoDelete> delTmpDir;

protected:
std::filesystem::path tmpDir;

void SetUp() override
{
tmpDir = createTempDir();
delTmpDir = std::make_unique<AutoDelete>(tmpDir, /*recursive=*/true);
nix::initLibStore(/*loadConfig=*/false);
git_libgit2_init();
}

void TearDown() override
{
delTmpDir.reset();
}
};

// Regression test for https://github.com/NixOS/nix/issues/13215
TEST_F(GitTest, submodulePeriodSupport)
{
auto storePath = tmpDir / "store";
auto repoPath = tmpDir / "repo";
auto submodulePath = tmpDir / "submodule";

// Set up our git directories: one top level and a submodule
// the submodule in the .gitmodules has the branch listed as '.'

// 1) Create sub repo
{
git_repository * raw = nullptr;
CHECK_LIBGIT(git_repository_init(&raw, submodulePath.string().c_str(), /*is_bare=*/0));
RepoHandle sub(raw);
writeFile(submodulePath / "lib.txt", "hello from submodule\n");
commitAll(sub.get(), "init sub");
}

// 2) Create super repo
RepoHandle super;
{
git_repository * raw = nullptr;
CHECK_LIBGIT(git_repository_init(&raw, repoPath.string().c_str(), /*is_bare=*/0));
super.reset(raw);
}

writeFile(repoPath / "README.md", "# super\n");
commitAll(super.get(), "init super");

// 3) Add submodule at deps/sub
{
git_repository * raw = nullptr;
git_clone_options cloneOpts = GIT_CLONE_OPTIONS_INIT;
// clone from local subPath into superPath/deps/sub
CHECK_LIBGIT(
git_clone(&raw, submodulePath.string().c_str(), (repoPath / "deps" / "sub").string().c_str(), &cloneOpts));
RepoHandle sub(raw);
}

// 4) Add submodule and set branch="."
SubmoduleHandle sm;
{
git_submodule * raw = nullptr;
CHECK_LIBGIT(git_submodule_add_setup(
&raw,
super.get(),
"../submodule",
"deps/sub",
/*use_gitlink=*/1));
sm.reset(raw);
}
CHECK_LIBGIT(git_submodule_set_branch(super.get(), git_submodule_name(sm.get()), /*branch=*/"."));
CHECK_LIBGIT(git_submodule_sync(sm.get()));

// 5) Finalize now that the worktree exists; libgit2 can read its HEAD OID
CHECK_LIBGIT(git_submodule_add_finalize(sm.get()));
// 6) Commit the addition in super
commitAll(super.get(), "Add submodule with branch='.'");

// TODO: Use dummy:// store with MemorySourceAccessor.
Path storeTmpDir = createTempDir();
auto storeTmpDirAutoDelete = AutoDelete(storeTmpDir, true);
ref<Store> store = openStore(storeTmpDir);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @Ericson2314 had a old PR to make dummy:// use more in-memory stuff. Would that be useful here? That never got merged.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one #10915

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discussed on matrix.
I will like to pick it up; seems worthwhile -- I might also make it a separate store.

Either way I think it doesn't block this PR.


auto settings = fetchers::Settings{};
auto input = fetchers::Input::fromAttrs(
settings,
{
{"url", "file://" + repoPath.string()},
{"submodules", Explicit{true}},
{"type", "git"},
{"ref", "main"},
});

auto [accessor, i] = input.getAccessor(store);

ASSERT_EQ(accessor->readFile(CanonPath("deps/sub/lib.txt")), "hello from submodule\n");
}
1 change: 1 addition & 0 deletions src/libfetchers-tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ subdir('nix-meson-build-support/common')
sources = files(
'access-tokens.cc',
'git-utils.cc',
'git.cc',
'nix_api_fetchers.cc',
'public-key.cc',
)
Expand Down
18 changes: 7 additions & 11 deletions src/libfetchers-tests/package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
buildPackages,
stdenv,
mkMesonExecutable,
writableTmpDirAsHomeHook,

nix-fetchers,
nix-fetchers-c,
Expand Down Expand Up @@ -57,18 +58,13 @@ mkMesonExecutable (finalAttrs: {
runCommand "${finalAttrs.pname}-run"
{
meta.broken = !stdenv.hostPlatform.emulatorAvailable buildPackages;
buildInputs = [ writableTmpDirAsHomeHook ];
}
(
lib.optionalString stdenv.hostPlatform.isWindows ''
export HOME="$PWD/home-dir"
mkdir -p "$HOME"
''
+ ''
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
touch $out
''
);
''
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
touch $out
'';
};
};

Expand Down
12 changes: 10 additions & 2 deletions src/libfetchers/git.cc
Original file line number Diff line number Diff line change
Expand Up @@ -739,8 +739,16 @@ struct GitInputScheme : InputScheme
fetchers::Attrs attrs;
attrs.insert_or_assign("type", "git");
attrs.insert_or_assign("url", resolved);
if (submodule.branch != "")
attrs.insert_or_assign("ref", submodule.branch);
if (submodule.branch != "") {
// A special value of . is used to indicate that the name of the branch in the submodule
// should be the same name as the current branch in the current repository.
// https://git-scm.com/docs/gitmodules
if (submodule.branch == ".") {
attrs.insert_or_assign("ref", ref);
} else {
attrs.insert_or_assign("ref", submodule.branch);
}
}
attrs.insert_or_assign("rev", submoduleRev.gitRev());
attrs.insert_or_assign("exportIgnore", Explicit<bool>{exportIgnore});
attrs.insert_or_assign("submodules", Explicit<bool>{true});
Expand Down
Loading