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

  - merge with stable
  - translate base85.c into rust code
  - move hgbase85 into independent module
  - add hgstorage crate
  - hg status implementation in rust

REPOSITORY
  rHG Mercurial

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

BRANCH
  phab-submit-D2057-2018-02-05 (bookmark) on default (branch)

REVISION DETAIL
  https://phab.mercurial-scm.org/D2057

AFFECTED FILES
  rust/Cargo.lock
  rust/Cargo.toml
  rust/hgbase85/Cargo.toml
  rust/hgbase85/build.rs
  rust/hgbase85/src/base85.rs
  rust/hgbase85/src/cpython_ext.rs
  rust/hgbase85/src/lib.rs
  rust/hgcli/Cargo.toml
  rust/hgcli/build.rs
  rust/hgcli/src/main.rs
  rust/hgstorage/Cargo.toml
  rust/hgstorage/src/changelog.rs
  rust/hgstorage/src/config.rs
  rust/hgstorage/src/dirstate.rs
  rust/hgstorage/src/lib.rs
  rust/hgstorage/src/local_repo.rs
  rust/hgstorage/src/manifest.rs
  rust/hgstorage/src/matcher.rs
  rust/hgstorage/src/mpatch.rs
  rust/hgstorage/src/path_encoding.rs
  rust/hgstorage/src/repository.rs
  rust/hgstorage/src/revlog.rs
  rust/hgstorage/src/revlog_v1.rs
  rust/hgstorage/src/working_context.rs

CHANGE DETAILS

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



To: Ivzhh, #hg-reviewers
Cc: krbullock, indygreg, durin42, kevincox, mercurial-devel
_______________________________________________
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel

Reply via email to