Repository: VirtusLab/git-machete

Issue:
`github checkout-prs --by=...` switches to ALL checked out branches one by one
Installed version from PR #435 (commit `6ee6681e33cf037e8c86b1cf340a443b4d77fcee`).

Testing on this very repo:

```
➜ 12:29 ~/git-machete develop $ g m s
  master  v3.7.2
  │ 
  └─develop  v3.8.0
➜ 12:29 ~/git-machete develop $ gb
* develop
  master
➜ 12:30 ~/git-machete develop $ g m github checkout-prs --by=amalota
Checking for open GitHub PRs...
branch 'feature/command_clean' set up to track 'origin/feature/command_clean'.
Switched to a new branch 'feature/command_clean'
Pull request #435 checked out at local branch feature/command_clean
branch 'code_quality/github_token_msg' set up to track 'origin/code_quality/github_token_msg'.
Switched to a new branch 'code_quality/github_token_msg'
Pull request #469 checked out at local branch code_quality/github_token_msg
branch 'feature/advance_with_push_to_remote' set up to track 'origin/feature/advance_with_push_to_remote'.
Switched to a new branch 'feature/advance_with_push_to_remote'
Pull request #473 checked out at local branch feature/advance_with_push_to_remote
➜ 12:30 ~/git-machete feature/advance_with_push_to_remote PR #473 (amalota) $ 
```

So... first HEAD hsa been switched to `feature/command_clean`, then to `code_quality/github_token_msg`, finally to `feature/advance_with_push_to_remote`.

This is contrary to docs in github.rst:

```
If only one PR is given, then switch the local repository's HEAD to its head branch.
```

In fact, the doc should probably say:

```
If only one PR has been checked out, then switch the local repository's HEAD to its head branch.
```

and `github checkout-prs` should behave adequately (i.e. for the sample above, it should **never** switch HEAD).

Pls provide regression tests as well.

Discussion:
PawelLipski: Let's include it in v3.8.0 as well! since it's a bug in a feature added by v3.8.0

Patch:
diff --git a/docs/source/cli_help/github.rst b/docs/source/cli_help/github.rst
index 0bfdb25..a5cef47 100644
--- a/docs/source/cli_help/github.rst
+++ b/docs/source/cli_help/github.rst
@@ -25,7 +25,7 @@ Creates, checks out and manages GitHub PRs while keeping them reflected in branc
   Check out the head branch of the given pull requests (specified by number),
   also traverse chain of pull requests upwards, adding branches one by one to git-machete and check them out locally.
   Once the specified pull requests are checked out locally, annotate local branches with corresponding pull request numbers.
-  If only one PR is given, then switch the local repository's HEAD to its head branch.
+  If only one PR has been checked out, then switch the local repository's HEAD to its head branch.
 
   **Options:**
 
diff --git a/git_machete/cli.py b/git_machete/cli.py
index d90f1a2..ec291d9 100644
--- a/git_machete/cli.py
+++ b/git_machete/cli.py
@@ -546,7 +546,9 @@ def launch(orig_args: List[str]) -> None:
                 branch=branch,
                 opt_onto=cli_opts.opt_onto,
                 opt_as_root=cli_opts.opt_as_root,
-                opt_yes=cli_opts.opt_yes)
+                opt_yes=cli_opts.opt_yes,
+                verbose=True,
+                switch_head_if_new_branch=True)
         elif cmd == "advance":
             machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
             git.expect_no_operation_in_progress()
diff --git a/git_machete/client.py b/git_machete/client.py
index 7871c51..1a42aa7 100644
--- a/git_machete/client.py
+++ b/git_machete/client.py
@@ -219,7 +219,8 @@ class MacheteClient:
             opt_onto: Optional[LocalBranchShortName],
             opt_as_root: bool,
             opt_yes: bool,
-            verbose: bool = True
+            verbose: bool,
+            switch_head_if_new_branch: bool
             ) -> None:
         if branch in self.managed_branches:
             raise MacheteException(
@@ -237,7 +238,7 @@ class MacheteClient:
                 msg = common_line + f"Check out `{branch}` locally?" + get_pretty_choices('y', 'N')
                 opt_yes_msg = common_line + f"Checking out `{branch}` locally..."
                 if self.ask_if(msg, opt_yes_msg, opt_yes=opt_yes, verbose=verbose) in ('y', 'yes'):
-                    self.__git.create_branch(branch, remote_branch.full_name())
+                    self.__git.create_branch(branch, remote_branch.full_name(), switch_head=switch_head_if_new_branch)
                 else:
                     return
                 # Not dealing with `onto` here. If it hasn't been explicitly
@@ -256,7 +257,7 @@ class MacheteClient:
                         current_branch = self.__git.get_current_branch_or_none()
                         if current_branch and current_branch in self.managed_branches:
                             opt_onto = current_branch
-                    self.__git.create_branch(branch, out_of)
+                    self.__git.create_branch(branch, out_of, switch_head=switch_head_if_new_branch)
                 else:
                     return
 
@@ -1913,21 +1914,23 @@ class MacheteClient:
                             opt_as_root=True,
                             opt_onto=None,
                             opt_yes=True,
-                            verbose=verbose)
+                            verbose=verbose,
+                            switch_head_if_new_branch=False)
                     else:
                         self.add(
                             branch=branch,
                             opt_onto=reversed_path[index - 1],
                             opt_as_root=False,
                             opt_yes=True,
-                            verbose=verbose)
+                            verbose=verbose,
+                            switch_head_if_new_branch=False)
                     if pr not in checked_out_prs:
                         print(fmt(f"Pull request `#{pr.number}` checked out at local branch `{pr.head}`"))
                         checked_out_prs.append(pr)
 
         debug('Current GitHub user is ' + (current_user or '<none>'))
         self.__sync_annotations_to_definition_file(all_open_prs, current_user, verbose=verbose)
-        if pr and len(applicable_prs) == 1 and len(checked_out_prs) == 1:
+        if pr and len(checked_out_prs) == 1:
             self.__git.checkout(LocalBranchShortName.of(pr.head))
             if verbose:
                 print(fmt(f"Switched to local branch `{pr.head}`"))
@@ -2266,7 +2269,12 @@ class MacheteClient:
 
         current_branch = self.__git.get_current_branch()
         if current_branch not in self.managed_branches:
-            self.add(branch=current_branch, opt_onto=opt_onto, opt_as_root=False, opt_yes=opt_yes)
+            self.add(branch=current_branch,
+                     opt_onto=opt_onto,
+                     opt_as_root=False,
+                     opt_yes=opt_yes,
+                     verbose=True,
+                     switch_head_if_new_branch=True)
             if current_branch not in self.managed_branches:
                 raise MacheteException(
                     "Command `github create-pr` can NOT be executed on the branch"
diff --git a/git_machete/docs.py b/git_machete/docs.py
index f6ad05e..b697234 100644
--- a/git_machete/docs.py
+++ b/git_machete/docs.py
@@ -351,7 +351,7 @@ long_docs: Dict[str, str] = {
           Check out the head branch of the given pull requests (specified by number),
           also traverse chain of pull requests upwards, adding branches one by one to git-machete and check them out locally.
           Once the specified pull requests are checked out locally, annotate local branches with corresponding pull request numbers.
-          If only one PR is given, then switch the local repository's HEAD to its head branch.
+          If only one PR has been checked out, then switch the local repository's HEAD to its head branch.
 
           <b>Options:</b>
             <b>--all</b>     Checkout all open PRs.
diff --git a/git_machete/git_operations.py b/git_machete/git_operations.py
index d83eaed..ca3a753 100644
--- a/git_machete/git_operations.py
+++ b/git_machete/git_operations.py
@@ -580,8 +580,10 @@ class GitContext:
                                                          ))
             return self.__reflogs_cached[branch]
 
-    def create_branch(self, branch: LocalBranchShortName, out_of_revision: AnyRevision) -> None:
-        self._run_git("checkout", "-b", branch, out_of_revision)
+    def create_branch(self, branch: LocalBranchShortName, out_of_revision: AnyRevision, switch_head: bool) -> None:
+        self._run_git("branch", branch, out_of_revision)
+        if switch_head:
+            self._run_git("checkout", branch)
         self.flush_caches()  # the repository state has changed because of a successful branch creation, let's defensively flush all the caches
 
     def flush_caches(self) -> None:


Test patch:
diff --git a/git_machete/tests/functional/test_machete.py b/git_machete/tests/functional/test_machete.py
index b76db02..38a3d45 100644
--- a/git_machete/tests/functional/test_machete.py
+++ b/git_machete/tests/functional/test_machete.py
@@ -50,8 +50,7 @@ def mock_exit_script(status_code: Optional[int] = None, error: Optional[BaseExce
 
 def mock_fetch_ref(cls: Any, remote: str, ref: str) -> None:
     branch: LocalBranchShortName = LocalBranchShortName.of(ref[ref.index(':') + 1:])
-    git.create_branch(branch, get_current_commit_hash())
-    git.checkout(branch)
+    git.create_branch(branch, get_current_commit_hash(), switch_head=True)
 
 
 def mock_run_cmd(cmd: str, *args: str, **kwargs: Any) -> int:
@@ -2404,13 +2403,13 @@ class MacheteTester(unittest.TestCase):
             |       |
             |       o-chore/redundant_checks  PR #18 (github_user)
             |
-            o-enhance/add_user *  PR #19 (github_user)
+            o-enhance/add_user  PR #19 (github_user)
 
             bugfix/add_user
             |
             o-testing/add_user  PR #22 (github_user)
               |
-              o-chore/comments  PR #24 (github_user)
+              o-chore/comments *  PR #24 (github_user)
             """
         )