1use std::fs;
4use std::io::Write;
5use std::path::Path;
6use std::process::Command;
7
8const GITATTRIBUTES_LINE: &str = "*.murk merge=murk";
10
11const GIT_CONFIG_MERGE_NAME: &str = "merge.murk.name";
13const GIT_CONFIG_MERGE_DRIVER: &str = "merge.murk.driver";
14
15#[derive(Debug, PartialEq, Eq)]
17pub enum MergeDriverSetupStep {
18 GitattributesAlreadyExists,
20 GitattributesAppended,
22 GitattributesCreated,
24 GitConfigured,
26}
27
28pub fn setup_merge_driver() -> Result<Vec<MergeDriverSetupStep>, String> {
35 let mut steps = Vec::new();
36
37 let gitattributes = Path::new(".gitattributes");
39 let merge_line = GITATTRIBUTES_LINE;
40
41 if gitattributes.exists() {
42 let contents = fs::read_to_string(gitattributes)
43 .map_err(|e| format!("reading .gitattributes: {e}"))?;
44 if contents.contains(merge_line) {
45 steps.push(MergeDriverSetupStep::GitattributesAlreadyExists);
46 } else {
47 let mut file = fs::OpenOptions::new()
48 .append(true)
49 .open(gitattributes)
50 .map_err(|e| format!("writing .gitattributes: {e}"))?;
51 writeln!(file, "{merge_line}").map_err(|e| format!("writing .gitattributes: {e}"))?;
52 steps.push(MergeDriverSetupStep::GitattributesAppended);
53 }
54 } else {
55 fs::write(gitattributes, format!("{merge_line}\n"))
56 .map_err(|e| format!("writing .gitattributes: {e}"))?;
57 steps.push(MergeDriverSetupStep::GitattributesCreated);
58 }
59
60 let configs = [
62 (GIT_CONFIG_MERGE_NAME, "murk vault merge"),
63 (GIT_CONFIG_MERGE_DRIVER, "murk merge-driver %O %A %B"),
64 ];
65 for (key, value) in &configs {
66 let status = Command::new("git")
67 .args(["config", key, value])
68 .status()
69 .map_err(|e| format!("running git config: {e}"))?;
70 if !status.success() {
71 return Err(format!("git config {key} failed (are you in a git repo?)"));
72 }
73 }
74 steps.push(MergeDriverSetupStep::GitConfigured);
75
76 Ok(steps)
77}
78
79#[cfg(test)]
80mod tests {
81 use super::*;
82 use std::sync::Mutex;
83
84 static CWD_LOCK: Mutex<()> = Mutex::new(());
86
87 #[test]
88 fn setup_merge_driver_creates_gitattributes() {
89 let _lock = CWD_LOCK.lock().unwrap();
90 let dir = std::env::temp_dir().join("murk_test_git_setup");
91 let _ = std::fs::remove_dir_all(&dir);
92 std::fs::create_dir_all(&dir).unwrap();
93
94 Command::new("git")
96 .args(["init"])
97 .current_dir(&dir)
98 .output()
99 .unwrap();
100
101 let original_dir = std::env::current_dir().unwrap();
102 std::env::set_current_dir(&dir).unwrap();
103
104 let steps = setup_merge_driver().unwrap();
105 assert!(steps.contains(&MergeDriverSetupStep::GitattributesCreated));
106 assert!(steps.contains(&MergeDriverSetupStep::GitConfigured));
107
108 let contents = std::fs::read_to_string(dir.join(".gitattributes")).unwrap();
109 assert!(contents.contains("*.murk merge=murk"));
110
111 std::env::set_current_dir(original_dir).unwrap();
112 std::fs::remove_dir_all(&dir).unwrap();
113 }
114
115 #[test]
116 fn setup_merge_driver_appends_gitattributes() {
117 let _lock = CWD_LOCK.lock().unwrap();
118 let dir = std::env::temp_dir().join("murk_test_git_append");
119 let _ = std::fs::remove_dir_all(&dir);
120 std::fs::create_dir_all(&dir).unwrap();
121
122 Command::new("git")
123 .args(["init"])
124 .current_dir(&dir)
125 .output()
126 .unwrap();
127
128 std::fs::write(dir.join(".gitattributes"), "*.txt text\n").unwrap();
129
130 let original_dir = std::env::current_dir().unwrap();
131 std::env::set_current_dir(&dir).unwrap();
132
133 let steps = setup_merge_driver().unwrap();
134 assert!(steps.contains(&MergeDriverSetupStep::GitattributesAppended));
135
136 let contents = std::fs::read_to_string(dir.join(".gitattributes")).unwrap();
137 assert!(contents.contains("*.txt text"));
138 assert!(contents.contains("*.murk merge=murk"));
139
140 std::env::set_current_dir(original_dir).unwrap();
141 std::fs::remove_dir_all(&dir).unwrap();
142 }
143
144 #[test]
145 fn setup_merge_driver_already_exists() {
146 let _lock = CWD_LOCK.lock().unwrap();
147 let dir = std::env::temp_dir().join("murk_test_git_exists");
148 let _ = std::fs::remove_dir_all(&dir);
149 std::fs::create_dir_all(&dir).unwrap();
150
151 Command::new("git")
152 .args(["init"])
153 .current_dir(&dir)
154 .output()
155 .unwrap();
156
157 std::fs::write(dir.join(".gitattributes"), "*.murk merge=murk\n").unwrap();
158
159 let original_dir = std::env::current_dir().unwrap();
160 std::env::set_current_dir(&dir).unwrap();
161
162 let steps = setup_merge_driver().unwrap();
163 assert!(steps.contains(&MergeDriverSetupStep::GitattributesAlreadyExists));
164
165 std::env::set_current_dir(original_dir).unwrap();
166 std::fs::remove_dir_all(&dir).unwrap();
167 }
168}