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 @@ -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 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) -> Cow<[u8]> { + if cwd.as_ref().is_empty() { + Cow::Borrowed(path.as_bytes()) + } else { + let mut res: Vec = 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/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()?;