Skip to content

Commit 9a8816c

Browse files
committed
Cache submodules between different checkouts of the same git dep
This base64-encodes the URLs to avoid errors like the following: ``` error: failed to get `dep1` as a dependency of package `foo v0.5.0 (D:/a/cargo/cargo/target/tmp/cit/t1035/foo)` Caused by: failed to load source for dependency `dep1` Caused by: Unable to update file:///D:/a/cargo/cargo/target/tmp/cit/t1035/dep1 Caused by: failed to update submodule `src` Caused by: failed to make directory 'D:/a/cargo/cargo/target/tmp/cit/t1035/home/.cargo/git/checkouts/submodules/file:': The filename, directory name, or volume label syntax is incorrect. ; class=Os (2) ', tests\testsuite\git.rs:2515:10 ``` It uses bare checkouts instead of symbolic links to avoid permission errors on Windows.
1 parent 28fffd6 commit 9a8816c

File tree

3 files changed

+46
-15
lines changed

3 files changed

+46
-15
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ path = "src/cargo/lib.rs"
1717

1818
[dependencies]
1919
atty = "0.2"
20+
base64 = "0.13"
2021
bytesize = "1.0"
2122
cargo-platform = { path = "crates/cargo-platform", version = "0.1.2" }
2223
cargo-util = { path = "crates/cargo-util", version = "0.1.2" }

src/cargo/sources/git/utils.rs

+43-14
Original file line numberDiff line numberDiff line change
@@ -344,18 +344,32 @@ impl<'a> GitCheckout<'a> {
344344
}
345345

346346
fn update_submodules(&self, cargo_config: &Config) -> CargoResult<()> {
347-
return update_submodules(&self.repo, cargo_config);
348-
349-
fn update_submodules(repo: &git2::Repository, cargo_config: &Config) -> CargoResult<()> {
347+
// `location` looks like `target/git/checkouts/crate-name-checksum/shorthash`
348+
// `submodule_root` looks like `target/git/checkouts/submodules`
349+
let checkout_root = self.location.parent().unwrap().parent().unwrap();
350+
// Share the same submodules between all checkouts. Without a shared path,
351+
// cargo would reclone the submodule for each commit that's checked out,
352+
// even if the submodule itself hasn't changed.
353+
let submodule_root = checkout_root.join("submodules");
354+
355+
return update_submodules(&self.repo, cargo_config, &submodule_root);
356+
357+
fn update_submodules(
358+
repo: &git2::Repository,
359+
cargo_config: &Config,
360+
submodule_root: &Path,
361+
) -> CargoResult<()> {
350362
debug!("update submodules for: {:?}", repo.workdir().unwrap());
351363

352364
for mut child in repo.submodules()? {
353-
update_submodule(repo, &mut child, cargo_config).with_context(|| {
354-
format!(
355-
"failed to update submodule `{}`",
356-
child.name().unwrap_or("")
357-
)
358-
})?;
365+
update_submodule(repo, &mut child, cargo_config, submodule_root).with_context(
366+
|| {
367+
format!(
368+
"failed to update submodule `{}`",
369+
child.name().unwrap_or("")
370+
)
371+
},
372+
)?;
359373
}
360374
Ok(())
361375
}
@@ -364,6 +378,7 @@ impl<'a> GitCheckout<'a> {
364378
parent: &git2::Repository,
365379
child: &mut git2::Submodule<'_>,
366380
cargo_config: &Config,
381+
submodule_root: &Path,
367382
) -> CargoResult<()> {
368383
child.init(false)?;
369384
let url = child.url().ok_or_else(|| {
@@ -388,14 +403,28 @@ impl<'a> GitCheckout<'a> {
388403
let mut repo = match head_and_repo {
389404
Ok((head, repo)) => {
390405
if child.head_id() == head {
391-
return update_submodules(&repo, cargo_config);
406+
debug!(
407+
"saw up-to-date oid={:?} for submodule {}; skipping update",
408+
head, url
409+
);
410+
return update_submodules(&repo, cargo_config, submodule_root);
392411
}
393412
repo
394413
}
395414
Err(..) => {
396-
let path = parent.workdir().unwrap().join(child.path());
397-
let _ = paths::remove_dir_all(&path);
398-
init(&path, false)?
415+
// NOTE: most URLs are invalid file paths on Windows.
416+
// Base64-encode them to avoid FS errors.
417+
let config = base64::Config::new(base64::CharacterSet::UrlSafe, true);
418+
let encoded = base64::encode_config(url, config);
419+
let shared_submodule_path = submodule_root.join(encoded);
420+
std::fs::create_dir_all(&shared_submodule_path)?;
421+
let submodule = init(&shared_submodule_path, true)?;
422+
423+
let checkout_path = parent.workdir().unwrap().join(child.path());
424+
let _ = paths::remove_dir_all(&checkout_path);
425+
std::fs::create_dir_all(&checkout_path)?;
426+
submodule.set_workdir(&checkout_path, false)?;
427+
submodule
399428
}
400429
};
401430
// Fetch data from origin and reset to the head commit
@@ -413,7 +442,7 @@ impl<'a> GitCheckout<'a> {
413442

414443
let obj = repo.find_object(head, None)?;
415444
reset(&repo, &obj, cargo_config)?;
416-
update_submodules(&repo, cargo_config)
445+
update_submodules(&repo, cargo_config, submodule_root)
417446
}
418447
}
419448
}

tests/testsuite/git.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -1287,7 +1287,8 @@ fn dep_with_changed_submodule() {
12871287
println!("last run");
12881288
p.cargo("run")
12891289
.with_stderr(
1290-
"[COMPILING] dep1 v0.5.0 ([..])\n\
1290+
"[UPDATING] git submodule `file://[..]/dep3`\n\
1291+
[COMPILING] dep1 v0.5.0 ([..])\n\
12911292
[COMPILING] foo v0.5.0 ([..])\n\
12921293
[FINISHED] dev [unoptimized + debuginfo] target(s) in \
12931294
[..]\n\

0 commit comments

Comments
 (0)