Skip to content

Commit ac261a7

Browse files
fix(git): tolerate noisy git rev-parse output in is_git_project
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, #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 #1497 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 4d99415 commit ac261a7

2 files changed

Lines changed: 58 additions & 1 deletion

File tree

commitizen/git.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
from enum import Enum
55
from functools import lru_cache
6+
from logging import getLogger
67
from pathlib import Path
78
from tempfile import NamedTemporaryFile
89
from typing import TYPE_CHECKING
@@ -14,6 +15,9 @@
1415
from collections.abc import Sequence
1516

1617

18+
logger = getLogger("commitizen")
19+
20+
1721
class EOLType(Enum):
1822
"""The EOL type from `git config core.eol`."""
1923

@@ -312,8 +316,30 @@ def is_staging_clean() -> bool:
312316

313317

314318
def is_git_project() -> bool:
319+
"""Check whether we're inside a git work tree.
320+
321+
Trusts ``git rev-parse``'s exit code as the primary signal: if the command
322+
exits non-zero, we're not in a git context. The textual output (``true`` /
323+
``false``) is then used to distinguish a work tree from a bare-repo
324+
interior. A loose ``endswith("true")`` accepts shell wrappers that prepend
325+
ANSI colour codes or trailing whitespace (#1497).
326+
"""
315327
c = cmd.run(["git", "rev-parse", "--is-inside-work-tree"])
316-
return c.out.strip() == "true"
328+
if c.return_code != 0:
329+
logger.debug(
330+
"is_git_project: git rev-parse failed (rc=%d) out=%r err=%r",
331+
c.return_code,
332+
c.out,
333+
c.err,
334+
)
335+
return False
336+
inside_work_tree = c.out.strip().lower().endswith("true")
337+
if not inside_work_tree:
338+
logger.debug(
339+
"is_git_project: git rev-parse said not a work tree: out=%r",
340+
c.out,
341+
)
342+
return inside_work_tree
317343

318344

319345
def get_core_editor() -> str | None:

tests/test_git.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,37 @@ def test_is_staging_clean_when_updating_file():
309309
assert git.is_staging_clean() is False
310310

311311

312+
@pytest.mark.usefixtures("tmp_commitizen_project")
313+
def test_is_git_project_inside_work_tree():
314+
assert git.is_git_project() is True
315+
316+
317+
def test_is_git_project_outside_repo(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
318+
"""When ``git rev-parse`` exits non-zero (no repo above us),
319+
``is_git_project`` returns ``False``."""
320+
monkeypatch.chdir(tmp_path)
321+
assert git.is_git_project() is False
322+
323+
324+
def test_is_git_project_accepts_loose_true_output(mocker: MockFixture):
325+
"""Regression test for #1497: shell wrappers that prepend ANSI colour
326+
codes or trailing whitespace to ``git rev-parse``'s output must not flip
327+
``is_git_project`` to ``False``. The check now trusts the exit code and
328+
a loose ``endswith('true')`` substring match."""
329+
fake = cmd.Command("\x1b[0mtrue\r\n", "", b"", b"", 0)
330+
mocker.patch("commitizen.cmd.run", return_value=fake)
331+
assert git.is_git_project() is True
332+
333+
334+
def test_is_git_project_returns_false_for_bare_repo(mocker: MockFixture):
335+
"""``git rev-parse`` exits 0 but says ``false`` when run from inside the
336+
``.git`` directory of a bare repo. ``is_git_project`` is meant to gate
337+
work-tree commands, so this case should still return ``False``."""
338+
fake = cmd.Command("false\n", "", b"", b"", 0)
339+
mocker.patch("commitizen.cmd.run", return_value=fake)
340+
assert git.is_git_project() is False
341+
342+
312343
@pytest.mark.usefixtures("tmp_commitizen_project")
313344
def test_get_eol_for_open():
314345
assert git.EOLType.for_open() == os.linesep

0 commit comments

Comments
 (0)