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 right_lines = right_text.split_inclusive(|b| *b == b'\n').map(BStr::new);
165                    for right_line in right_lines {
166                        self.current_line
167                            .hunks
168                            .push((DiffLineHunkSide::Right, right_line));
169                        if right_line.ends_with(b"\n") {
170                            self.queued_lines.push_back(self.current_line.take());
171                            self.current_line.line_number.right += 1;
172                        }
173                    }
174                }
175            }
176        }
177
178        if let Some(line) = self.queued_lines.pop_front() {
179            return Some(line);
180        }
181
182        if !self.current_line.hunks.is_empty() {
183            return Some(self.current_line.take());
184        }
185
186        None
187    }
188}
189
190/// Diff hunk that may be unresolved conflicts.
191#[derive(Clone, Debug, Eq, PartialEq)]
192pub struct ConflictDiffHunk<'input> {
193    pub kind: DiffHunkKind,
194    pub lefts: Merge<&'input BStr>,
195    pub rights: Merge<&'input BStr>,
196}
197
198/// Iterator adaptor that translates non-conflict hunks to resolved `Merge`.
199///
200/// Trivial conflicts in the diff inputs should have been resolved by caller.
201pub fn conflict_diff_hunks<'input, I>(
202    diff_hunks: I,
203    num_lefts: usize,
204) -> impl Iterator<Item = ConflictDiffHunk<'input>>
205where
206    I: IntoIterator,
207    I::Item: Borrow<DiffHunk<'input>>,
208{
209    fn to_merge<'input>(contents: &[&'input BStr]) -> Merge<&'input BStr> {
210        // Not using trivial_merge() so that the original content can be
211        // reproduced by concatenating hunks.
212        if contents.iter().all_equal() {
213            Merge::resolved(contents[0])
214        } else {
215            Merge::from_vec(contents)
216        }
217    }
218
219    diff_hunks.into_iter().map(move |hunk| {
220        let hunk = hunk.borrow();
221        let (lefts, rights) = hunk.contents.split_at(num_lefts);
222        if let ([left], [right]) = (lefts, rights) {
223            // Non-conflicting diff shouldn't have identical contents
224            ConflictDiffHunk {
225                kind: hunk.kind,
226                lefts: Merge::resolved(left),
227                rights: Merge::resolved(right),
228            }
229        } else {
230            let lefts = to_merge(lefts);
231            let rights = to_merge(rights);
232            let kind = match hunk.kind {
233                DiffHunkKind::Matching => DiffHunkKind::Matching,
234                DiffHunkKind::Different if lefts == rights => DiffHunkKind::Matching,
235                DiffHunkKind::Different => DiffHunkKind::Different,
236            };
237            ConflictDiffHunk {
238                kind,
239                lefts,
240                rights,
241            }
242        }
243    })
244}
245
246/// Merge result in either fully-resolved or conflicts form, akin to
247/// `Result<BString, Vec<Merge<BString>>>`.
248#[derive(PartialEq, Eq, Clone, Debug)]
249pub enum MergeResult {
250    /// Resolved content if inputs can be merged successfully.
251    Resolved(BString),
252    /// List of partially-resolved hunks if some of them cannot be merged.
253    Conflict(Vec<Merge<BString>>),
254}
255
256/// Splits `inputs` into hunks, resolves trivial merge conflicts for each.
257///
258/// Returns either fully-resolved content or list of partially-resolved hunks.
259pub fn merge_hunks<T: AsRef<[u8]>>(inputs: &Merge<T>) -> MergeResult {
260    merge_inner(inputs)
261}
262
263/// Splits `inputs` into hunks, resolves trivial merge conflicts for each, then
264/// concatenates the outcome back to single `Merge` object.
265///
266/// The returned merge object is either fully resolved or conflict having the
267/// same number of terms as the `inputs`.
268pub fn merge<T: AsRef<[u8]>>(inputs: &Merge<T>) -> Merge<BString> {
269    merge_inner(inputs)
270}
271
272/// Splits `inputs` into hunks, attempts to resolve trivial merge conflicts for
273/// each.
274///
275/// If all input hunks can be merged successfully, returns the merged content.
276pub fn try_merge<T: AsRef<[u8]>>(inputs: &Merge<T>) -> Option<BString> {
277    merge_inner(inputs)
278}
279
280fn merge_inner<'input, T: AsRef<[u8]>, B: FromMergeHunks<'input>>(inputs: &'input Merge<T>) -> B {
281    // TODO: Using the first remove as base (first in the inputs) is how it's
282    // usually done for 3-way conflicts. Are there better heuristics when there are
283    // more than 3 parts?
284    let num_diffs = inputs.removes().len();
285    let diff = Diff::by_line(inputs.removes().chain(inputs.adds()));
286    let hunks = resolve_diff_hunks(&diff, num_diffs);
287    B::from_hunks(hunks)
288}
289
290/// `FromIterator` for merge result.
291trait FromMergeHunks<'input>: Sized {
292    fn from_hunks<I: IntoIterator<Item = Merge<&'input BStr>>>(hunks: I) -> Self;
293}
294
295impl<'input> FromMergeHunks<'input> for MergeResult {
296    fn from_hunks<I: IntoIterator<Item = Merge<&'input BStr>>>(hunks: I) -> Self {
297        collect_hunks(hunks)
298    }
299}
300
301impl<'input> FromMergeHunks<'input> for Merge<BString> {
302    fn from_hunks<I: IntoIterator<Item = Merge<&'input BStr>>>(hunks: I) -> Self {
303        collect_merged(hunks)
304    }
305}
306
307impl<'input> FromMergeHunks<'input> for Option<BString> {
308    fn from_hunks<I: IntoIterator<Item = Merge<&'input BStr>>>(hunks: I) -> Self {
309        collect_resolved(hunks)
310    }
311}
312
313/// Collects merged hunks into either fully-resolved content or list of
314/// partially-resolved hunks.
315fn collect_hunks<'input>(hunks: impl IntoIterator<Item = Merge<&'input BStr>>) -> MergeResult {
316    let mut resolved_hunk = BString::new(vec![]);
317    let mut merge_hunks: Vec<Merge<BString>> = vec![];
318    for hunk in hunks {
319        if let Some(&content) = hunk.as_resolved() {
320            resolved_hunk.extend_from_slice(content);
321        } else {
322            if !resolved_hunk.is_empty() {
323                merge_hunks.push(Merge::resolved(resolved_hunk));
324                resolved_hunk = BString::new(vec![]);
325            }
326            merge_hunks.push(hunk.map(|&s| s.to_owned()));
327        }
328    }
329
330    if merge_hunks.is_empty() {
331        MergeResult::Resolved(resolved_hunk)
332    } else {
333        if !resolved_hunk.is_empty() {
334            merge_hunks.push(Merge::resolved(resolved_hunk));
335        }
336        MergeResult::Conflict(merge_hunks)
337    }
338}
339
340/// Collects merged hunks back to single `Merge` object, duplicating resolved
341/// hunks to all positive and negative terms.
342fn collect_merged<'input>(hunks: impl IntoIterator<Item = Merge<&'input BStr>>) -> Merge<BString> {
343    let mut maybe_resolved = Merge::resolved(BString::default());
344    for hunk in hunks {
345        if let Some(&content) = hunk.as_resolved() {
346            for buf in maybe_resolved.iter_mut() {
347                buf.extend_from_slice(content);
348            }
349        } else {
350            maybe_resolved = match maybe_resolved.into_resolved() {
351                Ok(content) => Merge::from_vec(vec![content; hunk.as_slice().len()]),
352                Err(conflict) => conflict,
353            };
354            assert_eq!(maybe_resolved.as_slice().len(), hunk.as_slice().len());
355            for (buf, s) in iter::zip(maybe_resolved.iter_mut(), hunk) {
356                buf.extend_from_slice(s);
357            }
358        }
359    }
360    maybe_resolved
361}
362
363/// Collects resolved merge hunks. Short-circuits on unresolved hunk.
364fn collect_resolved<'input>(
365    hunks: impl IntoIterator<Item = Merge<&'input BStr>>,
366) -> Option<BString> {
367    hunks
368        .into_iter()
369        .map(|hunk| hunk.into_resolved().ok())
370        .collect()
371}
372
373/// Iterator that attempts to resolve trivial merge conflict for each hunk.
374fn resolve_diff_hunks<'a, 'input>(
375    diff: &'a Diff<'input>,
376    num_diffs: usize,
377) -> impl Iterator<Item = Merge<&'input BStr>> + use<'a, 'input> {
378    diff.hunks().map(move |diff_hunk| match diff_hunk.kind {
379        DiffHunkKind::Matching => {
380            debug_assert!(diff_hunk.contents.iter().all_equal());
381            Merge::resolved(diff_hunk.contents[0])
382        }
383        DiffHunkKind::Different => {
384            let merge = Merge::from_removes_adds(
385                diff_hunk.contents[..num_diffs].iter().copied(),
386                diff_hunk.contents[num_diffs..].iter().copied(),
387            );
388            match merge.resolve_trivial() {
389                Some(&content) => Merge::resolved(content),
390                None => merge,
391            }
392        }
393    })
394}
395
396#[cfg(test)]
397mod tests {
398    use indoc::indoc;
399
400    use super::*;
401
402    fn conflict<const N: usize>(values: [&[u8]; N]) -> Merge<BString> {
403        Merge::from_vec(values.map(hunk).to_vec())
404    }
405
406    fn resolved(value: &[u8]) -> Merge<BString> {
407        Merge::resolved(hunk(value))
408    }
409
410    fn hunk(data: &[u8]) -> BString {
411        data.into()
412    }
413
414    #[test]
415    fn test_diff_line_iterator_line_numbers() {
416        let mut line_iter = DiffLineIterator::with_line_number(
417            [DiffHunk::different(["a\nb", "c\nd\n"])].into_iter(),
418            DiffLineNumber { left: 1, right: 10 },
419        );
420        // Nothing queued
421        assert_eq!(
422            line_iter.next_line_number(),
423            DiffLineNumber { left: 1, right: 10 }
424        );
425        assert_eq!(
426            line_iter.next().unwrap(),
427            DiffLine {
428                line_number: DiffLineNumber { left: 1, right: 10 },
429                hunks: vec![(DiffLineHunkSide::Left, "a\n".as_ref())],
430            }
431        );
432        // Multiple lines queued
433        assert_eq!(
434            line_iter.next_line_number(),
435            DiffLineNumber { left: 2, right: 10 }
436        );
437        assert_eq!(
438            line_iter.next().unwrap(),
439            DiffLine {
440                line_number: DiffLineNumber { left: 2, right: 10 },
441                hunks: vec![
442                    (DiffLineHunkSide::Left, "b".as_ref()),
443                    (DiffLineHunkSide::Right, "c\n".as_ref()),
444                ],
445            }
446        );
447        // Single line queued
448        assert_eq!(
449            line_iter.next_line_number(),
450            DiffLineNumber { left: 2, right: 11 }
451        );
452        assert_eq!(
453            line_iter.next().unwrap(),
454            DiffLine {
455                line_number: DiffLineNumber { left: 2, right: 11 },
456                hunks: vec![(DiffLineHunkSide::Right, "d\n".as_ref())],
457            }
458        );
459        // No more lines: left remains 2 as it lacks newline
460        assert_eq!(
461            line_iter.next_line_number(),
462            DiffLineNumber { left: 2, right: 12 }
463        );
464        assert!(line_iter.next().is_none());
465        assert_eq!(
466            line_iter.next_line_number(),
467            DiffLineNumber { left: 2, right: 12 }
468        );
469    }
470
471    #[test]
472    fn test_conflict_diff_hunks_no_conflicts() {
473        let diff_hunks = [
474            DiffHunk::matching(["a\n"].repeat(2)),
475            DiffHunk::different(["b\n", "c\n"]),
476        ];
477        let num_lefts = 1;
478        insta::assert_debug_snapshot!(
479            conflict_diff_hunks(&diff_hunks, num_lefts).collect_vec(), @r#"
480        [
481            ConflictDiffHunk {
482                kind: Matching,
483                lefts: Resolved(
484                    "a\n",
485                ),
486                rights: Resolved(
487                    "a\n",
488                ),
489            },
490            ConflictDiffHunk {
491                kind: Different,
492                lefts: Resolved(
493                    "b\n",
494                ),
495                rights: Resolved(
496                    "c\n",
497                ),
498            },
499        ]
500        "#);
501    }
502
503    #[test]
504    fn test_conflict_diff_hunks_simple_conflicts() {
505        let diff_hunks = [
506            // conflict hunk
507            DiffHunk::different(["a\n", "X\n", "b\n", "c\n"]),
508            DiffHunk::matching(["d\n"].repeat(4)),
509            // non-conflict hunk
510            DiffHunk::different(["e\n", "e\n", "e\n", "f\n"]),
511        ];
512        let num_lefts = 3;
513        insta::assert_debug_snapshot!(
514            conflict_diff_hunks(&diff_hunks, num_lefts).collect_vec(), @r#"
515        [
516            ConflictDiffHunk {
517                kind: Different,
518                lefts: Conflicted(
519                    [
520                        "a\n",
521                        "X\n",
522                        "b\n",
523                    ],
524                ),
525                rights: Resolved(
526                    "c\n",
527                ),
528            },
529            ConflictDiffHunk {
530                kind: Matching,
531                lefts: Resolved(
532                    "d\n",
533                ),
534                rights: Resolved(
535                    "d\n",
536                ),
537            },
538            ConflictDiffHunk {
539                kind: Different,
540                lefts: Resolved(
541                    "e\n",
542                ),
543                rights: Resolved(
544                    "f\n",
545                ),
546            },
547        ]
548        "#);
549    }
550
551    #[test]
552    fn test_conflict_diff_hunks_matching_conflicts() {
553        let diff_hunks = [
554            // matching conflict hunk
555            DiffHunk::different(["a\n", "X\n", "b\n", "a\n", "X\n", "b\n"]),
556            DiffHunk::matching(["c\n"].repeat(6)),
557        ];
558        let num_lefts = 3;
559        insta::assert_debug_snapshot!(
560            conflict_diff_hunks(&diff_hunks, num_lefts).collect_vec(), @r#"
561        [
562            ConflictDiffHunk {
563                kind: Matching,
564                lefts: Conflicted(
565                    [
566                        "a\n",
567                        "X\n",
568                        "b\n",
569                    ],
570                ),
571                rights: Conflicted(
572                    [
573                        "a\n",
574                        "X\n",
575                        "b\n",
576                    ],
577                ),
578            },
579            ConflictDiffHunk {
580                kind: Matching,
581                lefts: Resolved(
582                    "c\n",
583                ),
584                rights: Resolved(
585                    "c\n",
586                ),
587            },
588        ]
589        "#);
590    }
591
592    #[test]
593    fn test_conflict_diff_hunks_no_trivial_resolution() {
594        let diff_hunks = [DiffHunk::different(["a", "b", "a", "a"])];
595        let num_lefts = 1;
596        insta::assert_debug_snapshot!(
597            conflict_diff_hunks(&diff_hunks, num_lefts).collect_vec(), @r#"
598        [
599            ConflictDiffHunk {
600                kind: Different,
601                lefts: Resolved(
602                    "a",
603                ),
604                rights: Conflicted(
605                    [
606                        "b",
607                        "a",
608                        "a",
609                    ],
610                ),
611            },
612        ]
613        "#);
614        let num_lefts = 3;
615        insta::assert_debug_snapshot!(
616            conflict_diff_hunks(&diff_hunks, num_lefts).collect_vec(), @r#"
617        [
618            ConflictDiffHunk {
619                kind: Different,
620                lefts: Conflicted(
621                    [
622                        "a",
623                        "b",
624                        "a",
625                    ],
626                ),
627                rights: Resolved(
628                    "a",
629                ),
630            },
631        ]
632        "#);
633    }
634
635    #[test]
636    fn test_merge_single_hunk() {
637        // Unchanged and empty on all sides
638        assert_eq!(
639            merge_hunks(&conflict([b"", b"", b""])),
640            MergeResult::Resolved(hunk(b""))
641        );
642        // Unchanged on all sides
643        assert_eq!(
644            merge_hunks(&conflict([b"a", b"a", b"a"])),
645            MergeResult::Resolved(hunk(b"a"))
646        );
647        // One side removed, one side unchanged
648        assert_eq!(
649            merge_hunks(&conflict([b"", b"a\n", b"a\n"])),
650            MergeResult::Resolved(hunk(b""))
651        );
652        // One side unchanged, one side removed
653        assert_eq!(
654            merge_hunks(&conflict([b"a\n", b"a\n", b""])),
655            MergeResult::Resolved(hunk(b""))
656        );
657        // Both sides removed same line
658        assert_eq!(
659            merge_hunks(&conflict([b"", b"a\n", b""])),
660            MergeResult::Resolved(hunk(b""))
661        );
662        // One side modified, one side unchanged
663        assert_eq!(
664            merge_hunks(&conflict([b"a b", b"a", b"a"])),
665            MergeResult::Resolved(hunk(b"a b"))
666        );
667        // One side unchanged, one side modified
668        assert_eq!(
669            merge_hunks(&conflict([b"a", b"a", b"a b"])),
670            MergeResult::Resolved(hunk(b"a b"))
671        );
672        // All sides added same content
673        assert_eq!(
674            merge_hunks(&conflict([b"a\n", b"", b"a\n", b"", b"a\n"])),
675            MergeResult::Resolved(hunk(b"a\n"))
676        );
677        // One side modified, two sides added
678        assert_eq!(
679            merge_hunks(&conflict([b"b", b"a", b"b", b"", b"b"])),
680            MergeResult::Conflict(vec![conflict([b"b", b"a", b"b", b"", b"b"])])
681        );
682        // All sides removed same content
683        assert_eq!(
684            merge_hunks(&conflict([b"", b"a\n", b"", b"a\n", b"", b"a\n", b""])),
685            MergeResult::Resolved(hunk(b""))
686        );
687        // One side modified, two sides removed
688        assert_eq!(
689            merge_hunks(&conflict([b"b\n", b"a\n", b"", b"a\n", b""])),
690            MergeResult::Conflict(vec![conflict([b"b\n", b"a\n", b"", b"a\n", b""])])
691        );
692        // Three sides made the same change
693        assert_eq!(
694            merge_hunks(&conflict([b"b", b"a", b"b", b"a", b"b"])),
695            MergeResult::Resolved(hunk(b"b"))
696        );
697        // One side removed, one side modified
698        assert_eq!(
699            merge_hunks(&conflict([b"", b"a\n", b"b\n"])),
700            MergeResult::Conflict(vec![conflict([b"", b"a\n", b"b\n"])])
701        );
702        // One side modified, one side removed
703        assert_eq!(
704            merge_hunks(&conflict([b"b\n", b"a\n", b""])),
705            MergeResult::Conflict(vec![conflict([b"b\n", b"a\n", b""])])
706        );
707        // Two sides modified in different ways
708        assert_eq!(
709            merge_hunks(&conflict([b"b", b"a", b"c"])),
710            MergeResult::Conflict(vec![conflict([b"b", b"a", b"c"])])
711        );
712        // Two of three sides don't change, third side changes
713        assert_eq!(
714            merge_hunks(&conflict([b"a", b"a", b"", b"a", b"a"])),
715            MergeResult::Resolved(hunk(b""))
716        );
717        // One side unchanged, two other sides make the same change
718        assert_eq!(
719            merge_hunks(&conflict([b"b", b"a", b"a", b"a", b"b"])),
720            MergeResult::Resolved(hunk(b"b"))
721        );
722        // One side unchanged, two other sides make the different change
723        assert_eq!(
724            merge_hunks(&conflict([b"b", b"a", b"a", b"a", b"c"])),
725            MergeResult::Conflict(vec![conflict([b"b", b"a", b"a", b"a", b"c"])])
726        );
727        // Merge of an unresolved conflict and another branch, where the other branch
728        // undid the change from one of the inputs to the unresolved conflict in the
729        // first.
730        assert_eq!(
731            merge_hunks(&conflict([b"b", b"a", b"a", b"b", b"c"])),
732            MergeResult::Resolved(hunk(b"c"))
733        );
734        // Merge of an unresolved conflict and another branch.
735        assert_eq!(
736            merge_hunks(&conflict([b"c", b"a", b"d", b"b", b"e"])),
737            MergeResult::Conflict(vec![conflict([b"c", b"a", b"d", b"b", b"e"])])
738        );
739        // Two sides made the same change, third side made a different change
740        assert_eq!(
741            merge_hunks(&conflict([b"c", b"a", b"c", b"b", b"c"])),
742            MergeResult::Conflict(vec![conflict([b"c", b"a", b"c", b"b", b"c"])])
743        );
744    }
745
746    #[test]
747    fn test_merge_multi_hunk() {
748        // Two sides left one line unchanged, and added conflicting additional lines
749        let inputs = conflict([b"a\nb\n", b"a\n", b"a\nc\n"]);
750        assert_eq!(
751            merge_hunks(&inputs),
752            MergeResult::Conflict(vec![resolved(b"a\n"), conflict([b"b\n", b"", b"c\n"])])
753        );
754        assert_eq!(merge(&inputs), conflict([b"a\nb\n", b"a\n", b"a\nc\n"]));
755        assert_eq!(try_merge(&inputs), None);
756
757        // Two sides changed different lines: no conflict
758        let inputs = conflict([b"a2\nb\nc\n", b"a\nb\nc\n", b"a\nb\nc2\n"]);
759        assert_eq!(
760            merge_hunks(&inputs),
761            MergeResult::Resolved(hunk(b"a2\nb\nc2\n"))
762        );
763        assert_eq!(merge(&inputs), resolved(b"a2\nb\nc2\n"));
764        assert_eq!(try_merge(&inputs), Some(hunk(b"a2\nb\nc2\n")));
765
766        // Conflict with non-conflicting lines around
767        let inputs = conflict([b"a\nb1\nc\n", b"a\nb\nc\n", b"a\nb2\nc\n"]);
768        assert_eq!(
769            merge_hunks(&inputs),
770            MergeResult::Conflict(vec![
771                resolved(b"a\n"),
772                conflict([b"b1\n", b"b\n", b"b2\n"]),
773                resolved(b"c\n"),
774            ])
775        );
776        assert_eq!(
777            merge(&inputs),
778            conflict([b"a\nb1\nc\n", b"a\nb\nc\n", b"a\nb2\nc\n"])
779        );
780        assert_eq!(try_merge(&inputs), None);
781
782        // Two conflict hunks, one can be resolved
783        let inputs = conflict([b"a\nb\nc\n", b"a1\nb\nc\n", b"a2\nb\nc2\n"]);
784        assert_eq!(
785            merge_hunks(&inputs),
786            MergeResult::Conflict(vec![
787                conflict([b"a\n", b"a1\n", b"a2\n"]),
788                resolved(b"b\nc2\n"),
789            ])
790        );
791        assert_eq!(
792            merge(&inputs),
793            conflict([b"a\nb\nc2\n", b"a1\nb\nc2\n", b"a2\nb\nc2\n"])
794        );
795        assert_eq!(try_merge(&inputs), None);
796
797        // One side changes a line and adds a block after. The other side just adds the
798        // same block. You might expect the last block would be deduplicated. However,
799        // the changes in the first side can be parsed as follows:
800        // ```
801        //  a {
802        // -    p
803        // +    q
804        // +}
805        // +
806        // +b {
807        // +    x
808        //  }
809        // ```
810        // Therefore, the first side modifies the block `a { .. }`, and the second side
811        // adds `b { .. }`. Git and Mercurial both duplicate the block in the result.
812        let base = indoc! {b"
813            a {
814                p
815            }
816        "};
817        let left = indoc! {b"
818            a {
819                q
820            }
821
822            b {
823                x
824            }
825        "};
826        let right = indoc! {b"
827            a {
828                p
829            }
830
831            b {
832                x
833            }
834        "};
835        let merged = indoc! {b"
836            a {
837                q
838            }
839
840            b {
841                x
842            }
843
844            b {
845                x
846            }
847        "};
848        assert_eq!(merge(&conflict([left, base, right])), resolved(merged));
849    }
850}