Repository: VirtusLab/git-machete
Pull request #1460 (2025-07-07 14:08:45)

Release v3.36.1


Patch:
diff --git a/.circleci/config.yml b/.circleci/config.yml
index a87dd3f..e86bb25 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -38,6 +38,7 @@ jobs:
           # language=sh
           command: |
             set -x
+
             python --version
             pip3 install tox
 
@@ -280,12 +281,13 @@ jobs:
     steps:
       - checkout
       - run:
-          name: 'Create GitHub release'
+          name: 'Create GitHub release and close milestone'
           # language=sh
           command: |
             sudo apt-get update
-            sudo apt-get install hub
-            bash ci/github-releases/ci-deploy.sh
+            sudo apt-get install gh
+            bash ci/github/create-release.sh
+            bash ci/github/close-milestone.sh
 
 
 # Note that these top-level keys are NOT consumed by CircleCI,
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 3f0d058..eb3a885 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -17,3 +17,6 @@ updates:
         patterns:
           # To lump all updated dependencies together into a single PR (instead of PR-per-dependency)
           - "*"
+    ignore:
+      # This library needs to be updated in tandem with sphinx-book-theme
+      - dependency-name: pydata-sphinx-theme
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index c9a0e66..e8cd134 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,5 +1,12 @@
 # Release notes
 
+## New in git-machete 3.36.1
+
+- fixed: incorrect `Could not determine base branch for PR` error messages when creating PRs from `git machete traverse -H`
+- fixed: `git bisect` is recognized as a separate repository state by `git machete status` and side-effecting operations
+- fixed: branches marked as `slide-out=no` are no longer slid out by `git machete slide-out --removed-from-remote`
+- fixed: if current branch hasn't been slid out, then `git machete slide-out` no longer switches the current branch
+
 ## New in git-machete 3.36.0
 
 - deprecated: `git machete fork-point --override-to=...` and `--override-to-inferred` options; use `--override-to-parent`, or run `git machete update [--fork-point=...]` instead
diff --git a/ci/github/close-milestone.sh b/ci/github/close-milestone.sh
new file mode 100644
index 0000000..c41d928
--- /dev/null
+++ b/ci/github/close-milestone.sh
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+set -e -o pipefail -u -x
+
+milestone_title=v$(cut -d\' -f2 git_machete/__init__.py)
+
+milestones_json_array=$(gh api repos/VirtusLab/git-machete/milestones)
+milestone=$(echo "$milestones_json_array" | jq -c --arg TITLE "$milestone_title" '.[] | select(.title == $TITLE)')
+
+number=$(echo "$milestone" | jq '.number')
+# open issues and PRs are counted together in the same field
+open_issue_count=$(echo "$milestone" | jq '.open_issues')
+
+if [[ $open_issue_count = 0 ]]; then
+  gh api --method PATCH "repos/VirtusLab/git-machete/milestones/$number" -f state=closed
+fi
diff --git a/ci/github-releases/ci-deploy.sh b/ci/github/create-release.sh
similarity index 70%
rename from ci/github-releases/ci-deploy.sh
rename to ci/github/create-release.sh
index 3c65322..481928f 100644
--- a/ci/github-releases/ci-deploy.sh
+++ b/ci/github/create-release.sh
@@ -5,5 +5,6 @@ set -e -o pipefail -u -x
 tag=v$(cut -d\' -f2 git_machete/__init__.py)
 # Note that this will also create a git tag on the remote
 # (since apparently all non-draft releases on GitHub must have a corresponding git tag).
-hub release create "$tag" \
-  --message="$tag"$'\n\n'"$(sed '5,/^$/!d; /^$/d' RELEASE_NOTES.md)" \
+gh release create "$tag" \
+  --title "$tag" \
+  --notes "$(sed '5,/^$/!d; /^$/d' RELEASE_NOTES.md)"
diff --git a/ci/nixpkgs-build/Dockerfile b/ci/nixpkgs-build/Dockerfile
index 3166eb6..ebd3eb0 100644
--- a/ci/nixpkgs-build/Dockerfile
+++ b/ci/nixpkgs-build/Dockerfile
@@ -8,4 +8,4 @@ RUN chmod +x /root/entrypoint.sh
 CMD /root/entrypoint.sh
 
 WORKDIR /root/nixpkgs
-ENV EXPRESSION_PATH=pkgs/applications/version-management/git-machete/default.nix
+ENV EXPRESSION_PATH=pkgs/by-name/gi/git-machete/package.nix
diff --git a/ci/tox/build-context/requirements.txt b/ci/tox/build-context/requirements.txt
index 41b85f2..07640f4 100644
--- a/ci/tox/build-context/requirements.txt
+++ b/ci/tox/build-context/requirements.txt
@@ -10,4 +10,4 @@ setuptools==80.9.0; python_version >= '3.9'
 tox==3.28.0; python_version <= '3.6'
 tox==4.8.0; python_version >= '3.7' and python_version < '3.8'
 tox==4.25.0; python_version >= '3.8' and python_version < '3.9'
-tox==4.26.0; python_version >= '3.9'
+tox==4.27.0; python_version >= '3.9'
diff --git a/docs/generate_py_docs.py b/docs/generate_py_docs.py
index 1ce32ce..515bc9b 100644
--- a/docs/generate_py_docs.py
+++ b/docs/generate_py_docs.py
@@ -98,7 +98,7 @@ def html2txt(html_: str):
 
     # format elements in the `Option:` section by adding new line and indent
     for tag in html_elements.select('kbd'):
-        tag.insert_before(f'\n{INDENT_LEN_3}')
+        tag.insert_before(f'\n\n{INDENT_LEN_3}')
 
     # add indent to the description list
     for tag in html_elements.select('dt'):
@@ -186,8 +186,7 @@ if __name__ == '__main__':
     INDENT_LEN_4 = 4 * ' '
     docs_source_path = 'docs/source'
     warning_text = '# ---------------------------------------------------------------------------------------------------------\n' \
-                   '# Warning: This file is NOT supposed to be edited directly, ' \
-                   'but instead regenerated via `tox -e py-docs`\n' \
+                   '# Warning: This file is NOT supposed to be edited directly, but instead regenerated via `tox -e py-docs`\n' \
                    '# ---------------------------------------------------------------------------------------------------------\n'
     output_text = 'from typing import Dict\n\n' + warning_text
 
diff --git a/docs/man/git-machete.1 b/docs/man/git-machete.1
index 4d7d64a..5ceb92a 100644
--- a/docs/man/git-machete.1
+++ b/docs/man/git-machete.1
@@ -27,9 +27,9 @@ 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" "May 30, 2025" "" "git-machete"
+.TH "GIT-MACHETE" "1" "Jul 03, 2025" "" "git-machete"
 .SH NAME
-git-machete \- git-machete 3.36.0
+git-machete \- git-machete 3.36.1
 .sp
 git machete is a robust tool that \fBsimplifies your git workflows\fP\&.
 .sp
diff --git a/git_machete/__init__.py b/git_machete/__init__.py
index 094089c..29f8c80 100644
--- a/git_machete/__init__.py
+++ b/git_machete/__init__.py
@@ -1,1 +1,1 @@
-__version__ = '3.36.0'
+__version__ = '3.36.1'
diff --git a/git_machete/client/base.py b/git_machete/client/base.py
index c4115e7..93178e7 100644
--- a/git_machete/client/base.py
+++ b/git_machete/client/base.py
@@ -499,6 +499,7 @@ class MacheteClient:
             else:
                 sync_to_parent_status[branch] = SyncToParentStatus.IN_SYNC_BUT_FORK_POINT_OFF
 
+        currently_bisected_branch = self._git.get_currently_bisected_branch_or_none()
         currently_rebased_branch = self._git.get_currently_rebased_branch_or_none()
         currently_checked_out_branch = self._git.get_currently_checked_out_branch_or_none()
 
@@ -573,10 +574,12 @@ class MacheteClient:
                     out.write("\n")
                 out.write("  " + maybe_space_before_branch_name)
 
-            if branch in (currently_checked_out_branch, currently_rebased_branch):
-                # i.e. if branch is the current branch (checked out or being rebased)
+            if branch in (currently_checked_out_branch, currently_rebased_branch, currently_bisected_branch):
+                # i.e. if branch is the current branch (checked out or being rebased or being bisected)
                 if branch == currently_rebased_branch:
                     prefix = "REBASING "
+                elif branch == currently_bisected_branch:
+                    prefix = "BISECTING "
                 elif self._git.is_am_in_progress():
                     prefix = "GIT AM IN PROGRESS "
                 elif self._git.is_cherry_pick_in_progress():
diff --git a/git_machete/client/slide_out.py b/git_machete/client/slide_out.py
index d2e48e8..f6d0301 100644
--- a/git_machete/client/slide_out.py
+++ b/git_machete/client/slide_out.py
@@ -85,7 +85,9 @@ class SlideOutMacheteClient(MacheteClient):
             slid_out_branch=branches_to_slide_out[-1],
             new_downstreams=new_downstreams)
 
-        self._git.checkout(new_upstream)
+        if self._git.get_current_branch_or_none() in branches_to_slide_out:
+            self._git.checkout(new_upstream)
+
         for new_downstream in new_downstreams:
             anno = self.annotations.get(new_downstream)
             use_merge = opt_merge or (anno and anno.qualifiers.update_with_merge)
@@ -117,8 +119,12 @@ class SlideOutMacheteClient(MacheteClient):
         slid_out_branches: List[LocalBranchShortName] = []
         for branch in self.managed_branches.copy():
             if self._git.is_removed_from_remote(branch) and not self.down_branches_for(branch):
-                print(fmt(f"Sliding out <b>{branch}</b>"))
-                slid_out_branches.append(branch)
+                anno = self.annotations.get(branch)
+                if anno and not anno.qualifiers.slide_out:
+                    print(fmt(f"Skipping <b>{branch}</b> as it's marked as `slide-out=no`"))
+                else:
+                    print(fmt(f"Sliding out <b>{branch}</b>"))
+                    slid_out_branches.append(branch)
 
         self._remove_branches_from_layout(slid_out_branches)
         if opt_delete:
diff --git a/git_machete/client/traverse.py b/git_machete/client/traverse.py
index 1efef77..159f680 100644
--- a/git_machete/client/traverse.py
+++ b/git_machete/client/traverse.py
@@ -137,7 +137,8 @@ class TraverseMacheteClient(MacheteClientWithCodeHosting):
                 if needs_parent_sync and branch in self.annotations:
                     needs_parent_sync = self.annotations[branch].qualifiers.rebase
 
-            if branch != current_branch and (needs_slide_out or needs_parent_sync or needs_remote_sync or needs_retarget_pr):
+            needs_any_action = needs_slide_out or needs_parent_sync or needs_remote_sync or needs_retarget_pr or needs_create_pr
+            if branch != current_branch and needs_any_action:
                 self._print_new_line(False)
                 print(f"Checking out {bold(branch)}")
                 self._git.checkout(branch)
@@ -217,16 +218,11 @@ class TraverseMacheteClient(MacheteClientWithCodeHosting):
                             from_exclusive=fork_point,
                             branch=branch,
                             opt_no_interactive_rebase=opt_no_interactive_rebase)
-                        # It's clearly possible that rebase can be in progress
-                        # after 'git rebase' returned non-zero exit code;
-                        # this happens most commonly in case of conflicts,
-                        # regardless of whether the rebase is interactive or not.
-                        # But for interactive rebases, it's still possible that
-                        # even if 'git rebase' returned zero, the rebase is still
-                        # in progress; e.g. when interactive rebase gets to 'edit'
-                        # command, it will exit returning zero, but the rebase
-                        # will be still in progress, waiting for user edits and
-                        # a subsequent 'git rebase --continue'.
+                        # It's clearly possible that rebase can be in progress after 'git rebase' returned non-zero exit code;
+                        # this happens most commonly in case of conflicts, regardless of whether the rebase is interactive or not.
+                        # But for interactive rebases, it's still possible that even if 'git rebase' returned zero, the rebase is still
+                        # in progress; e.g. when interactive rebase gets to 'edit' command, it will exit returning zero, but the rebase
+                        # will be still in progress, waiting for user edits and a subsequent 'git rebase --continue'.
                         rebased_branch = self._git.get_currently_rebased_branch_or_none()
                         if rebased_branch:  # 'rebased_branch' should be equal to 'branch' at this point anyway
                             print(fmt(f"\nRebase of {bold(rebased_branch)} in progress; stopping the traversal"))
diff --git a/git_machete/generated_docs.py b/git_machete/generated_docs.py
index 74319de..39b71d1 100644
--- a/git_machete/generated_docs.py
+++ b/git_machete/generated_docs.py
@@ -55,15 +55,19 @@ long_docs: Dict[str, str] = {
         Note: all the effects of `add` (except git branch creation) can as well be achieved by manually editing the branch layout file.
 
         <b>Options:</b>
+
            <b>-f</b>, <b>--as-first-child</b>
               Add the given branch as the first (instead of last) child of its parent.
               Cannot be specified together with `-R/--as-root`.
+
            <b>-o</b>, <b>--onto=<target-upstream-branch></b>
               Specifies the target parent branch to add the given branch onto.
               Cannot be specified together with `-R/--as-root`.
+
            <b>-R</b>, <b>--as-root</b>
               Add the given branch as a new root (and not onto any other branch).
               Cannot be specified together with `-o/--onto`.
+
            <b>-y</b>, <b>--yes</b>
               Don't ask for confirmation whether to create the branch or whether to add onto the inferred upstream.
    """,
@@ -115,6 +119,7 @@ long_docs: Dict[str, str] = {
         See help for `traverse` for more details on the qualifiers.
 
         <b>Options:</b>
+
            <b>-y</b>, <b>--yes</b>
               Don't ask for confirmation whether to fast-forward the current branch or whether to slide-out the downstream.
               Fails if the current branch has more than one <green>green-edge</green> downstream branch.
@@ -154,10 +159,13 @@ long_docs: Dict[str, str] = {
         Note: all the effects of `anno` can be always achieved by manually editing the branch layout file.
 
         <b>Options:</b>
+
            <b>-b</b>, <b>--branch=<branch></b>
               Branch to set the annotation for.
+
            <b>-H</b>, <b>--sync-github-prs</b>
               Annotate with GitHub PR numbers and author logins where applicable.
+
            <b>-L</b>, <b>--sync-gitlab-mrs</b>
               Annotate with GitLab MR numbers and author logins where applicable.
    """,
@@ -179,8 +187,10 @@ long_docs: Dict[str, str] = {
         For enterprise domains, non-standard URLs etc., check git config keys in `github` help.
 
         <b>Options:</b>
+
            <b>-c</b>, <b>--checkout-my-github-prs</b>
               Checkout your open PRs into local branches.
+
            <b>-y</b>, <b>--yes</b>
               Don't ask for confirmation when deleting branches from git.
 
@@ -404,6 +414,7 @@ long_docs: Dict[str, str] = {
         See help for `fork-point` for more details.
 
         <b>Options:</b>
+
            <b>-y</b>, <b>--yes</b>
               Don't ask for confirmation.
    """,
@@ -434,17 +445,21 @@ long_docs: Dict[str, str] = {
         `discover` retains the existing branch qualifiers used by `git machete traverse` (see help for `traverse`).
 
         <b>Options:</b>
+
            <b>-C</b>, <b>--checked-out-since=<date></b>
               Only consider branches checked out at least once since the given date.
               `<date>` can be, for example, `2 weeks ago` or `2020-06-01`, as in `git log --since=<date>`.
               If not present, the date is selected automatically so that around 10 branches are included.
+
            <b>-l</b>, <b>--list-commits</b>
               When printing the discovered tree, additionally lists the messages of commits introduced on each branch
               (as for `git machete status`).
+
            <b>-r</b>, <b>--roots=<branch1,...></b>
               Comma-separated list of branches that should be considered roots of trees of branch dependencies.
               If not present, `master` is assumed to be a root. Note that in the process of discovery,
               certain other branches can also be additionally deemed to be roots as well.
+
            <b>-y</b>, <b>--yes</b>
               Don't ask for confirmation before saving the newly-discovered tree.
               Mostly useful in scripts; not recommended for manual use.
@@ -1192,6 +1207,7 @@ long_docs: Dict[str, str] = {
         but there is also dedicated `squash` command that achieves the same goal without running `git rebase`.
 
         <b>Options:</b>
+
            <b>-f</b>, <b>--fork-point=<fork-point-commit></b>
               Specifies the alternative fork point commit after which the rebased part of history is meant to start.
 
@@ -1270,25 +1286,32 @@ long_docs: Dict[str, str] = {
         See help for `traverse` for more details on the qualifiers.
 
         <b>Options:</b>
+
            <b>-d</b>, <b>--down-fork-point=<down-fork-point-commit></b>
               If updating by rebase, specifies the alternative fork point for downstream branches for the operation.
               `git machete fork-point` overrides for downstream branches are recommended over use of this option.
               See also doc for `--fork-point` option in `git machete help reapply` and `git machete help update`.
               Not allowed if updating by merge.
+
            <b>--delete</b>
               Delete the branches after sliding them out.
+
            <b>-M</b>, <b>--merge</b>
               Update the downstream branch by merge rather than by rebase.
+
            <b>-n</b>
               If updating by rebase, equivalent to `--no-interactive-rebase`.
               If updating by merge, equivalent to `--no-edit-merge`.
+
            <b>--no-edit-merge</b>
               If updating by merge, skip opening the editor for merge commit message while doing
               `git merge` (that is, pass `--no-edit` flag to the underlying `git merge`).
               Not allowed if updating by rebase.
+
            <b>--no-interactive-rebase</b>
               If updating by rebase, run `git rebase` in non-interactive mode (without `-i/--interactive` flag).
               Not allowed if updating by merge.
+
            <b>--removed-from-remote</b>
  
               Slide out managed branches whose remote tracking branches have been deleted and that have no downstreams.
@@ -1325,6 +1348,7 @@ long_docs: Dict[str, str] = {
         For more complex scenarios that require rewriting the history of current branch, see `reapply` and `update`.
 
         <b>Options:</b>
+
            <b>-f</b>, <b>--fork-point=<fork-point-commit></b>
               Specifies the alternative fork point commit after which the squashed part of history is meant to start.
    """,
@@ -1399,16 +1423,21 @@ long_docs: Dict[str, str] = {
         </dim>
 
         <b>Options:</b>
+
            <b>--color=WHEN</b>
               Colorize the output; WHEN can be `always`, `auto` (default: colorize only if stdout is a terminal), or `never`.
+
            <b>-l</b>, <b>--list-commits</b>
               Additionally list the commits introduced on each branch.
+
            <b>-L</b>, <b>--list-commits-with-hashes</b>
               Additionally list the short hashes and messages of commits introduced on each branch.
+
            <b>--no-detect-squash-merges</b>
               <b>Deprecated</b>, use `--squash-merge-detection=none` instead.
               Only consider strict (fast-forward or 2-parent) merges, rather than rebase/squash merges,
               when detecting if a branch is merged into its upstream (parent).
+
            <b>--squash-merge-detection=MODE</b>
               Specify the mode for detection of rebase/squash merges (grey edges).
               `MODE` can be `none` (fastest, no squash merges are detected), `simple` (default) or `exact` (slowest).
@@ -1518,58 +1547,77 @@ long_docs: Dict[str, str] = {
         when the current user is NOT the author of the PR/MR associated with that branch.
 
         <b>Options:</b>
+
            <b>-F</b>, <b>--fetch</b>
               Fetch the remotes of all managed branches at the beginning of traversal (no `git pull` involved, only `git fetch`).
+
            <b>-H</b>, <b>--sync-github-prs</b>
               Retarget the PR if it exists for the given branch and its base branch in GitHub is different than the upstream in machete file
+
            <b>-L</b>, <b>--sync-gitlab-mrs</b>
               Retarget the MR if it exists for the given branch and its target branch in GitLab is different than the upstream in machete file
+
            <b>-l</b>, <b>--list-commits</b>
               When printing the status, additionally list the messages of commits introduced on each branch.
+
            <b>-M</b>, <b>--merge</b>
               Update by merge rather than by rebase.
+
            <b>-n</b>
               If updating by rebase, equivalent to `--no-interactive-rebase`. If updating by merge, equivalent to `--no-edit-merge`.
+
            <b>--no-detect-squash-merges</b>
               <b>Deprecated</b>, use `--squash-merge-detection=none` instead.
               Only consider strict (fast-forward or 2-parent) merges, rather than rebase/squash merges,
               when detecting if a branch is merged into its upstream (parent).
+
            <b>--no-edit-merge</b>
               If updating by merge, skip opening the editor for merge commit message while doing `git merge`
               (that is, pass `--no-edit` flag to the underlying `git merge`). Not allowed if updating by rebase.
+
            <b>--no-interactive-rebase</b>
               If updating by rebase, run `git rebase` in non-interactive mode (without `-i/--interactive` flag).
               Not allowed if updating by merge.
+
            <b>--no-push</b>
               Do not push any (neither tracked nor untracked) branches to remote, re-enable via `--push`.
+
            <b>--no-push-untracked</b>
               Do not push untracked branches to remote, re-enable via `--push-untracked`.
+
            <b>--push</b>
               Push all (both tracked and untracked) branches to remote — default behavior. Default behavior can be changed
               by setting git configuration key `git config machete.traverse.push false`.
               Configuration key value can be overridden by the presence of the flag.
+
            <b>--push-untracked</b>
               Push untracked branches to remote.
+
            <b>--return-to=WHERE</b>
               Specifies the branch to return after traversal is successfully completed;
               WHERE can be `here` (the current branch at the moment when traversal starts), `nearest-remaining`
               (nearest remaining branch in case the `here` branch has been slid out by the traversal) or
               `stay` (the default — just stay wherever the traversal stops). Note: when user quits by `q`/`yq`
               or when traversal is stopped because one of git actions fails, the behavior is always `stay`.
+
            <b>--squash-merge-detection=MODE</b>
               Specifies the mode for detection of rebase/squash merges (grey edges).
               `MODE` can be `none` (fastest, no squash merges are detected), `simple` (default) or `exact` (slowest).
               See the below paragraph on `machete.squashMergeDetection` git config key for more details.
+
            <b>--start-from=WHERE</b>
               Specifies the branch to start the traversal from; WHERE can be `here`
               (the default — current branch, must be managed by git machete), `root` (root branch of the current branch,
               as in `git machete show root`) or `first-root` (first listed managed branch).
+
            <b>-w</b>, <b>--whole</b>
               Equivalent to `-n --start-from=first-root --return-to=nearest-remaining`;
               useful for quickly traversing & syncing all branches (rather than doing more fine-grained operations on the
               local section of the branch tree).
+
            <b>-W</b>
               Equivalent to `--fetch --whole`; useful for even more automated traversal of all branches.
+
            <b>-y</b>, <b>--yes</b>
               Don't ask for any interactive input, including confirmation of rebase/push/pull. Implies `-n`.
 
@@ -1626,17 +1674,22 @@ long_docs: Dict[str, str] = {
         If updating by merge, merges the upstream (parent) branch into the current branch.
 
         <b>Options:</b>
+
            <b>-f</b>, <b>--fork-point=<fork-point-commit></b>
               If updating by rebase, specifies the alternative fork point commit after which the rebased
               part of history is meant to start. Not allowed if updating by merge.
+
            <b>-M</b>, <b>--merge</b>
               Update by merge rather than by rebase.
+
            <b>-n</b>
               If updating by rebase, equivalent to `--no-interactive-rebase`.
               If updating by merge, equivalent to `--no-edit-merge`.
+
            <b>--no-edit-merge</b>
               If updating by merge, skip opening the editor for merge commit message while doing `git merge`
               (that is, pass `--no-edit` flag to underlying `git merge`). Not allowed if updating by rebase.
+
            <b>--no-interactive-rebase</b>
               If updating by rebase, run `git rebase` in non-interactive mode (without `-i/--interactive` flag).
               Not allowed if updating by merge.
diff --git a/git_machete/git_operations.py b/git_machete/git_operations.py
index ab22d66..47235d3 100644
--- a/git_machete/git_operations.py
+++ b/git_machete/git_operations.py
@@ -557,6 +557,9 @@ class GitContext:
         # As of git 2.24.1, this is how 'cmd_rebase()' in builtin/rebase.c checks whether am is in progress.
         return os.path.isfile(self.get_worktree_git_subpath("rebase-apply", "applying"))
 
+    def is_bisect_in_progress(self) -> bool:
+        return os.path.isfile(self.get_worktree_git_subpath("BISECT_START"))
+
     def is_cherry_pick_in_progress(self) -> bool:
         return os.path.isfile(self.get_worktree_git_subpath("CHERRY_PICK_HEAD"))
 
@@ -718,10 +721,18 @@ class GitContext:
         if switch_head:
             self._run_git("checkout", branch, flush_caches=True)
 
+    def get_currently_bisected_branch_or_none(self) -> Optional[LocalBranchShortName]:
+        bisect_start_file = self.get_worktree_git_subpath("BISECT_START")
+        if not os.path.exists(bisect_start_file):
+            return None
+        with open(bisect_start_file, "r") as f:
+            raw = f.read().strip()
+            return LocalBranchShortName.of(raw)
+
     # Note: while rebase is ongoing, the repository is always in a detached HEAD state,
     # so we need to extract the name of the currently rebased branch from the rebase-specific internals
     # rather than rely on 'git symbolic-ref HEAD' (i.e. the contents of .git/HEAD).
-    def get_currently_rebased_branch_or_none(self) -> Optional[LocalBranchShortName]:  # utils/private
+    def get_currently_rebased_branch_or_none(self) -> Optional[LocalBranchShortName]:
         # https://stackoverflow.com/questions/3921409
 
         head_name_file = None
@@ -760,6 +771,9 @@ class GitContext:
         if self.is_am_in_progress():
             raise UnderlyingGitException(
                 "`git am` session in progress. Conclude `git am` first with `git am --continue` or `git am --abort`.")
+        if self.is_bisect_in_progress():
+            raise UnderlyingGitException(
+                "Bisecting in progress. Conclude the bisecting first with `git bisect reset`.")
         if self.is_cherry_pick_in_progress():
             raise UnderlyingGitException(
                 "Cherry pick in progress. Conclude the cherry pick first with `git cherry-pick --continue` or `git cherry-pick --abort`.")
diff --git a/git_machete/utils.py b/git_machete/utils.py
index 5eb65cf..f73a3e2 100644
--- a/git_machete/utils.py
+++ b/git_machete/utils.py
@@ -285,7 +285,9 @@ def slurp_file(path: str) -> str:
 def is_terminal_fully_fledged() -> bool:
     try:
         stdout = popen_cmd('tput', 'colors')[1]
-        number_of_supported_colors = int(stdout)  # in CI, this line is only covered by tests on macOS
+        # In CI, this line is only covered by tests on macOS, which don't run on PRs by default.
+        # Let's skip to keep coverage results consistent between develop/master and PRs.
+        number_of_supported_colors = int(stdout)  # pragma: no cover
     except Exception:
         # If we cannot retrieve the number of supported colors, let's defensively assume it's low.
         number_of_supported_colors = 8
diff --git a/requirements/coverage.txt b/requirements/coverage.txt
index 74a6be2..91fca5a 100644
--- a/requirements/coverage.txt
+++ b/requirements/coverage.txt
@@ -1,9 +1,9 @@
 coverage==6.2; python_version <= '3.6'
 coverage==7.2.7; python_version >= '3.7' and python_version < '3.8'
 coverage==7.6.1; python_version >= '3.8' and python_version < '3.9'
-coverage==7.8.2; python_version >= '3.9'
+coverage==7.9.2; python_version >= '3.9'
 
 pytest-cov==4.0.0; python_version <= '3.6'
 pytest-cov==4.1.0; python_version >= '3.7' and python_version < '3.8'
 pytest-cov==5.0.0; python_version >= '3.8' and python_version < '3.9'
-pytest-cov==6.1.1; python_version >= '3.9'
+pytest-cov==6.2.1; python_version >= '3.9'
diff --git a/requirements/flake8-check.txt b/requirements/flake8-check.txt
index 8cd7ec0..c804c6a 100644
--- a/requirements/flake8-check.txt
+++ b/requirements/flake8-check.txt
@@ -1,4 +1,4 @@
-flake8==7.2.0
+flake8==7.3.0
 flake8-plugin-utils==1.3.3
 flake8-unused-arguments==0.0.13
 flake8-use-fstring==1.4
diff --git a/requirements/mypy.txt b/requirements/mypy.txt
index 7d16316..6d750ac 100644
--- a/requirements/mypy.txt
+++ b/requirements/mypy.txt
@@ -1,4 +1,4 @@
 mypy==0.971; python_version <= '3.6'
 mypy==1.4.1; python_version >= '3.7' and python_version < '3.8'
 mypy==1.14.1; python_version >= '3.8' and python_version < '3.9'
-mypy==1.16.0; python_version >= '3.9'
+mypy==1.16.1; python_version >= '3.9'
diff --git a/requirements/py-docs.txt b/requirements/py-docs.txt
index fc27b54..281d18f 100644
--- a/requirements/py-docs.txt
+++ b/requirements/py-docs.txt
@@ -1,3 +1,3 @@
 docutils==0.21.2
-pygments==2.19.1
+pygments==2.19.2
 beautifulsoup4==4.13.4
diff --git a/requirements/typos.txt b/requirements/typos.txt
index 0f1c453..7936696 100644
--- a/requirements/typos.txt
+++ b/requirements/typos.txt
@@ -1,1 +1,1 @@
-typos==1.32.0
+typos==1.34.0
diff --git a/tox.ini b/tox.ini
index 44c18cb..69fd78b 100644
--- a/tox.ini
+++ b/tox.ini
@@ -16,6 +16,7 @@ install_command =
   pip install {opts} {packages}
 deps =
   -r{[requirements]dir}/testenv.txt
+  -r{[requirements]dir}/testenv-runtime.txt
 allowlist_externals = mkdir
 commands =
   mkdir -p test-results/
@@ -91,6 +92,7 @@ description = "Check the test coverage of the code"
 deps =
   -r{[requirements]dir}/coverage.txt
   -r{[requirements]dir}/testenv.txt
+  -r{[requirements]dir}/testenv-runtime.txt
 passenv = PYTHON_VERSION
 allowlist_externals = cp
 commands =


Test patch:
diff --git a/requirements/testenv-runtime.txt b/requirements/testenv-runtime.txt
new file mode 100644
index 0000000..c334e32
--- /dev/null
+++ b/requirements/testenv-runtime.txt
@@ -0,0 +1,6 @@
+pytest-clarity==1.0.1
+
+pytest-xdist==2.4.0; python_version <= '3.6'
+pytest-xdist==3.5.0; python_version >= '3.7' and python_version < '3.8'
+pytest-xdist==3.6.1; python_version >= '3.8' and python_version < '3.9'
+pytest-xdist==3.8.0; python_version >= '3.9'
diff --git a/requirements/testenv.txt b/requirements/testenv.txt
index f9479fb..e09bb75 100644
--- a/requirements/testenv.txt
+++ b/requirements/testenv.txt
@@ -1,15 +1,9 @@
 pytest==7.0.1; python_version <= '3.6'
 pytest==7.4.4; python_version >= '3.7' and python_version < '3.8'
-pytest==8.3.5; python_version >= '3.8'
-
-pytest-clarity==1.0.1
+pytest==8.3.5; python_version >= '3.8' and python_version < '3.9'
+pytest==8.4.1; python_version >= '3.9'
 
 pytest-mock==3.6.1; python_version <= '3.6'
 pytest-mock==3.11.1; python_version >= '3.7' and python_version < '3.8'
 pytest-mock==3.14.0; python_version >= '3.8' and python_version < '3.9'
 pytest-mock==3.14.1; python_version >= '3.9'
-
-pytest-xdist==2.4.0; python_version <= '3.6'
-pytest-xdist==3.5.0; python_version >= '3.7' and python_version < '3.8'
-pytest-xdist==3.6.1; python_version >= '3.8' and python_version < '3.9'
-pytest-xdist==3.7.0; python_version >= '3.9'
diff --git a/tests/mockers_git_repository.py b/tests/mockers_git_repository.py
index 1a97866..2aa5c03 100644
--- a/tests/mockers_git_repository.py
+++ b/tests/mockers_git_repository.py
@@ -43,7 +43,9 @@ def check_out(branch: str) -> None:
 
 
 counter = 0
-def next_integer() -> int:  # noqa: E302
+
+
+def next_integer() -> int:
     global counter
     counter += 1
     return counter
diff --git a/tests/test_slide_out.py b/tests/test_slide_out.py
index ca99452..6f8b9c7 100644
--- a/tests/test_slide_out.py
+++ b/tests/test_slide_out.py
@@ -1,3 +1,4 @@
+
 import pytest
 from pytest_mock import MockerFixture
 
@@ -9,7 +10,7 @@ from .mockers import (assert_failure, assert_success,
                       set_file_executable, write_to_file)
 from .mockers_git_repository import (amend_commit, check_out, commit,
                                      create_repo, create_repo_with_remote,
-                                     delete_remote_branch,
+                                     delete_remote_branch, get_current_branch,
                                      get_current_commit_hash,
                                      get_local_branches, new_branch, push)
 
@@ -169,6 +170,27 @@ class TestSlideOut(BaseTest):
             """,
         )
 
+    def test_slide_out_branch_other_than_current(self) -> None:
+        create_repo()
+        new_branch('branch-0')
+        commit()
+        new_branch('branch-1')
+        commit()
+        new_branch('branch-2')
+        commit()
+
+        body: str = \
+            """
+            branch-0
+                branch-1
+                branch-2
+            """
+        rewrite_branch_layout_file(body)
+
+        launch_command("slide-out", "branch-1")
+        # Branch should not be changed by slide-out if the current branch has NOT been slid out
+        assert get_current_branch() == "branch-2"
+
     def test_slide_out_with_post_slide_out_hook(self) -> None:
         create_repo()
         new_branch('branch-0')
@@ -435,6 +457,7 @@ class TestSlideOut(BaseTest):
         new_branch('has_downstream')
         commit()
         push()
+        delete_remote_branch('origin/has_downstream')
         new_branch('downstream')
         commit()
         check_out('main')
@@ -442,7 +465,11 @@ class TestSlideOut(BaseTest):
         commit()
         push()
         delete_remote_branch('origin/should_be_pruned')
-        delete_remote_branch('origin/has_downstream')
+        check_out('main')
+        new_branch('with_qualifier')
+        commit()
+        push()
+        delete_remote_branch('origin/with_qualifier')
         check_out('main')
 
         body: str = \
@@ -453,14 +480,24 @@ class TestSlideOut(BaseTest):
                 has_downstream
                     downstream
                 should_be_pruned PR #123
+                with_qualifier slide-out=no
             """
         rewrite_branch_layout_file(body)
 
         assert_success(
             ['slide-out', '--removed-from-remote', '--delete'],
-            "Sliding out should_be_pruned\nDeleting branch should_be_pruned...\n")
-
-        assert read_branch_layout_file() == "main\n    unpushed\n    not_deleted_remotely\n    has_downstream\n        downstream\n"
+            "Sliding out should_be_pruned\n"
+            "Skipping with_qualifier as it's marked as slide-out=no\n"
+            "Deleting branch should_be_pruned...\n")
+
+        assert read_branch_layout_file().splitlines() == [
+            "main",
+            "    unpushed",
+            "    not_deleted_remotely",
+            "    has_downstream",
+            "        downstream",
+            "    with_qualifier slide-out=no"
+        ]
 
         expected_status_output = (
             """
@@ -471,8 +508,10 @@ class TestSlideOut(BaseTest):
               o-not_deleted_remotely
               |
               o-has_downstream (untracked)
-                |
-                o-downstream (untracked)
+              | |
+              | o-downstream (untracked)
+              |
+              o-with_qualifier  slide-out=no (untracked)
             """
         )
         assert_success(['status'], expected_status_output)
@@ -487,7 +526,13 @@ class TestSlideOut(BaseTest):
         delete_remote_branch('origin/not_deleted_remotely')
         launch_command('slide-out', '--removed-from-remote', '--verbose')
 
-        assert read_branch_layout_file() == "main\n    unpushed\n    has_downstream\n        downstream\n"
+        assert read_branch_layout_file().splitlines() == [
+            "main",
+            "    unpushed",
+            "    has_downstream",
+            "        downstream",
+            "    with_qualifier slide-out=no"
+        ]
         branches = get_local_branches()
         assert 'not_deleted_remotely' in branches
 
diff --git a/tests/test_status.py b/tests/test_status.py
index 05da99e..45b2964 100644
--- a/tests/test_status.py
+++ b/tests/test_status.py
@@ -811,6 +811,28 @@ class TestStatus(BaseTest):
 
         execute("git revert --abort")
 
+        # BISECT
+
+        commit("Another commit message.")
+        commit("Yet another commit message.")
+        execute("git bisect start")
+        execute("git bisect bad HEAD")
+        execute("git bisect good HEAD~3")
+
+        expected_status_output = (
+            """
+            master
+            |
+            | Some commit message.
+            | Another commit message.
+            | Yet another commit message.
+            o-BISECTING develop *
+            """
+        )
+        assert_success(['status', '-l'], expected_status_output)
+
+        execute("git bisect reset")
+
     def test_status_no_fork_point_for_child_branch(self) -> None:
         create_repo()
         new_branch("master")
diff --git a/tests/test_traverse_github.py b/tests/test_traverse_github.py
index c73f2e4..2471ebd 100644
--- a/tests/test_traverse_github.py
+++ b/tests/test_traverse_github.py
@@ -285,11 +285,24 @@ class TestTraverseGitHub(BaseTest):
             """
         )
 
+        check_out("develop")
         self.patch_symbol(mocker, 'builtins.input', mock_input_returning("yq"))
         assert_success(
             ["traverse", "--sync-github-prs"],
             """
             Checking for open GitHub PRs... OK
+            Checking out allow-ownership-link
+
+              develop
+              |
+              o-allow-ownership-link *
+                |
+                o-build-chain (untracked)
+                  |
+                  o-call-ws  PR #1
+                    |
+                    o-drop-constraint (untracked)
+
             Branch allow-ownership-link does not have a PR in GitHub.
             Create a PR from allow-ownership-link to develop? (y, d[raft], N, q, yq)
             Checking if base branch develop exists in origin remote... YES
diff --git a/tests/test_update.py b/tests/test_update.py
index 7dd5b90..a885117 100644
--- a/tests/test_update.py
+++ b/tests/test_update.py
@@ -387,3 +387,16 @@ class TestUpdate(BaseTest):
                        expected_type=UnderlyingGitException)
 
         execute("git revert --abort")
+
+        # BISECT
+
+        commit()
+        execute("git bisect start")
+        execute("git bisect bad HEAD")
+        execute("git bisect good HEAD~2")
+
+        assert_failure(["update"],
+                       "Bisecting in progress. Conclude the bisecting first with git bisect reset.",
+                       expected_type=UnderlyingGitException)
+
+        execute("git bisect reset")