grit_lib/config/handle.rs
1//! Repository-scoped configuration reads and local writes via [`ConfigHandle`].
2use std::fs;
3use std::sync::Arc;
4
5use crate::config::{ConfigFile, ConfigScope, ConfigSet};
6use crate::error::Result;
7use crate::repo::Repository;
8
9/// Read merged config and update the repository-local config file.
10#[derive(Debug, Copy, Clone)]
11pub struct ConfigHandle<'repo> {
12 repo: &'repo Repository,
13}
14
15impl<'repo> ConfigHandle<'repo> {
16 /// Bind config operations to `repo`.
17 #[inline]
18 #[must_use]
19 pub fn new(repo: &'repo Repository) -> Self {
20 Self { repo }
21 }
22
23 /// Underlying repository.
24 #[inline]
25 #[must_use]
26 pub fn repository(&self) -> &'repo Repository {
27 self.repo
28 }
29
30 /// Look up a merged config value (system / global / local / worktree cascade).
31 ///
32 /// # Errors
33 ///
34 /// Propagates config load failures.
35 ///
36 /// # Examples
37 ///
38 /// ```
39 /// # fn main() -> grit_lib::error::Result<()> {
40 /// use grit_lib::repo::{InitOptions, Repository};
41 /// let dir = std::env::temp_dir().join("grit-config-get-doc");
42 /// let repo = Repository::init(&dir, &InitOptions::default())?;
43 /// let _ = repo.config_handle().get("core.repositoryformatversion");
44 /// # Ok(())
45 /// # }
46 /// ```
47 #[inline]
48 pub fn get(&self, key: &str) -> Result<Option<String>> {
49 Ok(self.snapshot()?.get(key))
50 }
51
52 /// Set a key in the repository-local config file and reload the in-memory snapshot.
53 ///
54 /// For linked worktrees this writes `commondir/config` when the per-worktree git directory
55 /// has no `config` file, matching Git's `--local` target.
56 ///
57 /// # Errors
58 ///
59 /// Propagates config parse, validation, and I/O errors.
60 #[inline]
61 pub fn set_local(&self, key: &str, value: &str) -> Result<()> {
62 let path = crate::repo::local_config_write_path(self.repo.git_dir());
63 let content = fs::read_to_string(&path).unwrap_or_default();
64 let mut file = ConfigFile::parse(&path, &content, ConfigScope::Local)?;
65 file.set(key, value)?;
66 file.write()?;
67 self.repo.reload_config()
68 }
69
70 /// Remove `key` from the repository-local config file and reload the snapshot.
71 ///
72 /// # Errors
73 ///
74 /// Propagates config parse, validation, and I/O errors.
75 pub fn unset_local(&self, key: &str) -> Result<()> {
76 let path = crate::repo::local_config_write_path(self.repo.git_dir());
77 let content = fs::read_to_string(&path).unwrap_or_default();
78 let mut file = ConfigFile::parse(&path, &content, ConfigScope::Local)?;
79 file.unset(key)?;
80 file.write()?;
81 self.repo.reload_config()
82 }
83
84 /// Remove an entire section from the repository-local config file.
85 ///
86 /// Section names use dotted form (for example `branch.topic` or `branch.feature/x`).
87 ///
88 /// # Errors
89 ///
90 /// Propagates config parse, validation, and I/O errors.
91 pub fn remove_local_section(&self, section: &str) -> Result<()> {
92 let path = crate::repo::local_config_write_path(self.repo.git_dir());
93 let content = fs::read_to_string(&path).unwrap_or_default();
94 let mut file = ConfigFile::parse(&path, &content, ConfigScope::Local)?;
95 file.remove_section(section)?;
96 file.write()?;
97 self.repo.reload_config()
98 }
99
100 /// Shared merged config snapshot (same as [`Repository::config`]).
101 #[inline]
102 pub fn snapshot(&self) -> Result<Arc<ConfigSet>> {
103 self.repo.config()
104 }
105}