Repository: VirtusLab/git-machete
Pull request #1103 (2023-11-23 15:06:35)

Add `--removed-from-remote` flag to `slide-out`
Folllow-up to #1094 

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/VirtusLab/git-machete/pull/1103?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) Report
Attention: `2 lines` in your changes are missing coverage. Please review.
> Comparison is base [(`393d434`)](https://app.codecov.io/gh/VirtusLab/git-machete/commit/393d4341eea84d51afdb1c05c1073c47e56bf62e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) 98.62% compared to head [(`e029bda`)](https://app.codecov.io/gh/VirtusLab/git-machete/pull/1103?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) 98.64%.

| [Files](https://app.codecov.io/gh/VirtusLab/git-machete/pull/1103?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) | Patch % | Lines |
|---|---|---|
| [git\_machete/git\_operations.py](https://app.codecov.io/gh/VirtusLab/git-machete/pull/1103?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab#diff-Z2l0X21hY2hldGUvZ2l0X29wZXJhdGlvbnMucHk=) | 81.81% | [1 Missing and 1 partial :warning: ](https://app.codecov.io/gh/VirtusLab/git-machete/pull/1103?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) |

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##           develop    #1103      +/-   ##
===========================================
+ Coverage    98.62%   98.64%   +0.01%     
===========================================
  Files           12       12              
  Lines         3283     3315      +32     
  Branches       741      751      +10     
===========================================
+ Hits          3238     3270      +32     
  Misses          28       28              
  Partials        17       17              
```



</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/VirtusLab/git-machete/pull/1103?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab).


Patch:
diff --git a/PACKAGES.md b/PACKAGES.md
index d442ffb..1bb27ce 100644
--- a/PACKAGES.md
+++ b/PACKAGES.md
@@ -13,7 +13,7 @@ the **edge-most** channel that corresponds to the **released** versions of git-m
 | GitHub Releases (rpm)                               | ![GitHub latest release](https://img.shields.io/github/v/release/VirtusLab/git-machete?label=)                  | [**GitHub releases**](https://github.com/VirtusLab/git-machete/releases)                                                                                                                                                  |                                                                                                                                            |                                                                              | Ourselves                                                                                   | Our CI on `master` builds                                                                                                                                                             | -                                     | ![GitHub releases total downloads](https://img.shields.io/github/downloads/VirtusLab/git-machete/total)[^2]                                                                                                                                                                                                           |
 | Homebrew                                            | ![homebrew formula](https://img.shields.io/homebrew/v/git-machete.svg?label=)                                   | [**Formula**](https://formulae.brew.sh/formula/git-machete)                                                                                                                                                               | [Formula source](https://github.com/Homebrew/homebrew-core/blob/master/Formula/g/git-machete.rb) (points to PyPI)                          | [PRs](https://github.com/Homebrew/homebrew-core/pulls?q=is%3Apr+git-machete) | Homebrew maintainers                                                                        | Our CI on `master` builds (opens formula-bump PR), then homebrew-core GitHub Actions                                                                                                  | bash, fish, zsh + man page            | ![homebrew formula monthly downloads](https://img.shields.io/homebrew/installs/dm/git-machete.svg) <br/> ![homebrew formula quarterly downloads](https://img.shields.io/homebrew/installs/dq/git-machete.svg) <br/> ![homebrew formula yearly downloads](https://img.shields.io/homebrew/installs/dy/git-machete.svg) |
 | [LiGurOS (Gentoo fork)](https://liguros.gitlab.io/) | ![LiGurOS develop package](https://repology.org/badge/version-for-repo/liguros_develop/git-machete.svg?header=) |                                                                                                                                                                                                                           | [ebuild](https://gitlab.com/liguros/liguros-repo/-/tree/develop/dev-vcs/git-machete) (points to GitHub)                                    |                                                                              | LiGurOS package maintainers                                                                 | LiGurOS package maintainers                                                                                                                                                           | bash, fish, zsh                       |                                                                                                                                                                                                                                                                                                                       |
-| nixpkgs                                             | ![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/git-machete.svg?header=)   | [**nixpkgs unstable package**](https://search.nixos.org/packages?channel=unstable&show=git-machete&query=git-machete)                                                                                                     | [Expression](https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/version-management/git-machete/default.nix) (points to GitHub) | [PRs](https://github.com/NixOS/nixpkgs/pulls?q=is%3Apr+git-machete)          | nixpkgs maintainers, incl. [@blitz (Julian Stecklina)](https://github.com/blitz)            | [@r-ryantm bot](https://github.com/r-ryantm) ([logs](https://r.ryantm.com/log/updatescript/git-machete/))[^3]                                                                         | bash, fish, zsh                       |                                                                                                                                                                                                                                                                                                                       |
+| nixpkgs                                             | ![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/git-machete.svg?header=)   | [**nixpkgs unstable package**](https://search.nixos.org/packages?channel=unstable&show=git-machete&query=git-machete)                                                                                                     | [Expression](https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/version-management/git-machete/default.nix) (points to GitHub) | [PRs](https://github.com/NixOS/nixpkgs/pulls?q=is%3Apr+git-machete)          | nixpkgs maintainers, incl. [@blitz (Julian Stecklina)](https://github.com/blitz)            | [@r-ryantm bot](https://github.com/r-ryantm) ([logs](https://r.ryantm.com/log/git-machete/))[^3]                                                                                      | bash, fish, zsh                       |                                                                                                                                                                                                                                                                                                                       |
 | PyPI                                                | ![PyPI package](https://img.shields.io/pypi/v/git-machete.svg?label=)                                           | [**PyPI package**](https://pypi.org/project/git-machete) <br/> [Test PyPI package](https://test.pypi.org/project/git-machete)                                                                                             |                                                                                                                                            |                                                                              | Ourselves                                                                                   | Our CI on `master` builds[^4]                                                                                                                                                         | -                                     | ![PyPI daily downloads](https://img.shields.io/pypi/dd/git-machete.svg) <br/> ![PyPI weekly downloads](https://img.shields.io/pypi/dw/git-machete.svg) <br/> ![PyPI monthly downloads](https://img.shields.io/pypi/dm/git-machete.svg) <br/> ![PyPI wheel?](https://img.shields.io/pypi/wheel/git-machete.svg)        |
 | PPA (Ubuntu)                                        | [^5]                                                                                                            | [**PPA packages**](https://launchpad.net/~virtuslab/+archive/ubuntu/git-machete/+packages)                                                                                                                                | [debian/ folder (in this repo)](debian)                                                                                                    |                                                                              | Ourselves                                                                                   | Our CI on `master` builds (uploads source package), then Launchpad build system (see [builds](https://launchpad.net/~virtuslab/+archive/ubuntu/git-machete/+builds?&build_state=all)) | -                                     |                                                                                                                                                                                                                                                                                                                       |
 | Read the Docs                                       | ![Read the Docs](https://readthedocs.org/projects/git-machete/badge/?version=stable)                            | [**Read the Docs (stable)**](https://git-machete.readthedocs.io/en/stable) <br/> [Read the Docs (latest)](https://git-machete.readthedocs.io/en/latest) <br/> [Admin page](https://readthedocs.org/projects/git-machete/) |                                                                                                                                            |                                                                              | Ourselves                                                                                   | Read the Docs itself (see [builds](https://readthedocs.org/projects/git-machete/builds)), triggered by GitHub webhook [^6]                                                            | N/A                                   |                                                                                                                                                                                                                                                                                                                       |
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index fff94f8..b215f3c 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,7 +1,8 @@
 # Release notes
 
-## New in git-machete 3.20.1
+## New in git-machete 3.21.0
 
+- added: `--removed-from-remote` flag to `git machete slide-out` (contributed by @raylu)
 - 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
diff --git a/completion/git-machete.completion.bash b/completion/git-machete.completion.bash
index 9d8b6f9..fc691d0 100644
--- a/completion/git-machete.completion.bash
+++ b/completion/git-machete.completion.bash
@@ -26,7 +26,7 @@ _git_machete() {
   local github_checkout_prs_opts="--all --by= --mine"
   local github_retarget_pr_opts="-b --branch= --ignore-if-missing"
   local reapply_opts="-f --fork-point="
-  local slide_out_opts="-d --down-fork-point= --delete -M --merge -n --no-edit-merge --no-interactive-rebase"
+  local slide_out_opts="-d --down-fork-point= --delete -M --merge -n --no-edit-merge --no-interactive-rebase --removed-from-remote"
   local squash_opts="-f --fork-point="
   local status_opts="--color= -L --list-commits-with-hashes -l --list-commits --no-detect-squash-merges"
   local traverse_opts="-F --fetch -l --list-commits -M --merge -n --no-detect-squash-merges --no-edit-merge --no-interactive-rebase --no-push --no-push-untracked --push --push-untracked --return-to= --start-from= -w --whole -W -y --yes"
diff --git a/completion/git-machete.completion.zsh b/completion/git-machete.completion.zsh
index 774494c..75b9f2f 100644
--- a/completion/git-machete.completion.zsh
+++ b/completion/git-machete.completion.zsh
@@ -117,11 +117,12 @@ _git-machete() {
           _arguments \
             '*:: :__git_machete_list_slidable' \
             '(-d --down-fork-point)'{-d,--down-fork-point=}'[If updating by rebase, specify fork point commit after which the rebased part of history of the downstream branch is meant to start]: :__git_references' \
-            '(--delete)'--delete'[Delete slid-out branches]' \
+            '(--delete)'--delete'[Delete branches after sliding them out]' \
             '(-M --merge)'{-M,--merge}'[Update by merge rather than by rebase]' \
             '(-n)'-n'[If updating by rebase, equivalent to --no-interactive-rebase. If updating by merge, equivalent to --no-edit-merge]' \
             '(--no-edit-merge)'--no-edit-merge'[If updating by merge, pass --no-edit flag to underlying git merge]' \
             '(--no-interactive-rebase)'--no-interactive-rebase'[If updating by rebase, do NOT pass --interactive flag to underlying git rebase]' \
+            '(--removed-from-remote)'--removed-from-remote'[Slide out all branches removed from the remote]' \
             "${common_flags[@]}"
           ;;
         (squash)
diff --git a/completion/git-machete.fish b/completion/git-machete.fish
index 6640995..548a83b 100644
--- a/completion/git-machete.fish
+++ b/completion/git-machete.fish
@@ -162,12 +162,13 @@ complete -c git-machete -n "__fish_seen_subcommand_from show; and not __fish_see
 # git machete slide-out
 complete -c git-machete -n "not __fish_seen_subcommand_from $__machete_commands" -f -a slide-out -d 'Slide out the current branch and sync its downstream (child) branches with its upstream (parent) branch via rebase or merge'
 complete -c git-machete -n "__fish_seen_subcommand_from slide-out"               -f -a '(__machete_slidable_branches)'
-complete -c git-machete -n "__fish_seen_subcommand_from slide-out; and not __fish_seen_subcommand_from --merge -M --down-fork-point -d"                                                        -x -l down-fork-point -s d -a '(__fish_git_refs)' -d 'If updating by rebase, specifies the alternative fork point for downstream branches for the operation. Not allowed if updating by merge'
-complete -c git-machete -n "__fish_seen_subcommand_from slide-out; and not __fish_seen_subcommand_from --delete"                                                                               -f -l delete                                      -d 'Delete branches from git'
-complete -c git-machete -n "__fish_seen_subcommand_from slide-out; and not __fish_seen_subcommand_from --merge -M --down-fork-point -d"                                                        -f -l merge           -s M                        -d 'Update the downstream branch by merge rather than by rebase'
-complete -c git-machete -n "__fish_seen_subcommand_from slide-out; and not __fish_seen_subcommand_from -n --no-edit-merge --no-interactive-rebase"                                             -f                    -s n                        -d 'If updating by rebase, equivalent to --no-interactive-rebase. If updating by merge, equivalent to --no-edit-merge'
-complete -c git-machete -n "__fish_seen_subcommand_from slide-out; and not __fish_seen_subcommand_from -n --no-edit-merge --no-interactive-rebase; and __fish_seen_subcommand_from --merge -M" -f -l no-edit-merge                               -d 'If updating by merge, skip opening the editor for merge commit message while doing git merge (i.e. pass --no-edit flag to underlying git merge). Not allowed if updating by rebase'
-complete -c git-machete -n "__fish_seen_subcommand_from slide-out; and not __fish_seen_subcommand_from -n --no-edit-merge --no-interactive-rebase -M --merge"                                  -f -l no-interactive-rebase                       -d 'If updating by rebase, run git rebase in non-interactive mode (without -i/--interactive flag). Not allowed if updating by merge'
+complete -c git-machete -n "__fish_seen_subcommand_from slide-out; and not __fish_seen_subcommand_from --removed-from-remote"                                                        -f -l removed-from-remote                         -d 'Slide out all branches removed from the remote'
+complete -c git-machete -n "__fish_seen_subcommand_from slide-out; and not __fish_seen_subcommand_from --removed-from-remote --merge -M --down-fork-point -d"                        -x -l down-fork-point -s d -a '(__fish_git_refs)' -d 'If updating by rebase, specifies the alternative fork point for downstream branches for the operation. Not allowed if updating by merge'
+complete -c git-machete -n "__fish_seen_subcommand_from slide-out; and not __fish_seen_subcommand_from --delete"                                                                     -f -l delete                                      -d 'Delete branches after sliding them out'
+complete -c git-machete -n "__fish_seen_subcommand_from slide-out; and not __fish_seen_subcommand_from --removed-from-remote --merge -M --down-fork-point -d"                        -f -l merge           -s M                        -d 'Update the downstream branch by merge rather than by rebase'
+complete -c git-machete -n "__fish_seen_subcommand_from slide-out; and not __fish_seen_subcommand_from --removed-from-remote -n --no-edit-merge --no-interactive-rebase"             -f                    -s n                        -d 'If updating by rebase, equivalent to --no-interactive-rebase. If updating by merge, equivalent to --no-edit-merge'
+complete -c git-machete -n "__fish_seen_subcommand_from slide-out; and not __fish_seen_subcommand_from --removed-from-remote -n --no-edit-merge --no-interactive-rebase"             -f -l no-edit-merge                               -d 'If updating by merge, skip opening the editor for merge commit message while doing git merge (i.e. pass --no-edit flag to underlying git merge). Not allowed if updating by rebase'
+complete -c git-machete -n "__fish_seen_subcommand_from slide-out; and not __fish_seen_subcommand_from --removed-from-remote -M --merge -n --no-edit-merge --no-interactive-rebase"  -f -l no-interactive-rebase                       -d 'If updating by rebase, run git rebase in non-interactive mode (without -i/--interactive flag). Not allowed if updating by merge'
 
 # git machete squash
 complete -c git-machete -n "not __fish_seen_subcommand_from $__machete_commands" -f -a squash -d 'Squash the unique history of the current branch into a single commit'
diff --git a/docs/man/git-machete.1 b/docs/man/git-machete.1
index 8d553ec..6ecd00e 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" "Oct 22, 2023" "" "git-machete"
+.TH "GIT-MACHETE" "1" "Nov 25, 2023" "" "git-machete"
 .SH NAME
-git-machete \- git-machete 3.20.1
+git-machete \- git-machete 3.21.0
 .sp
 git machete is a robust tool that \fBsimplifies your git workflows\fP\&.
 .sp
@@ -1503,14 +1503,19 @@ since all branches are usually meant to be ultimately merged to one of those.
 .sp
 .nf
 .ft C
+git machete slide\-out \-\-removed\-from\-remote [\-\-delete]
 git machete slide\-out [\-d|\-\-down\-fork\-point=<down\-fork\-point\-commit>] [\-\-delete] [\-M|\-\-merge] [\-n|\-\-no\-edit\-merge|\-\-no\-interactive\-rebase] [<branch> [<branch> [<branch> ...]]]
 .ft P
 .fi
 .UNINDENT
 .UNINDENT
 .sp
-Removes the given branch (or multiple branches) from the branch layout. If no branch has been specified, current branch is removed.
-Then synchronizes the downstream (child) branches of the last specified branch on the top of the upstream (parent) branch of the first specified branch.
+Removes the given branch (or multiple branches) from the branch layout.
+If no branch has been specified, current branch is slid out.
+If \fB\-\-removed\-from\-remote\fP is specified, all branches that have been removed from the remote are slid out instead.
+.sp
+Also, if the last branch in the specified chain of \fB[<branch> [<branch>]]\fP had any children,
+these children are synced to the parent of the first specified branch.
 Sync is performed either by rebase (default) or by merge (if \fB\-\-merge\fP option passed).
 .sp
 For example, let\(aqs assume the following dependency tree:
@@ -1561,7 +1566,7 @@ all provided branches must have an upstream branch (so, in other words, roots of
 .UNINDENT
 .UNINDENT
 .sp
-Note: This command doesn\(aqt delete any branches from git, just removes them from the tree of branch dependencies.
+Note: Unless \fB\-\-delete\fP is passed, \fBslide\-out\fP doesn\(aqt delete any branches from git, just removes them from the tree of branch dependencies.
 .sp
 \fBOptions:\fP
 .INDENT 0.0
@@ -1573,7 +1578,7 @@ See also doc for \fB\-\-fork\-point\fP option in \fBgit machete help reapply\fP
 Not allowed if updating by merge.
 .TP
 .B  \-\-delete
-Delete the slid\-out branches.
+Delete the branches after sliding them out.
 .TP
 .B  \-M\fP,\fB  \-\-merge
 Update the downstream branch by merge rather than by rebase.
@@ -1590,6 +1595,24 @@ Not allowed if updating by rebase.
 .B  \-\-no\-interactive\-rebase
 If updating by rebase, run \fBgit rebase\fP in non\-interactive mode (without \fB\-i/\-\-interactive\fP flag).
 Not allowed if updating by merge.
+.TP
+.B  \-\-removed\-from\-remote
+Slide out managed branches whose remote tracking branches have been deleted and that have no downstreams.
+In other words, this deletes all branches except:
+.INDENT 7.0
+.INDENT 3.5
+.INDENT 0.0
+.IP \(bu 2
+those that are unmanaged,
+.IP \(bu 2
+those that have no remote tracking branch set (unpushed),
+.IP \(bu 2
+those whose remote tracking branches still exist (not deleted remotely),
+.IP \(bu 2
+those that have at least one downstream (child) branch.
+.UNINDENT
+.UNINDENT
+.UNINDENT
 .UNINDENT
 .sp
 \fBEnvironment variables:\fP
diff --git a/docs/source/cli/slide-out.rst b/docs/source/cli/slide-out.rst
index 4238978..eff4c94 100644
--- a/docs/source/cli/slide-out.rst
+++ b/docs/source/cli/slide-out.rst
@@ -6,10 +6,15 @@ slide-out
 
 .. code-block:: shell
 
+    git machete slide-out --removed-from-remote [--delete]
     git machete slide-out [-d|--down-fork-point=<down-fork-point-commit>] [--delete] [-M|--merge] [-n|--no-edit-merge|--no-interactive-rebase] [<branch> [<branch> [<branch> ...]]]
 
-Removes the given branch (or multiple branches) from the branch layout. If no branch has been specified, current branch is removed.
-Then synchronizes the downstream (child) branches of the last specified branch on the top of the upstream (parent) branch of the first specified branch.
+Removes the given branch (or multiple branches) from the branch layout.
+If no branch has been specified, current branch is slid out.
+If ``--removed-from-remote`` is specified, all branches that have been removed from the remote are slid out instead.
+
+Also, if the last branch in the specified chain of ``[<branch> [<branch>]]`` had any children,
+these children are synced to the parent of the first specified branch.
 Sync is performed either by rebase (default) or by merge (if ``--merge`` option passed).
 
 For example, let's assume the following dependency tree:
@@ -41,7 +46,7 @@ The provided branches must form a chain --- all of the following conditions must
     * for i=1..N-1, (i+1)-th branch must be the only downstream (child) branch of the i-th branch,
     * all provided branches must have an upstream branch (so, in other words, roots of branch dependency tree cannot be slid out).
 
-Note: This command doesn't delete any branches from git, just removes them from the tree of branch dependencies.
+Note: Unless ``--delete`` is passed, ``slide-out`` doesn't delete any branches from git, just removes them from the tree of branch dependencies.
 
 **Options:**
 
@@ -50,7 +55,7 @@ Note: This command doesn't delete any branches from git, just removes them from
                                                   See also doc for ``--fork-point`` option in ``git machete help reapply`` and ``git machete help update``.
                                                   Not allowed if updating by merge.
 
---delete                                          Delete the slid-out branches.
+--delete                                          Delete the branches after sliding them out.
 
 -M, --merge                                       Update the downstream branch by merge rather than by rebase.
 
@@ -64,6 +69,14 @@ Note: This command doesn't delete any branches from git, just removes them from
 --no-interactive-rebase                           If updating by rebase, run ``git rebase`` in non-interactive mode (without ``-i/--interactive`` flag).
                                                   Not allowed if updating by merge.
 
+--removed-from-remote                             Slide out managed branches whose remote tracking branches have been deleted and that have no downstreams.
+                                                  In other words, this deletes all branches except:
+
+                                                      * those that are unmanaged,
+                                                      * those that have no remote tracking branch set (unpushed),
+                                                      * those whose remote tracking branches still exist (not deleted remotely),
+                                                      * those that have at least one downstream (child) branch.
+
 **Environment variables:**
 
 ``GIT_MACHETE_REBASE_OPTS``
diff --git a/git_machete/__init__.py b/git_machete/__init__.py
index ff398e4..64b38b0 100644
--- a/git_machete/__init__.py
+++ b/git_machete/__init__.py
@@ -1,1 +1,1 @@
-__version__ = '3.20.1'
+__version__ = '3.21.0'
diff --git a/git_machete/cli.py b/git_machete/cli.py
index f3809a2..3aa4150 100644
--- a/git_machete/cli.py
+++ b/git_machete/cli.py
@@ -329,6 +329,7 @@ def create_cli_parser() -> argparse.ArgumentParser:
     slide_out_parser.add_argument('-n', action='store_true')
     slide_out_parser.add_argument('--no-edit-merge', action='store_true')
     slide_out_parser.add_argument('--no-interactive-rebase', action='store_true')
+    slide_out_parser.add_argument('--removed-from-remote', action='store_true')
 
     squash_parser = subparsers.add_parser(
         'squash',
@@ -410,7 +411,9 @@ def update_cli_options_using_parsed_args(
 
     for opt, arg in vars(parsed_args).items():
         # --color, --debug and --verbose are handled outside this method
-        if opt == "branch":
+        if opt == "as_root":
+            cli_opts.opt_as_root = True
+        elif opt == "branch":
             cli_opts.opt_branch = AnyBranchName.of(arg) if arg else None
         elif opt == "checked_out_since":
             cli_opts.opt_checked_out_since = arg
@@ -426,10 +429,10 @@ def update_cli_options_using_parsed_args(
             cli_opts.opt_fork_point = AnyRevision.of(arg) if arg else None
         elif opt == "inferred":
             cli_opts.opt_inferred = True
-        elif opt == "list_commits_with_hashes":
-            cli_opts.opt_list_commits = cli_opts.opt_list_commits_with_hashes = True
         elif opt == "list_commits":
             cli_opts.opt_list_commits = True
+        elif opt == "list_commits_with_hashes":
+            cli_opts.opt_list_commits = cli_opts.opt_list_commits_with_hashes = True
         elif opt == "merge":
             cli_opts.opt_merge = True
         elif opt == "n":
@@ -458,16 +461,16 @@ def update_cli_options_using_parsed_args(
             cli_opts.opt_push_untracked = True
         elif opt == "push_untracked":
             cli_opts.opt_push_untracked = True
-        elif opt == "as_root":
-            cli_opts.opt_as_root = True
-        elif opt == "roots":
-            cli_opts.opt_roots = list(map(LocalBranchShortName.of, filter(None, arg.split(","))))
+        elif opt == "removed_from_remote":
+            cli_opts.opt_removed_from_remote = True
         elif opt == "return_to":
             cli_opts.opt_return_to = arg
-        elif opt == "stat":
-            cli_opts.opt_stat = True
+        elif opt == "roots":
+            cli_opts.opt_roots = list(map(LocalBranchShortName.of, filter(None, arg.split(","))))
         elif opt == "start_from":
             cli_opts.opt_start_from = arg
+        elif opt == "stat":
+            cli_opts.opt_stat = True
         elif opt == "sync_github_prs":
             cli_opts.opt_sync_github_prs = True
         elif opt == "unset_override":
@@ -528,7 +531,7 @@ def launch(orig_args: List[str]) -> None:
 
         cli_parser: argparse.ArgumentParser = create_cli_parser()
         parsed_cli: argparse.Namespace = cli_parser.parse_args(orig_args)
-        parsed_cli_as_dict: Dict[str, str] = vars(parsed_cli)
+        parsed_cli_as_dict: Dict[str, Any] = vars(parsed_cli)
 
         # Let's set up options like debug/verbose before we first start reading `git config`.
         set_utils_global_variables(parsed_cli)
@@ -784,14 +787,19 @@ def launch(orig_args: List[str]) -> None:
         elif cmd == "slide-out":
             machete_client.read_branch_layout_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
             git.expect_no_operation_in_progress()
-            branches = parsed_cli_as_dict.get('branches', [git.get_current_branch()])
-            machete_client.slide_out(
-                branches_to_slide_out=list(map(LocalBranchShortName.of, branches)),
-                opt_delete=cli_opts.opt_delete,
-                opt_down_fork_point=cli_opts.opt_down_fork_point,
-                opt_merge=cli_opts.opt_merge,
-                opt_no_interactive_rebase=cli_opts.opt_no_interactive_rebase,
-                opt_no_edit_merge=cli_opts.opt_no_edit_merge)
+            branches_to_slide_out: Optional[List[str]] = parsed_cli_as_dict.get('branches')
+            if cli_opts.opt_removed_from_remote:
+                if branches_to_slide_out or cli_opts.opt_down_fork_point or cli_opts.opt_merge or cli_opts.opt_no_interactive_rebase:
+                    raise MacheteException("Only `--delete` can be passed with `--removed-from-remote`")
+                machete_client.slide_out_removed_from_remote(opt_delete=cli_opts.opt_delete)
+            else:
+                machete_client.slide_out(
+                    branches_to_slide_out=list(map(LocalBranchShortName.of, branches_to_slide_out or [git.get_current_branch()])),
+                    opt_delete=cli_opts.opt_delete,
+                    opt_down_fork_point=cli_opts.opt_down_fork_point,
+                    opt_merge=cli_opts.opt_merge,
+                    opt_no_interactive_rebase=cli_opts.opt_no_interactive_rebase,
+                    opt_no_edit_merge=cli_opts.opt_no_edit_merge)
         elif cmd == "squash":
             machete_client.read_branch_layout_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 22d30cf..8109965 100644
--- a/git_machete/client.py
+++ b/git_machete/client.py
@@ -2747,19 +2747,35 @@ class MacheteClient:
             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.__remove_branches_from_layout(branches_to_delete)
+        self.__delete_branches(branches_to_delete, opt_yes)
+
+    def slide_out_removed_from_remote(self, opt_delete: bool) -> None:
+        slid_out_branches: List[LocalBranchShortName] = []
+        for branch in self.managed_branches.copy():
+            if self.__git.is_missing_tracking_branch(branch) and not self.__down_branches.get(branch):
+                print(fmt(f"Sliding out <b>{branch}</b>"))
+                slid_out_branches.append(branch)
+
+        self.__remove_branches_from_layout(slid_out_branches)
+        if opt_delete:
+            self.__delete_branches(slid_out_branches, opt_yes=True)
+
+    def __remove_branches_from_layout(self, branches_to_delete: List[LocalBranchShortName]) -> None:
+        for branch in branches_to_delete:
+            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.save_branch_layout_file()
 
     @staticmethod
diff --git a/git_machete/generated_docs.py b/git_machete/generated_docs.py
index 86505ad..f8a8316 100644
--- a/git_machete/generated_docs.py
+++ b/git_machete/generated_docs.py
@@ -864,10 +864,15 @@ long_docs: Dict[str, str] = {
    """,
     "slide-out": """
         <b>Usage:</b><b>
+           git machete slide-out --removed-from-remote [--delete]
            git machete slide-out [-d|--down-fork-point=<down-fork-point-commit>] [--delete] [-M|--merge] [-n|--no-edit-merge|--no-interactive-rebase] [<branch> [<branch> [<branch> ...]]]</b>
 
-        Removes the given branch (or multiple branches) from the branch layout. If no branch has been specified, current branch is removed.
-        Then synchronizes the downstream (child) branches of the last specified branch on the top of the upstream (parent) branch of the first specified branch.
+        Removes the given branch (or multiple branches) from the branch layout.
+        If no branch has been specified, current branch is slid out.
+        If `--removed-from-remote` is specified, all branches that have been removed from the remote are slid out instead.
+
+        Also, if the last branch in the specified chain of `[<branch> [<branch>]]` had any children,
+        these children are synced to the parent of the first specified branch.
         Sync is performed either by rebase (default) or by merge (if `--merge` option passed).
 
         For example, let's assume the following dependency tree:
@@ -896,7 +901,7 @@ long_docs: Dict[str, str] = {
            * for i=1..N-1, (i+1)-th branch must be the only downstream (child) branch of the i-th branch,
            * all provided branches must have an upstream branch (so, in other words, roots of branch dependency tree cannot be slid out).
 
-        Note: This command doesn't delete any branches from git, just removes them from the tree of branch dependencies.
+        Note: Unless `--delete` is passed, `slide-out` doesn't delete any branches from git, just removes them from the tree of branch dependencies.
 
         <b>Options:</b>
            <b>-d</b>, <b>--down-fork-point=<down-fork-point-commit></b>
@@ -905,7 +910,7 @@ long_docs: Dict[str, str] = {
               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 slid-out branches.
+              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>
@@ -918,6 +923,18 @@ long_docs: Dict[str, str] = {
            <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.
+              In other words, this deletes all branches except:
+
+                 * those that are unmanaged,
+
+                 * those that have no remote tracking branch set (unpushed),
+
+                 * those whose remote tracking branches still exist (not deleted remotely),
+
+                 * those that have at least one downstream (child) branch.
 
         <b>Environment variables:</b>
            `GIT_MACHETE_REBASE_OPTS`
diff --git a/git_machete/git_operations.py b/git_machete/git_operations.py
index ae84255..e2420be 100644
--- a/git_machete/git_operations.py
+++ b/git_machete/git_operations.py
@@ -10,9 +10,9 @@ from typing import (Any, Dict, Iterator, List, Match, NamedTuple, Optional,
 from . import utils
 from .constants import (MAX_COUNT_FOR_INITIAL_LOG, GitFormatPatterns,
                         SyncToRemoteStatuses)
-from .exceptions import UnderlyingGitException
+from .exceptions import MacheteException, UnderlyingGitException
 from .utils import (GITHUB_NEW_ISSUE_MESSAGE, AnsiEscapeCodes, CommandResult,
-                    colored, debug, fmt)
+                    colored, debug, fmt, hex_repr)
 
 
 class AnyRevision(str):
@@ -198,6 +198,7 @@ class GitContext:
         self.__is_equivalent_tree_reachable_cached: Dict[Tuple[FullCommitHash, FullCommitHash], bool] = {}
         self.__local_branches_cached: Optional[List[LocalBranchShortName]] = None
         self.__merge_base_cached: Dict[Tuple[FullCommitHash, FullCommitHash], Optional[FullCommitHash]] = {}
+        self.__missing_tracking_branch: Optional[Set[str]] = None
         self.__reflogs_cached: Optional[Dict[AnyBranchName, List[GitReflogEntry]]] = None
         self.__remaining_log_hashes_cached: Dict[FullCommitHash, List[FullCommitHash]] = {}
         self.__remote_branches_cached: Optional[List[RemoteBranchShortName]] = None
@@ -213,6 +214,7 @@ class GitContext:
         self.__config_cached = None
         self.__counterparts_for_fetching_cached = None
         self.__local_branches_cached = None
+        self.__missing_tracking_branch = None
         self.__reflogs_cached = None
         self.__remote_branches_cached = None
         self.__remotes_cached = None
@@ -484,6 +486,12 @@ class GitContext:
         # we try to infer the remote if the tracking data is missing.
         return self.get_strict_counterpart_for_fetching_of_branch(branch) or self.__get_inferred_counterpart_for_fetching_of_branch(branch)
 
+    def is_missing_tracking_branch(self, branch: LocalBranchShortName) -> bool:
+        if self.__missing_tracking_branch is None:
+            self.__load_branches()
+        assert self.__missing_tracking_branch is not None
+        return branch in self.__missing_tracking_branch
+
     # Note that rebase/cherry-pick/merge/revert all happen on per-worktree basis,
     # so we need to check .git/worktrees/<worktree>/<file> rather than .git/<file>
 
@@ -520,6 +528,7 @@ class GitContext:
         self.__committer_unix_timestamp_by_revision_cached = {}
         self.__counterparts_for_fetching_cached = {}
         self.__local_branches_cached = []
+        self.__missing_tracking_branch = set()
         self.__remote_branches_cached = []
         self.__tree_hash_by_commit_hash_cached = {}
 
@@ -529,7 +538,9 @@ class GitContext:
         for line in raw_remote:
             values = line.split("\t")
             if len(values) != 4:
-                continue  # pragma: no cover; invalid, shouldn't happen
+                raise MacheteException(
+                    "`git for-each-ref` returned more than 4 values for `refs/remotes`: "
+                    f"{values} ({hex_repr(line)})\n\n" + GITHUB_NEW_ISSUE_MESSAGE)  # pragma: no cover
             branch, commit_hash, tree_hash, committer_unix_timestamp_and_time_zone = values
             b_stripped_remote = RemoteBranchFullName.of(branch).to_short_name()
             self.__remote_branches_cached += [b_stripped_remote]
@@ -545,7 +556,9 @@ class GitContext:
         for line in raw_local:
             values = line.split("\t")
             if len(values) != 5:
-                continue  # pragma: no cover; invalid, shouldn't happen
+                raise MacheteException(
+                    "`git for-each-ref` returned more than 5 values for `refs/heads`: "
+                    f"{values} ({hex_repr(line)})\n\n" + GITHUB_NEW_ISSUE_MESSAGE)  # pragma: no cover
             branch, commit_hash, tree_hash, committer_unix_timestamp_and_time_zone, fetch_counterpart = values
             b_stripped_local = LocalBranchFullName.of(branch).to_short_name()
             # fetch_counterpart might be empty, or might even point to a local branch
@@ -561,6 +574,8 @@ class GitContext:
                 committer_unix_timestamp_and_time_zone.split(' ')[0])
             if fetch_counterpart_stripped in self.__remote_branches_cached:
                 self.__counterparts_for_fetching_cached[b_stripped_local] = fetch_counterpart_stripped
+            elif fetch_counterpart_stripped is not None:
+                self.__missing_tracking_branch.add(b_stripped_local)
 
     def __get_log_hashes(self, revision: AnyRevision, max_count: Optional[int]) -> List[FullCommitHash]:
         opts = ([f"--max-count={str(max_count)}"] if max_count else []) + ["--format=%H", revision.full_name()]
diff --git a/git_machete/options.py b/git_machete/options.py
index 3c28512..9db76f7 100644
--- a/git_machete/options.py
+++ b/git_machete/options.py
@@ -29,6 +29,7 @@ class CommandLineOptions:
         self.opt_override_to_parent: bool = False
         self.opt_push_tracked: bool = True
         self.opt_push_untracked: bool = True
+        self.opt_removed_from_remote: bool = False
         self.opt_return_to: str = "stay"
         self.opt_roots: List[LocalBranchShortName] = list()
         self.opt_start_from: str = "here"
@@ -57,3 +58,7 @@ class CommandLineOptions:
             raise MacheteException(
                 "Option `-f/--fork-point` only makes sense when using rebase and"
                 " cannot be specified together with `-M/--merge`.")
+
+    def __repr__(self) -> str:  # pragma: no cover; debug only
+        attrs = ", ".join(f"{k}={v!r}" for k, v in self.__dict__.items())
+        return f"{self.__class__.__module__}.{self.__class__.__qualname__}({attrs})"
diff --git a/git_machete/utils.py b/git_machete/utils.py
index 1ccf253..5872d2c 100644
--- a/git_machete/utils.py
+++ b/git_machete/utils.py
@@ -255,6 +255,11 @@ def is_terminal_fully_fledged() -> bool:
     return number_of_supported_colors >= 256
 
 
+def hex_repr(input: str) -> str:
+    # Skip the first two `0x` characters.
+    return ':'.join(hex(ord(char))[2:] for char in input)
+
+
 class AnsiEscapeCodes:
 
     __is_terminal_fully_fledged = is_terminal_fully_fledged()
diff --git a/tox.ini b/tox.ini
index d5a1d8c..3e74ee9 100644
--- a/tox.ini
+++ b/tox.ini
@@ -3,7 +3,7 @@
 # Let's still include pep8-check despite a preceding autopep8 run to filter out certain errors
 # (like W293 blank line in multiline strings contains whitespace, or FS003 f-string missing prefix)
 # that don't get fixed by autopep8.
-envlist = pep8,isort,pep8-check,mypy,vulture-check,sphinx-man,py-docs
+envlist = pep8,isort,pep8-check,mypy,vulture-check,sphinx-man,sphinx-html,py-docs
 minversion = 2.3.2
 skipsdist = True
 


Test patch:
diff --git a/tests/completion_e2e/test_completion_e2e.py b/tests/completion_e2e/test_completion_e2e.py
index 11899f2..e6e1ae4 100644
--- a/tests/completion_e2e/test_completion_e2e.py
+++ b/tests/completion_e2e/test_completion_e2e.py
@@ -91,6 +91,9 @@ test_cases: Dict[str, str] = {
         "current down first last next prev root up",
     "git machete slide-out ":
         "develop",
+    "git machete slide-out -":
+        "-M -d --debug --delete --down-fork-point -h --help --merge -n "
+        "--no-edit-merge --no-interactive-rebase --removed-from-remote -v --verbose",
     "git machete slide-out --down-fork-point=":
         "HEAD develop feature master",
     "git machete squash -":
diff --git a/tests/test_clean.py b/tests/test_clean.py
index 9bc1100..d7d2703 100644
--- a/tests/test_clean.py
+++ b/tests/test_clean.py
@@ -18,21 +18,21 @@ class TestClean(BaseTest):
                 .commit()
                 .new_branch('bar2')
                 .commit()
-                .check_out("master")
+                .check_out('master')
                 .new_branch('foo')
                 .commit()
                 .push()
                 .new_branch('foo2')
                 .commit()
-                .check_out("master")
+                .check_out('master')
                 .new_branch('moo')
                 .commit()
                 .new_branch('moo2')
                 .commit()
-                .check_out("master")
+                .check_out('master')
                 .new_branch('mars')
                 .commit()
-                .check_out("master")
+                .check_out('master')
         )
 
         body: str = \
diff --git a/tests/test_slide_out.py b/tests/test_slide_out.py
index fd79e97..c273761 100644
--- a/tests/test_slide_out.py
+++ b/tests/test_slide_out.py
@@ -1,9 +1,11 @@
+import pytest
 from pytest_mock import MockerFixture
 
 from .base_test import BaseTest
 from .mockers import (assert_failure, assert_success,
                       fixed_author_and_committer_date_in_past, launch_command,
-                      mock_input_returning_y, rewrite_branch_layout_file)
+                      mock_input_returning_y, read_branch_layout_file,
+                      rewrite_branch_layout_file)
 
 
 class TestSlideOut(BaseTest):
@@ -192,15 +194,15 @@ class TestSlideOut(BaseTest):
         with fixed_author_and_committer_date_in_past():
             (
                 self.repo_sandbox.new_branch('branch-0')
-                    .commit()
-                    .new_branch('branch-1')
-                    .commit()
-                    .new_branch('branch-2')
-                    .commit()
-                    .new_branch('branch-3')
-                    .commit()
-                    .check_out('branch-2')
-                    .commit('Commit that is not ancestor of branch-3.')
+                .commit()
+                .new_branch('branch-1')
+                .commit()
+                .new_branch('branch-2')
+                .commit()
+                .new_branch('branch-3')
+                .commit()
+                .check_out('branch-2')
+                .commit('Commit that is not ancestor of branch-3.')
             )
         hash_of_commit_that_is_not_ancestor_of_branch_2 = self.repo_sandbox.get_current_commit_hash()
 
@@ -242,14 +244,14 @@ class TestSlideOut(BaseTest):
     def test_slide_out_with_down_fork_point_and_single_child_of_last_branch(self) -> None:
         (
             self.repo_sandbox.new_branch('branch-0')
-                .commit()
-                .new_branch('branch-1')
-                .commit()
-                .new_branch('branch-2')
-                .commit()
-                .new_branch('branch-3')
-                .commit()
-                .commit('Second commit on branch-3.')
+            .commit()
+            .new_branch('branch-1')
+            .commit()
+            .new_branch('branch-2')
+            .commit()
+            .new_branch('branch-3')
+            .commit()
+            .commit('Second commit on branch-3.')
         )
         hash_of_second_commit_on_branch_3 = self.repo_sandbox.get_current_commit_hash()
         self.repo_sandbox.commit("Third commit on branch-3.")
@@ -280,14 +282,14 @@ class TestSlideOut(BaseTest):
     def test_slide_out_with_down_fork_point_and_multiple_children_of_last_branch(self) -> None:
         (
             self.repo_sandbox.new_branch('branch-0')
-                .commit()
-                .new_branch('branch-1')
-                .commit()
-                .new_branch('branch-2a')
-                .commit()
-                .check_out('branch-1')
-                .new_branch('branch-2b')
-                .commit()
+            .commit()
+            .new_branch('branch-1')
+            .commit()
+            .new_branch('branch-2a')
+            .commit()
+            .check_out('branch-1')
+            .new_branch('branch-2b')
+            .commit()
         )
 
         hash_of_only_commit_on_branch_2b = self.repo_sandbox.get_current_commit_hash()
@@ -309,16 +311,16 @@ class TestSlideOut(BaseTest):
     def test_slide_out_with_invalid_sequence_of_branches(self) -> None:
         (
             self.repo_sandbox.new_branch('branch-0')
-                .commit()
-                .new_branch('branch-1')
-                .commit()
-                .new_branch('branch-2a')
-                .commit()
-                .new_branch('branch-3')
-                .commit()
-                .check_out('branch-1')
-                .new_branch('branch-2b')
-                .commit()
+            .commit()
+            .new_branch('branch-1')
+            .commit()
+            .new_branch('branch-2a')
+            .commit()
+            .new_branch('branch-3')
+            .commit()
+            .check_out('branch-1')
+            .new_branch('branch-2b')
+            .commit()
         )
 
         body: str = \
@@ -353,3 +355,85 @@ class TestSlideOut(BaseTest):
             ['slide-out', '--down-fork-point=@', '-M'],
             "Option -d/--down-fork-point only makes sense when using rebase and cannot be specified together with -M/--merge."
         )
+
+    def test_slide_out_removed_from_remote(self) -> None:
+        (
+            self.repo_sandbox.new_branch('main')
+            .commit()
+            .push()
+            .new_branch('unmanaged')
+            .commit()
+            .push()
+            .check_out('main')
+            .new_branch('unpushed')
+            .commit()
+            .check_out('main')
+            .new_branch('not_deleted_remotely')
+            .commit()
+            .push()
+            .check_out('main')
+            .new_branch('has_downstream')
+            .commit()
+            .push()
+            .new_branch('downstream')
+            .commit()
+            .check_out('main')
+            .new_branch('should_be_pruned')
+            .commit()
+            .push()
+            .delete_remote_branch('origin/should_be_pruned')
+            .check_out('main')
+        )
+
+        body: str = \
+            """
+            main
+                unpushed
+                not_deleted_remotely
+                has_downstream
+                    downstream
+                should_be_pruned PR #123
+            """
+        rewrite_branch_layout_file(body)
+
+        assert_success(
+            ['slide-out', '--removed-from-remote', '--delete'],
+            "Sliding out should_be_pruned\nDeleting branch should_be_pruned (unmerged to HEAD)...\n")
+
+        assert read_branch_layout_file() == "main\n    unpushed\n    not_deleted_remotely\n    has_downstream\n        downstream\n"
+
+        expected_status_output = (
+            """
+              main *
+              |
+              o-unpushed (untracked)
+              |
+              o-not_deleted_remotely
+              |
+              o-has_downstream
+                |
+                o-downstream (untracked)
+            """
+        )
+        assert_success(['status'], expected_status_output)
+
+        branches = self.repo_sandbox.get_local_branches()
+        assert 'unmanaged' in branches
+        assert 'unpushed' in branches
+        assert 'not_deleted_remotely' in branches
+        assert 'has_downstream' in branches
+        assert 'should_be_pruned' not in branches
+
+        self.repo_sandbox.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"
+        branches = self.repo_sandbox.get_local_branches()
+        assert 'not_deleted_remotely' in branches
+
+    @pytest.mark.parametrize('extra_arg', ['foo', '-d=foo', '--down-fork-point=foo', '-M', '--merge', '-n', '--no-interactive-rebase'])
+    def test_slide_out_removed_from_remote_with_extra_args(self, extra_arg: str) -> None:
+        assert_failure(
+            ['slide-out', '--removed-from-remote', extra_arg],
+            "Only --delete can be passed with --removed-from-remote",
+        )
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 0151f96..b10e6a7 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -1,13 +1,10 @@
 from git_machete import utils
+from git_machete.utils import hex_repr
 
 
 class TestUtils:
 
     def test_fmt(self) -> None:
-        """
-        Verify behaviour of a Utils fmt() function
-        """
-
         utils.ascii_only = False
         utils.AnsiEscapeCodes.UNDERLINE = '\033[4m'
         utils.AnsiEscapeCodes.RED = '\033[91m'
@@ -20,3 +17,6 @@ class TestUtils:
         ansi_string = utils.fmt(input_string)
 
         assert ansi_string == expected_ansi_string
+
+    def test_hex_repr(self) -> None:
+        assert hex_repr("Hello, world!") == "48:65:6c:6c:6f:2c:20:77:6f:72:6c:64:21"