git_policy_cli/
shadow_config.rs1use super::transaction_git::{GitContext, GitFailure, GitRequest, run_git_checked};
19use std::path::Path;
21
22#[derive(Clone, Debug, Eq, PartialEq)]
32pub struct RepositoryFormat {
33 pub version: String,
35 pub extensions: Vec<(String, String)>,
37}
38
39pub fn quote_git_config_value(value: &str) -> String {
51 let mut body: String = String::with_capacity(value.len());
52 for character in value.chars() {
53 match character {
54 '\\' => body.push_str(r"\\"),
55 '"' => body.push_str(r#"\""#),
56 '\n' => body.push_str(r"\n"),
57 '\t' => body.push_str(r"\t"),
58 other => body.push(other),
59 }
60 }
61 return format!("\"{body}\"");
62}
63
64pub fn read_repository_format(
75 context: &GitContext,
76 common_dir: &Path,
77) -> Result<RepositoryFormat, GitFailure> {
78 let mut request: GitRequest = GitRequest::new(
79 common_dir,
80 &["config", "--file", "config", "--null", "--list"],
81 );
82 request.without_prefix = true;
83 let output = run_git_checked(context, &request)?;
84 let mut version: Option<String> = None;
85 let mut extensions: Vec<(String, String)> = Vec::new();
86 for entry in output.stdout.split(|byte| { return *byte == 0; }).filter(|entry| { return !entry.is_empty(); }) {
87 let (key, value): (&[u8], &[u8]) = match entry.iter().position(|byte| { return *byte == b'\n'; }) {
89 Some(newline) => (&entry[..newline], &entry[newline + 1..]),
90 None => (entry, &b"true"[..]),
91 };
92 let key_text: String = String::from_utf8_lossy(key).into_owned();
93 let value_text: String = String::from_utf8_lossy(value).into_owned();
94 if key_text.to_lowercase() == "core.repositoryformatversion" {
95 version = Some(value_text.clone());
96 }
97 if key_text.is_char_boundary(11) && key_text[..11].to_lowercase() == "extensions." {
98 extensions.push((key_text[11..].to_string(), value_text));
99 }
100 }
101 return Ok(RepositoryFormat {
102 version: version.unwrap_or_else(|| { return String::from("0"); }),
103 extensions,
104 });
105}
106
107pub fn format_shadow_config(format: &RepositoryFormat, common_dir: &Path, worktree_root: &Path) -> String {
118 let common: String = common_dir.display().to_string();
119 let mut lines: Vec<String> = vec![
120 String::from("[core]"),
121 format!("\trepositoryformatversion = {}", quote_git_config_value(format.version.as_str())),
122 format!("\thooksPath = {}", quote_git_config_value(format!("{common}/hooks").as_str())),
123 ];
124 if !format.extensions.is_empty() {
125 lines.push(String::from("[extensions]"));
126 for (name, value) in &format.extensions {
127 lines.push(format!("\t{name} = {}", quote_git_config_value(value.as_str())));
128 }
129 }
130 lines.push(String::from("[include]"));
131 lines.push(format!(
132 "\tpath = {}",
133 quote_git_config_value(format!("{common}/config").as_str())
134 ));
135 lines.push(String::from("[core]"));
136 lines.push(format!(
137 "\tworktree = {}",
138 quote_git_config_value(worktree_root.display().to_string().as_str())
139 ));
140 lines.push(String::from("[gc]"));
141 lines.push(String::from("\tauto = 0"));
142 lines.push(String::from("[maintenance]"));
143 lines.push(String::from("\tauto = false"));
144 lines.push(String::new());
145 return lines.join("\n");
146}
147
148#[cfg(test)]
150#[path = "shadow_config_tests.rs"]
151mod tests;