1use crate::{DOT_GIT_DIR, MODULES};
2use std::ffi::OsStr;
3use std::path::Path;
4use std::{io::Read, path::PathBuf};
5
6#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
8pub enum RepositoryKind {
9 Submodule,
11 LinkedWorktree,
13 Common,
15}
16
17pub mod from_gitdir_file {
19 #[derive(Debug, thiserror::Error)]
21 #[expect(missing_docs)]
22 pub enum Error {
23 #[error(transparent)]
24 Io(#[from] std::io::Error),
25 #[error(transparent)]
26 Parse(#[from] crate::parse::gitdir::Error),
27 }
28}
29
30fn read_regular_file_content_with_size_limit(path: &std::path::Path) -> std::io::Result<Vec<u8>> {
31 let mut file = std::fs::File::open(path)?;
32 let max_file_size = 1024 * 64; let file_size = file.metadata()?.len();
34 if file_size > max_file_size {
35 return Err(std::io::Error::other(format!(
36 "Refusing to open files larger than {} bytes, '{}' was {} bytes large",
37 max_file_size,
38 path.display(),
39 file_size
40 )));
41 }
42 let mut buf = Vec::with_capacity(512);
43 file.read_to_end(&mut buf)?;
44 Ok(buf)
45}
46
47fn read_plain_file_content(path: &std::path::Path) -> Option<std::io::Result<Vec<u8>>> {
54 use bstr::ByteSlice;
55 let mut buf = match read_regular_file_content_with_size_limit(path) {
56 Ok(buf) => buf,
57 Err(err) if err.kind() == std::io::ErrorKind::NotFound => return None,
58 Err(err) => return Some(Err(err)),
59 };
60 let trimmed_len = buf.trim_end().len();
61 buf.truncate(trimmed_len);
62 if buf.is_empty() {
63 return Some(Err(std::io::Error::new(
64 std::io::ErrorKind::InvalidData,
65 format!("Refusing to read an empty path from '{}'", path.display()),
66 )));
67 }
68 Some(Ok(buf))
69}
70
71pub fn repository_kind(git_dir: &Path) -> Option<RepositoryKind> {
79 if git_dir.file_name() == Some(OsStr::new(DOT_GIT_DIR)) {
80 return Some(RepositoryKind::Common);
81 }
82
83 let mut last_comp = None;
84 git_dir.components().rev().skip(1).any(|c| {
85 if c.as_os_str() == OsStr::new(DOT_GIT_DIR) {
86 true
87 } else {
88 last_comp = Some(c.as_os_str());
89 false
90 }
91 });
92 let last_comp = last_comp?;
93 if last_comp == OsStr::new(MODULES) {
94 RepositoryKind::Submodule.into()
95 } else if last_comp == OsStr::new("worktrees") {
96 RepositoryKind::LinkedWorktree.into()
97 } else {
98 None
99 }
100}
101
102pub fn from_plain_file(path: &std::path::Path) -> Option<std::io::Result<PathBuf>> {
106 read_plain_file_content(path).map(|res| res.map(gix_path::from_bstring))
107}
108
109pub fn from_plain_file_relative_to_file(path: &std::path::Path) -> Option<std::io::Result<PathBuf>> {
115 read_plain_file_content(path).map(|res| {
116 res.and_then(|buf| {
117 let plain_path = gix_path::from_bstring(buf);
118 if !plain_path.is_relative() {
119 return Ok(plain_path);
120 }
121 match path.parent() {
122 Some(parent) => Ok(parent.join(plain_path)),
123 _ => Err(std::io::Error::other(format!(
124 "'{path}' has no parent, but '{plain_path}' is relative. It's impossible",
125 path = path.display(),
126 plain_path = plain_path.display()
127 ))),
128 }
129 })
130 })
131}
132
133pub fn from_gitdir_file(path: &std::path::Path) -> Result<PathBuf, from_gitdir_file::Error> {
135 let buf = read_regular_file_content_with_size_limit(path)?;
136 let mut gitdir = crate::parse::gitdir(&buf)?;
137 if let Some(parent) = path.parent() {
138 gitdir = parent.join(gitdir);
139 }
140 Ok(gitdir)
141}
142
143pub fn without_dot_git_dir(mut path: PathBuf) -> PathBuf {
145 if path.file_name().and_then(std::ffi::OsStr::to_str) == Some(DOT_GIT_DIR) {
146 path.pop();
147 }
148 path
149}