Patchwork D7864: rust-utils: add Rust implementation of Python's "os.path.splitdrive"

login
register
mail settings
Submitter phabricator
Date Jan. 15, 2020, 9:55 p.m.
Message ID <aca7dda026c43c2d21bc8050a73223b6@localhost.localdomain>
Download mbox | patch
Permalink /patch/44388/
State Not Applicable
Headers show

Comments

phabricator - Jan. 15, 2020, 9:55 p.m.
Alphare updated this revision to Diff 19316.

REPOSITORY
  rHG Mercurial

CHANGES SINCE LAST UPDATE
  https://phab.mercurial-scm.org/D7864?vs=19256&id=19316

BRANCH
  default

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

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

AFFECTED FILES
  rust/hg-core/src/utils/files.rs

CHANGE DETAILS




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

Patch

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
@@ -85,6 +85,77 @@ 
     path.to_ascii_lowercase()
 }
 
+#[cfg(windows)]
+/// Copied from the Python stdlib's `os.path.splitdrive` implementation.
+///
+/// Split a pathname into drive/UNC sharepoint and relative path specifiers.
+/// Returns a 2-tuple (drive_or_unc, path); either part may be empty.
+///
+/// If you assign
+///  result = split_drive(p)
+/// It is always true that:
+///  result[0] + result[1] == p
+///
+/// If the path contained a drive letter, drive_or_unc will contain everything
+/// up to and including the colon.
+/// e.g. split_drive("c:/dir") returns ("c:", "/dir")
+///
+/// If the path contained a UNC path, the drive_or_unc will contain the host
+/// name and share up to but not including the fourth directory separator
+/// character.
+/// e.g. split_drive("//host/computer/dir") returns ("//host/computer", "/dir")
+///
+/// Paths cannot contain both a drive letter and a UNC path.
+pub fn split_drive<'a>(path: &HgPath) -> (&HgPath, &HgPath) {
+    let bytes = path.as_bytes();
+    let is_sep = |b| std::path::is_separator(b as char);
+
+    if path.len() < 2 {
+        (HgPath::new(b""), path)
+    } else if is_sep(bytes[0])
+        && is_sep(bytes[1])
+        && (path.len() == 2 || !is_sep(bytes[2]))
+    {
+        // Is a UNC path:
+        // vvvvvvvvvvvvvvvvvvvv drive letter or UNC path
+        // \\machine\mountpoint\directory\etc\...
+        //           directory ^^^^^^^^^^^^^^^
+
+        let machine_end_index = bytes[2..].iter().position(|b| is_sep(*b));
+        let mountpoint_start_index = if let Some(i) = machine_end_index {
+            i + 2
+        } else {
+            return (HgPath::new(b""), path);
+        };
+
+        match bytes[mountpoint_start_index + 1..]
+            .iter()
+            .position(|b| is_sep(*b))
+        {
+            // A UNC path can't have two slashes in a row
+            // (after the initial two)
+            Some(0) => (HgPath::new(b""), path),
+            Some(i) => {
+                let (a, b) = bytes.split_at(mountpoint_start_index + 1 + i);
+                (HgPath::new(a), HgPath::new(b))
+            }
+            None => (path, HgPath::new(b"")),
+        }
+    } else if bytes[1] == b':' {
+        // Drive path c:\directory
+        let (a, b) = bytes.split_at(2);
+        (HgPath::new(a), HgPath::new(b))
+    } else {
+        (HgPath::new(b""), path)
+    }
+}
+
+#[cfg(unix)]
+/// Split a pathname into drive and path. On Posix, drive is always empty.
+pub fn split_drive(path: &HgPath) -> (&HgPath, &HgPath) {
+    (HgPath::new(b""), path)
+}
+
 #[derive(Eq, PartialEq, Ord, PartialOrd, Copy, Clone)]
 pub struct HgMetadata {
     pub st_dev: u64,
@@ -133,4 +204,101 @@ 
         assert_eq!(dirs.next(), None);
         assert_eq!(dirs.next(), None);
     }
+
+    #[test]
+    #[cfg(unix)]
+    fn test_split_drive() {
+        // Taken from the Python stdlib's tests
+        assert_eq!(
+            split_drive(HgPath::new(br"/foo/bar")),
+            (HgPath::new(b""), HgPath::new(br"/foo/bar"))
+        );
+        assert_eq!(
+            split_drive(HgPath::new(br"foo:bar")),
+            (HgPath::new(b""), HgPath::new(br"foo:bar"))
+        );
+        assert_eq!(
+            split_drive(HgPath::new(br":foo:bar")),
+            (HgPath::new(b""), HgPath::new(br":foo:bar"))
+        );
+        // Also try NT paths; should not split them
+        assert_eq!(
+            split_drive(HgPath::new(br"c:\foo\bar")),
+            (HgPath::new(b""), HgPath::new(br"c:\foo\bar"))
+        );
+        assert_eq!(
+            split_drive(HgPath::new(b"c:/foo/bar")),
+            (HgPath::new(b""), HgPath::new(br"c:/foo/bar"))
+        );
+        assert_eq!(
+            split_drive(HgPath::new(br"\\conky\mountpoint\foo\bar")),
+            (
+                HgPath::new(b""),
+                HgPath::new(br"\\conky\mountpoint\foo\bar")
+            )
+        );
+    }
+
+    #[test]
+    #[cfg(windows)]
+    fn test_split_drive() {
+        assert_eq!(
+            split_drive(HgPath::new(br"c:\foo\bar")),
+            (HgPath::new(br"c:"), HgPath::new(br"\foo\bar"))
+        );
+        assert_eq!(
+            split_drive(HgPath::new(b"c:/foo/bar")),
+            (HgPath::new(br"c:"), HgPath::new(br"/foo/bar"))
+        );
+        assert_eq!(
+            split_drive(HgPath::new(br"\\conky\mountpoint\foo\bar")),
+            (
+                HgPath::new(br"\\conky\mountpoint"),
+                HgPath::new(br"\foo\bar")
+            )
+        );
+        assert_eq!(
+            split_drive(HgPath::new(br"//conky/mountpoint/foo/bar")),
+            (
+                HgPath::new(br"//conky/mountpoint"),
+                HgPath::new(br"/foo/bar")
+            )
+        );
+        assert_eq!(
+            split_drive(HgPath::new(br"\\\conky\mountpoint\foo\bar")),
+            (
+                HgPath::new(br""),
+                HgPath::new(br"\\\conky\mountpoint\foo\bar")
+            )
+        );
+        assert_eq!(
+            split_drive(HgPath::new(br"///conky/mountpoint/foo/bar")),
+            (
+                HgPath::new(br""),
+                HgPath::new(br"///conky/mountpoint/foo/bar")
+            )
+        );
+        assert_eq!(
+            split_drive(HgPath::new(br"\\conky\\mountpoint\foo\bar")),
+            (
+                HgPath::new(br""),
+                HgPath::new(br"\\conky\\mountpoint\foo\bar")
+            )
+        );
+        assert_eq!(
+            split_drive(HgPath::new(br"//conky//mountpoint/foo/bar")),
+            (
+                HgPath::new(br""),
+                HgPath::new(br"//conky//mountpoint/foo/bar")
+            )
+        );
+        // UNC part containing U+0130
+        assert_eq!(
+            split_drive(HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT/foo/bar")),
+            (
+                HgPath::new(b"//conky/MOUNTPO\xc4\xb0NT"),
+                HgPath::new(br"/foo/bar")
+            )
+        );
+    }
 }