diff_tool/services/
git.rs

1use std::{
2    path::Path,
3    process::{Command, Stdio},
4};
5
6#[derive(Default, Debug)]
7pub struct Diff {
8    old_diff: Vec<DiffLine>,
9    current_diff: Vec<DiffLine>,
10}
11
12impl Diff {
13    pub fn longest_diff_len(&self) -> usize {
14        let old_diff = self.old_diff.len();
15        let current_diff = self.current_diff.len();
16
17        std::cmp::max(old_diff, current_diff) - 1
18    }
19
20    pub fn old_diff(&self) -> &[DiffLine] {
21        &self.old_diff
22    }
23
24    pub fn current_diff(&self) -> &[DiffLine] {
25        &self.current_diff
26    }
27
28    pub fn largest_line_number_char_len(&self) -> u16 {
29        let (old_diff, current_diff) = self.largest_line_number();
30
31        let largest_line_number = std::cmp::max(old_diff, current_diff);
32
33        let length = std::cmp::min(largest_line_number.to_string().len(), u16::MAX.into());
34        length.try_into().unwrap_or(4)
35    }
36
37    /// Gets the largest line number from each diff
38    fn largest_line_number(&self) -> (usize, usize) {
39        let old_diff = self
40            .old_diff
41            .iter()
42            .map(|x| x.line_number().unwrap_or(0))
43            .max()
44            .unwrap_or(0);
45
46        let current_diff = self
47            .old_diff
48            .iter()
49            .map(|x| x.line_number().unwrap_or(0))
50            .max()
51            .unwrap_or(0);
52
53        (old_diff, current_diff)
54    }
55
56    pub fn parse_diff(diff_string: &str) -> Self {
57        let lines = diff_string.split("\n");
58
59        let mut diff = Self::default();
60
61        let mut start = false;
62        let mut additions = 0;
63        let mut removals = 0;
64        let mut diff_one_line = 1;
65        let mut diff_two_line = 1;
66
67        for line in lines {
68            if line.starts_with("@@") && line.ends_with("@@") {
69                start = true;
70                continue;
71            }
72
73            if !start {
74                continue;
75            }
76
77            let (prefix, content) = remove_first_char(line);
78
79            match prefix {
80                '+' => {
81                    diff.current_diff.push(DiffLine::new(
82                        content,
83                        DiffKind::Addition,
84                        Some(diff_two_line),
85                    ));
86                    diff_two_line += 1;
87                    if removals > 0 {
88                        removals -= 1
89                    } else {
90                        additions += 1
91                    }
92                }
93
94                '-' => {
95                    diff.old_diff.push(DiffLine::new(
96                        content,
97                        DiffKind::Removal,
98                        Some(diff_one_line),
99                    ));
100                    diff_one_line += 1;
101                    removals += 1
102                }
103                _ => {
104                    for _ in 0..removals {
105                        diff.current_diff
106                            .push(DiffLine::new("", DiffKind::Blank, None))
107                    }
108
109                    removals = 0;
110
111                    for _ in 0..additions {
112                        diff.old_diff.push(DiffLine::new("", DiffKind::Blank, None))
113                    }
114
115                    additions = 0;
116
117                    diff.old_diff.push(DiffLine::new(
118                        content,
119                        DiffKind::Neutral,
120                        Some(diff_one_line),
121                    ));
122                    diff_one_line += 1;
123                    diff.current_diff.push(DiffLine::new(
124                        content,
125                        DiffKind::Neutral,
126                        Some(diff_two_line),
127                    ));
128                    diff_two_line += 1
129                }
130            }
131        }
132
133        for _ in 0..removals {
134            diff.current_diff
135                .push(DiffLine::new("", DiffKind::Blank, None))
136        }
137
138        for _ in 0..additions {
139            diff.old_diff.push(DiffLine::new("", DiffKind::Blank, None))
140        }
141
142        diff
143    }
144}
145
146#[derive(Debug, Clone, Default)]
147pub struct DiffLine {
148    content: String,
149    kind: DiffKind,
150    line_number: Option<usize>,
151}
152
153impl DiffLine {
154    fn new(content: &str, kind: DiffKind, line_number: Option<usize>) -> Self {
155        let content = content.to_string();
156        Self {
157            content,
158            kind,
159            line_number,
160        }
161    }
162
163    pub fn content(&self) -> &str {
164        &self.content
165    }
166
167    pub fn kind(&self) -> &DiffKind {
168        &self.kind
169    }
170
171    pub fn line_number(&self) -> &Option<usize> {
172        &self.line_number
173    }
174}
175
176#[derive(Debug, Clone, Copy, Default)]
177pub enum DiffKind {
178    Addition,
179    Removal,
180    Neutral,
181    #[default]
182    Blank,
183}
184
185impl DiffKind {
186    pub fn value(&self) -> &str {
187        match self {
188            DiffKind::Addition => "+",
189            DiffKind::Removal => "-",
190            DiffKind::Neutral => " ",
191            DiffKind::Blank => " ",
192        }
193    }
194}
195
196/// Performs 'git diff -U1000 <filename>' or 'git -C [path] diff -U1000 <filename>' and returns the result as a string
197pub fn get_raw_diff(path: &Path, dir_flag: bool) -> String {
198    let args = if !dir_flag {
199        vec!["diff", "-U1000", path.to_str().unwrap()]
200    } else {
201        vec![
202            "-C",
203            path.parent().unwrap().to_str().unwrap(),
204            "diff",
205            "-U1000",
206            path.file_name().unwrap().to_str().unwrap(),
207        ]
208    };
209
210    // Process git diff <filename> command and save the stdout response
211    let output = Command::new("git")
212        .args(args)
213        .stdout(Stdio::piped())
214        .output()
215        .expect("Failed to execute git diff");
216
217    // Convert stdout response to a string and return
218    String::from_utf8(output.stdout).expect("UTF8 data to convert to string")
219}
220
221fn remove_first_char(string: &str) -> (char, &str) {
222    let mut chars = string.chars();
223    (chars.next().unwrap_or(' '), chars.as_str())
224}