Skip to content

Commit a6841b8

Browse files
committed
Auto merge of #7245 - ehuss:warn-dirty-submodule, r=alexcrichton
Check in package/publish if a git submodule is dirty. This extends the "dirty" check during `cargo publish` to check files within a submodule. It is a little crude, since it unconditionally tries to open every submodule even if it is not relevant to the current package. The performance doesn't seem too bad (~2s for rust-lang/rust with 16 submodules). It's also a little lax, by ignoring uninitialized submodules. Presumably the verification build will fail if the submodule is not initialized. It could be more aggressive, by requiring all submodules to be initialized? Closes #3622
2 parents d19f614 + 22adaf9 commit a6841b8

File tree

3 files changed

+155
-3
lines changed

3 files changed

+155
-3
lines changed

src/cargo/ops/cargo_package.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,14 +267,32 @@ fn check_repo_state(
267267
allow_dirty: bool,
268268
) -> CargoResult<Option<String>> {
269269
let workdir = repo.workdir().unwrap();
270+
271+
let mut sub_repos = Vec::new();
272+
open_submodules(repo, &mut sub_repos)?;
273+
// Sort so that longest paths are first, to check nested submodules first.
274+
sub_repos.sort_unstable_by(|a, b| b.0.as_os_str().len().cmp(&a.0.as_os_str().len()));
275+
let submodule_dirty = |path: &Path| -> bool {
276+
sub_repos
277+
.iter()
278+
.filter(|(sub_path, _sub_repo)| path.starts_with(sub_path))
279+
.any(|(sub_path, sub_repo)| {
280+
let relative = path.strip_prefix(sub_path).unwrap();
281+
sub_repo
282+
.status_file(relative)
283+
.map(|status| status != git2::Status::CURRENT)
284+
.unwrap_or(false)
285+
})
286+
};
287+
270288
let dirty = src_files
271289
.iter()
272290
.filter(|file| {
273291
let relative = file.strip_prefix(workdir).unwrap();
274292
if let Ok(status) = repo.status_file(relative) {
275293
status != git2::Status::CURRENT
276294
} else {
277-
false
295+
submodule_dirty(file)
278296
}
279297
})
280298
.map(|path| {
@@ -300,6 +318,22 @@ fn check_repo_state(
300318
Ok(None)
301319
}
302320
}
321+
322+
/// Helper to recursively open all submodules.
323+
fn open_submodules(
324+
repo: &git2::Repository,
325+
sub_repos: &mut Vec<(PathBuf, git2::Repository)>,
326+
) -> CargoResult<()> {
327+
for submodule in repo.submodules()? {
328+
// Ignore submodules that don't open, they are probably not initialized.
329+
// If its files are required, then the verification step should fail.
330+
if let Ok(sub_repo) = submodule.open() {
331+
open_submodules(&sub_repo, sub_repos)?;
332+
sub_repos.push((sub_repo.workdir().unwrap().to_owned(), sub_repo));
333+
}
334+
}
335+
Ok(())
336+
}
303337
}
304338

305339
// Checks for and `bail!` if a source file matches `ROOT/VCS_INFO_FILE`, since

tests/testsuite/git.rs

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2691,3 +2691,117 @@ fn git_fetch_cli_env_clean() {
26912691
.env("GIT_DIR", git_proj.root().join(".git"))
26922692
.run();
26932693
}
2694+
2695+
#[cargo_test]
2696+
fn dirty_submodule() {
2697+
// `cargo package` warns for dirty file in submodule.
2698+
let (git_project, repo) = git::new_repo("foo", |project| {
2699+
project
2700+
.file("Cargo.toml", &basic_manifest("foo", "0.5.0"))
2701+
// This is necessary because `git::add` is too eager.
2702+
.file(".gitignore", "/target")
2703+
});
2704+
let git_project2 = git::new("src", |project| {
2705+
project.no_manifest().file("lib.rs", "pub fn f() {}")
2706+
});
2707+
2708+
let url = path2url(git_project2.root()).to_string();
2709+
git::add_submodule(&repo, &url, Path::new("src"));
2710+
2711+
// Submodule added, but not committed.
2712+
git_project
2713+
.cargo("package --no-verify")
2714+
.with_status(101)
2715+
.with_stderr(
2716+
"\
2717+
[WARNING] manifest has no [..]
2718+
See [..]
2719+
[ERROR] 1 files in the working directory contain changes that were not yet committed into git:
2720+
2721+
.gitmodules
2722+
2723+
to proceed despite [..]
2724+
",
2725+
)
2726+
.run();
2727+
2728+
git::commit(&repo);
2729+
git_project.cargo("package --no-verify").run();
2730+
2731+
// Modify file, check for warning.
2732+
git_project.change_file("src/lib.rs", "");
2733+
git_project
2734+
.cargo("package --no-verify")
2735+
.with_status(101)
2736+
.with_stderr(
2737+
"\
2738+
[WARNING] manifest has no [..]
2739+
See [..]
2740+
[ERROR] 1 files in the working directory contain changes that were not yet committed into git:
2741+
2742+
src/lib.rs
2743+
2744+
to proceed despite [..]
2745+
",
2746+
)
2747+
.run();
2748+
// Commit the change.
2749+
let sub_repo = git2::Repository::open(git_project.root().join("src")).unwrap();
2750+
git::add(&sub_repo);
2751+
git::commit(&sub_repo);
2752+
git::add(&repo);
2753+
git::commit(&repo);
2754+
git_project.cargo("package --no-verify").run();
2755+
2756+
// Try with a nested submodule.
2757+
let git_project3 = git::new("bar", |project| project.no_manifest().file("mod.rs", ""));
2758+
let url = path2url(git_project3.root()).to_string();
2759+
git::add_submodule(&sub_repo, &url, Path::new("bar"));
2760+
git_project
2761+
.cargo("package --no-verify")
2762+
.with_status(101)
2763+
.with_stderr(
2764+
"\
2765+
[WARNING] manifest has no [..]
2766+
See [..]
2767+
[ERROR] 1 files in the working directory contain changes that were not yet committed into git:
2768+
2769+
src/.gitmodules
2770+
2771+
to proceed despite [..]
2772+
",
2773+
)
2774+
.run();
2775+
2776+
// Commit the submodule addition.
2777+
git::commit(&sub_repo);
2778+
git::add(&repo);
2779+
git::commit(&repo);
2780+
git_project.cargo("package --no-verify").run();
2781+
// Modify within nested submodule.
2782+
git_project.change_file("src/bar/mod.rs", "//test");
2783+
git_project
2784+
.cargo("package --no-verify")
2785+
.with_status(101)
2786+
.with_stderr(
2787+
"\
2788+
[WARNING] manifest has no [..]
2789+
See [..]
2790+
[ERROR] 1 files in the working directory contain changes that were not yet committed into git:
2791+
2792+
src/bar/mod.rs
2793+
2794+
to proceed despite [..]
2795+
",
2796+
)
2797+
.run();
2798+
// And commit the change.
2799+
let sub_sub_repo = git2::Repository::open(git_project.root().join("src/bar")).unwrap();
2800+
git::add(&sub_sub_repo);
2801+
git::commit(&sub_sub_repo);
2802+
git::add(&sub_repo);
2803+
git::commit(&sub_repo);
2804+
git::add(&repo);
2805+
git::commit(&repo);
2806+
git_project.cargo("package --no-verify").run();
2807+
}

tests/testsuite/support/git.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,11 +129,14 @@ impl Repository {
129129
/// Initialize a new repository at the given path.
130130
pub fn init(path: &Path) -> git2::Repository {
131131
let repo = t!(git2::Repository::init(path));
132+
default_repo_cfg(&repo);
133+
repo
134+
}
135+
136+
fn default_repo_cfg(repo: &git2::Repository) {
132137
let mut cfg = t!(repo.config());
133138
t!(cfg.set_str("user.email", "foo@bar.com"));
134139
t!(cfg.set_str("user.name", "Foo Bar"));
135-
drop(cfg);
136-
repo
137140
}
138141

139142
/// Create a new git repository with a project.
@@ -193,6 +196,7 @@ pub fn add_submodule<'a>(
193196
let path = path.to_str().unwrap().replace(r"\", "/");
194197
let mut s = t!(repo.submodule(url, Path::new(&path), false));
195198
let subrepo = t!(s.open());
199+
default_repo_cfg(&subrepo);
196200
t!(subrepo.remote_add_fetch("origin", "refs/heads/*:refs/heads/*"));
197201
let mut origin = t!(subrepo.find_remote("origin"));
198202
t!(origin.fetch(&[], None, None));

0 commit comments

Comments
 (0)