Skip to content

Commit f0907fc

Browse files
committed
fix(package): report if the lockfile is dirty
Lockfile might not be under the package root, but its entries may be outdated and get packaged into the `.crate` tarball. We take a conservative action that if the lockfile is dirty, then all workspace members are considered dirty.
1 parent d9908b1 commit f0907fc

File tree

2 files changed

+55
-9
lines changed

2 files changed

+55
-9
lines changed

src/cargo/ops/cargo_package/vcs.rs

+37-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use tracing::debug;
1111

1212
use crate::core::Package;
1313
use crate::core::Workspace;
14+
use crate::ops::lockfile::LOCKFILE_NAME;
1415
use crate::sources::PathEntry;
1516
use crate::CargoResult;
1617
use crate::GlobalContext;
@@ -235,10 +236,15 @@ fn git(
235236
/// * `package.readme` and `package.license-file` pointing to paths outside package root
236237
/// * symlinks targets reside outside package root
237238
/// * Any change in the root workspace manifest, regardless of what has changed.
239+
/// * Changes in the lockfile [^1].
238240
///
239241
/// This is required because those paths may link to a file outside the
240242
/// current package root, but still under the git workdir, affecting the
241243
/// final packaged `.crate` file.
244+
///
245+
/// [^1]: Lockfile might be re-generated if it is too out of sync with the manifest.
246+
/// Therefore, even you have a modified lockfile,
247+
/// you might still get a new fresh one that matches what is in git index.
242248
fn dirty_files_outside_pkg_root(
243249
ws: &Workspace<'_>,
244250
pkg: &Package,
@@ -248,20 +254,50 @@ fn dirty_files_outside_pkg_root(
248254
let pkg_root = pkg.root();
249255
let workdir = repo.workdir().unwrap();
250256

257+
let mut dirty_files = HashSet::new();
258+
251259
let meta = pkg.manifest().metadata();
252260
let metadata_paths: Vec<_> = [&meta.license_file, &meta.readme]
253261
.into_iter()
254262
.filter_map(|p| p.as_deref())
255263
.map(|path| paths::normalize_path(&pkg_root.join(path)))
256264
.collect();
257265

258-
let mut dirty_files = HashSet::new();
266+
// Unlike other files, lockfile is allowed to be missing,
267+
// and can be generated during packaging.
268+
// We skip checking when it is missing in both workdir and git index,
269+
// otherwise cargo will fail with git2 not found error.
270+
let lockfile_path = ws.lock_root().as_path_unlocked().join(LOCKFILE_NAME);
271+
let lockfile_path = if lockfile_path.exists() {
272+
Some(lockfile_path)
273+
} else if let Ok(rel_path) = paths::normalize_path(&lockfile_path).strip_prefix(workdir) {
274+
// We don't canonicalize here because non-existing path can't be canonicalized.
275+
match repo.status_file(&rel_path) {
276+
Ok(s) if s != git2::Status::CURRENT => {
277+
dirty_files.insert(lockfile_path);
278+
}
279+
// Unmodified
280+
Ok(_) => {}
281+
Err(e) => {
282+
debug!(
283+
"check git status failed for `{}` in workdir `{}`: {e}",
284+
rel_path.display(),
285+
workdir.display(),
286+
);
287+
}
288+
}
289+
None
290+
} else {
291+
None
292+
};
293+
259294
for rel_path in src_files
260295
.iter()
261296
.filter(|p| p.is_symlink_or_under_symlink())
262297
.map(|p| p.as_ref().as_path())
263298
.chain(metadata_paths.iter().map(AsRef::as_ref))
264299
.chain([ws.root_manifest()])
300+
.chain(lockfile_path.as_deref().into_iter())
265301
// If inside package root. Don't bother checking git status.
266302
.filter(|p| paths::strip_prefix_canonical(p, pkg_root).is_err())
267303
// Handle files outside package root but under git workdir,

tests/testsuite/package.rs

+18-8
Original file line numberDiff line numberDiff line change
@@ -1465,9 +1465,13 @@ fn dirty_ws_lockfile_dirty() {
14651465
// lockfile is untracked.
14661466
p.cargo("generate-lockfile").run();
14671467
p.cargo("package --workspace --no-verify")
1468+
.with_status(101)
14681469
.with_stderr_data(str![[r#"
1469-
[PACKAGING] isengard v0.0.0 ([ROOT]/foo/isengard)
1470-
[PACKAGED] 5 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
1470+
[ERROR] 1 files in the working directory contain changes that were not yet committed into git:
1471+
1472+
Cargo.lock
1473+
1474+
to proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag
14711475
14721476
"#]])
14731477
.run();
@@ -1522,10 +1526,13 @@ dependencies = [
15221526
"#]])
15231527
.run();
15241528
p.cargo("package --workspace --no-verify")
1529+
.with_status(101)
15251530
.with_stderr_data(str![[r#"
1526-
[PACKAGING] isengard v0.0.0 ([ROOT]/foo/isengard)
1527-
[UPDATING] `dummy-registry` index
1528-
[PACKAGED] 5 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
1531+
[ERROR] 1 files in the working directory contain changes that were not yet committed into git:
1532+
1533+
Cargo.lock
1534+
1535+
to proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag
15291536
15301537
"#]])
15311538
.run();
@@ -1534,10 +1541,13 @@ dependencies = [
15341541
p.cargo("clean").run();
15351542
fs::remove_file(p.root().join("Cargo.lock")).unwrap();
15361543
p.cargo("package --workspace --no-verify")
1544+
.with_status(101)
15371545
.with_stderr_data(str![[r#"
1538-
[PACKAGING] isengard v0.0.0 ([ROOT]/foo/isengard)
1539-
[UPDATING] `dummy-registry` index
1540-
[PACKAGED] 5 files, [FILE_SIZE]B ([FILE_SIZE]B compressed)
1546+
[ERROR] 1 files in the working directory contain changes that were not yet committed into git:
1547+
1548+
Cargo.lock
1549+
1550+
to proceed despite this and include the uncommitted changes, pass the `--allow-dirty` flag
15411551
15421552
"#]])
15431553
.run();

0 commit comments

Comments
 (0)