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#[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 #[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 #[expect(dead_code)] 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 #[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}