1#[derive(Debug, Clone, Default, PartialEq, Eq)]
3pub struct Status {
4 pub oid: Option<String>,
5 pub head: Option<String>,
7 pub upstream: Option<String>,
8 pub ahead_behind: Option<(u32, u32)>,
9 pub entries: Vec<Entry>,
10}
11
12#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct Entry {
15 pub x: char,
16 pub y: char,
17 pub unmerged: bool,
18 pub path: String,
20 pub orig: Option<String>,
22 pub submodule: Option<SubmoduleState>,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct SubmoduleState {
28 pub commit_changed: bool,
30 pub modified: bool,
32 pub untracked: bool,
34 pub head: String,
36 pub index: String,
37}
38
39pub type Numstat = std::collections::HashMap<String, Option<(u64, u64)>>;
41
42pub fn parse_numstat_z(bytes: &[u8]) -> Numstat {
44 let text = String::from_utf8_lossy(bytes);
45 let mut recs = text.split('\0');
46 let mut out = Numstat::new();
47 while let Some(rec) = recs.next() {
48 let mut f = rec.splitn(3, '\t');
49 let (Some(a), Some(d), Some(p)) = (f.next(), f.next(), f.next()) else { continue };
50 let path = if p.is_empty() {
51 recs.next();
52 recs.next().unwrap_or("")
53 } else {
54 p
55 };
56 out.insert(path.to_string(), a.parse().ok().zip(d.parse().ok()));
57 }
58 out
59}
60
61pub fn parse_status_v2(bytes: &[u8]) -> Status {
62 let text = String::from_utf8_lossy(bytes);
63 let mut recs = text.split('\0');
64 let mut st = Status::default();
65 while let Some(rec) = recs.next() {
66 if let Some(h) = rec.strip_prefix("# ") {
67 let (k, v) = h.split_once(' ').unwrap_or((h, ""));
68 match k {
69 "branch.oid" if v != "(initial)" => st.oid = Some(v.to_string()),
70 "branch.head" if v != "(detached)" => st.head = Some(v.to_string()),
71 "branch.upstream" => st.upstream = Some(v.to_string()),
72 "branch.ab" => {
73 let mut it = v.split(' ').map(|n| n.trim_start_matches(['+', '-']).parse().unwrap_or(0));
74 st.ahead_behind = Some((it.next().unwrap_or(0), it.next().unwrap_or(0)));
75 }
76 _ => {}
77 }
78 continue;
79 }
80 let fields: Vec<&str> = rec.split(' ').collect();
81 let xy = |s: &str| {
82 let mut c = s.chars();
83 (c.next().unwrap_or('.'), c.next().unwrap_or('.'))
84 };
85 let path_after = |n: usize| rec.splitn(n + 1, ' ').nth(n).unwrap_or("").to_string();
87 let sub = || {
89 let f = fields.get(2)?.strip_prefix('S')?.as_bytes();
90 Some(SubmoduleState {
91 commit_changed: f.first() == Some(&b'C'),
92 modified: f.get(1) == Some(&b'M'),
93 untracked: f.get(2) == Some(&b'U'),
94 head: fields.get(6)?.to_string(),
95 index: fields.get(7)?.to_string(),
96 })
97 };
98 let entry = match fields.first() {
99 Some(&"1") => {
100 let (x, y) = xy(fields[1]);
101 Entry { x, y, unmerged: false, path: path_after(8), orig: None, submodule: sub() }
102 }
103 Some(&"2") => {
104 let (x, y) = xy(fields[1]);
105 let orig = recs.next().map(str::to_string);
106 Entry { x, y, unmerged: false, path: path_after(9), orig, submodule: sub() }
107 }
108 Some(&"u") => {
109 let (x, y) = xy(fields[1]);
110 Entry { x, y, unmerged: true, path: path_after(10), orig: None, submodule: None }
111 }
112 Some(&"?") => Entry { x: '?', y: '?', unmerged: false, path: path_after(1), orig: None, submodule: None },
113 _ => continue,
114 };
115 st.entries.push(entry);
116 }
117 st
118}