Repository: j178/prefligit (language: python)

Issue:
Implement `pre-commit init-templatedir`



Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index e13dd9a..6252880 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1544,6 +1544,7 @@ dependencies = [
  "rayon",
  "regex",
  "rusqlite",
+ "same-file",
  "serde",
  "serde_json",
  "serde_yaml",
diff --git a/Cargo.toml b/Cargo.toml
index 10d198e..3d74311 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -44,6 +44,7 @@ owo-colors = "4.1.0"
 rand = "0.8.5"
 rayon = "1.10.0"
 rusqlite = { version = "0.32.1", features = ["bundled"] }
+same-file = "1.0.6"
 serde = { version = "1.0.210", features = ["derive"] }
 serde_json = "1.0.132"
 serde_yaml = "0.9.34"
diff --git a/src/cli/install.rs b/src/cli/install.rs
index 940cfa4..acaa8eb 100644
--- a/src/cli/install.rs
+++ b/src/cli/install.rs
@@ -5,11 +5,13 @@ use std::path::{Path, PathBuf};
 use anyhow::Result;
 use indoc::indoc;
 use owo_colors::OwoColorize;
+use same_file::is_same_file;
 
 use crate::cli::run;
 use crate::cli::{ExitStatus, HookType};
 use crate::fs::Simplified;
 use crate::git;
+use crate::git::git_cmd;
 use crate::hook::Project;
 use crate::printer::Printer;
 use crate::store::Store;
@@ -21,8 +23,9 @@ pub(crate) async fn install(
     overwrite: bool,
     allow_missing_config: bool,
     printer: Printer,
+    git_dir: Option<&Path>,
 ) -> Result<ExitStatus> {
-    if git::has_hooks_path_set().await? {
+    if git_dir.is_none() && git::has_hooks_path_set().await? {
         writeln!(
             printer.stderr(),
             indoc::indoc! {"
@@ -35,7 +38,12 @@ pub(crate) async fn install(
 
     let hook_types = get_hook_types(config.clone(), hook_types);
 
-    let hooks_path = git::get_git_common_dir().await?.join("hooks");
+    let hooks_path = if let Some(dir) = git_dir {
+        dir.join("hooks")
+    } else {
+        git::get_git_common_dir().await?.join("hooks")
+    };
+
     fs_err::create_dir_all(&hooks_path)?;
 
     let project = Project::from_config_file(config);
@@ -233,3 +241,43 @@ pub(crate) async fn uninstall(
 
     Ok(ExitStatus::Success)
 }
+
+pub(crate) async fn init_template_dir(
+    directory: PathBuf,
+    config: Option<PathBuf>,
+    hook_types: Vec<HookType>,
+    requires_config: bool,
+    printer: Printer,
+) -> Result<ExitStatus> {
+    install(
+        config,
+        hook_types,
+        false,
+        true,
+        !requires_config,
+        printer,
+        Some(&directory),
+    )
+    .await?;
+
+    let output = git_cmd("git config")?
+        .arg("config")
+        .arg("init.templateDir")
+        .check(false)
+        .output()
+        .await?;
+    let template_dir = String::from_utf8_lossy(&output.stdout).trim().to_string();
+
+    if template_dir.is_empty() || !is_same_file(&directory, Path::new(&template_dir))? {
+        writeln!(
+            printer.stderr(),
+            "{}",
+            indoc::formatdoc! {"
+                `init.templateDir` not set to the target directory
+                try `git config --global init.templateDir '{directory}'`?
+            ", directory = directory.user_display().cyan() }
+        )?;
+    }
+
+    Ok(ExitStatus::Success)
+}
diff --git a/src/cli/mod.rs b/src/cli/mod.rs
index 1efae69..ce36749 100644
--- a/src/cli/mod.rs
+++ b/src/cli/mod.rs
@@ -16,7 +16,7 @@ mod validate;
 
 pub(crate) use clean::clean;
 pub(crate) use hook_impl::hook_impl;
-pub(crate) use install::{install, uninstall};
+pub(crate) use install::{init_template_dir, install, uninstall};
 pub(crate) use run::run;
 pub(crate) use sample_config::sample_config;
 pub(crate) use self_update::self_update;
@@ -172,7 +172,7 @@ pub(crate) enum Command {
     Clean,
     /// Install hook script in a directory intended for use with `git config init.templateDir`.
     #[command(name = "init-templatedir")]
-    InitTemplateDir,
+    InitTemplateDir(InitTemplateDirArgs),
     /// Try the pre-commit hooks in the current repo.
     TryRepo(Box<RunArgs>),
 
@@ -339,3 +339,17 @@ pub(crate) struct GenerateShellCompletionArgs {
     #[arg(value_enum)]
     pub shell: clap_complete::Shell,
 }
+
+#[derive(Debug, Args)]
+pub(crate) struct InitTemplateDirArgs {
+    /// The directory in which to write the hook script.
+    pub(crate) directory: PathBuf,
+
+    /// Assume cloned repos should have a `pre-commit` config.
+    #[arg(long)]
+    pub(crate) no_allow_missing_config: bool,
+
+    /// Which hook type to install.
+    #[arg(short = 't', long = "hook-type", value_name = "HOOK_TYPE", value_enum)]
+    pub(crate) hook_types: Vec<HookType>,
+}
diff --git a/src/main.rs b/src/main.rs
index 4991642..7580b77 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -172,6 +172,7 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
                 args.overwrite,
                 args.allow_missing_config,
                 printer,
+                None,
             )
             .await
         }
@@ -241,6 +242,18 @@ async fn run(mut cli: Cli) -> Result<ExitStatus> {
             clap_complete::generate(args.shell, &mut command, bin_name, &mut std::io::stdout());
             Ok(ExitStatus::Success)
         }
+        Command::InitTemplateDir(args) => {
+            show_settings!(args);
+
+            cli::init_template_dir(
+                args.directory,
+                cli.globals.config,
+                args.hook_types,
+                args.no_allow_missing_config,
+                printer,
+            )
+            .await
+        }
         _ => {
             writeln!(printer.stderr(), "Command not implemented yet")?;
             Ok(ExitStatus::Failure)


Test patch:
diff --git a/tests/install.rs b/tests/install.rs
index a5b8bd4..57c9433 100644
--- a/tests/install.rs
+++ b/tests/install.rs
@@ -218,3 +218,20 @@ fn uninstall() -> anyhow::Result<()> {
 
     Ok(())
 }
+
+#[test]
+fn init_template_dir() {
+    let context = TestContext::new();
+    context.init_project();
+
+    cmd_snapshot!(context.filters(), context.command().arg("init-templatedir").arg(".git"), @r#"
+    success: true
+    exit_code: 0
+    ----- stdout -----
+    pre-commit installed at .git/hooks/pre-commit
+
+    ----- stderr -----
+    `init.templateDir` not set to the target directory
+    try `git config --global init.templateDir '.git'`?
+    "#);
+}