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.
|
# Extract dirty paths from porcelain output and apply secret prefilter.
|
||||||
# Status format: "XY path" where XY is 2-char status, followed by the path.
|
# 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] = []
|
dirty_paths: list[str] = []
|
||||||
|
needs_staging: set[str] = set()
|
||||||
for line in status.splitlines():
|
for line in status.splitlines():
|
||||||
if not line.strip():
|
if not line.strip():
|
||||||
continue
|
continue
|
||||||
|
xy = line[:2]
|
||||||
# Skip the 3-char prefix "XY ". Handle renames ("R old -> new") by taking new.
|
# Skip the 3-char prefix "XY ". Handle renames ("R old -> new") by taking new.
|
||||||
entry = line[3:]
|
entry = line[3:]
|
||||||
if " -> " in entry:
|
if " -> " in entry:
|
||||||
entry = entry.split(" -> ", 1)[1]
|
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)
|
allowed, denied = filter_dirty_paths(dirty_paths)
|
||||||
if denied:
|
if denied:
|
||||||
|
|
@ -272,9 +281,11 @@ class LocalCommitAgent:
|
||||||
logger.debug(f"{_repo_display_name(repo_path)}: all dirty files on denylist, skipping")
|
logger.debug(f"{_repo_display_name(repo_path)}: all dirty files on denylist, skipping")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Stage only the allowed files (never blanket `git add -A` — that would
|
# Stage only the allowed files that aren't staged yet (never blanket
|
||||||
# stage denied secret paths too).
|
# `git add -A` — that would stage denied secret paths too).
|
||||||
_git(repo_path, "add", "--", *allowed)
|
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;
|
# 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.
|
# the 6000-byte stage-time cap is a sub-cap before the prefilter-level cap.
|
||||||
|
|
@ -311,9 +322,11 @@ class LocalCommitAgent:
|
||||||
|
|
||||||
if not message:
|
if not message:
|
||||||
logger.warning(f"Empty message for {repo_name}, skipping")
|
logger.warning(f"Empty message for {repo_name}, skipping")
|
||||||
# Unstage path-by-path — safe on unborn branches (no initial commit)
|
# Unstage only what WE staged, path-by-path — safe on unborn branches
|
||||||
# where `git reset HEAD` fails because HEAD is unresolved.
|
# (no initial commit) where `git reset HEAD` fails because HEAD is
|
||||||
_git(repo_path, "reset", "--", *allowed)
|
# unresolved. Paths staged before this cycle stay staged.
|
||||||
|
if to_stage:
|
||||||
|
_git(repo_path, "reset", "--", *to_stage)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Dry-run gate — log intent, unstage, do not commit or push
|
# 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"[DRY-RUN] {repo_name} ({branch}) would commit {len(allowed)} file(s) "
|
||||||
f"with message: {message.splitlines()[0] if message else '(empty)'}"
|
f"with message: {message.splitlines()[0] if message else '(empty)'}"
|
||||||
)
|
)
|
||||||
# Unstage path-by-path — safe on unborn branches (no initial commit)
|
# Unstage only what WE staged, path-by-path — safe on unborn branches
|
||||||
# where `git reset HEAD` fails because HEAD is unresolved.
|
# (no initial commit) where `git reset HEAD` fails because HEAD is
|
||||||
_git(repo_path, "reset", "--", *allowed)
|
# unresolved. Paths staged before this cycle stay staged.
|
||||||
|
if to_stage:
|
||||||
|
_git(repo_path, "reset", "--", *to_stage)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Commit
|
# Commit
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue