D2057: rust implementation of hg status

Ivzhh (Sheng Mao) phabricator at mercurial-scm.org
Wed Mar 21 14:24:45 EDT 2018


Ivzhh updated this revision to Diff 7188.
Ivzhh added a comment.


  - add revlog and mpatch facilities
  - add changelog parsing
  - add manifest parsing
  - path encoding for data store
  - add dirstate and matcher facilities
  - add local repository and the supporting modules
  - use cargo fmt to format code
  - add hg r-status command
  - bincode 1.0.0 is a bit slow in my test
  - delay pattern matching during dir walk
  - optimize out trie and enable CoreXL profiling
  - use hashmap
  - remove thread pool
  - rust default read is not buf-ed, this is the key of slowness
  - change to globset
  - convert glob to regex
  - hg ignore patterns are all converted to regex (as hg does), and now it is faster
  - filter dir early to prevent walking
  - Update matcher mod after testing Mozilla unified repo
  - bug fix: use byte literals instead of numbers
  - hg store path encoding is per byte style, update code according to Kevin Cox's comments
  - update matcher testing according to Match interface change
  - If clap fails to recognize r-* subcommands, then run python-version hg
  - changelog coding style revised
  - remove legacy revlog v0 and unfinished v2.
  - partially revise the dirstate reviews
  - remove duplicated build.rs, let the executable module guarantee the python
  - use cursor in base85 encoding, reducing raw index-math
  - use cursor in base85 decoding, reducing raw index-math
  - dirstate update according to review comments
  - config update according to review comments
  - mpatch rename to more meaningful names
  - simplify matcher as when there is no syntax named in the beginning, use regexp
  - local repo coding style update
  - dirstate coding style update
  - manifest coding style update

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D2057?vs=6724&id=7188

BRANCH
  rust-hg-optimize (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/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,114 @@
+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 = match DirState::new(dot_hg_path.join("dirstate")) {
+            Some(dir_state) => dir_state,
+            None => {
+                unimplemented!("creating dirstate is not supported yet.");
+            }
+        };
+
+        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)
+            .unwrap();
+
+        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,435 @@
+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, PatchChain};
+
+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);
+
+        //let mut ofs: u32 = 0;
+
+        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))));
+
+                    /*ofs += match dflag {
+                        Inline => {
+                            f.seek(SeekFrom::Current(hd.len_compressed as i64)).unwrap();
+                            hd.len_compressed + Entry::packed_size()
+                        }
+                        Separated(_) => Entry::packed_size(),
+                    };*/
+                }
+                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 {
+                b'x' => {
+                    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);
+                }
+                b'\0' => {
+                    return Some(buf);
+                }
+                b'u' => {
+                    return Some(buf[1..].to_vec());
+                }
+                _ => {
+                    panic!(
+                        "decoding pattern not supported yet. fail to decode {}, first byte {}",
+                        r, flag_byte
+                    );
+                }
+            }
+        } else {
+            panic!("read delta content failed.");
+        }
+    }
+
+    fn get_all_bins(&self, rs: &[usize]) -> PatchChain {
+        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 PatchChain { 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,206 @@
+use std::path::{Path, PathBuf};
+use std::str;
+
+const HEX_DIGIT: [u8; 16] = *b"0123456789abcdef";
+
+fn hex_encode(c: usize) -> (char, char) {
+    (char::from(HEX_DIGIT[c >> 4]), char::from(HEX_DIGIT[c & 15]))
+}
+
+/// according to hg-python implementation, escape() will escape u8 as "~\x\x" format
+fn escape(buf: &mut String, c: u8) {
+    buf.push('~');
+    let res = hex_encode(c as usize);
+    buf.push(res.0);
+    buf.push(res.1);
+}
+
+/// hg stores files not folders, for each file, it is stored as file_name.ext.i for index
+/// and optional file_name.ext.d for data. If there is a file "aaa" and folder "aaa.i",
+/// then there is a conflict in hg storage. Thus this function appends extra ".hg" extension
+/// to these dirs.
+fn encode_dir(p: String) -> String {
+    let mut p = p;
+
+    if p.ends_with(".i") || p.ends_with(".d") {
+        p.push_str(".hg");
+    } else if p.ends_with(".hg") {
+        p.push_str(".hg");
+    }
+
+    return p;
+}
+
+pub fn encode_special_letters(path: &str) -> String {
+    let mut out = String::with_capacity(path.len());
+
+    for c in path.bytes() {
+        match c {
+            b'A'...b'Z' => {
+                out.push('_');
+                out.push(char::from(c.to_ascii_lowercase()));
+            }
+            b'\\' | b':' | b'*' | b'?' | b'"' | b'<' | b'>' | b'|' => {
+                escape(&mut out, c);
+            }
+            // The rest of the printable range.
+            b'_' => {
+                out.push_str("__");
+            }
+            b' '...b'~' => {
+                out.push(char::from(c));
+            }
+            _ => {
+                escape(&mut out, c);
+            }
+        }
+    }
+
+    out
+}
+
+pub fn aux_encode(input_path: &str) -> String {
+    let mut path = String::with_capacity(input_path.len());
+
+    let bytes = input_path.as_bytes();
+
+    if bytes[0] == b'.' || bytes[0] == b' ' {
+        escape(&mut path, bytes[0].clone());
+        let mut iter = input_path.chars();
+        iter.next();
+        path.push_str(iter.as_str());
+    } else {
+        let dotpos = {
+            let mut i = 0;
+            loop {
+                if i < bytes.len() && bytes[i] == b'.' {
+                    break i as i32;
+                } else if i >= bytes.len() {
+                    break -1 as i32;
+                }
+
+                i += 1;
+            }
+        };
+
+        let l = if dotpos == -1 {
+            bytes.len()
+        } else {
+            dotpos as usize
+        };
+
+        let mut is_aux = false;
+        let mut cursor: usize;
+
+        if l == 3 {
+            let key = &bytes[..3];
+            if key == b"aux" || key == b"con" || key == b"prn" || key == b"nul" {
+                bytes[..2]
+                    .iter()
+                    .for_each(|b: &u8| path.push(char::from(*b)));
+                escape(&mut path, bytes[2]);
+                is_aux = true;
+            }
+        } else if l == 4 {
+            let key = &bytes[..3];
+            if (key == b"com" || key == b"lpt") && b'1' <= bytes[3] && bytes[3] <= b'9' {
+                bytes[..2]
+                    .iter()
+                    .for_each(|b: &u8| path.push(char::from(*b)));
+                escape(&mut path, bytes[2]);
+                path.push(char::from(bytes[3]));
+                is_aux = true;
+            }
+        }
+
+        if !is_aux {
+            path.push_str(str::from_utf8(&bytes[..l]).unwrap());
+        }
+
+        cursor = l;
+
+        if cursor < bytes.len() - 1 {
+            path.push_str(str::from_utf8(&bytes[cursor..(bytes.len() - 1)]).unwrap());
+            cursor = bytes.len() - 1;
+        }
+
+        if cursor == bytes.len() - 1 {
+            if bytes[cursor] == b'.' || bytes[cursor] == b' ' {
+                escape(&mut path, bytes[cursor]);
+            } else {
+                path.push(char::from(bytes[cursor]));
+            }
+        }
+    }
+
+    return path;
+}
+
+/// According to discussions in [D2057](https://phab.mercurial-scm.org/D2057#44269),
+/// hg uses raw byte strings internally. Raw byte string here means either utf8 (always used in
+/// *nix) or MBCS (windows with *A() functions).
+/// The hg store path encoding assumes byte strings underline, e.g. file name "测试.txt" is
+/// encoded as "~e6~b5~8b~e8~af~95.txt.i" underline, and the encoding is byte by byte from utf8 bytes
+/// Thus the following codes should directly focus on Vec<u8> internally, and when IO invoked,
+/// on windows platform, [crate local-encoding](https://crates.io/crates/local-encoding) can help call
+/// windows to convert MBCS to Unicode.
+pub fn encode_path(p: &Path) -> PathBuf {
+    let mut res = PathBuf::new();
+
+    let mut leaves = p.iter().map(|s| s.to_str().unwrap().to_string()).peekable();
+
+    let mut component = leaves
+        .next()
+        .expect("the path should have at least one component");
+
+    while !leaves.peek().is_none() {
+        let leaf = component;
+        let leaf = encode_dir(leaf);
+        let leaf = encode_special_letters(leaf.as_str());
+        let leaf = aux_encode(leaf.as_str());
+
+        res.push(leaf);
+
+        component = leaves
+            .next()
+            .expect("previous peak guarantees the next exists");
+    }
+
+    let leaf = component;
+    let leaf = encode_special_letters(leaf.as_str());
+    let mut leaf = aux_encode(leaf.as_str());
+    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.i",
+            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.i",
+            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.i",
+            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,153 @@
+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 PatchHeader {
+    /// the pos (offset of byte) where previous chunk ends
+    prev_chunk_ends: u32,
+    /// the pos where next chunk starts
+    next_chunk_starts: u32,
+    /// size of the current diff patch
+    diff_size: u32,
+}
+
+#[derive(Clone, Debug)]
+struct Patch {
+    len: u32,
+    offset: u32,
+}
+
+impl Patch {
+    pub fn new(len: u32, ofs: u32) -> Self {
+        Patch {
+            len: len,
+            offset: ofs,
+        }
+    }
+}
+
+fn pull_patch_into_queue(dst: &mut Vec<Patch>, src: &mut Vec<Patch>, l: u32) {
+    let mut l = l;
+
+    while l > 0 {
+        let f = src.pop().unwrap();
+        if f.len > l {
+            src.push(Patch::new(f.len - l, f.offset + l));
+            dst.push(Patch::new(l, f.offset));
+            return;
+        }
+        l -= f.len;
+        dst.push(f);
+    }
+}
+
+fn move_patch_to_new_pos(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_fragments_and_compact(m: &mut Cursor<Vec<u8>>, buf: u32, list: &Vec<Patch>) -> Patch {
+    let start = buf;
+    let mut buf = buf;
+
+    for &Patch { len: l, offset: p } in list.iter().rev() {
+        move_patch_to_new_pos(m, buf, p, l);
+        buf += l;
+    }
+    return Patch::new(buf - start, start);
+}
+
+#[derive(Debug)]
+pub struct PatchChain {
+    pub base: Vec<u8>,
+    pub bins: Vec<Vec<u8>>,
+}
+
+pub fn patches(ptc: &PatchChain) -> Vec<u8> {
+    let &PatchChain {
+        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![Patch::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_fragments_and_compact(&mut m, b1, &mut frags)];
+        }
+
+        let mut new: Vec<Patch> = 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_patch_into_queue(&mut new, &mut frags, p1 - last);
+            assert!(!frags.is_empty());
+            pull_patch_into_queue(&mut vec![], &mut frags, p2 - p1);
+
+            new.push(Patch::new(l, pos + 12));
+            pos += l + 12;
+            last = p2;
+        }
+
+        frags.extend(new.iter().rev().cloned());
+    }
+
+    let t = collect_fragments_and_compact(&mut m, b2, &mut frags);
+
+    m.seek(Start(t.offset as u64)).unwrap();
+
+    let mut res: Vec<u8> = Vec::new();
+    res.resize(t.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,246 @@
+use std::fs;
+use std::path::PathBuf;
+use std::vec::Vec;
+use std::io::BufReader;
+use std::io::BufRead;
+
+use regex::Regex;
+use regex::{escape, RegexSet};
+
+pub fn glob_to_re(glob: &str) -> String {
+    let mut res = String::with_capacity(glob.len());
+
+    let pat: Vec<char> = glob.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(glob: &str) -> String {
+    let mut res = String::with_capacity(glob.len());
+    //res.push_str("(?:|.*/)");
+    res.push_str(glob_to_re(glob).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 file_buffer = BufReader::new(fs::File::open(fname).unwrap());
+
+            #[derive(PartialEq)]
+            enum State {
+                Glob,
+                Re,
+            }
+
+            inner.push(relglob(".hg/*"));
+
+            let mut cur_state = State::Re;
+            for line in file_buffer.lines() {
+                let line = match line {
+                    Err(_) => break,
+                    Ok(line) => line,
+                };
+
+                let line = line.trim();
+
+                if line.is_empty() {
+                    continue;
+                }
+
+                if line.starts_with("syntax:") {
+                    let mut iter = line.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 => panic!(
+                            "syntax error in {:?}, please use 'syntax: [glob|regexp]'",
+                            fname
+                        ),*/
+                        State::Glob => {
+                            inner.push(relglob(line));
+                        }
+                        State::Re => {
+                            let check_support = Regex::new(line);
+                            match check_support {
+                                Ok(_) => inner.push(line.to_owned()),
+                                Err(_) => {}
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        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, rel_path: &str) -> bool {
+        if let Some(ref m) = self.inner {
+            m.is_match(rel_path)
+        } 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 ignore_file = tmp_dir.path().join("cffi/.hgignore");
+        let mut m = Matcher::new(&[ignore_file]);
+
+        assert!(!m.check_path_ignored("a.py"));
+        assert!(m.check_path_ignored("testing/__pycache__"));
+        assert!(m.check_path_ignored("test/dfsdf/a.egg-info"));
+        assert!(!m.check_path_ignored("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,121 @@
+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] == b'\0' {
+                // field break
+                prev_i = i;
+            } else if content[i] == b'\n' {
+                // line break
+                let file_name = str::from_utf8(&content[line_start..prev_i])
+                    .unwrap()
+                    .to_string();
+
+                line_start = i + 1;
+
+                let entry = 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)], // the length of node id in hex format is 40 bytes long
+                    ).unwrap());
+                    let flag = str::from_utf8(&content[(prev_i + 41)..i])   // an extra flag may exist after node id
+                        .unwrap()
+                        .to_string();
+                    ManifestEntry { id, flag }
+                };
+
+                res.insert(PathBuf::from(file_name.as_str()), entry);
+            }
+        }
+
+        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,164 @@
+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");
+                assert!(
+                    dot_hg_path.exists(),
+                    ".hg folder not found for the path given by -R argument: {:?}",
+                    p
+                );
+                p
+            }
+            None => {
+                let mut root = pwd.as_path();
+                while !root.join(".hg").exists() {
+                    root = root.parent().expect(".hg folder not found");
+                }
+                root.to_owned()
+            }
+        });
+
+        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()),
+        };
+
+        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, path: &Path) -> Arc<RwLock<Revlog>> {
+        let relpath = encode_path(path);
+        let abspath = self.store_data.join(&relpath);
+
+        assert!(abspath.exists(), "path not exists: {:?}", abspath);
+
+        let mut gd = self.revlog_db.write().unwrap();
+
+        if !gd.contains_key(path) {
+            let rl = self.revlog_factory.create(abspath.as_path());
+            gd.insert(path.to_path_buf(), rl);
+        }
+
+        return gd.get_mut(path).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_create_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 ds = DirState::new(dfile).unwrap();
+
+        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,271 @@
+use std::str;
+use std::path::{Path, PathBuf};
+use std::io;
+use std::io::Read;
+use std::fs::File;
+use std::collections::HashMap;
+#[cfg(target_family = "unix")]
+use std::os::unix::fs::FileTypeExt;
+use std::collections::HashSet;
+use std::time::{SystemTime, UNIX_EPOCH};
+use std::error::Error;
+
+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,
+) -> io::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,
+    /// corresponding to dmap in python version
+    pub dir_state_map: HashMap<PathBuf, DirStateEntry>,
+
+    pub mtime: SystemTime,
+}
+
+#[derive(Debug)]
+pub struct CurrentState {
+    /// per status-call
+    pub added: HashSet<PathBuf>,
+    /// a.k.a forget
+    pub removed: HashSet<PathBuf>,
+    /// a.k.a missing
+    pub deleted: HashSet<PathBuf>,
+    pub modified: HashSet<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: HashSet<PathBuf>,
+    pub unknown: HashSet<PathBuf>,
+    pub clean: HashSet<PathBuf>,
+    pub lookup: HashSet<PathBuf>,
+}
+
+impl CurrentState {
+    pub fn new() -> Self {
+        Self {
+            added: HashSet::new(),
+            removed: HashSet::new(),
+            deleted: HashSet::new(),
+            modified: HashSet::new(),
+            ignored: HashSet::new(),
+            unknown: HashSet::new(),
+            clean: HashSet::new(),
+            lookup: HashSet::new(),
+        }
+    }
+}
+
+fn build_relatvie_path_w_trailing_slash_for_dir_path(root: &Path, path: &Path) -> PathBuf {
+    let mut path_buf = path.strip_prefix(root).unwrap().to_owned();
+    path_buf.push("");
+
+    path_buf
+}
+
+impl DirState {
+    pub fn new(p: PathBuf) -> Option<Self> {
+        let meta_data = p.metadata();
+
+        if let Ok(meta_data) = meta_data {
+            let mtime = meta_data.modified().expect("default mtime must exist");
+
+            let mut ret = Self {
+                p1: NULL_ID.clone(),
+                p2: NULL_ID.clone(),
+                path: p,
+                dir_state_map: HashMap::new(),
+
+                mtime,
+            };
+
+            ret.parse_dirstate();
+
+            return Some(ret);
+        } else {
+            return None;
+        }
+    }
+
+    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.dir_state_map
+                .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,
+        matcher: &matcher::Matcher,
+    ) -> io::Result<CurrentState> {
+        let mut files_not_in_walkdir = {
+            let mut files_not_in_walkdir = HashSet::new();
+            files_not_in_walkdir.extend(self.dir_state_map.keys().map(|s| s.as_path()));
+            files_not_in_walkdir
+        };
+
+        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 p = build_relatvie_path_w_trailing_slash_for_dir_path(root, ent.path());
+                !matcher.check_path_ignored(p.to_str().unwrap())
+            } else {
+                true
+            }
+        }) {
+            match entry {
+                Ok(entry) => {
+                    let abs_path = entry.path();
+                    let rel_path = abs_path.strip_prefix(root).unwrap();
+
+                    if DirState::is_bad(&entry) || entry.file_type().is_dir() {
+                        continue;
+                    }
+
+                    if self.dir_state_map.contains_key(rel_path) {
+                        let dir_entry = &self.dir_state_map[rel_path];
+                        files_not_in_walkdir.remove(rel_path);
+                        DirState::check_status(&mut res, abs_path, rel_path, dir_entry);
+                    } else if !matcher.check_path_ignored(rel_path.to_str().unwrap()) {
+                        res.unknown.insert(rel_path.to_path_buf());
+                    }
+                }
+                Err(err) => {
+                    let path = err.path().unwrap_or(Path::new(""));
+                    eprintln!("failed to access entry {}", path.display());
+
+                    if let Some(inner) = err.io_error() {
+                        match inner.kind() {
+                            io::ErrorKind::InvalidData => {
+                                eprintln!("entry contains invalid data: {}", inner)
+                            }
+                            io::ErrorKind::PermissionDenied => {
+                                eprintln!("Missing permission to read entry: {}", inner)
+                            }
+                            _ => eprintln!("Unexpected error occurred: {}", inner),
+                        }
+
+                        if self.dir_state_map
+                            .contains_key(path.strip_prefix(root).unwrap())
+                        {
+                            return Err(io::Error::new(inner.kind(), inner.description()));
+                        }
+                    }
+                }
+            }
+        }
+
+        for path in files_not_in_walkdir.drain() {
+            if res.ignored.contains(path) {
+                res.ignored.remove(path);
+            }
+
+            let relpath = path;
+            let abspath = root.join(relpath);
+
+            let dir_entry = &self.dir_state_map[relpath];
+
+            DirState::check_status(&mut res, &abspath, &relpath, dir_entry);
+        }
+
+        return Ok(res);
+    }
+
+    fn check_status(
+        res: &mut CurrentState,
+        abspath: &Path,
+        relpath: &Path,
+        dir_entry: &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 dir_entry.status == b'r' {
+            res.removed.insert(pb);
+        } else if !abspath.exists() {
+            res.deleted.insert(pb);
+        } else if dir_entry.status == b'a' {
+            res.added.insert(pb);
+        } else {
+            let mtd = abspath.metadata().unwrap();
+
+            if mtd.len() != dir_entry.size as u64 {
+                res.modified.insert(pb);
+            } else if mtd.modified()
+                .unwrap()
+                .duration_since(UNIX_EPOCH)
+                .unwrap()
+                .as_secs() != dir_entry.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,101 @@
+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 {
+    V1,
+}
+
+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
+    }
+}
+
+/// the configuration is read from .hg/requires file
+#[derive(Default)]
+pub struct Configuration {
+    /// store all requires key words
+    pub requires: HashSet<String>,
+    /// callback functions, if any keyword is found, then do some settings
+    pub reg_conf: HashMap<String, RegFn>,
+    /// default revlog format, currently only support revlog v1
+    pub revlog_format: RevlogFormat,
+    /// where to get delta base, please refer to wiki page
+    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: String, func: RegFn) {
+        self.reg_conf.insert(key, func);
+    }
+
+    fn register_all(&mut self) {
+        self.register_conf(String::from("revlogv1"), |conf| {
+            conf.revlog_format = RevlogFormat::V1;
+        });
+        self.register_conf(String::from("generaldelta"), |conf| {
+            conf.delta = DeltaPolicy::GeneralDelta;
+        });
+    }
+
+    pub fn get_revlog_format(&self) -> RevlogFormat {
+        if self.requires.contains("revlogv1") {
+            RevlogFormat::V1
+        } else {
+            unimplemented!("right now only support revlog v1");
+        }
+    }
+}
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,39 @@
+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()], b'\n', "node id ends with \\n");
+
+        let manifest_id = {
+            let hex_id = &content[..NodeId::hex_len()];
+            NodeId::new_from_bytes(&hex::decode(hex_id).unwrap())
+        };
+
+        // keep the rest of the message for other functionalities
+        content.drain(..NodeId::hex_len());
+
+        CommitInfo {
+            manifest_id,
+            msg: content,
+        }
+    }
+}
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,29 @@
+[package]
+name = "hgstorage"
+version = "0.1.0"
+authors = ["Sheng Mao <shngmao at gmail.com>"]
+license = "GPL-2.0"
+
+[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,44 @@
 }
 
 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_safe()
+        .unwrap_or_else(|e| {
+            match run() {
+                Err(err) => {
+                    println!("running python-version hg with error code {}", err);
+                    println!("If you want to run rust-version hg, please see the error.");
+                    e.exit();
+                }
+                Ok(()) => std::process::exit(0),
+            };
+        });
 
-    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());
+        }
+    }
 }
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 = "~2.31.1"
 
 # 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,383 @@
+use std;
+use std::{mem, sync};
+use std::io::{Cursor, Write};
+
+use cpython::{exc, PyBytes, PyErr, PyObject, PyResult, Py_ssize_t, Python, PythonObject};
+use cpython::_detail::ffi;
+
+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 pad = pad != 0;
+    let text = text.as_bytes();
+    let tlen: Py_ssize_t = text.len() as Py_ssize_t;
+    let olen: Py_ssize_t = if pad {
+        ((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 mut dst = Cursor::new(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 src_buf = text.iter().cloned().peekable();
+
+    while src_buf.peek().is_some() {
+        let mut accum: u32 = 0;
+
+        for i in &[24, 16, 8, 0] {
+            if let Some(val) = src_buf.next() {
+                accum |= (val as u32) << i;
+            } else {
+                break;
+            }
+        }
+
+        let mut tmp_buf = [0; 5];
+
+        for i in &[4, 3, 2, 1, 0] {
+            let val = (accum % 85) as usize;
+            accum /= 85;
+
+            tmp_buf[*i] = B85CHARS[val];
+        }
+
+        match dst.write(&tmp_buf) {
+            Ok(_) => continue,
+            Err(_) => break,
+        }
+    }
+
+    if !pad {
+        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> {
+    // error[E0133]: use of mutable static requires unsafe function or block
+    let b85dec = unsafe { B85DEC };
+
+    let text = text.as_bytes();
+
+    let out_buf_len: usize = {
+        let len = { text.len() };
+        let i = len % 5;
+
+        len / 5 * 4 + {
+            if i > 0 {
+                i - 1
+            } else {
+                0
+            }
+        }
+    };
+
+    let out: PyBytes = cpython_ext::pybytes_new_without_copying(py, out_buf_len 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_cursor = 0;
+
+    let mut src = text.iter().cloned().peekable();
+
+    while src.peek().is_some() {
+        let mut acc: u32 = 0;
+
+        for _ in 0..4 {
+            if let Some(val) = src.next() {
+                let c = (b85dec[val as usize] as i32)
+                    .checked_sub(1)
+                    .ok_or_else(|| {
+                        PyErr::new::<exc::ValueError, _>(
+                            py,
+                            format!("bad base85 character {}", val),
+                        )
+                    })?;
+
+                acc = acc * 85 + (c as u32);
+            } else {
+                break;
+            }
+        }
+
+        if let Some(val) = src.next() {
+            let c = (b85dec[val as usize] as i32)
+                .checked_sub(1)
+                .ok_or_else(|| {
+                    PyErr::new::<exc::ValueError, _>(py, format!("bad base85 character {}", val))
+                })?;
+
+            acc = acc.checked_mul(85).ok_or_else(|| {
+                PyErr::new::<exc::ValueError, _>(py, format!("bad base85 character {}", val))
+            })?;
+
+            acc = acc.checked_add(c as u32).ok_or_else(|| {
+                PyErr::new::<exc::ValueError, _>(py, format!("bad base85 character {}", val))
+            })?;
+        }
+
+        let available_buf_len = out_buf_len - dst_cursor;
+
+        let cap = if available_buf_len < 4 {
+            available_buf_len
+        } 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_cursor] = acc as u8;
+        }
+
+        dst_cursor += 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/Cargo.toml b/rust/hgbase85/Cargo.toml
new file mode 100644
--- /dev/null
+++ b/rust/hgbase85/Cargo.toml
@@ -0,0 +1,34 @@
+[package]
+name = "hgext"
+version = "0.1.0"
+authors = ["Sheng Mao <shngmao at gmail.com>"]
+license = "GPL-2.0"
+
+[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,150 @@
 ]
 
 [[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.11.0"
+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 = "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.31.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ansi_term 0.11.0 (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.31.1 (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 +165,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 +281,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 +374,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.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+"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.31.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dc18f6f4005132120d9711636b32c46a233fad94df6217fa1d81c5e97a9f200"
 "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, kevincox
Cc: quark, yuja, glandium, krbullock, indygreg, durin42, kevincox, mercurial-devel


More information about the Mercurial-devel mailing list