149 lines · 5.5 KB · Rust
RawDownload
use crate::{DOT_GIT_DIR, MODULES};use std::ffi::OsStr;use std::path::Path;use std::{io::Read, path::PathBuf}; /// The kind of repository by looking exclusively at its `git_dir`.#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]pub enum RepositoryKind {    /// The repository resides in `.git/modules/`.    Submodule,    /// The repository resides in `.git/worktrees/`.    LinkedWorktree,    /// The repository is in a `.git` directory.    Common,} ///pub mod from_gitdir_file {    /// The error returned by [`from_gitdir_file()`][crate::path::from_gitdir_file()].    #[derive(Debug, thiserror::Error)]    #[expect(missing_docs)]    pub enum Error {        #[error(transparent)]        Io(#[from] std::io::Error),        #[error(transparent)]        Parse(#[from] crate::parse::gitdir::Error),    }} fn read_regular_file_content_with_size_limit(path: &std::path::Path) -> std::io::Result<Vec<u8>> {    let mut file = std::fs::File::open(path)?;    let max_file_size = 1024 * 64; // NOTE: git allows 1MB here    let file_size = file.metadata()?.len();    if file_size > max_file_size {        return Err(std::io::Error::other(format!(            "Refusing to open files larger than {} bytes, '{}' was {} bytes large",            max_file_size,            path.display(),            file_size        )));    }    let mut buf = Vec::with_capacity(512);    file.read_to_end(&mut buf)?;    Ok(buf)} /// Read a plain path file, returning `None` if the file is missing.////// Linked-worktree `gitdir` files are plain path files in Git, not `gitdir:`/// files. Match Git's `get_linked_worktree()` behavior by trimming trailing/// whitespace before interpreting the content. Empty or whitespace-only path/// files are invalid.fn read_plain_file_content(path: &std::path::Path) -> Option<std::io::Result<Vec<u8>>> {    use bstr::ByteSlice;    let mut buf = match read_regular_file_content_with_size_limit(path) {        Ok(buf) => buf,        Err(err) if err.kind() == std::io::ErrorKind::NotFound => return None,        Err(err) => return Some(Err(err)),    };    let trimmed_len = buf.trim_end().len();    buf.truncate(trimmed_len);    if buf.is_empty() {        return Some(Err(std::io::Error::new(            std::io::ErrorKind::InvalidData,            format!("Refusing to read an empty path from '{}'", path.display()),        )));    }    Some(Ok(buf))} /// Guess the kind of repository by looking at its `git_dir` path and return it./// Return `None` if `git_dir` isn't called `.git` or isn't within `.git/worktrees` or `.git/modules`, or if it's/// a `.git` suffix like in `foo.git`./// The check for markers is case-sensitive under the assumption that nobody meddles with standard directories.////// As this considers only the path, it cannot recognize linked worktrees of repositories whose Git directory isn't/// named `.git`, such as natively bare repositories. Inspect the worktree's `commondir` file to identify those.pub fn repository_kind(git_dir: &Path) -> Option<RepositoryKind> {    if git_dir.file_name() == Some(OsStr::new(DOT_GIT_DIR)) {        return Some(RepositoryKind::Common);    }     let mut last_comp = None;    git_dir.components().rev().skip(1).any(|c| {        if c.as_os_str() == OsStr::new(DOT_GIT_DIR) {            true        } else {            last_comp = Some(c.as_os_str());            false        }    });    let last_comp = last_comp?;    if last_comp == OsStr::new(MODULES) {        RepositoryKind::Submodule.into()    } else if last_comp == OsStr::new("worktrees") {        RepositoryKind::LinkedWorktree.into()    } else {        None    }} /// Reads a plain path from a file that contains it as its only content, with trailing whitespace trimmed.////// Empty or whitespace-only path files are invalid.pub fn from_plain_file(path: &std::path::Path) -> Option<std::io::Result<PathBuf>> {    read_plain_file_content(path).map(|res| res.map(gix_path::from_bstring))} /// Reads a plain path from a file like [`from_plain_file()`], resolving relative paths against/// the file's containing directory as needed.////// The `path` argument is expected to name the path file itself, which is always supposed to be/// a file path.pub fn from_plain_file_relative_to_file(path: &std::path::Path) -> Option<std::io::Result<PathBuf>> {    read_plain_file_content(path).map(|res| {        res.and_then(|buf| {            let plain_path = gix_path::from_bstring(buf);            if !plain_path.is_relative() {                return Ok(plain_path);            }            match path.parent() {                Some(parent) => Ok(parent.join(plain_path)),                _ => Err(std::io::Error::other(format!(                    "'{path}' has no parent, but '{plain_path}' is relative. It's impossible",                    path = path.display(),                    plain_path = plain_path.display()                ))),            }        })    })} /// Reads typical `gitdir: ` files from disk as used by worktrees and submodules.pub fn from_gitdir_file(path: &std::path::Path) -> Result<PathBuf, from_gitdir_file::Error> {    let buf = read_regular_file_content_with_size_limit(path)?;    let mut gitdir = crate::parse::gitdir(&buf)?;    if let Some(parent) = path.parent() {        gitdir = parent.join(gitdir);    }    Ok(gitdir)} /// Conditionally pop a trailing `.git` dir if present.pub fn without_dot_git_dir(mut path: PathBuf) -> PathBuf {    if path.file_name().and_then(std::ffi::OsStr::to_str) == Some(DOT_GIT_DIR) {        path.pop();    }    path}