Skip to content

Commit b2e52d7

Browse files
committed
Auto merge of rust-lang#9988 - ehuss:beta-git-fetch-force, r=alexcrichton
[beta] Fix fetching git repos after a force push. Beta backport of rust-lang#9979.
2 parents c7957a7 + ffa597e commit b2e52d7

File tree

2 files changed

+111
-6
lines changed

2 files changed

+111
-6
lines changed

src/cargo/sources/git/utils.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -785,29 +785,32 @@ pub fn fetch(
785785
// which need to get fetched. Additionally record if we're fetching tags.
786786
let mut refspecs = Vec::new();
787787
let mut tags = false;
788+
// The `+` symbol on the refspec means to allow a forced (fast-forward)
789+
// update which is needed if there is ever a force push that requires a
790+
// fast-forward.
788791
match reference {
789792
// For branches and tags we can fetch simply one reference and copy it
790793
// locally, no need to fetch other branches/tags.
791794
GitReference::Branch(b) => {
792-
refspecs.push(format!("refs/heads/{0}:refs/remotes/origin/{0}", b));
795+
refspecs.push(format!("+refs/heads/{0}:refs/remotes/origin/{0}", b));
793796
}
794797
GitReference::Tag(t) => {
795-
refspecs.push(format!("refs/tags/{0}:refs/remotes/origin/tags/{0}", t));
798+
refspecs.push(format!("+refs/tags/{0}:refs/remotes/origin/tags/{0}", t));
796799
}
797800

798801
GitReference::DefaultBranch => {
799-
refspecs.push(String::from("HEAD:refs/remotes/origin/HEAD"));
802+
refspecs.push(String::from("+HEAD:refs/remotes/origin/HEAD"));
800803
}
801804

802805
GitReference::Rev(rev) => {
803806
if rev.starts_with("refs/") {
804-
refspecs.push(format!("{0}:{0}", rev));
807+
refspecs.push(format!("+{0}:{0}", rev));
805808
} else {
806809
// We don't know what the rev will point to. To handle this
807810
// situation we fetch all branches and tags, and then we pray
808811
// 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"));
812+
refspecs.push(String::from("+refs/heads/*:refs/remotes/origin/*"));
813+
refspecs.push(String::from("+HEAD:refs/remotes/origin/HEAD"));
811814
tags = true;
812815
}
813816
}

tests/testsuite/git.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3280,3 +3280,105 @@ fn metadata_master_consistency() {
32803280
let bar_source = format!("git+{}", git_project.url());
32813281
p.cargo("metadata").with_json(&metadata(&bar_source)).run();
32823282
}
3283+
3284+
#[cargo_test]
3285+
fn git_with_force_push() {
3286+
// Checks that cargo can handle force-pushes to git repos.
3287+
// This works by having a git dependency that is updated with an amend
3288+
// commit, and tries with various forms (default branch, branch, rev,
3289+
// tag).
3290+
let main = |text| format!(r#"pub fn f() {{ println!("{}"); }}"#, text);
3291+
let (git_project, repo) = git::new_repo("dep1", |project| {
3292+
project
3293+
.file("Cargo.toml", &basic_lib_manifest("dep1"))
3294+
.file("src/lib.rs", &main("one"))
3295+
});
3296+
let manifest = |extra| {
3297+
format!(
3298+
r#"
3299+
[project]
3300+
name = "foo"
3301+
version = "0.0.1"
3302+
edition = "2018"
3303+
3304+
[dependencies]
3305+
dep1 = {{ git = "{}"{} }}
3306+
"#,
3307+
git_project.url(),
3308+
extra
3309+
)
3310+
};
3311+
let p = project()
3312+
.file("Cargo.toml", &manifest(""))
3313+
.file("src/main.rs", "fn main() { dep1::f(); }")
3314+
.build();
3315+
// Download the original and make sure it is OK.
3316+
p.cargo("build").run();
3317+
p.rename_run("foo", "foo1").with_stdout("one").run();
3318+
3319+
let find_head = || t!(t!(repo.head()).peel_to_commit());
3320+
3321+
let amend_commit = |text| {
3322+
// commit --amend a change that will require a force fetch.
3323+
git_project.change_file("src/lib.rs", &main(text));
3324+
git::add(&repo);
3325+
let commit = find_head();
3326+
let tree_id = t!(t!(repo.index()).write_tree());
3327+
t!(commit.amend(
3328+
Some("HEAD"),
3329+
None,
3330+
None,
3331+
None,
3332+
None,
3333+
Some(&t!(repo.find_tree(tree_id)))
3334+
));
3335+
};
3336+
3337+
let mut rename_annoyance = 1;
3338+
3339+
let mut verify = |text: &str| {
3340+
// Perform the fetch.
3341+
p.cargo("update").run();
3342+
p.cargo("build").run();
3343+
rename_annoyance += 1;
3344+
p.rename_run("foo", &format!("foo{}", rename_annoyance))
3345+
.with_stdout(text)
3346+
.run();
3347+
};
3348+
3349+
amend_commit("two");
3350+
verify("two");
3351+
3352+
// Try with a rev.
3353+
let head1 = find_head().id().to_string();
3354+
let extra = format!(", rev = \"{}\"", head1);
3355+
p.change_file("Cargo.toml", &manifest(&extra));
3356+
verify("two");
3357+
amend_commit("three");
3358+
let head2 = find_head().id().to_string();
3359+
assert_ne!(&head1, &head2);
3360+
let extra = format!(", rev = \"{}\"", head2);
3361+
p.change_file("Cargo.toml", &manifest(&extra));
3362+
verify("three");
3363+
3364+
// Try with a tag.
3365+
git::tag(&repo, "my-tag");
3366+
p.change_file("Cargo.toml", &manifest(", tag = \"my-tag\""));
3367+
verify("three");
3368+
amend_commit("tag-three");
3369+
let head = t!(t!(repo.head()).peel(git2::ObjectType::Commit));
3370+
t!(repo.tag("my-tag", &head, &t!(repo.signature()), "move tag", true));
3371+
verify("tag-three");
3372+
3373+
// Try with a branch.
3374+
let br = t!(repo.branch("awesome-stuff", &find_head(), false));
3375+
t!(repo.checkout_tree(&t!(br.get().peel(git2::ObjectType::Tree)), None));
3376+
t!(repo.set_head("refs/heads/awesome-stuff"));
3377+
git_project.change_file("src/lib.rs", &main("awesome-three"));
3378+
git::add(&repo);
3379+
git::commit(&repo);
3380+
p.change_file("Cargo.toml", &manifest(", branch = \"awesome-stuff\""));
3381+
verify("awesome-three");
3382+
amend_commit("awesome-four");
3383+
verify("awesome-four");
3384+
}

0 commit comments

Comments
 (0)