Merge pull request 'feat: discover merged cleanup worktree aliases (Closes #532)' (#571) from feat/issue-532-merged-cleanup-worktree-aliases into master
This commit was merged in pull request #571.
This commit is contained in:
@@ -4,7 +4,7 @@ import os
|
||||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest.mock import patch, MagicMock
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
sys.path.insert(0, str(__import__("pathlib").Path(__file__).resolve().parent.parent))
|
||||
|
||||
@@ -163,7 +163,7 @@ class TestMergedCleanupReport(unittest.TestCase):
|
||||
"branch refs/heads/feat/issue-11-x\n"
|
||||
"HEAD cafebabe\n\n"
|
||||
)
|
||||
mock_run.return_value = MagicMock(returncode=0, stdout=mock_output)
|
||||
mock_run.return_value = Mock(returncode=0, stdout=mock_output)
|
||||
res = mcr.discover_local_worktrees("/repo/Gitea-Tools")
|
||||
self.assertEqual(res, {"feat/issue-11-x": "/repo/Gitea-Tools/branches/custom-name"})
|
||||
|
||||
@@ -177,14 +177,23 @@ class TestMergedCleanupReport(unittest.TestCase):
|
||||
"branch refs/heads/feat/issue-11-x\n"
|
||||
"HEAD cafebabe\n\n"
|
||||
)
|
||||
mock_run.return_value = MagicMock(returncode=0, stdout=mock_output)
|
||||
mock_state.return_value = {
|
||||
"exists": True,
|
||||
"clean": True,
|
||||
"current_branch": "feat/issue-11-x",
|
||||
"head_sha": "cafebabe",
|
||||
"dirty_files": [],
|
||||
}
|
||||
mock_run.return_value = Mock(returncode=0, stdout=mock_output)
|
||||
mock_state.side_effect = [
|
||||
{
|
||||
"exists": False,
|
||||
"clean": None,
|
||||
"current_branch": None,
|
||||
"head_sha": None,
|
||||
"dirty_files": [],
|
||||
},
|
||||
{
|
||||
"exists": True,
|
||||
"clean": True,
|
||||
"current_branch": "feat/issue-11-x",
|
||||
"head_sha": "cafebabe",
|
||||
"dirty_files": [],
|
||||
},
|
||||
]
|
||||
|
||||
report = mcr.build_reconciliation_report(
|
||||
project_root="/repo/Gitea-Tools",
|
||||
@@ -209,29 +218,190 @@ class TestMergedCleanupReport(unittest.TestCase):
|
||||
self.assertEqual(local["worktree_path"], "/repo/Gitea-Tools/branches/custom-name")
|
||||
self.assertEqual(local["match_type"], "branch")
|
||||
|
||||
@patch("merged_cleanup_reconcile.read_local_worktree_state")
|
||||
@patch("subprocess.run")
|
||||
def test_remove_local_worktree_uses_discovered_path(self, mock_run):
|
||||
mock_output = (
|
||||
"worktree /repo/Gitea-Tools\n"
|
||||
"bare\n\n"
|
||||
"worktree /repo/Gitea-Tools/branches/custom-name\n"
|
||||
"branch refs/heads/feat/issue-11-x\n"
|
||||
"HEAD cafebabe\n\n"
|
||||
def test_build_report_discovers_issue_alias_worktree(self, mock_run, mock_state):
|
||||
mock_run.return_value = Mock(
|
||||
returncode=0,
|
||||
stdout=(
|
||||
"worktree /repo/Gitea-Tools\n"
|
||||
"HEAD aaaa\n"
|
||||
"branch refs/heads/master\n\n"
|
||||
"worktree /repo/Gitea-Tools/branches/issue-510-workspace-binding-isolation\n"
|
||||
"HEAD cafebabe\n"
|
||||
"branch refs/heads/feat/issue-510-workspace-binding-isolation\n\n"
|
||||
),
|
||||
)
|
||||
# First call is discover_local_worktrees, second is git worktree remove
|
||||
mock_run.side_effect = [
|
||||
MagicMock(returncode=0, stdout=mock_output),
|
||||
MagicMock(returncode=0, stdout="", stderr=""),
|
||||
mock_state.side_effect = [
|
||||
{
|
||||
"exists": False,
|
||||
"clean": None,
|
||||
"current_branch": None,
|
||||
"head_sha": None,
|
||||
"dirty_files": [],
|
||||
},
|
||||
{
|
||||
"exists": True,
|
||||
"clean": True,
|
||||
"current_branch": "feat/issue-510-workspace-binding-isolation",
|
||||
"head_sha": "cafebabe",
|
||||
"dirty_files": [],
|
||||
},
|
||||
]
|
||||
|
||||
with patch("os.path.isdir", return_value=True):
|
||||
result = mcr.remove_local_worktree("/repo/Gitea-Tools", "feat/issue-11-x")
|
||||
report = mcr.build_reconciliation_report(
|
||||
project_root="/repo/Gitea-Tools",
|
||||
closed_prs=[
|
||||
{
|
||||
"number": 512,
|
||||
"title": "merged cleanup",
|
||||
"body": "Closes #510",
|
||||
"head": {
|
||||
"ref": "feat/issue-510-workspace-binding-isolation",
|
||||
"sha": "cafebabe",
|
||||
},
|
||||
"merged_at": "2026-07-06T12:00:00Z",
|
||||
"merge_commit_sha": "abc123",
|
||||
}
|
||||
],
|
||||
open_prs=[],
|
||||
remote_branch_exists={"feat/issue-510-workspace-binding-isolation": True},
|
||||
head_on_master={512: True},
|
||||
delete_capability_allowed=True,
|
||||
target_ref="prgs/master",
|
||||
)
|
||||
|
||||
local = report["entries"][0]["local_worktree"]
|
||||
self.assertTrue(local["safe_to_remove_worktree"])
|
||||
self.assertEqual(local["match_type"], "branch")
|
||||
self.assertEqual(
|
||||
local["expected_worktree_path"],
|
||||
"/repo/Gitea-Tools/branches/feat-issue-510-workspace-binding-isolation",
|
||||
)
|
||||
self.assertEqual(
|
||||
local["discovered_worktree_path"],
|
||||
"/repo/Gitea-Tools/branches/issue-510-workspace-binding-isolation",
|
||||
)
|
||||
|
||||
@patch("merged_cleanup_reconcile.read_local_worktree_state")
|
||||
@patch("subprocess.run")
|
||||
def test_ambiguous_issue_alias_worktrees_fail_closed(self, mock_run, mock_state):
|
||||
mock_run.return_value = Mock(
|
||||
returncode=0,
|
||||
stdout=(
|
||||
"worktree /repo/Gitea-Tools/branches/issue-510-one\n"
|
||||
"HEAD cafebabe\n"
|
||||
"branch refs/heads/feat/issue-510-workspace-binding-isolation\n\n"
|
||||
"worktree /repo/Gitea-Tools/branches/issue-510-two\n"
|
||||
"HEAD cafebabe\n"
|
||||
"branch refs/heads/feat/issue-510-workspace-binding-isolation\n\n"
|
||||
),
|
||||
)
|
||||
mock_state.return_value = {
|
||||
"exists": False,
|
||||
"clean": None,
|
||||
"current_branch": None,
|
||||
"head_sha": None,
|
||||
"dirty_files": [],
|
||||
}
|
||||
|
||||
report = mcr.build_reconciliation_report(
|
||||
project_root="/repo/Gitea-Tools",
|
||||
closed_prs=[
|
||||
{
|
||||
"number": 512,
|
||||
"title": "merged cleanup",
|
||||
"body": "Closes #510",
|
||||
"head": {
|
||||
"ref": "feat/issue-510-workspace-binding-isolation",
|
||||
"sha": "cafebabe",
|
||||
},
|
||||
"merged_at": "2026-07-06T12:00:00Z",
|
||||
"merge_commit_sha": "abc123",
|
||||
}
|
||||
],
|
||||
open_prs=[],
|
||||
remote_branch_exists={"feat/issue-510-workspace-binding-isolation": True},
|
||||
head_on_master={512: True},
|
||||
delete_capability_allowed=True,
|
||||
target_ref="prgs/master",
|
||||
)
|
||||
|
||||
local = report["entries"][0]["local_worktree"]
|
||||
self.assertFalse(local["safe_to_remove_worktree"])
|
||||
self.assertEqual(local["match_type"], "ambiguous_alias")
|
||||
self.assertIn("ambiguous local worktree aliases", local["block_reasons"][0])
|
||||
|
||||
@patch("merged_cleanup_reconcile.is_head_ancestor_of_ref", return_value=True)
|
||||
@patch("merged_cleanup_reconcile.read_local_worktree_state")
|
||||
@patch("subprocess.run")
|
||||
def test_alias_head_on_target_branch_is_safe_cleanup_commit(
|
||||
self, mock_run, mock_state, _ancestor
|
||||
):
|
||||
mock_run.return_value = Mock(
|
||||
returncode=0,
|
||||
stdout=(
|
||||
"worktree /repo/Gitea-Tools/branches/issue-510-workspace-binding-isolation\n"
|
||||
"HEAD deadbeef\n"
|
||||
"branch refs/heads/feat/issue-510-workspace-binding-isolation\n\n"
|
||||
),
|
||||
)
|
||||
mock_state.side_effect = [
|
||||
{
|
||||
"exists": False,
|
||||
"clean": None,
|
||||
"current_branch": None,
|
||||
"head_sha": None,
|
||||
"dirty_files": [],
|
||||
},
|
||||
{
|
||||
"exists": True,
|
||||
"clean": True,
|
||||
"current_branch": "feat/issue-510-workspace-binding-isolation",
|
||||
"head_sha": "deadbeef",
|
||||
"dirty_files": [],
|
||||
},
|
||||
]
|
||||
|
||||
state = mcr.resolve_cleanup_worktree_state(
|
||||
project_root="/repo/Gitea-Tools",
|
||||
head_branch="feat/issue-510-workspace-binding-isolation",
|
||||
issue_number=510,
|
||||
pr_head_sha="cafebabe",
|
||||
target_ref="prgs/master",
|
||||
)
|
||||
|
||||
self.assertTrue(state["exists"])
|
||||
self.assertEqual(state["match_type"], "branch")
|
||||
self.assertEqual(
|
||||
state["discovered_worktree_path"],
|
||||
"/repo/Gitea-Tools/branches/issue-510-workspace-binding-isolation",
|
||||
)
|
||||
|
||||
@patch("os.path.isdir", return_value=True)
|
||||
@patch("subprocess.run")
|
||||
def test_remove_local_worktree_uses_assessed_path(self, mock_run, _isdir):
|
||||
mock_run.return_value = Mock(returncode=0, stdout="", stderr="")
|
||||
result = mcr.remove_local_worktree(
|
||||
"/repo/Gitea-Tools",
|
||||
"feat/issue-510-workspace-binding-isolation",
|
||||
worktree_path="/repo/Gitea-Tools/branches/issue-510-workspace-binding-isolation",
|
||||
)
|
||||
|
||||
self.assertTrue(result["success"])
|
||||
self.assertTrue(result["performed"])
|
||||
self.assertEqual(result["worktree_path"], "/repo/Gitea-Tools/branches/custom-name")
|
||||
# Assert git worktree remove was called with the custom path
|
||||
mock_run.assert_any_call(
|
||||
["git", "-C", "/repo/Gitea-Tools", "worktree", "remove", "/repo/Gitea-Tools/branches/custom-name"],
|
||||
self.assertEqual(
|
||||
result["worktree_path"],
|
||||
"/repo/Gitea-Tools/branches/issue-510-workspace-binding-isolation",
|
||||
)
|
||||
mock_run.assert_called_once_with(
|
||||
[
|
||||
"git",
|
||||
"-C",
|
||||
"/repo/Gitea-Tools",
|
||||
"worktree",
|
||||
"remove",
|
||||
"/repo/Gitea-Tools/branches/issue-510-workspace-binding-isolation",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=False,
|
||||
@@ -440,4 +610,4 @@ class TestReviewerScratchCleanup(unittest.TestCase):
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user