| 1 | |
| 2 | |
| 3 | use std::fs::{self, File}; |
| 4 | use std::io::Write; |
| 5 | use std::os::unix::fs::PermissionsExt; |
| 6 | use std::path::Path; |
| 7 | |
| 8 | use crate::error::{Error, Result}; |
| 9 | |
| 10 | |
| 11 | fn quote_config_value(v: &str) -> Result<String> { |
| 12 | if v.chars().any(char::is_control) { |
| 13 | return Err(Error::InvalidInput(format!( |
| 14 | "config value contains control characters: {v:?}" |
| 15 | ))); |
| 16 | } |
| 17 | Ok(format!( |
| 18 | "\"{}\"", |
| 19 | v.replace('\\', "\\\\").replace('"', "\\\"") |
| 20 | )) |
| 21 | } |
| 22 | |
| 23 | |
| 24 | fn write_atomic(dir: &Path, name: &str, contents: &[u8], mode: u32) -> Result<()> { |
| 25 | let tmp = dir.join(format!(".{name}.tmp")); |
| 26 | { |
| 27 | let mut f = fs::OpenOptions::new() |
| 28 | .write(true) |
| 29 | .create(true) |
| 30 | .truncate(true) |
| 31 | .open(&tmp)?; |
| 32 | f.write_all(contents)?; |
| 33 | f.set_permissions(fs::Permissions::from_mode(mode))?; |
| 34 | f.sync_all()?; |
| 35 | } |
| 36 | fs::rename(&tmp, dir.join(name))?; |
| 37 | File::open(dir)?.sync_all()?; |
| 38 | Ok(()) |
| 39 | } |
| 40 | |
| 41 | |
| 42 | pub fn write_managed_gitconfig(data_dir: &Path) -> Result<()> { |
| 43 | if !data_dir.is_absolute() { |
| 44 | return Err(Error::InvalidInput(format!( |
| 45 | "data dir must be absolute: {}", |
| 46 | data_dir.display() |
| 47 | ))); |
| 48 | } |
| 49 | let hooks = data_dir.join("hooks"); |
| 50 | let hooks = hooks |
| 51 | .to_str() |
| 52 | .ok_or_else(|| Error::InvalidInput("data dir is not valid UTF-8".into()))?; |
| 53 | let hooks = quote_config_value(hooks)?; |
| 54 | |
| 55 | |
| 56 | let body = format!( |
| 57 | "[core]\n\ |
| 58 | \thooksPath = {hooks}\n\ |
| 59 | \tfsync = committed,reference\n\ |
| 60 | \tfsyncMethod = fsync\n\ |
| 61 | \tcommitGraph = true\n\ |
| 62 | [receive]\n\ |
| 63 | \tfsckObjects = true\n\ |
| 64 | \tadvertisePushOptions = true\n\ |
| 65 | \tdenyNonFastForwards = false\n\ |
| 66 | [transfer]\n\ |
| 67 | \tfsckObjects = true\n\ |
| 68 | \thideRefs = refs/forge-internal/\n\ |
| 69 | [fetch]\n\ |
| 70 | \tfsckObjects = true\n\ |
| 71 | \twriteCommitGraph = true\n\ |
| 72 | [uploadpack]\n\ |
| 73 | \tallowFilter = true\n\ |
| 74 | \tallowRefInWant = true\n\ |
| 75 | \thideRefs = refs/forge-internal/\n\ |
| 76 | [protocol \"file\"]\n\ |
| 77 | \tallow = never\n\ |
| 78 | [protocol \"ext\"]\n\ |
| 79 | \tallow = never\n" |
| 80 | ); |
| 81 | fs::create_dir_all(data_dir)?; |
| 82 | write_atomic(data_dir, "gitconfig", body.as_bytes(), 0o644) |
| 83 | } |
| 84 | |
| 85 | |
| 86 | |
| 87 | pub fn write_hook_shims(data_dir: &Path, forge_bin: &Path) -> Result<()> { |
| 88 | let bin = forge_bin |
| 89 | .to_str() |
| 90 | .ok_or_else(|| Error::InvalidInput("forge binary path is not valid UTF-8".into()))?; |
| 91 | if !forge_bin.is_absolute() { |
| 92 | return Err(Error::InvalidInput(format!( |
| 93 | "forge binary path must be absolute: {bin}" |
| 94 | ))); |
| 95 | } |
| 96 | if bin.chars().any(|c| c == '\'' || c.is_control()) { |
| 97 | return Err(Error::InvalidInput(format!( |
| 98 | "forge binary path contains a quote or control character: {bin:?}" |
| 99 | ))); |
| 100 | } |
| 101 | let hooks = data_dir.join("hooks"); |
| 102 | fs::create_dir_all(&hooks)?; |
| 103 | for name in ["pre-receive", "post-receive"] { |
| 104 | let body = format!("#!/bin/sh\nexec '{bin}' hook {name}\n"); |
| 105 | write_atomic(&hooks, name, body.as_bytes(), 0o755)?; |
| 106 | } |
| 107 | Ok(()) |
| 108 | } |
| 109 | |
| 110 | #[cfg(test)] |
| 111 | mod tests { |
| 112 | use super::*; |
| 113 | |
| 114 | #[test] |
| 115 | fn managed_gitconfig_enables_commit_graph() { |
| 116 | let tmp = tempfile::tempdir().expect("tempdir"); |
| 117 | write_managed_gitconfig(tmp.path()).expect("write gitconfig"); |
| 118 | let body = fs::read_to_string(tmp.path().join("gitconfig")).expect("read gitconfig"); |
| 119 | assert!(body.contains("\tcommitGraph = true\n")); |
| 120 | assert!(body.contains("\twriteCommitGraph = true\n")); |
| 121 | assert!( |
| 122 | !body.contains("gc.auto"), |
| 123 | "gc.auto must stay at its default" |
| 124 | ); |
| 125 | } |
| 126 | } |