Repository: VirtusLab/git-machete
Pull request #1082 (2023-10-22 13:43:03)

Make `clean` and `github sync` also slide out untracked branches, not only delete them


Patch:
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 97c8c2f..fff94f8 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -2,6 +2,8 @@
 
 ## New in git-machete 3.20.1
 
+- fixed: `git machete clean` and `git machete github sync` not only delete untracked branches, but also actually slide them out of branch layout
+
 ## New in git-machete 3.20.0
 
 - added: `git machete github create-pr` adds a comment linking the PR to its base PR; `github retarget-pr` keeps that comment up to date (suggested by @guyboltonking)
diff --git a/docs/man/git-machete.1 b/docs/man/git-machete.1
index ec929ab..8d553ec 100644
--- a/docs/man/git-machete.1
+++ b/docs/man/git-machete.1
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
 .in \\n[rst2man-indent\\n[rst2man-indent-level]]u
 ..
-.TH "GIT-MACHETE" "1" "Oct 21, 2023" "" "git-machete"
+.TH "GIT-MACHETE" "1" "Oct 22, 2023" "" "git-machete"
 .SH NAME
 git-machete \- git-machete 3.20.1
 .sp
diff --git a/git_machete/client.py b/git_machete/client.py
index c3fd030..22d30cf 100644
--- a/git_machete/client.py
+++ b/git_machete/client.py
@@ -2743,15 +2743,21 @@ class MacheteClient:
     def delete_untracked(self, opt_yes: bool) -> None:
         print(bold('Checking for untracked managed branches with no downstream...'))
         branches_to_delete: List[LocalBranchShortName] = []
-        # TODO (#453): Consider switching to immutable collections for keeping the state (managed_branches etc.).
-        for managed_branch in self.managed_branches.copy():
-            status, _ = self.__git.get_combined_remote_sync_status(managed_branch)
-            if status == SyncToRemoteStatuses.UNTRACKED:
-                if not self.__down_branches.get(managed_branch):
-                    branches_to_delete.append(managed_branch)
-                    self.managed_branches.remove(managed_branch)
-                    if managed_branch in self.__up_branch:
-                        del self.__up_branch[managed_branch]
+        for branch in self.managed_branches.copy():
+            status, _ = self.__git.get_combined_remote_sync_status(branch)
+            if status == SyncToRemoteStatuses.UNTRACKED and not self.__down_branches.get(branch):
+                branches_to_delete.append(branch)
+                self.managed_branches.remove(branch)
+                if branch in self.__annotations:
+                    del self.__annotations[branch]
+                if branch in self.__up_branch:
+                    upstream = self.__up_branch[branch]
+                    del self.__up_branch[branch]
+                    self.__down_branches[upstream] = [
+                        b for b in (self.__down_branches.get(upstream) or []) if b != branch
+                    ]
+                else:
+                    self.__roots.remove(branch)
 
         self.__delete_branches(branches_to_delete=branches_to_delete, opt_yes=opt_yes)
         self.save_branch_layout_file()


Test patch:
diff --git a/tests/mockers.py b/tests/mockers.py
index 339278b..e53fbf4 100644
--- a/tests/mockers.py
+++ b/tests/mockers.py
@@ -73,6 +73,11 @@ def assert_failure(cmds: Iterable[str], expected_result: str, expected_exception
     assert error_message == expected_result
 
 
+def read_branch_layout_file() -> str:
+    with open(".git/machete") as def_file:
+        return def_file.read()
+
+
 def rewrite_branch_layout_file(new_body: str) -> None:
     new_body = textwrap.dedent(new_body)
     with open(".git/machete", 'w') as def_file:
diff --git a/tests/test_clean.py b/tests/test_clean.py
index fe06678..9bc1100 100644
--- a/tests/test_clean.py
+++ b/tests/test_clean.py
@@ -2,7 +2,7 @@ from pytest_mock import MockerFixture
 
 from .base_test import BaseTest
 from .mockers import (assert_success, launch_command, mock_input_returning_y,
-                      rewrite_branch_layout_file)
+                      read_branch_layout_file, rewrite_branch_layout_file)
 from .mockers_github import mock_github_token_for_domain_none
 
 
@@ -50,9 +50,10 @@ class TestClean(BaseTest):
 
         launch_command('clean')
 
+        assert read_branch_layout_file() == "master\n    bar\n    foo\n    moo\n"
+
         expected_status_output = (
             """
-            Warning: sliding invalid branches: bar2, foo2, moo2, mars out of the branch layout file
               master *
               |
               o-bar (untracked)
diff --git a/tests/test_github_sync.py b/tests/test_github_sync.py
index e8eff43..d75e767 100644
--- a/tests/test_github_sync.py
+++ b/tests/test_github_sync.py
@@ -70,7 +70,6 @@ class TestGitHubSync(BaseTest):
 
         expected_status_output = (
             """
-            Warning: sliding invalid branches: bar2, foo2, moo2 out of the branch layout file
               master
               |
               o-bar (untracked)