vdev/commands/meta/
install_git_hooks.rs

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