Patchwork D6774: rust-hgpath: replace all paths and filenames with HgPath/HgPathBuf

login
register
mail settings
Submitter phabricator
Date Aug. 29, 2019, 2:41 p.m.
Message ID <differential-rev-PHID-DREV-gzwehsjb6sfjoserabpc-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/41429/
State New
Headers show

Comments

phabricator - Aug. 29, 2019, 2:41 p.m.
Alphare created this revision.
Herald added subscribers: mercurial-devel, kevincox, durin42.
Herald added a reviewer: hg-reviewers.

REPOSITORY
  rHG Mercurial

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

AFFECTED FILES
  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/filepatterns.rs
  rust/hg-cpython/src/parsers.rs

CHANGE DETAILS




To: Alphare, #hg-reviewers
Cc: durin42, kevincox, mercurial-devel
phabricator - Sept. 12, 2019, 8:25 a.m.
kevincox added inline comments.
kevincox accepted this revision.

INLINE COMMENTS

> files.rs:75
> +/// TODO more than ASCII?
> +pub fn normalize_case(path: &HgPath) -> HgPathBuf {
> +    #[cfg(windows)] // NTFS compares via upper()

Is this used? Can you either use, delete or add tests.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D6774/new/

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

To: Alphare, #hg-reviewers, kevincox
Cc: durin42, kevincox, mercurial-devel
phabricator - Sept. 13, 2019, 10:10 a.m.
Alphare added inline comments.

INLINE COMMENTS

> kevincox wrote in files.rs:75
> Is this used? Can you either use, delete or add tests.

I think the rebase fixed the diff.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST ACTION
  https://phab.mercurial-scm.org/D6774/new/

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

To: Alphare, #hg-reviewers, kevincox
Cc: durin42, kevincox, mercurial-devel

Patch

diff --git a/rust/hg-cpython/src/parsers.rs b/rust/hg-cpython/src/parsers.rs
--- a/rust/hg-cpython/src/parsers.rs
+++ b/rust/hg-cpython/src/parsers.rs
@@ -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,
diff --git a/rust/hg-cpython/src/filepatterns.rs b/rust/hg-cpython/src/filepatterns.rs
--- a/rust/hg-cpython/src/filepatterns.rs
+++ b/rust/hg-cpython/src/filepatterns.rs
@@ -14,7 +14,11 @@ 
 use cpython::{
     PyBytes, PyDict, PyModule, PyObject, PyResult, PyTuple, Python, ToPyObject,
 };
-use hg::{build_single_regex, read_pattern_file, LineNumber, PatternTuple};
+use hg::{
+    build_single_regex, read_pattern_file,
+    utils::hg_path::{HgPath, HgPathBuf},
+    LineNumber, PatternTuple,
+};
 
 /// Rust does not like functions with different return signatures.
 /// The 3-tuple version is always returned by the hg-core function,
@@ -32,7 +36,10 @@ 
     warn: bool,
     source_info: bool,
 ) -> PyResult<PyTuple> {
-    match read_pattern_file(file_path.extract::<PyBytes>(py)?.data(py), warn) {
+    match read_pattern_file(
+        HgPath::new(file_path.extract::<PyBytes>(py)?.data(py)),
+        warn,
+    ) {
         Ok((patterns, warnings)) => {
             if source_info {
                 let itemgetter = |x: &PatternTuple| {
@@ -57,11 +64,13 @@ 
 
 fn warnings_to_py_bytes(
     py: Python,
-    warnings: &[(Vec<u8>, Vec<u8>)],
+    warnings: &[(HgPathBuf, Vec<u8>)],
 ) -> Vec<(PyBytes, PyBytes)> {
     warnings
         .iter()
-        .map(|(path, syn)| (PyBytes::new(py, path), PyBytes::new(py, syn)))
+        .map(|(path, syn)| {
+            (PyBytes::new(py, path.as_ref()), PyBytes::new(py, syn))
+        })
         .collect()
 }
 
diff --git a/rust/hg-cpython/src/dirstate/dirstate_map.rs b/rust/hg-cpython/src/dirstate/dirstate_map.rs
--- a/rust/hg-cpython/src/dirstate/dirstate_map.rs
+++ b/rust/hg-cpython/src/dirstate/dirstate_map.rs
@@ -24,6 +24,7 @@ 
     ref_sharing::PySharedState,
 };
 use hg::{
+    utils::hg_path::{HgPath, HgPathBuf},
     DirsMultiset, DirstateEntry, DirstateMap as RustDirstateMap,
     DirstateParents, DirstateParseError, EntryState, PARENT_SIZE,
 };
@@ -64,7 +65,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`.
@@ -90,7 +91,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| {
@@ -118,7 +119,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| {
@@ -142,7 +143,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| {
@@ -163,10 +164,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.inner(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),
         )?;
@@ -208,7 +211,7 @@ 
         Ok(self
             .inner(py)
             .borrow_mut()
-            .has_tracked_dir(d.data(py))
+            .has_tracked_dir(HgPath::new(d.data(py)))
             .to_py_object(py))
     }
 
@@ -217,7 +220,7 @@ 
         Ok(self
             .inner(py)
             .borrow_mut()
-            .has_dir(d.data(py))
+            .has_dir(HgPath::new(d.data(py)))
             .to_py_object(py))
     }
 
@@ -291,7 +294,7 @@ 
         for (key, value) in
             self.borrow_mut(py)?.build_file_fold_map().iter()
         {
-            dict.set_item(py, key, value)?;
+            dict.set_item(py, key.as_vec(), value.as_vec())?;
         }
         Ok(dict)
     }
@@ -302,12 +305,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
@@ -322,7 +325,7 @@ 
             },
             None => Err(PyErr::new::<exc::KeyError, _>(
                 py,
-                String::from_utf8_lossy(key),
+                String::from_utf8_lossy(key.bytes()),
             )),
         }
     }
@@ -378,15 +381,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)),
@@ -402,7 +409,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,
@@ -410,8 +421,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),
         }
     }
@@ -422,10 +440,10 @@ 
     ) -> PyResult<PyObject> {
         let key = key.extract::<PyBytes>(py)?;
         let value = value.extract::<PyBytes>(py)?;
-        self.inner(py)
-            .borrow_mut()
-            .copy_map
-            .insert(key.data(py).to_vec(), value.data(py).to_vec());
+        self.inner(py).borrow_mut().copy_map.insert(
+            HgPathBuf::from_bytes(key.data(py)),
+            HgPathBuf::from_bytes(value.data(py)),
+        );
         Ok(py.None())
     }
     def copymappop(
@@ -434,7 +452,12 @@ 
         default: Option<PyObject>
     ) -> PyResult<Option<PyObject>> {
         let key = key.extract::<PyBytes>(py)?;
-        match self.inner(py).borrow_mut().copy_map.remove(key.data(py)) {
+        match self
+            .inner(py)
+            .borrow_mut()
+            .copy_map
+            .remove(HgPath::new(key.data(py)))
+        {
             Some(_) => Ok(None),
             None => Ok(default),
         }
@@ -461,13 +484,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;
 
@@ -475,7 +498,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,
@@ -491,7 +514,7 @@ 
 py_shared_mapping_iterator!(
     DirstateMapKeysIterator,
     DirstateMapLeakedRef,
-    Vec<u8>,
+    HgPathBuf,
     DirstateEntry,
     DirstateMap::translate_key,
     Option<PyBytes>
@@ -500,7 +523,7 @@ 
 py_shared_mapping_iterator!(
     DirstateMapItemsIterator,
     DirstateMapLeakedRef,
-    Vec<u8>,
+    HgPathBuf,
     DirstateEntry,
     DirstateMap::translate_key_value,
     Option<(PyBytes, PyObject)>
diff --git a/rust/hg-cpython/src/dirstate/dirs_multiset.rs b/rust/hg-cpython/src/dirstate/dirs_multiset.rs
--- a/rust/hg-cpython/src/dirstate/dirs_multiset.rs
+++ b/rust/hg-cpython/src/dirstate/dirs_multiset.rs
@@ -17,7 +17,10 @@ 
 };
 
 use crate::{dirstate::extract_dirstate, ref_sharing::PySharedState};
-use hg::{DirsMultiset, DirstateMapError, DirstateParseError, EntryState};
+use hg::{
+    utils::hg_path::{HgPath, HgPathBuf},
+    DirsMultiset, DirstateMapError, DirstateParseError, EntryState,
+};
 
 py_class!(pub class Dirs |py| {
     data inner: RefCell<DirsMultiset>;
@@ -44,9 +47,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?)
         };
@@ -60,14 +67,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| {
@@ -93,10 +100,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(),
+        )))
     }
 });
 
@@ -107,15 +113,18 @@ 
         Self::create_instance(py, RefCell::new(d), PySharedState::default())
     }
 
-    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())))
     }
 }
 
 py_shared_sequence_iterator!(
     DirsMultisetKeysIterator,
     DirsMultisetLeakedRef,
-    Vec<u8>,
+    HgPathBuf,
     Dirs::translate_key,
     Option<PyBytes>
 );
diff --git a/rust/hg-cpython/src/dirstate/copymap.rs b/rust/hg-cpython/src/dirstate/copymap.rs
--- a/rust/hg-cpython/src/dirstate/copymap.rs
+++ b/rust/hg-cpython/src/dirstate/copymap.rs
@@ -12,6 +12,7 @@ 
 use std::cell::RefCell;
 
 use crate::dirstate::dirstate_map::{DirstateMap, DirstateMapLeakedRef};
+use hg::utils::hg_path::HgPathBuf;
 
 py_class!(pub class CopyMap |py| {
     data dirstate_map: DirstateMap;
@@ -84,24 +85,27 @@ 
     }
     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()),
+        )))
     }
 }
 
 py_shared_mapping_iterator!(
     CopyMapKeysIterator,
     DirstateMapLeakedRef,
-    Vec<u8>,
-    Vec<u8>,
+    HgPathBuf,
+    HgPathBuf,
     CopyMap::translate_key,
     Option<PyBytes>
 );
@@ -109,8 +113,8 @@ 
 py_shared_mapping_iterator!(
     CopyMapItemsIterator,
     DirstateMapLeakedRef,
-    Vec<u8>,
-    Vec<u8>,
+    HgPathBuf,
+    HgPathBuf,
     CopyMap::translate_key_value,
     Option<(PyBytes, PyBytes)>
 );
diff --git a/rust/hg-cpython/src/dirstate.rs b/rust/hg-cpython/src/dirstate.rs
--- a/rust/hg-cpython/src/dirstate.rs
+++ b/rust/hg-cpython/src/dirstate.rs
@@ -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,
diff --git a/rust/hg-core/src/utils/files.rs b/rust/hg-core/src/utils/files.rs
--- a/rust/hg-core/src/utils/files.rs
+++ b/rust/hg-core/src/utils/files.rs
@@ -9,43 +9,24 @@ 
 
 //! 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 {
-    let os_str;
-    #[cfg(unix)]
-    {
-        use std::os::unix::ffi::OsStrExt;
-        os_str = std::ffi::OsStr::from_bytes(bytes);
-    }
-    #[cfg(windows)]
-    {
-        // 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);
-    }
-
-    Path::new(os_str)
-}
 
 /// 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);
+                let p = s.iter().rposition(|c| c == b'/').unwrap_or(0);
                 Some(&s[..p])
             }
             None => None,
@@ -63,7 +44,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,14 +52,24 @@ 
     dirs
 }
 
+/// TODO more than ASCII?
+pub fn normalize_case(path: &HgPath) -> HgPathBuf {
+    #[cfg(windows)] // NTFS compares via upper()
+    return path.to_ascii_uppercase();
+    #[cfg(unix)]
+    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);
     }
@@ -86,8 +77,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);
     }
diff --git a/rust/hg-core/src/lib.rs b/rust/hg-core/src/lib.rs
--- a/rust/hg-core/src/lib.rs
+++ b/rust/hg-core/src/lib.rs
@@ -17,6 +17,7 @@ 
 mod filepatterns;
 pub mod utils;
 
+use crate::utils::hg_path::HgPathBuf;
 pub use filepatterns::{
     build_single_regex, read_pattern_file, PatternSyntax, PatternTuple,
 };
@@ -95,7 +96,7 @@ 
 }
 #[derive(Debug, PartialEq)]
 pub enum DirstateMapError {
-    PathNotFound(Vec<u8>),
+    PathNotFound(HgPathBuf),
     EmptyPath,
 }
 
diff --git a/rust/hg-core/src/filepatterns.rs b/rust/hg-core/src/filepatterns.rs
--- a/rust/hg-core/src/filepatterns.rs
+++ b/rust/hg-core/src/filepatterns.rs
@@ -8,14 +8,19 @@ 
 //! Handling of Mercurial-specific patterns.
 
 use crate::{
-    utils::{files::get_path_from_bytes, SliceExt},
+    utils::{
+        hg_path::{HgPath, HgPathBuf},
+        SliceExt,
+    },
     LineNumber, PatternError, PatternFileError,
 };
 use lazy_static::lazy_static;
 use regex::bytes::{NoExpand, Regex};
 use std::collections::HashMap;
+use std::convert::TryInto;
 use std::fs::File;
 use std::io::Read;
+use std::path::PathBuf;
 use std::vec::Vec;
 
 lazy_static! {
@@ -236,11 +241,11 @@ 
 }
 
 pub type PatternTuple = (Vec<u8>, LineNumber, Vec<u8>);
-type WarningTuple = (Vec<u8>, Vec<u8>);
+type WarningTuple = (HgPathBuf, Vec<u8>);
 
 pub fn parse_pattern_file_contents(
     lines: &[u8],
-    file_path: &[u8],
+    file_path: &HgPath,
     warn: bool,
 ) -> (Vec<PatternTuple>, Vec<WarningTuple>) {
     let comment_regex = Regex::new(r"((?:^|[^\\])(?:\\\\)*)#.*").unwrap();
@@ -303,10 +308,11 @@ 
 }
 
 pub fn read_pattern_file(
-    file_path: &[u8],
+    file_path: &HgPath,
     warn: bool,
 ) -> Result<(Vec<PatternTuple>, Vec<WarningTuple>), PatternFileError> {
-    let mut f = File::open(get_path_from_bytes(file_path))?;
+    let path: PathBuf = file_path.to_owned().try_into()?;
+    let mut f = File::open(path)?;
     let mut contents = Vec::new();
 
     f.read_to_end(&mut contents)?;
@@ -348,18 +354,33 @@ 
 
         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,
+                HgPath::new(b"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,
+                HgPath::new(b"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,
+                HgPath::new(b"file_path"),
+                false
+            )
+            .0,
             vec![(b"relglob:**.o".to_vec(), 1, b"**.o".to_vec())]
         );
     }
diff --git a/rust/hg-core/src/dirstate/parsers.rs b/rust/hg-core/src/dirstate/parsers.rs
--- a/rust/hg-core/src/dirstate/parsers.rs
+++ b/rust/hg-core/src/dirstate/parsers.rs
@@ -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 mut 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
@@ -130,7 +134,7 @@ 
 
         if let Some(copy) = copy_map.get(filename) {
             new_filename.push('\0' as u8);
-            new_filename.extend(copy);
+            new_filename.extend(copy.iter());
         }
 
         packed.write_u8(entry.state.into())?;
@@ -138,7 +142,7 @@ 
         packed.write_i32::<BigEndian>(entry.size)?;
         packed.write_i32::<BigEndian>(new_mtime)?;
         packed.write_i32::<BigEndian>(new_filename.len() as i32)?;
-        packed.extend(new_filename)
+        packed.extend(new_filename.iter())
     }
 
     if packed.len() != expected_size {
@@ -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,
diff --git a/rust/hg-core/src/dirstate/dirstate_map.rs b/rust/hg-core/src/dirstate/dirstate_map.rs
--- a/rust/hg-core/src/dirstate/dirstate_map.rs
+++ b/rust/hg-core/src/dirstate/dirstate_map.rs
@@ -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, CopyMap, DirsMultiset, DirstateEntry,
@@ -18,7 +19,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;
@@ -31,8 +32,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,
 }
@@ -46,8 +47,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 {
@@ -77,7 +78,7 @@ 
     /// Add a tracked file to the dirstate
     pub fn add_file(
         &mut self,
-        filename: &[u8],
+        filename: &HgPath,
         old_state: EntryState,
         entry: DirstateEntry,
     ) {
@@ -110,7 +111,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> {
@@ -146,7 +147,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();
@@ -171,7 +172,7 @@ 
 
     pub fn clear_ambiguous_times(
         &mut self,
-        filenames: Vec<Vec<u8>>,
+        filenames: Vec<HgPathBuf>,
         now: i32,
     ) {
         for filename in filenames {
@@ -196,7 +197,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();
 
@@ -238,12 +239,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)
     }
@@ -347,11 +348,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());
     }
 
@@ -362,7 +363,7 @@ 
         assert_eq!(0, map.len());
 
         map.add_file(
-            b"meh",
+            HgPath::new(b"meh"),
             EntryState::Normal,
             DirstateEntry {
                 state: EntryState::Normal,
@@ -395,7 +396,7 @@ 
         .iter()
         .map(|(fname, (state, mode, size, mtime))| {
             (
-                fname.to_vec(),
+                HgPathBuf::from_bytes(fname.as_ref()),
                 DirstateEntry {
                     state: *state,
                     mode: *mode,
@@ -410,11 +411,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),
diff --git a/rust/hg-core/src/dirstate/dirs_multiset.rs b/rust/hg-core/src/dirstate/dirs_multiset.rs
--- a/rust/hg-core/src/dirstate/dirs_multiset.rs
+++ b/rust/hg-core/src/dirstate/dirs_multiset.rs
@@ -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,
 };
@@ -16,7 +17,7 @@ 
 
 #[derive(PartialEq, Debug)]
 pub struct DirsMultiset {
-    inner: HashMap<Vec<u8>, u32>,
+    inner: HashMap<HgPathBuf, u32>,
 }
 
 impl DirsMultiset {
@@ -24,7 +25,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 {
@@ -46,7 +47,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(),
         };
@@ -61,7 +62,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;
@@ -78,7 +79,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()) {
@@ -101,11 +102,11 @@ 
         Ok(())
     }
 
-    pub fn contains(&self, key: &[u8]) -> bool {
+    pub fn contains(&self, key: &HgPath) -> bool {
         self.inner.contains_key(key)
     }
 
-    pub fn iter(&self) -> impl Iterator<Item = &Vec<u8>> {
+    pub fn iter(&self) -> impl Iterator<Item = &HgPathBuf> {
         self.inner.keys()
     }
 
@@ -122,20 +123,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)
         );
     }
@@ -145,34 +146,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());
@@ -182,42 +189,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);
@@ -242,11 +249,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);
@@ -259,7 +266,7 @@ 
             .iter()
             .map(|f| {
                 (
-                    f.as_bytes().to_vec(),
+                    HgPathBuf::from_bytes(f.as_bytes()),
                     DirstateEntry {
                         state: EntryState::Normal,
                         mode: 0,
@@ -271,7 +278,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);
@@ -292,7 +299,7 @@ 
         .iter()
         .map(|(f, state)| {
             (
-                f.as_bytes().to_vec(),
+                HgPathBuf::from_bytes(f.as_bytes()),
                 DirstateEntry {
                     state: *state,
                     mode: 0,
@@ -306,7 +313,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 =
diff --git a/rust/hg-core/src/dirstate.rs b/rust/hg-core/src/dirstate.rs
--- a/rust/hg-core/src/dirstate.rs
+++ b/rust/hg-core/src/dirstate.rs
@@ -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::HashMap;
 use std::convert::TryFrom;
 
@@ -30,8 +30,8 @@ 
     pub size: i32,
 }
 
-pub type StateMap = HashMap<Vec<u8>, DirstateEntry>;
-pub type CopyMap = HashMap<Vec<u8>, Vec<u8>>;
+pub type StateMap = HashMap<HgPathBuf, DirstateEntry>;
+pub type CopyMap = HashMap<HgPathBuf, HgPathBuf>;
 
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
 pub enum EntryState {