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: Vec::new(),
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                // DEVIATION: this makes tests (mostly) pass, but probably is very different from what Git does.
119                let hunk_storage;
120                let nl = detect_line_ending(
121                    if front_hunks.is_empty() {
122                        hunk_storage = Hunk {
123                            before: ancestor_integrated_until..first_hunk.before.start,
124                            after: Default::default(),
125                            side: Side::Ancestor,
126                        };
127                        std::slice::from_ref(&hunk_storage)
128                    } else {
129                        front_hunks
130                    },
131                    input,
132                    &current_tokens,
133                )
134                .or_else(|| detect_line_ending(our_hunks, input, &current_tokens))
135                .unwrap_or(b"\n".into());
136                match style {
137                    ConflictStyle::Merge => {
138                        if contains_lines(our_hunks) || contains_lines(their_hunks) {
139                            resolution = Resolution::Conflict;
140                            write_conflict_marker(out, b'<', current_label, marker_size, nl);
141                            write_hunks(our_hunks, input, &current_tokens, out);
142                            write_conflict_marker(out, b'=', None, marker_size, nl);
143                            write_hunks(their_hunks, input, &current_tokens, out);
144                            write_conflict_marker(out, b'>', other_label, marker_size, nl);
145                        }
146                    }
147                    ConflictStyle::Diff3 | ConflictStyle::ZealousDiff3 => {
148                        if contains_lines(our_hunks) || contains_lines(their_hunks) {
149                            if hunks_differ_in_diff3(style, our_hunks, their_hunks, input, &current_tokens) {
150                                resolution = Resolution::Conflict;
151                                write_conflict_marker(out, b'<', current_label, marker_size, nl);
152                                write_hunks(our_hunks, input, &current_tokens, out);
153                                let ancestor_hunk = Hunk {
154                                    before: first_hunk.before.start..last_hunk.before.end,
155                                    after: Default::default(),
156                                    side: Side::Ancestor,
157                                };
158                                let ancestor_hunk = std::slice::from_ref(&ancestor_hunk);
159                                let ancestor_nl = detect_line_ending_or_nl(ancestor_hunk, input, &current_tokens);
160                                write_conflict_marker(out, b'|', ancestor_label, marker_size, ancestor_nl);
161                                write_hunks(ancestor_hunk, input, &current_tokens, out);
162                                write_conflict_marker(out, b'=', None, marker_size, nl);
163                                write_hunks(their_hunks, input, &current_tokens, out);
164                                write_conflict_marker(out, b'>', other_label, marker_size, nl);
165                            } else {
166                                write_hunks(our_hunks, input, &current_tokens, out);
167                            }
168                        }
169                    }
170                }
171
172                write_hunks(back_hunks, input, &current_tokens, out);
173                ancestor_integrated_until = last_hunk.before.end;
174            }
175            Conflict::ResolveWithOurs | Conflict::ResolveWithTheirs => {
176                let (our_hunks, their_hunks) = match filled_hunks_side {
177                    Side::Current => (&current_hunks, &intersecting),
178                    Side::Other => (&intersecting, &current_hunks),
179                    Side::Ancestor => {
180                        unreachable!("initial hunks are never ancestors")
181                    }
182                };
183                if hunks_differ_in_diff3(ConflictStyle::Diff3, our_hunks, their_hunks, input, &current_tokens) {
184                    resolution = Resolution::CompleteWithAutoResolvedConflict;
185                }
186                let hunks_to_write = if conflict == Conflict::ResolveWithOurs {
187                    our_hunks
188                } else {
189                    their_hunks
190                };
191                if let Some(first_hunk) = hunks_to_write.first() {
192                    write_ancestor(input, ancestor_integrated_until, first_hunk.before.start as usize, out);
193                }
194                write_hunks(hunks_to_write, input, &current_tokens, out);
195                if let Some(last_hunk) = hunks_to_write.last() {
196                    ancestor_integrated_until = last_hunk.before.end;
197                }
198            }
199            Conflict::ResolveWithUnion => {
200                let (hunks_front_and_back, num_hunks_front) =
201                    zealously_contract_hunks(&mut current_hunks, &mut intersecting, input, &current_tokens);
202
203                let (our_hunks, their_hunks) = match filled_hunks_side {
204                    Side::Current => (&current_hunks, &intersecting),
205                    Side::Other => (&intersecting, &current_hunks),
206                    Side::Ancestor => {
207                        unreachable!("initial hunks are never ancestors")
208                    }
209                };
210                if hunks_differ_in_diff3(ConflictStyle::Diff3, our_hunks, their_hunks, input, &current_tokens) {
211                    resolution = Resolution::CompleteWithAutoResolvedConflict;
212                }
213                let (front_hunks, back_hunks) = hunks_front_and_back.split_at(num_hunks_front);
214                let first_hunk = first_hunk(front_hunks, our_hunks, their_hunks, back_hunks);
215                write_ancestor(input, ancestor_integrated_until, first_hunk.before.start as usize, out);
216                write_hunks(front_hunks, input, &current_tokens, out);
217                assure_ends_with_nl(out, detect_line_ending_or_nl(front_hunks, input, &current_tokens));
218                write_hunks(our_hunks, input, &current_tokens, out);
219                assure_ends_with_nl(out, detect_line_ending_or_nl(our_hunks, input, &current_tokens));
220                write_hunks(their_hunks, input, &current_tokens, out);
221                if !back_hunks.is_empty() {
222                    assure_ends_with_nl(out, detect_line_ending_or_nl(their_hunks, input, &current_tokens));
223                }
224                write_hunks(back_hunks, input, &current_tokens, out);
225                let last_hunk = last_hunk(front_hunks, our_hunks, their_hunks, back_hunks);
226                ancestor_integrated_until = last_hunk.before.end;
227            }
228        }
229    }
230    write_ancestor(input, ancestor_integrated_until, input.before.len(), out);
231
232    resolution
233}
234
235fn first_hunk<'a>(front: &'a [Hunk], ours: &'a [Hunk], theirs: &'a [Hunk], back: &'a [Hunk]) -> &'a Hunk {
236    front
237        .first()
238        .or(ours.first())
239        .or(theirs.first())
240        .or(back.first())
241        .expect("at least one hunk - we aborted if there are none anywhere")
242}
243
244/// Note that last-hunk could be [`first_hunk()`], so the hunk must only be used accordingly.
245fn last_hunk<'a>(front: &'a [Hunk], ours: &'a [Hunk], theirs: &'a [Hunk], back: &'a [Hunk]) -> &'a Hunk {
246    back.last()
247        .or(theirs.last())
248        .or(ours.last())
249        .or(front.last())
250        .expect("at least one hunk - we aborted if there are none anywhere")
251}
252
253fn before_range_from_hunks(hunks: &[Hunk]) -> Range<u32> {
254    hunks
255        .first()
256        .zip(hunks.last())
257        .map(|(f, l)| f.before.start..l.before.end)
258        .expect("at least one entry")
259}