Ivzhh updated this revision to Diff 6724. Ivzhh added a comment.
- merge with stable - translate base85.c into rust code - move hgbase85 into independent module - add hgstorage crate - hg status implementation in rust REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D2057?vs=5238&id=6724 BRANCH phab-submit-D2057-2018-02-05 (bookmark) on default (branch) REVISION DETAIL https://phab.mercurial-scm.org/D2057 AFFECTED FILES rust/Cargo.lock rust/Cargo.toml rust/hgbase85/Cargo.toml rust/hgbase85/build.rs rust/hgbase85/src/base85.rs rust/hgbase85/src/cpython_ext.rs rust/hgbase85/src/lib.rs rust/hgcli/Cargo.toml rust/hgcli/build.rs rust/hgcli/src/main.rs rust/hgstorage/Cargo.toml rust/hgstorage/src/changelog.rs rust/hgstorage/src/config.rs rust/hgstorage/src/dirstate.rs rust/hgstorage/src/lib.rs rust/hgstorage/src/local_repo.rs rust/hgstorage/src/manifest.rs rust/hgstorage/src/matcher.rs rust/hgstorage/src/mpatch.rs rust/hgstorage/src/path_encoding.rs rust/hgstorage/src/repository.rs rust/hgstorage/src/revlog.rs rust/hgstorage/src/revlog_v1.rs rust/hgstorage/src/working_context.rs CHANGE DETAILS diff --git a/rust/hgstorage/src/working_context.rs b/rust/hgstorage/src/working_context.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/working_context.rs @@ -0,0 +1,108 @@ +use std::path::PathBuf; +use std::io::prelude::*; +use std::fs; +use std::collections::HashMap; +use std::collections::HashSet as Set; +use std::sync::{Arc, Mutex, RwLock}; + +use threadpool::ThreadPool; +use num_cpus; + +use dirstate::{CurrentState, DirState}; +use local_repo::LocalRepo; +use manifest::{FlatManifest, ManifestEntry}; +use changelog::ChangeLog; + +pub struct WorkCtx { + pub dirstate: Arc<RwLock<DirState>>, + pub file_revs: HashMap<PathBuf, ManifestEntry>, +} + +impl WorkCtx { + pub fn new( + dot_hg_path: Arc<PathBuf>, + manifest: Arc<FlatManifest>, + changelog: Arc<ChangeLog>, + ) -> Self { + let dirstate = DirState::new(dot_hg_path.join("dirstate")); + + let manifest_id = changelog.get_commit_info(&dirstate.p1); + + let rev = manifest + .inner + .read() + .unwrap() + .node_id_to_rev(&manifest_id.manifest_id) + .unwrap(); + + let file_revs = manifest.build_file_rev_mapping(&rev); + + let dirstate = Arc::new(RwLock::new(dirstate)); + + Self { + dirstate, + file_revs, + } + } + + pub fn status(&self, repo: &LocalRepo) -> CurrentState { + let mut state = self.dirstate + .write() + .unwrap() + .walk_dir(repo.repo_root.as_path(), &repo.matcher); + + if !state.lookup.is_empty() { + let ncpus = num_cpus::get(); + + let nworkers = if state.lookup.len() < ncpus { + state.lookup.len() + } else { + ncpus + }; + + let pool = ThreadPool::new(nworkers); + + let clean = Arc::new(Mutex::new(Set::new())); + let modified = Arc::new(Mutex::new(Set::new())); + + for f in state.lookup.drain() { + let rl = repo.get_filelog(f.as_path()); + let fl = Arc::new(repo.repo_root.join(f.as_path())); + + let (id, p1, p2) = { + let id = &self.file_revs[f.as_path()].id; + let gd = rl.read().unwrap(); + let rev = gd.node_id_to_rev(id).unwrap(); + + let p1 = gd.p1_nodeid(&rev); + let p2 = gd.p2_nodeid(&rev); + (id.clone(), p1, p2) + }; + + let clean = clean.clone(); + let modified = modified.clone(); + + pool.execute(move || { + let mut wfile = fs::File::open(fl.as_path()).unwrap(); + let mut content = Vec::<u8>::new(); + wfile.read_to_end(&mut content).unwrap(); + if rl.read().unwrap().check_hash(&content, &p1, &p2) == id { + clean.lock().unwrap().insert(f); + } else { + modified.lock().unwrap().insert(f); + } + }); + } + + pool.join(); + assert_eq!(pool.panic_count(), 0); + + let mut gd = modified.lock().unwrap(); + state.modified.extend(gd.drain()); + let mut gd = clean.lock().unwrap(); + state.clean.extend(gd.drain()); + } + + return state; + } +} diff --git a/rust/hgstorage/src/revlog_v1.rs b/rust/hgstorage/src/revlog_v1.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/revlog_v1.rs @@ -0,0 +1,422 @@ +use std::path::{Path, PathBuf}; +use std::io; +use std::io::{BufReader, Read, Seek, SeekFrom}; +use std::fs; +use std::cell::RefCell; +use std::sync::{Arc, RwLock}; +use std::collections::HashMap as Map; + +use byteorder::{BigEndian, ReadBytesExt}; +use flate2::read::ZlibDecoder; +use sha1::Sha1 as Sha; + +use revlog::*; +use revlog::NodeLookupError; +use revlog::NodeLookupError::*; +use mpatch::{patches, Patch}; + +pub const FLAG_INLINE_DATA: u32 = (1 << 16) as u32; +pub const FLAG_GENERALDELTA: u32 = (1 << 17) as u32; + +#[derive(Debug, Default)] +pub struct Entry { + offset_w_flags: u64, + len_compressed: u32, + len_uncompressesd: u32, + base_rev: u32, + link_rev: u32, + parent1_rev: u32, + parent2_rev: u32, + nodeid: [u8; 20], + padding: [u8; 12], +} + +pub fn read_entry<R: Read + ?Sized + ReadBytesExt>(rdr: &mut R) -> io::Result<Entry> { + let offset_w_flags = rdr.read_u64::<BigEndian>()?; + let len_compressed = rdr.read_u32::<BigEndian>()?; + let len_uncompressesd = rdr.read_u32::<BigEndian>()?; + let base_rev = rdr.read_u32::<BigEndian>()?; + let link_rev = rdr.read_u32::<BigEndian>()?; + let parent1_rev = rdr.read_u32::<BigEndian>()?; + let parent2_rev = rdr.read_u32::<BigEndian>()?; + let mut nodeid = [0_u8; 20]; + rdr.read_exact(&mut nodeid)?; + let mut padding = [0_u8; 12]; + rdr.read_exact(&mut padding)?; + + Ok(Entry { + offset_w_flags, + len_compressed, + len_uncompressesd, + base_rev, + link_rev, + parent1_rev, + parent2_rev, + nodeid, + padding, + }) +} + +impl Entry { + pub fn packed_size() -> u32 { + 8 + 4 * 6 + 20 + 12 + } + + fn offset(&self) -> u64 { + self.offset_w_flags >> 16 + } +} + +pub enum DFileFlag { + Inline, + Separated(PathBuf), +} + +use self::DFileFlag::*; + +pub enum CachedEntry { + Offset(u32), + Cached(Box<Entry>), +} + +use self::CachedEntry::*; + +impl RevEntry for CachedEntry { + fn p1(&self) -> u32 { + return match self { + &Offset(_) => panic!("this rev should have been cached."), + &Cached(ref ent) => ent.parent1_rev, + }; + } + + fn p2(&self) -> u32 { + return match self { + &Offset(_) => panic!("this rev should have been cached."), + &Cached(ref ent) => ent.parent2_rev, + }; + } + + fn node_id(&self) -> NodeId { + return match self { + &Offset(_) => panic!("this rev should have been cached."), + &Cached(ref ent) => NodeId::new(ent.nodeid.clone()), + }; + } +} + +pub struct RevlogIO { + index_file: PathBuf, + dflag: DFileFlag, + other_flags: u32, + node2rev: Map<NodeId, i32>, + revs: Vec<RefCell<CachedEntry>>, +} + +unsafe impl Send for RevlogIO {} +unsafe impl Sync for RevlogIO {} + +/// currently asssume RevlogNG v1 format, with parent-delta, without inline +impl RevlogIO { + pub fn get_factory() -> Self { + Self { + index_file: PathBuf::new(), + dflag: DFileFlag::Inline, + other_flags: 0, + node2rev: Map::new(), + revs: Vec::<RefCell<CachedEntry>>::new(), + } + } + + pub fn new(index_file: &Path) -> Self { + println!("create revlog for {:?}", index_file); + if !index_file.exists() { + panic!("index file must exist: {:?}", index_file); + } + + let mut f = BufReader::new(fs::File::open(index_file).unwrap()); + let flag = f.read_u32::<BigEndian>().unwrap(); + f.seek(SeekFrom::Start(0)).unwrap(); + + let dflag = if (flag & FLAG_INLINE_DATA) > 0 { + Inline + } else { + let data_file = index_file.with_extension("d"); + assert!(data_file.exists()); + Separated(data_file) + }; + + let other_flags = flag; + + let max_cap = (index_file.metadata().unwrap().len() as u32) / Entry::packed_size(); + + let mut node2rev = Map::new(); + let mut revs = Vec::with_capacity(max_cap as usize); + + loop { + match read_entry(&mut f) { + Ok(hd) => { + let tip = revs.len() as i32; + + let id = NodeId::new(hd.nodeid.clone()); + + node2rev.insert(id, tip); + revs.push(RefCell::new(Cached(Box::new(hd)))); + } + Err(_) => break, + } + } + + return Self { + index_file: index_file.to_path_buf(), + dflag, + other_flags, + node2rev, + revs, + }; + } + + /// outer functions, which serve as interface, should check argument validity, + /// the internal calls use execute without question and thus panic on errors + fn make_sure_cached(&self, r: &usize) { + let r = *r; + + let ofs_opt = match *self.revs[r].borrow() { + Offset(ofs) => Some(ofs), + _ => None, + }; + + if let Some(ofs) = ofs_opt { + let mut f = fs::File::open(&self.index_file).unwrap(); + f.seek(SeekFrom::Start(ofs as u64)).unwrap(); + + let ent: Entry = read_entry(&mut f).unwrap(); + self.revs[r].replace(Cached(Box::new(ent))); + } else { + } + } + + fn prepare_id(&self, r: &i32) -> Result<usize, NodeLookupError> { + let len = self.revs.len() as i32; + + if *r < -len || *r >= len { + return Err(KeyNotFound); + } + + let r = if *r < 0 { + (len + *r) as usize + } else { + *r as usize + }; + + self.make_sure_cached(&r); + + return Ok(r); + } + + fn is_general_delta(&self) -> bool { + (self.other_flags & FLAG_GENERALDELTA) > 0 + } + + fn get_content(&self, f: &mut io::BufReader<fs::File>, r: &usize) -> Option<Vec<u8>> { + let r = *r; + + self.make_sure_cached(&r); + + if let Cached(ref hd) = *self.revs[r].borrow() { + let req_len = hd.len_compressed as usize; + + if req_len == 0 { + return None; + } + + let mut buf: Vec<u8> = Vec::new(); + buf.resize(req_len, 0); + + let ofs: u64 = if r == 0 { + 0_u64 + Entry::packed_size() as u64 + } else { + match self.dflag { + Inline => hd.offset() + (r * (Entry::packed_size() as usize)) as u64, + Separated(_) => hd.offset(), + } + }; + + f.seek(SeekFrom::Start(ofs)).unwrap(); + + f.read(&mut buf[..]).unwrap(); + + let flag_byte = buf[0]; + + match flag_byte { + 120 => { + let mut dec = ZlibDecoder::new(&buf[..]); + + let mut out_buf: Vec<u8> = Vec::new(); + //out_buf.resize(hd.len_compressed as usize, 0); + dec.read_to_end(&mut out_buf).unwrap(); + + return Some(out_buf); + } + 0 => { + return Some(buf); + } + 115 => { + return Some(buf[1..].to_vec()); + } + _ => { + return None; + } + } + } else { + panic!("read delta content failed."); + } + } + + fn get_all_bins(&self, rs: &[usize]) -> Patch { + assert_ne!(rs.len(), 0); + + let mut fhandle = BufReader::new(match self.dflag { + Inline => fs::File::open(self.index_file.as_path()).unwrap(), + Separated(ref dfile) => fs::File::open(dfile).unwrap(), + }); + + let mut it = rs.iter().rev(); + + let base_r = it.next().unwrap(); + let base = self.get_content(&mut fhandle, base_r).unwrap(); + + let mut bins: Vec<Vec<u8>> = Vec::with_capacity(rs.len() - 1); + + while let Some(ref chld_r) = it.next() { + if let Some(bin) = self.get_content(&mut fhandle, chld_r) { + bins.push(bin); + } else { + } + } + + return Patch { base, bins }; + } +} + +impl Revlog for RevlogIO { + fn node_id_to_rev(&self, id: &NodeId) -> Result<i32, NodeLookupError> { + let rev = { + if let Some(st) = self.node2rev.get(id) { + Ok(st) + } else { + Err(KeyNotFound) + } + }; + + match rev { + Ok(r) => Ok(*r), + Err(err) => Err(err), + } + } + + fn rev(&self, r: &i32) -> Result<&RefCell<RevEntry>, NodeLookupError> { + let r = self.prepare_id(r).unwrap(); + + Ok(&self.revs[r]) + } + + fn delta_chain(&self, r: &i32) -> Result<Vec<usize>, NodeLookupError> { + let mut r = self.prepare_id(r).unwrap(); + + let mut res = vec![r]; + + loop { + if let Cached(ref rev) = *self.revs[r].borrow() { + if (r == (rev.base_rev as usize)) || (r == 0) { + break; + } + + r = if self.is_general_delta() { + rev.base_rev as usize + } else { + r - 1 + }; + res.push(r); + + self.make_sure_cached(&r); + } else { + panic!("the rev must has been cached."); + } + } + + return Ok(res); + } + + fn revision(&self, id: &i32) -> Result<Vec<u8>, NodeLookupError> { + if let Ok(chn) = self.delta_chain(id) { + let ptc = self.get_all_bins(&chn); + let res = patches(&ptc); + return Ok(res); + } else { + return Err(KeyNotFound); + } + } + + fn tip(&self) -> usize { + return self.revs.len() - 1; + } + + fn check_hash(&self, text: &[u8], p1: &NodeId, p2: &NodeId) -> NodeId { + let mut s = Sha::new(); + + if p2.node == NULL_ID.node { + s.update(&NULL_ID.node); + s.update(&p1.node); + } else { + let (a, b) = if p1.node < p2.node { + (p1, p2) + } else { + (p2, p1) + }; + s.update(&a.node); + s.update(&b.node); + } + s.update(text); + return NodeId::new(s.digest().bytes()); + } + + fn p1(&self, rev: &i32) -> i32 { + return self.rev(rev).unwrap().borrow().p1() as i32; + } + + fn p1_nodeid(&self, rev: &i32) -> NodeId { + let prev = self.rev(rev).unwrap().borrow().p1() as i32; + + if prev == -1 { + return NULL_ID.clone(); + } else { + return self.rev(&prev).unwrap().borrow().node_id().clone(); + } + } + + fn p2(&self, rev: &i32) -> i32 { + return self.rev(rev).unwrap().borrow().p2() as i32; + } + + fn p2_nodeid(&self, rev: &i32) -> NodeId { + let prev = self.rev(rev).unwrap().borrow().p2() as i32; + + if prev == -1 { + return NULL_ID.clone(); + } else { + return self.rev(&prev).unwrap().borrow().node_id().clone(); + } + } + + fn node_id(&self, rev: &i32) -> NodeId { + if *rev == -1 { + return NULL_ID.clone(); + } else { + return self.rev(rev).unwrap().borrow().node_id().clone(); + } + } + + fn create(&self, index_file: &Path) -> Arc<RwLock<Revlog>> { + return Arc::new(RwLock::new(RevlogIO::new(index_file))); + } +} diff --git a/rust/hgstorage/src/revlog.rs b/rust/hgstorage/src/revlog.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/revlog.rs @@ -0,0 +1,82 @@ +use std::path::Path; +use std::cell::RefCell; +use std::fmt; +use std::sync::{Arc, RwLock}; + +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] +pub struct NodeId { + pub node: [u8; 20], +} + +lazy_static! { + pub static ref NULL_ID: NodeId = { NodeId::new([0_u8; 20]) }; +} + +impl NodeId { + pub fn new(content: [u8; 20]) -> Self { + assert_eq!(content.len(), 20); + return Self { node: content }; + } + + pub fn new_from_bytes(bytes: &[u8]) -> Self { + assert_eq!(bytes.len(), 20); + + let mut content = [0_u8; 20]; + + content.copy_from_slice(bytes); + + return Self { node: content }; + } + + pub fn null_id() -> Self { + NULL_ID.clone() + } + + pub fn is_valid(&self) -> bool { + self.node.len() <= 20 + } + + pub fn hex_len() -> usize { + 40 + } + pub fn bin_len() -> usize { + 20 + } +} + +impl fmt::Display for NodeId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for &byte in self.node.iter() { + write!(f, "{:02X}", byte).expect("Unable to write"); + } + return Ok(()); + } +} + +#[derive(Debug)] +pub enum NodeLookupError { + KeyNotFound, + AmbiguousKeys, +} + +pub trait RevEntry { + fn p1(&self) -> u32; + fn p2(&self) -> u32; + fn node_id(&self) -> NodeId; +} + +pub trait Revlog: Send + Sync { + fn node_id_to_rev(&self, id: &NodeId) -> Result<i32, NodeLookupError>; + fn rev(&self, id: &i32) -> Result<&RefCell<RevEntry>, NodeLookupError>; + fn delta_chain(&self, id: &i32) -> Result<Vec<usize>, NodeLookupError>; + fn revision(&self, id: &i32) -> Result<Vec<u8>, NodeLookupError>; + fn tip(&self) -> usize; + fn check_hash(&self, text: &[u8], p1: &NodeId, p2: &NodeId) -> NodeId; + fn p1(&self, rev: &i32) -> i32; + fn p1_nodeid(&self, rev: &i32) -> NodeId; + fn p2(&self, rev: &i32) -> i32; + fn p2_nodeid(&self, rev: &i32) -> NodeId; + fn node_id(&self, rev: &i32) -> NodeId; + + fn create(&self, index_file: &Path) -> Arc<RwLock<Revlog>>; +} diff --git a/rust/hgstorage/src/repository.rs b/rust/hgstorage/src/repository.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/repository.rs @@ -0,0 +1,5 @@ +use dirstate::CurrentState; + +pub trait Repository { + fn status(&self) -> CurrentState; +} diff --git a/rust/hgstorage/src/path_encoding.rs b/rust/hgstorage/src/path_encoding.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/path_encoding.rs @@ -0,0 +1,190 @@ +use std::path::{Path, PathBuf}; + +const HEX_DIGIT: [char; 16] = [ + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' +]; + +fn hex_encode(c: usize) -> (char, char) { + (HEX_DIGIT[c >> 4], HEX_DIGIT[c & 15]) +} + +fn escape(buf: &mut Vec<char>, c: &char) { + let c = *c as usize; + assert!(c < 256); + + buf.push('~'); + let res = hex_encode(c); + buf.push(res.0); + buf.push(res.1); +} + +fn encode_dir(p: &String) -> String { + let mut ps: Vec<char> = p.chars().collect(); + let len = ps.len(); + + if len >= 2 && ps[len - 2] == '.' && (ps[len - 1] == 'i' || ps[len - 1] == 'd') { + ps.extend(".hg".chars()); + } else if len >= 3 && ps[len - 3] == '.' && ps[len - 2] == 'h' && ps[len - 1] == 'g' { + ps.extend(".hg".chars()); + } + + return ps.into_iter().collect(); +} + +fn encode_fn(p: &String) -> String { + let mut ps: Vec<char> = Vec::new(); + + for c in p.bytes() { + match c { + 0...32 | 126...255 => escape(&mut ps, &char::from(c)), + 65...90 => { + // A...Z | _ + ps.push('_'); + ps.push(char::from(c - 65 + 97)); + } + 95 => { + ps.push('_'); + ps.push('_'); + } + _ => { + let c = char::from(c); + match c { + '\\' | ':' | '*' | '?' | '"' | '<' | '>' | '|' => escape(&mut ps, &c), + _ => ps.push(c), + } + } + } + } + + return ps.into_iter().collect(); +} + +pub fn aux_encode(p: &String) -> String { + let mut ps: Vec<char> = Vec::new(); + + let ch: Vec<char> = p.chars().collect(); + let len = ch.len(); + + if ch[0] == '.' || ch[0] == ' ' { + escape(&mut ps, &ch[0]); + ps.extend(ch[1..].iter()); + } else { + let dotpos = { + let mut i = 0; + loop { + if i < ch.len() && ch[i] == '.' { + break i as i32; + } else if i >= ch.len() { + break -1 as i32; + } + + i += 1; + } + }; + + let l = if dotpos == -1 { + ch.len() + } else { + dotpos as usize + }; + + let mut is_aux = false; + let mut cursor: usize; + + if l == 3 { + let key: String = ch[..3].into_iter().collect(); + if key == "aux" || key == "con" || key == "prn" || key == "nul" { + ps.extend(ch[..2].iter()); + escape(&mut ps, &ch[2]); + is_aux = true; + } + } else if l == 4 { + let key: String = ch[..3].into_iter().collect(); + if (key == "com" || key == "lpt") && '1' <= ch[3] && ch[3] <= '9' { + ps.extend(ch[..2].iter()); + escape(&mut ps, &ch[2]); + ps.push(ch[3]); + is_aux = true; + } + } + + if !is_aux { + ps.extend(ch[..l].iter()); + } + + cursor = l; + + if cursor < len - 1 { + ps.extend(ch[cursor..(len - 1)].iter()); + cursor = len - 1; + } + + if cursor == len - 1 { + if ch[cursor] == '.' || ch[cursor] == ' ' { + escape(&mut ps, &ch[cursor]); + } else { + ps.push(ch[cursor]); + } + } + } + + return ps.into_iter().collect(); +} + +pub fn encode_path(p: &Path) -> PathBuf { + let mut res = PathBuf::new(); + + let leaves: Vec<String> = p.iter().map(|s| s.to_str().unwrap().to_string()).collect(); + + let mut i = 0; + + assert_ne!(leaves.len(), 0); + + while i < leaves.len() - 1 { + let leaf = &leaves[i]; + let leaf = encode_dir(leaf); + let leaf = encode_fn(&leaf); + let leaf = aux_encode(&leaf); + + res.push(leaf); + i += 1; + } + + let leaf = &leaves[leaves.len() - 1]; + let leaf = encode_fn(&leaf); + let mut leaf = aux_encode(&leaf); + leaf.push_str(".i"); + res.push(leaf); + + return res; +} + +#[cfg(test)] +mod test { + use std::path::Path; + use super::*; + + #[test] + fn test_hgstorage_path_encoding() -> () { + assert_eq!( + "~2efoo/au~78.txt/txt.aux/co~6e/pr~6e/nu~6c/foo~2e", + encode_path(&Path::new(".foo/aux.txt/txt.aux/con/prn/nul/foo.")) + .to_str() + .unwrap() + ); + + assert_eq!( + "foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o", + encode_path(&Path::new("foo.i/bar.d/bla.hg/hi:world?/HELLO")) + .to_str() + .unwrap() + ); + + assert_eq!( + "~2ecom1com2/lp~749.lpt4.lpt1/conprn/com0/lpt0/foo~2e", + encode_path(&Path::new(".com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.")) + .to_str() + .unwrap() + ); + } +} diff --git a/rust/hgstorage/src/mpatch.rs b/rust/hgstorage/src/mpatch.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/mpatch.rs @@ -0,0 +1,158 @@ +use std::io::prelude::*; +use std::io::{Cursor, Seek}; +use std::io::SeekFrom::Start; +use std::vec::Vec; +use std::mem::swap; +use std::iter::Extend; + +use byteorder::{BigEndian, ReadBytesExt}; + +#[derive(Debug, Default)] +pub struct DiffHeader { + /// the line where previous chunk ends + prev_cnk_ln: u32, + /// the line where next chunk starts + next_cnk_ln: u32, + /// size of the current diff patch + diff_size: u32, +} + +#[derive(Clone, Debug)] +struct Fragment { + frag_len: u32, + frag_ofs: u32, +} + +impl Fragment { + pub fn new(len: u32, ofs: u32) -> Self { + Fragment { + frag_len: len, + frag_ofs: ofs, + } + } +} + +fn pull(dst: &mut Vec<Fragment>, src: &mut Vec<Fragment>, l: u32) { + let mut l = l; + + while l > 0 { + assert_ne!(src.len(), 0); + let f = src.pop().unwrap(); + if f.frag_len > l { + src.push(Fragment::new(f.frag_len - l, f.frag_ofs + l)); + dst.push(Fragment::new(l, f.frag_ofs)); + return; + } + l -= f.frag_len; + dst.push(f); + } +} + +fn mov(m: &mut Cursor<Vec<u8>>, dest: u32, src: u32, count: u32) { + m.seek(Start(src as u64)).unwrap(); + let mut buf: Vec<u8> = Vec::new(); + buf.resize(count as usize, 0); + + m.read_exact(&mut buf[..]).unwrap(); + m.seek(Start(dest as u64)).unwrap(); + m.write(&buf[..]).unwrap(); +} + +fn collect(m: &mut Cursor<Vec<u8>>, buf: u32, list: &Vec<Fragment>) -> Fragment { + let start = buf; + let mut buf = buf; + + for &Fragment { + frag_len: l, + frag_ofs: p, + } in list.iter().rev() + { + mov(m, buf, p, l); + buf += l; + } + return Fragment::new(buf - start, start); +} + +#[derive(Debug)] +pub struct Patch { + pub base: Vec<u8>, + pub bins: Vec<Vec<u8>>, +} + +pub fn patches(ptc: &Patch) -> Vec<u8> { + let &Patch { + base: ref a, + ref bins, + } = ptc; + + if bins.len() == 0 { + return a.iter().cloned().collect(); + } + + let plens: Vec<u32> = bins.iter().map(|it| it.len() as u32).collect(); + let pl: u32 = plens.iter().sum(); + let bl: u32 = a.len() as u32 + pl; + let tl: u32 = bl + bl + pl; + + if tl == 0 { + return a.iter().cloned().collect(); + } + + let (mut b1, mut b2) = (0_u32, bl); + + let mut m_buf = Vec::<u8>::new(); + m_buf.resize(tl as usize, 0); + + let mut m = Cursor::new(m_buf); + + m.write(&a[..]).unwrap(); + + let mut frags = vec![Fragment::new(a.len() as u32, b1 as u32)]; + + let mut pos: u32 = b2 + bl; + m.seek(Start(pos as u64)).unwrap(); + + for p in bins.iter() { + m.write(p).unwrap(); + } + + for plen in plens.iter() { + if frags.len() > 128 { + swap(&mut b1, &mut b2); + frags = vec![collect(&mut m, b1, &mut frags)]; + } + + let mut new: Vec<Fragment> = Vec::new(); + let end = pos + plen; + let mut last = 0; + + while pos < end { + m.seek(Start(pos as u64)).unwrap(); + + let p1 = m.read_u32::<BigEndian>().unwrap(); + let p2 = m.read_u32::<BigEndian>().unwrap(); + let l = m.read_u32::<BigEndian>().unwrap(); + + pull(&mut new, &mut frags, p1 - last); + assert_ne!(frags.len(), 0); + pull(&mut vec![], &mut frags, p2 - p1); + + new.push(Fragment::new(l, pos + 12)); + pos += l + 12; + last = p2; + } + + frags.extend(new.iter().rev().cloned()); + } + + let t = collect(&mut m, b2, &mut frags); + + m.seek(Start(t.frag_ofs as u64)).unwrap(); + + let mut res: Vec<u8> = Vec::new(); + res.resize(t.frag_len as usize, 0); + + m.read_exact(&mut res[..]).unwrap(); + + return res; +} diff --git a/rust/hgstorage/src/matcher.rs b/rust/hgstorage/src/matcher.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/matcher.rs @@ -0,0 +1,248 @@ +use std::fs; +use std::path::PathBuf; +use std::vec::Vec; +use std::io::BufReader; +use std::io::BufRead; + +use regex::{escape, RegexSet}; + +pub fn glob_to_re(pat: &str) -> String { + let mut res = String::new(); + + let pat: Vec<char> = pat.chars().collect(); + let mut i = 0; + let n = pat.len(); + + let mut group = 0; + + while i < n { + let c = pat[i]; + i += 1; + + match c { + '*' => { + if i < n && pat[i] == '*' { + i += 1; + if i < n && pat[i] == '/' { + i += 1; + res.push_str("(?:.*/)?"); + } else { + res.push_str(".*"); + } + } else { + res.push_str("[^/]*"); + } + } + '?' => { + res.push('.'); + } + '[' => { + let mut j = i; + if j < n && (pat[j] == '!' || pat[j] == ']') { + j += 1; + } + while j < n && pat[j] != ']' { + j += 1; + } + if j >= n { + res.push_str("\\["); + } else { + let mut stuff = String::new(); + + if pat[i] == '!' { + stuff.push('^'); + i += 1; + } else if pat[i] == '^' { + stuff.push('\\'); + stuff.push('^'); + i += 1; + } + + for cc in pat[i..j].iter().cloned() { + stuff.push(cc); + if cc == '\\' { + stuff.push('\\'); + } + } + i = j + 1; + + res.push('['); + res.push_str(stuff.as_str()); + res.push(']'); + } + } + '{' => { + group += 1; + res.push_str("(?:"); + } + '}' if group != 0 => { + res.push(')'); + group -= 1; + } + ',' if group != 0 => { + res.push('|'); + } + '\\' => { + if i < n { + res.push_str(escape(pat[i].to_string().as_str()).as_str()); + i += 1; + } else { + res.push_str(escape(pat[i].to_string().as_str()).as_str()); + } + } + _ => { + res.push_str(escape(c.to_string().as_str()).as_str()); + } + } + } + + let res = if cfg!(target_family = "unix") { + res + } else { + res.replace("/", "\\\\") + }; + + res +} + +fn relglob(pat: String) -> String { + let mut res = String::new(); + //res.push_str("(?:|.*/)"); + res.push_str(glob_to_re(pat.as_str()).as_str()); + //res.push_str("(?:/|$)"); + + res +} + +#[derive(Debug)] +pub struct Matcher { + pub inner: Option<RegexSet>, +} + +impl Matcher { + pub fn new(hgignores: &[PathBuf]) -> Self { + let mut inner = Vec::<String>::new(); + + for fname in hgignores { + if !fname.exists() { + continue; + } + + let fhnd = BufReader::new(fs::File::open(fname).unwrap()); + + #[derive(PartialEq)] + enum State { + None, + Glob, + Re, + } + + inner.push(relglob(".hg/*".to_owned())); + + let mut cur_state = State::None; + for ln in fhnd.lines() { + let ln = match ln { + Err(_) => break, + Ok(line) => line, + }; + + let ln = ln.trim(); + + if ln.is_empty() { + continue; + } + + if cur_state == State::None && !ln.starts_with("syntax:") { + eprintln!( + "syntax error in {:?}, please use 'syntax: [glob|regexp]'", + fname + ); + } + + if ln.starts_with("syntax:") { + let mut iter = ln.split_whitespace(); + assert_eq!("syntax:", iter.next().unwrap()); + let pat = iter.next().unwrap(); + + cur_state = if pat == "glob" { + State::Glob + } else if pat == "regexp" { + State::Re + } else { + panic!("unsupported pattern {} in file {:?}", pat, fname); + } + } else { + match cur_state { + State::None => (), + State::Glob => { + inner.push(relglob(ln.to_owned())); + } + State::Re => { + inner.push(ln.to_owned()); + } + } + } + } + } + + return match RegexSet::new(inner) { + Ok(inner) => Self { inner: Some(inner) }, + Err(e) => panic!("error in building ignore {:?}", e), + }; + } + + /// rp: relative path, relative to the root of repository + pub fn check_path_ignored(&self, rp: &str) -> bool { + if let Some(ref m) = self.inner { + m.is_match(rp) + } else { + false + } + } +} + +#[cfg(test)] +mod test { + use std::env; + use std::path::Path; + use tempdir::TempDir; + use super::*; + use regex::escape; + + #[test] + fn test_hgstorage_ignore() -> () { + println!( + "current dir {}", + env::current_dir().unwrap().to_str().unwrap() + ); + let tmp_dir = TempDir::new("cffi").unwrap(); + + println!("tmp_dir {:?}", tmp_dir.path()); + + ::prepare_testing_repo(tmp_dir.path()); + + let mut m = Matcher::new(); + let ignore_file = tmp_dir.path().join("cffi/.hgignore"); + + m.build_from_hgignore_files(&[ignore_file]); + + assert!(!m.check_path_ignored(&Path::new("a.py"))); + assert!(m.check_path_ignored(&Path::new("testing/__pycache__"))); + assert!(m.check_path_ignored(&Path::new("test/dfsdf/a.egg-info"))); + assert!(!m.check_path_ignored(&Path::new("a.egg-info.tmp"))); + } + + #[test] + fn test_hgstorage_globre() -> () { + //assert_eq!(escape(r"/"), r"\/"); + assert_eq!(glob_to_re(r"?"), r"."); + assert_eq!(glob_to_re(r"*"), r"[^/]*"); + assert_eq!(glob_to_re(r"**"), r".*"); + assert_eq!(glob_to_re(r"**/a"), r"(?:.*/)?a"); + //assert_eq!(glob_to_re(r"a/**/b"), r"a\/(?:.*/)?b"); + assert_eq!(glob_to_re(r"a/**/b"), r"a/(?:.*/)?b"); + assert_eq!(glob_to_re(r"[a*?!^][^b][!c]"), r"[a*?!^][\^b][^c]"); + assert_eq!(glob_to_re(r"{a,b}"), r"(?:a|b)"); + assert_eq!(glob_to_re(r".\*\?"), r"\.\*\?"); + } +} diff --git a/rust/hgstorage/src/manifest.rs b/rust/hgstorage/src/manifest.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/manifest.rs @@ -0,0 +1,119 @@ +use std::sync::{Arc, RwLock}; +use std::collections::HashMap as Map; +use std::path::PathBuf; +use std::str; + +use hex; + +use revlog::{NodeId, Revlog}; + +#[derive(Debug)] +pub struct ManifestEntry { + pub id: NodeId, + pub flag: String, +} + +type FileRevMap = Map<PathBuf, ManifestEntry>; + +#[derive(Clone)] +pub struct FlatManifest { + pub inner: Arc<RwLock<Revlog>>, +} + +impl FlatManifest { + pub fn build_file_rev_mapping(&self, rev: &i32) -> FileRevMap { + let mut res = FileRevMap::new(); + + let content = self.inner.read().unwrap().revision(rev).unwrap(); + + let mut line_start = 0; + let mut prev_i: usize = 0; + + for i in 0..(content.len()) { + if content[i] == 0 { + prev_i = i; + } else if content[i] == 10 { + let file_name = str::from_utf8(&content[line_start..prev_i]) + .unwrap() + .to_string(); + + line_start = i + 1; + + let ent = if i - prev_i - 1 == NodeId::hex_len() { + let id = + NodeId::new_from_bytes(&hex::decode(&content[(prev_i + 1)..i]).unwrap()); + let flag = "".to_string(); + ManifestEntry { id, flag } + } else { + let id = NodeId::new_from_bytes(&hex::decode( + &content[(prev_i + 1)..(prev_i + 41)], + ).unwrap()); + let flag = str::from_utf8(&content[(prev_i + 41)..i]) + .unwrap() + .to_string(); + ManifestEntry { id, flag } + }; + + res.insert(PathBuf::from(file_name.as_str()), ent); + } + } + + return res; + } +} + +#[cfg(test)] +mod test { + use std::env; + use tempdir::TempDir; + use super::*; + use manifest::FlatManifest; + use std::sync::{Arc, RwLock}; + use revlog_v1::*; + + #[test] + fn test_hgstorage_manifest() -> () { + println!( + "current dir {}", + env::current_dir().unwrap().to_str().unwrap() + ); + let tmp_dir = TempDir::new("cffi").unwrap(); + + println!("tmp_dir {:?}", tmp_dir.path()); + + ::prepare_testing_repo(tmp_dir.path()); + let mfest = tmp_dir.path().join("cffi/.hg/store/00manifest.i"); + println!("mfest {:?}", mfest); + + assert!(mfest.exists()); + + let rvlg = RevlogIO::new(&mfest); + + assert_eq!(rvlg.tip(), 2957); + + let tip = rvlg.tip() as i32; + let node = rvlg.node_id(&tip); + println!("node rev{}: {}", tip, node); + + let p1r = rvlg.p1(&tip); + let p1id = rvlg.p1_nodeid(&tip); + println!("p1 rev{}: {}", p1r, p1id); + + let p2r = rvlg.p2(&tip); + let p2id = rvlg.p2_nodeid(&tip); + println!("p2 rev{}: {}", p2r, p2id); + + let content = rvlg.revision(&tip).unwrap(); + + let hash = rvlg.check_hash(&content, &p1id, &p2id); + println!("{}", str::from_utf8(&content).unwrap()); + + assert_eq!(hash, rvlg.rev(&tip).unwrap().borrow().node_id()); + + let manifest = FlatManifest { + inner: Arc::new(RwLock::new(rvlg)), + }; + + manifest.build_file_rev_mapping(&2957); + } +} diff --git a/rust/hgstorage/src/local_repo.rs b/rust/hgstorage/src/local_repo.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/local_repo.rs @@ -0,0 +1,177 @@ +use std; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, RwLock}; + +use lru_cache::LruCache; + +use repository::Repository; +use config; +use revlog::Revlog; +use revlog_v1 as rv1; +use matcher::Matcher; +use dirstate::CurrentState; +use path_encoding::encode_path; +use manifest::FlatManifest; +use working_context::WorkCtx; +use changelog::ChangeLog; + +const LRU_SIZE: usize = 100; + +type RevlogPtr = Arc<RwLock<Revlog>>; +type RevlogLRU = Arc<RwLock<LruCache<PathBuf, RevlogPtr>>>; + +pub struct LocalRepo { + pub repo_root: Arc<PathBuf>, + pub dot_hg_path: Arc<PathBuf>, + pub pwd: Arc<PathBuf>, + pub store_data: Arc<PathBuf>, + pub matcher: Matcher, + pub config: Arc<config::Configuration>, + pub revlog_factory: Arc<Revlog>, + pub revlog_db: RevlogLRU, + pub manifest: Arc<FlatManifest>, + pub changelog: Arc<ChangeLog>, + pub work_ctx: Arc<WorkCtx>, +} + +impl LocalRepo { + pub fn new(dash_r: Option<PathBuf>) -> Self { + let pwd = Arc::new(std::env::current_dir().unwrap()); + + let repo_root = Arc::new(match dash_r { + Some(p) => { + let dot_hg_path = p.join(".hg"); + if dot_hg_path.exists() { + p + } else { + panic!(format!( + ".hg folder not found for the path given by -R argument: {:?}", + p + )); + } + } + None => { + let mut root = pwd.as_path(); + loop { + let dot_hg_path = root.join(".hg"); + if dot_hg_path.exists() { + break root.to_path_buf(); + } + match root.parent() { + Some(p) => { + root = p; + } + None => panic!(".hg folder not found"), + } + } + } + }); + + let dot_hg_path = Arc::new(repo_root.join(".hg")); + let requires = dot_hg_path.join("requires"); + let config = Arc::new(config::Configuration::new(&requires)); + let store = dot_hg_path.join("store"); + let store_data = Arc::new(store.join("data")); + //let fn_changelog = store.join("00changelog.i"); + let fn_manifest = store.join("00manifest.i"); + let fn_changelog = store.join("00changelog.i"); + + let revlog_factory = match config.revlog_format { + config::RevlogFormat::V1 => Arc::new(rv1::RevlogIO::get_factory()), + _ => panic!("other revlog formats not supported yet."), + }; + + let manifest = Arc::new(FlatManifest { + inner: revlog_factory.create(fn_manifest.as_path()), + }); + + let changelog = Arc::new(ChangeLog { + inner: revlog_factory.create(fn_changelog.as_path()), + }); + + let matcher = { + let default_hgignore = repo_root.join(".hgignore"); + let mut matcher = Matcher::new(&[default_hgignore]); + matcher + }; + + let revlog_db = Arc::new(RwLock::new(LruCache::new(LRU_SIZE))); + + let work_ctx = Arc::new(WorkCtx::new( + dot_hg_path.clone(), + manifest.clone(), + changelog.clone(), + )); + + return Self { + repo_root, + dot_hg_path, + pwd, + store_data, + matcher, + config, + revlog_factory, + revlog_db, + manifest, + changelog, + work_ctx, + }; + } + + pub fn get_filelog(&self, fp: &Path) -> Arc<RwLock<Revlog>> { + let relpath = encode_path(fp); + let abspath = self.store_data.join(&relpath); + + if !abspath.exists() { + panic!(format!("path not exists: {:?}", abspath)); + } + + let mut gd = self.revlog_db.write().unwrap(); + + if !gd.contains_key(fp) { + let rl = self.revlog_factory.create(abspath.as_path()); + gd.insert(fp.to_path_buf(), rl); + } + + return gd.get_mut(fp).unwrap().clone(); + } +} + +impl Repository for LocalRepo { + fn status(&self) -> CurrentState { + self.work_ctx.status(&self) + } +} + +#[cfg(test)] +mod test { + use std::env; + use tempdir::TempDir; + use local_repo::LocalRepo; + use repository::Repository; + use dirstate::DirState; + + #[test] + fn test_hgstorage_dirstate() -> () { + println!( + "current dir {}", + env::current_dir().unwrap().to_str().unwrap() + ); + let tmp_dir = TempDir::new("cffi").unwrap(); + + println!("tmp_dir {:?}", tmp_dir.path()); + + ::prepare_testing_repo(tmp_dir.path()); + + let root = tmp_dir.path().join("cffi"); + + let dfile = tmp_dir.path().join("cffi/.hg/dirstate"); + let mut ds = DirState::new(dfile); + ds.parse_dirstate(); + println!("p1: {}", ds.p1); + + let repo = LocalRepo::new(Some(root)); + + println!("status: {:?}", repo.status()); + } +} diff --git a/rust/hgstorage/src/lib.rs b/rust/hgstorage/src/lib.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/lib.rs @@ -0,0 +1,64 @@ +extern crate byteorder; +extern crate flate2; +extern crate hex; +#[macro_use] +extern crate lazy_static; +extern crate lru_cache; +extern crate num_cpus; +extern crate regex; +extern crate sha1; +extern crate tempdir; +extern crate threadpool; +extern crate walkdir; + +use std::process::Command; +use std::path::Path; + +pub mod mpatch; +pub mod revlog; +pub mod revlog_v1; +pub mod changelog; +pub mod manifest; +pub mod path_encoding; +pub mod matcher; +pub mod dirstate; +pub mod repository; +pub mod local_repo; +pub mod config; +pub mod working_context; + +/// assume cffi repo is in the same level of hg repo +pub fn prepare_testing_repo(temp_dir: &Path) { + if !Path::new("../../../cffi/").exists() { + let hg_msg = Command::new("hg") + .args(&[ + "clone", + "https://iv...@bitbucket.org/ivzhh/cffi", + "../../../cffi/", + "-u", + "e8f05076085cd24d01ba1f5d6163fdee16e90103", + ]) + .output() + .unwrap(); + println!("stdout: {}", String::from_utf8_lossy(&hg_msg.stdout)); + println!("stderr: {}", String::from_utf8_lossy(&hg_msg.stderr)); + } + + let dst = temp_dir.join("cffi"); + let hg_msg = Command::new("hg") + .args(&[ + "clone", + "../../../cffi/", + "-u", + "e8f05076085cd24d01ba1f5d6163fdee16e90103", + dst.to_str().unwrap(), + ]) + .output() + .unwrap(); + if !hg_msg.stdout.is_empty() { + println!("stdout: {}", String::from_utf8_lossy(&hg_msg.stdout)); + } + if !hg_msg.stderr.is_empty() { + println!("stderr: {}", String::from_utf8_lossy(&hg_msg.stderr)); + } +} diff --git a/rust/hgstorage/src/dirstate.rs b/rust/hgstorage/src/dirstate.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/dirstate.rs @@ -0,0 +1,229 @@ +use std::str; +use std::path::{Path, PathBuf}; +use std::io::{Read, Result}; +use std::fs::File; +use std::collections::HashMap as Map; +#[cfg(target_family = "unix")] +use std::os::unix::fs::FileTypeExt; +use std::collections::HashSet as Set; +use std::time::{SystemTime, UNIX_EPOCH}; + +use byteorder::{BigEndian, ReadBytesExt}; +use walkdir::{DirEntry, WalkDir}; + +use matcher; +use revlog::*; + +#[derive(Debug, Default)] +pub struct DirStateEntry { + status: u8, + mode: u32, + /// size of file + size: u32, + mtime: u32, + /// length of file name + length: u32, +} + +pub fn read_dirstate_entry<R: Read + ?Sized + ReadBytesExt>(rdr: &mut R) -> Result<DirStateEntry> { + let status = rdr.read_u8()?; + let mode = rdr.read_u32::<BigEndian>()?; + let size = rdr.read_u32::<BigEndian>()?; + let mtime = rdr.read_u32::<BigEndian>()?; + let length = rdr.read_u32::<BigEndian>()?; + + Ok(DirStateEntry { + status, + mode, + size, + mtime, + length, + }) +} + +pub struct DirState { + pub p1: NodeId, + pub p2: NodeId, + pub path: PathBuf, + pub dmap: Map<PathBuf, DirStateEntry>, + + pub mtime: SystemTime, +} + +#[derive(Debug)] +pub struct CurrentState { + /// per status-call + pub added: Set<PathBuf>, + /// a.k.a forget + pub removed: Set<PathBuf>, + /// a.k.a missing + pub deleted: Set<PathBuf>, + pub modified: Set<PathBuf>, + /// the worker thread handling ignored will first add all sub files of the ignored dir/file + /// to the ignored set, later, when checking the remaining paths, if the path is in ignored set, + /// then remove them from ignored set + pub ignored: Set<PathBuf>, + pub unknown: Set<PathBuf>, + pub clean: Set<PathBuf>, + pub lookup: Set<PathBuf>, +} + +impl CurrentState { + pub fn new() -> Self { + Self { + added: Set::new(), + removed: Set::new(), + deleted: Set::new(), + modified: Set::new(), + ignored: Set::new(), + unknown: Set::new(), + clean: Set::new(), + lookup: Set::new(), + } + } +} + +impl DirState { + pub fn new(p: PathBuf) -> Self { + if !p.exists() { + panic!("dirstate file is missing") + } + + let mtime = p.metadata().unwrap().modified().unwrap(); + + let mut ret = Self { + p1: NULL_ID.clone(), + p2: NULL_ID.clone(), + path: p, + dmap: Map::new(), + + mtime, + }; + + ret.parse_dirstate(); + + return ret; + } + + pub fn parse_dirstate(&mut self) { + let mut dfile = File::open(&self.path).expect("Cannot open dirstate file"); + + dfile.read_exact(&mut self.p1.node).unwrap(); + dfile.read_exact(&mut self.p2.node).unwrap(); + + loop { + let entry: DirStateEntry = match read_dirstate_entry(&mut dfile) { + Ok(v) => v, + Err(_) => break, + }; + + let mut fname = vec![0u8; entry.length as usize]; + dfile.read_exact(fname.as_mut()).unwrap(); + + self.dmap + .entry(PathBuf::from(str::from_utf8(fname.as_ref()).unwrap())) + .or_insert(entry); + } + } + + #[cfg(target_family = "unix")] + fn _is_bad(entry: &DirEntry) -> bool { + entry.file_type().is_block_device() || entry.file_type().is_fifo() + || entry.file_type().is_char_device() || entry.file_type().is_symlink() + || entry.file_type().is_socket() + } + + #[cfg(not(target_family = "unix"))] + fn _is_bad(_entry: &DirEntry) -> bool { + false + } + + pub fn walk_dir(&mut self, root: &Path, mtc: &matcher::Matcher) -> CurrentState { + let mut grey = { + let mut grey = Set::new(); + grey.extend(self.dmap.keys().map(|s| s.as_path())); + grey + }; + + let mut res = CurrentState::new(); + + let walker = WalkDir::new(root).into_iter(); + + for entry in walker.filter_entry(|ent| { + if ent.file_type().is_dir() { + let mut p = ent.path().strip_prefix(root).unwrap().to_owned(); + p.push(""); + !mtc.check_path_ignored(p.to_str().unwrap()) + } else { + true + } + }) { + if let Ok(entry) = entry { + let pbuf = entry.path(); + let relpath = pbuf.strip_prefix(root).unwrap(); + + if DirState::_is_bad(&entry) { + continue; + } + + if !entry.file_type().is_dir() { + if self.dmap.contains_key(relpath) { + let stent = &self.dmap[relpath]; + grey.remove(relpath); + DirState::check_status(&mut res, pbuf, relpath, stent); + } else { + if !mtc.check_path_ignored(relpath.to_str().unwrap()) { + res.unknown.insert(relpath.to_path_buf()); + } + } + } + } + } + + for rem in grey.drain() { + if res.ignored.contains(rem) { + res.ignored.remove(rem); + } + + let relpath = rem; + let abspath = root.join(relpath); + + let stent = &self.dmap[relpath]; + + DirState::check_status(&mut res, &abspath, &relpath, stent); + } + + return res; + } + + fn check_status(res: &mut CurrentState, abspath: &Path, relpath: &Path, stent: &DirStateEntry) { + let pb = relpath.to_path_buf(); + + // the order here is very important + // if it is 'r' then it can be 'forget' or 'remove', + // so the file existence doesn't matter. + // other status all rely on file existence. + if stent.status == ('r' as u8) { + res.removed.insert(pb); + } else if !abspath.exists() { + res.deleted.insert(pb); + } else if stent.status == ('a' as u8) { + res.added.insert(pb); + } else { + let mtd = abspath.metadata().unwrap(); + + if mtd.len() != (stent.size as u64) { + res.modified.insert(pb); + } else if mtd.modified() + .unwrap() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() != (stent.mtime as u64) + { + res.lookup.insert(pb); + } else { + res.clean.insert(pb); + } + } + } +} diff --git a/rust/hgstorage/src/config.rs b/rust/hgstorage/src/config.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/config.rs @@ -0,0 +1,98 @@ +use std::default::Default; +use std::collections::{HashMap, HashSet}; +use std::io::{BufRead, BufReader}; +use std::fs::File; +use std::path::Path; + +pub enum RevlogFormat { + V0, + V1, + V2, +} + +impl Default for RevlogFormat { + fn default() -> Self { + RevlogFormat::V1 + } +} + +pub enum Compressor { + Zlib, + Zstd, + Gzip, + None, +} + +impl Default for Compressor { + fn default() -> Self { + Compressor::Zlib + } +} + +pub enum DeltaPolicy { + ParentDelta, + GeneralDelta, +} + +impl Default for DeltaPolicy { + fn default() -> Self { + DeltaPolicy::GeneralDelta + } +} + +#[derive(Default)] +pub struct Configuration { + pub requires: HashSet<String>, + pub reg_conf: HashMap<String, RegFn>, + pub revlog_format: RevlogFormat, + pub delta: DeltaPolicy, +} + +pub type RegFn = fn(&mut Configuration) -> (); + +impl Configuration { + pub fn new(path: &Path) -> Self { + let mut s: Configuration = Default::default(); + + s.register_all(); + + if path.exists() { + let f = File::open(path).unwrap(); + + let buffer = BufReader::new(&f); + + for line in buffer.lines() { + let key = line.unwrap(); + + if s.reg_conf.contains_key(&key) { + s.reg_conf[&key](&mut s); + } + + s.requires.insert(key); + } + } + + s + } + + pub fn register_conf(&mut self, key: &str, func: RegFn) { + self.reg_conf.insert(key.to_string(), func); + } + + fn register_all(&mut self) { + self.register_conf("revlogv1", |conf| { + conf.revlog_format = RevlogFormat::V1; + }); + self.register_conf("generaldelta", |conf| { + conf.delta = DeltaPolicy::GeneralDelta; + }); + } + + pub fn get_revlog_format(&self) -> RevlogFormat { + if self.requires.contains("revlogv1") { + RevlogFormat::V1 + } else { + RevlogFormat::V0 + } + } +} diff --git a/rust/hgstorage/src/changelog.rs b/rust/hgstorage/src/changelog.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/changelog.rs @@ -0,0 +1,37 @@ +use std::sync::{Arc, RwLock}; + +use hex; + +use revlog::{NodeId, Revlog}; + +#[derive(Clone)] +pub struct CommitInfo { + pub manifest_id: NodeId, + pub msg: Vec<u8>, +} + +#[derive(Clone)] +pub struct ChangeLog { + pub inner: Arc<RwLock<Revlog>>, +} + +impl ChangeLog { + pub fn get_commit_info(&self, id: &NodeId) -> CommitInfo { + let rev = self.inner.read().unwrap().node_id_to_rev(id).unwrap(); + + let mut content = self.inner.read().unwrap().revision(&rev).unwrap(); + + assert_eq!(content[NodeId::hex_len()], '\n' as u8); + + let manifest_id = { + let hex_id = &content[..NodeId::hex_len()]; + NodeId::new_from_bytes(&hex::decode(hex_id).unwrap()) + }; + + content.drain(..NodeId::hex_len()); + + let msg = content; + + CommitInfo { manifest_id, msg } + } +} diff --git a/rust/hgstorage/Cargo.toml b/rust/hgstorage/Cargo.toml new file mode 100644 --- /dev/null +++ b/rust/hgstorage/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "hgstorage" +version = "0.1.0" +authors = ["Sheng Mao <shng...@gmail.com>"] +license = "GPL-2.0" + +#build = "build.rs" + +[lib] +name = "hgstorage" +crate-type = ["cdylib", "rlib"] + +[features] +# localdev: detect Python in PATH and use files from source checkout. +default = ["localdev"] +localdev = [] + +[dependencies] +libc = "0.2.34" +byteorder = "1.0" +walkdir = "2" +tempdir = "0.3.6" +regex = "0.2.6" +threadpool = "1.7.1" +num_cpus = "1.0" +lazy_static = "1.0.0" +lru-cache = "0.1.1" +flate2 = { version = "1.0", features = ["rust_backend"], default-features = false } +sha1 = "0.6.0" +hex = "0.3.1" + diff --git a/rust/hgcli/src/main.rs b/rust/hgcli/src/main.rs --- a/rust/hgcli/src/main.rs +++ b/rust/hgcli/src/main.rs @@ -5,10 +5,13 @@ // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. -extern crate libc; +extern crate clap; extern crate cpython; +extern crate libc; extern crate python27_sys; +extern crate hgstorage; + use cpython::{NoArgs, ObjectProtocol, PyModule, PyResult, Python}; use libc::{c_char, c_int}; @@ -18,6 +21,9 @@ #[cfg(target_family = "unix")] use std::os::unix::ffi::{OsStrExt, OsStringExt}; +use hgstorage::local_repo; +use hgstorage::repository::Repository; + #[derive(Debug)] struct Environment { _exe: PathBuf, @@ -224,10 +230,41 @@ } fn main() { - let exit_code = match run() { - Err(err) => err, - Ok(()) => 0, - }; + let matches = clap::App::new("hg rust oxidation") + .arg( + clap::Arg::with_name("repository") + .short("c") + .long("repository") + .value_name("dash_r"), + ) + .subcommand(clap::SubCommand::with_name("r-status")) + .get_matches(); - std::process::exit(exit_code); + if let Some(_r_matches) = matches.subcommand_matches("r-status") { + let dash_r = match matches.value_of("dash_r") { + Some(dash_r) => Some(PathBuf::from(dash_r)), + None => None, + }; + let repo = local_repo::LocalRepo::new(dash_r); + let res = repo.status(); + for f in res.modified.iter() { + println!("M {}", f.to_str().unwrap()); + } + for f in res.added.iter() { + println!("A {}", f.to_str().unwrap()); + } + for f in res.removed.iter() { + println!("R {}", f.to_str().unwrap()); + } + for f in res.unknown.iter() { + println!("? {}", f.to_str().unwrap()); + } + } else { + let exit_code = match run() { + Err(err) => err, + Ok(()) => 0, + }; + + std::process::exit(exit_code); + } } diff --git a/rust/hgcli/build.rs b/rust/hgcli/build.rs --- a/rust/hgcli/build.rs +++ b/rust/hgcli/build.rs @@ -18,10 +18,10 @@ fn get_python_config() -> PythonConfig { // The python27-sys crate exports a Cargo variable defining the full // path to the interpreter being used. - let python = env::var("DEP_PYTHON27_PYTHON_INTERPRETER").expect( - "Missing DEP_PYTHON27_PYTHON_INTERPRETER; bad python27-sys crate?", - ); + let python = env::var("DEP_PYTHON27_PYTHON_INTERPRETER") + .expect("Missing DEP_PYTHON27_PYTHON_INTERPRETER; bad python27-sys crate?"); + println!("{}", python); if !Path::new(&python).exists() { panic!( "Python interpreter {} does not exist; this should never happen", @@ -33,8 +33,8 @@ let separator = "SEPARATOR STRING"; let script = "import sysconfig; \ -c = sysconfig.get_config_vars(); \ -print('SEPARATOR STRING'.join('%s=%s' % i for i in c.items()))"; + c = sysconfig.get_config_vars(); \ + print('SEPARATOR STRING'.join('%s=%s' % i for i in c.items()))"; let mut command = Command::new(&python); command.arg("-c").arg(script); @@ -110,6 +110,8 @@ } } + println!("have shared {}", have_shared(&config)); + // We need a Python shared library. if !have_shared(&config) { panic!("Detected Python lacks a shared library, which is required"); diff --git a/rust/hgcli/Cargo.toml b/rust/hgcli/Cargo.toml --- a/rust/hgcli/Cargo.toml +++ b/rust/hgcli/Cargo.toml @@ -17,6 +17,8 @@ [dependencies] libc = "0.2.34" +hgstorage = { path = "../hgstorage" } +clap = "" # We currently use a custom build of cpython and python27-sys with the # following changes: diff --git a/rust/hgbase85/src/lib.rs b/rust/hgbase85/src/lib.rs new file mode 100644 --- /dev/null +++ b/rust/hgbase85/src/lib.rs @@ -0,0 +1,104 @@ +#[macro_use] +extern crate cpython; +extern crate libc; +extern crate python27_sys; + +use python27_sys as ffi; + +pub mod base85; +pub mod cpython_ext; + +use std::{env, sync}; +use std::path::PathBuf; +use std::ffi::{CString, OsStr}; + +#[cfg(target_family = "unix")] +use std::os::unix::ffi::OsStrExt; + +static HG_EXT_REG: sync::Once = sync::ONCE_INIT; + +#[no_mangle] +pub fn init_all_hg_ext(_py: cpython::Python) { + HG_EXT_REG.call_once(|| unsafe { + base85::initbase85(); + }); +} + +#[derive(Debug)] +pub struct Environment { + _exe: PathBuf, + python_exe: PathBuf, + python_home: PathBuf, + mercurial_modules: PathBuf, +} + +// On UNIX, platform string is just bytes and should not contain NUL. +#[cfg(target_family = "unix")] +fn cstring_from_os<T: AsRef<OsStr>>(s: T) -> CString { + CString::new(s.as_ref().as_bytes()).unwrap() +} + +#[cfg(target_family = "windows")] +fn cstring_from_os<T: AsRef<OsStr>>(s: T) -> CString { + CString::new(s.as_ref().to_str().unwrap()).unwrap() +} + +fn set_python_home(env: &Environment) { + let raw = cstring_from_os(&env.python_home).into_raw(); + unsafe { + ffi::Py_SetPythonHome(raw); + } +} + +static PYTHON_ENV_START: sync::Once = sync::ONCE_INIT; + +/// the second half initialization code are copied from rust-cpython +/// fn pythonrun::prepare_freethreaded_python() +/// because this function is called mainly by `cargo test` +/// and the multi-thread nature requires to properly +/// set up threads and GIL. In the corresponding version, +/// prepare_freethreaded_python() is turned off, so the cargo +/// test features must be properly called. +pub fn set_py_env() { + PYTHON_ENV_START.call_once(|| { + let env = { + let exe = env::current_exe().unwrap(); + + let mercurial_modules = std::env::var("HGROOT").expect( + "must set mercurial's root folder (one layer above mercurial folder itself", + ); + + let python_exe = std::env::var("HGRUST_PYTHONEXE") + .expect("set PYTHONEXE to the full path of the python.exe file"); + + let python_home = std::env::var("HGRUST_PYTHONHOME").expect( + "if you don't want to use system one, set PYTHONHOME according to python doc", + ); + + Environment { + _exe: exe.clone(), + python_exe: PathBuf::from(python_exe), + python_home: PathBuf::from(python_home), + mercurial_modules: PathBuf::from(mercurial_modules), + } + }; + + set_python_home(&env); + + let program_name = cstring_from_os(&env.python_exe).as_ptr(); + unsafe { + ffi::Py_SetProgramName(program_name as *mut i8); + } + + unsafe { + if ffi::Py_IsInitialized() != 0 { + assert!(ffi::PyEval_ThreadsInitialized() != 0); + } else { + assert!(ffi::PyEval_ThreadsInitialized() == 0); + ffi::Py_InitializeEx(0); + ffi::PyEval_InitThreads(); + let _thread_state = ffi::PyEval_SaveThread(); + } + } + }); +} diff --git a/rust/hgbase85/src/cpython_ext.rs b/rust/hgbase85/src/cpython_ext.rs new file mode 100644 --- /dev/null +++ b/rust/hgbase85/src/cpython_ext.rs @@ -0,0 +1,26 @@ +use cpython::{PyBytes, PyObject, Py_ssize_t, Python, PythonObjectWithCheckedDowncast}; + +use python27_sys as ffi; + +use std; + +#[inline] +pub unsafe fn cast_from_owned_ptr_or_panic<T>(py: Python, p: *mut ffi::PyObject) -> T +where + T: PythonObjectWithCheckedDowncast, +{ + if p.is_null() { + panic!("NULL pointer detected.") + } else { + PyObject::from_owned_ptr(py, p).cast_into(py).unwrap() + } +} + +pub fn pybytes_new_without_copying(py: Python, len: Py_ssize_t) -> PyBytes { + unsafe { + if len <= 0 { + panic!("the request bytes length should be > 0.") + } + cast_from_owned_ptr_or_panic(py, ffi::PyBytes_FromStringAndSize(std::ptr::null(), len)) + } +} diff --git a/rust/hgbase85/src/base85.rs b/rust/hgbase85/src/base85.rs new file mode 100644 --- /dev/null +++ b/rust/hgbase85/src/base85.rs @@ -0,0 +1,387 @@ +use cpython::{exc, PyBytes, PyErr, PyObject, PyResult, Py_ssize_t, Python, PythonObject}; +use cpython::_detail::ffi; + +use std; +use std::{mem, sync}; +use super::cpython_ext; + +const B85CHARS: &[u8; 85] = + b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; +static mut B85DEC: [u8; 256] = [0; 256]; +static B85DEC_START: sync::Once = sync::ONCE_INIT; + +fn b85prep() { + B85DEC_START.call_once(|| { + for i in 0..mem::size_of_val(B85CHARS) { + unsafe { + B85DEC[B85CHARS[i] as usize] = (i + 1) as u8; + } + } + }); +} + +pub fn b85encode(py: Python, text: &str, pad: i32) -> PyResult<PyObject> { + let text = text.as_bytes(); + let tlen: Py_ssize_t = { text.len() as Py_ssize_t }; + let olen: Py_ssize_t = if pad != 0 { + ((tlen + 3) / 4 * 5) - 3 + } else { + let mut olen: Py_ssize_t = tlen % 4; + if olen > 0 { + olen += 1; + } + olen += tlen / 4 * 5; + olen + }; + + let out: PyBytes = cpython_ext::pybytes_new_without_copying(py, olen + 3); + + let dst = unsafe { + let buffer = ffi::PyBytes_AsString(out.as_object().as_ptr()) as *mut u8; + let length = ffi::PyBytes_Size(out.as_object().as_ptr()) as usize; + std::slice::from_raw_parts_mut(buffer, length) + }; + + let mut ptext = &text[..]; + let mut len = { ptext.len() }; + let mut dst_off: usize = 0; + + loop { + if len == 0 { + break; + } + + let mut acc: u32 = 0; + + for i in [24, 16, 8, 0].iter() { + let ch = ptext[0] as u32; + acc |= ch << i; + + ptext = &ptext[1..]; + len -= 1; + + if len == 0 { + break; + } + } + + for i in [4, 3, 2, 1, 0].iter() { + let val: usize = (acc % 85) as usize; + acc /= 85; + + dst[*i + dst_off] = B85CHARS[val]; + } + + dst_off += 5; + } + + if pad == 0 { + unsafe { + ffi::_PyString_Resize( + &mut out.as_object().as_ptr() as *mut *mut ffi::PyObject, + olen, + ); + } + } + + return Ok(out.into_object()); +} + +pub fn b85decode(py: Python, text: &str) -> PyResult<PyObject> { + let b85dec = unsafe { B85DEC }; + + let text = text.as_bytes(); + let len = { text.len() }; + let mut ptext = &text[..]; + let i = len % 5; + let olen_g: usize = len / 5 * 4 + { + if i > 0 { + i - 1 + } else { + 0 + } + }; + + let out: PyBytes = cpython_ext::pybytes_new_without_copying(py, olen_g as Py_ssize_t); + + let dst = unsafe { + let buffer = ffi::PyBytes_AsString(out.as_object().as_ptr()) as *mut u8; + let length = ffi::PyBytes_Size(out.as_object().as_ptr()) as usize; + std::slice::from_raw_parts_mut(buffer, length) + }; + let mut dst_off = 0; + + let mut i = 0; + while i < len { + let mut acc: u32 = 0; + let mut cap = len - i - 1; + if cap > 4 { + cap = 4 + } + for _ in 0..cap { + let c = b85dec[ptext[0] as usize] as i32 - 1; + ptext = &ptext[1..]; + if c < 0 { + return Err(PyErr::new::<exc::ValueError, _>( + py, + format!("bad base85 character at position {}", i), + )); + } + acc = acc * 85 + (c as u32); + i += 1; + } + if i < len { + i += 1; + let c = b85dec[ptext[0] as usize] as i32 - 1; + ptext = &ptext[1..]; + if c < 0 { + return Err(PyErr::new::<exc::ValueError, _>( + py, + format!("bad base85 character at position {}", i), + )); + } + /* overflow detection: 0xffffffff == "|NsC0", + * "|NsC" == 0x03030303 */ + if acc > 0x03030303 { + return Err(PyErr::new::<exc::ValueError, _>( + py, + format!("bad base85 character at position {}", i), + )); + } + + acc *= 85; + + if acc > (0xffffffff_u32 - (c as u32)) { + return Err(PyErr::new::<exc::ValueError, _>( + py, + format!("bad base85 character at position {}", i), + )); + } + acc += c as u32; + } + + let olen = olen_g - dst_off; + + cap = if olen < 4 { olen } else { 4 }; + + for _ in 0..(4 - cap) { + acc *= 85; + } + + if (cap > 0) && (cap < 4) { + acc += 0xffffff >> (cap - 1) * 8; + } + + for j in 0..cap { + acc = (acc << 8) | (acc >> 24); + dst[j + dst_off] = acc as u8; + } + + dst_off += cap; + } + + return Ok(out.into_object()); +} + +py_module_initializer!(base85, initbase85, PyInit_base85, |py, m| { + b85prep(); + m.add(py, "__doc__", "base85 module")?; + m.add(py, "b85encode", py_fn!(py, b85encode(text: &str, pad: i32)))?; + m.add(py, "b85decode", py_fn!(py, b85decode(text: &str)))?; + Ok(()) +}); + +#[cfg(test)] +mod test { + use cpython::Python; + + #[test] + fn test_encoder_abc_pad() -> () { + ::set_py_env(); + + let gil = Python::acquire_gil(); + let py = gil.python(); + ::init_all_hg_ext(py); + + let res: String = super::b85encode(py, "abc", 1).unwrap().extract(py).unwrap(); + assert_eq!(res, "VPazd"); + + let base85 = py.import("base85").unwrap(); + let res: String = base85 + .call(py, "b85encode", ("abc", 1), None) + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "VPazd"); + } + + #[test] + fn test_encoder_chinese_pad() -> () { + ::set_py_env(); + + let gil = Python::acquire_gil(); + let py = gil.python(); + ::init_all_hg_ext(py); + + let res: String = super::b85encode(py, "这是一个测试的例子", 1) + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "=)alfn6KoxfaJKU=CzCHua)PTgyg=9<*kqa"); + + let base85 = py.import("base85").unwrap(); + let res: String = base85 + .call(py, "b85encode", ("这是一个测试的例子", 1), None) + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "=)alfn6KoxfaJKU=CzCHua)PTgyg=9<*kqa"); + } + + #[test] + fn test_encoder_abc_no_pad() -> () { + ::set_py_env(); + + let gil = Python::acquire_gil(); + let py = gil.python(); + ::init_all_hg_ext(py); + + let res: String = super::b85encode(py, "abc", 0).unwrap().extract(py).unwrap(); + assert_eq!(res, "VPaz"); + + let base85 = py.import("base85").unwrap(); + let res: String = base85 + .call(py, "b85encode", ("abc", 0), None) + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "VPaz"); + } + + #[test] + fn test_encoder_chinese_no_pad() -> () { + ::set_py_env(); + + let gil = Python::acquire_gil(); + let py = gil.python(); + ::init_all_hg_ext(py); + + let res: String = super::b85encode(py, "这是一个测试的例子", 0) + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "=)alfn6KoxfaJKU=CzCHua)PTgyg=9<*kq"); + + let base85 = py.import("base85").unwrap(); + let res: String = base85 + .call(py, "b85encode", ("这是一个测试的例子", 0), None) + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "=)alfn6KoxfaJKU=CzCHua)PTgyg=9<*kq"); + } + + #[test] + fn test_decoder_abc_no_pad() -> () { + ::set_py_env(); + + let gil = Python::acquire_gil(); + let py = gil.python(); + ::init_all_hg_ext(py); + + let res: String = super::b85decode(py, "VPaz").unwrap().extract(py).unwrap(); + assert_eq!(res, "abc"); + + let base85 = py.import("base85").unwrap(); + let res: String = base85 + .call(py, "b85decode", ("VPaz",), None) + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "abc"); + } + + #[test] + fn test_decoder_abc_pad() -> () { + ::set_py_env(); + + let gil = Python::acquire_gil(); + let py = gil.python(); + ::init_all_hg_ext(py); + + let mut res: String = super::b85decode(py, "VPazd").unwrap().extract(py).unwrap(); + let len = { res.len() }; + res.truncate(len - 1); + assert_eq!(res, "abc"); + + let base85 = py.import("base85").unwrap(); + let mut res: String = base85 + .call(py, "b85decode", ("VPazd",), None) + .unwrap() + .extract(py) + .unwrap(); + let len = { res.len() }; + res.truncate(len - 1); + assert_eq!(res, "abc"); + } + + #[test] + fn test_decoder_chinese_pad() -> () { + ::set_py_env(); + + let gil = Python::acquire_gil(); + let py = gil.python(); + ::init_all_hg_ext(py); + + let mut res: String = super::b85decode(py, "=)alfn6KoxfaJKU=CzCHua)PTgyg=9<*kqa") + .unwrap() + .extract(py) + .unwrap(); + let len = { res.len() }; + res.truncate(len - 1); + assert_eq!(res, "这是一个测试的例子"); + + let base85 = py.import("base85").unwrap(); + let mut res: String = base85 + .call( + py, + "b85decode", + ("=)alfn6KoxfaJKU=CzCHua)PTgyg=9<*kqa",), + None, + ) + .unwrap() + .extract(py) + .unwrap(); + let len = { res.len() }; + res.truncate(len - 1); + assert_eq!(res, "这是一个测试的例子"); + } + + #[test] + fn test_decoder_chinese_no_pad() -> () { + ::set_py_env(); + + let gil = Python::acquire_gil(); + let py = gil.python(); + ::init_all_hg_ext(py); + + let res: String = super::b85decode(py, "=)alfn6KoxfaJKU=CzCHua)PTgyg=9<*kq") + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "这是一个测试的例子"); + + let base85 = py.import("base85").unwrap(); + let res: String = base85 + .call( + py, + "b85decode", + ("=)alfn6KoxfaJKU=CzCHua)PTgyg=9<*kq",), + None, + ) + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "这是一个测试的例子"); + } +} diff --git a/rust/hgbase85/build.rs b/rust/hgbase85/build.rs new file mode 100644 --- /dev/null +++ b/rust/hgbase85/build.rs @@ -0,0 +1,129 @@ +// build.rs -- Configure build environment for `hgcli` Rust package. +// +// Copyright 2017 Gregory Szorc <gregory.sz...@gmail.com> +// +// This software may be used and distributed according to the terms of the +// GNU General Public License version 2 or any later version. + +use std::collections::HashMap; +use std::env; +use std::path::Path; +use std::process::Command; + +struct PythonConfig { + python: String, + config: HashMap<String, String>, +} + +fn get_python_config() -> PythonConfig { + // The python27-sys crate exports a Cargo variable defining the full + // path to the interpreter being used. + let python = env::var("DEP_PYTHON27_PYTHON_INTERPRETER") + .expect("Missing DEP_PYTHON27_PYTHON_INTERPRETER; bad python27-sys crate?"); + + println!("{}", python); + if !Path::new(&python).exists() { + panic!( + "Python interpreter {} does not exist; this should never happen", + python + ); + } + + // This is a bit hacky but it gets the job done. + let separator = "SEPARATOR STRING"; + + let script = "import sysconfig; \ + c = sysconfig.get_config_vars(); \ + print('SEPARATOR STRING'.join('%s=%s' % i for i in c.items()))"; + + let mut command = Command::new(&python); + command.arg("-c").arg(script); + + let out = command.output().unwrap(); + + if !out.status.success() { + panic!( + "python script failed: {}", + String::from_utf8_lossy(&out.stderr) + ); + } + + let stdout = String::from_utf8_lossy(&out.stdout); + let mut m = HashMap::new(); + + for entry in stdout.split(separator) { + let mut parts = entry.splitn(2, "="); + let key = parts.next().unwrap(); + let value = parts.next().unwrap(); + m.insert(String::from(key), String::from(value)); + } + + PythonConfig { + python: python, + config: m, + } +} + +#[cfg(not(target_os = "windows"))] +fn have_shared(config: &PythonConfig) -> bool { + match config.config.get("Py_ENABLE_SHARED") { + Some(value) => value == "1", + None => false, + } +} + +#[cfg(target_os = "windows")] +fn have_shared(config: &PythonConfig) -> bool { + use std::path::PathBuf; + + // python27.dll should exist next to python2.7.exe. + let mut dll = PathBuf::from(&config.python); + dll.pop(); + dll.push("python27.dll"); + + return dll.exists(); +} + +const REQUIRED_CONFIG_FLAGS: [&str; 2] = ["Py_USING_UNICODE", "WITH_THREAD"]; + +fn main() { + let config = get_python_config(); + + println!("Using Python: {}", config.python); + println!("cargo:rustc-env=PYTHON_INTERPRETER={}", config.python); + + let prefix = config.config.get("prefix").unwrap(); + + println!("Prefix: {}", prefix); + + // TODO Windows builds don't expose these config flags. Figure out another + // way. + #[cfg(not(target_os = "windows"))] + for key in REQUIRED_CONFIG_FLAGS.iter() { + let result = match config.config.get(*key) { + Some(value) => value == "1", + None => false, + }; + + if !result { + panic!("Detected Python requires feature {}", key); + } + } + + println!("have shared {}", have_shared(&config)); + + // We need a Python shared library. + if !have_shared(&config) { + panic!("Detected Python lacks a shared library, which is required"); + } + + let ucs4 = match config.config.get("Py_UNICODE_SIZE") { + Some(value) => value == "4", + None => false, + }; + + if !ucs4 { + #[cfg(not(target_os = "windows"))] + panic!("Detected Python doesn't support UCS-4 code points"); + } +} diff --git a/rust/hgbase85/Cargo.toml b/rust/hgbase85/Cargo.toml new file mode 100644 --- /dev/null +++ b/rust/hgbase85/Cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "hgext" +version = "0.1.0" +authors = ["Sheng Mao <shng...@gmail.com>"] +license = "GPL-2.0" + +build = "build.rs" + +[lib] +name = "hgbase85" +crate-type = ["cdylib", "rlib"] + +[features] +# localdev: detect Python in PATH and use files from source checkout. +default = ["localdev"] +localdev = [] + +[dependencies] +libc = "0.2.34" + +# We currently use a custom build of cpython and python27-sys with the +# following changes: +# * GILGuard call of prepare_freethreaded_python() is removed. +# TODO switch to official release when our changes are incorporated. +[dependencies.cpython] +version = "0.1" +default-features = false +features = ["python27-sys"] +git = "https://github.com/indygreg/rust-cpython.git" +rev = "c90d65cf84abfffce7ef54476bbfed56017a2f52" + +[dependencies.python27-sys] +version = "0.1.2" +git = "https://github.com/indygreg/rust-cpython.git" +rev = "c90d65cf84abfffce7ef54476bbfed56017a2f52" + diff --git a/rust/Cargo.toml b/rust/Cargo.toml --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,2 +1,8 @@ [workspace] -members = ["hgcli"] +members = ["hgcli", "hgbase85", "hgstorage"] + +[profile.release] +debug = true + +[profile.debug] +debug = true diff --git a/rust/Cargo.lock b/rust/Cargo.lock --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1,3 +1,8 @@ +[[package]] +name = "adler32" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "aho-corasick" version = "0.5.3" @@ -7,25 +12,147 @@ ] [[package]] +name = "aho-corasick" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ansi_term" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "atty" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "build_const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cc" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "clap" +version = "2.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "cpython" version = "0.1.0" source = "git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52#c90d65cf84abfffce7ef54476bbfed56017a2f52" dependencies = [ - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)", ] [[package]] +name = "crc" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "build_const 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "flate2" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide_c_api 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "hgcli" version = "0.1.0" dependencies = [ + "clap 2.30.0 (registry+https://github.com/rust-lang/crates.io-index)", "cpython 0.1.0 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)", - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "hgstorage 0.1.0", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)", +] + +[[package]] +name = "hgext" +version = "0.1.0" +dependencies = [ + "cpython 0.1.0 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)", ] [[package]] +name = "hgstorage" +version = "0.1.0" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "flate2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hex 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "kernel32-sys" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -35,33 +162,110 @@ ] [[package]] +name = "lazy_static" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "libc" -version = "0.2.35" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "linked-hash-map" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "lru-cache" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "memchr" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miniz_oxide" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miniz_oxide_c_api" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" -version = "0.1.41" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "num_cpus" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "python27-sys" version = "0.1.2" source = "git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52#c90d65cf84abfffce7ef54476bbfed56017a2f52" dependencies = [ - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] +name = "rand" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "regex" version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -74,17 +278,88 @@ ] [[package]] +name = "regex" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "regex-syntax" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "regex-syntax" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "remove_dir_all" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "same-file" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "strsim" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "tempdir" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "thread-id" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -96,32 +371,149 @@ ] [[package]] +name = "thread_local" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "threadpool" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-width" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "utf8-ranges" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "utf8-ranges" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "walkdir" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "winapi" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "winapi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "winapi-build" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [metadata] +"checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45" "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" +"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" +"checksum ansi_term 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6b3568b48b7cefa6b8ce125f9bb4989e52fbcc29ebea88df04cc7c5f12f70455" +"checksum atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8352656fd42c30a0c3c89d26dea01e3b77c0ab2af18230835c15e2e13cd51859" +"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" +"checksum build_const 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e90dc84f5e62d2ebe7676b83c22d33b6db8bd27340fb6ffbff0a364efa0cb9c9" +"checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" +"checksum cc 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9be26b24e988625409b19736d130f0c7d224f01d06454b5f81d8d23d6c1a618f" +"checksum clap 2.30.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1c07b9257a00f3fc93b7f3c417fc15607ec7a56823bc2c37ec744e266387de5b" "checksum cpython 0.1.0 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)" = "<none>" +"checksum crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd5d02c0aac6bd68393ed69e00bbc2457f3e89075c6349db7189618dc4ddc1d7" +"checksum flate2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fac2277e84e5e858483756647a9d0aa8d9a2b7cba517fd84325a0aaa69a0909" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum hex 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "459d3cf58137bb02ad4adeef5036377ff59f066dbb82517b7192e3a5462a2abc" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)" = "96264e9b293e95d25bfcbbf8a88ffd1aedc85b754eba8b7d78012f638ba220eb" +"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" +"checksum libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1e5d97d6708edaa407429faa671b942dc0f2727222fb6b6539bf1db936e4b121" +"checksum linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7860ec297f7008ff7a1e3382d7f7e1dcd69efc94751a2284bafc3d013c2aa939" +"checksum lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d06ff7ff06f729ce5f4e227876cb88d10bc59cd4ae1e09fbb2bde15c850dc21" "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" -"checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070" +"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" +"checksum miniz_oxide 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aaa2d3ad070f428fffbd7d3ca2ea20bb0d8cffe9024405c44e1840bc1418b398" +"checksum miniz_oxide_c_api 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "92d98fdbd6145645828069b37ea92ca3de225e000d80702da25c20d3584b38a5" +"checksum num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "9936036cc70fe4a8b2d338ab665900323290efb03983c86cbe235ae800ad8017" +"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" "checksum python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)" = "<none>" +"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" +"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" +"checksum regex 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "5be5347bde0c48cfd8c3fdc0766cdfe9d8a755ef84d620d6794c778c91de8b2b" "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" +"checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e" +"checksum remove_dir_all 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5d2f806b0fcdabd98acd380dc8daef485e22bcb7cddc811d1337967f2528cf5" +"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637" +"checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f73eebdb68c14bcb24aef74ea96079830e7fa7b31a6106e42ea7ee887c1e134e" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" +"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" +"checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865" +"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" +"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" +"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" +"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "63636bd0eb3d00ccb8b9036381b526efac53caf112b7783b730ab3f8e44da369" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" To: Ivzhh, #hg-reviewers Cc: krbullock, indygreg, durin42, kevincox, mercurial-devel _______________________________________________ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel