mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-12 23:23:20 +00:00
local_working_copy: when all sides of a conflict are executable, materialise the conflicted file as executable
Fixes #3579 and adds a testcase for an executable conflict treevalue.
This commit is contained in:
parent
5041649cbb
commit
59d3a2c866
4 changed files with 68 additions and 7 deletions
|
@ -40,6 +40,9 @@ to avoid letting the user edit the immutable one.
|
|||
|
||||
* `jj config list` now properly escapes TOML keys (#1322).
|
||||
|
||||
* Files with conflicts are now checked out as executable if all sides of the
|
||||
conflict are executable.
|
||||
|
||||
## [0.17.1] - 2024-05-07
|
||||
|
||||
### Fixed bugs
|
||||
|
|
|
@ -1152,10 +1152,15 @@ impl TreeState {
|
|||
.block_on()?;
|
||||
match new_file_ids.into_resolved() {
|
||||
Ok(file_id) => {
|
||||
// On Windows, we preserve the executable bit from the merged trees.
|
||||
#[cfg(windows)]
|
||||
let executable = {
|
||||
let () = executable; // use the variable
|
||||
false
|
||||
if let Some(merge) = current_tree_values.to_executable_merge() {
|
||||
merge.resolve_trivial().copied().unwrap_or_default()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
Ok(Merge::normal(TreeValue::File {
|
||||
id: file_id.unwrap(),
|
||||
|
@ -1224,6 +1229,7 @@ impl TreeState {
|
|||
&self,
|
||||
disk_path: &Path,
|
||||
conflict_data: Vec<u8>,
|
||||
executable: bool,
|
||||
) -> Result<FileState, CheckoutError> {
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
|
@ -1239,12 +1245,11 @@ impl TreeState {
|
|||
err: err.into(),
|
||||
})?;
|
||||
let size = conflict_data.len() as u64;
|
||||
// TODO: Set the executable bit correctly (when possible) and preserve that on
|
||||
// Windows like we do with the executable bit for regular files.
|
||||
self.set_executable(disk_path, executable)?;
|
||||
let metadata = file
|
||||
.metadata()
|
||||
.map_err(|err| checkout_error_for_stat_error(err, disk_path))?;
|
||||
Ok(FileState::for_file(false, size, &metadata))
|
||||
Ok(FileState::for_file(executable, size, &metadata))
|
||||
}
|
||||
|
||||
#[cfg_attr(windows, allow(unused_variables))]
|
||||
|
@ -1393,8 +1398,13 @@ impl TreeState {
|
|||
MaterializedTreeValue::Tree(_) => {
|
||||
panic!("unexpected tree entry in diff at {path:?}");
|
||||
}
|
||||
MaterializedTreeValue::Conflict { id: _, contents } => {
|
||||
self.write_conflict(&disk_path, contents)?
|
||||
MaterializedTreeValue::Conflict { id, contents } => {
|
||||
let executable = if let Some(merge) = id.to_executable_merge() {
|
||||
merge.resolve_trivial().copied().unwrap_or_default()
|
||||
} else {
|
||||
false
|
||||
};
|
||||
self.write_conflict(&disk_path, contents, executable)?
|
||||
}
|
||||
};
|
||||
changed_file_states.push((path, file_state));
|
||||
|
|
|
@ -512,6 +512,16 @@ impl MergedTreeValue {
|
|||
})
|
||||
}
|
||||
|
||||
/// If this merge contains only files or absent entries, returns a merge of
|
||||
/// the files' executable bits.
|
||||
pub fn to_executable_merge(&self) -> Option<Merge<bool>> {
|
||||
self.maybe_map(|term| match term {
|
||||
None => Some(false),
|
||||
Some(TreeValue::File { id: _, executable }) => Some(*executable),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a new merge with the file ids from the given merge. In other
|
||||
/// words, only the executable bits from `self` will be preserved.
|
||||
pub fn with_new_file_ids(&self, file_ids: &Merge<Option<FileId>>) -> Self {
|
||||
|
|
|
@ -87,6 +87,8 @@ fn test_checkout_file_transitions(backend: TestRepoBackend) {
|
|||
// Executable, but same content as Normal, to test transition where only the bit changed
|
||||
ExecutableNormalContent,
|
||||
Conflict,
|
||||
// Same content as Executable, to test that transition preserves the executable bit
|
||||
ConflictedExecutableContent,
|
||||
Symlink,
|
||||
Tree,
|
||||
GitSubmodule,
|
||||
|
@ -110,7 +112,8 @@ fn test_checkout_file_transitions(backend: TestRepoBackend) {
|
|||
})
|
||||
}
|
||||
Kind::Executable => {
|
||||
let id = testutils::write_file(store, path, "executable file contents");
|
||||
let id: jj_lib::backend::FileId =
|
||||
testutils::write_file(store, path, "executable file contents");
|
||||
Merge::normal(TreeValue::File {
|
||||
id,
|
||||
executable: true,
|
||||
|
@ -144,6 +147,29 @@ fn test_checkout_file_transitions(backend: TestRepoBackend) {
|
|||
],
|
||||
)
|
||||
}
|
||||
Kind::ConflictedExecutableContent => {
|
||||
let base_file_id = testutils::write_file(store, path, "executable file contents");
|
||||
let left_file_id =
|
||||
testutils::write_file(store, path, "left executable file contents");
|
||||
let right_file_id =
|
||||
testutils::write_file(store, path, "right executable file contents");
|
||||
Merge::from_removes_adds(
|
||||
vec![Some(TreeValue::File {
|
||||
id: base_file_id,
|
||||
executable: true,
|
||||
})],
|
||||
vec![
|
||||
Some(TreeValue::File {
|
||||
id: left_file_id,
|
||||
executable: true,
|
||||
}),
|
||||
Some(TreeValue::File {
|
||||
id: right_file_id,
|
||||
executable: true,
|
||||
}),
|
||||
],
|
||||
)
|
||||
}
|
||||
Kind::Symlink => {
|
||||
let id = store.write_symlink(path, "target").unwrap();
|
||||
Merge::normal(TreeValue::Symlink(id))
|
||||
|
@ -174,6 +200,7 @@ fn test_checkout_file_transitions(backend: TestRepoBackend) {
|
|||
Kind::Executable,
|
||||
Kind::ExecutableNormalContent,
|
||||
Kind::Conflict,
|
||||
Kind::ConflictedExecutableContent,
|
||||
Kind::Tree,
|
||||
];
|
||||
kinds.push(Kind::Symlink);
|
||||
|
@ -246,6 +273,17 @@ fn test_checkout_file_transitions(backend: TestRepoBackend) {
|
|||
"{path:?} should not be executable"
|
||||
);
|
||||
}
|
||||
Kind::ConflictedExecutableContent => {
|
||||
assert!(maybe_metadata.is_ok(), "{path:?} should exist");
|
||||
let metadata = maybe_metadata.unwrap();
|
||||
assert!(metadata.is_file(), "{path:?} should be a file");
|
||||
#[cfg(unix)]
|
||||
assert_ne!(
|
||||
metadata.permissions().mode() & 0o111,
|
||||
0,
|
||||
"{path:?} should be executable"
|
||||
);
|
||||
}
|
||||
Kind::Symlink => {
|
||||
assert!(maybe_metadata.is_ok(), "{path:?} should exist");
|
||||
let metadata = maybe_metadata.unwrap();
|
||||
|
|
Loading…
Reference in a new issue