feat(git): support GIT_DIR/GIT_WORK_TREE for bare-repo dotfile managers#847
feat(git): support GIT_DIR/GIT_WORK_TREE for bare-repo dotfile managers#847
Conversation
Honor the standard git environment variables during repository discovery so hk works with YADM and other bare-repo dotfile managers where there is no `.git` in the work tree. - `Git::new()` uses `Repository::open_from_env()` when either var is set, and falls back to the shell-git path when the opened repo is bare (libgit2 refuses status/diff on bare repos even when a work tree is provided via `GIT_WORK_TREE`). - `find_git_path()` returns `$GIT_DIR` directly when set so hooks resolve to the bare repository's hooks dir. - `tera.rs` and `test_runner.rs` use a new `find_work_tree_root()` helper that honors `GIT_WORK_TREE`. - `hk builtins` no longer loads project settings, so it works outside a repo instead of panicking. Fixes #831 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Regression test for the GIT_DIR/GIT_WORK_TREE support, exercising both the libgit2 and shell-git code paths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request introduces support for GIT_DIR and GIT_WORK_TREE environment variables, enabling compatibility with bare-repository managers like YADM. It centralizes work-tree resolution logic in git_util and updates the Git initialization to handle bare repositories by falling back to shell-based Git when libgit2 is insufficient. Feedback was provided regarding the handling of current directory changes when Git environment variables are present, specifically the need to absolute-ify relative paths and ensure the process remains at the repository root to avoid breaking internal operations. Additionally, a suggestion was made to improve error handling when retrieving the current working directory to avoid issues with empty paths.
| if !has_git_env { | ||
| let cwd = std::env::current_dir()?; | ||
| let root = xx::file::find_up(&cwd, &[".git"]) | ||
| .and_then(|p| p.parent().map(|p| p.to_path_buf())) | ||
| .ok_or(eyre!("failed to find git repository"))?; | ||
| std::env::set_current_dir(&root)?; | ||
| } |
There was a problem hiding this comment.
By skipping set_current_dir when GIT_DIR or GIT_WORK_TREE is set, hk remains in the user's current working directory. This breaks many internal operations (like status or all_files) that expect the process to be at the repository root to correctly resolve relative paths returned by Git.
Additionally, if we do change the directory, any relative paths in GIT_DIR or GIT_WORK_TREE must be converted to absolute paths first, otherwise they will point to the wrong location after the cd.
let root = crate::git_util::find_work_tree_root();
if !has_git_env {
let cwd = std::env::current_dir()?;
if xx::file::find_up(&cwd, &[".git"]).is_none() {
return Err(eyre!("failed to find git repository"));
}
}
// Absolute-ify relative git env vars before changing directory
for var in ["GIT_DIR", "GIT_WORK_TREE"] {
if let Some(val) = std::env::var_os(var) {
let p = std::path::Path::new(&val);
if p.is_relative() {
std::env::set_var(var, std::env::current_dir()?.join(p));
}
}
}
std::env::set_current_dir(&root)?;| /// (for bare-repo setups like YADM). Falls back to walking up for `.git`, and | ||
| /// finally to `cwd` if no repository is found. | ||
| pub fn find_work_tree_root() -> PathBuf { | ||
| let cwd = std::env::current_dir().unwrap_or_default(); |
There was a problem hiding this comment.
Using unwrap_or_default() on current_dir() returns an empty PathBuf if the call fails. This can lead to confusing behavior or incorrect path joining later. It's safer to handle the error or at least fall back to . explicitly.
| let cwd = std::env::current_dir().unwrap_or_default(); | |
| let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); |
Greptile SummaryThis PR adds support for bare-repo dotfile managers (YADM, etc.) by honoring Confidence Score: 5/5Safe to merge; the one remaining concern is a narrow theoretical race with set_var in a live tokio runtime, which has very low practical impact. All changes are well-scoped: the previously flagged CWD issue is addressed and regression-tested, the bare-repo fallback logic is clean, and the builtins settings-skip is a safe no-op for existing users. The only outstanding finding is a P2 note about set_var soundness in an async context — not a production blocker. src/git.rs lines 119–129 (unsafe set_var in live tokio runtime) Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Git::new] --> B{GIT_DIR or\nGIT_WORK_TREE set?}
B -- yes --> C[Absolutize relative\nGIT_DIR / GIT_WORK_TREE\n/ GIT_INDEX_FILE via set_var]
C --> D[root = find_work_tree_root\nhonors GIT_WORK_TREE]
B -- no --> E[root = walk up for .git\nparent dir]
D --> F[set_current_dir to root]
E --> F
F --> G{HK_LIBGIT2?}
G -- yes --> H{has_git_env?}
H -- yes --> I[Repository::open_from_env]
H -- no --> J[Repository::open dot]
I --> K{repo.is_bare?}
J --> K
K -- yes --> L[repo = None\nshell-git fallback]
K -- no --> M[Apply GIT_INDEX_FILE\nif set, repo = Some]
G -- no --> L
L --> N[Git struct created]
M --> N
Reviews (2): Last reviewed commit: "fix(git): always cd to work tree root wh..." | Re-trigger Greptile |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 4d3c390. Configure here.
Addresses code-review feedback on #847. Skipping set_current_dir in the env-var branch left cwd pointing at a subdirectory of the work tree, which broke path.exists() filtering in status() — modified files silently disappeared from hk's file list when running from a subdir. Always cd to the work tree root after absolutizing relative GIT_DIR / GIT_WORK_TREE / GIT_INDEX_FILE values (so libgit2 and shell-git resolve them correctly after the cwd change). Adds a regression test that modifies a file at the work-tree root, runs hk check from a subdirectory, and asserts the file is processed. The test fails on both HK_LIBGIT2=1 and HK_LIBGIT2=0 without this fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
### 🚀 Features - **(check)** implement --plan, --why, and --json by [@jdx](https://github.com/jdx) in [#848](#848) - **(cocogitto)** add cocogitto conventional commits config to hk builtin config by [@hituzi-no-sippo](https://github.com/hituzi-no-sippo) in [#838](#838) - **(git)** support GIT_DIR/GIT_WORK_TREE for bare-repo dotfile managers by [@jdx](https://github.com/jdx) in [#847](#847) - **(install)** use Git 2.54 config-based hooks with --global support by [@jdx](https://github.com/jdx) in [#853](#853) ### 🐛 Bug Fixes - use text progress in CI by [@jdx](https://github.com/jdx) in [#845](#845) ### 📚 Documentation - generalize agent guidelines by [@jdx](https://github.com/jdx) in [#846](#846) - add releases nav and aube lock by [@jdx](https://github.com/jdx) in [#849](#849) ### 🔍 Other Changes - bump communique to 1.0.1 by [@jdx](https://github.com/jdx) in [#850](#850) ### 📦️ Dependency Updates - update actions-rust-lang/setup-rust-toolchain digest to 2b1f5e9 by [@renovate[bot]](https://github.com/renovate[bot]) in [#832](#832) - update anthropics/claude-code-action digest to c3d45e8 by [@renovate[bot]](https://github.com/renovate[bot]) in [#833](#833) - update rust crate tokio to v1.52.1 by [@renovate[bot]](https://github.com/renovate[bot]) in [#834](#834) - update actions/upload-pages-artifact action to v5 by [@renovate[bot]](https://github.com/renovate[bot]) in [#835](#835) - update taiki-e/upload-rust-binary-action digest to f0d45ae by [@renovate[bot]](https://github.com/renovate[bot]) in [#839](#839) - update rust crate clx to v2 by [@renovate[bot]](https://github.com/renovate[bot]) in [#836](#836) - update anthropics/claude-code-action digest to 0d2971c by [@renovate[bot]](https://github.com/renovate[bot]) in [#841](#841) - update anthropics/claude-code-action digest to 38ec876 by [@renovate[bot]](https://github.com/renovate[bot]) in [#842](#842) - lock file maintenance by [@renovate[bot]](https://github.com/renovate[bot]) in [#851](#851) <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Low Risk** > Low risk release bookkeeping: version bumps and doc/CLI artifact updates, plus minor dependency patch updates in `Cargo.lock`. No functional Rust source changes are included in this diff. > > **Overview** > Bumps `hk` to **v1.44.0** and publishes the corresponding release notes in `CHANGELOG.md`. > > Updates generated/packaged artifacts to match the new version (CLI docs/specs and Pkl package URLs in docs/examples), and refreshes `Cargo.lock` for the release (including patch-level updates like `rustls` and `winnow`). > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit a36c7a6. 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
GIT_DIR/GIT_WORK_TREEenv vars during repository discovery so hk works with YADM and other bare-repo dotfile managers where there is no.gitin the work treeGIT_WORK_TREEoverride)hk builtinsno longer loads project settings, so it works outside a repo instead of panickingFixes #831.
Changes
src/git.rs—Git::new()usesRepository::open_from_env()when either env var is set; bare repos fall back to shell gitsrc/git_util.rs—find_git_path()returns$GIT_DIRwhen set; newfind_work_tree_root()helpersrc/tera.rs,src/test_runner.rs— usefind_work_tree_root()instead of walking up for.gitsrc/cli/mod.rs— addCommands::Builtinsto the settings-skip listTest plan
hk check,hk fix,hk install,hk uninstall,hk builtinsall work against a bare repo +GIT_WORK_TREEsetupHK_LIBGIT2=1(default) andHK_LIBGIT2=0🤖 Generated with Claude Code
Note
Medium Risk
Moderate risk because it changes repository discovery and working-directory behavior in
Git::new(), which can affect path resolution and git operations across many commands; mitigated by explicit fallbacks and new regression tests.Overview
Enables
hkto operate in bare-repo dotfile-manager setups (e.g., YADM) by honoringGIT_DIR/GIT_WORK_TREE, normalizing relative git env vars, and alwayscding to the effective work-tree root duringGit::new().When libgit2 opens a bare repo, git status/diff now falls back to shell
gitto keep operations working.find_git_path()and a newfind_work_tree_root()helper are added/used to resolve roots consistently, andhk builtinsnow skips project settings loading so it runs outside a repo. Addsbatsregression tests covering check/install/uninstall and subdirectory execution under bare-repo env vars.Reviewed by Cursor Bugbot for commit 3799316. Bugbot is set up for automated code reviews on this repo. Configure here.