gix_merge/blob/builtin_driver/text/
function.rs

1use std::ops::Range;
2
3use crate::blob::{
4    builtin_driver::text::{
5        utils::{
6            assure_ends_with_nl, contains_lines, detect_line_ending, detect_line_ending_or_nl, fill_ancestor,
7            hunks_differ_in_diff3, take_intersecting, tokens, write_ancestor, write_conflict_marker, write_hunks,
8            zealously_contract_hunks, CollectHunks, Hunk, Side,
9        },
10        Conflict, ConflictStyle, Labels, Options,
11    },
12    Resolution,
13};
14
15/// Merge `current` and `other` with `ancestor` as base according to `opts`.
16///
17/// Use `labels` to annotate conflict sections.
18///
19/// `input` is for reusing memory for lists of tokens, but note that it grows indefinitely
20/// while tokens for `current`, `ancestor` and `other` are added.
21/// Place the merged result in `out` (cleared before use) and return the resolution.
22///
23/// # Important
24///
25/// *The caller* is responsible for clearing `input`, otherwise tokens will accumulate.
26/// This idea is to save time if the input is known to be very similar.
27#[allow(clippy::too_many_arguments)]
28pub fn merge<'a>(
29    out: &mut Vec<u8>,
30    input: &mut imara_diff::intern::InternedInput<&'a [u8]>,
31    Labels {
32        ancestor: ancestor_label,
33        current: current_label,
34        other: other_label,
35    }: Labels<'_>,
36    current: &'a [u8],
37    ancestor: &'a [u8],
38    other: &'a [u8],
39    Options {
40        diff_algorithm,
41        conflict,
42    }: Options,
43) -> Resolution {
44    out.clear();
45    input.update_before(tokens(ancestor));
46    input.update_after(tokens(current));
47
48    let hunks = imara_diff::diff(
49        diff_algorithm,
50        input,
51        CollectHunks {
52            side: Side::Current,
53            hunks: Default::default(),
54        },
55    );
56
57    let current_tokens = std::mem::take(&mut input.after);
58    input.update_after(tokens(other));
59
60    let mut hunks = imara_diff::diff(
61        diff_algorithm,
62        input,
63        CollectHunks {
64            side: Side::Other,
65            hunks,
66        },
67    );
68
69    if hunks.is_empty() {
70        write_ancestor(input, 0, input.before.len(), out);
71        return Resolution::Complete;
72    }
73
74    hunks.sort_by(|a, b| a.before.start.cmp(&b.before.start));
75    let mut hunks = hunks.into_iter().peekable();
76    let mut intersecting = Vec::new();
77    let mut ancestor_integrated_until = 0;
78    let mut resolution = Resolution::Complete;
79    let mut current_hunks = Vec::with_capacity(2);
80    while take_intersecting(&mut hunks, &mut current_hunks, &mut intersecting).is_some() {
81        if intersecting.is_empty() {
82            let hunk = current_hunks.pop().expect("always pushed during intersection check");
83            write_ancestor(input, ancestor_integrated_until, hunk.before.start as usize, out);
84            ancestor_integrated_until = hunk.before.end;
85            write_hunks(std::slice::from_ref(&hunk), input, &current_tokens, out);
86            continue;
87        }
88
89        let filled_hunks_side = current_hunks.first().expect("at least one hunk").side;
90        {
91            let filled_hunks_range = before_range_from_hunks(&current_hunks);
92            let intersecting_range = before_range_from_hunks(&intersecting);
93            let extended_range = filled_hunks_range.start..intersecting_range.end.max(filled_hunks_range.end);
94            fill_ancestor(&extended_range, &mut current_hunks);
95            fill_ancestor(&extended_range, &mut intersecting);
96        }
97        match conflict {
98            Conflict::Keep { style, marker_size } => {
99                let marker_size = marker_size.get();
100                let (hunks_front_and_back, num_hunks_front) = match style {
101                    ConflictStyle::Merge | ConflictStyle::ZealousDiff3 => {
102                        zealously_contract_hunks(&mut current_hunks, &mut intersecting, input, &current_tokens)
103                    }
104                    ConflictStyle::Diff3 => (Vec::new(), 0),
105                };
106                let (our_hunks, their_hunks) = match filled_hunks_side {
107                    Side::Current => (&current_hunks, &intersecting),
108                    Side::Other => (&intersecting, &current_hunks),
109                    Side::Ancestor => {
110                        unreachable!("initial hunks are never ancestors")
111                    }
112                };
113                let (front_hunks, back_hunks) = hunks_front_and_back.split_at(num_hunks_front);
114                let first_hunk = first_hunk(front_hunks, our_hunks, their_hunks, back_hunks);
115                let last_hunk = last_hunk(front_hunks, our_hunks, their_hunks, back_hunks);
116                write_ancestor(input, ancestor_integrated_until, first_hunk.before.start as usize, out);
117                write_hunks(front_hunks, input, &current_tokens, out);
118                if their_hunks.is_empty() {
119                    write_hunks(our_hunks, input, &current_tokens, out);
120                } else if our_hunks.is_empty() {
121                    write_hunks(their_hunks, input, &current_tokens, out);
122                } else {
123                    // DEVIATION: this makes tests (mostly) pass, but probably is very different from what Git does.
124                    let hunk_storage;
125                    let nl = detect_line_ending(
126                        if front_hunks.is_empty() {
127                            hunk_storage = Hunk {
128                                before: ancestor_integrated_until..first_hunk.before.start,
129                                after: Default::default(),
130                                side: Side::Ancestor,
131                            };
132                            std::slice::from_ref(&hunk_storage)
133                        } else {
134                            front_hunks
135                        },
136                        input,
137                        &current_tokens,
138                    )
139                    .or_else(|| detect_line_ending(our_hunks, input, &current_tokens))
140                    .unwrap_or(b"\n".into());
141                    match style {
142                        ConflictStyle::Merge => {
143                            if contains_lines(our_hunks) || contains_lines(their_hunks) {
144                                resolution = Resolution::Conflict;
145                                write_conflict_marker(out, b'<', current_label, marker_size, nl);
146                                write_hunks(our_hunks, input, &current_tokens, out);
147                                write_conflict_marker(out, b'=', None, marker_size, nl);
148                                write_hunks(their_hunks, input, &current_tokens, out);
149                                write_conflict_marker(out, b'>', other_label, marker_size, nl);
150                            }
151                        }
152                        ConflictStyle::Diff3 | ConflictStyle::ZealousDiff3 => {
153                            if contains_lines(our_hunks) || contains_lines(their_hunks) {
154                                if hunks_differ_in_diff3(style, our_hunks, their_hunks, input, &current_tokens) {
155                                    resolution = Resolution::Conflict;
156                                    write_conflict_marker(out, b'<', current_label, marker_size, nl);
157                                    write_hunks(our_hunks, input, &current_tokens, out);
158                                    let ancestor_hunk = Hunk {
159                                        before: first_hunk.before.start..last_hunk.before.end,
160                                        after: Default::default(),
161                                        side: Side::Ancestor,
162                                    };
163                                    let ancestor_hunk = std::slice::from_ref(&ancestor_hunk);
164                                    let ancestor_nl = detect_line_ending_or_nl(ancestor_hunk, input, &current_tokens);
165                                    write_conflict_marker(out, b'|', ancestor_label, marker_size, ancestor_nl);
166                                    write_hunks(ancestor_hunk, input, &current_tokens, out);
167                                    write_conflict_marker(out, b'=', None, marker_size, nl);
168                                    write_hunks(their_hunks, input, &current_tokens, out);
169                                    write_conflict_marker(out, b'>', other_label, marker_size, nl);
170                                } else {
171                                    write_hunks(our_hunks, input, &current_tokens, out);
172                                }
173                            }
174                        }
175                    }
176                }
177                write_hunks(back_hunks, input, &current_tokens, out);
178                ancestor_integrated_until = last_hunk.before.end;
179            }
180            Conflict::ResolveWithOurs | Conflict::ResolveWithTheirs => {
181                let (our_hunks, their_hunks) = match filled_hunks_side {
182                    Side::Current => (&current_hunks, &intersecting),
183                    Side::Other => (&intersecting, &current_hunks),
184                    Side::Ancestor => {
185                        unreachable!("initial hunks are never ancestors")
186                    }
187                };
188                if hunks_differ_in_diff3(ConflictStyle::Diff3, our_hunks, their_hunks, input, &current_tokens) {
189                    resolution = Resolution::CompleteWithAutoResolvedConflict;
190                }
191                let hunks_to_write = if conflict == Conflict::ResolveWithOurs {
192                    our_hunks
193                } else {
194                    their_hunks
195                };
196                if let Some(first_hunk) = hunks_to_write.first() {
197                    write_ancestor(input, ancestor_integrated_until, first_hunk.before.start as usize, out);
198                }
199                write_hunks(hunks_to_write, input, &current_tokens, out);
200                if let Some(last_hunk) = hunks_to_write.last() {
201                    ancestor_integrated_until = last_hunk.before.end;
202                }
203            }
204            Conflict::ResolveWithUnion => {
205                let (hunks_front_and_back, num_hunks_front) =
206                    zealously_contract_hunks(&mut current_hunks, &mut intersecting, input, &current_tokens);
207
208                let (our_hunks, their_hunks) = match filled_hunks_side {
209                    Side::Current => (&current_hunks, &intersecting),
210                    Side::Other => (&intersecting, &current_hunks),
211                    Side::Ancestor => {
212                        unreachable!("initial hunks are never ancestors")
213                    }
214                };
215                if hunks_differ_in_diff3(ConflictStyle::Diff3, our_hunks, their_hunks, input, &current_tokens) {
216                    resolution = Resolution::CompleteWithAutoResolvedConflict;
217                }
218                let (front_hunks, back_hunks) = hunks_front_and_back.split_at(num_hunks_front);
219                let first_hunk = first_hunk(front_hunks, our_hunks, their_hunks, back_hunks);
220                write_ancestor(input, ancestor_integrated_until, first_hunk.before.start as usize, out);
221                write_hunks(front_hunks, input, &current_tokens, out);
222                assure_ends_with_nl(out, detect_line_ending_or_nl(front_hunks, input, &current_tokens));
223                write_hunks(our_hunks, input, &current_tokens, out);
224                assure_ends_with_nl(out, detect_line_ending_or_nl(our_hunks, input, &current_tokens));
225                write_hunks(their_hunks, input, &current_tokens, out);
226                if !back_hunks.is_empty() {
227                    assure_ends_with_nl(out, detect_line_ending_or_nl(their_hunks, input, &current_tokens));
228                }
229                write_hunks(back_hunks, input, &current_tokens, out);
230                let last_hunk = last_hunk(front_hunks, our_hunks, their_hunks, back_hunks);
231                ancestor_integrated_until = last_hunk.before.end;
232            }
233        }
234    }
235    write_ancestor(input, ancestor_integrated_until, input.before.len(), out);
236
237    resolution
238}
239
240fn first_hunk<'a>(front: &'a [Hunk], ours: &'a [Hunk], theirs: &'a [Hunk], back: &'a [Hunk]) -> &'a Hunk {
241    front
242        .first()
243        .or(ours.first())
244        .or(theirs.first())
245        .or(back.first())
246        .expect("at least one hunk - we aborted if there are none anywhere")
247}
248
249/// Note that last-hunk could be [`first_hunk()`], so the hunk must only be used accordingly.
250fn last_hunk<'a>(front: &'a [Hunk], ours: &'a [Hunk], theirs: &'a [Hunk], back: &'a [Hunk]) -> &'a Hunk {
251    back.last()
252        .or(theirs.last())
253        .or(ours.last())
254        .or(front.last())
255        .expect("at least one hunk - we aborted if there are none anywhere")
256}
257
258fn before_range_from_hunks(hunks: &[Hunk]) -> Range<u32> {
259    hunks
260        .first()
261        .zip(hunks.last())
262        .map(|(f, l)| f.before.start..l.before.end)
263        .expect("at least one entry")
264}