1use std::{borrow::Cow, ffi::OsStr, path::Path};
2
3use crate::{DOT_GIT_DIR, MODULES};
4
5pub fn bare(git_dir_candidate: &Path) -> bool {
9 !(git_dir_candidate.join("index").exists() || (git_dir_candidate.file_name() == Some(OsStr::new(DOT_GIT_DIR))))
10}
11
12pub fn submodule_git_dir(git_dir: &Path) -> bool {
14 let mut last_comp = None;
15 git_dir.file_name() != Some(OsStr::new(DOT_GIT_DIR))
16 && git_dir.components().rev().skip(1).any(|c| {
17 if c.as_os_str() == OsStr::new(DOT_GIT_DIR) {
18 true
19 } else {
20 last_comp = Some(c.as_os_str());
21 false
22 }
23 })
24 && last_comp == Some(OsStr::new(MODULES))
25}
26
27pub fn git(git_dir: &Path) -> Result<crate::repository::Kind, crate::is_git::Error> {
39 let git_dir_metadata = git_dir.metadata().map_err(|err| crate::is_git::Error::Metadata {
40 source: err,
41 path: git_dir.into(),
42 })?;
43 let cwd = gix_fs::current_dir(false)?;
45 git_with_metadata(git_dir, git_dir_metadata, &cwd)
46}
47
48pub(crate) fn git_with_metadata(
49 git_dir: &Path,
50 git_dir_metadata: std::fs::Metadata,
51 cwd: &Path,
52) -> Result<crate::repository::Kind, crate::is_git::Error> {
53 #[derive(Eq, PartialEq)]
54 enum Kind {
55 MaybeRepo,
56 Submodule,
57 LinkedWorkTreeDir,
58 WorkTreeGitDir { work_dir: std::path::PathBuf },
59 }
60
61 let dot_git = if git_dir_metadata.is_file() {
62 let private_git_dir = crate::path::from_gitdir_file(git_dir)?;
63 Cow::Owned(private_git_dir)
64 } else {
65 Cow::Borrowed(git_dir)
66 };
67
68 {
69 if !dot_git.join("HEAD").exists() {
71 return Err(crate::is_git::Error::MissingHead);
72 }
73 let refs = gix_ref::file::Store::at(dot_git.as_ref().into(), Default::default());
77 match refs.find_loose("HEAD") {
78 Ok(head) => {
79 if head.name.as_bstr() != "HEAD" {
80 return Err(crate::is_git::Error::MisplacedHead {
81 name: head.name.into_inner(),
82 });
83 }
84 }
85 Err(gix_ref::file::find::existing::Error::Find(gix_ref::file::find::Error::ReferenceCreation {
86 source: _,
87 relative_path,
88 })) if relative_path == Path::new("HEAD") => {
89 }
91 Err(err) => {
92 return Err(err.into());
93 }
94 }
95 }
96
97 let (common_dir, kind) = if git_dir_metadata.is_file() {
98 let common_dir = dot_git.join("commondir");
99 match crate::path::from_plain_file(&common_dir) {
100 Some(Err(err)) => {
101 return Err(crate::is_git::Error::MissingCommonDir {
102 missing: common_dir,
103 source: err,
104 })
105 }
106 Some(Ok(common_dir)) => {
107 let common_dir = dot_git.join(common_dir);
108 (Cow::Owned(common_dir), Kind::LinkedWorkTreeDir)
109 }
110 None => (dot_git.clone(), Kind::Submodule),
111 }
112 } else {
113 let common_dir = dot_git.join("commondir");
114 let worktree_and_common_dir = crate::path::from_plain_file(&common_dir)
115 .and_then(Result::ok)
116 .and_then(|cd| {
117 crate::path::from_plain_file(&dot_git.join("gitdir"))
118 .and_then(Result::ok)
119 .map(|worktree_gitfile| (crate::path::without_dot_git_dir(worktree_gitfile), cd))
120 });
121 match worktree_and_common_dir {
122 Some((work_dir, common_dir)) => {
123 let common_dir = dot_git.join(common_dir);
124 (Cow::Owned(common_dir), Kind::WorkTreeGitDir { work_dir })
125 }
126 None => (dot_git.clone(), Kind::MaybeRepo),
127 }
128 };
129
130 {
131 let objects_path = common_dir.join("objects");
132 if !objects_path.is_dir() {
133 return Err(crate::is_git::Error::MissingObjectsDirectory { missing: objects_path });
134 }
135 }
136 {
137 let refs_path = common_dir.join("refs");
138 if !refs_path.is_dir() {
139 return Err(crate::is_git::Error::MissingRefsDirectory { missing: refs_path });
140 }
141 }
142 Ok(match kind {
143 Kind::LinkedWorkTreeDir => crate::repository::Kind::WorkTree {
144 linked_git_dir: Some(dot_git.into_owned()),
145 },
146 Kind::WorkTreeGitDir { work_dir } => crate::repository::Kind::WorkTreeGitDir { work_dir },
147 Kind::Submodule => crate::repository::Kind::Submodule {
148 git_dir: dot_git.into_owned(),
149 },
150 Kind::MaybeRepo => {
151 let conformed_git_dir = if git_dir == Path::new(".") {
152 gix_path::realpath_opts(git_dir, cwd, gix_path::realpath::MAX_SYMLINKS)
153 .map(Cow::Owned)
154 .unwrap_or(Cow::Borrowed(git_dir))
155 } else {
156 gix_path::normalize(git_dir.into(), cwd).unwrap_or(Cow::Borrowed(git_dir))
157 };
158 if bare(conformed_git_dir.as_ref()) || conformed_git_dir.extension() == Some(OsStr::new("git")) {
159 crate::repository::Kind::PossiblyBare
160 } else if submodule_git_dir(conformed_git_dir.as_ref()) {
161 crate::repository::Kind::SubmoduleGitDir
162 } else if conformed_git_dir.file_name() == Some(OsStr::new(DOT_GIT_DIR)) {
163 crate::repository::Kind::WorkTree { linked_git_dir: None }
164 } else {
173 crate::repository::Kind::PossiblyBare
174 }
175 }
176 })
177}