rhg: make output of `files` relative to the current directory and the root
authorRaphaël Gomès <rgomes@octobus.net>
Thu, 30 Jul 2020 16:55:44 +0200
changeset 45436 1b3197047f5c
parent 45435 64de86fd0984
child 45437 e339693addc0
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
rust/hg-core/src/operations/list_tracked_files.rs
rust/hg-core/src/utils/files.rs
rust/rhg/src/commands/files.rs
--- a/rust/hg-core/src/operations/list_tracked_files.rs	Tue Sep 08 19:36:40 2020 +0530
+++ b/rust/hg-core/src/operations/list_tracked_files.rs	Thu Jul 30 16:55:44 2020 +0200
@@ -14,7 +14,7 @@
 use std::fmt;
 use std::fs;
 use std::io;
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
 
 /// Kind of error encoutered by ListTrackedFiles
 #[derive(Debug)]
@@ -60,6 +60,15 @@
         let content = fs::read(&dirstate)?;
         Ok(ListDirstateTrackedFiles { content })
     }
+
+    /// Returns the repository root directory
+    /// TODO I think this is a crutch that creates a dependency that should not
+    /// be there. Operations that need the root of the repository should get
+    /// it themselves, probably in a lazy fashion. But this would make the
+    /// current series even larger, so this is simplified for now.
+    pub fn get_root(&self) -> &Path {
+        &self.root
+    }
 }
 
 /// List files under Mercurial control in the working directory
--- 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::*;
--- a/rust/rhg/src/commands/files.rs	Tue Sep 08 19:36:40 2020 +0530
+++ b/rust/rhg/src/commands/files.rs	Thu Jul 30 16:55:44 2020 +0200
@@ -2,6 +2,8 @@
 use crate::error::{CommandError, CommandErrorKind};
 use crate::ui::Ui;
 use hg::operations::{ListTrackedFiles, ListTrackedFilesErrorKind};
+use hg::utils::files::{get_bytes_from_path, relativize_path};
+use hg::utils::hg_path::HgPathBuf;
 
 pub const HELP_TEXT: &str = "
 List tracked files.
@@ -38,9 +40,17 @@
             }
         })?;
 
+        let cwd = std::env::current_dir()
+            .or_else(|e| Err(CommandErrorKind::CurrentDirNotFound(e)))?;
+        let rooted_cwd = cwd
+            .strip_prefix(operation_builder.get_root())
+            .expect("cwd was already checked within the repository");
+        let rooted_cwd = HgPathBuf::from(get_bytes_from_path(rooted_cwd));
+
         let mut stdout = self.ui.stdout_buffer();
+
         for file in files {
-            stdout.write_all(file.as_bytes())?;
+            stdout.write_all(relativize_path(file, &rooted_cwd).as_ref())?;
             stdout.write_all(b"\n")?;
         }
         stdout.flush()?;