Skip to content

Commit 31c96be

Browse files
authored
git: do not validate submodules of fresh checkouts (#14605)
Fixes #14603 ### What does this PR try to resolve? As is, we unconditionally validate freshness of the submodules of a checkout, even though we could assume that a fresh checkout has to have up-to-date submodules as well. ### How should we test and review this PR? N/A ### Additional information N/A
2 parents 2bdeb60 + e82ad5f commit 31c96be

File tree

2 files changed

+57
-18
lines changed

2 files changed

+57
-18
lines changed

src/cargo/sources/git/utils.rs

Lines changed: 35 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -181,9 +181,14 @@ impl GitDatabase {
181181
.filter(|co| co.is_fresh())
182182
{
183183
Some(co) => co,
184-
None => GitCheckout::clone_into(dest, self, rev, gctx)?,
184+
None => {
185+
let (checkout, guard) = GitCheckout::clone_into(dest, self, rev, gctx)?;
186+
checkout.update_submodules(gctx)?;
187+
guard.mark_ok()?;
188+
checkout
189+
}
185190
};
186-
checkout.update_submodules(gctx)?;
191+
187192
Ok(checkout)
188193
}
189194

@@ -280,7 +285,7 @@ impl<'a> GitCheckout<'a> {
280285
database: &'a GitDatabase,
281286
revision: git2::Oid,
282287
gctx: &GlobalContext,
283-
) -> CargoResult<GitCheckout<'a>> {
288+
) -> CargoResult<(GitCheckout<'a>, CheckoutGuard)> {
284289
let dirname = into.parent().unwrap();
285290
paths::create_dir_all(&dirname)?;
286291
if into.exists() {
@@ -329,8 +334,8 @@ impl<'a> GitCheckout<'a> {
329334
let repo = repo.unwrap();
330335

331336
let checkout = GitCheckout::new(database, revision, repo);
332-
checkout.reset(gctx)?;
333-
Ok(checkout)
337+
let guard = checkout.reset(gctx)?;
338+
Ok((checkout, guard))
334339
}
335340

336341
/// Checks if the `HEAD` of this checkout points to the expected revision.
@@ -355,12 +360,12 @@ impl<'a> GitCheckout<'a> {
355360
/// To enable this we have a dummy file in our checkout, [`.cargo-ok`],
356361
/// which if present means that the repo has been successfully reset and is
357362
/// ready to go. Hence if we start to do a reset, we make sure this file
358-
/// *doesn't* exist, and then once we're done we create the file.
363+
/// *doesn't* exist. The caller of [`reset`] has an option to perform additional operations
364+
/// (e.g. submodule update) before marking the check-out as ready.
359365
///
360366
/// [`.cargo-ok`]: CHECKOUT_READY_LOCK
361-
fn reset(&self, gctx: &GlobalContext) -> CargoResult<()> {
362-
let ok_file = self.path.join(CHECKOUT_READY_LOCK);
363-
let _ = paths::remove_file(&ok_file);
367+
fn reset(&self, gctx: &GlobalContext) -> CargoResult<CheckoutGuard> {
368+
let guard = CheckoutGuard::guard(&self.path);
364369
info!("reset {} to {}", self.repo.path().display(), self.revision);
365370

366371
// Ensure libgit2 won't mess with newlines when we vendor.
@@ -370,8 +375,8 @@ impl<'a> GitCheckout<'a> {
370375

371376
let object = self.repo.find_object(self.revision, None)?;
372377
reset(&self.repo, &object, gctx)?;
373-
paths::create(ok_file)?;
374-
Ok(())
378+
379+
Ok(guard)
375380
}
376381

377382
/// Like `git submodule update --recursive` but for this git checkout.
@@ -479,6 +484,25 @@ impl<'a> GitCheckout<'a> {
479484
}
480485
}
481486

487+
/// See [`GitCheckout::reset`] for rationale on this type.
488+
#[must_use]
489+
struct CheckoutGuard {
490+
ok_file: PathBuf,
491+
}
492+
493+
impl CheckoutGuard {
494+
fn guard(path: &Path) -> Self {
495+
let ok_file = path.join(CHECKOUT_READY_LOCK);
496+
let _ = paths::remove_file(&ok_file);
497+
Self { ok_file }
498+
}
499+
500+
fn mark_ok(self) -> CargoResult<()> {
501+
let _ = paths::create(self.ok_file)?;
502+
Ok(())
503+
}
504+
}
505+
482506
/// Constructs an absolute URL for a child submodule URL with its parent base URL.
483507
///
484508
/// Git only assumes a submodule URL is a relative path if it starts with `./`

tests/testsuite/git.rs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use std::sync::atomic::{AtomicBool, Ordering};
99
use std::sync::Arc;
1010
use std::thread;
1111

12-
use cargo_test_support::git::cargo_uses_gitoxide;
12+
use cargo_test_support::git::{add_submodule, cargo_uses_gitoxide};
1313
use cargo_test_support::paths;
1414
use cargo_test_support::prelude::IntoData;
1515
use cargo_test_support::prelude::*;
@@ -3847,11 +3847,20 @@ fn corrupted_checkout_with_cli() {
38473847
}
38483848

38493849
fn _corrupted_checkout(with_cli: bool) {
3850-
let git_project = git::new("dep1", |project| {
3850+
let (git_project, repository) = git::new_repo("dep1", |project| {
38513851
project
38523852
.file("Cargo.toml", &basic_manifest("dep1", "0.5.0"))
38533853
.file("src/lib.rs", "")
38543854
});
3855+
3856+
let project2 = git::new("dep2", |project| {
3857+
project.no_manifest().file("README.md", "")
3858+
});
3859+
let url = project2.root().to_url().to_string();
3860+
add_submodule(&repository, &url, Path::new("dep2"));
3861+
git::commit(&repository);
3862+
drop(repository);
3863+
38553864
let p = project()
38563865
.file(
38573866
"Cargo.toml",
@@ -3873,25 +3882,31 @@ fn _corrupted_checkout(with_cli: bool) {
38733882

38743883
p.cargo("fetch").run();
38753884

3876-
let mut paths = t!(glob::glob(
3885+
let mut dep1_co_paths = t!(glob::glob(
38773886
paths::home()
38783887
.join(".cargo/git/checkouts/dep1-*/*")
38793888
.to_str()
38803889
.unwrap()
38813890
));
3882-
let path = paths.next().unwrap().unwrap();
3883-
let ok = path.join(".cargo-ok");
3891+
let dep1_co_path = dep1_co_paths.next().unwrap().unwrap();
3892+
let dep1_ok = dep1_co_path.join(".cargo-ok");
3893+
let dep1_manifest = dep1_co_path.join("Cargo.toml");
3894+
let dep2_readme = dep1_co_path.join("dep2/README.md");
38843895

38853896
// Deleting this file simulates an interrupted checkout.
3886-
t!(fs::remove_file(&ok));
3897+
t!(fs::remove_file(&dep1_ok));
3898+
t!(fs::remove_file(&dep1_manifest));
3899+
t!(fs::remove_file(&dep2_readme));
38873900

38883901
// This should refresh the checkout.
38893902
let mut e = p.cargo("fetch");
38903903
if with_cli {
38913904
e.env("CARGO_NET_GIT_FETCH_WITH_CLI", "true");
38923905
}
38933906
e.run();
3894-
assert!(ok.exists());
3907+
assert!(dep1_ok.exists());
3908+
assert!(dep1_manifest.exists());
3909+
assert!(dep2_readme.exists());
38953910
}
38963911

38973912
#[cargo_test]

0 commit comments

Comments
 (0)