Build git 2.54 from source and add hook subcommand
Debian bookworm ships git 2.39 but we need 2.54+ for
hook.<name>.command config support, which lets quire register
hooks via git config instead of writing shim scripts to disk.

The hook subcommand is a no-op that logs invocations — enough to
verify the dispatch path works end-to-end.

Assisted-by: GLM-5.1 via pi
change rrtolvtzvoxsxrvqvqtzznoxkntnnsqt
commit 507fb0263daa4b2fce8606a2f9090bc58f239841
author Alpha Chen <alpha@kejadlen.dev>
date
parent rzxkqkul
diff --git a/Dockerfile b/Dockerfile
index fc74769..7923e3b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,32 @@
-# Build stage.
+# Git build stage.
+#
+# Debian bookworm ships git 2.39, but we need 2.54+ for hook.<name>.command
+# config support. This lets quire register hooks via git config instead of
+# writing shim scripts to disk — the hook dispatches directly into the
+# quire binary as `quire hook <name>`.
+ARG GIT_VERSION=2.54.0
+FROM debian:bookworm-slim AS git-builder
+
+RUN apt-get update \
+    && apt-get install -y --no-install-recommends \
+        ca-certificates \
+        curl \
+        gcc \
+        gettext \
+        libcurl4-openssl-dev \
+        libexpat1-dev \
+        libssl-dev \
+        libz-dev \
+        make \
+    && rm -rf /var/lib/apt/lists/*
+
+RUN curl -fsSL https://github.com/git/git/archive/refs/tags/v${GIT_VERSION}.tar.gz \
+    | tar xz \
+    && cd git-${GIT_VERSION} \
+    && make -j$(nproc) prefix=/usr/local NO_TCLTK=1 NO_GETTEXT= \
+    && make prefix=/usr/local install
+
+# Quire build stage.
 FROM rust:1.88-bookworm AS builder
 
 WORKDIR /usr/src/quire
@@ -12,10 +40,13 @@ FROM debian:bookworm-slim
 
 RUN apt-get update \
     && apt-get install -y --no-install-recommends \
-        git \
         ca-certificates \
+        libcurl4 \
+        libexpat1 \
     && rm -rf /var/lib/apt/lists/*
 
+COPY --from=git-builder /usr/local/bin/git /usr/local/bin/git
+COPY --from=git-builder /usr/local/libexec/git-core/ /usr/local/libexec/git-core/
 COPY --from=builder /usr/local/cargo/bin/quire /usr/local/bin/quire
 
 # Volume layout per PLAN.md. Ownership is set on the host; the container
diff --git a/src/bin/quire/commands/hook.rs b/src/bin/quire/commands/hook.rs
new file mode 100644
index 0000000..17c5fb2
--- /dev/null
+++ b/src/bin/quire/commands/hook.rs
@@ -0,0 +1,6 @@
+use miette::Result;
+
+pub async fn run(hook_name: &str) -> Result<()> {
+    tracing::info!(hook = %hook_name, "hook invoked");
+    Ok(())
+}
diff --git a/src/bin/quire/commands/mod.rs b/src/bin/quire/commands/mod.rs
index 003cac0..68ed5a0 100644
--- a/src/bin/quire/commands/mod.rs
+++ b/src/bin/quire/commands/mod.rs
@@ -1,2 +1,3 @@
 pub mod exec;
+pub mod hook;
 pub mod serve;
diff --git a/src/bin/quire/main.rs b/src/bin/quire/main.rs
index 6842589..5ab7114 100644
--- a/src/bin/quire/main.rs
+++ b/src/bin/quire/main.rs
@@ -37,6 +37,12 @@ enum Commands {
         /// Pass as a single argument: quire exec "git-receive-pack '/foo.git'"
         command: Vec<String>,
     },
+
+    /// Invoked by git hooks configured via hook.<name>.command.
+    Hook {
+        /// The hook name (e.g. pre-receive, post-receive).
+        hook_name: String,
+    },
 }
 
 #[tokio::main]
@@ -63,6 +69,7 @@ async fn main() -> Result<()> {
     match command {
         Commands::Serve => commands::serve::run(&config).await?,
         Commands::Exec { command } => commands::exec::run(&config, command).await?,
+        Commands::Hook { hook_name } => commands::hook::run(&hook_name).await?,
     }
 
     Ok(())