Repository navigation
Conversation
| if url.contains('#') { | ||
| return Err(anyhow::format_err!( | ||
| "invalid git url to install from\n\n\ | ||
| help: use `--rev <SHA>` to specify a commit" | ||
| ) | ||
| .into()); | ||
| } | ||
|
|
There was a problem hiding this comment.
Is this definitively a bad URL or should this be a hint on the existing error?
There was a problem hiding this comment.
Cargo supports more than Github. The question would be more about the git protocol and what is expected around it as we could be turning a success case into an error case.
There was a problem hiding this comment.
From what I'm seeing here, other git servers should be the same.
There was a problem hiding this comment.
I might be missing it but I'm not seeing anything at that link saying servers will reject fragments.
There was a problem hiding this comment.
Sorry, I don't remember what I thought back then.
552440b to
dcc8cef
Compare
There was a problem hiding this comment.
I did some digging through the history to understand why we have this issue.
Cargo has long used # internally to serialize the resolved git revision in SourceId (e465ace, 2014), which is reasonable for lockfile/source tracking.
cargo install --git was added later in PR #2026 / commit bc60f64 (2015). It parsed the user-provided git URL but did not reject URL fragments. Install tracking then serialized the package source as: original URL + Cargo's internal #<resolved_commit>.
So https://host/repo.git#foo became git+https://host/repo.git#foo#actual_commit.
Thanks for working on this. Not sure if you still want to revisit this one, but I think the check fragment seems reasonable here.
|
@rustbot author |
|
Reminder, once the PR becomes ready for a review, use |
dcc8cef to
f9b37b9
Compare
This comment has been minimized.
This comment has been minimized.
|
@rustbot ready |
There was a problem hiding this comment.
@epage I'm not sure whether we need to consider this a breaking change or just a bug. It does look like a bug, but I'm not sure if someone would really rely on this. What are your thoughts on this?
| fn install_git_with_rev_in_url() { | ||
| let p = git::repo(&paths::root().join("foo")) | ||
| .file("Cargo.toml", &basic_manifest("foo", "0.1.0")) | ||
| .file("src/main.rs", "fn main() {}") | ||
| .build(); | ||
|
|
||
| let mut url = p.url().to_string(); | ||
| url.push_str("#0e8d88b5cfc173c5f5a6a0fe0ce1d4e6018600d4"); | ||
|
|
||
| cargo_process("install --locked --git") | ||
| .arg(url.to_string()) | ||
| .with_stderr_data(str![[r#" | ||
| [UPDATING] git repository `[ROOTURL]/foo#0e8d88b5cfc173c5f5a6a0fe0ce1d4e6018600d4` | ||
| [WARNING] spurious network error (3 tries remaining): failed to resolve path '[ROOT]/foo#0e8d88b5cfc173c5f5a6a0fe0ce1d4e6018600d4': No such file or directory; class=Os (2) | ||
| [WARNING] spurious network error (2 tries remaining): failed to resolve path '[ROOT]/foo#0e8d88b5cfc173c5f5a6a0fe0ce1d4e6018600d4': No such file or directory; class=Os (2) | ||
| [WARNING] spurious network error (1 try remaining): failed to resolve path '[ROOT]/foo#0e8d88b5cfc173c5f5a6a0fe0ce1d4e6018600d4': No such file or directory; class=Os (2) | ||
| [ERROR] failed to clone into: [ROOT]/home/.cargo/git/db/foo-[HASH] | ||
|
|
||
| Caused by: | ||
| failed to resolve path '[ROOT]/foo#0e8d88b5cfc173c5f5a6a0fe0ce1d4e6018600d4': No such file or directory; class=Os (2) | ||
|
|
||
| "#]]) | ||
| .with_status(101) | ||
| .run(); | ||
| } |
There was a problem hiding this comment.
#[cargo_test]
fn install_git_with_rev_in_url() {
let p = git::repo(&paths::root().join("foo"))
.file("Cargo.toml", &basic_manifest("foo", "0.1.0"))
.file("src/main.rs", "fn main() {}")
.build();
let rev = p.revparse_head();
// The malformed URL is passed to libgit2 as a path containing `#`.
// Create that path so the clone succeeds and the install can be tracked.
let fragment_path = paths::root().join(format!("foo#{rev}"));
git2::Repository::clone(p.url().as_str(), &fragment_path).unwrap();
let mut url = p.url().to_string();
url.push('#');
url.push_str(&rev);
cargo_process("install --locked --git").arg(url).run();
assert_has_installed_exe(paths::cargo_home(), "foo");
let crates_toml = fs::read_to_string(paths::home().join(".cargo/.crates.toml")).unwrap();
assert!(
crates_toml.contains(&format!("git+{}#{rev}#{rev}", p.url())),
"{crates_toml}"
);
}This would make this case really catch the buggy behavior from the issue.
There was a problem hiding this comment.
@0xPoe , thanks, that's a nice repro indeed.
f9b37b9 to
b6d0cee
Compare
It is more like a bug indeed, and I sympathize who ever relying on this. Though to be a bit cautious we can give a transition period and turn it a hard warning I guess? |
I tried to check whether that change would have any edge cases. But it seems there are no bad cases, as Git’s standard behavior never allows fragments in the URL: https://git-scm.com/docs/git-clone#_git_urls https://git-scm.com/docs/http-protocol And from this documentation, Git seems to use only the path and query, and no fragment is used at all. And for most Git platforms, the fragment is used to create the permalink. But I did notice that npm would accept git+https://github.com/org/foo.git#v1.0.0 as a valid URL to pick version 1.0.0, and so would other JavaScript tools like Yarn and Bun. On the other hand, would their code prove that it is safe to assume there is no # in the repository name at all? Otherwise, they would hit the bug caused by having too many # characters in the URL. https://github.com/pnpm/pnpm/blob/fbf78bd7d26a3da41b384f1ee35539992b4f799b/pnpm11/resolving/git-resolver/src/parseBareSpecifier.ts#L51 So I guess going with the direct error would be OK? |
The changes look good to me. I would like to wait for @weihanglo to check my response and decide whether we should add a transition period before turning it into a hard error. |
|
Sounds reasonable. Can go either way. |
|
@epage Just want to double-check with you before I merge it. Do you think a hard error would be OK here? Or do you prefer we go with a transition period here? |
|
|
||
| let source = if let Some(url) = args.get_one::<String>("git") { | ||
| let url = url.into_url()?; | ||
| if url.fragment().is_some() { |
There was a problem hiding this comment.
This change is directly in cargo install.
Does this mean use of fragments in dependencies gets the old behavior?
Does cargo add --git error before we write to the manifest?
There was a problem hiding this comment.
This change is directly in
cargo install.Does this mean use of fragments in dependencies gets the old behavior?
Yes, that behavior isn't touched.
Does
cargo add --giterror before we write to the manifest?
No, it doesn't error and doesn't warn from what I see.
There was a problem hiding this comment.
Yes, that behavior isn't touched.
is there a reason we are putting it in this layer so that it only affects one code path, rather than lower down so it affects all? For example, if we keep the error as is, what if we moved this into SourceId? That doesn't cover git add but it does cover quite a good number of cases.
There was a problem hiding this comment.
Mmh, maybe I misunderstand you but wouldn't disallowing a rev in SourceId make all the Cargo.toml files out there that already have such a git url fail to parse?
There was a problem hiding this comment.
Not disallowing it in the SourceId URL but in the URL we pass to SourceId::for_git.
There was a problem hiding this comment.
Having the error inside of SourceId::for_git escalates a warning to an error if there's a malformed git dependency in a Cargo.toml. I'm afraid this could affect lots of people.
Checking the argument at all callsides or changing the return of SourceId::for_git to keep it as a potential warning is, of course, against the idea to catch all code paths with a central check.
b6d0cee to
8251237
Compare
This comment has been minimized.
This comment has been minimized.
8251237 to
70f08b0
Compare
|
This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed. Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers. |
return error if the git url passed to `SourceId::for_git` contains a # and hint user to remove the fragment or use a flag
70f08b0 to
5d813a6
Compare
|
☔ The latest upstream changes (possibly #17506) made this pull request unmergeable. Please resolve the merge conflicts. |

View all comments
Check if the git url contains a '#' which is likely a sign for the user's desire to specify a rev. Give hint to use
--revFixes #15336