test(git): Clean up shallow fetch tests (#15002)

### What does this PR try to resolve?

When looking to add shallow fetch for the git CLI, I found it hard to
tell what we test. This tries to clean up the tests and make it easier
to add new ones. My hope is to later generalize the test bodies so we
can more easily test any git backend in any combination of shallow or
deep fetches.

This is part of #13285.

### How should we test and review this PR?

### Additional information
This commit is contained in:
Weihang Lo 2025-01-02 04:15:36 +00:00 committed by GitHub
commit fbca21b87b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 208 additions and 216 deletions

View File

@ -1524,6 +1524,12 @@ impl ArgLineCommandExt for &mut ProcessBuilder {
}
}
impl ArgLineCommandExt for &mut Execs {
fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
self.arg(s)
}
}
impl ArgLineCommandExt for snapbox::cmd::Command {
fn arg<S: AsRef<std::ffi::OsStr>>(self, s: S) -> Self {
self.arg(s)

View File

@ -4,17 +4,45 @@ use cargo_test_support::{basic_manifest, git, paths, project};
use crate::git_gc::find_index;
enum Backend {
Git2,
Gitoxide,
}
impl Backend {
fn to_arg(&self) -> &'static str {
match self {
Backend::Git2 => "",
Backend::Gitoxide => "-Zgitoxide=fetch",
}
}
}
enum RepoMode {
Shallow,
Complete,
}
#[cargo_test]
fn gitoxide_clones_shallow_two_revs_same_deps() {
perform_two_revs_same_deps(true)
impl RepoMode {
fn to_deps_arg(&self) -> &'static str {
match self {
RepoMode::Complete => "",
RepoMode::Shallow => "-Zgit=shallow-deps",
}
}
}
fn perform_two_revs_same_deps(shallow: bool) {
#[cargo_test]
fn gitoxide_fetch_shallow_dep_two_revs() {
fetch_dep_two_revs(Backend::Gitoxide, RepoMode::Shallow)
}
#[cargo_test]
fn git2_fetch_complete_dep_two_revs() {
fetch_dep_two_revs(Backend::Git2, RepoMode::Complete)
}
fn fetch_dep_two_revs(backend: Backend, mode: RepoMode) {
let bar = git::new("meta-dep", |project| {
project
.file("Cargo.toml", &basic_manifest("bar", "0.0.0"))
@ -92,28 +120,153 @@ fn perform_two_revs_same_deps(shallow: bool) {
)
.build();
let args = if shallow {
"build -v -Zgitoxide=fetch -Zgit=shallow-deps"
} else {
"build -v"
};
foo.cargo(args)
.masquerade_as_nightly_cargo(&[
"unstable features must be available for -Z gitoxide and -Z git",
])
foo.cargo("check -v")
.arg_line(backend.to_arg())
.arg_line(mode.to_deps_arg())
.env("__CARGO_USE_GITOXIDE_INSTEAD_OF_GIT2", "0") // respect `backend`
.masquerade_as_nightly_cargo(&["gitoxide=fetch", "git=shallow-deps"])
.run();
assert!(foo.bin("foo").is_file());
foo.process(&foo.bin("foo")).run();
}
#[cargo_test]
fn two_revs_same_deps() {
perform_two_revs_same_deps(false)
fn gitoxide_fetch_shallow_dep_branch_and_rev() -> anyhow::Result<()> {
let (bar, bar_repo) = git::new_repo("bar", |p| {
p.file("Cargo.toml", &basic_manifest("bar", "1.0.0"))
.file("src/lib.rs", "")
});
// this commit would not be available in a shallow fetch.
let first_commit_pre_change = bar_repo.head().unwrap().target().unwrap();
bar.change_file("src/lib.rs", "// change");
git::add(&bar_repo);
git::commit(&bar_repo);
let p = project()
.file(
"Cargo.toml",
&format!(
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar-renamed = {{ package = "bar", git = "{}", rev = "{}" }}
bar = {{ git = "{}", branch = "master" }}
"#,
bar.url(),
first_commit_pre_change,
bar.url(),
),
)
.file("src/lib.rs", "")
.build();
p.cargo("check")
.arg("-Zgitoxide=fetch")
.arg("-Zgit=shallow-deps")
.masquerade_as_nightly_cargo(&["gitoxide=fetch", "git=shallow-deps"])
.run();
let db_paths = glob::glob(paths::home().join(".cargo/git/db/bar-*").to_str().unwrap())?
.map(Result::unwrap)
.collect::<Vec<_>>();
assert_eq!(
db_paths.len(),
1,
"only one db checkout source is used per dependency"
);
let db_clone = gix::open_opts(&db_paths[0], gix::open::Options::isolated())?;
assert!(
db_clone.is_shallow(),
"the repo is shallow while having all data it needs"
);
Ok(())
}
#[cargo_test]
fn gitoxide_clones_registry_with_shallow_protocol_and_follow_up_with_git2_fetch(
) -> anyhow::Result<()> {
fn gitoxide_fetch_shallow_dep_branch_to_rev() -> anyhow::Result<()> {
// db exists from previous build, then dependency changes to refer to revision that isn't
// available in the shallow fetch.
let (bar, bar_repo) = git::new_repo("bar", |p| {
p.file("Cargo.toml", &basic_manifest("bar", "1.0.0"))
.file("src/lib.rs", "")
});
// this commit would not be available in a shallow fetch.
let first_commit_pre_change = bar_repo.head().unwrap().target().unwrap();
bar.change_file("src/lib.rs", "// change");
git::add(&bar_repo);
git::commit(&bar_repo);
let p = project()
.file(
"Cargo.toml",
&format!(
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = {{ git = "{}", branch = "master" }}
"#,
bar.url(),
),
)
.file("src/lib.rs", "")
.build();
p.cargo("check")
.arg("-Zgitoxide=fetch")
.arg("-Zgit=shallow-deps")
.masquerade_as_nightly_cargo(&["gitoxide=fetch", "git=shallow-deps"])
.run();
let db_clone = gix::open_opts(
find_bar_db(RepoMode::Shallow),
gix::open::Options::isolated(),
)?;
assert!(db_clone.is_shallow());
let p = project()
.file(
"Cargo.toml",
&format!(
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = {{ git = "{}", rev = "{}" }}
"#,
bar.url(),
first_commit_pre_change
),
)
.file("src/lib.rs", "")
.build();
p.cargo("check")
.arg("-Zgitoxide=fetch")
.arg("-Zgit=shallow-deps")
.masquerade_as_nightly_cargo(&["gitoxide=fetch", "git=shallow-deps"])
.run();
assert!(
db_clone.is_shallow(),
"we maintain shallowness and never unshallow"
);
Ok(())
}
#[cargo_test]
fn gitoxide_fetch_shallow_index_then_git2_fetch_complete() -> anyhow::Result<()> {
Package::new("bar", "1.0.0").publish();
let p = project()
.file(
@ -132,9 +285,7 @@ fn gitoxide_clones_registry_with_shallow_protocol_and_follow_up_with_git2_fetch(
p.cargo("fetch")
.arg("-Zgitoxide=fetch")
.arg("-Zgit=shallow-index")
.masquerade_as_nightly_cargo(&[
"unstable features must be available for -Z gitoxide and -Z git",
])
.masquerade_as_nightly_cargo(&["gitoxide=fetch", "git=shallow-index"])
.run();
let shallow_repo = gix::open_opts(find_index(), gix::open::Options::isolated())?;
@ -145,7 +296,7 @@ fn gitoxide_clones_registry_with_shallow_protocol_and_follow_up_with_git2_fetch(
.all()?
.count(),
1,
"shallow clones always start at depth of 1 to minimize download size"
"shallow fetch always start at depth of 1 to minimize download size"
);
assert!(shallow_repo.is_shallow());
@ -164,15 +315,14 @@ fn gitoxide_clones_registry_with_shallow_protocol_and_follow_up_with_git2_fetch(
.all()?
.count(),
3,
"an entirely new repo was cloned which is never shallow"
"an entirely new repo was fetched which is never shallow"
);
assert!(!repo.is_shallow());
Ok(())
}
#[cargo_test]
fn gitoxide_clones_git_dependency_with_shallow_protocol_and_git2_is_used_for_followup_fetches(
) -> anyhow::Result<()> {
fn gitoxide_fetch_shallow_dep_then_git2_fetch_complete() -> anyhow::Result<()> {
// Example where an old lockfile with an explicit branch="master" in Cargo.toml.
Package::new("bar", "1.0.0").publish();
let (bar, bar_repo) = git::new_repo("bar", |p| {
@ -215,9 +365,7 @@ fn gitoxide_clones_git_dependency_with_shallow_protocol_and_git2_is_used_for_fol
p.cargo("update")
.arg("-Zgitoxide=fetch")
.arg("-Zgit=shallow-deps")
.masquerade_as_nightly_cargo(&[
"unstable features must be available for -Z gitoxide and -Z git",
])
.masquerade_as_nightly_cargo(&["gitoxide=fetch", "git=shallow-deps"])
.run();
let db_clone = gix::open_opts(
@ -232,7 +380,7 @@ fn gitoxide_clones_git_dependency_with_shallow_protocol_and_git2_is_used_for_fol
.all()?
.count(),
1,
"db clones are shallow and have a shortened history"
"db fetch are shallow and have a shortened history"
);
let dep_checkout = gix::open_opts(
@ -243,7 +391,7 @@ fn gitoxide_clones_git_dependency_with_shallow_protocol_and_git2_is_used_for_fol
assert_eq!(
dep_checkout.head_id()?.ancestors().all()?.count(),
1,
"db checkouts are hard-linked clones with the shallow file copied separately."
"db checkouts are hard-linked fetches with the shallow file copied separately."
);
bar.change_file("src/lib.rs", "// another change");
@ -311,7 +459,7 @@ fn gitoxide_clones_git_dependency_with_shallow_protocol_and_git2_is_used_for_fol
}
#[cargo_test]
fn gitoxide_shallow_clone_followed_by_non_shallow_update() -> anyhow::Result<()> {
fn gitoxide_fetch_shallow_dep_then_gitoxide_fetch_complete() -> anyhow::Result<()> {
Package::new("bar", "1.0.0").publish();
let (bar, bar_repo) = git::new_repo("bar", |p| {
p.file("Cargo.toml", &basic_manifest("bar", "1.0.0"))
@ -353,9 +501,7 @@ fn gitoxide_shallow_clone_followed_by_non_shallow_update() -> anyhow::Result<()>
p.cargo("update")
.arg("-Zgitoxide=fetch")
.arg("-Zgit=shallow-deps")
.masquerade_as_nightly_cargo(&[
"unstable features must be available for -Z gitoxide and -Z git",
])
.masquerade_as_nightly_cargo(&["gitoxide=fetch", "git=shallow-deps"])
.run();
let shallow_db_clone = gix::open_opts(
@ -370,7 +516,7 @@ fn gitoxide_shallow_clone_followed_by_non_shallow_update() -> anyhow::Result<()>
.all()?
.count(),
1,
"db clones are shallow and have a shortened history"
"db fetches are shallow and have a shortened history"
);
let dep_checkout = gix::open_opts(
@ -381,7 +527,7 @@ fn gitoxide_shallow_clone_followed_by_non_shallow_update() -> anyhow::Result<()>
assert_eq!(
dep_checkout.head_id()?.ancestors().all()?.count(),
1,
"db checkouts are hard-linked clones with the shallow file copied separately."
"db checkouts are hard-linked fetches with the shallow file copied separately."
);
bar.change_file("src/lib.rs", "// another change");
@ -399,7 +545,7 @@ fn gitoxide_shallow_clone_followed_by_non_shallow_update() -> anyhow::Result<()>
p.cargo("update")
.arg("-Zgitoxide=fetch") // shallow-deps is omitted intentionally
.masquerade_as_nightly_cargo(&["unstable features must be available for -Z gitoxide"])
.masquerade_as_nightly_cargo(&["gitoxide=fetch"])
.run();
let db_clone = gix::open_opts(
@ -452,8 +598,7 @@ fn gitoxide_shallow_clone_followed_by_non_shallow_update() -> anyhow::Result<()>
}
#[cargo_test]
fn gitoxide_clones_registry_with_shallow_protocol_and_follow_up_fetch_maintains_shallowness(
) -> anyhow::Result<()> {
fn gitoxide_fetch_shallow_index_then_preserve_shallow() -> anyhow::Result<()> {
Package::new("bar", "1.0.0").publish();
let p = project()
.file(
@ -472,9 +617,7 @@ fn gitoxide_clones_registry_with_shallow_protocol_and_follow_up_fetch_maintains_
p.cargo("fetch")
.arg("-Zgitoxide=fetch")
.arg("-Zgit=shallow-index")
.masquerade_as_nightly_cargo(&[
"unstable features must be available for -Z gitoxide and -Z git",
])
.masquerade_as_nightly_cargo(&["gitoxide=fetch", "git=shallow-index"])
.run();
let repo = gix::open_opts(find_index(), gix::open::Options::isolated())?;
@ -484,7 +627,7 @@ fn gitoxide_clones_registry_with_shallow_protocol_and_follow_up_fetch_maintains_
.all()?
.count(),
1,
"shallow clones always start at depth of 1 to minimize download size"
"shallow fetches always start at depth of 1 to minimize download size"
);
assert!(repo.is_shallow());
@ -492,9 +635,7 @@ fn gitoxide_clones_registry_with_shallow_protocol_and_follow_up_fetch_maintains_
p.cargo("update")
.arg("-Zgitoxide=fetch")
.arg("-Zgit=shallow-index") // NOTE: the flag needs to be consistent or else a different index is created
.masquerade_as_nightly_cargo(&[
"unstable features must be available for -Z gitoxide and -Z git",
])
.masquerade_as_nightly_cargo(&["gitoxide=fetch", "git=shallow-index"])
.run();
assert_eq!(
@ -512,9 +653,7 @@ fn gitoxide_clones_registry_with_shallow_protocol_and_follow_up_fetch_maintains_
p.cargo("update")
.arg("-Zgitoxide=fetch")
.arg("-Zgit=shallow-index")
.masquerade_as_nightly_cargo(&[
"unstable features must be available for -Z gitoxide and -Z git",
])
.masquerade_as_nightly_cargo(&["gitoxide=fetch", "git=shallow-index"])
.run();
assert_eq!(
@ -530,10 +669,9 @@ fn gitoxide_clones_registry_with_shallow_protocol_and_follow_up_fetch_maintains_
Ok(())
}
/// If there is shallow *and* non-shallow clones, non-shallow will naturally be returned due to sort order.
/// If there is shallow *and* non-shallow fetches, non-shallow will naturally be returned due to sort order.
#[cargo_test]
fn gitoxide_clones_registry_without_shallow_protocol_and_follow_up_fetch_uses_shallowness(
) -> anyhow::Result<()> {
fn gitoxide_fetch_complete_index_then_shallow() -> anyhow::Result<()> {
Package::new("bar", "1.0.0").publish();
let p = project()
.file(
@ -551,7 +689,7 @@ fn gitoxide_clones_registry_without_shallow_protocol_and_follow_up_fetch_uses_sh
.build();
p.cargo("fetch")
.arg("-Zgitoxide=fetch")
.masquerade_as_nightly_cargo(&["unstable features must be available for -Z gitoxide"])
.masquerade_as_nightly_cargo(&["gitoxide=fetch"])
.run();
let repo = gix::open_opts(find_index(), gix::open::Options::isolated())?;
@ -569,9 +707,7 @@ fn gitoxide_clones_registry_without_shallow_protocol_and_follow_up_fetch_uses_sh
p.cargo("update")
.arg("-Zgitoxide=fetch")
.arg("-Zgit=shallow-index")
.masquerade_as_nightly_cargo(&[
"unstable features must be available for -Z gitoxide and -Z git",
])
.masquerade_as_nightly_cargo(&["gitoxide=fetch", "git=shallow-index"])
.run();
let shallow_repo = gix::open_opts(
@ -584,7 +720,7 @@ fn gitoxide_clones_registry_without_shallow_protocol_and_follow_up_fetch_uses_sh
.all()?
.count(),
1,
"the follow up clones an entirely new index which is now shallow and which is in its own location"
"the follow up fetch an entirely new index which is now shallow and which is in its own location"
);
assert!(shallow_repo.is_shallow());
@ -593,9 +729,7 @@ fn gitoxide_clones_registry_without_shallow_protocol_and_follow_up_fetch_uses_sh
p.cargo("update")
.arg("-Zgitoxide=fetch")
.arg("-Zgit=shallow-index")
.masquerade_as_nightly_cargo(&[
"unstable features must be available for -Z gitoxide and -Z git",
])
.masquerade_as_nightly_cargo(&["gitoxide=fetch", "git=shallow-index"])
.run();
assert_eq!(
@ -611,7 +745,7 @@ fn gitoxide_clones_registry_without_shallow_protocol_and_follow_up_fetch_uses_sh
p.cargo("update")
.arg("-Zgitoxide=fetch")
.masquerade_as_nightly_cargo(&["unstable features must be available for -Z gitoxide"])
.masquerade_as_nightly_cargo(&["gitoxide=fetch"])
.run();
assert_eq!(
@ -627,151 +761,7 @@ fn gitoxide_clones_registry_without_shallow_protocol_and_follow_up_fetch_uses_sh
}
#[cargo_test]
fn gitoxide_git_dependencies_switch_from_branch_to_rev() -> anyhow::Result<()> {
// db exists from previous build, then dependency changes to refer to revision that isn't
// available in the shallow clone.
let (bar, bar_repo) = git::new_repo("bar", |p| {
p.file("Cargo.toml", &basic_manifest("bar", "1.0.0"))
.file("src/lib.rs", "")
});
// this commit would not be available in a shallow clone.
let first_commit_pre_change = bar_repo.head().unwrap().target().unwrap();
bar.change_file("src/lib.rs", "// change");
git::add(&bar_repo);
git::commit(&bar_repo);
let p = project()
.file(
"Cargo.toml",
&format!(
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = {{ git = "{}", branch = "master" }}
"#,
bar.url(),
),
)
.file("src/lib.rs", "")
.build();
p.cargo("check")
.arg("-Zgitoxide=fetch")
.arg("-Zgit=shallow-deps")
.masquerade_as_nightly_cargo(&[
"unstable features must be available for -Z gitoxide and -Z git",
])
.run();
let db_clone = gix::open_opts(
find_bar_db(RepoMode::Shallow),
gix::open::Options::isolated(),
)?;
assert!(db_clone.is_shallow());
let p = project()
.file(
"Cargo.toml",
&format!(
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar = {{ git = "{}", rev = "{}" }}
"#,
bar.url(),
first_commit_pre_change
),
)
.file("src/lib.rs", "")
.build();
p.cargo("check")
.arg("-Zgitoxide=fetch")
.arg("-Zgit=shallow-deps")
.masquerade_as_nightly_cargo(&[
"unstable features must be available for -Z gitoxide and -Z git",
])
.run();
assert!(
db_clone.is_shallow(),
"we maintain shallowness and never unshallow"
);
Ok(())
}
#[cargo_test]
fn shallow_deps_work_with_revisions_and_branches_mixed_on_same_dependency() -> anyhow::Result<()> {
let (bar, bar_repo) = git::new_repo("bar", |p| {
p.file("Cargo.toml", &basic_manifest("bar", "1.0.0"))
.file("src/lib.rs", "")
});
// this commit would not be available in a shallow clone.
let first_commit_pre_change = bar_repo.head().unwrap().target().unwrap();
bar.change_file("src/lib.rs", "// change");
git::add(&bar_repo);
git::commit(&bar_repo);
let p = project()
.file(
"Cargo.toml",
&format!(
r#"
[package]
name = "foo"
version = "0.1.0"
[dependencies]
bar-renamed = {{ package = "bar", git = "{}", rev = "{}" }}
bar = {{ git = "{}", branch = "master" }}
"#,
bar.url(),
first_commit_pre_change,
bar.url(),
),
)
.file("src/lib.rs", "")
.build();
p.cargo("check")
.arg("-Zgitoxide=fetch")
.arg("-Zgit=shallow-deps")
.masquerade_as_nightly_cargo(&[
"unstable features must be available for -Z gitoxide and -Z git",
])
.run();
let db_paths = glob::glob(paths::home().join(".cargo/git/db/bar-*").to_str().unwrap())?
.map(Result::unwrap)
.collect::<Vec<_>>();
assert_eq!(
db_paths.len(),
1,
"only one db checkout source is used per dependency"
);
let db_clone = gix::open_opts(&db_paths[0], gix::open::Options::isolated())?;
assert!(
db_clone.is_shallow(),
"the repo is shallow while having all data it needs"
);
Ok(())
}
#[cargo_test]
fn gitoxide_clones_registry_with_shallow_protocol_and_aborts_and_updates_again(
) -> anyhow::Result<()> {
fn gitoxide_fetch_shallow_index_then_abort_and_update() -> anyhow::Result<()> {
Package::new("bar", "1.0.0").publish();
let p = project()
.file(
@ -790,9 +780,7 @@ fn gitoxide_clones_registry_with_shallow_protocol_and_aborts_and_updates_again(
p.cargo("fetch")
.arg("-Zgitoxide=fetch")
.arg("-Zgit=shallow-index")
.masquerade_as_nightly_cargo(&[
"unstable features must be available for -Z gitoxide and -Z git",
])
.masquerade_as_nightly_cargo(&["gitoxide=fetch", "git=shallow-index"])
.run();
let repo = gix::open_opts(find_index(), gix::open::Options::isolated())?;
@ -802,11 +790,11 @@ fn gitoxide_clones_registry_with_shallow_protocol_and_aborts_and_updates_again(
.all()?
.count(),
1,
"shallow clones always start at depth of 1 to minimize download size"
"shallow fetches always start at depth of 1 to minimize download size"
);
assert!(repo.is_shallow());
let shallow_lock = repo.shallow_file().with_extension("lock");
// adding a lock file and deleting the original simulates a left-over clone that was aborted, leaving a lock file
// adding a lock file and deleting the original simulates a left-over fetch that was aborted, leaving a lock file
// in place without ever having moved it to the right location.
std::fs::write(&shallow_lock, &[])?;
std::fs::remove_file(repo.shallow_file())?;
@ -815,9 +803,7 @@ fn gitoxide_clones_registry_with_shallow_protocol_and_aborts_and_updates_again(
p.cargo("update")
.arg("-Zgitoxide=fetch")
.arg("-Zgit=shallow-index")
.masquerade_as_nightly_cargo(&[
"unstable features must be available for -Z gitoxide and -Z git",
])
.masquerade_as_nightly_cargo(&["gitoxide=fetch", "git=shallow-index"])
.run();
assert!(!shallow_lock.is_file(), "the repository was re-initialized");
@ -828,7 +814,7 @@ fn gitoxide_clones_registry_with_shallow_protocol_and_aborts_and_updates_again(
.all()?
.count(),
1,
"it's a fresh shallow clone - otherwise it would have 2 commits if the previous shallow clone would still be present"
"it's a fresh shallow fetch - otherwise it would have 2 commits if the previous shallow fetch would still be present"
);
Ok(())