gitfetch_rs/utils/
timeline.rs1use anyhow::Result;
2use regex::Regex;
3use std::process::Command;
4
5pub fn get_git_timeline_graph(vertical: bool) -> Result<String> {
6 let output = Command::new("git")
7 .args(&[
8 "--no-pager",
9 "log",
10 "--color=always",
11 "--graph",
12 "--all",
13 "--pretty=format:\"\"",
14 ])
15 .output()?;
16
17 if !output.status.success() {
18 return Err(anyhow::anyhow!("Failed to execute git log"));
19 }
20
21 let mut text = String::from_utf8_lossy(&output.stdout)
22 .to_string()
23 .replace("\"", "");
24
25 if vertical {
26 return Ok(text);
27 }
28
29 text = text
31 .chars()
32 .map(|c| match c {
33 '\\' => '/',
34 '/' => '\\',
35 '|' => '-',
36 _ => c,
37 })
38 .collect();
39
40 let ansi_pattern = Regex::new(r"\x1b\[[0-9;]*m")?;
41
42 let lines: Vec<_> = text.lines().collect();
43 let mut parsed_lines = Vec::new();
44
45 for line in lines {
46 let parts: Vec<_> = ansi_pattern.split(line).collect();
47 let codes: Vec<_> = ansi_pattern.find_iter(line).map(|m| m.as_str()).collect();
48
49 let mut current_color = String::new();
50 let mut parsed = Vec::new();
51
52 for (i, seg) in parts.iter().enumerate() {
53 for ch in seg.chars() {
54 parsed.push((ch, current_color.clone()));
55 }
56 if i < codes.len() {
57 let code = codes[i];
58 current_color = if code == "\x1b[0m" {
59 String::new()
60 } else {
61 code.to_string()
62 };
63 }
64 }
65 parsed_lines.push(parsed);
66 }
67
68 if parsed_lines.is_empty() {
69 return Ok(String::new());
70 }
71
72 let max_len = parsed_lines
73 .iter()
74 .map(|line| line.len())
75 .max()
76 .unwrap_or(0);
77 let padded: Vec<_> = parsed_lines
78 .iter()
79 .map(|line| {
80 let mut padded = line.clone();
81 padded.resize(max_len, (' ', String::new()));
82 padded
83 })
84 .collect();
85
86 let mut rotated = Vec::new();
87 for col in (0..max_len).rev() {
88 let mut new_row = Vec::new();
89 for row in 0..padded.len() {
90 new_row.push(padded[row][col].clone());
91 }
92 rotated.push(new_row);
93 }
94
95 let mut out_lines = Vec::new();
96 for row in rotated {
97 let mut cur_color = String::new();
98 let mut out_line = String::new();
99
100 for (ch, color) in row {
101 if color != cur_color {
102 out_line.push_str(&color);
103 cur_color = color.clone();
104 }
105 out_line.push(ch);
106 }
107
108 if !cur_color.is_empty() {
109 out_line.push_str("\x1b[0m");
110 }
111 out_lines.push(out_line);
112 }
113
114 Ok(out_lines.join("\n"))
115}