A binary file's new contents and new executable bit are both kept 6eae06b6ec04
For an opaque file the mode change was recorded only when its blob was
unchanged, so new contents and chmod +x in one change kept the contents
and dropped the bit: in oxy commit, and in the Git import, where VS
Code's build/win32/inno_updater.exe lost +x at 0aab49c6cb6 and 49 later
commits failed verify. The SetMode now goes with the Replace.
closes #87
Author Andy McCall @andymccall <[email protected] >
When 27 days ago · 2026-10-08 22:31 UTC
Signature ✔ signed by Andy McCall
Hash 6eae06b6ec0478bd090b1dc0b282ccde811b8a5ab84f308b15c0d86be7afbc33
Depends on 75167672ebcd 83e0677b07fe 982864068a59
Depended on by f0a517d28237
In bookmarks bare-hashes carried-not-removed carry-identical doctor-trust identical-sides keyless-tags main merge-remote-bookmark only-help-103 paths-for-resolutions-and-amend push-notes show-stat subrepo-pin-state switch-says
In tags v0.10.0 v0.11.0 v0.4.2 v0.4.3 v0.4.4 v0.4.5 v0.4.6 v0.5.0 v0.5.1 v0.6.0 v0.7.0 v0.8.0 v0.9.0
Edits 2 atoms, 0 tree operations, 2 files
Browse the tree at this change
crates/oxy-core/src/repo/snapshot.rs crates/oxy-core/src/repo/tests.rs
modified
crates/oxy-core/src/repo/snapshot.rs
+15 −0
…
1639 unchanged lines
1640
1640
old: current,
1641
1641
new: Some(blob),
1642
1642
});
1643
+
// New contents and a new executable bit at once: the
1644
+
// bit was dropped (VS Code's inno_updater.exe,
1645
+
// 2018-02-28, lost +x for 49 commits; oxygen#87).
1646
+
if e.kind == NodeKind::File && e.exec != expected.mode.exec {
1647
+
if let Some(old) = mode_atom {
1648
+
self.tree_ops.push(TreeOp::SetMode {
1649
+
node,
1650
+
old,
1651
+
mode: Mode {
1652
+
exec: e.exec,
1653
+
opaque: true,
1654
+
},
1655
+
});
1656
+
}
1657
+
}
1643
1658
}
1644
1659
status = Some(ChangeStatus::Modified);
1645
1660
} else if e.kind == NodeKind::File && e.exec != expected.mode.exec {
…
345 unchanged lines
modified
crates/oxy-core/src/repo/tests.rs
+24 −0
…
946 unchanged lines
947
947
assert!(!snap.is_conflicted());
948
948
}
949
949
950
+
/// oxygen#87: a binary file given new contents and the executable bit in
951
+
/// one commit kept the contents and lost the bit.
952
+
#[cfg(unix)]
953
+
#[test]
954
+
fn a_binary_file_changed_and_made_executable_at_once_keeps_both() {
955
+
use std::os::unix::fs::PermissionsExt;
956
+
hermetic();
957
+
let dir = tempfile::tempdir().unwrap();
958
+
let root = dir.path();
959
+
let repo = Repo::init(root).unwrap();
960
+
write(root, "tool.exe", b"MZ\0one");
961
+
repo.commit("add", &[]).unwrap();
962
+
write(root, "tool.exe", b"MZ\0two");
963
+
fs::set_permissions(root.join("tool.exe"), fs::Permissions::from_mode(0o755)).unwrap();
964
+
let h = repo.commit("update and +x", &[]).unwrap();
965
+
let snap = repo.load_state(&[h]).unwrap().materialize();
966
+
let f = &snap.files["tool.exe"];
967
+
assert!(f.mode.opaque && f.mode.exec, "{:?}", f.mode);
968
+
assert!(
969
+
repo.snapshot("status").unwrap().statuses.is_empty(),
970
+
"nothing left to commit"
971
+
);
972
+
}
973
+
950
974
#[test]
951
975
fn trust_policy_governs_pushes_and_fetches() {
952
976
hermetic();
…
2918 unchanged lines