Skip to content

Commit 8d5576b

Browse files
committed
Fix fetching git repos after a force push.
1 parent bd6a512 commit 8d5576b

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

0 commit comments

Comments
 (0)