Skip to content

feat: add {{ hook_stdin }} for pre-push stdin forwarding#825

Merged
jdx merged 1 commit intojdx:mainfrom
JohanLorenzo:feature/hook-stdin
Apr 16, 2026
Merged

feat: add {{ hook_stdin }} for pre-push stdin forwarding#825
jdx merged 1 commit intojdx:mainfrom
JohanLorenzo:feature/hook-stdin

Conversation

@JohanLorenzo
Copy link
Copy Markdown
Contributor

Hey again! Thank you very much for merging and releasing #807! It's been working great with almost all git lfs hooks. There's one I missed in my original PR: pre-push. While {{ hook_args }} solved positional argument forwarding, git lfs pre-push also needs stdin. git pipes ref data through it so LFS knows which objects to upload.

Currently, hk's PrePush handler consumes stdin for its own ref resolution, leaving nothing for step commands. This PR preserves the raw stdin as {{ hook_stdin }} so steps can pipe it via the existing stdin field.

The problem

git lfs pre-push receives ref data via stdin:
refs/heads/main refs/heads/main

Without it, git lfs pre-push exits 0 silently and uploads nothing. The remote then rejects the push:

  $ git push origin main
    files - Fetching git status
  ✔ files - Fetching files between refs/remotes/origin/main and HEAD (1 file)
  ❯ git-lfs
    git-lfs – 0 files –  – git lfs pre-push origin URL
  ✔ git-lfs
  remote: LFS objects are missing. Ensure LFS is properly set up or try a manual "git lfs push --all".
   ! [remote rejected]   main -> main (pre-receive hook declined)

After the fix

  $ git push origin main
    files - Fetching git status
  ✔ files - Fetching files between refs/remotes/origin/main and HEAD (1 file)
  ❯ git-lfs
    git-lfs – 0 files –  – git lfs pre-push origin URL
    git-lfs – Uploading LFS objects: 100% (1/1), 0 B | 0 B/s, done.
  ✔ git-lfs
  To URL
     cdd0595..21798fe  main -> main

Steps to reproduce locally

  mkdir /tmp/hk-lfs-test && cd /tmp/hk-lfs-test
  git init && git lfs install
  echo "*.bin filter=lfs diff=lfs merge=lfs -text" > .gitattributes

  cat > hk.pkl << 'EOF'
  amends "package://github.com/jdx/hk/releases/download/v1.42.0/[email protected]#/Config.pkl"
  hooks {
      ["pre-push"] {
          steps {
              ["git-lfs"] {
                  check = "git lfs pre-push {{ hook_args }}"
                  stdin = "{{ hook_stdin }}"
              }
          }
      }
  }
  EOF

  dd if=/dev/urandom bs=1024 count=5 of=big.bin 2>/dev/null
  git add .gitattributes hk.pkl big.bin && git commit -m "init"
  git init --bare ../hk-lfs-remote.git
  git remote add origin ../hk-lfs-remote.git
  HK=0 git push -u origin main          # initial push without hk
  git remote set-head origin main

  hk install
  dd if=/dev/urandom bs=1024 count=5 of=big2.bin 2>/dev/null
  git add big2.bin && git commit -m "second"
  git push origin main                   # should show "Uploading LFS objects"

Usage

hooks {
    ["pre-push"] {
        steps {
            ["git-lfs"] {
                check = "git lfs pre-push {{ hook_args }}"
                stdin = "{{ hook_stdin }}"
            }
        }
    }
}

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request enables the pre-push hook to capture standard input and expose it through the hook_stdin template variable. The changes include updating the PrePush execution logic to store stdin in the template context and adding a BATS test suite to verify the functionality with git references and git-lfs. I have no feedback to provide.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 12, 2026

Greptile Summary

Implements {{ hook_stdin }} for pre-push and post-rewrite hooks, capturing git's piped stdin and exposing it as a Tera template variable so steps like git lfs pre-push can receive the ref data they need. The Rust changes in both handlers are correct — Tera serialises on insert so the borrow of input after the call is safe, and the terminal-vs-pipe guard matches the existing pre-push pattern.

Confidence Score: 5/5

Safe to merge — the Rust implementation is correct and the only finding is a P2 test-portability concern in the LFS bats test.

Both Rust changes are correct: Tera serialises on insert so there is no borrow/lifetime issue, the terminal guard is consistent with existing code, and post_rewrite now properly captures stdin for rebase/amend. The single P2 finding (missing lfs.customtransfer.* config in the LFS bats test) affects only test reliability in certain CI environments, not the shipped functionality.

test/hook_stdin.bats — specifically the pre-push hook_stdin works with git-lfs test around the standalonetransferagent configuration.

Important Files Changed

Filename Overview
src/cli/run/pre_push.rs Adds hook_stdin to the Tera template context — empty string when stdin is a terminal, raw stdin content otherwise. Tera serialises on insert so borrowing input after the insert is safe.
src/cli/run/post_rewrite.rs Adds hook_stdin capture using the same is_terminal / read_to_string pattern as pre_push.rs; correctly reads the old/new SHA pairs piped by git on amend and rebase.
test/hook_stdin.bats Three new bats tests covering post-rewrite amend, pre-push ref forwarding, and LFS object transfer; the LFS test uses lfs.standalonetransferagent without the required lfs.customtransfer.* path/args settings, which may not work in all CI environments.

Sequence Diagram

sequenceDiagram
    participant Git
    participant hk as hk pre-push/post-rewrite
    participant Tera as Template Engine
    participant Step as Step Command

    Git->>hk: invoke hook (ref data via stdin)
    hk->>hk: is_terminal()?
    alt stdin is a terminal (no pipe)
        hk->>Tera: insert("hook_stdin", "")
    else stdin is piped
        hk->>hk: read_to_string(&mut input)
        hk->>Tera: insert("hook_stdin", &input)
    end
    hk->>Tera: render step config (stdin = "{{ hook_stdin }}")
    Tera-->>hk: rendered stdin string
    hk->>Step: execute with rendered stdin piped in
    Step-->>hk: exit code
    hk-->>Git: exit code
Loading

Reviews (2): Last reviewed commit: "feat: add {{ hook_stdin }} for pre-push ..." | Re-trigger Greptile

Comment thread src/cli/run/pre_push.rs
Comment on lines +45 to +50
self.hook.tctx.insert("hook_stdin", "");
vec![]
} else {
let mut input = String::new();
std::io::stdin().read_to_string(&mut input)?;
self.hook.tctx.insert("hook_stdin", &input);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 hook_stdin not initialised for non-pre-push hooks

hook_args is always available — it's set to "" in every dedicated handler and in the mod.rs fallback (self.hook.tctx.insert("hook_args", "")). hook_stdin is only inserted here, so using {{ hook_stdin }} in any other hook (e.g. pre-commit) will cause a Tera template-rendering error rather than silently expanding to an empty string. Adding self.hook.tctx.insert("hook_stdin", ""); to mod.rs's fallback branch (and to the other dedicated handlers) would make the variable consistently available everywhere, matching the hook_args pattern.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! After checking the git source code, only 3 hook types actually receive stdin via feed_pipe or path_to_stdin:

Hook Stdin format Source
pre-push <local-ref> <local-sha> <remote-ref> <remote-sha> per ref transport.c:1325
post-rewrite (amend) <old-sha> <new-sha> single line sequencer.c:1313
post-rewrite (rebase) <old-sha> <new-sha> per rewritten commit sequencer.c:5175
reference-transaction <old-oid> <new-oid> <ref-name> per ref refs.c:2614

All other hooks (pre-commit, commit-msg, prepare-commit-msg, post-checkout, post-merge, post-commit) receive no stdin.

Among git-lfs hooks, only pre-push reads stdin.

So rather than initializing hook_stdin to "" everywhere, let's only set it in pre_push.rs and post_rewrite.rs. {{ hook_stdin }} in any other hook will produce a Tera error, which is the correct behavior (the variable genuinely doesn't exist there).

reference-transaction can get a dedicated handler later if needed. I see it's currently not supported yet.

@JohanLorenzo JohanLorenzo marked this pull request as ready for review April 12, 2026 21:17
@jdx jdx merged commit b204624 into jdx:main Apr 16, 2026
18 checks passed
@jdx jdx mentioned this pull request Apr 16, 2026
@JohanLorenzo JohanLorenzo deleted the feature/hook-stdin branch April 16, 2026 18:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants