mirror of
https://github.com/rust-lang/cargo.git
synced 2025-10-01 11:30:39 +00:00
fix: normalize relative git submodule urls with ssh://
Git only assumes a submodule URL is a relative path if it starts with `./` or `../` [^1]. To fetch the correct repo, we need to construct an aboslute submodule URL. At this moment it comes with some limitations: * GitHub doesn't accept non-normalized URLs wth relative paths. (`ssh://git@github.com/rust-lang/cargo.git/relative/..` is invalid) * `url` crate cannot parse SCP-like URLs. (`git@github.com:rust-lang/cargo.git` is not a valid WHATWG URL) To overcome these, this patch always tries `Url::parse` first to normalize the path. If it couldn't, append the relative path as the last resort and pray the remote git service supports non-normalized URLs. See also rust-lang/cargo#12404 and rust-lang/cargo#12295. [^1]: <https://git-scm.com/docs/git-submodule>
This commit is contained in:
parent
e9999571f6
commit
8a4d04414e
@ -440,20 +440,7 @@ impl<'a> GitCheckout<'a> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Git only assumes a URL is a relative path if it starts with `./` or `../`.
|
let child_remote_url = absolute_submodule_url(parent_remote_url, child_url_str)?;
|
||||||
// See [`git submodule add`] documentation.
|
|
||||||
//
|
|
||||||
// [`git submodule add`]: https://git-scm.com/docs/git-submodule
|
|
||||||
let child_remote_url = if ["./", "../"].iter().any(|p| child_url_str.starts_with(p)) {
|
|
||||||
let mut new_remote_url = parent_remote_url.to_string();
|
|
||||||
if !new_remote_url.ends_with('/') {
|
|
||||||
new_remote_url.push('/');
|
|
||||||
}
|
|
||||||
new_remote_url.push_str(child_url_str);
|
|
||||||
Cow::from(new_remote_url)
|
|
||||||
} else {
|
|
||||||
Cow::from(child_url_str)
|
|
||||||
};
|
|
||||||
|
|
||||||
// A submodule which is listed in .gitmodules but not actually
|
// A submodule which is listed in .gitmodules but not actually
|
||||||
// checked out will not have a head id, so we should ignore it.
|
// checked out will not have a head id, so we should ignore it.
|
||||||
@ -507,6 +494,58 @@ impl<'a> GitCheckout<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs an absolute URL for a child submodule URL with its parent base URL.
|
||||||
|
///
|
||||||
|
/// Git only assumes a submodule URL is a relative path if it starts with `./`
|
||||||
|
/// or `../` [^1]. To fetch the correct repo, we need to construct an absolute
|
||||||
|
/// submodule URL.
|
||||||
|
///
|
||||||
|
/// At this moment it comes with some limitations:
|
||||||
|
///
|
||||||
|
/// * GitHub doesn't accept non-normalized URLs with relative paths.
|
||||||
|
/// (`ssh://git@github.com/rust-lang/cargo.git/relative/..` is invalid)
|
||||||
|
/// * `url` crate cannot parse SCP-like URLs.
|
||||||
|
/// (`git@github.com:rust-lang/cargo.git` is not a valid WHATWG URL)
|
||||||
|
///
|
||||||
|
/// To overcome these, this patch always tries [`Url::parse`] first to normalize
|
||||||
|
/// the path. If it couldn't, append the relative path as the last resort and
|
||||||
|
/// pray the remote git service supports non-normalized URLs.
|
||||||
|
///
|
||||||
|
/// See also rust-lang/cargo#12404 and rust-lang/cargo#12295.
|
||||||
|
///
|
||||||
|
/// [^1]: <https://git-scm.com/docs/git-submodule>
|
||||||
|
fn absolute_submodule_url<'s>(base_url: &str, submodule_url: &'s str) -> CargoResult<Cow<'s, str>> {
|
||||||
|
let absolute_url = if ["./", "../"].iter().any(|p| submodule_url.starts_with(p)) {
|
||||||
|
match Url::parse(base_url) {
|
||||||
|
Ok(mut base_url) => {
|
||||||
|
let path = base_url.path();
|
||||||
|
if !path.ends_with('/') {
|
||||||
|
base_url.set_path(&format!("{path}/"));
|
||||||
|
}
|
||||||
|
let absolute_url = base_url.join(submodule_url).with_context(|| {
|
||||||
|
format!(
|
||||||
|
"failed to parse relative child submodule url `{submodule_url}` \
|
||||||
|
using parent base url `{base_url}`"
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
Cow::from(absolute_url.to_string())
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
let mut absolute_url = base_url.to_string();
|
||||||
|
if !absolute_url.ends_with('/') {
|
||||||
|
absolute_url.push('/');
|
||||||
|
}
|
||||||
|
absolute_url.push_str(submodule_url);
|
||||||
|
Cow::from(absolute_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Cow::from(submodule_url)
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(absolute_url)
|
||||||
|
}
|
||||||
|
|
||||||
/// Prepare the authentication callbacks for cloning a git repository.
|
/// Prepare the authentication callbacks for cloning a git repository.
|
||||||
///
|
///
|
||||||
/// The main purpose of this function is to construct the "authentication
|
/// The main purpose of this function is to construct the "authentication
|
||||||
@ -1486,3 +1525,102 @@ fn is_short_hash_of(rev: &str, oid: Oid) -> bool {
|
|||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::absolute_submodule_url;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_absolute_submodule_url() {
|
||||||
|
let cases = [
|
||||||
|
(
|
||||||
|
"ssh://git@gitub.com/rust-lang/cargo",
|
||||||
|
"git@github.com:rust-lang/cargo.git",
|
||||||
|
"git@github.com:rust-lang/cargo.git",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ssh://git@gitub.com/rust-lang/cargo",
|
||||||
|
"./",
|
||||||
|
"ssh://git@gitub.com/rust-lang/cargo/",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ssh://git@gitub.com/rust-lang/cargo",
|
||||||
|
"../",
|
||||||
|
"ssh://git@gitub.com/rust-lang/",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ssh://git@gitub.com/rust-lang/cargo",
|
||||||
|
"./foo",
|
||||||
|
"ssh://git@gitub.com/rust-lang/cargo/foo",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ssh://git@gitub.com/rust-lang/cargo/",
|
||||||
|
"./foo",
|
||||||
|
"ssh://git@gitub.com/rust-lang/cargo/foo",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ssh://git@gitub.com/rust-lang/cargo/",
|
||||||
|
"../foo",
|
||||||
|
"ssh://git@gitub.com/rust-lang/foo",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ssh://git@gitub.com/rust-lang/cargo",
|
||||||
|
"../foo",
|
||||||
|
"ssh://git@gitub.com/rust-lang/foo",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"ssh://git@gitub.com/rust-lang/cargo",
|
||||||
|
"../foo/bar/../baz",
|
||||||
|
"ssh://git@gitub.com/rust-lang/foo/baz",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"git@github.com:rust-lang/cargo.git",
|
||||||
|
"ssh://git@gitub.com/rust-lang/cargo",
|
||||||
|
"ssh://git@gitub.com/rust-lang/cargo",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"git@github.com:rust-lang/cargo.git",
|
||||||
|
"./",
|
||||||
|
"git@github.com:rust-lang/cargo.git/./",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"git@github.com:rust-lang/cargo.git",
|
||||||
|
"../",
|
||||||
|
"git@github.com:rust-lang/cargo.git/../",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"git@github.com:rust-lang/cargo.git",
|
||||||
|
"./foo",
|
||||||
|
"git@github.com:rust-lang/cargo.git/./foo",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"git@github.com:rust-lang/cargo.git/",
|
||||||
|
"./foo",
|
||||||
|
"git@github.com:rust-lang/cargo.git/./foo",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"git@github.com:rust-lang/cargo.git",
|
||||||
|
"../foo",
|
||||||
|
"git@github.com:rust-lang/cargo.git/../foo",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"git@github.com:rust-lang/cargo.git/",
|
||||||
|
"../foo",
|
||||||
|
"git@github.com:rust-lang/cargo.git/../foo",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"git@github.com:rust-lang/cargo.git",
|
||||||
|
"../foo/bar/../baz",
|
||||||
|
"git@github.com:rust-lang/cargo.git/../foo/bar/../baz",
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (base_url, submodule_url, expected) in cases {
|
||||||
|
let url = absolute_submodule_url(base_url, submodule_url).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
expected, url,
|
||||||
|
"base `{base_url}`; submodule `{submodule_url}`"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -3633,7 +3633,7 @@ fn different_user_relative_submodules() {
|
|||||||
.file("Cargo.toml", &basic_lib_manifest("dep1"))
|
.file("Cargo.toml", &basic_lib_manifest("dep1"))
|
||||||
.file("src/lib.rs", "")
|
.file("src/lib.rs", "")
|
||||||
});
|
});
|
||||||
let _user2_git_project2 = git::new("user2/dep2", |project| {
|
let user2_git_project2 = git::new("user2/dep2", |project| {
|
||||||
project
|
project
|
||||||
.file("Cargo.toml", &basic_lib_manifest("dep1"))
|
.file("Cargo.toml", &basic_lib_manifest("dep1"))
|
||||||
.file("src/lib.rs", "")
|
.file("src/lib.rs", "")
|
||||||
@ -3673,14 +3673,14 @@ fn different_user_relative_submodules() {
|
|||||||
"\
|
"\
|
||||||
[UPDATING] git repository `{}`
|
[UPDATING] git repository `{}`
|
||||||
[UPDATING] git submodule `{}`
|
[UPDATING] git submodule `{}`
|
||||||
[UPDATING] git submodule `{}/../dep2`
|
[UPDATING] git submodule `{}`
|
||||||
[COMPILING] dep1 v0.5.0 ({}#[..])
|
[COMPILING] dep1 v0.5.0 ({}#[..])
|
||||||
[COMPILING] foo v0.5.0 ([CWD])
|
[COMPILING] foo v0.5.0 ([CWD])
|
||||||
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
|
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]
|
||||||
",
|
",
|
||||||
path2url(&user1_git_project.root()),
|
path2url(&user1_git_project.root()),
|
||||||
path2url(&user2_git_project.root()),
|
path2url(&user2_git_project.root()),
|
||||||
path2url(&user2_git_project.root()),
|
path2url(&user2_git_project2.root()),
|
||||||
path2url(&user1_git_project.root()),
|
path2url(&user1_git_project.root()),
|
||||||
))
|
))
|
||||||
.run();
|
.run();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user