jj_lib/
files.rs

1// Copyright 2020 The Jujutsu Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#![allow(missing_docs)]
16
17use std::borrow::Borrow;
18use std::collections::VecDeque;
19use std::iter;
20use std::mem;
21
22use bstr::BStr;
23use bstr::BString;
24use itertools::Itertools as _;
25
26use crate::diff::Diff;
27use crate::diff::DiffHunk;
28use crate::diff::DiffHunkKind;
29use crate::merge::Merge;
30
31/// A diff line which may contain small hunks originating from both sides.
32#[derive(PartialEq, Eq, Clone, Debug)]
33pub struct DiffLine<'a> {
34    pub line_number: DiffLineNumber,
35    pub hunks: Vec<(DiffLineHunkSide, &'a BStr)>,
36}
37
38impl DiffLine<'_> {
39    pub fn has_left_content(&self) -> bool {
40        self.hunks
41            .iter()
42            .any(|&(side, _)| side != DiffLineHunkSide::Right)
43    }
44
45    pub fn has_right_content(&self) -> bool {
46        self.hunks
47            .iter()
48            .any(|&(side, _)| side != DiffLineHunkSide::Left)
49    }
50
51    pub fn is_unmodified(&self) -> bool {
52        self.hunks
53            .iter()
54            .all(|&(side, _)| side == DiffLineHunkSide::Both)
55    }
56
57    fn take(&mut self) -> Self {
58        DiffLine {
59            line_number: self.line_number,
60            hunks: mem::take(&mut self.hunks),
61        }
62    }
63}
64
65#[derive(Clone, Copy, Debug, Eq, PartialEq)]
66pub struct DiffLineNumber {
67    pub left: u32,
68    pub right: u32,
69}
70
71/// Which side a `DiffLine` hunk belongs to?
72#[derive(Clone, Copy, Debug, Eq, PartialEq)]
73pub enum DiffLineHunkSide {
74    Both,
75    Left,
76    Right,
77}
78
79pub struct DiffLineIterator<'a, I> {
80    diff_hunks: iter::Fuse<I>,
81    current_line: DiffLine<'a>,
82    queued_lines: VecDeque<DiffLine<'a>>,
83}
84
85impl<'a, I> DiffLineIterator<'a, I>
86where
87    I: Iterator,
88    I::Item: Borrow<DiffHunk<'a>>,
89{
90    /// Iterates `diff_hunks` by line. Each hunk should have exactly two inputs.
91    pub fn new(diff_hunks: I) -> Self {
92        let line_number = DiffLineNumber { left: 1, right: 1 };
93        Self::with_line_number(diff_hunks, line_number)
94    }
95
96    /// Iterates `diff_hunks` by line. Each hunk should have exactly two inputs.
97    /// Hunk's line numbers start from the given `line_number`.
98    pub fn with_line_number(diff_hunks: I, line_number: DiffLineNumber) -> Self {
99        let current_line = DiffLine {
100            line_number,
101            hunks: vec![],
102        };
103        DiffLineIterator {
104            diff_hunks: diff_hunks.fuse(),
105            current_line,
106            queued_lines: VecDeque::new(),
107        }
108    }
109}
110
111impl<I> DiffLineIterator<'_, I> {
112    /// Returns line number of the next hunk. After all hunks are consumed, this
113    /// returns the next line number if the last hunk ends with newline.
114    pub fn next_line_number(&self) -> DiffLineNumber {
115        let next_line = self.queued_lines.front().unwrap_or(&self.current_line);
116        next_line.line_number
117    }
118}
119
120impl<'a, I> Iterator for DiffLineIterator<'a, I>
121where
122    I: Iterator,
123    I::Item: Borrow<DiffHunk<'a>>,
124{
125    type Item = DiffLine<'a>;
126
127    fn next(&mut self) -> Option<Self::Item> {
128        // TODO: Should we attempt to interpret as utf-8 and otherwise break only at
129        // newlines?
130        while self.queued_lines.is_empty() {
131            let Some(hunk) = self.diff_hunks.next() else {
132                break;
133            };
134            let hunk = hunk.borrow();
135            match hunk.kind {
136                DiffHunkKind::Matching => {
137                    // TODO: add support for unmatched contexts?
138                    debug_assert!(hunk.contents.iter().all_equal());
139                    let text = hunk.contents[0];
140                    let lines = text.split_inclusive(|b| *b == b'\n').map(BStr::new);
141                    for line in lines {
142                        self.current_line.hunks.push((DiffLineHunkSide::Both, line));
143                        if line.ends_with(b"\n") {
144                            self.queued_lines.push_back(self.current_line.take());
145                            self.current_line.line_number.left += 1;
146                            self.current_line.line_number.right += 1;
147                        }
148                    }
149                }
150                DiffHunkKind::Different => {
151                    let [left_text, right_text] = hunk.contents[..]
152                        .try_into()
153                        .expect("hunk should have exactly two inputs");
154                    let left_lines = left_text.split_inclusive(|b| *b == b'\n').map(BStr::new);
155                    for left_line in left_lines {
156                        self.current_line
157                            .hunks
158                            .push((DiffLineHunkSide::Left, left_line));
159                        if left_line.ends_with(b"\n") {
160                            self.queued_lines.push_back(self.current_line.take());
161                            self.current_line.line_number.left += 1;
162                        }
163                    }
164                    let mut right_lines =
165                        right_text.split_inclusive(|b| *b == b'\n').map(BStr::new);
166                    // Omit blank right line if matching hunk of the same line
167                    // number has already been queued. Here we only need to
168                    // check the first queued line since the other lines should
169                    // be created in the left_lines loop above.
170                    if right_text.starts_with(b"\n")
171                        && self.current_line.hunks.is_empty()
172                        && self
173                            .queued_lines
174                            .front()
175                            .is_some_and(|queued| queued.has_right_content())
176                    {
177                        let blank_line = right_lines.next().unwrap();
178                        assert_eq!(blank_line, b"\n");
179                        self.current_line.line_number.right += 1;
180                    }
181                    for right_line in right_lines {
182                        self.current_line
183                            .hunks
184                            .push((DiffLineHunkSide::Right, right_line));
185                        if right_line.ends_with(b"\n") {
186                            self.queued_lines.push_back(self.current_line.take());
187                            self.current_line.line_number.right += 1;
188                        }
189                    }
190                }
191            }
192        }
193
194        if let Some(line) = self.queued_lines.pop_front() {
195            return Some(line);
196        }
197
198        if !self.current_line.hunks.is_empty() {
199            return Some(self.current_line.take());
200        }
201
202        None
203    }
204}
205
206/// Diff hunk that may be unresolved conflicts.
207#[derive(Clone, Debug, Eq, PartialEq)]
208pub struct ConflictDiffHunk<'input> {
209    pub kind: DiffHunkKind,
210    pub lefts: Merge<&'input BStr>,
211    pub rights: Merge<&'input BStr>,
212}
213
214/// Iterator adaptor that translates non-conflict hunks to resolved `Merge`.
215///
216/// Trivial conflicts in the diff inputs should have been resolved by caller.
217pub fn conflict_diff_hunks<'input, I>(
218    diff_hunks: I,
219    num_lefts: usize,
220) -> impl Iterator<Item = ConflictDiffHunk<'input>>
221where
222    I: IntoIterator,
223    I::Item: Borrow<DiffHunk<'input>>,
224{
225    fn to_merge<'input>(contents: &[&'input BStr]) -> Merge<&'input BStr> {
226        // Not using trivial_merge() so that the original content can be
227        // reproduced by concatenating hunks.
228        if contents.iter().all_equal() {
229            Merge::resolved(contents[0])
230        } else {
231            Merge::from_vec(contents)
232        }
233    }
234
235    diff_hunks.into_iter().map(move |hunk| {
236        let hunk = hunk.borrow();
237        let (lefts, rights) = hunk.contents.split_at(num_lefts);
238        if let ([left], [right]) = (lefts, rights) {
239            // Non-conflicting diff shouldn't have identical contents
240            ConflictDiffHunk {
241                kind: hunk.kind,
242                lefts: Merge::resolved(left),
243                rights: Merge::resolved(right),
244            }
245        } else {
246            let lefts = to_merge(lefts);
247            let rights = to_merge(rights);
248            let kind = match hunk.kind {
249                DiffHunkKind::Matching => DiffHunkKind::Matching,
250                DiffHunkKind::Different if lefts == rights => DiffHunkKind::Matching,
251                DiffHunkKind::Different => DiffHunkKind::Different,
252            };
253            ConflictDiffHunk {
254                kind,
255                lefts,
256                rights,
257            }
258        }
259    })
260}
261
262/// Merge result in either fully-resolved or conflicts form, akin to
263/// `Result<BString, Vec<Merge<BString>>>`.
264#[derive(PartialEq, Eq, Clone, Debug)]
265pub enum MergeResult {
266    /// Resolved content if inputs can be merged successfully.
267    Resolved(BString),
268    /// List of partially-resolved hunks if some of them cannot be merged.
269    Conflict(Vec<Merge<BString>>),
270}
271
272/// Splits `inputs` into hunks, resolves trivial merge conflicts for each.
273///
274/// Returns either fully-resolved content or list of partially-resolved hunks.
275pub fn merge_hunks<T: AsRef<[u8]>>(inputs: &Merge<T>) -> MergeResult {
276    merge_inner(inputs)
277}
278
279/// Splits `inputs` into hunks, resolves trivial merge conflicts for each, then
280/// concatenates the outcome back to single `Merge` object.
281///
282/// The returned merge object is either fully resolved or conflict having the
283/// same number of terms as the `inputs`.
284pub fn merge<T: AsRef<[u8]>>(inputs: &Merge<T>) -> Merge<BString> {
285    merge_inner(inputs)
286}
287
288/// Splits `inputs` into hunks, attempts to resolve trivial merge conflicts for
289/// each.
290///
291/// If all input hunks can be merged successfully, returns the merged content.
292pub fn try_merge<T: AsRef<[u8]>>(inputs: &Merge<T>) -> Option<BString> {
293    merge_inner(inputs)
294}
295
296fn merge_inner<'input, T: AsRef<[u8]>, B: FromMergeHunks<'input>>(inputs: &'input Merge<T>) -> B {
297    // TODO: Using the first remove as base (first in the inputs) is how it's
298    // usually done for 3-way conflicts. Are there better heuristics when there are
299    // more than 3 parts?
300    let num_diffs = inputs.removes().len();
301    let diff = Diff::by_line(inputs.removes().chain(inputs.adds()));
302    let hunks = resolve_diff_hunks(&diff, num_diffs);
303    B::from_hunks(hunks)
304}
305
306/// `FromIterator` for merge result.
307trait FromMergeHunks<'input>: Sized {
308    fn from_hunks<I: IntoIterator<Item = Merge<&'input BStr>>>(hunks: I) -> Self;
309}
310
311impl<'input> FromMergeHunks<'input> for MergeResult {
312    fn from_hunks<I: IntoIterator<Item = Merge<&'input BStr>>>(hunks: I) -> Self {
313        collect_hunks(hunks)
314    }
315}
316
317impl<'input> FromMergeHunks<'input> for Merge<BString> {
318    fn from_hunks<I: IntoIterator<Item = Merge<&'input BStr>>>(hunks: I) -> Self {
319        collect_merged(hunks)
320    }
321}
322
323impl<'input> FromMergeHunks<'input> for Option<BString> {
324    fn from_hunks<I: IntoIterator<Item = Merge<&'input BStr>>>(hunks: I) -> Self {
325        collect_resolved(hunks)
326    }
327}
328
329/// Collects merged hunks into either fully-resolved content or list of
330/// partially-resolved hunks.
331fn collect_hunks<'input>(hunks: impl IntoIterator<Item = Merge<&'input BStr>>) -> MergeResult {
332    let mut resolved_hunk = BString::new(vec![]);
333    let mut merge_hunks: Vec<Merge<BString>> = vec![];
334    for hunk in hunks {
335        if let Some(&content) = hunk.as_resolved() {
336            resolved_hunk.extend_from_slice(content);
337        } else {
338            if !resolved_hunk.is_empty() {
339                merge_hunks.push(Merge::resolved(resolved_hunk));
340                resolved_hunk = BString::new(vec![]);
341            }
342            merge_hunks.push(hunk.map(|&s| s.to_owned()));
343        }
344    }
345
346    if merge_hunks.is_empty() {
347        MergeResult::Resolved(resolved_hunk)
348    } else {
349        if !resolved_hunk.is_empty() {
350            merge_hunks.push(Merge::resolved(resolved_hunk));
351        }
352        MergeResult::Conflict(merge_hunks)
353    }
354}
355
356/// Collects merged hunks back to single `Merge` object, duplicating resolved
357/// hunks to all positive and negative terms.
358fn collect_merged<'input>(hunks: impl IntoIterator<Item = Merge<&'input BStr>>) -> Merge<BString> {
359    let mut maybe_resolved = Merge::resolved(BString::default());
360    for hunk in hunks {
361        if let Some(&content) = hunk.as_resolved() {
362            for buf in maybe_resolved.iter_mut() {
363                buf.extend_from_slice(content);
364            }
365        } else {
366            maybe_resolved = match maybe_resolved.into_resolved() {
367                Ok(content) => Merge::from_vec(vec![content; hunk.as_slice().len()]),
368                Err(conflict) => conflict,
369            };
370            assert_eq!(maybe_resolved.as_slice().len(), hunk.as_slice().len());
371            for (buf, s) in iter::zip(maybe_resolved.iter_mut(), hunk) {
372                buf.extend_from_slice(s);
373            }
374        }
375    }
376    maybe_resolved
377}
378
379/// Collects resolved merge hunks. Short-circuits on unresolved hunk.
380fn collect_resolved<'input>(
381    hunks: impl IntoIterator<Item = Merge<&'input BStr>>,
382) -> Option<BString> {
383    hunks
384        .into_iter()
385        .map(|hunk| hunk.into_resolved().ok())
386        .collect()
387}
388
389/// Iterator that attempts to resolve trivial merge conflict for each hunk.
390fn resolve_diff_hunks<'a, 'input>(
391    diff: &'a Diff<'input>,
392    num_diffs: usize,
393) -> impl Iterator<Item = Merge<&'input BStr>> + use<'a, 'input> {
394    diff.hunks().map(move |diff_hunk| match diff_hunk.kind {
395        DiffHunkKind::Matching => {
396            debug_assert!(diff_hunk.contents.iter().all_equal());
397            Merge::resolved(diff_hunk.contents[0])
398        }
399        DiffHunkKind::Different => {
400            let merge = Merge::from_removes_adds(
401                diff_hunk.contents[..num_diffs].iter().copied(),
402                diff_hunk.contents[num_diffs..].iter().copied(),
403            );
404            match merge.resolve_trivial() {
405                Some(&content) => Merge::resolved(content),
406                None => merge,
407            }
408        }
409    })
410}
411
412#[cfg(test)]
413mod tests {
414    use indoc::indoc;
415
416    use super::*;
417
418    fn conflict<const N: usize>(values: [&[u8]; N]) -> Merge<BString> {
419        Merge::from_vec(values.map(hunk).to_vec())
420    }
421
422    fn resolved(value: &[u8]) -> Merge<BString> {
423        Merge::resolved(hunk(value))
424    }
425
426    fn hunk(data: &[u8]) -> BString {
427        data.into()
428    }
429
430    #[test]
431    fn test_diff_line_iterator_line_numbers() {
432        let mut line_iter = DiffLineIterator::with_line_number(
433            [DiffHunk::different(["a\nb", "c\nd\n"])].into_iter(),
434            DiffLineNumber { left: 1, right: 10 },
435        );
436        // Nothing queued
437        assert_eq!(
438            line_iter.next_line_number(),
439            DiffLineNumber { left: 1, right: 10 }
440        );
441        assert_eq!(
442            line_iter.next().unwrap(),
443            DiffLine {
444                line_number: DiffLineNumber { left: 1, right: 10 },
445                hunks: vec![(DiffLineHunkSide::Left, "a\n".as_ref())],
446            }
447        );
448        // Multiple lines queued
449        assert_eq!(
450            line_iter.next_line_number(),
451            DiffLineNumber { left: 2, right: 10 }
452        );
453        assert_eq!(
454            line_iter.next().unwrap(),
455            DiffLine {
456                line_number: DiffLineNumber { left: 2, right: 10 },
457                hunks: vec![
458                    (DiffLineHunkSide::Left, "b".as_ref()),
459                    (DiffLineHunkSide::Right, "c\n".as_ref()),
460                ],
461            }
462        );
463        // Single line queued
464        assert_eq!(
465            line_iter.next_line_number(),
466            DiffLineNumber { left: 2, right: 11 }
467        );
468        assert_eq!(
469            line_iter.next().unwrap(),
470            DiffLine {
471                line_number: DiffLineNumber { left: 2, right: 11 },
472                hunks: vec![(DiffLineHunkSide::Right, "d\n".as_ref())],
473            }
474        );
475        // No more lines: left remains 2 as it lacks newline
476        assert_eq!(
477            line_iter.next_line_number(),
478            DiffLineNumber { left: 2, right: 12 }
479        );
480        assert!(line_iter.next().is_none());
481        assert_eq!(
482            line_iter.next_line_number(),
483            DiffLineNumber { left: 2, right: 12 }
484        );
485    }
486
487    #[test]
488    fn test_diff_line_iterator_blank_right_line_single_left() {
489        let mut line_iter = DiffLineIterator::new(
490            [
491                DiffHunk::matching(["a"].repeat(2)),
492                DiffHunk::different(["x\n", "\ny\n"]),
493            ]
494            .into_iter(),
495        );
496        assert_eq!(
497            line_iter.next().unwrap(),
498            DiffLine {
499                line_number: DiffLineNumber { left: 1, right: 1 },
500                hunks: vec![
501                    (DiffLineHunkSide::Both, "a".as_ref()),
502                    (DiffLineHunkSide::Left, "x\n".as_ref()),
503                ],
504            }
505        );
506        // "\n" (line_number.right = 1) can be omitted because the previous diff
507        // line has a right content.
508        assert_eq!(
509            line_iter.next().unwrap(),
510            DiffLine {
511                line_number: DiffLineNumber { left: 2, right: 2 },
512                hunks: vec![(DiffLineHunkSide::Right, "y\n".as_ref())],
513            }
514        );
515    }
516
517    #[test]
518    fn test_diff_line_iterator_blank_right_line_multiple_lefts() {
519        let mut line_iter = DiffLineIterator::new(
520            [
521                DiffHunk::matching(["a"].repeat(2)),
522                DiffHunk::different(["x\n\n", "\ny\n"]),
523            ]
524            .into_iter(),
525        );
526        assert_eq!(
527            line_iter.next().unwrap(),
528            DiffLine {
529                line_number: DiffLineNumber { left: 1, right: 1 },
530                hunks: vec![
531                    (DiffLineHunkSide::Both, "a".as_ref()),
532                    (DiffLineHunkSide::Left, "x\n".as_ref()),
533                ],
534            }
535        );
536        assert_eq!(
537            line_iter.next().unwrap(),
538            DiffLine {
539                line_number: DiffLineNumber { left: 2, right: 1 },
540                hunks: vec![(DiffLineHunkSide::Left, "\n".as_ref())],
541            }
542        );
543        // "\n" (line_number.right = 1) can still be omitted because one of the
544        // preceding diff line has a right content.
545        assert_eq!(
546            line_iter.next().unwrap(),
547            DiffLine {
548                line_number: DiffLineNumber { left: 3, right: 2 },
549                hunks: vec![(DiffLineHunkSide::Right, "y\n".as_ref())],
550            }
551        );
552    }
553
554    #[test]
555    fn test_diff_line_iterator_blank_right_line_after_non_empty_left() {
556        let mut line_iter = DiffLineIterator::new(
557            [
558                DiffHunk::matching(["a"].repeat(2)),
559                DiffHunk::different(["x\nz", "\ny\n"]),
560            ]
561            .into_iter(),
562        );
563        assert_eq!(
564            line_iter.next().unwrap(),
565            DiffLine {
566                line_number: DiffLineNumber { left: 1, right: 1 },
567                hunks: vec![
568                    (DiffLineHunkSide::Both, "a".as_ref()),
569                    (DiffLineHunkSide::Left, "x\n".as_ref()),
570                ],
571            }
572        );
573        assert_eq!(
574            line_iter.next().unwrap(),
575            DiffLine {
576                line_number: DiffLineNumber { left: 2, right: 1 },
577                hunks: vec![
578                    (DiffLineHunkSide::Left, "z".as_ref()),
579                    (DiffLineHunkSide::Right, "\n".as_ref()),
580                ],
581            }
582        );
583        assert_eq!(
584            line_iter.next().unwrap(),
585            DiffLine {
586                line_number: DiffLineNumber { left: 2, right: 2 },
587                hunks: vec![(DiffLineHunkSide::Right, "y\n".as_ref())],
588            }
589        );
590    }
591
592    #[test]
593    fn test_diff_line_iterator_blank_right_line_without_preceding_lines() {
594        let mut line_iter = DiffLineIterator::new([DiffHunk::different(["", "\ny\n"])].into_iter());
595        assert_eq!(
596            line_iter.next().unwrap(),
597            DiffLine {
598                line_number: DiffLineNumber { left: 1, right: 1 },
599                hunks: vec![(DiffLineHunkSide::Right, "\n".as_ref())],
600            }
601        );
602        assert_eq!(
603            line_iter.next().unwrap(),
604            DiffLine {
605                line_number: DiffLineNumber { left: 1, right: 2 },
606                hunks: vec![(DiffLineHunkSide::Right, "y\n".as_ref())],
607            }
608        );
609    }
610
611    #[test]
612    fn test_conflict_diff_hunks_no_conflicts() {
613        let diff_hunks = [
614            DiffHunk::matching(["a\n"].repeat(2)),
615            DiffHunk::different(["b\n", "c\n"]),
616        ];
617        let num_lefts = 1;
618        insta::assert_debug_snapshot!(
619            conflict_diff_hunks(&diff_hunks, num_lefts).collect_vec(), @r#"
620        [
621            ConflictDiffHunk {
622                kind: Matching,
623                lefts: Resolved(
624                    "a\n",
625                ),
626                rights: Resolved(
627                    "a\n",
628                ),
629            },
630            ConflictDiffHunk {
631                kind: Different,
632                lefts: Resolved(
633                    "b\n",
634                ),
635                rights: Resolved(
636                    "c\n",
637                ),
638            },
639        ]
640        "#);
641    }
642
643    #[test]
644    fn test_conflict_diff_hunks_simple_conflicts() {
645        let diff_hunks = [
646            // conflict hunk
647            DiffHunk::different(["a\n", "X\n", "b\n", "c\n"]),
648            DiffHunk::matching(["d\n"].repeat(4)),
649            // non-conflict hunk
650            DiffHunk::different(["e\n", "e\n", "e\n", "f\n"]),
651        ];
652        let num_lefts = 3;
653        insta::assert_debug_snapshot!(
654            conflict_diff_hunks(&diff_hunks, num_lefts).collect_vec(), @r#"
655        [
656            ConflictDiffHunk {
657                kind: Different,
658                lefts: Conflicted(
659                    [
660                        "a\n",
661                        "X\n",
662                        "b\n",
663                    ],
664                ),
665                rights: Resolved(
666                    "c\n",
667                ),
668            },
669            ConflictDiffHunk {
670                kind: Matching,
671                lefts: Resolved(
672                    "d\n",
673                ),
674                rights: Resolved(
675                    "d\n",
676                ),
677            },
678            ConflictDiffHunk {
679                kind: Different,
680                lefts: Resolved(
681                    "e\n",
682                ),
683                rights: Resolved(
684                    "f\n",
685                ),
686            },
687        ]
688        "#);
689    }
690
691    #[test]
692    fn test_conflict_diff_hunks_matching_conflicts() {
693        let diff_hunks = [
694            // matching conflict hunk
695            DiffHunk::different(["a\n", "X\n", "b\n", "a\n", "X\n", "b\n"]),
696            DiffHunk::matching(["c\n"].repeat(6)),
697        ];
698        let num_lefts = 3;
699        insta::assert_debug_snapshot!(
700            conflict_diff_hunks(&diff_hunks, num_lefts).collect_vec(), @r#"
701        [
702            ConflictDiffHunk {
703                kind: Matching,
704                lefts: Conflicted(
705                    [
706                        "a\n",
707                        "X\n",
708                        "b\n",
709                    ],
710                ),
711                rights: Conflicted(
712                    [
713                        "a\n",
714                        "X\n",
715                        "b\n",
716                    ],
717                ),
718            },
719            ConflictDiffHunk {
720                kind: Matching,
721                lefts: Resolved(
722                    "c\n",
723                ),
724                rights: Resolved(
725                    "c\n",
726                ),
727            },
728        ]
729        "#);
730    }
731
732    #[test]
733    fn test_conflict_diff_hunks_no_trivial_resolution() {
734        let diff_hunks = [DiffHunk::different(["a", "b", "a", "a"])];
735        let num_lefts = 1;
736        insta::assert_debug_snapshot!(
737            conflict_diff_hunks(&diff_hunks, num_lefts).collect_vec(), @r#"
738        [
739            ConflictDiffHunk {
740                kind: Different,
741                lefts: Resolved(
742                    "a",
743                ),
744                rights: Conflicted(
745                    [
746                        "b",
747                        "a",
748                        "a",
749                    ],
750                ),
751            },
752        ]
753        "#);
754        let num_lefts = 3;
755        insta::assert_debug_snapshot!(
756            conflict_diff_hunks(&diff_hunks, num_lefts).collect_vec(), @r#"
757        [
758            ConflictDiffHunk {
759                kind: Different,
760                lefts: Conflicted(
761                    [
762                        "a",
763                        "b",
764                        "a",
765                    ],
766                ),
767                rights: Resolved(
768                    "a",
769                ),
770            },
771        ]
772        "#);
773    }
774
775    #[test]
776    fn test_merge_single_hunk() {
777        // Unchanged and empty on all sides
778        assert_eq!(
779            merge_hunks(&conflict([b"", b"", b""])),
780            MergeResult::Resolved(hunk(b""))
781        );
782        // Unchanged on all sides
783        assert_eq!(
784            merge_hunks(&conflict([b"a", b"a", b"a"])),
785            MergeResult::Resolved(hunk(b"a"))
786        );
787        // One side removed, one side unchanged
788        assert_eq!(
789            merge_hunks(&conflict([b"", b"a\n", b"a\n"])),
790            MergeResult::Resolved(hunk(b""))
791        );
792        // One side unchanged, one side removed
793        assert_eq!(
794            merge_hunks(&conflict([b"a\n", b"a\n", b""])),
795            MergeResult::Resolved(hunk(b""))
796        );
797        // Both sides removed same line
798        assert_eq!(
799            merge_hunks(&conflict([b"", b"a\n", b""])),
800            MergeResult::Resolved(hunk(b""))
801        );
802        // One side modified, one side unchanged
803        assert_eq!(
804            merge_hunks(&conflict([b"a b", b"a", b"a"])),
805            MergeResult::Resolved(hunk(b"a b"))
806        );
807        // One side unchanged, one side modified
808        assert_eq!(
809            merge_hunks(&conflict([b"a", b"a", b"a b"])),
810            MergeResult::Resolved(hunk(b"a b"))
811        );
812        // All sides added same content
813        assert_eq!(
814            merge_hunks(&conflict([b"a\n", b"", b"a\n", b"", b"a\n"])),
815            MergeResult::Resolved(hunk(b"a\n"))
816        );
817        // One side modified, two sides added
818        assert_eq!(
819            merge_hunks(&conflict([b"b", b"a", b"b", b"", b"b"])),
820            MergeResult::Conflict(vec![conflict([b"b", b"a", b"b", b"", b"b"])])
821        );
822        // All sides removed same content
823        assert_eq!(
824            merge_hunks(&conflict([b"", b"a\n", b"", b"a\n", b"", b"a\n", b""])),
825            MergeResult::Resolved(hunk(b""))
826        );
827        // One side modified, two sides removed
828        assert_eq!(
829            merge_hunks(&conflict([b"b\n", b"a\n", b"", b"a\n", b""])),
830            MergeResult::Conflict(vec![conflict([b"b\n", b"a\n", b"", b"a\n", b""])])
831        );
832        // Three sides made the same change
833        assert_eq!(
834            merge_hunks(&conflict([b"b", b"a", b"b", b"a", b"b"])),
835            MergeResult::Resolved(hunk(b"b"))
836        );
837        // One side removed, one side modified
838        assert_eq!(
839            merge_hunks(&conflict([b"", b"a\n", b"b\n"])),
840            MergeResult::Conflict(vec![conflict([b"", b"a\n", b"b\n"])])
841        );
842        // One side modified, one side removed
843        assert_eq!(
844            merge_hunks(&conflict([b"b\n", b"a\n", b""])),
845            MergeResult::Conflict(vec![conflict([b"b\n", b"a\n", b""])])
846        );
847        // Two sides modified in different ways
848        assert_eq!(
849            merge_hunks(&conflict([b"b", b"a", b"c"])),
850            MergeResult::Conflict(vec![conflict([b"b", b"a", b"c"])])
851        );
852        // Two of three sides don't change, third side changes
853        assert_eq!(
854            merge_hunks(&conflict([b"a", b"a", b"", b"a", b"a"])),
855            MergeResult::Resolved(hunk(b""))
856        );
857        // One side unchanged, two other sides make the same change
858        assert_eq!(
859            merge_hunks(&conflict([b"b", b"a", b"a", b"a", b"b"])),
860            MergeResult::Resolved(hunk(b"b"))
861        );
862        // One side unchanged, two other sides make the different change
863        assert_eq!(
864            merge_hunks(&conflict([b"b", b"a", b"a", b"a", b"c"])),
865            MergeResult::Conflict(vec![conflict([b"b", b"a", b"a", b"a", b"c"])])
866        );
867        // Merge of an unresolved conflict and another branch, where the other branch
868        // undid the change from one of the inputs to the unresolved conflict in the
869        // first.
870        assert_eq!(
871            merge_hunks(&conflict([b"b", b"a", b"a", b"b", b"c"])),
872            MergeResult::Resolved(hunk(b"c"))
873        );
874        // Merge of an unresolved conflict and another branch.
875        assert_eq!(
876            merge_hunks(&conflict([b"c", b"a", b"d", b"b", b"e"])),
877            MergeResult::Conflict(vec![conflict([b"c", b"a", b"d", b"b", b"e"])])
878        );
879        // Two sides made the same change, third side made a different change
880        assert_eq!(
881            merge_hunks(&conflict([b"c", b"a", b"c", b"b", b"c"])),
882            MergeResult::Conflict(vec![conflict([b"c", b"a", b"c", b"b", b"c"])])
883        );
884    }
885
886    #[test]
887    fn test_merge_multi_hunk() {
888        // Two sides left one line unchanged, and added conflicting additional lines
889        let inputs = conflict([b"a\nb\n", b"a\n", b"a\nc\n"]);
890        assert_eq!(
891            merge_hunks(&inputs),
892            MergeResult::Conflict(vec![resolved(b"a\n"), conflict([b"b\n", b"", b"c\n"])])
893        );
894        assert_eq!(merge(&inputs), conflict([b"a\nb\n", b"a\n", b"a\nc\n"]));
895        assert_eq!(try_merge(&inputs), None);
896
897        // Two sides changed different lines: no conflict
898        let inputs = conflict([b"a2\nb\nc\n", b"a\nb\nc\n", b"a\nb\nc2\n"]);
899        assert_eq!(
900            merge_hunks(&inputs),
901            MergeResult::Resolved(hunk(b"a2\nb\nc2\n"))
902        );
903        assert_eq!(merge(&inputs), resolved(b"a2\nb\nc2\n"));
904        assert_eq!(try_merge(&inputs), Some(hunk(b"a2\nb\nc2\n")));
905
906        // Conflict with non-conflicting lines around
907        let inputs = conflict([b"a\nb1\nc\n", b"a\nb\nc\n", b"a\nb2\nc\n"]);
908        assert_eq!(
909            merge_hunks(&inputs),
910            MergeResult::Conflict(vec![
911                resolved(b"a\n"),
912                conflict([b"b1\n", b"b\n", b"b2\n"]),
913                resolved(b"c\n"),
914            ])
915        );
916        assert_eq!(
917            merge(&inputs),
918            conflict([b"a\nb1\nc\n", b"a\nb\nc\n", b"a\nb2\nc\n"])
919        );
920        assert_eq!(try_merge(&inputs), None);
921
922        // Two conflict hunks, one can be resolved
923        let inputs = conflict([b"a\nb\nc\n", b"a1\nb\nc\n", b"a2\nb\nc2\n"]);
924        assert_eq!(
925            merge_hunks(&inputs),
926            MergeResult::Conflict(vec![
927                conflict([b"a\n", b"a1\n", b"a2\n"]),
928                resolved(b"b\nc2\n"),
929            ])
930        );
931        assert_eq!(
932            merge(&inputs),
933            conflict([b"a\nb\nc2\n", b"a1\nb\nc2\n", b"a2\nb\nc2\n"])
934        );
935        assert_eq!(try_merge(&inputs), None);
936
937        // One side changes a line and adds a block after. The other side just adds the
938        // same block. You might expect the last block would be deduplicated. However,
939        // the changes in the first side can be parsed as follows:
940        // ```
941        //  a {
942        // -    p
943        // +    q
944        // +}
945        // +
946        // +b {
947        // +    x
948        //  }
949        // ```
950        // Therefore, the first side modifies the block `a { .. }`, and the second side
951        // adds `b { .. }`. Git and Mercurial both duplicate the block in the result.
952        let base = indoc! {b"
953            a {
954                p
955            }
956        "};
957        let left = indoc! {b"
958            a {
959                q
960            }
961
962            b {
963                x
964            }
965        "};
966        let right = indoc! {b"
967            a {
968                p
969            }
970
971            b {
972                x
973            }
974        "};
975        let merged = indoc! {b"
976            a {
977                q
978            }
979
980            b {
981                x
982            }
983
984            b {
985                x
986            }
987        "};
988        assert_eq!(merge(&conflict([left, base, right])), resolved(merged));
989    }
990}