hglib/commands/
summary.rs1use std::collections::HashMap;
6
7use crate::client::{Client, HglibError, Runner};
8use crate::{runcommand, MkArg};
9
10pub struct Arg {
11 pub remote: bool,
12}
13
14impl Default for Arg {
15 fn default() -> Self {
16 Self { remote: false }
17 }
18}
19
20impl Arg {
21 fn run(&self, client: &mut Client) -> Result<(Vec<u8>, i32), HglibError> {
22 runcommand!(client, "summary", &[""], "--remote", self.remote)
23 }
24}
25
26#[derive(Debug, Default, PartialEq)]
27pub struct Revision {
28 pub rev: i64,
29 pub node: String,
30 pub tags: String,
31 pub message: String,
32}
33
34#[derive(Debug, Default, PartialEq)]
35pub struct Remote {
36 pub outgoing: u64,
37 pub incoming: u64,
38 pub outgoing_bookmarks: u64,
39 pub incoming_bookmarks: u64,
40}
41
42#[derive(Debug, Default, PartialEq)]
43pub struct Mq {
44 pub applied: u64,
45 pub unapplied: u64,
46}
47
48#[derive(Debug, Default, PartialEq)]
49pub struct Summary {
50 pub parent: Vec<Revision>,
51 pub branch: String,
52 pub commit: bool,
53 pub update: u32,
54 pub remote: Option<Remote>,
55 pub mq: Mq,
56 pub others: HashMap<String, String>,
57}
58
59impl Client {
60 pub fn summary(&mut self, x: Arg) -> Result<Summary, HglibError> {
61 let (data, _) = x.run(self)?;
62
63 let mut summary = Summary::default();
64 let mut wait_message = false;
65
66 for line in data.split(|x| *x == b'\n').filter(|x| !x.is_empty()) {
67 if wait_message {
68 let message = String::from_utf8(line[1..].to_vec()).unwrap();
69 summary.parent.last_mut().unwrap().message = message;
70 wait_message = false;
71 continue;
72 }
73
74 let i = line.iter().position(|x| *x == b':').unwrap();
75 let name = &line[..i];
76 let value = &line[i + 2..];
77
78 match name {
79 b"parent" => {
80 let iter = &mut value.iter();
81 let first = iter.next().unwrap();
82 let (neg, init) = if *first == b'-' {
83 (true, 0)
84 } else {
85 (false, i64::from(*first - b'0'))
86 };
87
88 let rev = iter
89 .take_while(|x| **x != b':')
90 .fold(init, |r, x| r * 10 + i64::from(*x - b'0'));
91 let rev = if neg { -rev } else { rev };
92
93 let node = iter
94 .take_while(|x| **x != b' ')
95 .map(|x| *x as char)
96 .collect::<String>();
97
98 let mut tags = iter.map(|x| *x as char).collect::<String>();
99 if !tags.is_empty() {
100 let empty = " (empty repository)";
101 if tags.ends_with(empty) {
102 tags.replace_range(tags.len() - empty.len().., "")
103 }
104 }
105
106 summary.parent.push(Revision {
107 rev,
108 node,
109 tags,
110 message: "".to_string(),
111 });
112
113 wait_message = rev != -1;
114 }
115 b"branch" => {
116 summary.branch = String::from_utf8(value.to_vec()).unwrap();
117 }
118 b"commit" => {
119 let clean = b"(clean)";
120 summary.commit = line.windows(clean.len()).any(|w| w == clean);
121 }
122 b"update" => {
123 summary.update = if value == b"(current)" {
124 0
125 } else {
126 value
127 .iter()
128 .take_while(|x| **x != b' ')
129 .fold(0, |r, x| r * 10 + u32::from(*x - b'0'))
130 };
131 }
132 b"remote" => {
133 if !x.remote {
134 continue;
135 }
136
137 summary.remote = Some(if value == b"(synced)" {
138 Remote::default()
139 } else {
140 let mut rem = Remote::default();
141 let iter = &mut value.iter();
142 loop {
143 let n = iter
144 .take_while(|x| **x != b' ')
145 .fold(0, |r, x| r * 10 + u64::from(*x - b'0'));
146
147 let typ: Vec<_> = iter.take_while(|x| **x != b',').copied().collect();
148 match typ.as_slice() {
149 b"outgoing" => {
150 rem.outgoing = n;
151 }
152 b"outgoing bookmarks" => {
153 rem.outgoing_bookmarks = n;
154 }
155 b"incoming bookmarks" => {
156 rem.incoming_bookmarks = n;
157 }
158 _ => {
159 if typ.ends_with(b"incoming") {
160 rem.incoming = n;
161 }
162 }
163 }
164
165 if iter.next().is_none() {
166 break;
167 }
168 }
169 rem
170 });
171 }
172 b"mq" => {}
173 _ => {
174 let name = String::from_utf8(name.to_vec()).unwrap();
175 let value = String::from_utf8(value.to_vec()).unwrap();
176
177 summary.others.insert(name, value);
178 }
179 }
180 }
181
182 Ok(summary)
183 }
184}