diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc index 9cae9034e..3b593ae52 100644 --- a/src/libfetchers/git-utils.cc +++ b/src/libfetchers/git-utils.cc @@ -197,6 +197,12 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this return git_repository_is_shallow(*this); } + void setRemote(const std::string & name, const std::string & url) override + { + if (git_remote_set_url(*this, name.c_str(), url.c_str())) + throw Error("setting remote '%s' URL to '%s': %s", name, url, git_error_last()->message); + } + Hash resolveRef(std::string ref) override { // Handle revisions used as refs. @@ -318,9 +324,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this std::vector> getSubmodules(const Hash & rev, bool exportIgnore) override; - std::string resolveSubmoduleUrl( - const std::string & url, - const std::string & base) 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())) @@ -328,10 +332,6 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this Finally cleanup = [&]() { git_buf_dispose(&buf); }; std::string res(buf.ptr); - - if (!hasPrefix(res, "/") && res.find("://") == res.npos) - res = parseURL(base + "/" + res).canonicalise().to_string(); - return res; } diff --git a/src/libfetchers/git-utils.hh b/src/libfetchers/git-utils.hh index fbb2d947b..600a42da0 100644 --- a/src/libfetchers/git-utils.hh +++ b/src/libfetchers/git-utils.hh @@ -32,6 +32,8 @@ struct GitRepo /* Return the commit hash to which a ref points. */ virtual Hash resolveRef(std::string ref) = 0; + virtual void setRemote(const std::string & name, const std::string & url) = 0; + /** * Info about a submodule. */ @@ -69,9 +71,7 @@ struct GitRepo */ virtual std::vector> getSubmodules(const Hash & rev, bool exportIgnore) = 0; - virtual std::string resolveSubmoduleUrl( - const std::string & url, - const std::string & base) = 0; + virtual std::string resolveSubmoduleUrl(const std::string & url) = 0; virtual bool hasObject(const Hash & oid) = 0; diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 25eabb1dc..f59d4d2c9 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -523,6 +523,9 @@ struct GitInputScheme : InputScheme auto repo = GitRepo::openRepo(cacheDir, true, true); + // We need to set the origin so resolving submodule URLs works + repo->setRemote("origin", repoInfo.url); + Path localRefFile = ref.compare(0, 5, "refs/") == 0 ? cacheDir + "/" + ref @@ -626,7 +629,7 @@ struct GitInputScheme : InputScheme std::map> mounts; for (auto & [submodule, submoduleRev] : repo->getSubmodules(rev, exportIgnore)) { - auto resolved = repo->resolveSubmoduleUrl(submodule.url, repoInfo.url); + auto resolved = repo->resolveSubmoduleUrl(submodule.url); debug("Git submodule %s: %s %s %s -> %s", submodule.path, submodule.url, submodule.branch, submoduleRev.gitRev(), resolved); fetchers::Attrs attrs; diff --git a/tests/nixos/default.nix b/tests/nixos/default.nix index 98de31e13..086fe41b6 100644 --- a/tests/nixos/default.nix +++ b/tests/nixos/default.nix @@ -145,6 +145,8 @@ in githubFlakes = runNixOSTestFor "x86_64-linux" ./github-flakes.nix; + gitSubmodules = runNixOSTestFor "x86_64-linux" ./git-submodules.nix; + sourcehutFlakes = runNixOSTestFor "x86_64-linux" ./sourcehut-flakes.nix; tarballFlakes = runNixOSTestFor "x86_64-linux" ./tarball-flakes.nix; diff --git a/tests/nixos/git-submodules.nix b/tests/nixos/git-submodules.nix new file mode 100644 index 000000000..9264369ef --- /dev/null +++ b/tests/nixos/git-submodules.nix @@ -0,0 +1,54 @@ +# Test Nix's remote build feature. + +{ lib, hostPkgs, ... }: + +{ + config = { + name = lib.mkDefault "git-submodules"; + + nodes = + { + remote = + { config, pkgs, ... }: + { + services.openssh.enable = true; + environment.systemPackages = [ pkgs.git ]; + }; + + client = + { config, lib, pkgs, ... }: + { + programs.ssh.extraConfig = "ConnectTimeout 30"; + environment.systemPackages = [ pkgs.git ]; + nix.extraOptions = "experimental-features = nix-command flakes"; + }; + }; + + testScript = { nodes }: '' + # fmt: off + import subprocess + + start_all() + + # Create an SSH key on the client. + subprocess.run([ + "${hostPkgs.openssh}/bin/ssh-keygen", "-t", "ed25519", "-f", "key", "-N", "" + ], capture_output=True, check=True) + client.succeed("mkdir -p -m 700 /root/.ssh") + client.copy_from_host("key", "/root/.ssh/id_ed25519") + client.succeed("chmod 600 /root/.ssh/id_ed25519") + + # Install the SSH key on the builders. + client.wait_for_unit("network.target") + + remote.succeed("mkdir -p -m 700 /root/.ssh") + remote.copy_from_host("key.pub", "/root/.ssh/authorized_keys") + remote.wait_for_unit("sshd") + client.succeed(f"ssh -o StrictHostKeyChecking=no {remote.name} 'echo hello world'") + + remote.succeed("git init bar && git -C bar config user.email foobar@example.com && git -C bar config user.name Foobar && echo test >> bar/content && git -C bar add content && git -C bar commit -m 'Initial commit'") + client.succeed(f"git init foo && git -C foo config user.email foobar@example.com && git -C foo config user.name Foobar && git -C foo submodule add root@{remote.name}:/tmp/bar sub && git -C foo add sub && git -C foo commit -m 'Add submodule'") + client.succeed("nix --flake-registry \"\" flake prefetch 'git+file:///tmp/foo?submodules=1&ref=master'") + ''; + }; +}