view rust/hg-core/src/operations/find_root.rs @ 45877:5c736ba5dc27

rust-status: don't bubble up os errors, translate them to bad matches In the rare cases when either the OS/filesystem throws an error on an otherwise valid action, or because a path is not representable on the filesystem, or because of concurrent actions in the filesystem, we want to warn the user about said path instead of bubbling up the error, causing an exception to be raised in the Python layer. Differential Revision: https://phab.mercurial-scm.org/D9320
author Rapha?l Gom?s <rgomes@octobus.net>
date Mon, 16 Nov 2020 16:38:57 +0100
parents 3d9f1dfc52c2
children dca9cb99971c
line wrap: on
line source

use std::fmt;
use std::path::{Path, PathBuf};

/// Kind of error encoutered by FindRoot
#[derive(Debug)]
pub enum FindRootErrorKind {
    /// Root of the repository has not been found
    /// Contains the current directory used by FindRoot
    RootNotFound(PathBuf),
    /// The current directory does not exists or permissions are insufficient
    /// to get access to it
    GetCurrentDirError(std::io::Error),
}

/// A FindRoot error
#[derive(Debug)]
pub struct FindRootError {
    /// Kind of error encoutered by FindRoot
    pub kind: FindRootErrorKind,
}

impl std::error::Error for FindRootError {}

impl fmt::Display for FindRootError {
    fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> fmt::Result {
        unimplemented!()
    }
}

/// Find the root of the repository
/// by searching for a .hg directory in the current directory and its
/// ancestors
pub struct FindRoot<'a> {
    current_dir: Option<&'a Path>,
}

impl<'a> FindRoot<'a> {
    pub fn new() -> Self {
        Self { current_dir: None }
    }

    pub fn new_from_path(current_dir: &'a Path) -> Self {
        Self {
            current_dir: Some(current_dir),
        }
    }

    pub fn run(&self) -> Result<PathBuf, FindRootError> {
        let current_dir = match self.current_dir {
            None => std::env::current_dir().or_else(|e| {
                Err(FindRootError {
                    kind: FindRootErrorKind::GetCurrentDirError(e),
                })
            })?,
            Some(path) => path.into(),
        };

        if current_dir.join(".hg").exists() {
            return Ok(current_dir);
        }
        let ancestors = current_dir.ancestors();
        for parent in ancestors {
            if parent.join(".hg").exists() {
                return Ok(parent.into());
            }
        }
        Err(FindRootError {
            kind: FindRootErrorKind::RootNotFound(current_dir.to_path_buf()),
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use tempfile;

    #[test]
    fn dot_hg_not_found() {
        let tmp_dir = tempfile::tempdir().unwrap();
        let path = tmp_dir.path();

        let err = FindRoot::new_from_path(&path).run().unwrap_err();

        // TODO do something better
        assert!(match err {
            FindRootError { kind } => match kind {
                FindRootErrorKind::RootNotFound(p) => p == path.to_path_buf(),
                _ => false,
            },
        })
    }

    #[test]
    fn dot_hg_in_current_path() {
        let tmp_dir = tempfile::tempdir().unwrap();
        let root = tmp_dir.path();
        fs::create_dir_all(root.join(".hg")).unwrap();

        let result = FindRoot::new_from_path(&root).run().unwrap();

        assert_eq!(result, root)
    }

    #[test]
    fn dot_hg_in_parent() {
        let tmp_dir = tempfile::tempdir().unwrap();
        let root = tmp_dir.path();
        fs::create_dir_all(root.join(".hg")).unwrap();

        let result =
            FindRoot::new_from_path(&root.join("some/nested/directory"))
                .run()
                .unwrap();

        assert_eq!(result, root)
    }
} /* tests */