git_branch_stash/
config.rs1#[derive(Default, Clone, Debug)]
2pub struct RepoConfig {
3 pub protected_branches: Option<Vec<String>>,
4 pub capacity: Option<usize>,
5}
6
7static STACK_FIELD: &str = "stack.stack";
8static PROTECTED_STACK_FIELD: &str = "stack.protected-branch";
9static BACKUP_CAPACITY_FIELD: &str = "branch-stash.capacity";
10
11static DEFAULT_PROTECTED_BRANCHES: [&str; 4] = ["main", "master", "dev", "stable"];
12const DEFAULT_CAPACITY: usize = 30;
13
14impl RepoConfig {
15 pub fn from_all(repo: &git2::Repository) -> eyre::Result<Self> {
16 log::trace!("Loading gitconfig");
17 let default_config = match git2::Config::open_default() {
18 Ok(config) => Some(config),
19 Err(err) => {
20 log::debug!("Failed to load git config: {err}");
21 None
22 }
23 };
24 let config = Self::from_defaults_internal(default_config.as_ref());
25 let config = if let Some(default_config) = default_config.as_ref() {
26 config.update(Self::from_gitconfig(default_config))
27 } else {
28 config
29 };
30 let config = config.update(Self::from_workdir(repo)?);
31 let config = config.update(Self::from_repo(repo)?);
32 let config = config.update(Self::from_env());
33 Ok(config)
34 }
35
36 pub fn from_repo(repo: &git2::Repository) -> eyre::Result<Self> {
37 let config_path = git_dir_config(repo);
38 log::trace!("Loading {}", config_path.display());
39 if config_path.exists() {
40 match git2::Config::open(&config_path) {
41 Ok(config) => Ok(Self::from_gitconfig(&config)),
42 Err(err) => {
43 log::debug!("Failed to load git config: {err}");
44 Ok(Default::default())
45 }
46 }
47 } else {
48 Ok(Default::default())
49 }
50 }
51
52 pub fn from_workdir(repo: &git2::Repository) -> eyre::Result<Self> {
53 let workdir = repo
54 .workdir()
55 .ok_or_else(|| eyre::eyre!("Cannot read config in bare repository."))?;
56 let config_path = workdir.join(".gitconfig");
57 log::trace!("Loading {}", config_path.display());
58 if config_path.exists() {
59 match git2::Config::open(&config_path) {
60 Ok(config) => Ok(Self::from_gitconfig(&config)),
61 Err(err) => {
62 log::debug!("Failed to load git config: {err}");
63 Ok(Default::default())
64 }
65 }
66 } else {
67 Ok(Default::default())
68 }
69 }
70
71 pub fn from_env() -> Self {
72 let mut config = Self::default();
73
74 let params = git_config_env::ConfigParameters::new();
75 config = config.update(Self::from_env_iter(params.iter()));
76
77 let params = git_config_env::ConfigEnv::new();
78 config = config.update(Self::from_env_iter(
79 params.iter().map(|(k, v)| (k, Some(v))),
80 ));
81
82 config
83 }
84
85 fn from_env_iter<'s>(
86 iter: impl Iterator<Item = (std::borrow::Cow<'s, str>, Option<std::borrow::Cow<'s, str>>)>,
87 ) -> Self {
88 let mut config = Self::default();
89
90 for (key, value) in iter {
91 log::trace!("Env config: {key}={value:?}");
92 if key == PROTECTED_STACK_FIELD {
93 if let Some(value) = value {
94 config
95 .protected_branches
96 .get_or_insert_with(Vec::new)
97 .push(value.into_owned());
98 }
99 } else if key == BACKUP_CAPACITY_FIELD {
100 config.capacity = value.as_deref().and_then(|s| s.parse::<usize>().ok());
101 } else {
102 log::warn!(
103 "Unsupported config: {}={}",
104 key,
105 value.as_deref().unwrap_or("")
106 );
107 }
108 }
109
110 config
111 }
112
113 pub fn from_defaults() -> Self {
114 log::trace!("Loading gitconfig");
115 let config = match git2::Config::open_default() {
116 Ok(config) => Some(config),
117 Err(err) => {
118 log::debug!("Failed to load git config: {err}");
119 None
120 }
121 };
122 Self::from_defaults_internal(config.as_ref())
123 }
124
125 fn from_defaults_internal(config: Option<&git2::Config>) -> Self {
126 let mut conf = Self::default();
127
128 let mut protected_branches: Vec<String> = Vec::new();
129 if let Some(config) = config {
130 let default_branch = default_branch(config);
131 let default_branch_ignore = default_branch.to_owned();
132 protected_branches.push(default_branch_ignore);
133 }
134 protected_branches.extend(DEFAULT_PROTECTED_BRANCHES.iter().map(|s| (*s).to_owned()));
137 conf.protected_branches = Some(protected_branches);
138
139 conf.capacity = Some(DEFAULT_CAPACITY);
140
141 conf
142 }
143
144 pub fn from_gitconfig(config: &git2::Config) -> Self {
145 let protected_branches = config
146 .multivar(PROTECTED_STACK_FIELD, None)
147 .map(|entries| {
148 let mut protected_branches = Vec::new();
149 entries
150 .for_each(|entry| {
151 if let Some(value) = entry.value() {
152 protected_branches.push(value.to_owned());
153 }
154 })
155 .unwrap();
156 if protected_branches.is_empty() {
157 None
158 } else {
159 Some(protected_branches)
160 }
161 })
162 .unwrap_or(None);
163
164 let capacity = config
165 .get_i64(BACKUP_CAPACITY_FIELD)
166 .map(|i| i as usize)
167 .ok();
168
169 Self {
170 protected_branches,
171 capacity,
172 }
173 }
174
175 pub fn write_repo(&self, repo: &git2::Repository) -> eyre::Result<()> {
176 let config_path = git_dir_config(repo);
177 log::trace!("Loading {}", config_path.display());
178 let mut config = git2::Config::open(&config_path)?;
179 log::info!("Writing {}", config_path.display());
180 self.to_gitconfig(&mut config)?;
181 Ok(())
182 }
183
184 pub fn to_gitconfig(&self, config: &mut git2::Config) -> eyre::Result<()> {
185 if let Some(protected_branches) = self.protected_branches.as_ref() {
186 let _ = config.remove_multivar(PROTECTED_STACK_FIELD, ".*");
188 for branch in protected_branches {
189 config.set_multivar(PROTECTED_STACK_FIELD, "^$", branch)?;
190 }
191 }
192 Ok(())
193 }
194
195 pub fn update(mut self, other: Self) -> Self {
196 match (&mut self.protected_branches, other.protected_branches) {
197 (Some(lhs), Some(rhs)) => lhs.extend(rhs),
198 (None, Some(rhs)) => self.protected_branches = Some(rhs),
199 (_, _) => (),
200 }
201 self.capacity = other.capacity.or(self.capacity);
202
203 self
204 }
205
206 pub fn protected_branches(&self) -> &[String] {
207 self.protected_branches.as_deref().unwrap_or(&[])
208 }
209
210 pub fn capacity(&self) -> Option<usize> {
211 let capacity = self.capacity.unwrap_or(DEFAULT_CAPACITY);
212 (capacity != 0).then_some(capacity)
213 }
214}
215
216impl std::fmt::Display for RepoConfig {
217 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
218 writeln!(f, "[{}]", STACK_FIELD.split_once('.').unwrap().0)?;
219 for branch in self.protected_branches() {
220 writeln!(
221 f,
222 "\t{}={}",
223 PROTECTED_STACK_FIELD.split_once('.').unwrap().1,
224 branch
225 )?;
226 }
227 writeln!(f, "[{}]", BACKUP_CAPACITY_FIELD.split_once('.').unwrap().0)?;
228 writeln!(
229 f,
230 "\t{}={}",
231 BACKUP_CAPACITY_FIELD.split_once('.').unwrap().1,
232 self.capacity().unwrap_or(0)
233 )?;
234 Ok(())
235 }
236}
237
238fn git_dir_config(repo: &git2::Repository) -> std::path::PathBuf {
239 repo.path().join("config")
240}
241
242fn default_branch(config: &git2::Config) -> &str {
243 config.get_str("init.defaultBranch").ok().unwrap_or("main")
244}