diffy_imara/merge/
mod.rs

1use imara_diff::sources::{byte_lines_with_terminator, lines_with_terminator};
2
3use crate::{
4    diff::DiffOptions,
5    range::{DiffRange, Range, SliceLike},
6    utils::InternedMergeInput,
7    Algorithm,
8};
9use std::{cmp, fmt};
10
11#[cfg(test)]
12mod tests;
13
14const DEFAULT_CONFLICT_MARKER_LENGTH: usize = 7;
15
16enum Diff3Range<'ancestor, 'ours, 'theirs, T: ?Sized> {
17    Equal(Range<'ancestor, T>, Range<'ours, T>, Range<'theirs, T>),
18    Ancestor(Range<'ancestor, T>),
19    AncestorOurs(Range<'ancestor, T>, Range<'ours, T>),
20    AncestorTheirs(Range<'ancestor, T>, Range<'theirs, T>),
21    Ours(Range<'ours, T>),
22    Theirs(Range<'theirs, T>),
23}
24
25impl<T: ?Sized + fmt::Debug + SliceLike> fmt::Debug for Diff3Range<'_, '_, '_, T> {
26    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27        match self {
28            Diff3Range::Equal(range, ..) => write!(f, "Equal: {:?}", range.as_slice()),
29            Diff3Range::Ancestor(range) => write!(f, "Ancestor: {:?}", range.as_slice()),
30            Diff3Range::AncestorOurs(range, ..) => {
31                write!(f, "AncestorOurs: {:?}", range.as_slice())
32            }
33            Diff3Range::AncestorTheirs(range, ..) => {
34                write!(f, "AncestorTheirs: {:?}", range.as_slice())
35            }
36            Diff3Range::Ours(range) => write!(f, "Ours: {:?}", range.as_slice()),
37            Diff3Range::Theirs(range) => write!(f, "Theirs: {:?}", range.as_slice()),
38        }
39    }
40}
41
42impl<T: ?Sized> Copy for Diff3Range<'_, '_, '_, T> {}
43
44impl<T: ?Sized> Clone for Diff3Range<'_, '_, '_, T> {
45    fn clone(&self) -> Self {
46        *self
47    }
48}
49
50enum MergeRange<'ancestor, 'ours, 'theirs, T: ?Sized> {
51    Equal(Range<'ancestor, T>, Range<'ours, T>, Range<'theirs, T>),
52    Conflict(Range<'ancestor, T>, Range<'ours, T>, Range<'theirs, T>),
53    Ours(Range<'ours, T>),
54    Theirs(Range<'theirs, T>),
55    Both(Range<'ours, T>, Range<'theirs, T>),
56}
57
58impl<T: ?Sized + fmt::Debug + SliceLike> fmt::Debug for MergeRange<'_, '_, '_, T> {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        match self {
61            MergeRange::Equal(range, ..) => write!(f, "Equal: {:?}", range.as_slice()),
62            MergeRange::Conflict(ancestor, ours, theirs) => write!(
63                f,
64                "Conflict: ancestor: {:?} ours: {:?} theirs: {:?}",
65                ancestor.as_slice(),
66                ours.as_slice(),
67                theirs.as_slice()
68            ),
69            MergeRange::Ours(range) => write!(f, "Ours: {:?}", range.as_slice()),
70            MergeRange::Theirs(range) => write!(f, "Theirs: {:?}", range.as_slice()),
71            MergeRange::Both(ours, theirs) => write!(
72                f,
73                "Both: ours: {:?} theirs: {:?}",
74                ours.as_slice(),
75                theirs.as_slice()
76            ),
77        }
78    }
79}
80
81impl<T: ?Sized> Copy for MergeRange<'_, '_, '_, T> {}
82
83impl<T: ?Sized> Clone for MergeRange<'_, '_, '_, T> {
84    fn clone(&self) -> Self {
85        *self
86    }
87}
88
89/// Style used when rendering a conflict
90#[derive(Copy, Clone, Debug, Default)]
91pub enum ConflictStyle {
92    /// Renders conflicting lines from both files, separated by conflict markers.
93    ///
94    /// ```console
95    /// <<<<<<< A
96    /// lines in file A
97    /// =======
98    /// lines in file B
99    /// >>>>>>> B
100    /// ```
101    Merge,
102
103    /// Renders conflicting lines from both files including lines from the original files,
104    /// separated by conflict markers.
105    ///
106    /// ```console
107    /// <<<<<<< A
108    /// lines in file A
109    /// ||||||| Original
110    /// lines in Original file
111    /// =======
112    /// lines in file B
113    /// >>>>>>> B
114    /// ```
115    #[default]
116    Diff3,
117}
118
119/// A collection of options for modifying the way a merge is performed
120#[derive(Debug)]
121pub struct MergeOptions {
122    /// The length of the conflict markers used when displaying a merge conflict
123    pub conflict_marker_length: usize,
124    /// The conflict style used when displaying a merge conflict
125    pub style: ConflictStyle,
126    pub algorithm: Algorithm,
127}
128
129impl MergeOptions {
130    /// Constructs a new `MergeOptions` with default settings
131    ///
132    /// ## Defaults
133    /// * conflict_marker_length = 7
134    /// * style = ConflictStyle::Diff3
135    pub fn new() -> Self {
136        Self {
137            conflict_marker_length: DEFAULT_CONFLICT_MARKER_LENGTH,
138            style: ConflictStyle::Diff3,
139            algorithm: Algorithm::Histogram,
140        }
141    }
142
143    /// Merge two files, given a common ancestor, based on the configured options
144    /// The algorithm set in [`Self::algorithm`] is used for computing the diff between `ancestor`
145    /// and `ours`, and between `ancestor` and `theirs`
146    pub fn merge<'a>(
147        &self,
148        ancestor: &'a str,
149        ours: &'a str,
150        theirs: &'a str,
151    ) -> Result<String, String> {
152        let input = InternedMergeInput::new(ancestor, ours, theirs);
153        let ancestor_lines: Vec<_> = lines_with_terminator(ancestor).collect();
154        let our_lines: Vec<_> = lines_with_terminator(ours).collect();
155        let their_lines: Vec<_> = lines_with_terminator(theirs).collect();
156
157        let mut opts = DiffOptions::new();
158        opts.set_algorithm(self.algorithm);
159
160        let our_solution = opts.diff_tokens(&input.base, &input.left, input.interner.num_tokens());
161        let their_solution =
162            opts.diff_tokens(&input.base, &input.right, input.interner.num_tokens());
163
164        let merged = merge_solutions(&our_solution, &their_solution);
165        let mut merge = diff3_range_to_merge_range(&merged);
166
167        cleanup_conflicts(&mut merge);
168
169        output_result(
170            &ancestor_lines,
171            &our_lines,
172            &their_lines,
173            &merge,
174            self.conflict_marker_length,
175            self.style,
176        )
177    }
178
179    /// Perform a 3-way merge between potentially non-utf8 texts
180    pub fn merge_bytes<'a>(
181        &self,
182        ancestor: &'a [u8],
183        ours: &'a [u8],
184        theirs: &'a [u8],
185    ) -> Result<Vec<u8>, Vec<u8>> {
186        let input = InternedMergeInput::new(ancestor, ours, theirs);
187        let ancestor_lines: Vec<_> = byte_lines_with_terminator(ancestor).collect();
188        let our_lines: Vec<_> = byte_lines_with_terminator(ours).collect();
189        let their_lines: Vec<_> = byte_lines_with_terminator(theirs).collect();
190
191        let mut opts = DiffOptions::new();
192        opts.set_algorithm(self.algorithm);
193
194        let our_solution = opts.diff_tokens(&input.base, &input.left, input.interner.num_tokens());
195        let their_solution =
196            opts.diff_tokens(&input.base, &input.right, input.interner.num_tokens());
197
198        let merged = merge_solutions(&our_solution, &their_solution);
199        let mut merge = diff3_range_to_merge_range(&merged);
200
201        cleanup_conflicts(&mut merge);
202
203        output_result_bytes(
204            &ancestor_lines,
205            &our_lines,
206            &their_lines,
207            &merge,
208            self.conflict_marker_length,
209            self.style,
210        )
211    }
212}
213
214impl Default for MergeOptions {
215    fn default() -> Self {
216        Self::new()
217    }
218}
219
220/// Merge two files given a common ancestor.
221///
222/// Returns `Ok(String)` upon a successful merge.
223/// Returns `Err(String)` if there were conflicts, with the conflicting
224/// regions marked with conflict markers.
225///
226/// ## Merging two files without conflicts
227/// ```
228/// # use diffy_imara::merge;
229/// let original = "\
230/// Devotion
231/// Dominion
232/// Odium
233/// Preservation
234/// Ruin
235/// Cultivation
236/// Honor
237/// Endowment
238/// Autonomy
239/// Ambition
240/// ";
241/// let a = "\
242/// Odium
243/// Preservation
244/// Ruin
245/// Cultivation
246/// Endowment
247/// Autonomy
248/// ";
249/// let b = "\
250/// Devotion
251/// Dominion
252/// Odium
253/// Harmony
254/// Cultivation
255/// Honor
256/// Endowment
257/// Autonomy
258/// Ambition
259/// ";
260///
261/// let expected = "\
262/// Odium
263/// Harmony
264/// Cultivation
265/// Endowment
266/// Autonomy
267/// ";
268///
269/// assert_eq!(merge(original, a, b).unwrap(), expected);
270/// ```
271pub fn merge<'a>(ancestor: &'a str, ours: &'a str, theirs: &'a str) -> Result<String, String> {
272    MergeOptions::default().merge(ancestor, ours, theirs)
273}
274
275/// Perform a 3-way merge between potentially non-utf8 texts
276pub fn merge_bytes<'a>(
277    ancestor: &'a [u8],
278    ours: &'a [u8],
279    theirs: &'a [u8],
280) -> Result<Vec<u8>, Vec<u8>> {
281    MergeOptions::default().merge_bytes(ancestor, ours, theirs)
282}
283
284fn merge_solutions<'ancestor, 'ours, 'theirs, T: ?Sized + SliceLike>(
285    our_solution: &[DiffRange<'ancestor, 'ours, T>],
286    their_solution: &[DiffRange<'ancestor, 'theirs, T>],
287) -> Vec<Diff3Range<'ancestor, 'ours, 'theirs, T>> {
288    let mut our_solution = our_solution.iter().copied();
289    let mut their_solution = their_solution.iter().copied();
290    let mut ours = our_solution.next();
291    let mut theirs = their_solution.next();
292
293    let mut solution = Vec::new();
294
295    while ours.is_some() || theirs.is_some() {
296        use DiffRange as DR;
297        let merge_range = match (ours, theirs) {
298            //
299            // Inserts can't easily be checked to see if they match each other
300            //
301            (Some(DR::Insert(range)), _) => {
302                ours.take();
303                Diff3Range::Ours(range)
304            }
305            (_, Some(DR::Insert(range))) => {
306                theirs.take();
307                Diff3Range::Theirs(range)
308            }
309
310            (Some(DR::Equal(ancestor1, our_range)), Some(DR::Equal(ancestor2, their_range))) => {
311                assert_eq!(ancestor1.offset(), ancestor2.offset());
312                let len = cmp::min(ancestor1.len(), ancestor2.len());
313
314                shrink_front(&mut ours, len);
315                shrink_front(&mut theirs, len);
316
317                Diff3Range::Equal(
318                    ancestor1.slice(..len),
319                    our_range.slice(..len),
320                    their_range.slice(..len),
321                )
322            }
323
324            (Some(DR::Equal(ancestor1, our_range)), Some(DR::Delete(ancestor2))) => {
325                assert_eq!(ancestor1.offset(), ancestor2.offset());
326                let len = cmp::min(ancestor1.len(), ancestor2.len());
327
328                shrink_front(&mut ours, len);
329                shrink_front(&mut theirs, len);
330
331                Diff3Range::AncestorOurs(ancestor1.slice(..len), our_range.slice(..len))
332            }
333
334            (Some(DR::Delete(ancestor1)), Some(DR::Equal(ancestor2, their_range))) => {
335                assert_eq!(ancestor1.offset(), ancestor2.offset());
336                let len = cmp::min(ancestor1.len(), ancestor2.len());
337
338                shrink_front(&mut ours, len);
339                shrink_front(&mut theirs, len);
340
341                Diff3Range::AncestorTheirs(ancestor2.slice(..len), their_range.slice(..len))
342            }
343
344            (Some(DR::Delete(ancestor1)), Some(DR::Delete(ancestor2))) => {
345                assert_eq!(ancestor1.offset(), ancestor2.offset());
346                let len = cmp::min(ancestor1.len(), ancestor2.len());
347
348                shrink_front(&mut ours, len);
349                shrink_front(&mut theirs, len);
350
351                Diff3Range::Ancestor(ancestor1.slice(..len))
352            }
353
354            //
355            // Unreachable cases
356            //
357            (Some(DR::Equal(..) | DR::Delete(..)), None)
358            | (None, Some(DR::Equal(..) | DR::Delete(_)))
359            | (None, None) => unreachable!("Equal/Delete should match up"),
360        };
361
362        solution.push(merge_range);
363
364        if ours.map_or(true, |range| range.is_empty()) {
365            ours = our_solution.next();
366        }
367        if theirs.map_or(true, |range| range.is_empty()) {
368            theirs = their_solution.next();
369        }
370    }
371
372    solution
373}
374
375fn shrink_front<T: ?Sized + SliceLike>(maybe_range: &mut Option<DiffRange<T>>, len: usize) {
376    if let Some(range) = maybe_range {
377        range.shrink_front(len)
378    }
379}
380
381fn diff3_range_to_merge_range<'ancestor, 'ours, 'theirs, T: ?Sized + SliceLike>(
382    solution: &[Diff3Range<'ancestor, 'ours, 'theirs, T>],
383) -> Vec<MergeRange<'ancestor, 'ours, 'theirs, T>> {
384    let mut ancestor: Option<Range<'ancestor, T>> = None;
385    let mut ours: Option<Range<'ours, T>> = None;
386    let mut theirs: Option<Range<'theirs, T>> = None;
387
388    let mut merge = Vec::new();
389
390    for &diff3 in solution {
391        match diff3 {
392            Diff3Range::Equal(ancestor_range, our_range, their_range) => {
393                if let Some(merge_range) =
394                    create_merge_range(ancestor.take(), ours.take(), theirs.take())
395                {
396                    merge.push(merge_range);
397                }
398                merge.push(MergeRange::Equal(ancestor_range, our_range, their_range));
399            }
400            Diff3Range::Ancestor(range) => {
401                set_or_merge_range(&mut ancestor, range);
402                set_or_merge_range(&mut ours, Range::empty());
403                set_or_merge_range(&mut theirs, Range::empty());
404            }
405            Diff3Range::AncestorOurs(ancestor_range, our_range) => {
406                set_or_merge_range(&mut ancestor, ancestor_range);
407                set_or_merge_range(&mut ours, our_range);
408            }
409            Diff3Range::AncestorTheirs(ancestor_range, their_range) => {
410                set_or_merge_range(&mut ancestor, ancestor_range);
411                set_or_merge_range(&mut theirs, their_range);
412            }
413            Diff3Range::Ours(range) => set_or_merge_range(&mut ours, range),
414            Diff3Range::Theirs(range) => set_or_merge_range(&mut theirs, range),
415        }
416    }
417
418    if let Some(merge_range) = create_merge_range(ancestor.take(), ours.take(), theirs.take()) {
419        merge.push(merge_range);
420    }
421
422    merge
423}
424
425fn set_or_merge_range<'a, T: ?Sized>(range1: &mut Option<Range<'a, T>>, range2: Range<'a, T>) {
426    if let Some(range1) = range1 {
427        if range1.is_empty() {
428            *range1 = range2;
429        } else if !range2.is_empty() {
430            assert_eq!(range1.offset() + range1.len(), range2.offset());
431            range1.grow_down(range2.len());
432        }
433    } else {
434        *range1 = Some(range2);
435    }
436}
437
438fn create_merge_range<'ancestor, 'ours, 'theirs, T: ?Sized + SliceLike>(
439    ancestor: Option<Range<'ancestor, T>>,
440    ours: Option<Range<'ours, T>>,
441    theirs: Option<Range<'theirs, T>>,
442) -> Option<MergeRange<'ancestor, 'ours, 'theirs, T>> {
443    match (ancestor, ours, theirs) {
444        (_, None, None) => None,
445
446        (None, Some(ours), None) => Some(MergeRange::Ours(ours)),
447        (None, None, Some(theirs)) => Some(MergeRange::Theirs(theirs)),
448
449        (ancestor, ours, theirs) => Some(MergeRange::Conflict(
450            ancestor.unwrap_or_default(),
451            ours.unwrap_or_default(),
452            theirs.unwrap_or_default(),
453        )),
454    }
455}
456
457#[allow(clippy::needless_lifetimes)]
458fn cleanup_conflicts<'ancestor, 'ours, 'theirs, T: ?Sized + SliceLike + PartialEq>(
459    solution: &mut [MergeRange<'ancestor, 'ours, 'theirs, T>],
460) {
461    // TODO this could probably be more sophisticated:
462    // e.g. run the diff algorithm on the conflict area
463    for merge in solution {
464        if let MergeRange::Conflict(ancestor, ours, theirs) = *merge {
465            // If the ranges in the conflict end up being the same on both sides then we can
466            // eliminate the conflict
467            if ours.as_slice() == theirs.as_slice() {
468                *merge = MergeRange::Both(ours, theirs);
469            // If either ours or theirs exactly matches ancestor then we can also eliminate the
470            // conflict
471            } else if ancestor.as_slice() == ours.as_slice() {
472                *merge = MergeRange::Theirs(theirs);
473            } else if ancestor.as_slice() == theirs.as_slice() {
474                *merge = MergeRange::Ours(ours);
475            }
476        }
477    }
478}
479
480fn output_result<'a, T: ?Sized>(
481    ancestor: &[&'a str],
482    ours: &[&'a str],
483    theirs: &[&'a str],
484    merge: &[MergeRange<T>],
485    marker_len: usize,
486    style: ConflictStyle,
487) -> Result<String, String> {
488    let mut conflicts = 0;
489    let mut output = String::new();
490
491    for (range_idx, merge_range) in merge.iter().enumerate() {
492        match merge_range {
493            MergeRange::Equal(range, ..) => {
494                add_lines(&mut output, &ancestor[range.range()]);
495            }
496            MergeRange::Conflict(ancestor_range, ours_range, theirs_range) => {
497                let ancestor = &ancestor[ancestor_range.range()];
498                let ours = &ours[ours_range.range()];
499                let theirs = &theirs[theirs_range.range()];
500
501                if range_idx == merge.len() - 1 {
502                    let ancestor_last_ends_with_newline =
503                        ancestor.last().map_or(false, |l| l.ends_with('\n'));
504                    let ours_last_ends_with_newline =
505                        ours.last().map_or(false, |l| l.ends_with('\n'));
506                    let theirs_last_ends_with_newline =
507                        theirs.last().map_or(false, |l| l.ends_with('\n'));
508
509                    let (add_after_lines, add_after_right_marker) =
510                        if ancestor_last_ends_with_newline
511                            && ours_last_ends_with_newline
512                            && theirs_last_ends_with_newline
513                        {
514                            (false, true)
515                        } else {
516                            (true, false)
517                        };
518
519                    add_conflict_marker(&mut output, '<', marker_len, Some("ours"));
520                    add_lines(&mut output, ours);
521                    if add_after_lines {
522                        output.push('\n');
523                    }
524
525                    if let ConflictStyle::Diff3 = style {
526                        add_conflict_marker(&mut output, '|', marker_len, Some("original"));
527                        add_lines(&mut output, ancestor);
528                        if add_after_lines {
529                            output.push('\n');
530                        }
531                    }
532
533                    add_conflict_marker(&mut output, '=', marker_len, None);
534
535                    add_lines(&mut output, theirs);
536                    if add_after_lines {
537                        output.push('\n');
538                    }
539                    add_conflict_marker(&mut output, '>', marker_len, Some("theirs"));
540                    if !add_after_right_marker {
541                        output
542                            .pop()
543                            .expect("a `\n` is always added by the `add_conflict_marker` above");
544                    }
545                } else {
546                    add_conflict_marker(&mut output, '<', marker_len, Some("ours"));
547                    add_lines(&mut output, ours);
548
549                    if let ConflictStyle::Diff3 = style {
550                        add_conflict_marker(&mut output, '|', marker_len, Some("original"));
551                        add_lines(&mut output, ancestor);
552                    }
553
554                    add_conflict_marker(&mut output, '=', marker_len, None);
555                    add_lines(&mut output, theirs);
556                    add_conflict_marker(&mut output, '>', marker_len, Some("theirs"));
557                }
558
559                conflicts += 1;
560            }
561            MergeRange::Ours(range) => {
562                add_lines(&mut output, &ours[range.range()]);
563            }
564            MergeRange::Theirs(range) => {
565                add_lines(&mut output, &theirs[range.range()]);
566            }
567            MergeRange::Both(range, _) => {
568                add_lines(&mut output, &ours[range.range()]);
569            }
570        }
571    }
572
573    if conflicts != 0 {
574        Err(output)
575    } else {
576        Ok(output)
577    }
578}
579
580fn add_lines(dest: &mut String, lines: &[&str]) {
581    dest.extend(lines.iter().copied());
582}
583
584fn add_conflict_marker(
585    output: &mut String,
586    marker: char,
587    marker_len: usize,
588    filename: Option<&str>,
589) {
590    for _ in 0..marker_len {
591        output.push(marker);
592    }
593
594    if let Some(filename) = filename {
595        output.push(' ');
596        output.push_str(filename);
597    }
598    output.push('\n');
599}
600
601fn output_result_bytes<'a, T: ?Sized>(
602    ancestor: &[&'a [u8]],
603    ours: &[&'a [u8]],
604    theirs: &[&'a [u8]],
605    merge: &[MergeRange<T>],
606    marker_len: usize,
607    style: ConflictStyle,
608) -> Result<Vec<u8>, Vec<u8>> {
609    let mut conflicts = 0;
610    let mut output: Vec<u8> = Vec::new();
611
612    for (range_idx, merge_range) in merge.iter().enumerate() {
613        match merge_range {
614            MergeRange::Equal(range, ..) => {
615                add_lines_bytes(&mut output, &ancestor[range.range()]);
616            }
617            MergeRange::Conflict(ancestor_range, ours_range, theirs_range) => {
618                let ancestor = &ancestor[ancestor_range.range()];
619                let ours = &ours[ours_range.range()];
620                let theirs = &theirs[theirs_range.range()];
621
622                if range_idx == merge.len() - 1 {
623                    let ancestor_last_ends_with_newline =
624                        ancestor.last().map_or(false, |l| l.ends_with(b"\n"));
625                    let ours_last_ends_with_newline =
626                        ours.last().map_or(false, |l| l.ends_with(b"\n"));
627                    let theirs_last_ends_with_newline =
628                        theirs.last().map_or(false, |l| l.ends_with(b"\n"));
629
630                    let (add_after_lines, add_after_right_marker) =
631                        if ancestor_last_ends_with_newline
632                            && ours_last_ends_with_newline
633                            && theirs_last_ends_with_newline
634                        {
635                            (false, true)
636                        } else {
637                            (true, false)
638                        };
639
640                    add_conflict_marker_bytes(&mut output, b'<', marker_len, Some(b"ours"));
641                    add_lines_bytes(&mut output, ours);
642                    if add_after_lines {
643                        output.push(b'\n');
644                    }
645
646                    if let ConflictStyle::Diff3 = style {
647                        add_conflict_marker_bytes(&mut output, b'|', marker_len, Some(b"original"));
648                        add_lines_bytes(&mut output, ancestor);
649                        if add_after_lines {
650                            output.push(b'\n');
651                        }
652                    }
653
654                    add_conflict_marker_bytes(&mut output, b'=', marker_len, None);
655
656                    add_lines_bytes(&mut output, theirs);
657                    if add_after_lines {
658                        output.push(b'\n');
659                    }
660                    add_conflict_marker_bytes(&mut output, b'>', marker_len, Some(b"theirs"));
661                    if !add_after_right_marker {
662                        output.pop().expect(
663                            "a `\n` is always added by the `add_conflict_marker_bytes` above",
664                        );
665                    }
666                } else {
667                    add_conflict_marker_bytes(&mut output, b'<', marker_len, Some(b"ours"));
668                    add_lines_bytes(&mut output, ours);
669
670                    if let ConflictStyle::Diff3 = style {
671                        add_conflict_marker_bytes(&mut output, b'|', marker_len, Some(b"original"));
672                        add_lines_bytes(&mut output, ancestor);
673                    }
674
675                    add_conflict_marker_bytes(&mut output, b'=', marker_len, None);
676                    add_lines_bytes(&mut output, theirs);
677                    add_conflict_marker_bytes(&mut output, b'>', marker_len, Some(b"theirs"));
678                }
679
680                conflicts += 1;
681            }
682            MergeRange::Ours(range) => {
683                add_lines_bytes(&mut output, &ours[range.range()]);
684            }
685            MergeRange::Theirs(range) => {
686                add_lines_bytes(&mut output, &theirs[range.range()]);
687            }
688            MergeRange::Both(range, _) => {
689                add_lines_bytes(&mut output, &ours[range.range()]);
690            }
691        }
692    }
693
694    if conflicts != 0 {
695        Err(output)
696    } else {
697        Ok(output)
698    }
699}
700
701fn add_lines_bytes(output: &mut Vec<u8>, lines: &[&[u8]]) {
702    lines.iter().for_each(|line| output.extend_from_slice(line));
703}
704
705fn add_conflict_marker_bytes(
706    output: &mut Vec<u8>,
707    marker: u8,
708    marker_len: usize,
709    filename: Option<&[u8]>,
710) {
711    for _ in 0..marker_len {
712        output.push(marker);
713    }
714
715    if let Some(filename) = filename {
716        output.push(b' ');
717        output.extend_from_slice(filename);
718    }
719    output.push(b'\n');
720}