shrink_conflicts/
lib.rs

1use std::fmt::Display;
2
3#[derive(Debug, Clone, Copy, PartialEq)]
4enum State {
5    Context,
6    Left,
7    Common,
8    Right,
9}
10
11pub fn run(file: String) -> anyhow::Result<String> {
12    use std::fmt::Write;
13    let mut output = String::new();
14    let mut state = State::Context;
15    let mut conflict = Conflict::default();
16    for l in file.lines() {
17        if l.len() >= 7 {
18            match &l[..7] {
19                "<<<<<<<" => {
20                    if state == State::Context {
21                        state = State::Left;
22                        continue;
23                    }
24                }
25                "|||||||" => {
26                    if state == State::Left {
27                        state = State::Common;
28                        continue;
29                    }
30                }
31                "=======" => {
32                    if state == State::Common {
33                        state = State::Right;
34                        continue;
35                    }
36                }
37                "+++++++" => match l.split_once(' ') {
38                    Some((_, "Contents of side #1")) => {
39                        if state == State::Left {
40                            state = State::Left;
41                            continue;
42                        }
43                    }
44                    Some((_, "Contents of side #2")) => {
45                        if state == State::Common {
46                            state = State::Right;
47                            continue;
48                        }
49                    }
50                    _ => (),
51                },
52                "-------" => match l.split_once(' ') {
53                    Some((_, "Contents of base")) => {
54                        if state == State::Left {
55                            state = State::Common;
56                            continue;
57                        }
58                    }
59                    _ => (),
60                },
61                ">>>>>>>" => {
62                    if state == State::Right {
63                        state = State::Context;
64
65                        conflict.minimise();
66                        write!(output, "{conflict}")?;
67                        conflict.clear();
68
69                        continue;
70                    }
71                }
72                "%%%%%%%" => {
73                    eprintln!(
74                        "WARN: jj's \"diff\"-style conflict markers are not \
75                        supported. Set conflict-marker-style=\"snapshot\"."
76                    );
77                }
78                _ => (),
79            }
80        }
81        match state {
82            State::Left => conflict.left.push(l.to_string()),
83            State::Common => conflict.common.push(l.to_string()),
84            State::Right => conflict.right.push(l.to_string()),
85            State::Context => writeln!(output, "{l}")?,
86        }
87    }
88    match state {
89        State::Context => (),
90        State::Left => {
91            writeln!(output, "<<<<<<<")?;
92            for l in conflict.left {
93                writeln!(output, "{l}")?;
94            }
95        }
96        State::Common => {
97            writeln!(output, "<<<<<<<")?;
98            for l in conflict.left {
99                writeln!(output, "{l}")?;
100            }
101            writeln!(output, "|||||||")?;
102            for l in conflict.common {
103                writeln!(output, "{l}")?;
104            }
105        }
106        State::Right => {
107            writeln!(output, "<<<<<<<")?;
108            for l in conflict.left {
109                writeln!(output, "{l}")?;
110            }
111            writeln!(output, "|||||||")?;
112            for l in conflict.common {
113                writeln!(output, "{l}")?;
114            }
115            writeln!(output, "=======")?;
116            for l in conflict.right {
117                writeln!(output, "{l}")?;
118            }
119        }
120    }
121    if let Some(c) = file.as_bytes().last() {
122        if *c != b'\n' {
123            output.remove(output.len() - 1);
124        }
125    }
126    Ok(output)
127}
128
129#[derive(Default, Debug, Clone)]
130struct Conflict {
131    pre: Vec<String>,
132    left: Vec<String>,
133    common: Vec<String>,
134    right: Vec<String>,
135    post: Vec<String>,
136}
137
138impl Display for Conflict {
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        for l in &self.pre {
141            writeln!(f, "{l}")?;
142        }
143        if !self.is_resolved() {
144            writeln!(f, "<<<<<<<")?;
145            for l in &self.left {
146                writeln!(f, "{l}")?;
147            }
148            writeln!(f, "|||||||")?;
149            for l in &self.common {
150                writeln!(f, "{l}")?;
151            }
152            writeln!(f, "=======")?;
153            for l in &self.right {
154                writeln!(f, "{l}")?;
155            }
156            writeln!(f, ">>>>>>>")?;
157        }
158        for l in &self.post {
159            writeln!(f, "{l}")?;
160        }
161        Ok(())
162    }
163}
164
165impl Conflict {
166    fn minimise(&mut self) {
167        let mut prefix = 0;
168        for ((l, c), r) in self
169            .left
170            .iter()
171            .zip(self.common.iter())
172            .zip(self.right.iter())
173        {
174            if l == c && c == r {
175                prefix += 1;
176            } else {
177                break;
178            }
179        }
180        self.pre.extend(self.left.drain(..prefix));
181        self.common.drain(..prefix);
182        self.right.drain(..prefix);
183
184        let mut suffix = 0;
185        for ((l, c), r) in self
186            .left
187            .iter()
188            .rev()
189            .zip(self.common.iter().rev())
190            .zip(self.right.iter().rev())
191        {
192            if l == c && c == r {
193                suffix += 1;
194            } else {
195                break;
196            }
197        }
198        self.post
199            .extend(self.left.drain(self.left.len() - suffix..));
200        self.common.drain(self.common.len() - suffix..);
201        self.right.drain(self.right.len() - suffix..);
202
203        if self.left == self.common {
204            self.pre.extend(self.right.drain(..));
205            self.left.clear();
206            self.common.clear();
207        }
208        if self.right == self.common {
209            self.pre.extend(self.left.drain(..));
210            self.right.clear();
211            self.common.clear();
212        }
213    }
214
215    fn is_resolved(&self) -> bool {
216        self.left.is_empty() && self.common.is_empty() && self.right.is_empty()
217    }
218
219    fn clear(&mut self) {
220        self.pre.clear();
221        self.left.clear();
222        self.common.clear();
223        self.right.clear();
224        self.post.clear();
225    }
226}