kin: auto-commit after pipeline

This commit is contained in:
Gros Frumos 2026-03-21 11:39:10 +02:00
parent 977176f004
commit d7f7193ad7
4 changed files with 352 additions and 52 deletions

View file

@ -10,6 +10,7 @@ All functions are defensive: never raise, always log warnings on error.
import logging
import shutil
import subprocess
import time
from pathlib import Path
_logger = logging.getLogger("kin.worktree")
@ -59,12 +60,15 @@ def create_worktree(project_path: str, task_id: str, step_name: str = "step") ->
return None
def merge_worktree(worktree_path: str, project_path: str) -> dict:
def merge_worktree(worktree_path: str, project_path: str, max_retries: int = 0, retry_delay_s: int = 15) -> dict:
"""Merge the worktree branch back into current HEAD of project_path.
Branch name is derived from the worktree directory name.
On conflict: aborts merge and returns success=False with conflict list.
max_retries: number of retry attempts after the first failure (default 0 = no retry).
retry_delay_s: seconds to wait between retry attempts.
Returns {success: bool, conflicts: list[str], merged_files: list[str]}
"""
git = _git(project_path)
@ -88,50 +92,60 @@ def merge_worktree(worktree_path: str, project_path: str) -> dict:
)
commit_had_changes = commit_result.returncode == 0
merge_result = subprocess.run(
[git, "merge", "--no-ff", branch_name],
cwd=project_path,
capture_output=True,
text=True,
timeout=60,
)
if merge_result.returncode == 0:
diff_result = subprocess.run(
[git, "diff", "HEAD~1", "HEAD", "--name-only"],
for attempt in range(max_retries + 1):
merge_result = subprocess.run(
[git, "merge", "--no-ff", branch_name],
cwd=project_path,
capture_output=True,
text=True,
timeout=60,
)
if merge_result.returncode == 0:
diff_result = subprocess.run(
[git, "diff", "HEAD~1", "HEAD", "--name-only"],
cwd=project_path,
capture_output=True,
text=True,
timeout=10,
)
merged_files = [
f.strip() for f in diff_result.stdout.splitlines() if f.strip()
]
_logger.info("Merged worktree %s: %d files", branch_name, len(merged_files))
return {"success": True, "conflicts": [], "merged_files": merged_files}
# Merge failed — if commit was also empty (nothing to commit), treat as success
if not commit_had_changes:
_logger.info("Worktree %s: nothing to commit, skipping merge", branch_name)
return {"success": True, "conflicts": [], "merged_files": []}
# Merge failed — collect conflicts and abort
conflict_result = subprocess.run(
[git, "diff", "--name-only", "--diff-filter=U"],
cwd=project_path,
capture_output=True,
text=True,
timeout=10,
)
merged_files = [
f.strip() for f in diff_result.stdout.splitlines() if f.strip()
]
_logger.info("Merged worktree %s: %d files", branch_name, len(merged_files))
return {"success": True, "conflicts": [], "merged_files": merged_files}
conflicts = [f.strip() for f in conflict_result.stdout.splitlines() if f.strip()]
# Merge failed — if commit was also empty (nothing to commit), treat as success
if not commit_had_changes:
_logger.info("Worktree %s: nothing to commit, skipping merge", branch_name)
return {"success": True, "conflicts": [], "merged_files": []}
subprocess.run(
[git, "merge", "--abort"],
cwd=project_path,
capture_output=True,
timeout=10,
)
# Merge failed — collect conflicts and abort
conflict_result = subprocess.run(
[git, "diff", "--name-only", "--diff-filter=U"],
cwd=project_path,
capture_output=True,
text=True,
timeout=10,
)
conflicts = [f.strip() for f in conflict_result.stdout.splitlines() if f.strip()]
if attempt < max_retries:
_logger.warning(
"KIN-139: merge conflict in %s (attempt %d/%d), retrying in %ds",
branch_name, attempt + 1, max_retries + 1, retry_delay_s,
)
time.sleep(retry_delay_s)
continue
subprocess.run(
[git, "merge", "--abort"],
cwd=project_path,
capture_output=True,
timeout=10,
)
_logger.warning("Merge conflict in worktree %s: %s", branch_name, conflicts)
return {"success": False, "conflicts": conflicts, "merged_files": []}
_logger.warning("Merge conflict in worktree %s: %s", branch_name, conflicts)
return {"success": False, "conflicts": conflicts, "merged_files": []}
except Exception as exc:
_logger.warning("merge_worktree error for %s: %s", branch_name, exc)