fix(@ml/auto-commit-service): 🐛 handle staged-only unstage on empty commit
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
2280d37bbc
commit
a9fa13d242
1 changed files with 25 additions and 10 deletions
|
|
@ -251,15 +251,24 @@ class LocalCommitAgent:
|
|||
|
||||
# Extract dirty paths from porcelain output and apply secret prefilter.
|
||||
# Status format: "XY path" where XY is 2-char status, followed by the path.
|
||||
# Track which paths have a worktree-side change (Y != ' ') or are untracked:
|
||||
# only those need `git add`. Index-only entries (e.g. a staged deletion
|
||||
# "D path") exist in neither worktree nor index, so adding them fatals
|
||||
# with "pathspec did not match any files" and kills the whole repo cycle.
|
||||
dirty_paths: list[str] = []
|
||||
needs_staging: set[str] = set()
|
||||
for line in status.splitlines():
|
||||
if not line.strip():
|
||||
continue
|
||||
xy = line[:2]
|
||||
# Skip the 3-char prefix "XY ". Handle renames ("R old -> new") by taking new.
|
||||
entry = line[3:]
|
||||
if " -> " in entry:
|
||||
entry = entry.split(" -> ", 1)[1]
|
||||
dirty_paths.append(entry.strip().strip('"'))
|
||||
path = entry.strip().strip('"')
|
||||
dirty_paths.append(path)
|
||||
if xy == "??" or xy[1] != " ":
|
||||
needs_staging.add(path)
|
||||
|
||||
allowed, denied = filter_dirty_paths(dirty_paths)
|
||||
if denied:
|
||||
|
|
@ -272,9 +281,11 @@ class LocalCommitAgent:
|
|||
logger.debug(f"{_repo_display_name(repo_path)}: all dirty files on denylist, skipping")
|
||||
return False
|
||||
|
||||
# Stage only the allowed files (never blanket `git add -A` — that would
|
||||
# stage denied secret paths too).
|
||||
_git(repo_path, "add", "--", *allowed)
|
||||
# Stage only the allowed files that aren't staged yet (never blanket
|
||||
# `git add -A` — that would stage denied secret paths too).
|
||||
to_stage = [p for p in allowed if p in needs_staging]
|
||||
if to_stage:
|
||||
_git(repo_path, "add", "--", *to_stage)
|
||||
|
||||
# Get the diff of staged changes. Size cap comes from self.max_diff_bytes;
|
||||
# the 6000-byte stage-time cap is a sub-cap before the prefilter-level cap.
|
||||
|
|
@ -311,9 +322,11 @@ class LocalCommitAgent:
|
|||
|
||||
if not message:
|
||||
logger.warning(f"Empty message for {repo_name}, skipping")
|
||||
# Unstage path-by-path — safe on unborn branches (no initial commit)
|
||||
# where `git reset HEAD` fails because HEAD is unresolved.
|
||||
_git(repo_path, "reset", "--", *allowed)
|
||||
# Unstage only what WE staged, path-by-path — safe on unborn branches
|
||||
# (no initial commit) where `git reset HEAD` fails because HEAD is
|
||||
# unresolved. Paths staged before this cycle stay staged.
|
||||
if to_stage:
|
||||
_git(repo_path, "reset", "--", *to_stage)
|
||||
return False
|
||||
|
||||
# Dry-run gate — log intent, unstage, do not commit or push
|
||||
|
|
@ -322,9 +335,11 @@ class LocalCommitAgent:
|
|||
f"[DRY-RUN] {repo_name} ({branch}) would commit {len(allowed)} file(s) "
|
||||
f"with message: {message.splitlines()[0] if message else '(empty)'}"
|
||||
)
|
||||
# Unstage path-by-path — safe on unborn branches (no initial commit)
|
||||
# where `git reset HEAD` fails because HEAD is unresolved.
|
||||
_git(repo_path, "reset", "--", *allowed)
|
||||
# Unstage only what WE staged, path-by-path — safe on unborn branches
|
||||
# (no initial commit) where `git reset HEAD` fails because HEAD is
|
||||
# unresolved. Paths staged before this cycle stay staged.
|
||||
if to_stage:
|
||||
_git(repo_path, "reset", "--", *to_stage)
|
||||
return False
|
||||
|
||||
# Commit
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue