Skip to content

Commit 26031b9

Browse files
committed
Respect submodule update=none strategy in .gitmodules
Git lets users define the default update/checkout strategy for a submodule by setting the `submodule.<name>.update` key in `.gitmodules` file. If the update strategy is `none`, the submodule will be skipped during update. It will not be fetched and checked out: 1. *foo* is a big git repo ``` /tmp $ git init foo Initialized empty Git repository in /tmp/foo/.git/ /tmp $ dd if=/dev/zero of=foo/big bs=1000M count=1 1+0 records in 1+0 records out 1048576000 bytes (1.0 GB, 1000 MiB) copied, 0.482087 s, 2.2 GB/s /tmp $ git -C foo add big /tmp $ git -C foo commit -m 'I am big' [main (root-commit) 84fb533] I am big 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 big ``` 2. *bar* is a repo with a big submodule with `update=none` ``` /tmp $ git init bar Initialized empty Git repository in /tmp/bar/.git/ /tmp $ git -C bar submodule add file:///tmp/foo foo Cloning into '/tmp/bar/foo'... remote: Enumerating objects: 3, done. remote: Counting objects: 100% (3/3), done. remote: Total 3 (delta 0), reused 1 (delta 0), pack-reused 0 Receiving objects: 100% (3/3), 995.50 KiB | 338.00 KiB/s, done. /tmp $ git -C bar config --file .gitmodules submodule.foo.update none /tmp $ cat bar/.gitmodules [submodule "foo"] path = foo url = file:///tmp/foo update = none /tmp $ git -C bar commit --all -m 'I have a big submodule with update=none' [main (root-commit) 6c355ea] I have a big submodule not updated by default 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 foo ``` 3. *baz* is a clone of *bar*, notice *foo* submodule gets skipped ``` /tmp $ git clone --recurse-submodules file:///tmp/bar baz Cloning into 'baz'... remote: Enumerating objects: 3, done. remote: Counting objects: 100% (3/3), done. remote: Compressing objects: 100% (3/3), done. remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 Receiving objects: 100% (3/3), done. Submodule 'foo' (file:///tmp/foo) registered for path 'foo' Skipping submodule 'foo' /tmp $ git -C baz submodule update --init Skipping submodule 'foo' /tmp $ ``` Cargo, on the other hand, ignores the submodule update strategy set in `.gitmodules` properties when updating dependencies. Such behavior can be considered against the wish of the crate publisher. 4. *bar* is now a lib with a big submodule with update disabled ``` /tmp $ cargo init --lib bar Created library package /tmp $ git -C bar add . /tmp $ git -C bar commit -m 'I am a lib with a big submodule but update=none' [main eb07cf7] I am a lib with a big submodule but update=none 3 files changed, 18 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.toml create mode 100644 src/lib.rs /tmp $ ``` 5. *qux* depends on *bar*, notice *bar*'s submodules are fetched ``` /tmp $ cargo init qux && cd qux Created binary (application) package /tmp/qux $ echo -e '[dependencies.bar]\ngit = "file:///tmp/bar"' >> Cargo.toml /tmp/qux $ time cargo update Updating git repository `file:///tmp/bar` Updating git submodule `file:///tmp/foo` real 0m22.182s user 0m20.402s sys 0m1.714s /tmp/qux $ ``` Fix it by checking if a Git repository submodule should be updated when cargo processes dependencies. 6. With the change applied, submodules with `update=none` are skipped ``` /tmp/qux $ cargo cache -a > /dev/null /tmp/qux $ time ~/src/cargo/target/debug/cargo update Updating git repository `file:///tmp/bar` Skipping git submodule `file:///tmp/foo` real 0m0.029s user 0m0.021s sys 0m0.008s /tmp/qux $ ``` Fixes #4247. Signed-off-by: Jakub Sitnicki <[email protected]>
1 parent 7d289b1 commit 26031b9

File tree

3 files changed

+67
-0
lines changed

3 files changed

+67
-0
lines changed

crates/cargo-test-support/src/compare.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ fn substitute_macros(input: &str) -> String {
196196
("[OWNER]", " Owner"),
197197
("[MIGRATING]", " Migrating"),
198198
("[EXECUTABLE]", " Executable"),
199+
("[SKIPPING]", " Skipping"),
199200
];
200201
let mut result = input.to_owned();
201202
for &(pat, subst) in &macros {

src/cargo/sources/git/utils.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,18 @@ impl<'a> GitCheckout<'a> {
370370
anyhow::format_err!("non-utf8 url for submodule {:?}?", child.path())
371371
})?;
372372

373+
// Skip the submodule if the config says not to update it.
374+
if child.update_strategy() == git2::SubmoduleUpdate::None {
375+
cargo_config.shell().status(
376+
"Skipping",
377+
format!(
378+
"git submodule `{}` due to update strategy in .gitmodules",
379+
url
380+
),
381+
)?;
382+
return Ok(());
383+
}
384+
373385
// A submodule which is listed in .gitmodules but not actually
374386
// checked out will not have a head id, so we should ignore it.
375387
let head = match child.head_id() {

tests/testsuite/git.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,60 @@ Caused by:
10231023
.run();
10241024
}
10251025

1026+
#[cargo_test]
1027+
fn dep_with_skipped_submodule() {
1028+
// Ensure we skip dependency submodules if their update strategy is `none`.
1029+
let qux = git::new("qux", |project| {
1030+
project.no_manifest().file("README", "skip me")
1031+
});
1032+
1033+
let bar = git::new("bar", |project| {
1034+
project
1035+
.file("Cargo.toml", &basic_manifest("bar", "0.0.0"))
1036+
.file("src/lib.rs", "")
1037+
});
1038+
1039+
// `qux` is a submodule of `bar`, but we don't want to update it.
1040+
let repo = git2::Repository::open(&bar.root()).unwrap();
1041+
git::add_submodule(&repo, qux.url().as_str(), Path::new("qux"));
1042+
1043+
let mut conf = git2::Config::open(&bar.root().join(".gitmodules")).unwrap();
1044+
conf.set_str("submodule.qux.update", "none").unwrap();
1045+
1046+
git::add(&repo);
1047+
git::commit(&repo);
1048+
1049+
let foo = project()
1050+
.file(
1051+
"Cargo.toml",
1052+
&format!(
1053+
r#"
1054+
[project]
1055+
name = "foo"
1056+
version = "0.0.0"
1057+
authors = []
1058+
1059+
[dependencies.bar]
1060+
git = "{}"
1061+
"#,
1062+
bar.url()
1063+
),
1064+
)
1065+
.file("src/main.rs", "fn main() {}")
1066+
.build();
1067+
1068+
foo.cargo("build")
1069+
.with_stderr(
1070+
"\
1071+
[UPDATING] git repository `file://[..]/bar`
1072+
[SKIPPING] git submodule `file://[..]/qux` [..]
1073+
[COMPILING] bar [..]
1074+
[COMPILING] foo [..]
1075+
[FINISHED] dev [unoptimized + debuginfo] target(s) in [..]\n",
1076+
)
1077+
.run();
1078+
}
1079+
10261080
#[cargo_test]
10271081
fn dep_ambiguous() {
10281082
let project = project();

0 commit comments

Comments
 (0)