Skip to content

Commit

Permalink
files: replace uses of MergeHunk by Conflict<ContentHunk>
Browse files Browse the repository at this point in the history
Since `Conflict`s can represent the resolved state, so
`Conflict<ContentHunk>` can represent the states that we use
`MergeHunk` for. `MergeHunk` does force the user to handle the
resolved case, which may be useful. I suppose one could use the same
argument for making `Conflict` an enum, i.e. if we think that
`MergeHunk`'s two variants are beneficial, then we should consider
making `Conflict` an enum with those two variants.
  • Loading branch information
martinvonz committed Jun 28, 2023
1 parent c625e93 commit 779b8ba
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 183 deletions.
161 changes: 78 additions & 83 deletions lib/src/conflicts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use itertools::Itertools;

use crate::backend::{BackendResult, FileId, ObjectId, TreeValue};
use crate::diff::{find_line_ranges, Diff, DiffHunk};
use crate::files::{ContentHunk, MergeHunk, MergeResult};
use crate::files::{ContentHunk, MergeResult};
use crate::merge::trivial_merge;
use crate::repo_path::RepoPath;
use crate::store::Store;
Expand Down Expand Up @@ -48,6 +48,10 @@ impl<T> Conflict<T> {
Conflict { removes, adds }
}

pub fn resolved(value: T) -> Self {
Conflict::new(vec![], vec![value])
}

/// Create a `Conflict` from a `removes` and `adds`, padding with `None` to
/// make sure that there is exactly one more `adds` than `removes`.
pub fn from_legacy_form(removes: Vec<T>, adds: Vec<T>) -> Conflict<Option<T>> {
Expand Down Expand Up @@ -242,23 +246,20 @@ impl Conflict<Option<TreeValue>> {
return Ok(None);
};
for hunk in hunks {
match hunk {
MergeHunk::Resolved(slice) => {
for buf in &mut removed_content {
buf.extend_from_slice(&slice.0);
}
for buf in &mut added_content {
buf.extend_from_slice(&slice.0);
}
if let Some(slice) = hunk.as_resolved() {
for buf in &mut removed_content {
buf.extend_from_slice(&slice.0);
}
MergeHunk::Conflict(conflict) => {
let (removes, adds) = conflict.take();
for (i, buf) in removes.into_iter().enumerate() {
removed_content[i].extend(buf.0);
}
for (i, buf) in adds.into_iter().enumerate() {
added_content[i].extend(buf.0);
}
for buf in &mut added_content {
buf.extend_from_slice(&slice.0);
}
} else {
let (removes, adds) = hunk.take();
for (i, buf) in removes.into_iter().enumerate() {
removed_content[i].extend(buf.0);
}
for (i, buf) in adds.into_iter().enumerate() {
added_content[i].extend(buf.0);
}
}
}
Expand Down Expand Up @@ -423,60 +424,56 @@ pub fn materialize_merge_result(
}
MergeResult::Conflict(hunks) => {
for hunk in hunks {
match hunk {
MergeHunk::Resolved(content) => {
output.write_all(&content.0)?;
}
MergeHunk::Conflict(conflict) => {
output.write_all(CONFLICT_START_LINE)?;
let mut add_index = 0;
for left in conflict.removes() {
let right1 = if let Some(right1) = conflict.adds().get(add_index) {
right1
} else {
// If we have no more positive terms, emit the remaining negative
// terms as snapshots.
output.write_all(CONFLICT_MINUS_LINE)?;
output.write_all(&left.0)?;
continue;
};
let diff1 =
Diff::for_tokenizer(&[&left.0, &right1.0], &find_line_ranges)
if let Some(content) = hunk.as_resolved() {
output.write_all(&content.0)?;
} else {
output.write_all(CONFLICT_START_LINE)?;
let mut add_index = 0;
for left in hunk.removes() {
let right1 = if let Some(right1) = hunk.adds().get(add_index) {
right1
} else {
// If we have no more positive terms, emit the remaining negative
// terms as snapshots.
output.write_all(CONFLICT_MINUS_LINE)?;
output.write_all(&left.0)?;
continue;
};
let diff1 = Diff::for_tokenizer(&[&left.0, &right1.0], &find_line_ranges)
.hunks()
.collect_vec();
// Check if the diff against the next positive term is better. Since
// we want to preserve the order of the terms, we don't match against
// any later positive terms.
if let Some(right2) = hunk.adds().get(add_index + 1) {
let diff2 =
Diff::for_tokenizer(&[&left.0, &right2.0], &find_line_ranges)
.hunks()
.collect_vec();
// Check if the diff against the next positive term is better. Since
// we want to preserve the order of the terms, we don't match against
// any later positive terms.
if let Some(right2) = conflict.adds().get(add_index + 1) {
let diff2 =
Diff::for_tokenizer(&[&left.0, &right2.0], &find_line_ranges)
.hunks()
.collect_vec();
if diff_size(&diff2) < diff_size(&diff1) {
// If the next positive term is a better match, emit
// the current positive term as a snapshot and the next
// positive term as a diff.
output.write_all(CONFLICT_PLUS_LINE)?;
output.write_all(&right1.0)?;
output.write_all(CONFLICT_DIFF_LINE)?;
write_diff_hunks(&diff2, output)?;
add_index += 2;
continue;
}
if diff_size(&diff2) < diff_size(&diff1) {
// If the next positive term is a better match, emit
// the current positive term as a snapshot and the next
// positive term as a diff.
output.write_all(CONFLICT_PLUS_LINE)?;
output.write_all(&right1.0)?;
output.write_all(CONFLICT_DIFF_LINE)?;
write_diff_hunks(&diff2, output)?;
add_index += 2;
continue;
}

output.write_all(CONFLICT_DIFF_LINE)?;
write_diff_hunks(&diff1, output)?;
add_index += 1;
}

// Emit the remaining positive terms as snapshots.
for slice in &conflict.adds()[add_index..] {
output.write_all(CONFLICT_PLUS_LINE)?;
output.write_all(&slice.0)?;
}
output.write_all(CONFLICT_END_LINE)?;
output.write_all(CONFLICT_DIFF_LINE)?;
write_diff_hunks(&diff1, output)?;
add_index += 1;
}

// Emit the remaining positive terms as snapshots.
for slice in &hunk.adds()[add_index..] {
output.write_all(CONFLICT_PLUS_LINE)?;
output.write_all(&slice.0)?;
}
output.write_all(CONFLICT_END_LINE)?;
}
}
}
Expand All @@ -500,7 +497,11 @@ fn diff_size(hunks: &[DiffHunk]) -> usize {
/// will be considered invalid if they don't have the expected arity.
// TODO: "parse" is not usually the opposite of "materialize", so maybe we
// should rename them to "serialize" and "deserialize"?
pub fn parse_conflict(input: &[u8], num_removes: usize, num_adds: usize) -> Option<Vec<MergeHunk>> {
pub fn parse_conflict(
input: &[u8],
num_removes: usize,
num_adds: usize,
) -> Option<Vec<Conflict<ContentHunk>>> {
if input.is_empty() {
return None;
}
Expand All @@ -514,19 +515,13 @@ pub fn parse_conflict(input: &[u8], num_removes: usize, num_adds: usize) -> Opti
} else if conflict_start.is_some() && line == CONFLICT_END_LINE {
let conflict_body = &input[conflict_start.unwrap() + CONFLICT_START_LINE.len()..pos];
let hunk = parse_conflict_hunk(conflict_body);
match &hunk {
MergeHunk::Conflict(conflict)
if conflict.removes().len() == num_removes
&& conflict.adds().len() == num_adds =>
{
let resolved_slice = &input[resolved_start..conflict_start.unwrap()];
if !resolved_slice.is_empty() {
hunks.push(MergeHunk::Resolved(ContentHunk(resolved_slice.to_vec())));
}
hunks.push(hunk);
resolved_start = pos + line.len();
if hunk.removes().len() == num_removes && hunk.adds().len() == num_adds {
let resolved_slice = &input[resolved_start..conflict_start.unwrap()];
if !resolved_slice.is_empty() {
hunks.push(Conflict::resolved(ContentHunk(resolved_slice.to_vec())));
}
_ => {}
hunks.push(hunk);
resolved_start = pos + line.len();
}
conflict_start = None;
}
Expand All @@ -537,15 +532,15 @@ pub fn parse_conflict(input: &[u8], num_removes: usize, num_adds: usize) -> Opti
None
} else {
if resolved_start < input.len() {
hunks.push(MergeHunk::Resolved(ContentHunk(
hunks.push(Conflict::resolved(ContentHunk(
input[resolved_start..].to_vec(),
)));
}
Some(hunks)
}
}

fn parse_conflict_hunk(input: &[u8]) -> MergeHunk {
fn parse_conflict_hunk(input: &[u8]) -> Conflict<ContentHunk> {
enum State {
Diff,
Minus,
Expand Down Expand Up @@ -586,7 +581,7 @@ fn parse_conflict_hunk(input: &[u8]) -> MergeHunk {
adds.last_mut().unwrap().0.extend_from_slice(rest);
} else {
// Doesn't look like a conflict
return MergeHunk::Resolved(ContentHunk(vec![]));
return Conflict::resolved(ContentHunk(vec![]));
}
}
State::Minus => {
Expand All @@ -597,12 +592,12 @@ fn parse_conflict_hunk(input: &[u8]) -> MergeHunk {
}
State::Unknown => {
// Doesn't look like a conflict
return MergeHunk::Resolved(ContentHunk(vec![]));
return Conflict::resolved(ContentHunk(vec![]));
}
}
}

MergeHunk::Conflict(Conflict::new(removes, adds))
Conflict::new(removes, adds)
}

#[cfg(test)]
Expand Down
Loading

0 comments on commit 779b8ba

Please sign in to comment.