Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for relative git submodule paths #11106

Merged
merged 4 commits into from
Sep 21, 2022
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
3 changes: 2 additions & 1 deletion src/cargo/sources/git/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ impl<'cfg> Source for GitSource<'cfg> {
.join("checkouts")
.join(&self.ident)
.join(short_id.as_str());
db.copy_to(actual_rev, &checkout_path, self.config)?;
let parent_remote_url = self.url();
db.copy_to(actual_rev, &checkout_path, self.config, parent_remote_url)?;

let source_id = self.source_id.with_precise(Some(actual_rev.to_string()));
let path_source = PathSource::new_recursive(&checkout_path, source_id, self.config);
Expand Down
66 changes: 51 additions & 15 deletions src/cargo/sources/git/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use git2::{self, ErrorClass, ObjectType, Oid};
use log::{debug, info};
use serde::ser;
use serde::Serialize;
use std::borrow::Cow;
use std::env;
use std::fmt;
use std::path::{Path, PathBuf};
Expand Down Expand Up @@ -151,6 +152,7 @@ impl GitDatabase {
rev: git2::Oid,
dest: &Path,
cargo_config: &Config,
parent_remote_url: &Url,
) -> CargoResult<GitCheckout<'_>> {
// If the existing checkout exists, and it is fresh, use it.
// A non-fresh checkout can happen if the checkout operation was
Expand All @@ -164,7 +166,7 @@ impl GitDatabase {
Some(co) => co,
None => GitCheckout::clone_into(dest, self, rev, cargo_config)?,
};
checkout.update_submodules(cargo_config)?;
checkout.update_submodules(cargo_config, parent_remote_url)?;
Ok(checkout)
}

Expand Down Expand Up @@ -322,19 +324,25 @@ impl<'a> GitCheckout<'a> {
Ok(())
}

fn update_submodules(&self, cargo_config: &Config) -> CargoResult<()> {
return update_submodules(&self.repo, cargo_config);
fn update_submodules(&self, cargo_config: &Config, parent_remote_url: &Url) -> CargoResult<()> {
return update_submodules(&self.repo, cargo_config, parent_remote_url);

fn update_submodules(repo: &git2::Repository, cargo_config: &Config) -> CargoResult<()> {
fn update_submodules(
repo: &git2::Repository,
cargo_config: &Config,
parent_remote_url: &Url,
) -> CargoResult<()> {
debug!("update submodules for: {:?}", repo.workdir().unwrap());

for mut child in repo.submodules()? {
update_submodule(repo, &mut child, cargo_config).with_context(|| {
format!(
"failed to update submodule `{}`",
child.name().unwrap_or("")
)
})?;
update_submodule(repo, &mut child, cargo_config, parent_remote_url).with_context(
|| {
format!(
"failed to update submodule `{}`",
child.name().unwrap_or("")
)
},
)?;
}
Ok(())
}
Expand All @@ -343,9 +351,11 @@ impl<'a> GitCheckout<'a> {
parent: &git2::Repository,
child: &mut git2::Submodule<'_>,
cargo_config: &Config,
parent_remote_url: &Url,
) -> CargoResult<()> {
child.init(false)?;
let url = child.url().ok_or_else(|| {

let child_url_str = child.url().ok_or_else(|| {
anyhow::format_err!("non-utf8 url for submodule {:?}?", child.path())
})?;

Expand All @@ -355,12 +365,38 @@ impl<'a> GitCheckout<'a> {
"Skipping",
format!(
"git submodule `{}` due to update strategy in .gitmodules",
url
child_url_str
),
)?;
return Ok(());
}

// Git only assumes a URL is a relative path if it starts with `./` or `../`.
// See [`git submodule add`] documentation.
//
// [`git submodule add`]: https://git-scm.com/docs/git-submodule
let url = if child_url_str.starts_with("./") || child_url_str.starts_with("../") {
let mut new_parent_remote_url = parent_remote_url.clone();

let mut new_path = Cow::from(parent_remote_url.path());
if !new_path.ends_with('/') {
new_path.to_mut().push('/');
}
new_parent_remote_url.set_path(&new_path);

match new_parent_remote_url.join(child_url_str) {
Ok(x) => x.to_string(),
Err(err) => Err(err).with_context(|| {
format!(
"failed to parse relative child submodule url `{}` using parent base url `{}`",
child_url_str, new_parent_remote_url
)
})?,
}
} else {
child_url_str.to_string()
};

// A submodule which is listed in .gitmodules but not actually
// checked out will not have a head id, so we should ignore it.
let head = match child.head_id() {
Expand All @@ -379,7 +415,7 @@ impl<'a> GitCheckout<'a> {
let mut repo = match head_and_repo {
Ok((head, repo)) => {
if child.head_id() == head {
return update_submodules(&repo, cargo_config);
return update_submodules(&repo, cargo_config, parent_remote_url);
}
repo
}
Expand All @@ -394,7 +430,7 @@ impl<'a> GitCheckout<'a> {
cargo_config
.shell()
.status("Updating", format!("git submodule `{}`", url))?;
fetch(&mut repo, url, &reference, cargo_config).with_context(|| {
fetch(&mut repo, &url, &reference, cargo_config).with_context(|| {
format!(
"failed to fetch submodule `{}` from {}",
child.name().unwrap_or(""),
Expand All @@ -404,7 +440,7 @@ impl<'a> GitCheckout<'a> {

let obj = repo.find_object(head, None)?;
reset(&repo, &obj, cargo_config)?;
update_submodules(&repo, cargo_config)
update_submodules(&repo, cargo_config, parent_remote_url)
}
}
}
Expand Down
67 changes: 67 additions & 0 deletions tests/testsuite/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,73 @@ fn dep_with_submodule() {
.run();
}

#[cargo_test]
fn dep_with_relative_submodule() {
let foo = project();
let base = git::new("base", |project| {
project
.file(
"Cargo.toml",
r#"
[package]
name = "base"
version = "0.5.0"

[dependencies]
deployment.path = "deployment"
"#,
)
.file(
"src/lib.rs",
r#"
pub fn dep() {
deployment::deployment_func();
}
"#,
)
});
let _deployment = git::new("deployment", |project| {
project
.file("src/lib.rs", "pub fn deployment_func() {}")
.file("Cargo.toml", &basic_lib_manifest("deployment"))
});

let base_repo = git2::Repository::open(&base.root()).unwrap();
git::add_submodule(&base_repo, "../deployment", Path::new("deployment"));
git::commit(&base_repo);

let project = foo
.file(
"Cargo.toml",
&format!(
r#"
[project]
name = "foo"
version = "0.5.0"

[dependencies.base]
git = '{}'
"#,
base.url()
),
)
.file("src/lib.rs", "pub fn foo() { }")
.build();

project
.cargo("build")
.with_stderr(
"\
[UPDATING] git repository [..]
[UPDATING] git submodule `file://[..]/deployment`
[COMPILING] deployment [..]
[COMPILING] base [..]
[COMPILING] foo [..]
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
)
.run();
}

#[cargo_test]
fn dep_with_bad_submodule() {
let project = project();
Expand Down