Skip to content

Commit 273f481

Browse files
authored
Merge branch 'main' into copilot/fix-f17b10ff-14e3-4e2b-9419-6198abd56d45
2 parents ab28f3c + 81357d8 commit 273f481

File tree

6 files changed

+61
-19
lines changed

6 files changed

+61
-19
lines changed

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ num_cpus = "1.16"
4747
patch = "0.7"
4848
path-slash = "0.2"
4949
quote = "1.0.35"
50+
reflink = "0.1"
5051
regex = "1.10"
5152
schemars = "0.9"
5253
serde_json = "1.0.128"

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Changed: Tree copying now attempts to use reflinks (copy-on-write) for faster copying on supported filesystems (Btrfs, XFS, APFS), with automatic fallback to regular copying.
6+
57
- Book: Recommend using the `-Zunstable-options --fail-fast` argument to test targets to speed up mutation testing, on recent nightly toolchains.
68

79
- Fixed: Don't error if the `--in-diff` patch file contains non-UTF-8 data in non-Rust files.
@@ -10,6 +12,8 @@
1012

1113
- New: `cargo_mutants_version` field in `outcomes.json`.
1214

15+
- Changed: The bitwise assignment operators `&=` and `|=` are no longer mutated to `^=`. In code that accumulates bits into a bitmap starting from zero (e.g., `bitmap |= new_bits`), `|=` and `^=` produce the same result, making such mutations uninformative.
16+
1317
## 25.3.1 2025-08-10
1418

1519
- Fixed: cargo-mutants' own tests were failing on nightly due to a change in the format of messages emitted by tests.

book/src/mutants.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,17 @@ like `a == 0`.
8383
| `&` | `\|`,`^` |
8484
| `\|` | `&`, `^` |
8585
| `^` | `&`, `\|` |
86-
| `+=` and similar assignments | assignment corresponding to the line above |
86+
| `&=` | `\|=` |
87+
| `\|=` | `&=` |
88+
| `^=` | `\|=`, `&=` |
89+
| `+=`, `-=`, `*=`, `/=`, `%=`, `<<=`, `>>=` | assignment corresponding to the operator above |
8790

8891
Equality operators are not currently replaced with comparisons like `<` or `<=`
8992
because they are
9093
too prone to generate false positives, for example when unsigned integers are compared to 0.
9194

95+
The bitwise assignment operators `&=` and `|=` are not mutated to `^=` because in code that accumulates bits (e.g., `bitmap |= new_bits`), `|=` and `^=` produce the same result when starting from zero, making such mutations uninformative.
96+
9297
## Unary operators
9398

9499
Unary operators are deleted in expressions like `-a` and `!a`.

src/copy_tree.rs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
//! Copy a source tree, with some exclusions, to a new temporary directory.
44
5+
use std::fs;
6+
use std::path::Path;
7+
use std::sync::atomic::{AtomicBool, Ordering};
8+
59
use anyhow::Context;
610
use camino::{Utf8Path, Utf8PathBuf};
711
use ignore::WalkBuilder;
8-
use path_slash::PathExt;
912
use tempfile::TempDir;
1013
use tracing::{debug, warn};
1114

@@ -24,6 +27,32 @@ use windows::copy_symlink;
2427

2528
static VCS_DIRS: &[&str] = &[".git", ".hg", ".bzr", ".svn", "_darcs", ".jj", ".pijul"];
2629

30+
/// Copy a file, attempting to use reflink if supported.
31+
/// Returns the number of bytes copied.
32+
fn copy_file(src: &Path, dest: &Path, reflink_supported: &AtomicBool) -> Result<u64> {
33+
// Try reflink first if we haven't determined it's not supported
34+
if reflink_supported.load(Ordering::Relaxed) {
35+
match reflink::reflink(src, dest) {
36+
Ok(()) => {
37+
// Reflink succeeded, get file size for progress tracking
38+
let metadata = fs::metadata(dest)
39+
.with_context(|| format!("Failed to get metadata for {}", dest.display()))?;
40+
return Ok(metadata.len());
41+
}
42+
Err(e) => {
43+
// On Windows, reflink can fail without returning ErrorKind::Unsupported,
44+
// so we give up on reflinks after any error to avoid repeated failures.
45+
reflink_supported.store(false, Ordering::Relaxed);
46+
debug!("Reflink failed: {}, falling back to regular copy", e);
47+
}
48+
}
49+
}
50+
51+
// Fall back to regular copy
52+
fs::copy(src, dest)
53+
.with_context(|| format!("Failed to copy {} to {}", src.display(), dest.display()))
54+
}
55+
2756
/// Copy a source tree, with some exclusions, to a new temporary directory.
2857
pub fn copy_tree(
2958
from_path: &Utf8Path,
@@ -33,6 +62,7 @@ pub fn copy_tree(
3362
) -> Result<TempDir> {
3463
let mut total_bytes = 0;
3564
let mut total_files = 0;
65+
let reflink_supported = AtomicBool::new(true);
3666
let temp_dir = tempfile::Builder::new()
3767
.prefix(name_base)
3868
.suffix(".tmp")
@@ -86,12 +116,8 @@ pub fn copy_tree(
86116
)
87117
})?;
88118
if ft.is_file() {
89-
let bytes_copied = std::fs::copy(entry.path(), &dest_path).with_context(|| {
90-
format!(
91-
"Failed to copy {:?} to {dest_path:?}",
92-
entry.path().to_slash_lossy(),
93-
)
94-
})?;
119+
let bytes_copied =
120+
copy_file(entry.path(), dest_path.as_std_path(), &reflink_supported)?;
95121
total_bytes += bytes_copied;
96122
total_files += 1;
97123
console.copy_progress(dest, total_bytes);
@@ -112,7 +138,8 @@ pub fn copy_tree(
112138
}
113139
}
114140
console.finish_copy(dest);
115-
debug!(?total_bytes, ?total_files, temp_dir = ?temp_dir.path(), "Copied source tree");
141+
let reflink_used = reflink_supported.load(Ordering::Relaxed);
142+
debug!(?total_bytes, ?total_files, ?reflink_used, temp_dir = ?temp_dir.path(), "Copied source tree");
116143
Ok(temp_dir)
117144
}
118145

src/visit.rs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -591,9 +591,9 @@ impl<'ast> Visit<'ast> for DiscoveryVisitor<'_> {
591591
BinOp::Shr(_) => vec![quote! {<<}],
592592
BinOp::ShrAssign(_) => vec![quote! {<<=}],
593593
BinOp::BitAnd(_) => vec![quote! {|}, quote! {^}],
594-
BinOp::BitAndAssign(_) => vec![quote! {|=}, quote! {^=}],
594+
BinOp::BitAndAssign(_) => vec![quote! {|=}],
595595
BinOp::BitOr(_) => vec![quote! {&}, quote! {^}],
596-
BinOp::BitOrAssign(_) => vec![quote! {&=}, quote! {^=}],
596+
BinOp::BitOrAssign(_) => vec![quote! {&=}],
597597
BinOp::BitXor(_) => vec![quote! {|}, quote! {&}],
598598
BinOp::BitXorAssign(_) => vec![quote! {|=}, quote! {&=}],
599599
_ => {
@@ -1389,14 +1389,8 @@ mod test {
13891389
mutate_expr("a /= b"),
13901390
&["replace /= with %=", "replace /= with *="]
13911391
);
1392-
assert_eq!(
1393-
mutate_expr("a &= b"),
1394-
&["replace &= with |=", "replace &= with ^="]
1395-
);
1396-
assert_eq!(
1397-
mutate_expr("a |= b"),
1398-
&["replace |= with &=", "replace |= with ^="]
1399-
);
1392+
assert_eq!(mutate_expr("a &= b"), &["replace &= with |="]);
1393+
assert_eq!(mutate_expr("a |= b"), &["replace |= with &="]);
14001394
assert_eq!(
14011395
mutate_expr("a ^= b"),
14021396
&["replace ^= with |=", "replace ^= with &="]

0 commit comments

Comments
 (0)