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
--- 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()?;