feat(commit): run pre-commit and post-commit git hooks#198
Conversation
Codecov Report❌ Patch coverage is
... and 1 file with indirect coverage changes 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR updates Koji’s commit flow to more closely match git commit by executing pre-commit and post-commit hooks around the commit operation, with a new --no-verify flag to bypass them.
Changes:
- Run
pre-commitbefore committing and abort on failure; runpost-commitafter a successful commit. - Add
--no-verifyCLI flag to skip both hooks. - Add integration tests for
pre-commitbehavior and update README documentation; adjust dependencies (including promotinggit2).
Reviewed changes
Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/integration.rs | Adds integration coverage for pre-commit and --no-verify behavior. |
| src/lib/commit.rs | Implements pre/post hook execution and pre-staging logic prior to pre-commit. |
| src/bin/main.rs | Adds --no-verify flag and threads it into the commit call. |
| README.md | Documents git hook behavior and --no-verify. |
| Cargo.toml | Promotes git2 and updates dependency versions (notably cocogitto/gix). |
| Cargo.lock | Lockfile updates from dependency changes. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
cococonscious
left a comment
There was a problem hiding this comment.
Hi, thanks for the PR. There's a few changes I'd like to make.
One concern I have is regarding the differing staging behavior with and without having hooks enabled. With --all + hooks enabled, koji only stages tracked files while with --all + --no-verify, cocogitto stages both tracked and untracked files. Please find a solution for this, perhaps always staging with koji and not ever staging with cocogitto, but you're free to find the best solution.
There's also that post-commit hook failures are reported as an error, even though by default git commit ignores post-commit hook failures, so we should probably follow that. The thing is that cocogitto also propagates the errors, wrongfully so...
|
|
||
| [dependencies] | ||
| anyhow = "1.0" | ||
| clap = { version = "4.5", features = ["derive"] } |
There was a problem hiding this comment.
Please keep the minor versions of the dependencies and the patch versions of the dev-dependencies.
| config = { version = "0.15", features = ["toml"] } | ||
| xdg = "3.0" | ||
| gix = "0.80.0" | ||
| git2 = "0.20" |
There was a problem hiding this comment.
I'd prefer to not include git2 again, would it be possible to implement this with purely gix?
There was a problem hiding this comment.
@veeso It definitely is! Gix is still maturing, but they have all of the relevant pieces of late. It would be the simplest for Koji if we got the plumbing for this upstreamed, but short-term we could host it inline in the repository until the higher-level APIs mature. From what I found:
- gix::discover() for the repository discovering
- Statting modifications: repo.status()
- For writing new blobs: repo.write_blob
- The hardest part seems to be the loop for mutating index entries: You'll need to edit state.entries' id and stat fields manually in a manual loop. (I didn't research this deeply enough :()
- Index writing with index.write
Sorry for the difficulty! At least if we get it into mainline Gix then other Rust projects can use it too :)
There was a problem hiding this comment.
Would you consider upstreaming at gitoxide? Not to hold up merging now, but for future simplifications.
| all: bool, | ||
|
|
||
| #[arg(long, help = "Bypass the pre-commit and post-commit git hooks")] | ||
| no_verify: bool, |
There was a problem hiding this comment.
You should add conflicts_with = "hook" as no_verify has no effect in hook mode.
Wraps the cocogitto commit with `pre-commit` and `post-commit` git hooks, matching `git commit` behavior. A failing `pre-commit` hook aborts the commit; a failing `post-commit` hook is reported as a warning and does not abort, matching `git commit`. Staging is always performed by koji (via gix) regardless of whether hooks are enabled, so `--all` and `--all --no-verify` produce the same staged set: tracked modified/deleted files only, matching the documented `--all` flag and `git commit -a` semantics. Cocogitto's own staging is bypassed. Adds `--no-verify` to skip both hooks (conflicts with `--hook`).
`stage_tracked` updates index entries' blob ids in-place but left two gix-index pieces stale, which manifested only when cocogitto (libgit2) wrote the commit afterwards: - The TREE extension still pointed at the pre-edit root tree, so libgit2's `index.write_tree()` returned the cached old tree id — producing a commit whose tree equals its parent's (empty diff). - The entry `stat` (mtime/size/ino) was not refreshed from disk, so `git status` reported the file as modified after commit even though index, HEAD and worktree blobs all matched. Drop the TREE extension and rewrite `entry.stat` from the worktree file's metadata before writing the index.
Stage untracked files (matching documented `git add -A` semantics) and gracefully open `.git/index` via `at_or_default` so `koji -a` works in a fresh repo before any commit exists.
Summary
Koji currently bypasses git's
pre-commitandpost-commithooks because the underlying cocogitto API only invokes thecommit-msghook. This PR runs both hooks around the commit so koji behaves likegit commit, and adds a--no-verifyflag to bypass them.Changes
pre-commitruns before the commit. If it exits non-zero, the commit is aborted and koji exits non-zero.post-commitruns after a successful commit. A failingpost-commithook is reported as a warning on stderr and does not abort, matchinggit commit.--allis set, files are staged beforepre-commitruns so the hook sees the same index that will be committed (matchesgit commit -asemantics). Staging is performed by koji directly viagix(customstage_trackedhelper), not delegated to cocogitto, so the same set of files is staged regardless of whether hooks run. Only tracked modified/deleted files are staged, matching koji's documented--allflag.--no-verifyflag to skip both hooks. Conflicts with--hook(the git-hook mode already bypasses koji's own hook orchestration).commit-msgcontinues to be handled by cocogitto.prepare-commit-msgis intentionally skipped because koji generates the message itself.Dependency changes
The new hook runner (
CommitHook::PreCommit/CommitHook::PostCommit) is exposed by cocogitto 7, so this PR also bumps several core dependencies:cocogitto6.5 → 7 (major bump — required for the hook API;CommitOptionsis re-exported by koji's lib, so this is a semver-relevant change for downstream users of the crate).gix0.80 → 0.83 (transitive alignment with cocogitto 7).The signature of
koji::commit::commitgains ano_verify: boolparameter. This is a breaking change to the public lib API, but the cocogitto 7 upgrade is already a breaking change at the same surface (re-exportedCommitOptions), so the new parameter ships alongside it.Test plan
Seven integration tests cover the new behavior:
test_pre_commit_hook_runs: apre-commithook that touches a sentinel file fires during the koji commit flow.test_pre_commit_hook_failure_aborts: a failingpre-commithook causes koji to exit non-zero and no new commit is created.test_no_verify_skips_pre_commit_hook:--no-verifybypasses a failingpre-commithook and the commit succeeds.test_post_commit_hook_runs: apost-commithook fires after a successful koji commit.test_post_commit_hook_failure_does_not_abort: a failingpost-commithook is reported as a warning but the commit still lands and koji exits zero.test_no_verify_skips_post_commit_hook:--no-verifyskips thepost-commithook.test_all_stages_tracked_only:--allstages tracked modified/deleted files but leaves untracked files alone, matchinggit commit -a.All existing tests still pass (
cargo testandcargo clippy --all-targets -- -D warnings).Docs
README has a new "Git hooks" subsection under "Usage" documenting the default behavior, the
--no-verifyflag, the post-commit warning behavior, and the--allordering note.