diff rust/hg-core/src/utils/files.rs @ 45436:1b3197047f5c

rhg: make output of `files` relative to the current directory and the root This matches the behavior of `hg files`. The util is added in `hg-core` instead of `rhg` because this operation could be useful for other external tools. (this was definitely not prompted by rust issue #50784, I swear) Differential Revision: https://phab.mercurial-scm.org/D8872
author Raphaël Gomès <rgomes@octobus.net>
date Thu, 30 Jul 2020 16:55:44 +0200
parents 26114bd6ec60
children 95d6f31e88db
line wrap: on
line diff
--- a/rust/hg-core/src/utils/files.rs	Tue Sep 08 19:36:40 2020 +0530
+++ b/rust/hg-core/src/utils/files.rs	Thu Jul 30 16:55:44 2020 +0200
@@ -16,7 +16,7 @@
 };
 use lazy_static::lazy_static;
 use same_file::is_same_file;
-use std::borrow::ToOwned;
+use std::borrow::{Cow, ToOwned};
 use std::fs::Metadata;
 use std::iter::FusedIterator;
 use std::ops::Deref;
@@ -248,6 +248,66 @@
     }
 }
 
+/// Returns the representation of the path relative to the current working
+/// directory for display purposes.
+///
+/// `cwd` is a `HgPath`, so it is considered relative to the root directory
+/// of the repository.
+///
+/// # Examples
+///
+/// ```
+/// use hg::utils::hg_path::HgPath;
+/// use hg::utils::files::relativize_path;
+/// use std::borrow::Cow;
+///
+/// let file = HgPath::new(b"nested/file");
+/// let cwd = HgPath::new(b"");
+/// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"nested/file"));
+///
+/// let cwd = HgPath::new(b"nested");
+/// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"file"));
+///
+/// let cwd = HgPath::new(b"other");
+/// assert_eq!(relativize_path(file, cwd), Cow::Borrowed(b"../nested/file"));
+/// ```
+pub fn relativize_path(path: &HgPath, cwd: impl AsRef<HgPath>) -> Cow<[u8]> {
+    if cwd.as_ref().is_empty() {
+        Cow::Borrowed(path.as_bytes())
+    } else {
+        let mut res: Vec<u8> = Vec::new();
+        let mut path_iter = path.as_bytes().split(|b| *b == b'/').peekable();
+        let mut cwd_iter =
+            cwd.as_ref().as_bytes().split(|b| *b == b'/').peekable();
+        loop {
+            match (path_iter.peek(), cwd_iter.peek()) {
+                (Some(a), Some(b)) if a == b => (),
+                _ => break,
+            }
+            path_iter.next();
+            cwd_iter.next();
+        }
+        let mut need_sep = false;
+        for _ in cwd_iter {
+            if need_sep {
+                res.extend(b"/")
+            } else {
+                need_sep = true
+            };
+            res.extend(b"..");
+        }
+        for c in path_iter {
+            if need_sep {
+                res.extend(b"/")
+            } else {
+                need_sep = true
+            };
+            res.extend(c);
+        }
+        Cow::Owned(res)
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;