feat(branch): cleanup --local deletes merged local branches only when git itself would

pkgmgr branch cleanup --local [--apply] [--json] [--merged-into REF]
deletes local branches whose tip is an ancestor of the remote-tracking
default or a --merged-into ref, each with update-ref against the planned
sha. It keeps every branch git branch --delete-merged keeps: symbolic,
checked out or registered in any worktree, under a rebase or bisect,
deleteMerged=false in any worktree, system or global config, and the
local upstream of any branch that stays. Git runs without inherited
GIT_* variables except the ceiling; the keep reads run once with the
caller's config variables and once with the default system and
passwd-home config, so the environment can add keeps but never remove
one. Repositories with symlinked refs, foreign worktree registrations or
unreadable holds are refused with the plan still printed.

MEDUSA: MEDUSA-OUTPUT-SQL-003 at branch_cmd.py:121 accepted after an
independent review: the SQL comment+DELETE regex matched the help text
--merged-into and the git key deleteMerged; no SQL in the change.

🎯 Fix: 92 %
💥 Break: 5 %
💸 Cost: 100 %
⏱️ Time: 100 %
🛡️ Security: 100 %
🏆 Score: 87
🚦 Gate: ✅ 90/5, cost and time <= 100, security 100 %

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kevin Veen-BirkenbachandClaude Opus 5.5 committed 2026-11-13 08:01:06 +00:00
1 parent 814322f8f5
commit 0d88c69c51
7 files changed
+2783 -37

No files matched your search

+97 -1
View File
@@ -1,8 +1,12 @@
from __future__ import annotations
import os
import pwd
import unittest
from unittest.mock import MagicMock, patch
from pkgmgr.core.git.errors import GitRunError
from pkgmgr.core.git.run import run
from pkgmgr.core.git.run import CALLER_CONFIG, DEFAULT_CONFIG, run, strict_env
class TestGitRun(unittest.TestCase):
@@ -55,6 +59,98 @@ class TestGitRun(unittest.TestCase):
self.assertNotIn("stdin", kwargs)
self.assertIn("timeout", kwargs)
OVERRIDES = (
"GIT_DIR",
"GIT_WORK_TREE",
"GIT_INDEX_FILE",
"GIT_COMMON_DIR",
"GIT_CONFIG",
"GIT_CEILING_DIRECTORIES",
"GIT_CONFIG_GLOBAL",
"GIT_CONFIG_SYSTEM",
"GIT_CONFIG_PARAMETERS",
"GIT_CONFIG_COUNT",
"GIT_CONFIG_KEY_0",
"GIT_CONFIG_VALUE_0",
"GIT_CONFIG_KEY_X",
)
CONFIG_READ = (
"GIT_CEILING_DIRECTORIES",
"GIT_CONFIG_COUNT",
"GIT_CONFIG_GLOBAL",
"GIT_CONFIG_KEY_0",
"GIT_CONFIG_PARAMETERS",
"GIT_CONFIG_SYSTEM",
"GIT_CONFIG_VALUE_0",
)
def _env(
self, scan: bool, strict: bool, view: str | None = None
) -> dict[str, str] | None:
completed = MagicMock()
completed.stdout = ""
planted = {
**dict.fromkeys(self.OVERRIDES, "/elsewhere"),
"HOME": "/elsewhere",
"XDG_CONFIG_HOME": "/elsewhere",
}
with (
patch.dict("os.environ", planted),
patch(
"pkgmgr.core.git.run.subprocess.run", return_value=completed
) as mock_run,
):
if strict:
with strict_env():
run(["status"], scan=scan, config_view=view)
else:
run(["status"], scan=scan, config_view=view)
return mock_run.call_args.kwargs.get("env")
def _kept(self, env: dict[str, str]) -> list[str]:
return sorted(name for name in self.OVERRIDES if name in env)
def test_scan_drops_repository_overrides_from_the_environment(self) -> None:
unset = ("GIT_DIR", "GIT_WORK_TREE", "GIT_INDEX_FILE")
for view in (None, CALLER_CONFIG):
with self.subTest(view=view):
env = self._env(scan=True, strict=False, view=view)
self.assertEqual(
self._kept(env),
sorted(name for name in self.OVERRIDES if name not in unset),
)
self.assertEqual(env.get("GIT_CONFIG_NOSYSTEM"), None if view else "1")
self.assertIsNone(self._env(scan=False, strict=False, view=view))
def test_strict_env_keeps_only_the_ceiling(self) -> None:
for scan in (True, False):
with self.subTest(scan=scan):
env = self._env(scan=scan, strict=True)
self.assertEqual(self._kept(env), ["GIT_CEILING_DIRECTORIES"])
self.assertEqual(env.get("GIT_CONFIG_NOSYSTEM"), "1")
def test_strict_caller_config_also_keeps_the_config_variables(self) -> None:
for scan in (True, False):
with self.subTest(scan=scan):
env = self._env(scan=scan, strict=True, view=CALLER_CONFIG)
self.assertEqual(self._kept(env), list(self.CONFIG_READ))
self.assertIsNone(env.get("GIT_CONFIG_NOSYSTEM"))
self.assertEqual(env.get("HOME"), "/elsewhere")
def test_default_config_ignores_the_environment(self) -> None:
home = pwd.getpwuid(os.getuid()).pw_dir
for strict in (True, False):
with self.subTest(strict=strict):
env = self._env(scan=True, strict=strict, view=DEFAULT_CONFIG)
self.assertEqual(self._kept(env), ["GIT_CEILING_DIRECTORIES"])
self.assertIsNone(env.get("GIT_CONFIG_NOSYSTEM"))
self.assertEqual(env.get("HOME"), home)
self.assertFalse("XDG_CONFIG_HOME" in env)
def test_failure_raises_giterror_with_details(self) -> None:
# Build a CalledProcessError with stdout/stderr populated
import subprocess as sp