Skip to content

Commit df43517

Browse files
committed
conflicts: compare content hash to detect unchanged materialized conflicts
It seemed odd that we had to pass ConflictMarkerStyle to snapshot functions. Since we just need to check if the on-disk data has changed, we can instead store and compare content hashes. Conflict files checked out by old jj might be updated on mtime change, which seems okay. It's less likely to leave conflict files in the working copy for long and touch them. updated_from_content() is called by merge tools, and unchanged conflicts may be updated there. I think that's okay since "jj resolve" is an explicit operation. test_materialize_parse_roundtrip_different_markers() is simplified as there should no longer be "pair" of marker styles.
1 parent 9b45187 commit df43517

File tree

6 files changed

+53
-147
lines changed

6 files changed

+53
-147
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
3939
* The `conflict` label used for coloring log graph nodes was renamed to
4040
`conflicted`.
4141

42+
* `jj` now records content hash of materialized conflict files in addition to
43+
file modification time, and relies on them to detect changes. Unchanged
44+
conflicts checked out by old `jj` may be snapshotted by new `jj`. Run `jj new
45+
<REVISION-WITHOUT-CONFLICTS> && jj undo` to check out fresh conflict files.
46+
4247
### Deprecations
4348

4449
* The on-disk index format has changed. `jj` will write index files in both old

cli/src/merge_tools/builtin.rs

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -386,7 +386,6 @@ fn apply_diff_builtin(
386386
right_tree: &MergedTree,
387387
changed_files: Vec<RepoPathBuf>,
388388
files: &[scm_record::File],
389-
conflict_marker_style: ConflictMarkerStyle,
390389
) -> BackendResult<MergedTreeId> {
391390
// Start with the right tree to match external tool behavior.
392391
// This ensures unmatched paths keep their values from the right tree.
@@ -421,7 +420,6 @@ fn apply_diff_builtin(
421420
store,
422421
path,
423422
contents,
424-
conflict_marker_style,
425423
MIN_CONFLICT_MARKER_LEN, // TODO: use the materialization parameter
426424
)
427425
.block_on()?;
@@ -559,15 +557,8 @@ pub fn edit_diff_builtin(
559557
&mut input,
560558
);
561559
let result = recorder.run().map_err(BuiltinToolError::Record)?;
562-
let tree_id = apply_diff_builtin(
563-
&store,
564-
left_tree,
565-
right_tree,
566-
changed_files,
567-
&result.files,
568-
conflict_marker_style,
569-
)
570-
.map_err(BuiltinToolError::BackendError)?;
560+
let tree_id = apply_diff_builtin(&store, left_tree, right_tree, changed_files, &result.files)
561+
.map_err(BuiltinToolError::BackendError)?;
571562
Ok(tree_id)
572563
}
573564

@@ -771,15 +762,7 @@ mod tests {
771762
changed_files: &[RepoPathBuf],
772763
files: &[scm_record::File],
773764
) -> MergedTreeId {
774-
apply_diff_builtin(
775-
store,
776-
left_tree,
777-
right_tree,
778-
changed_files.to_vec(),
779-
files,
780-
ConflictMarkerStyle::Diff,
781-
)
782-
.unwrap()
765+
apply_diff_builtin(store, left_tree, right_tree, changed_files.to_vec(), files).unwrap()
783766
}
784767

785768
#[test]
@@ -1809,15 +1792,8 @@ mod tests {
18091792

18101793
assert_eq!(changed_files, vec![matched_path.to_owned()]);
18111794

1812-
let result_tree_id = apply_diff_builtin(
1813-
store,
1814-
&left_tree,
1815-
&right_tree,
1816-
changed_files,
1817-
&files,
1818-
ConflictMarkerStyle::Diff,
1819-
)
1820-
.unwrap();
1795+
let result_tree_id =
1796+
apply_diff_builtin(store, &left_tree, &right_tree, changed_files, &files).unwrap();
18211797
let result_tree = store.get_root_tree(&result_tree_id).unwrap();
18221798

18231799
assert_eq!(

cli/src/merge_tools/external.rs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,6 @@ fn run_mergetool_external_single_file(
192192
file,
193193
} = merge_tool_file;
194194

195-
let conflict_marker_style = editor
196-
.conflict_marker_style
197-
.unwrap_or(default_conflict_marker_style);
198-
199195
let uses_marker_length = find_all_variables(&editor.merge_args).contains(&"marker_length");
200196

201197
// If the merge tool doesn't get conflict markers pre-populated in the output
@@ -209,7 +205,9 @@ fn run_mergetool_external_single_file(
209205
};
210206
let initial_output_content = if editor.merge_tool_edits_conflict_markers {
211207
let options = ConflictMaterializeOptions {
212-
marker_style: conflict_marker_style,
208+
marker_style: editor
209+
.conflict_marker_style
210+
.unwrap_or(default_conflict_marker_style),
213211
marker_len: Some(conflict_marker_len),
214212
};
215213
materialize_merge_result_to_bytes(&file.contents, &options)
@@ -294,7 +292,6 @@ fn run_mergetool_external_single_file(
294292
store,
295293
repo_path,
296294
output_file_contents.as_slice(),
297-
conflict_marker_style,
298295
conflict_marker_len,
299296
)
300297
.block_on()?

lib/src/conflicts.rs

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -923,27 +923,10 @@ pub async fn update_from_content(
923923
store: &Store,
924924
path: &RepoPath,
925925
content: &[u8],
926-
conflict_marker_style: ConflictMarkerStyle,
927926
conflict_marker_len: usize,
928927
) -> BackendResult<Merge<Option<FileId>>> {
929928
let simplified_file_ids = file_ids.simplify();
930929

931-
// First check if the new content is unchanged compared to the old content. If
932-
// it is, we don't need parse the content or write any new objects to the
933-
// store. This is also a way of making sure that unchanged tree/file
934-
// conflicts (for example) are not converted to regular files in the working
935-
// copy.
936-
let mut old_content = Vec::with_capacity(content.len());
937-
let merge_hunk = extract_as_single_hunk(&simplified_file_ids, store, path).await?;
938-
let materialize_options = ConflictMaterializeOptions {
939-
marker_style: conflict_marker_style,
940-
marker_len: Some(conflict_marker_len),
941-
};
942-
materialize_merge_result(&merge_hunk, &mut old_content, &materialize_options).unwrap();
943-
if content == old_content {
944-
return Ok(file_ids.clone());
945-
}
946-
947930
// Parse conflicts from the new content using the arity of the simplified
948931
// conflicts.
949932
let Some(mut hunks) = parse_conflict(
@@ -960,6 +943,7 @@ pub async fn update_from_content(
960943
// check whether the original term ended with a newline. If it didn't, then
961944
// remove the newline since it was added automatically when materializing.
962945
if let Some(last_hunk) = hunks.last_mut().filter(|hunk| !hunk.is_resolved()) {
946+
let merge_hunk = extract_as_single_hunk(&simplified_file_ids, store, path).await?;
963947
for (original_content, term) in merge_hunk.iter().zip_eq(last_hunk.iter_mut()) {
964948
if term.last() == Some(&b'\n') && has_no_eol(original_content) {
965949
term.pop();

lib/src/local_working_copy.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1621,7 +1621,14 @@ impl FileSnapshotter<'_> {
16211621
message: "Failed to read the EOL converted contents".to_string(),
16221622
err: err.into(),
16231623
})?;
1624+
// Check if the new content is unchanged. This makes sure that
1625+
// unchanged conflicts are not updated.
1626+
let old_content_hash =
1627+
current_conflict_data.and_then(|data| data.content_hash.as_deref());
16241628
let new_content_hash = blake2b_hash(&contents);
1629+
if old_content_hash == Some(&*new_content_hash) {
1630+
return Ok((current_tree_values.clone(), current_conflict_data.cloned()));
1631+
}
16251632
// If the file contained a conflict before and is a normal file on
16261633
// disk, we try to parse any conflict markers in the file into a
16271634
// conflict.
@@ -1634,7 +1641,6 @@ impl FileSnapshotter<'_> {
16341641
self.store(),
16351642
repo_path,
16361643
&contents,
1637-
self.tree_state.conflict_marker_style,
16381644
conflict_marker_len as usize,
16391645
)
16401646
.await?;

0 commit comments

Comments
 (0)