D2057: translate base85.c into rust code
Ivzhh (Sheng Mao)
phabricator at mercurial-scm.org
Thu Mar 8 06:31:41 UTC 2018
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://ivzhh@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 <shngmao at 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.szorc at 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 <shngmao at 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
More information about the Mercurial-devel
mailing list