git_prole/git/
path.rs

1use std::fmt::Debug;
2
3use camino::Utf8PathBuf;
4use command_error::CommandExt;
5use miette::miette;
6use tracing::instrument;
7
8use crate::PathDisplay;
9
10use super::GitLike;
11
12/// Git methods for dealing with paths.
13#[repr(transparent)]
14pub struct GitPath<'a, G>(&'a G);
15
16impl<G> Debug for GitPath<'_, G>
17where
18    G: GitLike,
19{
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        f.debug_tuple("GitPath")
22            .field(&self.0.get_current_dir().as_ref())
23            .finish()
24    }
25}
26
27impl<'a, G> GitPath<'a, G>
28where
29    G: GitLike,
30{
31    pub fn new(git: &'a G) -> Self {
32        Self(git)
33    }
34
35    /// Get the path of the repository root, for display purposes only.
36    ///
37    /// If in a working tree, get the repository root (`git rev-parse --show-toplevel`).
38    ///
39    /// If the repository is bare, get the `.git` directory (`git rev-parse --git-dir`):
40    /// - If it's named `.git`, get its parent.
41    /// - Otherwise, return it directly.
42    ///
43    /// Otherwise, error.
44    #[instrument(level = "trace")]
45    pub fn repo_root_display(&self) -> miette::Result<Utf8PathBuf> {
46        if self.0.worktree().is_inside()? {
47            self.0.worktree().root()
48        } else if self.0.config().is_bare()? {
49            let git_dir = self.git_common_dir()?;
50            let git_dir_basename = git_dir
51                .file_name()
52                .ok_or_else(|| miette!("Git directory has no basename: {git_dir}"))?;
53            if git_dir_basename == ".git" {
54                Ok(git_dir
55                    .parent()
56                    .ok_or_else(|| miette!("Git directory has no parent: {git_dir}"))?
57                    .to_owned())
58            } else {
59                Ok(git_dir)
60            }
61        } else {
62            Err(miette!(
63                "Path is not in a working tree or a bare repository: {}",
64                self.0.get_current_dir().as_ref().display_path_cwd()
65            ))
66        }
67    }
68
69    /// Get the `.git` directory path.
70    #[expect(dead_code)] // #[instrument(level = "trace")]
71    pub(crate) fn get_git_dir(&self) -> miette::Result<Utf8PathBuf> {
72        Ok(self
73            .0
74            .as_git()
75            .rev_parse_command()
76            .arg("--git-dir")
77            .output_checked_utf8()
78            .map(|output| Utf8PathBuf::from(output.stdout.trim()))?)
79    }
80
81    /// Get the common `.git` directory for all worktrees.
82    #[instrument(level = "trace")]
83    pub fn git_common_dir(&self) -> miette::Result<Utf8PathBuf> {
84        Ok(self
85            .0
86            .as_git()
87            .rev_parse_command()
88            .arg("--git-common-dir")
89            .output_checked_utf8()
90            .map(|output| Utf8PathBuf::from(output.stdout.trim()))?)
91    }
92}