Title: fix(git): tolerate noisy git rev-parse output in is_git_project by bearomorphism · Pull Request #1980 · commitizen-tools/commitizen · GitHub
Open Graph Title: fix(git): tolerate noisy git rev-parse output in is_git_project by bearomorphism · Pull Request #1980 · commitizen-tools/commitizen
X Title: fix(git): tolerate noisy git rev-parse output in is_git_project by bearomorphism · Pull Request #1980 · commitizen-tools/commitizen
Description: Description Closes #1497. Why Users running commitizen via uv tool install commitizen inside Git Bash on Windows 11 encounter NotAGitProjectError even though running git rev-parse --is-inside-work-tree manually in the same shell returns true. Commands that require a git context — cz commit, cz bump, cz changelog — all raise the error, while read-only commands such as cz info and cz ls succeed because they never call is_git_project(). The root cause is the single-line check at commitizen/git.py:316 (master): return c.out.strip() == "true" 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, and is_git_project() returns False, triggering the error even inside a valid work tree. This was surfaced during the #1964 audit: "Depending on subprocess setup with uv tool install on git-bash for Windows, [the output] may not match exactly." Because the original reporter's exact environment couldn't be reproduced locally, the new --debug logging path provides the next-best diagnostic: any future reporter can run cz --debug commit and see the exact bytes returned by git rev-parse. What changed File Change commitizen/git.py Add _ANSI_ESCAPE compiled regex; rewrite is_git_project() to trust the exit code first, strip ANSI codes, then exact-match "true"; add logger.debug for both failure paths tests/test_git.py Add five targeted regression tests: inside-work-tree, outside-repo, ANSI-prefixed output (the #1497 case), bare-repo interior, and substring-true rejection (parametrised) How it works Exit-code-first gate: if git rev-parse returns non-zero, we are definitively outside any git context — is_git_project() returns False immediately, independent of output parsing. This handles the genuine "not a repo" case cleanly and makes the diagnostic log message (rc=128) unambiguous. ANSI stripping before comparison: the module-level constant _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(). Exact match, not substring (the non-obvious choice): after stripping ANSI codes the cleaned output is compared with == "true". An earlier draft used .lower().endswith("true"), which also accepts "untrue" or "nottrue" — strings git rev-parse would 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.debug diagnostics: both failure paths (non-zero exit and unexpected textual output) emit a DEBUG-level message that includes rc, out, and err. Running cz --debug commit will now surface the exact bytes returned by git 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 within commitizen/commands/. In the standard path (exit code 0, output "true\n"), the new code is semantically identical to the original one-liner at commitizen/git.py:316. is_staging_clean() and every other helper in commitizen/git.py are untouched. All five new tests plus every previously-existing is_git_project / is_staging_clean test in tests/test_git.py continue to pass. Checklist I have read the contributing guidelines Was generative AI tooling used to co-author this PR? Yes (please specify the tool below) Generated-by: Claude following the guidelines Code Changes Add test cases to all the changes you introduce Run uv run poe all locally to ensure this change passes linter check and tests Manually test the changes (see "Steps to Test" below) Update the documentation for the changes Documentation 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 Scenario Outcome Inside a git work tree, clean subprocess output ("true\n") is_git_project() returns True — unchanged Outside any git repo (git rev-parse exits 128) is_git_project() returns False; --debug shows rc=128 and the fatal error text Inside a git work tree, output has ANSI colour prefix (e.g., "\x1b[0mtrue\r\n") is_git_project() returns True — regression fix for #1497 Inside .git of a bare repo (git rev-parse exits 0, output "false\n") is_git_project() returns False Output is a string containing "true" as a substring ("untrue", "nottrue") is_git_project() returns False Steps to Test This Pull Request git fetch fork fix/1497-better-not-a-git-project-diagnostics git checkout fork/fix/1497-better-not-a-git-project-diagnostics # 1. Targeted regression test. uv run pytest tests/test_git.py -k is_git_project -v # 2. Reproduce-the-bug-then-verify-the-fix sequence. # Simulate the ANSI-prefixed output that Git Bash on Windows injects before "true". python - <<'EOF' from unittest.mock import patch from commitizen import cmd, git # #1497 failure mode: shell wrapper prepends an ANSI SGR reset noisy = cmd.Command("\x1b[0mtrue\r\n", "", b"", b"", 0) with patch("commitizen.cmd.run", return_value=noisy): result = git.is_git_project() assert result is True, f"Expected True, got {result!r}" print("OK: ANSI-prefixed 'true' is accepted") # Defensive check: garbage substrings ending in "true" must still be rejected for bad in ("untrue", "nottrue", "test true false"): fake = cmd.Command(f"{bad}\n", "", b"", b"", 0) with patch("commitizen.cmd.run", return_value=fake): result = git.is_git_project() assert result is False, f"Expected False for {bad!r}, got {result!r}" print("OK: substring-'true' strings are all rejected") EOF 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 new logger.debug path 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.
Open Graph Description: Description Closes #1497. Why Users running commitizen via uv tool install commitizen inside Git Bash on Windows 11 encounter NotAGitProjectError even though running git rev-parse --is-inside-work-...
X Description: Description Closes #1497. Why Users running commitizen via uv tool install commitizen inside Git Bash on Windows 11 encounter NotAGitProjectError even though running git rev-parse --is-inside-work-...
Opengraph URL: https://github.com/commitizen-tools/commitizen/pull/1980
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:7010db99-0a0d-10c6-01f3-f9bb628e30d1 |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | ED8C:D3583:1038CEE:16A99B0:6A4DF4D4 |
| html-safe-nonce | 926570488648e3671b4afd618aacb1a30d4c41f20a7dad8d23e3da039f675393 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJFRDhDOkQzNTgzOjEwMzhDRUU6MTZBOTlCMDo2QTRERjRENCIsInZpc2l0b3JfaWQiOiI2OTYzMjkzNzg2MDMwNzA2NzYiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | feb7061d138ef5ce1fa6d6d28d3341f9a1a6d70df9f1a870dff84c92edc060ef |
| hovercard-subject-tag | pull_request:3654780217 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/commitizen-tools/commitizen/pull/1980/files |
| twitter:image | https://avatars.githubusercontent.com/u/26526132?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/26526132?s=400&v=4 |
| og:image:alt | Description Closes #1497. Why Users running commitizen via uv tool install commitizen inside Git Bash on Windows 11 encounter NotAGitProjectError even though running git rev-parse --is-inside-work-... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | 5818716c93c6a2925b815402541a32814e43a7b1261c322b0c2df75224289566 |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/commitizen-tools/commitizen git https://github.com/commitizen-tools/commitizen.git |
| octolytics-dimension-user_id | 62252524 |
| octolytics-dimension-user_login | commitizen-tools |
| octolytics-dimension-repository_id | 106127589 |
| octolytics-dimension-repository_nwo | commitizen-tools/commitizen |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 106127589 |
| octolytics-dimension-repository_network_root_nwo | commitizen-tools/commitizen |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | f4bb89367ca678f057d79b1abc45d6675b1bd5b2 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width