vdev/commands/meta/
install_git_hooks.rs

1use anyhow::{Ok, Result};
2use std::fs::File;
3use std::io::Write;
4
5#[cfg(unix)]
6use std::os::unix::fs::PermissionsExt;
7
8use crate::app;
9use std::path::Path;
10
11const SIGNOFF_HOOK: &str = r#"#!/usr/bin/env bash
12set -euo pipefail
13
14# Automatically sign off your commits.
15#
16# Installation:
17#
18#    cp scripts/signoff-git-hook.sh .git/hooks/commit-msg
19#
20# It's also possible to symlink the script, however that's a security hazard and
21# is not recommended.
22
23NAME="$(git config user.name)"
24EMAIL="$(git config user.email)"
25
26if [ -z "$NAME" ]; then
27  echo "empty git config user.name"
28  exit 1
29fi
30
31if [ -z "$EMAIL" ]; then
32  echo "empty git config user.email"
33  exit 1
34fi
35
36git interpret-trailers --if-exists doNothing --trailer \
37  "Signed-off-by: $NAME <$EMAIL>" \
38  --in-place "$1"
39
40"#;
41
42/// Install a Git commit message hook that verifies
43/// that all commits have been signed off.
44#[derive(clap::Args, Debug)]
45#[command()]
46pub struct Cli {}
47
48impl Cli {
49    pub fn exec(self) -> Result<()> {
50        let hook_dir = Path::new(app::path()).join(".git").join("hooks");
51
52        // Create a new directory named hooks in the .git directory if it
53        // doesn't already exist.
54        // Use create_dir_all to avoid Error: File exists (os error 17)"
55        std::fs::create_dir_all(&hook_dir)?;
56
57        let file_path = hook_dir.join("commit-msg");
58        let mut file = File::create(&file_path)?;
59        file.write_all(SIGNOFF_HOOK.as_bytes())?;
60        #[cfg(unix)]
61        {
62            file.metadata()?.permissions().set_mode(0o755);
63        }
64        println!("Created signoff script in {}", file_path.display());
65
66        Ok(())
67    }
68}