fix(git): tolerate noisy git rev-parse output in is_git_project#1980
fix(git): tolerate noisy git rev-parse output in is_git_project#1980bearomorphism wants to merge 2 commits intocommitizen-tools:masterfrom
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #1980 +/- ##
=======================================
Coverage 98.23% 98.24%
=======================================
Files 61 61
Lines 2779 2790 +11
=======================================
+ Hits 2730 2741 +11
Misses 49 49 ☔ View full report in Codecov by Sentry. |
The previous `is_git_project` implementation compared `git rev-parse --is-inside-work-tree` output to the literal string `"true"` after stripping. Shell wrappers that prepend ANSI colour codes or extra whitespace (reported on git-bash + `uv tool install` on Windows, commitizen-tools#1497) flipped the check to `False` even when git itself considered the directory a valid work tree. Trust the exit code as the primary signal: if `git rev-parse` exits non-zero, we're not in a git context. Then use a loose `endswith("true")` match to distinguish a work tree from a bare-repo interior. Add `logger.debug` lines so users running with `--debug` can see exactly what git returned when the check fails. Closes commitizen-tools#1497 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
ac261a7 to
ee91310
Compare
|
Updated per @bearomorphism's review (thanks for catching the loose match!): Replaced cleaned = _ANSI_ESCAPE.sub("", c.out).strip().lower()
inside_work_tree = cleaned == "true"Now correctly rejects |
There was a problem hiding this comment.
Pull request overview
This PR fixes false NotAGitProjectError reports on Windows/Git Bash by making is_git_project() tolerant of ANSI escape sequences/noisy git rev-parse stdout while preserving strict "true" matching semantics.
Changes:
- Strip ANSI CSI escape sequences from
git rev-parse --is-inside-work-treeoutput and gate on exit code first. - Add
DEBUGlogging to help diagnose unexpectedgit rev-parseoutputs/exit codes undercz --debug. - Add regression tests covering inside/outside repo, ANSI-prefixed output, bare/non-worktree output, and substring-
truerejection.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| commitizen/git.py | Make is_git_project() robust to ANSI/noisy stdout and add debug diagnostics. |
| tests/test_git.py | Add targeted regression tests for is_git_project() behaviors and edge cases. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
* the 'not inside work tree' case covers both .git/ subdirs of normal repos AND the root of bare repos (which have no .git directory at all) * the substring-rejection test verifies strict full-string equality after ANSI/whitespace strip, not whole-word matching Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Description
Closes #1497.
Why
Users running commitizen via
uv tool install commitizeninside Git Bash on Windows 11 encounterNotAGitProjectErroreven though runninggit rev-parse --is-inside-work-treemanually in the same shell returnstrue. Commands that require a git context —cz commit,cz bump,cz changelog— all raise the error, while read-only commands such ascz infoandcz lssucceed because they never callis_git_project().The root cause is the single-line check at
commitizen/git.py:316(master):Some shell wrappers — including Git Bash's colour-prompt infrastructure and certain terminal emulators on Windows — inject ANSI CSI escape sequences (e.g.,
\x1b[0m) into subprocess standard output. When that happens,c.out.strip()yields"\x1b[0mtrue"rather than the bare string"true", the strict equality check fails silently, andis_git_project()returnsFalse, triggering the error even inside a valid work tree.This was surfaced during the #1964 audit: "Depending on subprocess setup with
uv tool installon git-bash for Windows, [the output] may not match exactly." Because the original reporter's exact environment couldn't be reproduced locally, the new--debuglogging path provides the next-best diagnostic: any future reporter can runcz --debug commitand see the exact bytes returned bygit rev-parse.What changed
commitizen/git.py_ANSI_ESCAPEcompiled regex; rewriteis_git_project()to trust the exit code first, strip ANSI codes, then exact-match"true"; addlogger.debugfor both failure pathstests/test_git.pytruerejection (parametrised)How it works
git rev-parsereturns non-zero, we are definitively outside any git context —is_git_project()returnsFalseimmediately, independent of output parsing. This handles the genuine "not a repo" case cleanly and makes the diagnostic log message (rc=128) unambiguous._ANSI_ESCAPE = re.compile(r"\x1b\[[0-?]*[ -/]*[@-~]")(compiled once at import time) covers all CSI sequences — SGR colour codes, cursor commands, and resets. It is applied via.sub("", c.out)before.strip().lower().== "true". An earlier draft used.lower().endswith("true"), which also accepts"untrue"or"nottrue"— stringsgit rev-parsewould never emit, but which a defensive matcher should still reject. The ANSI-strip step is the only looseness introduced; the final comparison remains a full-string equality check.logger.debugdiagnostics: both failure paths (non-zero exit and unexpected textual output) emit aDEBUG-level message that includesrc,out, anderr. Runningcz --debug commitwill now surface the exact bytes returned bygit rev-parse, eliminating guesswork for future environment-specific reports.Backward compatibility
is_git_project()is an internal helper with no public API surface; all callers are withincommitizen/commands/."true\n"), the new code is semantically identical to the original one-liner atcommitizen/git.py:316.is_staging_clean()and every other helper incommitizen/git.pyare untouched.is_git_project/is_staging_cleantest intests/test_git.pycontinue to pass.Checklist
Was generative AI tooling used to co-author this PR?
Generated-by: Claude following the guidelines
Code Changes
uv run poe alllocally to ensure this change passes linter check and testsDocumentation Changes
is_git_project()is an internal helper with no user-facing documentation page. A full docstring explaining the exit-code-first logic and ANSI-strip rationale has been added to the function itself.Expected Behavior
"true\n")is_git_project()returnsTrue— unchangedgit rev-parseexits 128)is_git_project()returnsFalse;--debugshowsrc=128and the fatal error text"\x1b[0mtrue\r\n")is_git_project()returnsTrue— regression fix for #1497.gitof a bare repo (git rev-parseexits 0, output"false\n")is_git_project()returnsFalse"true"as a substring ("untrue","nottrue")is_git_project()returnsFalseSteps to Test This Pull Request
Additional Context
This fix was surfaced during the #1964 issue audit. The triage comment on #1497 identified
commitizen/git.py:314–316(master) as the likely culprit — the strict== "true"equality check fails when a shell wrapper injects ANSI colour codes into subprocess output, a documented behaviour of Git Bash on Windows when colour is enabled globally. Because the exact failure environment couldn't be reproduced locally, the newlogger.debugpath at both non-zero-exit and unexpected-output branches is the primary diagnostic improvement: it closes the feedback loop for any future reporter without requiring a code change.