1extern crate byteorder;
6extern crate subprocess;
7
8use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
9use std::env;
10use std::ffi::OsString;
11use std::fs::File;
12use std::io::{Cursor, Read, Write};
13use std::path::{Path, PathBuf};
14use std::result::Result;
15use subprocess::{Popen, PopenConfig, Redirection};
16
17pub trait Runner {
18 fn runcommand<'a>(
20 &mut self,
21 args: &'a [&str],
22 prompt: Option<Box<dyn Prompt + 'a>>,
23 ) -> Result<(Vec<u8>, i32), HglibError>;
24}
25
26pub trait Prompt {
27 fn call(&mut self, size: usize) -> &[u8];
28}
29
30#[derive(Debug)]
31pub struct Client {
32 server: Popen,
34 encoding: String,
36 path: PathBuf,
38}
39
40pub struct Basic {}
41
42#[derive(Debug)]
43pub struct HglibError {
44 pub code: i32,
45 pub out: Option<Vec<u8>>,
46 msg: String,
47}
48
49impl HglibError {
50 pub(crate) fn handle_err(x: Result<(Vec<u8>, i32), HglibError>) -> Result<bool, HglibError> {
51 match x {
52 Ok((_, code)) => Ok(code == 0),
53 Err(err) => {
54 if err.code == 0 {
55 Ok(true)
56 } else if err.code == 1 {
57 Ok(false)
58 } else {
59 Err(err)
60 }
61 }
62 }
63 }
64}
65
66impl<T: std::string::ToString> From<T> for HglibError {
67 fn from(err: T) -> HglibError {
68 HglibError {
69 code: -1,
70 out: None,
71 msg: err.to_string(),
72 }
73 }
74}
75
76impl Drop for Client {
77 fn drop(&mut self) {
78 self.close().unwrap();
79 }
80}
81
82impl Client {
83 pub fn open<P: AsRef<Path>>(
117 path: P,
118 encoding: &str,
119 configs: &[&str],
120 ) -> Result<Client, HglibError> {
121 let mut env: Vec<(OsString, OsString)> = env::vars_os().collect();
122 env.push((OsString::from("HGPLAIN"), OsString::from("1")));
123 if !encoding.is_empty() {
124 env.push((OsString::from("HGENCODING"), OsString::from(encoding)));
125 }
126
127 let path = path.as_ref().to_path_buf().canonicalize()?;
128 let path_str = path.to_str().unwrap();
129
130 let mut args = vec!["hg", "serve", "--cmdserver", "pipe", "-R", path_str];
131 for c in configs.iter() {
132 args.push("--config");
133 args.push(c);
134 }
135 let mut server = Popen::create(
136 &args,
137 PopenConfig {
138 stdout: Redirection::Pipe,
139 stdin: Redirection::Pipe,
140 stderr: Redirection::Pipe,
141 env: Some(env),
142 cwd: Some(OsString::from(path_str)),
143 ..Default::default()
144 },
145 )?;
146 let encoding = Client::read_hello(&mut server)?;
147 let client = Client {
148 server,
149 encoding,
150 path,
151 };
152 Ok(client)
153 }
154
155 pub fn close(&mut self) -> Result<(), HglibError> {
157 self.server.terminate()?;
158 self.server.wait()?;
159 Ok(())
160 }
161
162 pub fn get_path(&self) -> &PathBuf {
164 &self.path
165 }
166
167 fn read_hello(server: &mut Popen) -> Result<String, HglibError> {
168 let stdout = server.stdout.as_mut().unwrap();
169 let mut chan: Vec<u8> = vec![0; 1];
170 let n = stdout.read(&mut chan)?;
171 if n != 1 || chan[0] != b'o' {
172 return Err("Cannot read hello".into());
173 }
174
175 let len = stdout.read_u32::<BigEndian>()? as usize;
176 let mut data: Vec<u8> = vec![0; len];
177
178 let n = stdout.read(&mut data)?;
179 if n != len {
180 return Err("Cannot read hello (invalid length)".into());
181 }
182
183 let out = std::str::from_utf8(&data)?;
184 let out: Vec<&str> = out.split('\n').collect();
185
186 if !out[0].contains("capabilities: ") {
187 return Err("Cannot read hello: no capabilities ".into());
188 }
189
190 if !out[1].contains("encoding: ") {
191 return Err("Cannot read hello: no encoding ".into());
192 }
193
194 Ok(out[1]["encoding: ".len()..].to_string())
195 }
196
197 fn read_data(
198 mut to_read: usize,
199 output: &mut Vec<u8>,
200 stdout: &mut File,
201 ) -> Result<(), HglibError> {
202 let mut pos = output.len();
203 output.resize(pos + to_read, 0);
204 loop {
205 let n = stdout.read(&mut output[pos..])?;
206 if n == to_read {
207 break;
208 }
209 to_read -= n;
210 pos += n;
211 }
212 Ok(())
213 }
214
215 pub fn encoding(&self) -> &str {
216 &self.encoding
217 }
218}
219
220impl Runner for Client {
221 fn runcommand<'a>(
222 &mut self,
223 args: &'a [&str],
224 mut prompt: Option<Box<dyn Prompt + 'a>>,
225 ) -> Result<(Vec<u8>, i32), HglibError> {
226 let mut stdin = self.server.stdin.as_mut().unwrap();
231 let args_size: usize = args.iter().map(|arg| -> usize { arg.len() }).sum();
232 let size = args_size + args.len() - 1;
233 writeln!(&mut stdin, "runcommand")?;
234 stdin.write_u32::<BigEndian>(size as u32)?;
235 if let Some((first, args)) = args.split_first() {
236 write!(&mut stdin, "{}", first)?;
237 for arg in args {
238 write!(&mut stdin, "\0{}", arg)?;
239 }
240 }
241 stdin.flush()?;
242
243 let stdout = self.server.stdout.as_mut().unwrap();
248 let mut out = Vec::<u8>::with_capacity(4096);
249 let mut chan: Vec<u8> = vec![0; 1];
250 loop {
252 let n = stdout.read(&mut chan)?;
253 if n != 1 {
254 return Err("Empty stdout".into());
255 }
256 let len = stdout.read_u32::<BigEndian>()? as usize;
257 match chan[0] {
258 b'e' => {
259 let mut err = Vec::<u8>::with_capacity(512);
261 Client::read_data(len, &mut err, stdout)?;
262 }
265 b'o' => {
266 Client::read_data(len, &mut out, stdout)?;
267 }
268 b'r' => {
269 let mut code: Vec<u8> = vec![0; len];
270 stdout.read_exact(&mut code)?;
271 let mut cur = Cursor::new(&code);
272 let code = cur.read_i32::<BigEndian>()?;
273 return if code != 0 {
282 Err(HglibError {
283 code,
284 out: Some(out.clone()),
285 msg: String::from_utf8(out).unwrap(),
286 })
287 } else {
288 Ok((out, code))
289 };
290 }
291 b'L' => {
292 if let Some(prompt) = prompt.as_mut() {
293 let buf = prompt.call(len);
294 stdin.write_u32::<BigEndian>(buf.len() as u32)?;
295 stdin.write_all(buf)?;
296 stdin.flush()?;
297 } else {
298 stdin.write_u32::<BigEndian>(0)?;
299 stdin.flush()?;
300 return Err("Hglib error: something is expected on stdin, please implement a Prompt".into());
301 }
302 }
303 _ => {
304 return Err(format!("Hglib error: invalid channel {}", chan[0] as char).into());
305 }
306 }
307 }
308 }
309}
310
311impl Runner for Basic {
312 fn runcommand<'a>(
313 &mut self,
314 args: &'a [&str],
315 _: Option<Box<dyn Prompt + 'a>>,
316 ) -> Result<(Vec<u8>, i32), HglibError> {
317 let env: Vec<(OsString, OsString)> = env::vars_os().collect();
318 let mut command = Vec::with_capacity(args.len() + 1);
319 command.push("hg");
320 command.extend(args);
321
322 let mut process = Popen::create(
323 &command,
324 PopenConfig {
325 stdout: Redirection::Pipe,
326 cwd: Some(OsString::from(std::env::current_dir().unwrap())),
327 env: Some(env),
328 ..Default::default()
329 },
330 )?;
331
332 process.wait()?;
333
334 Ok((Vec::new(), 0))
335 }
336}