rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf
authorRaphaël Gomès <rgomes@octobus.net>
Sun, 01 Sep 2019 20:53:14 +0200
changeset 42957 7a01778bc7b7
parent 42956 3fe40dd6355d
child 42959 af2b5562fcaf
rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf Differential Revision: https://phab.mercurial-scm.org/D6774
rust/hg-core/src/dirstate.rs
rust/hg-core/src/dirstate/dirs_multiset.rs
rust/hg-core/src/dirstate/dirstate_map.rs
rust/hg-core/src/dirstate/parsers.rs
rust/hg-core/src/filepatterns.rs
rust/hg-core/src/lib.rs
rust/hg-core/src/utils/files.rs
rust/hg-cpython/src/dirstate.rs
rust/hg-cpython/src/dirstate/copymap.rs
rust/hg-cpython/src/dirstate/dirs_multiset.rs
rust/hg-cpython/src/dirstate/dirstate_map.rs
rust/hg-cpython/src/exceptions.rs
rust/hg-cpython/src/filepatterns.rs
rust/hg-cpython/src/parsers.rs
--- a/rust/hg-core/src/dirstate.rs	Sun Sep 01 20:53:14 2019 +0200
+++ b/rust/hg-core/src/dirstate.rs	Sun Sep 01 20:53:14 2019 +0200
@@ -5,7 +5,7 @@
 // This software may be used and distributed according to the terms of the
 // GNU General Public License version 2 or any later version.
 
-use crate::DirstateParseError;
+use crate::{utils::hg_path::HgPathBuf, DirstateParseError};
 use std::collections::hash_map;
 use std::collections::HashMap;
 use std::convert::TryFrom;
@@ -31,10 +31,10 @@
     pub size: i32,
 }
 
-pub type StateMap = HashMap<Vec<u8>, DirstateEntry>;
-pub type StateMapIter<'a> = hash_map::Iter<'a, Vec<u8>, DirstateEntry>;
-pub type CopyMap = HashMap<Vec<u8>, Vec<u8>>;
-pub type CopyMapIter<'a> = hash_map::Iter<'a, Vec<u8>, Vec<u8>>;
+pub type StateMap = HashMap<HgPathBuf, DirstateEntry>;
+pub type StateMapIter<'a> = hash_map::Iter<'a, HgPathBuf, DirstateEntry>;
+pub type CopyMap = HashMap<HgPathBuf, HgPathBuf>;
+pub type CopyMapIter<'a> = hash_map::Iter<'a, HgPathBuf, HgPathBuf>;
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
 pub enum EntryState {
--- a/rust/hg-core/src/dirstate/dirs_multiset.rs	Sun Sep 01 20:53:14 2019 +0200
+++ b/rust/hg-core/src/dirstate/dirs_multiset.rs	Sun Sep 01 20:53:14 2019 +0200
@@ -8,6 +8,7 @@
 //! A multiset of directory names.
 //!
 //! Used to counts the references to directories in a manifest or dirstate.
+use crate::utils::hg_path::{HgPath, HgPathBuf};
 use crate::{
     dirstate::EntryState, utils::files, DirstateEntry, DirstateMapError,
 };
@@ -15,11 +16,11 @@
 use std::collections::HashMap;
 
 // could be encapsulated if we care API stability more seriously
-pub type DirsMultisetIter<'a> = hash_map::Keys<'a, Vec<u8>, u32>;
+pub type DirsMultisetIter<'a> = hash_map::Keys<'a, HgPathBuf, u32>;
 
 #[derive(PartialEq, Debug)]
 pub struct DirsMultiset {
-    inner: HashMap<Vec<u8>, u32>,
+    inner: HashMap<HgPathBuf, u32>,
 }
 
 impl DirsMultiset {
@@ -27,7 +28,7 @@
     ///
     /// If `skip_state` is provided, skips dirstate entries with equal state.
     pub fn from_dirstate(
-        vec: &HashMap<Vec<u8>, DirstateEntry>,
+        vec: &HashMap<HgPathBuf, DirstateEntry>,
         skip_state: Option<EntryState>,
     ) -> Self {
         let mut multiset = DirsMultiset {
@@ -49,7 +50,7 @@
     }
 
     /// Initializes the multiset from a manifest.
-    pub fn from_manifest(vec: &Vec<Vec<u8>>) -> Self {
+    pub fn from_manifest(vec: &Vec<HgPathBuf>) -> Self {
         let mut multiset = DirsMultiset {
             inner: HashMap::new(),
         };
@@ -64,7 +65,7 @@
     /// Increases the count of deepest directory contained in the path.
     ///
     /// If the directory is not yet in the map, adds its parents.
-    pub fn add_path(&mut self, path: &[u8]) {
+    pub fn add_path(&mut self, path: &HgPath) {
         for subpath in files::find_dirs(path) {
             if let Some(val) = self.inner.get_mut(subpath) {
                 *val += 1;
@@ -81,7 +82,7 @@
     /// If the directory is not in the map, something horrible has happened.
     pub fn delete_path(
         &mut self,
-        path: &[u8],
+        path: &HgPath,
     ) -> Result<(), DirstateMapError> {
         for subpath in files::find_dirs(path) {
             match self.inner.entry(subpath.to_owned()) {
@@ -104,7 +105,7 @@
         Ok(())
     }
 
-    pub fn contains(&self, key: &[u8]) -> bool {
+    pub fn contains(&self, key: &HgPath) -> bool {
         self.inner.contains_key(key)
     }
 
@@ -125,20 +126,20 @@
     #[test]
     fn test_delete_path_path_not_found() {
         let mut map = DirsMultiset::from_manifest(&vec![]);
-        let path = b"doesnotexist/";
+        let path = HgPathBuf::from_bytes(b"doesnotexist/");
         assert_eq!(
-            Err(DirstateMapError::PathNotFound(path.to_vec())),
-            map.delete_path(path)
+            Err(DirstateMapError::PathNotFound(path.to_owned())),
+            map.delete_path(&path)
         );
     }
 
     #[test]
     fn test_delete_path_empty_path() {
-        let mut map = DirsMultiset::from_manifest(&vec![vec![]]);
-        let path = b"";
+        let mut map = DirsMultiset::from_manifest(&vec![HgPathBuf::new()]);
+        let path = HgPath::new(b"");
         assert_eq!(Ok(()), map.delete_path(path));
         assert_eq!(
-            Err(DirstateMapError::PathNotFound(path.to_vec())),
+            Err(DirstateMapError::PathNotFound(path.to_owned())),
             map.delete_path(path)
         );
     }
@@ -148,34 +149,40 @@
         let mut map = DirsMultiset {
             inner: [("", 5), ("a", 3), ("a/b", 2), ("a/c", 1)]
                 .iter()
-                .map(|(k, v)| (k.as_bytes().to_vec(), *v))
+                .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
                 .collect(),
         };
 
-        assert_eq!(Ok(()), map.delete_path(b"a/b/"));
-        assert_eq!(Ok(()), map.delete_path(b"a/b/"));
+        assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/b/")));
+        eprintln!("{:?}", map);
+        assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/b/")));
+        eprintln!("{:?}", map);
         assert_eq!(
-            Err(DirstateMapError::PathNotFound(b"a/b/".to_vec())),
-            map.delete_path(b"a/b/")
+            Err(DirstateMapError::PathNotFound(HgPathBuf::from_bytes(
+                b"a/b/"
+            ))),
+            map.delete_path(HgPath::new(b"a/b/"))
         );
 
-        assert_eq!(2, *map.inner.get(&b"a".to_vec()).unwrap());
-        assert_eq!(1, *map.inner.get(&b"a/c".to_vec()).unwrap());
+        assert_eq!(2, *map.inner.get(HgPath::new(b"a")).unwrap());
+        assert_eq!(1, *map.inner.get(HgPath::new(b"a/c")).unwrap());
         eprintln!("{:?}", map);
-        assert_eq!(Ok(()), map.delete_path(b"a/"));
+        assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/")));
         eprintln!("{:?}", map);
 
-        assert_eq!(Ok(()), map.delete_path(b"a/c/"));
+        assert_eq!(Ok(()), map.delete_path(HgPath::new(b"a/c/")));
         assert_eq!(
-            Err(DirstateMapError::PathNotFound(b"a/c/".to_vec())),
-            map.delete_path(b"a/c/")
+            Err(DirstateMapError::PathNotFound(HgPathBuf::from_bytes(
+                b"a/c/"
+            ))),
+            map.delete_path(HgPath::new(b"a/c/"))
         );
     }
 
     #[test]
     fn test_add_path_empty_path() {
         let mut map = DirsMultiset::from_manifest(&vec![]);
-        let path = b"";
+        let path = HgPath::new(b"");
         map.add_path(path);
 
         assert_eq!(1, map.len());
@@ -185,42 +192,42 @@
     fn test_add_path_successful() {
         let mut map = DirsMultiset::from_manifest(&vec![]);
 
-        map.add_path(b"a/");
-        assert_eq!(1, *map.inner.get(&b"a".to_vec()).unwrap());
-        assert_eq!(1, *map.inner.get(&Vec::new()).unwrap());
+        map.add_path(HgPath::new(b"a/"));
+        assert_eq!(1, *map.inner.get(HgPath::new(b"a")).unwrap());
+        assert_eq!(1, *map.inner.get(HgPath::new(b"")).unwrap());
         assert_eq!(2, map.len());
 
         // Non directory should be ignored
-        map.add_path(b"a");
-        assert_eq!(1, *map.inner.get(&b"a".to_vec()).unwrap());
+        map.add_path(HgPath::new(b"a"));
+        assert_eq!(1, *map.inner.get(HgPath::new(b"a")).unwrap());
         assert_eq!(2, map.len());
 
         // Non directory will still add its base
-        map.add_path(b"a/b");
-        assert_eq!(2, *map.inner.get(&b"a".to_vec()).unwrap());
+        map.add_path(HgPath::new(b"a/b"));
+        assert_eq!(2, *map.inner.get(HgPath::new(b"a")).unwrap());
         assert_eq!(2, map.len());
 
         // Duplicate path works
-        map.add_path(b"a/");
-        assert_eq!(3, *map.inner.get(&b"a".to_vec()).unwrap());
+        map.add_path(HgPath::new(b"a/"));
+        assert_eq!(3, *map.inner.get(HgPath::new(b"a")).unwrap());
 
         // Nested dir adds to its base
-        map.add_path(b"a/b/");
-        assert_eq!(4, *map.inner.get(&b"a".to_vec()).unwrap());
-        assert_eq!(1, *map.inner.get(&b"a/b".to_vec()).unwrap());
+        map.add_path(HgPath::new(b"a/b/"));
+        assert_eq!(4, *map.inner.get(HgPath::new(b"a")).unwrap());
+        assert_eq!(1, *map.inner.get(HgPath::new(b"a/b")).unwrap());
 
         // but not its base's base, because it already existed
-        map.add_path(b"a/b/c/");
-        assert_eq!(4, *map.inner.get(&b"a".to_vec()).unwrap());
-        assert_eq!(2, *map.inner.get(&b"a/b".to_vec()).unwrap());
+        map.add_path(HgPath::new(b"a/b/c/"));
+        assert_eq!(4, *map.inner.get(HgPath::new(b"a")).unwrap());
+        assert_eq!(2, *map.inner.get(HgPath::new(b"a/b")).unwrap());
 
-        map.add_path(b"a/c/");
-        assert_eq!(1, *map.inner.get(&b"a/c".to_vec()).unwrap());
+        map.add_path(HgPath::new(b"a/c/"));
+        assert_eq!(1, *map.inner.get(HgPath::new(b"a/c")).unwrap());
 
         let expected = DirsMultiset {
             inner: [("", 2), ("a", 5), ("a/b", 2), ("a/b/c", 1), ("a/c", 1)]
                 .iter()
-                .map(|(k, v)| (k.as_bytes().to_vec(), *v))
+                .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
                 .collect(),
         };
         assert_eq!(map, expected);
@@ -245,11 +252,11 @@
     fn test_dirsmultiset_new_no_skip() {
         let input_vec = ["a/", "b/", "a/c", "a/d/"]
             .iter()
-            .map(|e| e.as_bytes().to_vec())
+            .map(|e| HgPathBuf::from_bytes(e.as_bytes()))
             .collect();
         let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
             .iter()
-            .map(|(k, v)| (k.as_bytes().to_vec(), *v))
+            .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
             .collect();
 
         let new = DirsMultiset::from_manifest(&input_vec);
@@ -262,7 +269,7 @@
             .iter()
             .map(|f| {
                 (
-                    f.as_bytes().to_vec(),
+                    HgPathBuf::from_bytes(f.as_bytes()),
                     DirstateEntry {
                         state: EntryState::Normal,
                         mode: 0,
@@ -274,7 +281,7 @@
             .collect();
         let expected_inner = [("", 2), ("a", 3), ("b", 1), ("a/d", 1)]
             .iter()
-            .map(|(k, v)| (k.as_bytes().to_vec(), *v))
+            .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
             .collect();
 
         let new = DirsMultiset::from_dirstate(&input_map, None);
@@ -295,7 +302,7 @@
         .iter()
         .map(|(f, state)| {
             (
-                f.as_bytes().to_vec(),
+                HgPathBuf::from_bytes(f.as_bytes()),
                 DirstateEntry {
                     state: *state,
                     mode: 0,
@@ -309,7 +316,7 @@
         // "a" incremented with "a/c" and "a/d/"
         let expected_inner = [("", 1), ("a", 2), ("a/d", 1)]
             .iter()
-            .map(|(k, v)| (k.as_bytes().to_vec(), *v))
+            .map(|(k, v)| (HgPathBuf::from_bytes(k.as_bytes()), *v))
             .collect();
 
         let new =
--- a/rust/hg-core/src/dirstate/dirstate_map.rs	Sun Sep 01 20:53:14 2019 +0200
+++ b/rust/hg-core/src/dirstate/dirstate_map.rs	Sun Sep 01 20:53:14 2019 +0200
@@ -5,6 +5,7 @@
 // This software may be used and distributed according to the terms of the
 // GNU General Public License version 2 or any later version.
 
+use crate::utils::hg_path::{HgPath, HgPathBuf};
 use crate::{
     dirstate::{parsers::PARENT_SIZE, EntryState},
     pack_dirstate, parse_dirstate,
@@ -19,7 +20,7 @@
 use std::ops::Deref;
 use std::time::Duration;
 
-pub type FileFoldMap = HashMap<Vec<u8>, Vec<u8>>;
+pub type FileFoldMap = HashMap<HgPathBuf, HgPathBuf>;
 
 const NULL_ID: [u8; 20] = [0; 20];
 const MTIME_UNSET: i32 = -1;
@@ -32,8 +33,8 @@
     file_fold_map: Option<FileFoldMap>,
     pub dirs: Option<DirsMultiset>,
     pub all_dirs: Option<DirsMultiset>,
-    non_normal_set: HashSet<Vec<u8>>,
-    other_parent_set: HashSet<Vec<u8>>,
+    non_normal_set: HashSet<HgPathBuf>,
+    other_parent_set: HashSet<HgPathBuf>,
     parents: Option<DirstateParents>,
     dirty_parents: bool,
 }
@@ -47,8 +48,8 @@
     }
 }
 
-impl FromIterator<(Vec<u8>, DirstateEntry)> for DirstateMap {
-    fn from_iter<I: IntoIterator<Item = (Vec<u8>, DirstateEntry)>>(
+impl FromIterator<(HgPathBuf, DirstateEntry)> for DirstateMap {
+    fn from_iter<I: IntoIterator<Item = (HgPathBuf, DirstateEntry)>>(
         iter: I,
     ) -> Self {
         Self {
@@ -78,7 +79,7 @@
     /// Add a tracked file to the dirstate
     pub fn add_file(
         &mut self,
-        filename: &[u8],
+        filename: &HgPath,
         old_state: EntryState,
         entry: DirstateEntry,
     ) {
@@ -111,7 +112,7 @@
     /// to be more explicit about what that state is.
     pub fn remove_file(
         &mut self,
-        filename: &[u8],
+        filename: &HgPath,
         old_state: EntryState,
         size: i32,
     ) -> Result<(), DirstateMapError> {
@@ -147,7 +148,7 @@
     /// Returns `true` if the file was previously recorded.
     pub fn drop_file(
         &mut self,
-        filename: &[u8],
+        filename: &HgPath,
         old_state: EntryState,
     ) -> Result<bool, DirstateMapError> {
         let exists = self.state_map.remove(filename).is_some();
@@ -172,7 +173,7 @@
 
     pub fn clear_ambiguous_times(
         &mut self,
-        filenames: Vec<Vec<u8>>,
+        filenames: Vec<HgPathBuf>,
         now: i32,
     ) {
         for filename in filenames {
@@ -197,7 +198,7 @@
 
     pub fn non_normal_other_parent_entries(
         &self,
-    ) -> (HashSet<Vec<u8>>, HashSet<Vec<u8>>) {
+    ) -> (HashSet<HgPathBuf>, HashSet<HgPathBuf>) {
         let mut non_normal = HashSet::new();
         let mut other_parent = HashSet::new();
 
@@ -239,12 +240,12 @@
         }
     }
 
-    pub fn has_tracked_dir(&mut self, directory: &[u8]) -> bool {
+    pub fn has_tracked_dir(&mut self, directory: &HgPath) -> bool {
         self.set_dirs();
         self.dirs.as_ref().unwrap().contains(directory)
     }
 
-    pub fn has_dir(&mut self, directory: &[u8]) -> bool {
+    pub fn has_dir(&mut self, directory: &HgPath) -> bool {
         self.set_all_dirs();
         self.all_dirs.as_ref().unwrap().contains(directory)
     }
@@ -346,11 +347,11 @@
         assert!(map.dirs.is_none());
         assert!(map.all_dirs.is_none());
 
-        assert_eq!(false, map.has_dir(b"nope"));
+        assert_eq!(false, map.has_dir(HgPath::new(b"nope")));
         assert!(map.all_dirs.is_some());
         assert!(map.dirs.is_none());
 
-        assert_eq!(false, map.has_tracked_dir(b"nope"));
+        assert_eq!(false, map.has_tracked_dir(HgPath::new(b"nope")));
         assert!(map.dirs.is_some());
     }
 
@@ -361,7 +362,7 @@
         assert_eq!(0, map.len());
 
         map.add_file(
-            b"meh",
+            HgPath::new(b"meh"),
             EntryState::Normal,
             DirstateEntry {
                 state: EntryState::Normal,
@@ -394,7 +395,7 @@
         .iter()
         .map(|(fname, (state, mode, size, mtime))| {
             (
-                fname.to_vec(),
+                HgPathBuf::from_bytes(fname.as_ref()),
                 DirstateEntry {
                     state: *state,
                     mode: *mode,
@@ -409,11 +410,11 @@
             b"f1", b"f2", b"f5", b"f6", b"f7", b"f8", b"f9", b"fa", b"fb",
         ]
         .iter()
-        .map(|x| x.to_vec())
+        .map(|x| HgPathBuf::from_bytes(x.as_ref()))
         .collect();
 
         let mut other_parent = HashSet::new();
-        other_parent.insert(b"f4".to_vec());
+        other_parent.insert(HgPathBuf::from_bytes(b"f4"));
 
         assert_eq!(
             (non_normal, other_parent),
--- a/rust/hg-core/src/dirstate/parsers.rs	Sun Sep 01 20:53:14 2019 +0200
+++ b/rust/hg-core/src/dirstate/parsers.rs	Sun Sep 01 20:53:14 2019 +0200
@@ -3,6 +3,7 @@
 // This software may be used and distributed according to the terms of the
 // GNU General Public License version 2 or any later version.
 
+use crate::utils::hg_path::HgPath;
 use crate::{
     dirstate::{CopyMap, EntryState, StateMap},
     DirstateEntry, DirstatePackError, DirstateParents, DirstateParseError,
@@ -60,10 +61,13 @@
         };
 
         if let Some(copy_path) = copy {
-            copy_map.insert(path.to_owned(), copy_path.to_owned());
+            copy_map.insert(
+                HgPath::new(path).to_owned(),
+                HgPath::new(copy_path).to_owned(),
+            );
         };
         state_map.insert(
-            path.to_owned(),
+            HgPath::new(path).to_owned(),
             DirstateEntry {
                 state,
                 mode,
@@ -106,7 +110,7 @@
     packed.extend(&parents.p2);
 
     for (filename, entry) in state_map.iter() {
-        let mut new_filename: Vec<u8> = filename.to_owned();
+        let new_filename = filename.to_owned();
         let mut new_mtime: i32 = entry.mtime;
         if entry.state == EntryState::Normal && entry.mtime == now {
             // The file was last modified "simultaneously" with the current
@@ -127,10 +131,10 @@
                 },
             ));
         }
-
+        let mut new_filename = new_filename.into_vec();
         if let Some(copy) = copy_map.get(filename) {
             new_filename.push('\0' as u8);
-            new_filename.extend(copy);
+            new_filename.extend(copy.bytes());
         }
 
         packed.write_u8(entry.state.into())?;
@@ -153,6 +157,7 @@
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::utils::hg_path::HgPathBuf;
     use std::collections::HashMap;
 
     #[test]
@@ -176,7 +181,7 @@
     #[test]
     fn test_pack_dirstate_one_entry() {
         let expected_state_map: StateMap = [(
-            b"f1".to_vec(),
+            HgPathBuf::from_bytes(b"f1"),
             DirstateEntry {
                 state: EntryState::Normal,
                 mode: 0o644,
@@ -213,7 +218,7 @@
     #[test]
     fn test_pack_dirstate_one_entry_with_copy() {
         let expected_state_map: StateMap = [(
-            b"f1".to_vec(),
+            HgPathBuf::from_bytes(b"f1"),
             DirstateEntry {
                 state: EntryState::Normal,
                 mode: 0o644,
@@ -226,7 +231,10 @@
         .collect();
         let mut state_map = expected_state_map.clone();
         let mut copymap = HashMap::new();
-        copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
+        copymap.insert(
+            HgPathBuf::from_bytes(b"f1"),
+            HgPathBuf::from_bytes(b"copyname"),
+        );
         let parents = DirstateParents {
             p1: *b"12345678910111213141",
             p2: *b"00000000000000000000",
@@ -251,7 +259,7 @@
     #[test]
     fn test_parse_pack_one_entry_with_copy() {
         let mut state_map: StateMap = [(
-            b"f1".to_vec(),
+            HgPathBuf::from_bytes(b"f1"),
             DirstateEntry {
                 state: EntryState::Normal,
                 mode: 0o644,
@@ -263,7 +271,10 @@
         .cloned()
         .collect();
         let mut copymap = HashMap::new();
-        copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
+        copymap.insert(
+            HgPathBuf::from_bytes(b"f1"),
+            HgPathBuf::from_bytes(b"copyname"),
+        );
         let parents = DirstateParents {
             p1: *b"12345678910111213141",
             p2: *b"00000000000000000000",
@@ -291,7 +302,7 @@
     fn test_parse_pack_multiple_entries_with_copy() {
         let mut state_map: StateMap = [
             (
-                b"f1".to_vec(),
+                HgPathBuf::from_bytes(b"f1"),
                 DirstateEntry {
                     state: EntryState::Normal,
                     mode: 0o644,
@@ -300,7 +311,7 @@
                 },
             ),
             (
-                b"f2".to_vec(),
+                HgPathBuf::from_bytes(b"f2"),
                 DirstateEntry {
                     state: EntryState::Merged,
                     mode: 0o777,
@@ -309,7 +320,7 @@
                 },
             ),
             (
-                b"f3".to_vec(),
+                HgPathBuf::from_bytes(b"f3"),
                 DirstateEntry {
                     state: EntryState::Removed,
                     mode: 0o644,
@@ -318,7 +329,7 @@
                 },
             ),
             (
-                b"f4\xF6".to_vec(),
+                HgPathBuf::from_bytes(b"f4\xF6"),
                 DirstateEntry {
                     state: EntryState::Added,
                     mode: 0o644,
@@ -331,8 +342,14 @@
         .cloned()
         .collect();
         let mut copymap = HashMap::new();
-        copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
-        copymap.insert(b"f4\xF6".to_vec(), b"copyname2".to_vec());
+        copymap.insert(
+            HgPathBuf::from_bytes(b"f1"),
+            HgPathBuf::from_bytes(b"copyname"),
+        );
+        copymap.insert(
+            HgPathBuf::from_bytes(b"f4\xF6"),
+            HgPathBuf::from_bytes(b"copyname2"),
+        );
         let parents = DirstateParents {
             p1: *b"12345678910111213141",
             p2: *b"00000000000000000000",
@@ -360,7 +377,7 @@
     /// https://www.mercurial-scm.org/repo/hg/rev/af3f26b6bba4
     fn test_parse_pack_one_entry_with_copy_and_time_conflict() {
         let mut state_map: StateMap = [(
-            b"f1".to_vec(),
+            HgPathBuf::from_bytes(b"f1"),
             DirstateEntry {
                 state: EntryState::Normal,
                 mode: 0o644,
@@ -372,7 +389,10 @@
         .cloned()
         .collect();
         let mut copymap = HashMap::new();
-        copymap.insert(b"f1".to_vec(), b"copyname".to_vec());
+        copymap.insert(
+            HgPathBuf::from_bytes(b"f1"),
+            HgPathBuf::from_bytes(b"copyname"),
+        );
         let parents = DirstateParents {
             p1: *b"12345678910111213141",
             p2: *b"00000000000000000000",
@@ -395,7 +415,7 @@
             (
                 parents,
                 [(
-                    b"f1".to_vec(),
+                    HgPathBuf::from_bytes(b"f1"),
                     DirstateEntry {
                         state: EntryState::Normal,
                         mode: 0o644,
--- a/rust/hg-core/src/filepatterns.rs	Sun Sep 01 20:53:14 2019 +0200
+++ b/rust/hg-core/src/filepatterns.rs	Sun Sep 01 20:53:14 2019 +0200
@@ -7,15 +7,13 @@
 
 //! Handling of Mercurial-specific patterns.
 
-use crate::{
-    utils::{files::get_path_from_bytes, SliceExt},
-    LineNumber, PatternError, PatternFileError,
-};
+use crate::{utils::SliceExt, LineNumber, PatternError, PatternFileError};
 use lazy_static::lazy_static;
 use regex::bytes::{NoExpand, Regex};
 use std::collections::HashMap;
 use std::fs::File;
 use std::io::Read;
+use std::path::{Path, PathBuf};
 use std::vec::Vec;
 
 lazy_static! {
@@ -230,11 +228,11 @@
 }
 
 pub type PatternTuple = (Vec<u8>, LineNumber, Vec<u8>);
-type WarningTuple = (Vec<u8>, Vec<u8>);
+type WarningTuple = (PathBuf, Vec<u8>);
 
-pub fn parse_pattern_file_contents(
+pub fn parse_pattern_file_contents<P: AsRef<Path>>(
     lines: &[u8],
-    file_path: &[u8],
+    file_path: P,
     warn: bool,
 ) -> (Vec<PatternTuple>, Vec<WarningTuple>) {
     let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
@@ -268,7 +266,8 @@
             if let Some(rel_syntax) = SYNTAXES.get(syntax) {
                 current_syntax = rel_syntax;
             } else if warn {
-                warnings.push((file_path.to_owned(), syntax.to_owned()));
+                warnings
+                    .push((file_path.as_ref().to_owned(), syntax.to_owned()));
             }
             continue;
         }
@@ -297,11 +296,11 @@
     (inputs, warnings)
 }
 
-pub fn read_pattern_file(
-    file_path: &[u8],
+pub fn read_pattern_file<P: AsRef<Path>>(
+    file_path: P,
     warn: bool,
 ) -> Result<(Vec<PatternTuple>, Vec<WarningTuple>), PatternFileError> {
-    let mut f = File::open(get_path_from_bytes(file_path))?;
+    let mut f = File::open(file_path.as_ref())?;
     let mut contents = Vec::new();
 
     f.read_to_end(&mut contents)?;
@@ -343,18 +342,21 @@
 
         assert_eq!(
             vec![(b"relglob:*.elc".to_vec(), 2, b"*.elc".to_vec())],
-            parse_pattern_file_contents(lines, b"file_path", false).0,
+            parse_pattern_file_contents(lines, Path::new("file_path"), false)
+                .0,
         );
 
         let lines = b"syntax: include\nsyntax: glob";
 
         assert_eq!(
-            parse_pattern_file_contents(lines, b"file_path", false).0,
+            parse_pattern_file_contents(lines, Path::new("file_path"), false)
+                .0,
             vec![]
         );
         let lines = b"glob:**.o";
         assert_eq!(
-            parse_pattern_file_contents(lines, b"file_path", false).0,
+            parse_pattern_file_contents(lines, Path::new("file_path"), false)
+                .0,
             vec![(b"relglob:**.o".to_vec(), 1, b"**.o".to_vec())]
         );
     }
--- a/rust/hg-core/src/lib.rs	Sun Sep 01 20:53:14 2019 +0200
+++ b/rust/hg-core/src/lib.rs	Sun Sep 01 20:53:14 2019 +0200
@@ -18,6 +18,7 @@
 mod filepatterns;
 pub mod utils;
 
+use crate::utils::hg_path::HgPathBuf;
 pub use filepatterns::{
     build_single_regex, read_pattern_file, PatternSyntax, PatternTuple,
 };
@@ -96,7 +97,7 @@
 }
 #[derive(Debug, PartialEq)]
 pub enum DirstateMapError {
-    PathNotFound(Vec<u8>),
+    PathNotFound(HgPathBuf),
     EmptyPath,
 }
 
--- a/rust/hg-core/src/utils/files.rs	Sun Sep 01 20:53:14 2019 +0200
+++ b/rust/hg-core/src/utils/files.rs	Sun Sep 01 20:53:14 2019 +0200
@@ -9,7 +9,9 @@
 
 //! Functions for fiddling with files.
 
+use crate::utils::hg_path::{HgPath, HgPathBuf};
 use std::iter::FusedIterator;
+
 use std::path::Path;
 
 pub fn get_path_from_bytes(bytes: &[u8]) -> &Path {
@@ -23,8 +25,7 @@
     {
         // TODO: convert from Windows MBCS (ANSI encoding) to WTF8.
         // Perhaps, the return type would have to be Result<PathBuf>.
-        use std::os::windows::ffi::OsStrExt;
-        os_str = std::ffi::OsString::from_wide(bytes);
+        unimplemented!()
     }
 
     Path::new(os_str)
@@ -33,20 +34,19 @@
 /// An iterator over repository path yielding itself and its ancestors.
 #[derive(Copy, Clone, Debug)]
 pub struct Ancestors<'a> {
-    next: Option<&'a [u8]>,
+    next: Option<&'a HgPath>,
 }
 
 impl<'a> Iterator for Ancestors<'a> {
-    // if we had an HgPath type, this would yield &'a HgPath
-    type Item = &'a [u8];
+    type Item = &'a HgPath;
 
     fn next(&mut self) -> Option<Self::Item> {
         let next = self.next;
         self.next = match self.next {
             Some(s) if s.is_empty() => None,
             Some(s) => {
-                let p = s.iter().rposition(|&c| c == b'/').unwrap_or(0);
-                Some(&s[..p])
+                let p = s.bytes().rposition(|c| *c == b'/').unwrap_or(0);
+                Some(HgPath::new(&s.as_bytes()[..p]))
             }
             None => None,
         };
@@ -63,7 +63,7 @@
 ///
 /// The path itself isn't included unless it is b"" (meaning the root
 /// directory.)
-pub fn find_dirs<'a>(path: &'a [u8]) -> Ancestors<'a> {
+pub fn find_dirs<'a>(path: &'a HgPath) -> Ancestors<'a> {
     let mut dirs = Ancestors { next: Some(path) };
     if !path.is_empty() {
         dirs.next(); // skip itself
@@ -71,23 +71,24 @@
     dirs
 }
 
-/// TODO improve handling of utf8 file names. Our overall strategy for
-/// filenames has to be revisited anyway, since Windows is UTF-16.
-pub fn normalize_case(bytes: &[u8]) -> Vec<u8> {
+/// TODO more than ASCII?
+pub fn normalize_case(path: &HgPath) -> HgPathBuf {
     #[cfg(windows)] // NTFS compares via upper()
-    return bytes.to_ascii_uppercase();
+    return path.to_ascii_uppercase();
     #[cfg(unix)]
-    bytes.to_ascii_lowercase()
+    path.to_ascii_lowercase()
 }
 
 #[cfg(test)]
 mod tests {
+    use super::*;
+
     #[test]
     fn find_dirs_some() {
-        let mut dirs = super::find_dirs(b"foo/bar/baz");
-        assert_eq!(dirs.next(), Some(b"foo/bar".as_ref()));
-        assert_eq!(dirs.next(), Some(b"foo".as_ref()));
-        assert_eq!(dirs.next(), Some(b"".as_ref()));
+        let mut dirs = super::find_dirs(HgPath::new(b"foo/bar/baz"));
+        assert_eq!(dirs.next(), Some(HgPath::new(b"foo/bar")));
+        assert_eq!(dirs.next(), Some(HgPath::new(b"foo")));
+        assert_eq!(dirs.next(), Some(HgPath::new(b"")));
         assert_eq!(dirs.next(), None);
         assert_eq!(dirs.next(), None);
     }
@@ -95,8 +96,8 @@
     #[test]
     fn find_dirs_empty() {
         // looks weird, but mercurial.util.finddirs(b"") yields b""
-        let mut dirs = super::find_dirs(b"");
-        assert_eq!(dirs.next(), Some(b"".as_ref()));
+        let mut dirs = super::find_dirs(HgPath::new(b""));
+        assert_eq!(dirs.next(), Some(HgPath::new(b"")));
         assert_eq!(dirs.next(), None);
         assert_eq!(dirs.next(), None);
     }
--- a/rust/hg-cpython/src/dirstate.rs	Sun Sep 01 20:53:14 2019 +0200
+++ b/rust/hg-cpython/src/dirstate.rs	Sun Sep 01 20:53:14 2019 +0200
@@ -17,7 +17,10 @@
     exc, PyBytes, PyDict, PyErr, PyModule, PyObject, PyResult, PySequence,
     Python,
 };
-use hg::{DirstateEntry, DirstateParseError, EntryState, StateMap};
+use hg::{
+    utils::hg_path::HgPathBuf, DirstateEntry, DirstateParseError, EntryState,
+    StateMap,
+};
 use libc::{c_char, c_int};
 #[cfg(feature = "python27")]
 use python27_sys::PyCapsule_Import;
@@ -75,7 +78,7 @@
             let filename = filename.extract::<PyBytes>(py)?;
             let filename = filename.data(py);
             Ok((
-                filename.to_owned(),
+                HgPathBuf::from(filename.to_owned()),
                 DirstateEntry {
                     state,
                     mode,
--- a/rust/hg-cpython/src/dirstate/copymap.rs	Sun Sep 01 20:53:14 2019 +0200
+++ b/rust/hg-cpython/src/dirstate/copymap.rs	Sun Sep 01 20:53:14 2019 +0200
@@ -12,7 +12,7 @@
 use std::cell::RefCell;
 
 use crate::dirstate::dirstate_map::{DirstateMap, DirstateMapLeakedRef};
-use hg::CopyMapIter;
+use hg::{utils::hg_path::HgPathBuf, CopyMapIter};
 
 py_class!(pub class CopyMap |py| {
     data dirstate_map: DirstateMap;
@@ -85,16 +85,19 @@
     }
     fn translate_key(
         py: Python,
-        res: (&Vec<u8>, &Vec<u8>),
+        res: (&HgPathBuf, &HgPathBuf),
     ) -> PyResult<Option<PyBytes>> {
-        Ok(Some(PyBytes::new(py, res.0)))
+        Ok(Some(PyBytes::new(py, res.0.as_ref())))
     }
     fn translate_key_value(
         py: Python,
-        res: (&Vec<u8>, &Vec<u8>),
+        res: (&HgPathBuf, &HgPathBuf),
     ) -> PyResult<Option<(PyBytes, PyBytes)>> {
         let (k, v) = res;
-        Ok(Some((PyBytes::new(py, k), PyBytes::new(py, v))))
+        Ok(Some((
+            PyBytes::new(py, k.as_ref()),
+            PyBytes::new(py, v.as_ref()),
+        )))
     }
 }
 
--- a/rust/hg-cpython/src/dirstate/dirs_multiset.rs	Sun Sep 01 20:53:14 2019 +0200
+++ b/rust/hg-cpython/src/dirstate/dirs_multiset.rs	Sun Sep 01 20:53:14 2019 +0200
@@ -16,9 +16,12 @@
     Python,
 };
 
-use crate::dirstate::extract_dirstate;
-use crate::ref_sharing::{PySharedRefCell, PySharedState};
+use crate::{
+    dirstate::extract_dirstate,
+    ref_sharing::{PySharedRefCell, PySharedState},
+};
 use hg::{
+    utils::hg_path::{HgPath, HgPathBuf},
     DirsMultiset, DirsMultisetIter, DirstateMapError, DirstateParseError,
     EntryState,
 };
@@ -48,9 +51,13 @@
             let dirstate = extract_dirstate(py, &map)?;
             DirsMultiset::from_dirstate(&dirstate, skip_state)
         } else {
-            let map: Result<Vec<Vec<u8>>, PyErr> = map
+            let map: Result<Vec<HgPathBuf>, PyErr> = map
                 .iter(py)?
-                .map(|o| Ok(o?.extract::<PyBytes>(py)?.data(py).to_owned()))
+                .map(|o| {
+                    Ok(HgPathBuf::from_bytes(
+                        o?.extract::<PyBytes>(py)?.data(py),
+                    ))
+                })
                 .collect();
             DirsMultiset::from_manifest(&map?)
         };
@@ -64,14 +71,14 @@
 
     def addpath(&self, path: PyObject) -> PyResult<PyObject> {
         self.borrow_mut(py)?.add_path(
-            path.extract::<PyBytes>(py)?.data(py),
+            HgPath::new(path.extract::<PyBytes>(py)?.data(py)),
         );
         Ok(py.None())
     }
 
     def delpath(&self, path: PyObject) -> PyResult<PyObject> {
         self.borrow_mut(py)?.delete_path(
-            path.extract::<PyBytes>(py)?.data(py),
+            HgPath::new(path.extract::<PyBytes>(py)?.data(py)),
         )
             .and(Ok(py.None()))
             .or_else(|e| {
@@ -98,10 +105,9 @@
     }
 
     def __contains__(&self, item: PyObject) -> PyResult<bool> {
-        Ok(self
-            .inner(py)
-            .borrow()
-            .contains(item.extract::<PyBytes>(py)?.data(py).as_ref()))
+        Ok(self.inner(py).borrow().contains(HgPath::new(
+            item.extract::<PyBytes>(py)?.data(py).as_ref(),
+        )))
     }
 });
 
@@ -116,8 +122,11 @@
         )
     }
 
-    fn translate_key(py: Python, res: &Vec<u8>) -> PyResult<Option<PyBytes>> {
-        Ok(Some(PyBytes::new(py, res)))
+    fn translate_key(
+        py: Python,
+        res: &HgPathBuf,
+    ) -> PyResult<Option<PyBytes>> {
+        Ok(Some(PyBytes::new(py, res.as_ref())))
     }
 }
 
--- a/rust/hg-cpython/src/dirstate/dirstate_map.rs	Sun Sep 01 20:53:14 2019 +0200
+++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs	Sun Sep 01 20:53:14 2019 +0200
@@ -24,6 +24,7 @@
     ref_sharing::{PySharedRefCell, PySharedState},
 };
 use hg::{
+    utils::hg_path::{HgPath, HgPathBuf},
     DirsMultiset, DirstateEntry, DirstateMap as RustDirstateMap,
     DirstateParents, DirstateParseError, EntryState, StateMapIter,
     PARENT_SIZE,
@@ -65,7 +66,7 @@
         default: Option<PyObject> = None
     ) -> PyResult<Option<PyObject>> {
         let key = key.extract::<PyBytes>(py)?;
-        match self.inner(py).borrow().get(key.data(py)) {
+        match self.inner(py).borrow().get(HgPath::new(key.data(py))) {
             Some(entry) => {
                 // Explicitly go through u8 first, then cast to
                 // platform-specific `c_char`.
@@ -91,7 +92,7 @@
         mtime: PyObject
     ) -> PyResult<PyObject> {
         self.borrow_mut(py)?.add_file(
-            f.extract::<PyBytes>(py)?.data(py),
+            HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
             oldstate.extract::<PyBytes>(py)?.data(py)[0]
                 .try_into()
                 .map_err(|e: DirstateParseError| {
@@ -119,7 +120,7 @@
     ) -> PyResult<PyObject> {
         self.borrow_mut(py)?
             .remove_file(
-                f.extract::<PyBytes>(py)?.data(py),
+                HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
                 oldstate.extract::<PyBytes>(py)?.data(py)[0]
                     .try_into()
                     .map_err(|e: DirstateParseError| {
@@ -143,7 +144,7 @@
     ) -> PyResult<PyBool> {
         self.borrow_mut(py)?
             .drop_file(
-                f.extract::<PyBytes>(py)?.data(py),
+                HgPath::new(f.extract::<PyBytes>(py)?.data(py)),
                 oldstate.extract::<PyBytes>(py)?.data(py)[0]
                     .try_into()
                     .map_err(|e: DirstateParseError| {
@@ -164,10 +165,12 @@
         files: PyObject,
         now: PyObject
     ) -> PyResult<PyObject> {
-        let files: PyResult<Vec<Vec<u8>>> = files
+        let files: PyResult<Vec<HgPathBuf>> = files
             .iter(py)?
             .map(|filename| {
-                Ok(filename?.extract::<PyBytes>(py)?.data(py).to_owned())
+                Ok(HgPathBuf::from_bytes(
+                    filename?.extract::<PyBytes>(py)?.data(py),
+                ))
             })
             .collect();
         self.borrow_mut(py)?
@@ -186,7 +189,7 @@
             "non_normal",
             non_normal
                 .iter()
-                .map(|v| PyBytes::new(py, &v))
+                .map(|v| PyBytes::new(py, v.as_ref()))
                 .collect::<Vec<PyBytes>>()
                 .to_py_object(py),
         )?;
@@ -195,7 +198,7 @@
             "other_parent",
             other_parent
                 .iter()
-                .map(|v| PyBytes::new(py, &v))
+                .map(|v| PyBytes::new(py, v.as_ref()))
                 .collect::<Vec<PyBytes>>()
                 .to_py_object(py),
         )?;
@@ -206,14 +209,14 @@
     def hastrackeddir(&self, d: PyObject) -> PyResult<PyBool> {
         let d = d.extract::<PyBytes>(py)?;
         Ok(self.borrow_mut(py)?
-            .has_tracked_dir(d.data(py))
+            .has_tracked_dir(HgPath::new(d.data(py)))
             .to_py_object(py))
     }
 
     def hasdir(&self, d: PyObject) -> PyResult<PyBool> {
         let d = d.extract::<PyBytes>(py)?;
         Ok(self.borrow_mut(py)?
-            .has_dir(d.data(py))
+            .has_dir(HgPath::new(d.data(py)))
             .to_py_object(py))
     }
 
@@ -280,10 +283,8 @@
 
     def filefoldmapasdict(&self) -> PyResult<PyDict> {
         let dict = PyDict::new(py);
-        for (key, value) in
-            self.borrow_mut(py)?.build_file_fold_map().iter()
-        {
-            dict.set_item(py, key, value)?;
+        for (key, value) in self.borrow_mut(py)?.build_file_fold_map().iter() {
+            dict.set_item(py, key.as_ref().to_vec(), value.as_ref().to_vec())?;
         }
         Ok(dict)
     }
@@ -294,12 +295,12 @@
 
     def __contains__(&self, key: PyObject) -> PyResult<bool> {
         let key = key.extract::<PyBytes>(py)?;
-        Ok(self.inner(py).borrow().contains_key(key.data(py)))
+        Ok(self.inner(py).borrow().contains_key(HgPath::new(key.data(py))))
     }
 
     def __getitem__(&self, key: PyObject) -> PyResult<PyObject> {
         let key = key.extract::<PyBytes>(py)?;
-        let key = key.data(py);
+        let key = HgPath::new(key.data(py));
         match self.inner(py).borrow().get(key) {
             Some(entry) => {
                 // Explicitly go through u8 first, then cast to
@@ -314,7 +315,7 @@
             },
             None => Err(PyErr::new::<exc::KeyError, _>(
                 py,
-                String::from_utf8_lossy(key),
+                String::from_utf8_lossy(key.as_bytes()),
             )),
         }
     }
@@ -373,15 +374,19 @@
     def copymapcopy(&self) -> PyResult<PyDict> {
         let dict = PyDict::new(py);
         for (key, value) in self.inner(py).borrow().copy_map.iter() {
-            dict.set_item(py, PyBytes::new(py, key), PyBytes::new(py, value))?;
+            dict.set_item(
+                py,
+                PyBytes::new(py, key.as_ref()),
+                PyBytes::new(py, value.as_ref()),
+            )?;
         }
         Ok(dict)
     }
 
     def copymapgetitem(&self, key: PyObject) -> PyResult<PyBytes> {
         let key = key.extract::<PyBytes>(py)?;
-        match self.inner(py).borrow().copy_map.get(key.data(py)) {
-            Some(copy) => Ok(PyBytes::new(py, copy)),
+        match self.inner(py).borrow().copy_map.get(HgPath::new(key.data(py))) {
+            Some(copy) => Ok(PyBytes::new(py, copy.as_ref())),
             None => Err(PyErr::new::<exc::KeyError, _>(
                 py,
                 String::from_utf8_lossy(key.data(py)),
@@ -397,7 +402,11 @@
     }
     def copymapcontains(&self, key: PyObject) -> PyResult<bool> {
         let key = key.extract::<PyBytes>(py)?;
-        Ok(self.inner(py).borrow().copy_map.contains_key(key.data(py)))
+        Ok(self
+            .inner(py)
+            .borrow()
+            .copy_map
+            .contains_key(HgPath::new(key.data(py))))
     }
     def copymapget(
         &self,
@@ -405,8 +414,15 @@
         default: Option<PyObject>
     ) -> PyResult<Option<PyObject>> {
         let key = key.extract::<PyBytes>(py)?;
-        match self.inner(py).borrow().copy_map.get(key.data(py)) {
-            Some(copy) => Ok(Some(PyBytes::new(py, copy).into_object())),
+        match self
+            .inner(py)
+            .borrow()
+            .copy_map
+            .get(HgPath::new(key.data(py)))
+        {
+            Some(copy) => Ok(Some(
+                PyBytes::new(py, copy.as_ref()).into_object(),
+            )),
             None => Ok(default),
         }
     }
@@ -417,9 +433,10 @@
     ) -> PyResult<PyObject> {
         let key = key.extract::<PyBytes>(py)?;
         let value = value.extract::<PyBytes>(py)?;
-        self.borrow_mut(py)?
-            .copy_map
-            .insert(key.data(py).to_vec(), value.data(py).to_vec());
+        self.borrow_mut(py)?.copy_map.insert(
+            HgPathBuf::from_bytes(key.data(py)),
+            HgPathBuf::from_bytes(value.data(py)),
+        );
         Ok(py.None())
     }
     def copymappop(
@@ -428,7 +445,11 @@
         default: Option<PyObject>
     ) -> PyResult<Option<PyObject>> {
         let key = key.extract::<PyBytes>(py)?;
-        match self.borrow_mut(py)?.copy_map.remove(key.data(py)) {
+        match self
+            .borrow_mut(py)?
+            .copy_map
+            .remove(HgPath::new(key.data(py)))
+        {
             Some(_) => Ok(None),
             None => Ok(default),
         }
@@ -457,13 +478,13 @@
 impl DirstateMap {
     fn translate_key(
         py: Python,
-        res: (&Vec<u8>, &DirstateEntry),
+        res: (&HgPathBuf, &DirstateEntry),
     ) -> PyResult<Option<PyBytes>> {
-        Ok(Some(PyBytes::new(py, res.0)))
+        Ok(Some(PyBytes::new(py, res.0.as_ref())))
     }
     fn translate_key_value(
         py: Python,
-        res: (&Vec<u8>, &DirstateEntry),
+        res: (&HgPathBuf, &DirstateEntry),
     ) -> PyResult<Option<(PyBytes, PyObject)>> {
         let (f, entry) = res;
 
@@ -471,7 +492,7 @@
         // platform-specific `c_char`.
         let state: u8 = entry.state.into();
         Ok(Some((
-            PyBytes::new(py, f),
+            PyBytes::new(py, f.as_ref()),
             decapsule_make_dirstate_tuple(py)?(
                 state as c_char,
                 entry.mode,
--- a/rust/hg-cpython/src/exceptions.rs	Sun Sep 01 20:53:14 2019 +0200
+++ b/rust/hg-cpython/src/exceptions.rs	Sun Sep 01 20:53:14 2019 +0200
@@ -41,6 +41,7 @@
 
 py_exception!(rustext, PatternError, RuntimeError);
 py_exception!(rustext, PatternFileError, RuntimeError);
+py_exception!(rustext, HgPathPyError, RuntimeError);
 
 impl PatternError {
     pub fn pynew(py: Python, inner: hg::PatternError) -> PyErr {
--- a/rust/hg-cpython/src/filepatterns.rs	Sun Sep 01 20:53:14 2019 +0200
+++ b/rust/hg-cpython/src/filepatterns.rs	Sun Sep 01 20:53:14 2019 +0200
@@ -13,9 +13,14 @@
 //!
 use crate::exceptions::{PatternError, PatternFileError};
 use cpython::{
-    PyBytes, PyDict, PyModule, PyObject, PyResult, PyTuple, Python, ToPyObject,
+    PyBytes, PyDict, PyModule, PyObject, PyResult, PyString, PyTuple, Python,
+    ToPyObject,
 };
-use hg::{build_single_regex, read_pattern_file, LineNumber, PatternTuple};
+use hg::{
+    build_single_regex, read_pattern_file, utils::files::get_path_from_bytes,
+    LineNumber, PatternTuple,
+};
+use std::path::PathBuf;
 
 /// Rust does not like functions with different return signatures.
 /// The 3-tuple version is always returned by the hg-core function,
@@ -33,7 +38,9 @@
     warn: bool,
     source_info: bool,
 ) -> PyResult<PyTuple> {
-    match read_pattern_file(file_path.extract::<PyBytes>(py)?.data(py), warn) {
+    let bytes = file_path.extract::<PyBytes>(py)?;
+    let path = get_path_from_bytes(bytes.data(py));
+    match read_pattern_file(path, warn) {
         Ok((patterns, warnings)) => {
             if source_info {
                 let itemgetter = |x: &PatternTuple| {
@@ -58,11 +65,16 @@
 
 fn warnings_to_py_bytes(
     py: Python,
-    warnings: &[(Vec<u8>, Vec<u8>)],
-) -> Vec<(PyBytes, PyBytes)> {
+    warnings: &[(PathBuf, Vec<u8>)],
+) -> Vec<(PyString, PyBytes)> {
     warnings
         .iter()
-        .map(|(path, syn)| (PyBytes::new(py, path), PyBytes::new(py, syn)))
+        .map(|(path, syn)| {
+            (
+                PyString::new(py, &path.to_string_lossy()),
+                PyBytes::new(py, syn),
+            )
+        })
         .collect()
 }
 
--- a/rust/hg-cpython/src/parsers.rs	Sun Sep 01 20:53:14 2019 +0200
+++ b/rust/hg-cpython/src/parsers.rs	Sun Sep 01 20:53:14 2019 +0200
@@ -15,8 +15,8 @@
     PythonObject, ToPyObject,
 };
 use hg::{
-    pack_dirstate, parse_dirstate, DirstateEntry, DirstatePackError,
-    DirstateParents, DirstateParseError, PARENT_SIZE,
+    pack_dirstate, parse_dirstate, utils::hg_path::HgPathBuf, DirstateEntry,
+    DirstatePackError, DirstateParents, DirstateParseError, PARENT_SIZE,
 };
 use std::collections::HashMap;
 use std::convert::TryInto;
@@ -46,7 +46,7 @@
 
                 dmap.set_item(
                     py,
-                    PyBytes::new(py, &filename),
+                    PyBytes::new(py, filename.as_ref()),
                     decapsule_make_dirstate_tuple(py)?(
                         state as c_char,
                         entry.mode,
@@ -58,8 +58,8 @@
             for (path, copy_path) in copies {
                 copymap.set_item(
                     py,
-                    PyBytes::new(py, &path),
-                    PyBytes::new(py, &copy_path),
+                    PyBytes::new(py, path.as_ref()),
+                    PyBytes::new(py, copy_path.as_ref()),
                 )?;
             }
             Ok(
@@ -99,13 +99,13 @@
 
     let mut dirstate_map = extract_dirstate(py, &dmap)?;
 
-    let copies: Result<HashMap<Vec<u8>, Vec<u8>>, PyErr> = copymap
+    let copies: Result<HashMap<HgPathBuf, HgPathBuf>, PyErr> = copymap
         .items(py)
         .iter()
         .map(|(key, value)| {
             Ok((
-                key.extract::<PyBytes>(py)?.data(py).to_owned(),
-                value.extract::<PyBytes>(py)?.data(py).to_owned(),
+                HgPathBuf::from_bytes(key.extract::<PyBytes>(py)?.data(py)),
+                HgPathBuf::from_bytes(value.extract::<PyBytes>(py)?.data(py)),
             ))
         })
         .collect();
@@ -144,7 +144,7 @@
                 let state: u8 = state.into();
                 dmap.set_item(
                     py,
-                    PyBytes::new(py, &filename[..]),
+                    PyBytes::new(py, filename.as_ref()),
                     decapsule_make_dirstate_tuple(py)?(
                         state as c_char,
                         mode,