Patchwork D8872: rhg: make output of `files` relative to the current directory and the root

login
register
mail settings
Submitter phabricator
Date Aug. 3, 2020, 2:28 p.m.
Message ID <differential-rev-PHID-DREV-3zudpev2awsnsh6p3m37-req@mercurial-scm.org>
Download mbox | patch
Permalink /patch/46971/
State Superseded
Headers show

Comments

phabricator - Aug. 3, 2020, 2:28 p.m.
acezar created this revision.
Herald added a reviewer: hg-reviewers.
Herald added a subscriber: mercurial-patches.

REVISION SUMMARY
  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)

REPOSITORY
  rHG Mercurial

BRANCH
  default

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

AFFECTED FILES
  rust/hg-core/src/operations/list_tracked_files.rs
  rust/hg-core/src/utils/files.rs
  rust/rhg/src/commands/files.rs

CHANGE DETAILS




To: acezar, #hg-reviewers
Cc: mercurial-patches, mercurial-devel

Patch

diff --git a/rust/rhg/src/commands/files.rs b/rust/rhg/src/commands/files.rs
--- a/rust/rhg/src/commands/files.rs
+++ b/rust/rhg/src/commands/files.rs
@@ -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()?;
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
@@ -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::*;
diff --git a/rust/hg-core/src/operations/list_tracked_files.rs b/rust/hg-core/src/operations/list_tracked_files.rs
--- a/rust/hg-core/src/operations/list_tracked_files.rs
+++ b/rust/hg-core/src/operations/list_tracked_files.rs
@@ -7,7 +7,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)]
@@ -53,6 +53,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