1use std::fmt;
7use std::path::Path;
8use std::str::FromStr;
9
10use crate::error::{Error, Result};
11use crate::repo::{read_repository_format_from_git_dir, validate_repository_format_parsed};
12
13#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
15#[non_exhaustive]
16pub enum RefStorageFormat {
17 #[default]
19 Files,
20 Reftable,
22}
23
24impl fmt::Display for RefStorageFormat {
25 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
26 f.write_str(match self {
27 Self::Files => "files",
28 Self::Reftable => "reftable",
29 })
30 }
31}
32
33impl FromStr for RefStorageFormat {
34 type Err = Error;
35
36 fn from_str(s: &str) -> Result<Self> {
37 Self::parse_config_value(s)
38 }
39}
40
41impl RefStorageFormat {
42 pub fn parse_config_value(raw: &str) -> Result<Self> {
48 let trimmed = raw.trim();
49 let lower = trimmed.to_ascii_lowercase();
50 let name = lower
51 .split_once(':')
52 .map(|(prefix, _)| prefix)
53 .unwrap_or(lower.as_str());
54 match name {
55 "files" => Ok(Self::Files),
56 "reftable" => Ok(Self::Reftable),
57 _ => Err(Error::InvalidRefStorageFormat {
58 value: trimmed.to_owned(),
59 }),
60 }
61 }
62
63 pub fn detect(git_dir: &Path) -> Result<Self> {
71 let parsed = read_repository_format_from_git_dir(git_dir)?;
72 Self::from_repository_format(&parsed)
73 }
74
75 pub(crate) fn from_repository_format(parsed: &crate::repo::RepositoryFormat) -> Result<Self> {
81 validate_repository_format_parsed(parsed)?;
82 match parsed.ref_storage.as_deref() {
83 None => Ok(Self::Files),
84 Some(raw) => Self::parse_config_value(raw),
85 }
86 }
87
88 pub fn validate_config_extension(raw: &str) -> Result<()> {
94 Self::parse_config_value(raw).map(|_| ())
95 }
96
97 #[must_use]
99 pub const fn is_reftable(self) -> bool {
100 matches!(self, Self::Reftable)
101 }
102}
103
104pub fn detect_format(git_dir: &Path) -> Result<RefStorageFormat> {
106 RefStorageFormat::detect(git_dir)
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use std::fs;
113 use tempfile::TempDir;
114
115 #[test]
116 fn detect_reflects_rewritten_ref_storage_without_repository() {
117 let tmp = TempDir::new().expect("tempdir");
118 let git_dir = tmp.path().join(".git");
119 fs::create_dir_all(&git_dir).expect("git dir");
120 fs::write(
121 git_dir.join("config"),
122 "[core]\n\trepositoryformatversion = 0\n",
123 )
124 .expect("config");
125 assert_eq!(
126 RefStorageFormat::detect(&git_dir).expect("detect"),
127 RefStorageFormat::Files
128 );
129
130 fs::write(
131 git_dir.join("config"),
132 "[core]\n\trepositoryformatversion = 1\n[extensions]\n\trefStorage = reftable\n",
133 )
134 .expect("rewrite");
135 assert_eq!(
136 RefStorageFormat::detect(&git_dir).expect("detect again"),
137 RefStorageFormat::Reftable
138 );
139 }
140}