docs(install): recommend global hooks as primary setup path#855
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements global git hook support for hk using Git 2.54+ config-based hooks, adding --global, --legacy, and --from-hook flags to manage these hooks safely across repositories. Documentation and tests were updated to support these features. Review feedback identified several compilation issues due to missing info! macro imports and the use of unstable let_chains syntax, as well as an opportunity to optimize the removal of git configuration sections.
| @@ -1,15 +1,48 @@ | |||
| use crate::{Result, config::Config, env, git_util}; | |||
| use eyre::bail; | |||
| use log::warn; | |||
There was a problem hiding this comment.
The info! macro is used later in this file (line 269) but is not imported. This will cause a compilation error unless the log macros are globally available via a crate-level #[macro_use]. Given that warn is explicitly imported, info should be as well for consistency and correctness.
| use log::warn; | |
| use log::{info, warn}; |
| if let Ok(output) = Command::new("git") | ||
| .args(["config", "--global", "--get", key.as_str()]) | ||
| .output() | ||
| && output.status.success() | ||
| && !output.stdout.is_empty() | ||
| { | ||
| overlapping.push(event); | ||
| } |
There was a problem hiding this comment.
This code uses the let_chains feature (if let ... && ...), which is currently unstable in Rust (see tracking issue #53667). Unless this project specifically requires a nightly toolchain and has the feature enabled, this will cause a compilation error on stable Rust. It is safer to use nested if statements.
| if let Ok(output) = Command::new("git") | |
| .args(["config", "--global", "--get", key.as_str()]) | |
| .output() | |
| && output.status.success() | |
| && !output.stdout.is_empty() | |
| { | |
| overlapping.push(event); | |
| } | |
| if let Ok(output) = Command::new("git") | |
| .args(["config", "--global", "--get", key.as_str()]) | |
| .output() | |
| { | |
| if output.status.success() && !output.stdout.is_empty() { | |
| overlapping.push(event); | |
| } | |
| } |
| @@ -1,33 +1,25 @@ | |||
| use crate::{Result, git_util}; | |||
| use crate::{Result, cli::install}; | |||
| pub(crate) fn remove_config_entries(scope: &str) -> Result<usize> { | ||
| let output = Command::new("git") | ||
| .args([ | ||
| "config", | ||
| scope, | ||
| "--name-only", | ||
| "--get-regexp", | ||
| "^hook\\.hk-", | ||
| ]) | ||
| .output()?; | ||
| // git config --get-regexp: 0 = matches, 1 = no matches, ≥2 = real error | ||
| // (e.g. unreadable config). Don't conflate "nothing to remove" with a | ||
| // failed uninstall. | ||
| let code = output.status.code().unwrap_or(1); | ||
| if code == 1 { | ||
| return Ok(0); | ||
| } | ||
| if !output.status.success() { | ||
| bail!( | ||
| "git config --get-regexp failed (exit {}): {}", | ||
| code, | ||
| String::from_utf8_lossy(&output.stderr).trim() | ||
| ); | ||
| } | ||
| let keys: Vec<String> = String::from_utf8_lossy(&output.stdout) | ||
| .lines() | ||
| .map(|s| s.trim().to_string()) | ||
| .filter(|s| !s.is_empty()) | ||
| .collect(); | ||
| // Dedupe since multi-valued keys appear once per value. | ||
| let mut seen = std::collections::BTreeSet::new(); | ||
| let mut removed = 0; | ||
| for key in keys { | ||
| if seen.insert(key.clone()) { | ||
| run_git(&["config", scope, "--unset-all", key.as_str()])?; | ||
| // Count one per hook event, not one per key (command + event). | ||
| if key.ends_with(".command") { | ||
| removed += 1; | ||
| } | ||
| let hook_file = hooks.join(hook); | ||
| xx::file::write(&hook_file, git_hook_content(&command, hook))?; | ||
| xx::file::make_executable(&hook_file)?; | ||
| println!("Installed hk hook: {}", hook_file.display()); | ||
| } | ||
| Ok(()) | ||
| } | ||
| Ok(removed) | ||
| } |
There was a problem hiding this comment.
The current implementation of remove_config_entries is inefficient as it spawns a new git config --unset-all process for every single key found. For a standard installation with 9 hooks, this results in 18 process spawns. Additionally, unsetting keys individually leaves empty section headers (e.g., [hook "hk-pre-commit"]) in the git config file.
A more efficient and cleaner approach is to identify the unique section names and use git config --remove-section, which removes all keys within the section and the section header itself in a single call per hook.
pub(crate) fn remove_config_entries(scope: &str) -> Result<usize> {
let output = Command::new("git")
.args(["config", scope, "--name-only", "--get-regexp", "^hook\\.hk-"])
.output()?;
// git config --get-regexp: 0 = matches, 1 = no matches, ≥2 = real error
// (e.g. unreadable config). Don't conflate "nothing to remove" with a
// failed uninstall.
let code = output.status.code().unwrap_or(1);
if code == 1 {
return Ok(0);
}
if !output.status.success() {
bail!(
"git config --get-regexp failed (exit {}): {}",
code,
String::from_utf8_lossy(&output.stderr).trim()
);
}
let stdout = String::from_utf8_lossy(&output.stdout);
let mut sections = std::collections::BTreeSet::new();
for key in stdout.lines().filter(|s| !s.trim().is_empty()) {
if let Some(section) = key.rsplitn(2, '.').nth(1) {
sections.insert(section.to_string());
}
}
let removed = sections.len();
for section in sections {
run_git(&["config", scope, "--remove-section", §ion])?;
}
Ok(removed)
}
Greptile SummaryThis PR promotes
Confidence Score: 4/5Safe to merge after addressing the run_git stderr omission; all other changes are docs/copy and well-guarded logic. One P1 remains in run_git: failures from git config writes (the primary write path for the new feature) surface no diagnostic information, making debugging hard for users. All other findings are P2 or lower. src/cli/install.rs — run_git stderr handling Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[hk install] --> B{--global?}
B -- yes --> C{Git >= 2.54?}
C -- no --> D[bail! requires Git 2.54+]
C -- yes --> E[remove_config_entries --global]
E --> F[write_config_hook --global for each DEFAULT_GLOBAL_EVENT]
F --> G[Print success message]
B -- no --> H[use_config_hooks = !legacy && git>=2.54]
H --> I[Config::get — load hk.pkl]
I --> J[remove_local_shims + remove_local_config_entries]
J --> K{events empty?}
K -- yes --> L[warn and return]
K -- no --> M{use_config_hooks?}
M -- yes --> N[install_local_config]
N --> O[warn_if_global_overlap]
M -- no --> P[install_local_shims]
N --> Q[write_config_hook --local per event]
P --> R[write shim file per event in .git/hooks/]
|
Lead with `hk install --global` in getting_started.md and the install CLI reference. The global install is a silent no-op in repos without an `hk.pkl`, so it's safe to enable everywhere and saves users from re-running `hk install` in every clone. Per-repo install is now framed as an alternative for pre-Git-2.54 or selective-repo use. Also renames the pre-existing "Global Configuration" section to "Global `hkrc` Configuration" to disambiguate from global hooks. Regenerates docs/cli from updated clap docstrings. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5305307 to
c47b15a
Compare
### 🐛 Bug Fixes - **(git)** skip untracked scan when HK_STASH_UNTRACKED=false by [@jdx](https://github.com/jdx) in [#861](#861) - **(run)** add post-commit and pre-rebase subcommands by [@jdx](https://github.com/jdx) in [#858](#858) ### 📚 Documentation - **(install)** recommend global hooks as primary setup path by [@jdx](https://github.com/jdx) in [#855](#855) - add cross-site announcement banner by [@jdx](https://github.com/jdx) in [#857](#857) - respect banner expires field by [@jdx](https://github.com/jdx) in [#862](#862) ### 🔍 Other Changes - vendor bats test helpers instead of git submodules by [@jdx](https://github.com/jdx) in [#859](#859) ### 📦️ Dependency Updates - bump communique to 1.0.3 by [@jdx](https://github.com/jdx) in [#863](#863) - update anthropics/claude-code-action digest to e58dfa5 by [@renovate[bot]](https://github.com/renovate[bot]) in [#864](#864) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk release bookkeeping only: version numbers, generated CLI/docs, and lockfile dependency bumps with no runtime logic changes. > > **Overview** > **Release prep for `v1.44.1`.** Updates `Cargo.toml`/`Cargo.lock` (including `libc`) and all version references in generated CLI docs (`docs/cli/*`, `hk.usage.kdl`). > > Refreshes documentation examples to reference the new `v1.44.1` Pkl package URLs and adds the `1.44.1` entry to `CHANGELOG.md`. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit d7637d4. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY --> Co-authored-by: mise-en-dev <123107610+mise-en-dev@users.noreply.github.com>
This MR contains the following updates: | Package | Update | Change | |---|---|---| | [hk](https://github.com/jdx/hk) | minor | `1.43.0` → `1.45.0` | MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot). **Proposed changes to behavior should be submitted there as MRs.** --- ### Release Notes <details> <summary>jdx/hk (hk)</summary> ### [`v1.45.0`](https://github.com/jdx/hk/blob/HEAD/CHANGELOG.md#1450---2026-05-04) [Compare Source](jdx/hk@v1.44.3...v1.45.0) ##### 🚀 Features - **(builtins)** add `buildifier` format and lint built-ins by [@​plx](https://github.com/plx) in [#​896](jdx/hk#896) ##### 🐛 Bug Fixes - **(step)** only auto-batch when rendered command exceeds ARG\_MAX by [@​jdx](https://github.com/jdx) in [#​901](jdx/hk#901) ##### 📚 Documentation - thank Namespace for GitHub Actions runner support by [@​jdx](https://github.com/jdx) in [#​895](jdx/hk#895) ##### 🔍 Other Changes - **(ci)** use !cancelled() instead of always() for final job by [@​jdx](https://github.com/jdx) in [#​906](jdx/hk#906) - **(docs)** remove shrill.en.dev analytics script by [@​jdx](https://github.com/jdx) in [#​903](jdx/hk#903) - remove rust-cache from release jobs by [@​jdx](https://github.com/jdx) in [#​893](jdx/hk#893) - invert CLAUDE.md/AGENTS.md so AGENTS.md is canonical by [@​jdx](https://github.com/jdx) in [#​905](jdx/hk#905) - set dev profile debug to 1 by [@​jdx](https://github.com/jdx) in [#​907](jdx/hk#907) ##### 📦️ Dependency Updates - update anthropics/claude-code-action digest to [`fefa07e`](jdx/hk@fefa07e) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​897](jdx/hk#897) - update jdx/mise-action digest to [`1648a78`](jdx/hk@1648a78) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​898](jdx/hk#898) - update apple-actions/import-codesign-certs action to v7 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​900](jdx/hk#900) - update autofix-ci/action action to v1.3.4 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​899](jdx/hk#899) - lock file maintenance by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​908](jdx/hk#908) ##### New Contributors - [@​plx](https://github.com/plx) made their first contribution in [#​896](jdx/hk#896) ### [`v1.44.3`](https://github.com/jdx/hk/blob/HEAD/CHANGELOG.md#1443---2026-04-30) [Compare Source](jdx/hk@v1.44.2...v1.44.3) ##### 🐛 Bug Fixes - **(hook)** do not stage fixes when fail\_on\_fix=true by [@​jdx](https://github.com/jdx) in [#​892](jdx/hk#892) - use site domain for plausible data-domain by [@​jdx](https://github.com/jdx) in [#​886](jdx/hk#886) - make text-mode progress output usable in CI by [@​jdx](https://github.com/jdx) in [#​890](jdx/hk#890) ##### 📚 Documentation - prefix GitHub star count with ★ glyph by [@​jdx](https://github.com/jdx) in [#​883](jdx/hk#883) ##### 🔍 Other Changes - **(release)** dedupe sponsor section in release notes by [@​jdx](https://github.com/jdx) in [#​881](jdx/hk#881) - switch analytics from gtm/goatcounter to plausible by [@​jdx](https://github.com/jdx) in [#​885](jdx/hk#885) - migrate to namespace.so runners by [@​jdx](https://github.com/jdx) in [#​891](jdx/hk#891) ### [`v1.44.2`](https://github.com/jdx/hk/blob/HEAD/CHANGELOG.md#1442---2026-04-26) [Compare Source](jdx/hk@v1.44.1...v1.44.2) ##### 🐛 Bug Fixes - **(builtins)** silence pklr deprecation warnings on Builtins.pkl load by [@​jdx](https://github.com/jdx) in [#​880](jdx/hk#880) - **(ci)** serialize docs lint step by [@​jdx](https://github.com/jdx) in [#​874](jdx/hk#874) - **(config)** include main pkl path in cache fresh files by [@​jdx](https://github.com/jdx) in [#​879](jdx/hk#879) - **(docs)** stack banner message and link on mobile by [@​jdx](https://github.com/jdx) in [#​865](jdx/hk#865) - **(docs)** pin banner close button to top-right corner on mobile by [@​jdx](https://github.com/jdx) in [#​867](jdx/hk#867) ##### 📚 Documentation - **(site)** show release version and github stars by [@​jdx](https://github.com/jdx) in [#​872](jdx/hk#872) ##### 🔍 Other Changes - add pr-closer workflow by [@​jdx](https://github.com/jdx) in [#​876](jdx/hk#876) ##### 📦️ Dependency Updates - bump communique 1.0.3 → 1.0.4 by [@​jdx](https://github.com/jdx) in [#​868](jdx/hk#868) - update anthropics/claude-code-action digest to [`2da6cfa`](jdx/hk@2da6cfa) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​869](jdx/hk#869) - update anthropics/claude-code-action digest to [`567fe95`](jdx/hk@567fe95) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​870](jdx/hk#870) - bump communique to 1.1.2 by [@​jdx](https://github.com/jdx) in [#​875](jdx/hk#875) ### [`v1.44.1`](https://github.com/jdx/hk/blob/HEAD/CHANGELOG.md#1441---2026-04-24) [Compare Source](jdx/hk@v1.44.0...v1.44.1) ##### 🐛 Bug Fixes - **(git)** skip untracked scan when HK\_STASH\_UNTRACKED=false by [@​jdx](https://github.com/jdx) in [#​861](jdx/hk#861) - **(run)** add post-commit and pre-rebase subcommands by [@​jdx](https://github.com/jdx) in [#​858](jdx/hk#858) ##### 📚 Documentation - **(install)** recommend global hooks as primary setup path by [@​jdx](https://github.com/jdx) in [#​855](jdx/hk#855) - add cross-site announcement banner by [@​jdx](https://github.com/jdx) in [#​857](jdx/hk#857) - respect banner expires field by [@​jdx](https://github.com/jdx) in [#​862](jdx/hk#862) ##### 🔍 Other Changes - vendor bats test helpers instead of git submodules by [@​jdx](https://github.com/jdx) in [#​859](jdx/hk#859) ##### 📦️ Dependency Updates - bump communique to 1.0.3 by [@​jdx](https://github.com/jdx) in [#​863](jdx/hk#863) - update anthropics/claude-code-action digest to [`e58dfa5`](jdx/hk@e58dfa5) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​864](jdx/hk#864) ### [`v1.44.0`](https://github.com/jdx/hk/blob/HEAD/CHANGELOG.md#1440---2026-04-23) [Compare Source](jdx/hk@v1.43.0...v1.44.0) ##### 🚀 Features - **(check)** implement --plan, --why, and --json by [@​jdx](https://github.com/jdx) in [#​848](jdx/hk#848) - **(cocogitto)** add cocogitto conventional commits config to hk builtin config by [@​hituzi-no-sippo](https://github.com/hituzi-no-sippo) in [#​838](jdx/hk#838) - **(git)** support GIT\_DIR/GIT\_WORK\_TREE for bare-repo dotfile managers by [@​jdx](https://github.com/jdx) in [#​847](jdx/hk#847) - **(install)** use Git 2.54 config-based hooks with --global support by [@​jdx](https://github.com/jdx) in [#​853](jdx/hk#853) ##### 🐛 Bug Fixes - use text progress in CI by [@​jdx](https://github.com/jdx) in [#​845](jdx/hk#845) ##### 📚 Documentation - generalize agent guidelines by [@​jdx](https://github.com/jdx) in [#​846](jdx/hk#846) - add releases nav and aube lock by [@​jdx](https://github.com/jdx) in [#​849](jdx/hk#849) ##### 🔍 Other Changes - **(release)** append en.dev sponsor blurb to release notes by [@​jdx](https://github.com/jdx) in [#​854](jdx/hk#854) - bump communique to 1.0.1 by [@​jdx](https://github.com/jdx) in [#​850](jdx/hk#850) ##### 📦️ Dependency Updates - update actions-rust-lang/setup-rust-toolchain digest to [`2b1f5e9`](jdx/hk@2b1f5e9) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​832](jdx/hk#832) - update anthropics/claude-code-action digest to [`c3d45e8`](jdx/hk@c3d45e8) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​833](jdx/hk#833) - update rust crate tokio to v1.52.1 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​834](jdx/hk#834) - update actions/upload-pages-artifact action to v5 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​835](jdx/hk#835) - update taiki-e/upload-rust-binary-action digest to [`f0d45ae`](jdx/hk@f0d45ae) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​839](jdx/hk#839) - update rust crate clx to v2 by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​836](jdx/hk#836) - update anthropics/claude-code-action digest to [`0d2971c`](jdx/hk@0d2971c) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​841](jdx/hk#841) - update anthropics/claude-code-action digest to [`38ec876`](jdx/hk@38ec876) by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​842](jdx/hk#842) - lock file maintenance by [@​renovate\[bot\]](https://github.com/renovate\[bot]) in [#​851](jdx/hk#851) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - At any time (no schedule defined) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this MR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box --- This MR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xNjguNSIsInVwZGF0ZWRJblZlciI6IjQzLjE2OC41IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiLCJhdXRvbWF0aW9uOmJvdC1hdXRob3JlZCIsImRlcGVuZGVuY3ktdHlwZTo6bWlub3IiXX0=-->
Summary
Leads the install docs with
hk install --globalas the recommended setup path. The global install is a silent no-op in repos without anhk.pkl, so it's safe to enable once per machine and avoids re-runninghk installin every clone.Follow-up to #853 (the config-based hooks +
--globalfeature that this documents).What changed
hk installis now framed as an alternative for pre-Git-2.54 or selective-repo use. The overlap warning (Git aggregateshook.<name>.commandacross scopes) and the manual~/.gitconfigsetup are retained as subsections.Installstruct docstring and--globalflag help sohk install --helpleads with the recommendation.hk.usage.kdl,docs/cli/commands.json— regenerated viahk usage→usage g markdown."version": "0.0.0"+"private": trueso the bun workspace resolves cleanly (the pre-pushdocs:buildhook was failing on missing version).Why
New users land on getting_started.md; leading with per-repo
hk installmade them re-install in every clone and gave no signal that the global option existed. The--from-hookshort-circuit (also from #853) makes global genuinely safe to enable everywhere, so it deserves the headline.Test plan
hk check --allpasses.🤖 Generated with Claude Code