Skip to content

Commit 7d7c370

Browse files
committed
Auto merge of #9859 - dtolnay-contrib:pullrequest, r=ehuss
rev = "refs/pull/𑑛/head" GitHub exposes a named reference associated with the head of every pull request. For example, you can fetch *this* pull request: ```console $ git fetch origin refs/pull/9859/head $ git show FETCH_HEAD ``` Usually when I care about pulling in a patch of one of my dependencies using `[patch.crates-io]`, this is the ref that I want to depend on. None of the alternatives are good: - `{ git = "the fork", branch = "the-pr-branch" }` — this is second closest to what I want, except that when the PR merges and the PR author deletes their fork, it'll breaks. - `{ git = "the fork", rev = "commithash" }` — same failure mode as the previous. Also doesn't stay up to date with PR, which is sometimes what I want. - `{ git = "the upstream", rev = "commithash" }` — doesn't work until the PR is merged or the repo owner creates a named branch or tag containing the PR commit among its ancestors, because otherwise the commit doesn't participate in Cargo's fetch. - `{ git = "my own fork of PR author's repo", branch = "the-pr-branch" }` — doesn't stay up to date with PR. This PR adds support for specifying a git dependency on the head of a pull request via the existing `rev` setting of git dependencies: `{ git = "the upstream", rev = "refs/pull/9859/head" }`. Previously this would fail because the `cargo::sources::git::fetch` function touched in this pull request did not fetch the refspec that we care about. The failures look like: ```console error: failed to get `mockall` as a dependency of package `testing v0.0.0` Caused by: failed to load source for dependency `mockall` Caused by: Unable to update https://github.com/asomers/mockall?rev=refs/pull/330/head Caused by: revspec 'refs/pull/330/head' not found; class=Reference (4); code=NotFound (-3) ``` If dual purposing `rev` for this is not appealing, I would alternatively propose `{ git = "the upstream", pull-request = "9859" }` which Cargo will interpret using GitHub's ref naming convention as `refs/pull/9859/head`.
2 parents 941b123 + 86276d4 commit 7d7c370

File tree

3 files changed

+87
-10
lines changed

3 files changed

+87
-10
lines changed

src/cargo/sources/git/utils.rs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -799,13 +799,17 @@ pub fn fetch(
799799
refspecs.push(String::from("HEAD:refs/remotes/origin/HEAD"));
800800
}
801801

802-
// For `rev` dependencies we don't know what the rev will point to. To
803-
// handle this situation we fetch all branches and tags, and then we
804-
// pray it's somewhere in there.
805-
GitReference::Rev(_) => {
806-
refspecs.push(String::from("refs/heads/*:refs/remotes/origin/*"));
807-
refspecs.push(String::from("HEAD:refs/remotes/origin/HEAD"));
808-
tags = true;
802+
GitReference::Rev(rev) => {
803+
if rev.starts_with("refs/") {
804+
refspecs.push(format!("{0}:{0}", rev));
805+
} else {
806+
// We don't know what the rev will point to. To handle this
807+
// situation we fetch all branches and tags, and then we pray
808+
// it's somewhere in there.
809+
refspecs.push(String::from("refs/heads/*:refs/remotes/origin/*"));
810+
refspecs.push(String::from("HEAD:refs/remotes/origin/HEAD"));
811+
tags = true;
812+
}
809813
}
810814
}
811815

@@ -1025,9 +1029,13 @@ fn github_up_to_date(
10251029
GitReference::Branch(branch) => branch,
10261030
GitReference::Tag(tag) => tag,
10271031
GitReference::DefaultBranch => "HEAD",
1028-
GitReference::Rev(_) => {
1029-
debug!("can't use github fast path with `rev`");
1030-
return Ok(false);
1032+
GitReference::Rev(rev) => {
1033+
if rev.starts_with("refs/") {
1034+
rev
1035+
} else {
1036+
debug!("can't use github fast path with `rev = \"{}\"`", rev);
1037+
return Ok(false);
1038+
}
10311039
}
10321040
};
10331041

src/doc/src/reference/specifying-dependencies.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,13 @@ the latest commit on a branch named `next`:
147147
regex = { git = "https://github.com/rust-lang/regex", branch = "next" }
148148
```
149149

150+
Anything that is not a branch or tag falls under `rev`. This can be a commit
151+
hash like `rev = "4c59b707"`, or a named reference exposed by the remote
152+
repository such as `rev = "refs/pull/493/head"`. What references are available
153+
varies by where the repo is hosted; GitHub in particular exposes a reference to
154+
the most recent commit of every pull request as shown, but other git hosts often
155+
provide something equivalent, possibly under a different naming scheme.
156+
150157
Once a `git` dependency has been added, Cargo will lock that dependency to the
151158
latest commit at the time. New commits will not be pulled down automatically
152159
once the lock is in place. However, they can be pulled down manually with

tests/testsuite/git.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,68 @@ fn cargo_compile_git_dep_tag() {
230230
project.cargo("build").run();
231231
}
232232

233+
#[cargo_test]
234+
fn cargo_compile_git_dep_pull_request() {
235+
let project = project();
236+
let git_project = git::new("dep1", |project| {
237+
project
238+
.file("Cargo.toml", &basic_lib_manifest("dep1"))
239+
.file(
240+
"src/dep1.rs",
241+
r#"
242+
pub fn hello() -> &'static str {
243+
"hello world"
244+
}
245+
"#,
246+
)
247+
});
248+
249+
// Make a reference in GitHub's pull request ref naming convention.
250+
let repo = git2::Repository::open(&git_project.root()).unwrap();
251+
let oid = repo.refname_to_id("HEAD").unwrap();
252+
let force = false;
253+
let log_message = "open pull request";
254+
repo.reference("refs/pull/330/head", oid, force, log_message)
255+
.unwrap();
256+
257+
let project = project
258+
.file(
259+
"Cargo.toml",
260+
&format!(
261+
r#"
262+
[project]
263+
name = "foo"
264+
version = "0.0.0"
265+
266+
[dependencies]
267+
dep1 = {{ git = "{}", rev = "refs/pull/330/head" }}
268+
"#,
269+
git_project.url()
270+
),
271+
)
272+
.file(
273+
"src/main.rs",
274+
&main_file(r#""{}", dep1::hello()"#, &["dep1"]),
275+
)
276+
.build();
277+
278+
let git_root = git_project.root();
279+
280+
project
281+
.cargo("build")
282+
.with_stderr(&format!(
283+
"[UPDATING] git repository `{}`\n\
284+
[COMPILING] dep1 v0.5.0 ({}?rev=refs/pull/330/head#[..])\n\
285+
[COMPILING] foo v0.0.0 ([CWD])\n\
286+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
287+
path2url(&git_root),
288+
path2url(&git_root),
289+
))
290+
.run();
291+
292+
assert!(project.bin("foo").is_file());
293+
}
294+
233295
#[cargo_test]
234296
fn cargo_compile_with_nested_paths() {
235297
let git_project = git::new("dep1", |project| {

0 commit comments

Comments
 (0)