Skip to content

Commit 6792815

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 7b7dd19 commit 6792815

File tree

9 files changed

+51
-156
lines changed

9 files changed

+51
-156
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/cli_util.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,14 +1383,12 @@ to the current parents may contain changes from multiple commits.
13831383
if max_new_file_size == 0 {
13841384
max_new_file_size = u64::MAX;
13851385
}
1386-
let conflict_marker_style = self.env.conflict_marker_style();
13871386
Ok(SnapshotOptions {
13881387
base_ignores,
13891388
fsmonitor_settings,
13901389
progress: None,
13911390
start_tracking_matcher,
13921391
max_new_file_size,
1393-
conflict_marker_style,
13941392
})
13951393
}
13961394

cli/src/merge_tools/builtin.rs

Lines changed: 5 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,6 @@ fn apply_diff_builtin(
382382
right_tree: &MergedTree,
383383
changed_files: Vec<RepoPathBuf>,
384384
files: &[scm_record::File],
385-
conflict_marker_style: ConflictMarkerStyle,
386385
) -> BackendResult<MergedTreeId> {
387386
// Start with the right tree to match external tool behavior.
388387
// This ensures unmatched paths keep their values from the right tree.
@@ -417,7 +416,6 @@ fn apply_diff_builtin(
417416
store,
418417
path,
419418
contents,
420-
conflict_marker_style,
421419
MIN_CONFLICT_MARKER_LEN, // TODO: use the materialization parameter
422420
)
423421
.block_on()?;
@@ -555,15 +553,8 @@ pub fn edit_diff_builtin(
555553
&mut input,
556554
);
557555
let result = recorder.run().map_err(BuiltinToolError::Record)?;
558-
let tree_id = apply_diff_builtin(
559-
&store,
560-
left_tree,
561-
right_tree,
562-
changed_files,
563-
&result.files,
564-
conflict_marker_style,
565-
)
566-
.map_err(BuiltinToolError::BackendError)?;
556+
let tree_id = apply_diff_builtin(&store, left_tree, right_tree, changed_files, &result.files)
557+
.map_err(BuiltinToolError::BackendError)?;
567558
Ok(tree_id)
568559
}
569560

@@ -767,15 +758,7 @@ mod tests {
767758
changed_files: &[RepoPathBuf],
768759
files: &[scm_record::File],
769760
) -> MergedTreeId {
770-
apply_diff_builtin(
771-
store,
772-
left_tree,
773-
right_tree,
774-
changed_files.to_vec(),
775-
files,
776-
ConflictMarkerStyle::Diff,
777-
)
778-
.unwrap()
761+
apply_diff_builtin(store, left_tree, right_tree, changed_files.to_vec(), files).unwrap()
779762
}
780763

781764
#[test]
@@ -1805,15 +1788,8 @@ mod tests {
18051788

18061789
assert_eq!(changed_files, vec![matched_path.to_owned()]);
18071790

1808-
let result_tree_id = apply_diff_builtin(
1809-
store,
1810-
&left_tree,
1811-
&right_tree,
1812-
changed_files,
1813-
&files,
1814-
ConflictMarkerStyle::Diff,
1815-
)
1816-
.unwrap();
1791+
let result_tree_id =
1792+
apply_diff_builtin(store, &left_tree, &right_tree, changed_files, &files).unwrap();
18171793
let result_tree = store.get_root_tree(&result_tree_id).unwrap();
18181794

18191795
assert_eq!(

cli/src/merge_tools/diff_working_copies.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ use std::sync::Arc;
88

99
use futures::StreamExt as _;
1010
use jj_lib::backend::MergedTreeId;
11-
use jj_lib::conflicts::ConflictMarkerStyle;
1211
use jj_lib::fsmonitor::FsmonitorSettings;
1312
use jj_lib::gitignore::GitIgnoreFile;
1413
use jj_lib::local_working_copy::TreeState;
@@ -274,7 +273,6 @@ diff editing in mind and be a little inaccurate.
274273
pub fn snapshot_results(
275274
self,
276275
base_ignores: Arc<GitIgnoreFile>,
277-
conflict_marker_style: ConflictMarkerStyle,
278276
) -> Result<MergedTreeId, DiffEditError> {
279277
if let Some(path) = self.instructions_path_to_cleanup {
280278
std::fs::remove_file(path).ok();
@@ -289,7 +287,6 @@ diff editing in mind and be a little inaccurate.
289287
progress: None,
290288
start_tracking_matcher: &EverythingMatcher,
291289
max_new_file_size: u64::MAX,
292-
conflict_marker_style,
293290
})?;
294291
Ok(output_tree_state.current_tree_id().clone())
295292
}

cli/src/merge_tools/external.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,6 @@ fn run_mergetool_external_single_file(
294294
store,
295295
repo_path,
296296
output_file_contents.as_slice(),
297-
conflict_marker_style,
298297
conflict_marker_len,
299298
)
300299
.block_on()?
@@ -425,7 +424,7 @@ pub fn edit_diff_external(
425424
}));
426425
}
427426

428-
diffedit_wc.snapshot_results(base_ignores, options.conflict_marker_style)
427+
diffedit_wc.snapshot_results(base_ignores)
429428
}
430429

431430
/// Generates textual diff by the specified `tool` and writes into `writer`.

lib/src/conflicts.rs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -954,29 +954,10 @@ pub async fn update_from_content(
954954
store: &Store,
955955
path: &RepoPath,
956956
content: &[u8],
957-
conflict_marker_style: ConflictMarkerStyle,
958957
conflict_marker_len: usize,
959958
) -> BackendResult<Merge<Option<FileId>>> {
960959
let simplified_file_ids = file_ids.simplify();
961960

962-
// First check if the new content is unchanged compared to the old content. If
963-
// it is, we don't need parse the content or write any new objects to the
964-
// store. This is also a way of making sure that unchanged tree/file
965-
// conflicts (for example) are not converted to regular files in the working
966-
// copy.
967-
let mut old_content = Vec::with_capacity(content.len());
968-
let merge_hunk = extract_as_single_hunk(&simplified_file_ids, store, path).await?;
969-
materialize_merge_result_with_marker_len(
970-
&merge_hunk,
971-
conflict_marker_style,
972-
conflict_marker_len,
973-
&mut old_content,
974-
)
975-
.unwrap();
976-
if content == old_content {
977-
return Ok(file_ids.clone());
978-
}
979-
980961
// Parse conflicts from the new content using the arity of the simplified
981962
// conflicts.
982963
let Some(mut hunks) = parse_conflict(
@@ -993,6 +974,7 @@ pub async fn update_from_content(
993974
// check whether the original term ended with a newline. If it didn't, then
994975
// remove the newline since it was added automatically when materializing.
995976
if let Some(last_hunk) = hunks.last_mut().filter(|hunk| !hunk.is_resolved()) {
977+
let merge_hunk = extract_as_single_hunk(&simplified_file_ids, store, path).await?;
996978
for (original_content, term) in merge_hunk.iter().zip_eq(last_hunk.iter_mut()) {
997979
if term.last() == Some(&b'\n') && has_no_eol(original_content) {
998980
term.pop();

lib/src/local_working_copy.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,7 +1037,6 @@ impl TreeState {
10371037
progress,
10381038
start_tracking_matcher,
10391039
max_new_file_size,
1040-
conflict_marker_style,
10411040
} = options;
10421041

10431042
let sparse_matcher = self.sparse_matcher();
@@ -1079,7 +1078,6 @@ impl TreeState {
10791078
error: OnceLock::new(),
10801079
progress,
10811080
max_new_file_size,
1082-
conflict_marker_style,
10831081
target_eol_strategy: self.target_eol_strategy.clone(),
10841082
};
10851083
let directory_to_visit = DirectoryToVisit {
@@ -1228,7 +1226,6 @@ struct FileSnapshotter<'a> {
12281226
error: OnceLock<SnapshotError>,
12291227
progress: Option<&'a SnapshotProgress<'a>>,
12301228
max_new_file_size: u64,
1231-
conflict_marker_style: ConflictMarkerStyle,
12321229
target_eol_strategy: TargetEolStrategy,
12331230
}
12341231

@@ -1619,7 +1616,14 @@ impl FileSnapshotter<'_> {
16191616
message: "Failed to read the EOL converted contents".to_string(),
16201617
err: err.into(),
16211618
})?;
1619+
// Check if the new content is unchanged. This makes sure that
1620+
// unchanged conflicts are not updated.
1621+
let old_content_hash =
1622+
current_conflict_data.and_then(|data| data.content_hash.as_deref());
16221623
let new_content_hash = blake2b_hash(&contents);
1624+
if old_content_hash == Some(&*new_content_hash) {
1625+
return Ok((current_tree_values.clone(), current_conflict_data.cloned()));
1626+
}
16231627
// If the file contained a conflict before and is a normal file on
16241628
// disk, we try to parse any conflict markers in the file into a
16251629
// conflict.
@@ -1632,7 +1636,6 @@ impl FileSnapshotter<'_> {
16321636
self.store(),
16331637
repo_path,
16341638
&contents,
1635-
self.conflict_marker_style,
16361639
conflict_marker_len as usize,
16371640
)
16381641
.await?;

lib/src/working_copy.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -223,8 +223,6 @@ pub struct SnapshotOptions<'a> {
223223
/// (depending on implementation)
224224
/// return `SnapshotError::NewFileTooLarge`.
225225
pub max_new_file_size: u64,
226-
/// Expected conflict marker style for checking for changed files.
227-
pub conflict_marker_style: ConflictMarkerStyle,
228226
}
229227

230228
impl SnapshotOptions<'_> {
@@ -236,7 +234,6 @@ impl SnapshotOptions<'_> {
236234
progress: None,
237235
start_tracking_matcher: &EverythingMatcher,
238236
max_new_file_size: u64::MAX,
239-
conflict_marker_style: ConflictMarkerStyle::default(),
240237
}
241238
}
242239
}

0 commit comments

Comments
 (0)