fix(git): replace git diff --cached with git status --porcelain in get_status
repo.is_dirty() and repo.index.diff("HEAD") internally call git diff --cached,
which fails with exit 129 (unknown option) in some environments.
Introduce _parse_status_porcelain() using git status --porcelain,
a self-contained command that avoids --cached entirely.
All 25 git route tests continue to pass.
This commit is contained in:
@@ -1192,12 +1192,62 @@ class GitService:
|
||||
raise HTTPException(status_code=500, detail=f"Git pull failed: {str(e)}")
|
||||
# [/DEF:pull_changes:Function]
|
||||
|
||||
# [DEF:_parse_status_porcelain:Function]
|
||||
# @COMPLEXITY: 2
|
||||
# @PURPOSE: Parse git status --porcelain output into staged, modified, and untracked file lists.
|
||||
# @PRE: `repo` is an open GitPython Repo instance.
|
||||
# @POST: Returns (staged, modified, untracked) tuple of file path lists.
|
||||
# @RATIONALE: Avoids repo.is_dirty() / repo.index.diff("HEAD") which internally
|
||||
# call git diff --cached, a flag unsupported in some Git environments (exit 129).
|
||||
# Using git status --porcelain is self-contained and avoids the --cached flag entirely.
|
||||
# @RELATION: CALLED_BY -> [GitService.get_status]
|
||||
def _parse_status_porcelain(self, repo) -> tuple[list[str], list[str], list[str]]:
|
||||
with belief_scope("GitService._parse_status_porcelain"):
|
||||
staged: list[str] = []
|
||||
modified: list[str] = []
|
||||
untracked: list[str] = []
|
||||
try:
|
||||
output = repo.git.status("--porcelain")
|
||||
except Exception:
|
||||
logger.warning("[_parse_status_porcelain][Coherence:Failed] git status --porcelain failed")
|
||||
return staged, modified, untracked
|
||||
|
||||
for line in output.split("\n"):
|
||||
# Do NOT strip the line — porcelain v1 format "X Y PATH" uses
|
||||
# leading space (X=' ') to indicate "unmodified in index".
|
||||
# Stripping would shift column alignment.
|
||||
if not line:
|
||||
continue
|
||||
# Untracked: "?? path"
|
||||
if line.startswith("??"):
|
||||
untracked.append(line[2:].strip())
|
||||
continue
|
||||
# Ignored: "!! path" (skip)
|
||||
if line.startswith("!!"):
|
||||
continue
|
||||
# Normal entry: "XY path" or "XY orig -> dest" for renames
|
||||
if len(line) < 3:
|
||||
continue
|
||||
x = line[0] # index (staged) status
|
||||
y = line[1] # work-tree status
|
||||
rest = line[3:] # strip "XY "
|
||||
# For renames/copies: "R orig -> dest" — use the destination
|
||||
path = rest.split(" -> ")[-1].strip() if " -> " in rest else rest.strip()
|
||||
if x != " " and x != "?":
|
||||
staged.append(path)
|
||||
if y != " " and y != "?":
|
||||
if path not in modified:
|
||||
modified.append(path)
|
||||
return staged, modified, untracked
|
||||
# [/DEF:_parse_status_porcelain:Function]
|
||||
|
||||
# [DEF:get_status:Function]
|
||||
# @PURPOSE: Get current repository status (dirty files, untracked, etc.)
|
||||
# @PRE: Repository for dashboard_id exists.
|
||||
# @POST: Returns a dictionary representing the Git status.
|
||||
# @RETURN: dict
|
||||
# @RELATION: CALLS -> [GitService.get_repo]
|
||||
# @RELATION: CALLS -> [GitService._parse_status_porcelain]
|
||||
def get_status(self, dashboard_id: int) -> dict:
|
||||
with belief_scope("GitService.get_status"):
|
||||
repo = self.get_repo(dashboard_id)
|
||||
@@ -1237,10 +1287,11 @@ class GitService:
|
||||
ahead_count = 0
|
||||
behind_count = 0
|
||||
|
||||
is_dirty = repo.is_dirty(untracked_files=True)
|
||||
untracked_files = repo.untracked_files
|
||||
modified_files = [item.a_path for item in repo.index.diff(None)]
|
||||
staged_files = [item.a_path for item in repo.index.diff("HEAD")] if has_commits else []
|
||||
# Use git status --porcelain to gather file state safely.
|
||||
# Avoids repo.is_dirty() and repo.index.diff("HEAD") which internally
|
||||
# call git diff --cached -- a flag unsupported in some Git environments.
|
||||
staged_files, modified_files, untracked_files = self._parse_status_porcelain(repo)
|
||||
is_dirty = bool(staged_files or modified_files or untracked_files)
|
||||
is_diverged = ahead_count > 0 and behind_count > 0
|
||||
|
||||
if is_diverged:
|
||||
|
||||
Reference in New Issue
Block a user