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:
1 parent
814322f8f5
commit
0d88c69c51
7 files changed
+2783
-37
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -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
|
||||
|
||||
Reference in new issue
Block a user