git_graph/
settings.rs

1//! Graph generation settings.
2
3use crate::print::format::CommitFormat;
4use regex::{Error, Regex};
5use serde_derive::{Deserialize, Serialize};
6use std::str::FromStr;
7
8/// Repository settings for the branching model.
9/// Used to read repo's git-graph.toml
10#[derive(Serialize, Deserialize)]
11pub struct RepoSettings {
12    /// The repository's branching model
13    pub model: String,
14}
15
16/// Ordering policy for branches in visual columns.
17pub enum BranchOrder {
18    /// Recommended! Shortest branches are inserted left-most.
19    ///
20    /// For branches with equal length, branches ending last are inserted first.
21    /// Reverse (arg = false): Branches ending first are inserted first.
22    ShortestFirst(bool),
23    /// Longest branches are inserted left-most.
24    ///
25    /// For branches with equal length, branches ending last are inserted first.
26    /// Reverse (arg = false): Branches ending first are inserted first.
27    LongestFirst(bool),
28}
29
30/// Top-level settings
31pub struct Settings {
32    /// Reverse the order of commits
33    pub reverse_commit_order: bool,
34    /// Debug printing and drawing
35    pub debug: bool,
36    /// Compact text-based graph
37    pub compact: bool,
38    /// Colored text-based graph
39    pub colored: bool,
40    /// Include remote branches?
41    pub include_remote: bool,
42    /// Formatting for commits
43    pub format: CommitFormat,
44    /// Text wrapping options
45    pub wrapping: Option<(Option<usize>, Option<usize>, Option<usize>)>,
46    /// Characters to use for text-based graph
47    pub characters: Characters,
48    /// Branch column sorting algorithm
49    pub branch_order: BranchOrder,
50    /// Settings for branches
51    pub branches: BranchSettings,
52    /// Regex patterns for finding branch names in merge commit summaries
53    pub merge_patterns: MergePatterns,
54}
55
56/// Helper for reading BranchSettings, required due to RegEx.
57#[derive(Serialize, Deserialize)]
58pub struct BranchSettingsDef {
59    /// Branch persistence
60    pub persistence: Vec<String>,
61    /// Branch ordering
62    pub order: Vec<String>,
63    /// Branch colors
64    pub terminal_colors: ColorsDef,
65    /// Branch colors for SVG output
66    pub svg_colors: ColorsDef,
67}
68
69/// Helper for reading branch colors, required due to RegEx.
70#[derive(Serialize, Deserialize)]
71pub struct ColorsDef {
72    matches: Vec<(String, Vec<String>)>,
73    unknown: Vec<String>,
74}
75
76impl BranchSettingsDef {
77    /// The Git-Flow model.
78    pub fn git_flow() -> Self {
79        BranchSettingsDef {
80            persistence: vec![
81                r"^(master|main)$".to_string(),
82                r"^(develop|dev)$".to_string(),
83                r"^feature.*$".to_string(),
84                r"^release.*$".to_string(),
85                r"^hotfix.*$".to_string(),
86                r"^bugfix.*$".to_string(),
87            ],
88            order: vec![
89                r"^(master|main)$".to_string(),
90                r"^(hotfix|release).*$".to_string(),
91                r"^(develop|dev)$".to_string(),
92            ],
93            terminal_colors: ColorsDef {
94                matches: vec![
95                    (
96                        r"^(master|main)$".to_string(),
97                        vec!["bright_blue".to_string()],
98                    ),
99                    (
100                        r"^(develop|dev)$".to_string(),
101                        vec!["bright_yellow".to_string()],
102                    ),
103                    (
104                        r"^(feature|fork/).*$".to_string(),
105                        vec!["bright_magenta".to_string(), "bright_cyan".to_string()],
106                    ),
107                    (r"^release.*$".to_string(), vec!["bright_green".to_string()]),
108                    (
109                        r"^(bugfix|hotfix).*$".to_string(),
110                        vec!["bright_red".to_string()],
111                    ),
112                    (r"^tags/.*$".to_string(), vec!["bright_green".to_string()]),
113                ],
114                unknown: vec!["white".to_string()],
115            },
116
117            svg_colors: ColorsDef {
118                matches: vec![
119                    (r"^(master|main)$".to_string(), vec!["blue".to_string()]),
120                    (r"^(develop|dev)$".to_string(), vec!["orange".to_string()]),
121                    (
122                        r"^(feature|fork/).*$".to_string(),
123                        vec!["purple".to_string(), "turquoise".to_string()],
124                    ),
125                    (r"^release.*$".to_string(), vec!["green".to_string()]),
126                    (r"^(bugfix|hotfix).*$".to_string(), vec!["red".to_string()]),
127                    (r"^tags/.*$".to_string(), vec!["green".to_string()]),
128                ],
129                unknown: vec!["gray".to_string()],
130            },
131        }
132    }
133
134    /// Simple feature-based model.
135    pub fn simple() -> Self {
136        BranchSettingsDef {
137            persistence: vec![r"^(master|main)$".to_string()],
138            order: vec![r"^tags/.*$".to_string(), r"^(master|main)$".to_string()],
139            terminal_colors: ColorsDef {
140                matches: vec![
141                    (
142                        r"^(master|main)$".to_string(),
143                        vec!["bright_blue".to_string()],
144                    ),
145                    (r"^tags/.*$".to_string(), vec!["bright_green".to_string()]),
146                ],
147                unknown: vec![
148                    "bright_yellow".to_string(),
149                    "bright_green".to_string(),
150                    "bright_red".to_string(),
151                    "bright_magenta".to_string(),
152                    "bright_cyan".to_string(),
153                ],
154            },
155
156            svg_colors: ColorsDef {
157                matches: vec![
158                    (r"^(master|main)$".to_string(), vec!["blue".to_string()]),
159                    (r"^tags/.*$".to_string(), vec!["green".to_string()]),
160                ],
161                unknown: vec![
162                    "orange".to_string(),
163                    "green".to_string(),
164                    "red".to_string(),
165                    "purple".to_string(),
166                    "turquoise".to_string(),
167                ],
168            },
169        }
170    }
171
172    /// Very simple model without any defined branch roles.
173    pub fn none() -> Self {
174        BranchSettingsDef {
175            persistence: vec![],
176            order: vec![],
177            terminal_colors: ColorsDef {
178                matches: vec![],
179                unknown: vec![
180                    "bright_blue".to_string(),
181                    "bright_yellow".to_string(),
182                    "bright_green".to_string(),
183                    "bright_red".to_string(),
184                    "bright_magenta".to_string(),
185                    "bright_cyan".to_string(),
186                ],
187            },
188
189            svg_colors: ColorsDef {
190                matches: vec![],
191                unknown: vec![
192                    "blue".to_string(),
193                    "orange".to_string(),
194                    "green".to_string(),
195                    "red".to_string(),
196                    "purple".to_string(),
197                    "turquoise".to_string(),
198                ],
199            },
200        }
201    }
202}
203
204/// Settings defining branching models
205pub struct BranchSettings {
206    /// Branch persistence
207    pub persistence: Vec<Regex>,
208    /// Branch ordering
209    pub order: Vec<Regex>,
210    /// Branch colors
211    pub terminal_colors: Vec<(Regex, Vec<String>)>,
212    /// Colors for branches not matching any of `colors`
213    pub terminal_colors_unknown: Vec<String>,
214    /// Branch colors for SVG output
215    pub svg_colors: Vec<(Regex, Vec<String>)>,
216    /// Colors for branches not matching any of `colors` for SVG output
217    pub svg_colors_unknown: Vec<String>,
218}
219
220impl BranchSettings {
221    pub fn from(def: BranchSettingsDef) -> Result<Self, Error> {
222        let persistence = def
223            .persistence
224            .iter()
225            .map(|str| Regex::new(str))
226            .collect::<Result<Vec<_>, Error>>()?;
227
228        let order = def
229            .order
230            .iter()
231            .map(|str| Regex::new(str))
232            .collect::<Result<Vec<_>, Error>>()?;
233
234        let terminal_colors = def
235            .terminal_colors
236            .matches
237            .into_iter()
238            .map(|(str, vec)| Regex::new(&str).map(|re| (re, vec)))
239            .collect::<Result<Vec<_>, Error>>()?;
240
241        let terminal_colors_unknown = def.terminal_colors.unknown;
242
243        let svg_colors = def
244            .svg_colors
245            .matches
246            .into_iter()
247            .map(|(str, vec)| Regex::new(&str).map(|re| (re, vec)))
248            .collect::<Result<Vec<_>, Error>>()?;
249
250        let svg_colors_unknown = def.svg_colors.unknown;
251
252        Ok(BranchSettings {
253            persistence,
254            order,
255            terminal_colors,
256            terminal_colors_unknown,
257            svg_colors,
258            svg_colors_unknown,
259        })
260    }
261}
262
263/// RegEx patterns for extracting branch names from merge commit summaries.
264pub struct MergePatterns {
265    /// The patterns. Evaluated in the given order.
266    pub patterns: Vec<Regex>,
267}
268
269impl Default for MergePatterns {
270    fn default() -> Self {
271        MergePatterns {
272            patterns: vec![
273                // GitLab pull request
274                Regex::new(r"^Merge branch '(.+)' into '.+'$").unwrap(),
275                // Git default
276                Regex::new(r"^Merge branch '(.+)' into .+$").unwrap(),
277                // Git default into main branch
278                Regex::new(r"^Merge branch '(.+)'$").unwrap(),
279                // GitHub pull request
280                Regex::new(r"^Merge pull request #[0-9]+ from .[^/]+/(.+)$").unwrap(),
281                // GitHub pull request (from fork?)
282                Regex::new(r"^Merge branch '(.+)' of .+$").unwrap(),
283                // BitBucket pull request
284                Regex::new(r"^Merged in (.+) \(pull request #[0-9]+\)$").unwrap(),
285            ],
286        }
287    }
288}
289
290/// The characters used for drawing text-based graphs.
291pub struct Characters {
292    pub chars: Vec<char>,
293}
294
295impl FromStr for Characters {
296    type Err = String;
297
298    fn from_str(str: &str) -> Result<Self, Self::Err> {
299        match str {
300            "normal" | "thin" | "n" | "t" => Ok(Characters::thin()),
301            "round" | "r" => Ok(Characters::round()),
302            "bold" | "b" => Ok(Characters::bold()),
303            "double" | "d" => Ok(Characters::double()),
304            "ascii" | "a" => Ok(Characters::ascii()),
305            _ => Err(format!("Unknown characters/style '{}'. Must be one of [normal|thin|round|bold|double|ascii]", str)),
306        }
307    }
308}
309
310impl Characters {
311    /// Default/thin graphs
312    pub fn thin() -> Self {
313        Characters {
314            chars: " ●○│─┼└┌┐┘┤├┴┬<>".chars().collect(),
315        }
316    }
317    /// Graphs with rounded corners
318    pub fn round() -> Self {
319        Characters {
320            chars: " ●○│─┼╰╭╮╯┤├┴┬<>".chars().collect(),
321        }
322    }
323    /// Bold/fat graphs
324    pub fn bold() -> Self {
325        Characters {
326            chars: " ●○┃━╋┗┏┓┛┫┣┻┳<>".chars().collect(),
327        }
328    }
329    /// Double-lined graphs
330    pub fn double() -> Self {
331        Characters {
332            chars: " ●○║═╬╚╔╗╝╣╠╩╦<>".chars().collect(),
333        }
334    }
335    /// ASCII-only graphs
336    pub fn ascii() -> Self {
337        Characters {
338            chars: " *o|-+'..'||++<>".chars().collect(),
339        }
340    }
341
342    pub fn reverse(self) -> Self {
343        let mut chars = self.chars;
344
345        chars.swap(6, 8);
346        chars.swap(7, 9);
347        chars.swap(10, 11);
348        chars.swap(12, 13);
349        chars.swap(14, 15);
350
351        Characters { chars }
352    }
353}