Repository: VirtusLab/git-machete
Pull request #924 (2023-05-02 20:23:54)

Add missing tests + improve test code quality - vol. 3

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/VirtusLab/git-machete/pull/924?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) Report
> Merging [#924](https://app.codecov.io/gh/VirtusLab/git-machete/pull/924?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) (52d2fbe) into [develop](https://app.codecov.io/gh/VirtusLab/git-machete/commit/862c182f83c502c89a8f6f7e9a9107e18626c793?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) (862c182) will **decrease** coverage by `4.77%`.
> The diff coverage is `80.51%`.

```diff
@@             Coverage Diff             @@
##           develop     #924      +/-   ##
===========================================
- Coverage    84.67%   79.91%   -4.77%     
===========================================
  Files           12       12              
  Lines         3106     3231     +125     
  Branches       623      739     +116     
===========================================
- Hits          2630     2582      -48     
- Misses         313      408      +95     
- Partials       163      241      +78     
```


| [Impacted Files](https://app.codecov.io/gh/VirtusLab/git-machete/pull/924?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) | Coverage Δ | |
|---|---|---|
| [git\_machete/exceptions.py](https://app.codecov.io/gh/VirtusLab/git-machete/pull/924?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab#diff-Z2l0X21hY2hldGUvZXhjZXB0aW9ucy5weQ==) | `89.47% <66.66%> (-1.01%)` | :arrow_down: |
| [git\_machete/cli.py](https://app.codecov.io/gh/VirtusLab/git-machete/pull/924?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab#diff-Z2l0X21hY2hldGUvY2xpLnB5) | `87.75% <79.81%> (-8.66%)` | :arrow_down: |
| [git\_machete/\_\_init\_\_.py](https://app.codecov.io/gh/VirtusLab/git-machete/pull/924?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab#diff-Z2l0X21hY2hldGUvX19pbml0X18ucHk=) | `100.00% <100.00%> (ø)` | |
| [git\_machete/client.py](https://app.codecov.io/gh/VirtusLab/git-machete/pull/924?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab#diff-Z2l0X21hY2hldGUvY2xpZW50LnB5) | `74.12% <100.00%> (-4.65%)` | :arrow_down: |
| [git\_machete/git\_operations.py](https://app.codecov.io/gh/VirtusLab/git-machete/pull/924?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab#diff-Z2l0X21hY2hldGUvZ2l0X29wZXJhdGlvbnMucHk=) | `81.40% <100.00%> (-3.86%)` | :arrow_down: |

... and [3 files with indirect coverage changes](https://app.codecov.io/gh/VirtusLab/git-machete/pull/924/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab)



Patch:
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index ad88415..d79da4c 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,5 +1,7 @@
 # Release notes
 
+## New in git-machete 3.17.3
+
 ## New in git-machete 3.17.2
 
 - fixed: when `origin/feature/foo` branch exists, `git machete add foo` no longer falsely recognizes `origin/feature/foo` as a potential remote tracking branch for `foo`
diff --git a/ci/pypi/ci-deploy.sh b/ci/pypi/ci-deploy.sh
index 4035375..b217739 100644
--- a/ci/pypi/ci-deploy.sh
+++ b/ci/pypi/ci-deploy.sh
@@ -17,7 +17,7 @@ password = $token
 EOF
 
 set -x
-pip3 install twine wheel
+pip3 install -r requirements.pypi-deploy.txt
 python3 setup.py sdist
 python3 setup.py bdist_wheel
 python3 -m twine upload --repository $repository --skip-existing dist/*
diff --git a/docs/man/git-machete.1 b/docs/man/git-machete.1
index 200cf12..331496c 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" "Apr 25, 2023" "" "git-machete"
+.TH "GIT-MACHETE" "1" "May 04, 2023" "" "git-machete"
 .SH NAME
-git-machete \- git-machete 3.17.2
+git-machete \- git-machete 3.17.3
 .sp
 Probably the sharpest git repository organizer & rebase/merge workflow automation tool you\(aqve ever seen.
 .sp
diff --git a/git_machete/__init__.py b/git_machete/__init__.py
index b486aa5..54ed654 100644
--- a/git_machete/__init__.py
+++ b/git_machete/__init__.py
@@ -1,1 +1,1 @@
-__version__ = '3.17.2'
+__version__ = '3.17.3'
diff --git a/git_machete/cli.py b/git_machete/cli.py
index 79305d2..4597607 100644
--- a/git_machete/cli.py
+++ b/git_machete/cli.py
@@ -488,370 +488,352 @@ def get_local_branch_short_name_from_arg(branch_from_arg: AnyBranchName) -> Loca
     return LocalBranchShortName.of(branch_from_arg.replace('refs/heads/', ''))
 
 
-def exit_script(status_code: int, error: Optional[BaseException] = None) -> None:
-    # Single point of exit is useful, because we can mock this method in tests
-    # and verify that actual errors like MacheteException are raised with
-    # appropriate messages. Otherwise it's not possible, because SystemError
-    # caused by sys.exit() is overshadowing actual errors. As of the issue #287,
-    # there are some additional points of exit from the script in client.py
-    # method MacheteClient.pick() there are left on purpose since removing them
-    # would enforce significant refactor.
-    if error:
-        print(f"{error}", file=sys.stderr)
-    sys.exit(status_code)
-
-
 def launch(orig_args: List[str]) -> None:
     cli_opts = git_machete.options.CommandLineOptions()
     git = GitContext()
-    initial_current_directory: Optional[str] = utils.get_current_directory_or_none()
-
-    try:
-        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)
-
-        update_cli_options_using_config_keys(cli_opts, git)
-
-        update_cli_opts_using_parsed_args(cli_opts, parsed_cli)
-        cli_opts.validate()
-        set_utils_global_variables(cli_opts)
-
-        if not orig_args:
-            print(get_help_description(display_help_topics=False))
-            exit_script(ExitCode.ARGUMENT_ERROR)
-
-        cmd = parsed_cli.command
-
-        if cmd == "help":
-            print(get_help_description(display_help_topics=True, command=parsed_cli.topic_or_cmd))
-            exit_script(ExitCode.SUCCESS)
-        elif cmd == "version":
-            version()
-            exit_script(ExitCode.SUCCESS)
-
-        machete_client = MacheteClient(git)
-
-        if not os.path.exists(machete_client.definition_file_path):
-            # We're opening in "append" and not "write" mode to avoid a race condition:
-            # if other process writes to the file between we check the
-            # result of `os.path.exists` and call `open`,
-            # then open(..., "w") would result in us clearing up the file
-            # contents, while open(..., "a") has no effect.
-            with open(machete_client.definition_file_path, "a"):
-                pass
-        elif os.path.isdir(machete_client.definition_file_path):
-            # Extremely unlikely case, basically checking if anybody
-            # tampered with the repository.
-            raise MacheteException(
-                f"{machete_client.definition_file_path} is a directory "
-                "rather than a regular file, aborting")
 
-        should_perform_interactive_slide_out = MacheteClient.should_perform_interactive_slide_out(cmd)
-        if cmd == "add":
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+    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)
+
+    update_cli_options_using_config_keys(cli_opts, git)
+
+    update_cli_opts_using_parsed_args(cli_opts, parsed_cli)
+    cli_opts.validate()
+    set_utils_global_variables(cli_opts)
+
+    if not orig_args:
+        print(get_help_description(display_help_topics=False))
+        sys.exit(ExitCode.ARGUMENT_ERROR)
+
+    cmd = parsed_cli.command
+
+    if cmd == "help":
+        print(get_help_description(display_help_topics=True, command=parsed_cli.topic_or_cmd))
+        return
+    elif cmd == "version":
+        version()
+        return
+
+    machete_client = MacheteClient(git)
+
+    if not os.path.exists(machete_client.definition_file_path):
+        # We're opening in "append" and not "write" mode to avoid a race condition:
+        # if other process writes to the file between we check the
+        # result of `os.path.exists` and call `open`,
+        # then open(..., "w") would result in us clearing up the file
+        # contents, while open(..., "a") has no effect.
+        with open(machete_client.definition_file_path, "a"):
+            pass
+    elif os.path.isdir(machete_client.definition_file_path):
+        # Extremely unlikely case, basically checking if anybody
+        # tampered with the repository.
+        raise MacheteException(
+            f"{machete_client.definition_file_path} is a directory "
+            "rather than a regular file, aborting")
+
+    should_perform_interactive_slide_out = MacheteClient.should_perform_interactive_slide_out(cmd)
+    if cmd == "add":
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+        branch = get_local_branch_short_name_from_arg_or_current_branch(cli_opts.opt_branch, git)
+        machete_client.add(
+            branch=branch,
+            opt_onto=cli_opts.opt_onto,
+            opt_as_root=cli_opts.opt_as_root,
+            opt_yes=cli_opts.opt_yes,
+            verbose=True,
+            switch_head_if_new_branch=True)
+    elif cmd == "advance":
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+        git.expect_no_operation_in_progress()
+        current_branch = git.get_current_branch()
+        machete_client.expect_in_managed_branches(current_branch)
+        machete_client.advance(branch=current_branch, opt_yes=cli_opts.opt_yes)
+    elif cmd == "anno":
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out,
+                                            verify_branches=False)
+        if cli_opts.opt_sync_github_prs:
+            machete_client.sync_annotations_to_github_prs()
+        else:
             branch = get_local_branch_short_name_from_arg_or_current_branch(cli_opts.opt_branch, git)
-            machete_client.add(
-                branch=branch,
-                opt_onto=cli_opts.opt_onto,
-                opt_as_root=cli_opts.opt_as_root,
-                opt_yes=cli_opts.opt_yes,
-                verbose=True,
-                switch_head_if_new_branch=True)
-        elif cmd == "advance":
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
-            git.expect_no_operation_in_progress()
-            current_branch = git.get_current_branch()
-            machete_client.expect_in_managed_branches(current_branch)
-            machete_client.advance(branch=current_branch, opt_yes=cli_opts.opt_yes)
-        elif cmd == "anno":
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out,
-                                                verify_branches=False)
-            if cli_opts.opt_sync_github_prs:
-                machete_client.sync_annotations_to_github_prs()
+            machete_client.expect_in_managed_branches(branch)
+            if parsed_cli.annotation_text:
+                machete_client.annotate(branch, parsed_cli.annotation_text)
             else:
-                branch = get_local_branch_short_name_from_arg_or_current_branch(cli_opts.opt_branch, git)
-                machete_client.expect_in_managed_branches(branch)
-                if parsed_cli.annotation_text:
-                    machete_client.annotate(branch, parsed_cli.annotation_text)
-                else:
-                    machete_client.print_annotation(branch)
-        elif cmd == "clean":
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
-            if 'checkout_my_github_prs' in parsed_cli:
-                machete_client.checkout_github_prs(pr_nos=[],
-                                                   my_opened_prs=True)
-            machete_client.delete_unmanaged(opt_yes=cli_opts.opt_yes)
-            machete_client.delete_untracked(opt_yes=cli_opts.opt_yes)
-        elif cmd == "delete-unmanaged":
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
-            machete_client.delete_unmanaged(opt_yes=cli_opts.opt_yes)
-        elif cmd in {"diff", alias_by_command["diff"]}:
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
-            diff_branch = get_local_branch_short_name_from_arg(cli_opts.opt_branch) if (cli_opts.opt_branch is not None) else None
-            machete_client.diff(branch=diff_branch, opt_stat=cli_opts.opt_stat)
-        elif cmd == "discover":
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
-            machete_client.discover_tree(
-                opt_checked_out_since=cli_opts.opt_checked_out_since,
-                opt_list_commits=cli_opts.opt_list_commits,
-                opt_roots=cli_opts.opt_roots,
-                opt_yes=cli_opts.opt_yes)
-        elif cmd in {"edit", alias_by_command["edit"]}:
-            # No need to read definition file.
-            machete_client.edit()
-        elif cmd == "file":
-            # No need to read definition file.
-            print(os.path.abspath(machete_client.definition_file_path))
-        elif cmd == "fork-point":
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
-            branch = get_local_branch_short_name_from_arg_or_current_branch(cli_opts.opt_branch, git)
-            if cli_opts.opt_inferred:
-                fork_point = machete_client.fork_point(branch=branch, use_overrides=False,
-                                                       opt_no_detect_squash_merges=cli_opts.opt_no_detect_squash_merges)
-                if fork_point:
-                    print(fork_point)
-            elif cli_opts.opt_override_to:
-                machete_client.set_fork_point_override(branch, AnyRevision.of(cli_opts.opt_override_to))
-            elif cli_opts.opt_override_to_inferred:
-                fork_point = machete_client.fork_point(branch=branch, use_overrides=False,
-                                                       opt_no_detect_squash_merges=cli_opts.opt_no_detect_squash_merges)
-                if fork_point:
-                    machete_client.set_fork_point_override(branch, fork_point)
-                else:
-                    raise MacheteException(f"Cannot infer fork point for branch <b>{branch}</b>. Use `--override-to=...` flag instead.")
-            elif cli_opts.opt_override_to_parent:
-                upstream = machete_client.up_branch.get(branch)
-                if upstream:
-                    machete_client.set_fork_point_override(branch, upstream)
-                else:
-                    raise MacheteException(
-                        f"Branch {bold(branch)} does not have upstream (parent) branch")
-            elif cli_opts.opt_unset_override:
-                machete_client.unset_fork_point_override(branch)
+                machete_client.print_annotation(branch)
+    elif cmd == "clean":
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+        if 'checkout_my_github_prs' in parsed_cli:
+            machete_client.checkout_github_prs(pr_nos=[],
+                                               my_opened_prs=True)
+        machete_client.delete_unmanaged(opt_yes=cli_opts.opt_yes)
+        machete_client.delete_untracked(opt_yes=cli_opts.opt_yes)
+    elif cmd == "delete-unmanaged":
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+        machete_client.delete_unmanaged(opt_yes=cli_opts.opt_yes)
+    elif cmd in {"diff", alias_by_command["diff"]}:
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+        diff_branch = get_local_branch_short_name_from_arg(cli_opts.opt_branch) if (cli_opts.opt_branch is not None) else None
+        machete_client.diff(branch=diff_branch, opt_stat=cli_opts.opt_stat)
+    elif cmd == "discover":
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+        machete_client.discover_tree(
+            opt_checked_out_since=cli_opts.opt_checked_out_since,
+            opt_list_commits=cli_opts.opt_list_commits,
+            opt_roots=cli_opts.opt_roots,
+            opt_yes=cli_opts.opt_yes)
+    elif cmd in {"edit", alias_by_command["edit"]}:
+        # No need to read definition file.
+        machete_client.edit()
+    elif cmd == "file":
+        # No need to read definition file.
+        print(os.path.abspath(machete_client.definition_file_path))
+    elif cmd == "fork-point":
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+        branch = get_local_branch_short_name_from_arg_or_current_branch(cli_opts.opt_branch, git)
+        if cli_opts.opt_inferred:
+            fork_point = machete_client.fork_point(branch=branch, use_overrides=False)
+            if fork_point:
+                print(fork_point)
+        elif cli_opts.opt_override_to:
+            machete_client.set_fork_point_override(branch, AnyRevision.of(cli_opts.opt_override_to))
+        elif cli_opts.opt_override_to_inferred:
+            fork_point = machete_client.fork_point(branch=branch, use_overrides=False)
+            if fork_point:
+                machete_client.set_fork_point_override(branch, fork_point)
             else:
-                print(machete_client.fork_point(
-                    branch=branch,
-                    use_overrides=True,
-                    opt_no_detect_squash_merges=cli_opts.opt_no_detect_squash_merges))
-        elif cmd in {"go", alias_by_command["go"]}:
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
-            git.expect_no_operation_in_progress()
-            current_branch = git.get_current_branch()
-            dest = machete_client.parse_direction(parsed_cli.direction, current_branch, allow_current=False, down_pick_mode=True)[0]
-            # with down_pick_mode=True there is only one element in list allowed
-            if dest != current_branch:
-                git.checkout(dest)
-        elif cmd == "github":
-            github_subcommand = parsed_cli.subcommand
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
-
-            if 'draft' in parsed_cli and github_subcommand != 'create-pr':
-                raise MacheteException("'--draft' option is only valid with 'create-pr' subcommand.")
-            for command in ('all', 'by', 'mine'):
-                if command in parsed_cli and github_subcommand != 'checkout-prs':
-                    raise MacheteException(f"'--{command}' argument is only valid with 'checkout-prs' subcommand.")
-            if 'pr_no' in parsed_cli and github_subcommand != 'checkout-prs':
-                raise MacheteException("'pr_no' option is only valid with 'checkout-prs' subcommand.")
-            if 'branch' in parsed_cli and github_subcommand != 'retarget-pr':
-                raise MacheteException("'--branch' option is only valid with 'retarget-pr' subcommand.")
-            if 'ignore-if-missing' in parsed_cli and github_subcommand != 'retarget-pr':
-                raise MacheteException("'--ignore-if-missing' option is only valid with 'retarget-pr' subcommand.")
-
-            if github_subcommand == "anno-prs":
-                machete_client.sync_annotations_to_github_prs()
-            elif github_subcommand == "checkout-prs":
-                if len(set(parsed_cli_as_dict.keys()).intersection({'all', 'by', 'mine', 'pr_no'})) != 1:
-                    raise MacheteException(f"'checkout-prs' subcommand must take only one of the following options: "
-                                           f"{', '.join(['--all', '--by', '--mine', 'pr-no'])}")
-                machete_client.checkout_github_prs(pr_nos=parsed_cli.pr_no if 'pr_no' in parsed_cli else [],
-                                                   all_opened_prs=parsed_cli.all if 'all' in parsed_cli else False,
-                                                   my_opened_prs=parsed_cli.mine if 'mine' in parsed_cli else False,
-                                                   opened_by=parsed_cli.by if 'by' in parsed_cli else None,
-                                                   fail_on_missing_current_user_for_my_opened_prs=False)
-            elif github_subcommand == "create-pr":
-                current_branch = git.get_current_branch()
-                machete_client.create_github_pr(
-                    head=current_branch,
-                    opt_draft=cli_opts.opt_draft,
-                    opt_onto=cli_opts.opt_onto)
-            elif github_subcommand == "retarget-pr":
-                branch = parsed_cli.branch if 'branch' in parsed_cli else git.get_current_branch()
-                machete_client.expect_in_managed_branches(branch)
-                machete_client.retarget_github_pr(head=branch,
-                                                  ignore_if_missing=parsed_cli.ignore_if_missing if 'ignore_if_missing' in parsed_cli
-                                                  else False)
-            elif github_subcommand == "sync":
-                machete_client.checkout_github_prs(pr_nos=[],
-                                                   my_opened_prs=True)
-                machete_client.delete_unmanaged(opt_yes=False)
-                machete_client.delete_untracked(opt_yes=cli_opts.opt_yes)
-        elif cmd == "is-managed":
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
-            branch = get_local_branch_short_name_from_arg_or_current_branch(cli_opts.opt_branch, git)
-            if branch is None or branch not in machete_client.managed_branches:
-                exit_script(ExitCode.MACHETE_EXCEPTION)
-        elif cmd == "list":
-            category = parsed_cli.category
-            if category == 'slidable-after' and 'branch' not in parsed_cli_as_dict:
-                raise MacheteException(
-                    f"`git machete list {category}` requires an extra <branch> argument")
-            elif category != 'slidable-after' and 'branch' in parsed_cli_as_dict:
-                raise MacheteException(
-                    f"`git machete list {category}` does not expect extra arguments")
-
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
-            res = []
-            if category == "addable":
-                def strip_remote_name(remote_branch: RemoteBranchShortName) -> LocalBranchShortName:
-                    return LocalBranchShortName.of(re.sub("^[^/]+/", "", remote_branch))
-
-                remote_counterparts_of_local_branches = utils.map_truthy_only(
-                    git.get_combined_counterpart_for_fetching_of_branch,
-                    git.get_local_branches())
-                qualifying_remote_branches: List[RemoteBranchShortName] = \
-                    excluding(git.get_remote_branches(), remote_counterparts_of_local_branches)
-                res = excluding(git.get_local_branches(), machete_client.managed_branches) + list(
-                    map(strip_remote_name, qualifying_remote_branches))
-            elif category == "childless":
-                res = machete_client.get_childless_branches()
-            elif category == "managed":
-                res = machete_client.managed_branches
-            elif category == "slidable":
-                res = machete_client.get_slidable_branches()
-            elif category == "slidable-after":
-                machete_client.expect_in_managed_branches(parsed_cli.branch)
-                res = machete_client.get_slidable_after(parsed_cli.branch)
-            elif category == "unmanaged":
-                res = excluding(git.get_local_branches(), machete_client.managed_branches)
-            elif category == "with-overridden-fork-point":
-                res = list(
-                    filter(
-                        lambda _branch: machete_client.has_any_fork_point_override_config(_branch),
-                        git.get_local_branches()))
-            else:  # should never happen
-                raise MacheteException(f"Invalid category: `{category}`")
-
-            if res:
-                print("\n".join(res))
-        elif cmd in {"log", alias_by_command["log"]}:
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
-            branch = get_local_branch_short_name_from_arg_or_current_branch(cli_opts.opt_branch, git)
-            machete_client.log(branch)
-        elif cmd == "reapply":
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
-            git.expect_no_operation_in_progress()
-            current_branch = git.get_current_branch()
-            if "fork_point" in parsed_cli and cli_opts.opt_fork_point:
-                machete_client.check_that_fork_point_is_ancestor_or_equal_to_tip_of_branch(
-                    fork_point_hash=cli_opts.opt_fork_point, branch=current_branch)
-
-            reapply_fork_point = cli_opts.opt_fork_point or machete_client.fork_point(
-                branch=current_branch,
-                use_overrides=True,
-                opt_no_detect_squash_merges=cli_opts.opt_no_detect_squash_merges)
-            if not reapply_fork_point:
-                raise MacheteException(f"Fork point not found for branch <b>{current_branch}</b>; use `--fork-point` flag")
-
-            git.rebase_onto_ancestor_commit(current_branch, reapply_fork_point, cli_opts.opt_no_interactive_rebase)
-        elif cmd == "show":
-            direction = parsed_cli.direction
-            branch = get_local_branch_short_name_from_arg_or_current_branch(cli_opts.opt_branch, git)
-            if direction == "current":
-                if 'branch' in parsed_cli:
-                    raise MacheteException(
-                        '`show current` with a <branch> argument does not make sense')
-                print(branch)
+                raise MacheteException(f"Cannot infer fork point for branch <b>{branch}</b>. Use `--override-to=...` flag instead.")
+        elif cli_opts.opt_override_to_parent:
+            upstream = machete_client.up_branch.get(branch)
+            if upstream:
+                machete_client.set_fork_point_override(branch, upstream)
             else:
-                machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out,
-                                                    verify_branches=False)
-                print(
-                    '\n'.join(machete_client.parse_direction(
-                        direction,
-                        branch,
-                        allow_current=True,
-                        down_pick_mode=False
-                    ))
-                )
-        elif cmd == "slide-out":
-            machete_client.read_definition_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)
-        elif cmd == "squash":
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
-            git.expect_no_operation_in_progress()
-            current_branch = git.get_current_branch()
-            if "fork_point" in parsed_cli and cli_opts.opt_fork_point:
-                machete_client.check_that_fork_point_is_ancestor_or_equal_to_tip_of_branch(
-                    fork_point_hash=cli_opts.opt_fork_point, branch=current_branch)
-
-            squash_fork_point = cli_opts.opt_fork_point or machete_client.fork_point(
-                branch=current_branch,
-                use_overrides=True,
-                opt_no_detect_squash_merges=cli_opts.opt_no_detect_squash_merges)
-            if not squash_fork_point:
-                raise MacheteException(f"Fork point not found for branch <b>{current_branch}</b>; use `--fork-point` flag")
-
-            machete_client.squash(current_branch=current_branch, opt_fork_point=squash_fork_point)
-        elif cmd in {"status", alias_by_command["status"]}:
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
-            machete_client.expect_at_least_one_managed_branch()
-            machete_client.status(
-                warn_when_branch_in_sync_but_fork_point_off=True,
-                opt_list_commits=cli_opts.opt_list_commits,
-                opt_list_commits_with_hashes=cli_opts.opt_list_commits_with_hashes,
-                opt_no_detect_squash_merges=cli_opts.opt_no_detect_squash_merges)
-        elif cmd in {"traverse", alias_by_command["traverse"]}:
-            if cli_opts.opt_start_from not in {"here", "root", "first-root"}:
                 raise MacheteException(
-                    "Invalid argument for `--start-from`. "
-                    "Valid arguments: `here|root|first-root`.")
-            if cli_opts.opt_return_to not in ("here", "nearest-remaining", "stay"):
+                    f"Branch {bold(branch)} does not have upstream (parent) branch")
+        elif cli_opts.opt_unset_override:
+            machete_client.unset_fork_point_override(branch)
+        else:
+            print(machete_client.fork_point(branch=branch, use_overrides=True))
+    elif cmd in {"go", alias_by_command["go"]}:
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+        git.expect_no_operation_in_progress()
+        current_branch = git.get_current_branch()
+        dest = machete_client.parse_direction(parsed_cli.direction, current_branch, allow_current=False, down_pick_mode=True)[0]
+        # with down_pick_mode=True there is only one element in list allowed
+        if dest != current_branch:
+            git.checkout(dest)
+    elif cmd == "github":
+        github_subcommand = parsed_cli.subcommand
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+
+        if 'draft' in parsed_cli and github_subcommand != 'create-pr':
+            raise MacheteException("'--draft' option is only valid with 'create-pr' subcommand.")
+        for command in ('all', 'by', 'mine'):
+            if command in parsed_cli and github_subcommand != 'checkout-prs':
+                raise MacheteException(f"'--{command}' argument is only valid with 'checkout-prs' subcommand.")
+        if 'pr_no' in parsed_cli and github_subcommand != 'checkout-prs':
+            raise MacheteException("'pr_no' option is only valid with 'checkout-prs' subcommand.")
+        if 'branch' in parsed_cli and github_subcommand != 'retarget-pr':
+            raise MacheteException("'--branch' option is only valid with 'retarget-pr' subcommand.")
+        if 'ignore-if-missing' in parsed_cli and github_subcommand != 'retarget-pr':
+            raise MacheteException("'--ignore-if-missing' option is only valid with 'retarget-pr' subcommand.")
+
+        if github_subcommand == "anno-prs":
+            machete_client.sync_annotations_to_github_prs()
+        elif github_subcommand == "checkout-prs":
+            if len(set(parsed_cli_as_dict.keys()).intersection({'all', 'by', 'mine', 'pr_no'})) != 1:
+                raise MacheteException(f"'checkout-prs' subcommand must take only one of the following options: "
+                                       f"{', '.join(['--all', '--by', '--mine', 'pr-no'])}")
+            machete_client.checkout_github_prs(pr_nos=parsed_cli.pr_no if 'pr_no' in parsed_cli else [],
+                                               all_opened_prs=parsed_cli.all if 'all' in parsed_cli else False,
+                                               my_opened_prs=parsed_cli.mine if 'mine' in parsed_cli else False,
+                                               opened_by=parsed_cli.by if 'by' in parsed_cli else None,
+                                               fail_on_missing_current_user_for_my_opened_prs=False)
+        elif github_subcommand == "create-pr":
+            current_branch = git.get_current_branch()
+            machete_client.create_github_pr(
+                head=current_branch,
+                opt_draft=cli_opts.opt_draft,
+                opt_onto=cli_opts.opt_onto)
+        elif github_subcommand == "retarget-pr":
+            branch = parsed_cli.branch if 'branch' in parsed_cli else git.get_current_branch()
+            machete_client.expect_in_managed_branches(branch)
+            machete_client.retarget_github_pr(head=branch,
+                                              ignore_if_missing=parsed_cli.ignore_if_missing if 'ignore_if_missing' in parsed_cli
+                                              else False)
+        elif github_subcommand == "sync":
+            machete_client.checkout_github_prs(pr_nos=[],
+                                               my_opened_prs=True)
+            machete_client.delete_unmanaged(opt_yes=False)
+            machete_client.delete_untracked(opt_yes=cli_opts.opt_yes)
+    elif cmd == "is-managed":
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+        branch = get_local_branch_short_name_from_arg_or_current_branch(cli_opts.opt_branch, git)
+        if branch is None or branch not in machete_client.managed_branches:
+            sys.exit(ExitCode.MACHETE_EXCEPTION)
+    elif cmd == "list":
+        category = parsed_cli.category
+        if category == 'slidable-after' and 'branch' not in parsed_cli_as_dict:
+            raise MacheteException(
+                f"`git machete list {category}` requires an extra <branch> argument")
+        elif category != 'slidable-after' and 'branch' in parsed_cli_as_dict:
+            raise MacheteException(
+                f"`git machete list {category}` does not expect extra arguments")
+
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+        res = []
+        if category == "addable":
+            def strip_remote_name(remote_branch: RemoteBranchShortName) -> LocalBranchShortName:
+                return LocalBranchShortName.of(re.sub("^[^/]+/", "", remote_branch))
+
+            remote_counterparts_of_local_branches = utils.map_truthy_only(
+                git.get_combined_counterpart_for_fetching_of_branch,
+                git.get_local_branches())
+            qualifying_remote_branches: List[RemoteBranchShortName] = \
+                excluding(git.get_remote_branches(), remote_counterparts_of_local_branches)
+            res = excluding(git.get_local_branches(), machete_client.managed_branches) + list(
+                map(strip_remote_name, qualifying_remote_branches))
+        elif category == "childless":
+            res = machete_client.get_childless_branches()
+        elif category == "managed":
+            res = machete_client.managed_branches
+        elif category == "slidable":
+            res = machete_client.get_slidable_branches()
+        elif category == "slidable-after":
+            machete_client.expect_in_managed_branches(parsed_cli.branch)
+            res = machete_client.get_slidable_after(parsed_cli.branch)
+        elif category == "unmanaged":
+            res = excluding(git.get_local_branches(), machete_client.managed_branches)
+        elif category == "with-overridden-fork-point":
+            res = list(
+                filter(
+                    lambda _branch: machete_client.has_any_fork_point_override_config(_branch),
+                    git.get_local_branches()))
+        else:  # should never happen
+            raise MacheteException(f"Invalid category: `{category}`")
+
+        if res:
+            print("\n".join(res))
+    elif cmd in {"log", alias_by_command["log"]}:
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+        branch = get_local_branch_short_name_from_arg_or_current_branch(cli_opts.opt_branch, git)
+        machete_client.log(branch)
+    elif cmd == "reapply":
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+        git.expect_no_operation_in_progress()
+        current_branch = git.get_current_branch()
+        if "fork_point" in parsed_cli and cli_opts.opt_fork_point:
+            machete_client.check_that_fork_point_is_ancestor_or_equal_to_tip_of_branch(
+                fork_point_hash=cli_opts.opt_fork_point, branch=current_branch)
+
+        reapply_fork_point = cli_opts.opt_fork_point or machete_client.fork_point(branch=current_branch, use_overrides=True)
+        if not reapply_fork_point:
+            raise MacheteException(f"Fork point not found for branch <b>{current_branch}</b>; use `--fork-point` flag")
+
+        git.rebase_onto_ancestor_commit(current_branch, reapply_fork_point, cli_opts.opt_no_interactive_rebase)
+    elif cmd == "show":
+        direction = parsed_cli.direction
+        branch = get_local_branch_short_name_from_arg_or_current_branch(cli_opts.opt_branch, git)
+        if direction == "current":
+            if 'branch' in parsed_cli:
                 raise MacheteException(
-                    "Invalid argument for `--return-to`. "
-                    "Valid arguments: `here|nearest-remaining|stay`.")
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
-            git.expect_no_operation_in_progress()
-            machete_client.traverse(
-                opt_fetch=cli_opts.opt_fetch,
-                opt_list_commits=cli_opts.opt_list_commits,
-                opt_merge=cli_opts.opt_merge,
-                opt_no_detect_squash_merges=cli_opts.opt_no_detect_squash_merges,
-                opt_no_edit_merge=cli_opts.opt_no_edit_merge,
-                opt_no_interactive_rebase=cli_opts.opt_no_interactive_rebase,
-                opt_push_tracked=cli_opts.opt_push_tracked,
-                opt_push_untracked=cli_opts.opt_push_untracked,
-                opt_return_to=cli_opts.opt_return_to,
-                opt_start_from=cli_opts.opt_start_from,
-                opt_yes=cli_opts.opt_yes)
-        elif cmd == "update":
-            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
-            git.expect_no_operation_in_progress()
-            if "fork_point" in parsed_cli and cli_opts.opt_fork_point:
-                machete_client.check_that_fork_point_is_ancestor_or_equal_to_tip_of_branch(
-                    fork_point_hash=cli_opts.opt_fork_point, branch=git.get_current_branch())
-            machete_client.update(
-                opt_merge=cli_opts.opt_merge,
-                opt_no_edit_merge=cli_opts.opt_no_edit_merge,
-                opt_no_interactive_rebase=cli_opts.opt_no_interactive_rebase,
-                opt_fork_point=cli_opts.opt_fork_point)
+                    '`show current` with a <branch> argument does not make sense')
+            print(branch)
+        else:
+            machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out,
+                                                verify_branches=False)
+            print(
+                '\n'.join(machete_client.parse_direction(
+                    direction,
+                    branch,
+                    allow_current=True,
+                    down_pick_mode=False
+                ))
+            )
+    elif cmd == "slide-out":
+        machete_client.read_definition_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)
+    elif cmd == "squash":
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+        git.expect_no_operation_in_progress()
+        current_branch = git.get_current_branch()
+        if "fork_point" in parsed_cli and cli_opts.opt_fork_point:
+            machete_client.check_that_fork_point_is_ancestor_or_equal_to_tip_of_branch(
+                fork_point_hash=cli_opts.opt_fork_point, branch=current_branch)
+
+        squash_fork_point = cli_opts.opt_fork_point or machete_client.fork_point(branch=current_branch, use_overrides=True)
+        if not squash_fork_point:
+            raise MacheteException(f"Fork point not found for branch <b>{current_branch}</b>; use `--fork-point` flag")
+
+        machete_client.squash(current_branch=current_branch, opt_fork_point=squash_fork_point)
+    elif cmd in {"status", alias_by_command["status"]}:
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+        machete_client.expect_at_least_one_managed_branch()
+        machete_client.status(
+            warn_when_branch_in_sync_but_fork_point_off=True,
+            opt_list_commits=cli_opts.opt_list_commits,
+            opt_list_commits_with_hashes=cli_opts.opt_list_commits_with_hashes,
+            opt_no_detect_squash_merges=cli_opts.opt_no_detect_squash_merges)
+    elif cmd in {"traverse", alias_by_command["traverse"]}:
+        if cli_opts.opt_start_from not in {"here", "root", "first-root"}:
+            raise MacheteException(
+                "Invalid argument for `--start-from`. "
+                "Valid arguments: `here|root|first-root`.")
+        if cli_opts.opt_return_to not in ("here", "nearest-remaining", "stay"):
+            raise MacheteException(
+                "Invalid argument for `--return-to`. "
+                "Valid arguments: `here|nearest-remaining|stay`.")
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+        git.expect_no_operation_in_progress()
+        machete_client.traverse(
+            opt_fetch=cli_opts.opt_fetch,
+            opt_list_commits=cli_opts.opt_list_commits,
+            opt_merge=cli_opts.opt_merge,
+            opt_no_detect_squash_merges=cli_opts.opt_no_detect_squash_merges,
+            opt_no_edit_merge=cli_opts.opt_no_edit_merge,
+            opt_no_interactive_rebase=cli_opts.opt_no_interactive_rebase,
+            opt_push_tracked=cli_opts.opt_push_tracked,
+            opt_push_untracked=cli_opts.opt_push_untracked,
+            opt_return_to=cli_opts.opt_return_to,
+            opt_start_from=cli_opts.opt_start_from,
+            opt_yes=cli_opts.opt_yes)
+    elif cmd == "update":
+        machete_client.read_definition_file(perform_interactive_slide_out=should_perform_interactive_slide_out)
+        git.expect_no_operation_in_progress()
+        if "fork_point" in parsed_cli and cli_opts.opt_fork_point:
+            machete_client.check_that_fork_point_is_ancestor_or_equal_to_tip_of_branch(
+                fork_point_hash=cli_opts.opt_fork_point, branch=git.get_current_branch())
+        machete_client.update(
+            opt_merge=cli_opts.opt_merge,
+            opt_no_edit_merge=cli_opts.opt_no_edit_merge,
+            opt_no_interactive_rebase=cli_opts.opt_no_interactive_rebase,
+            opt_fork_point=cli_opts.opt_fork_point)
+
+
+def main() -> None:
+    initial_current_directory: Optional[str] = utils.get_current_directory_or_none()
 
+    try:
+        launch(sys.argv[1:])
     except EOFError:
-        exit_script(ExitCode.END_OF_FILE_SIGNAL)
+        sys.exit(ExitCode.END_OF_FILE_SIGNAL)
     except KeyboardInterrupt:
-        exit_script(ExitCode.KEYBOARD_INTERRUPT)
+        sys.exit(ExitCode.KEYBOARD_INTERRUPT)
     except (argparse.ArgumentError, argparse.ArgumentTypeError) as e:
         print(get_short_general_usage())
-        exit_script(ExitCode.ARGUMENT_ERROR, e)
+        print(e, file=sys.stderr)
+        sys.exit(ExitCode.ARGUMENT_ERROR)
     except MacheteException as e:
-        exit_script(ExitCode.MACHETE_EXCEPTION, e)
+        print(e, file=sys.stderr)
+        sys.exit(ExitCode.MACHETE_EXCEPTION)
     except StopInteraction:
         pass
     finally:
@@ -869,9 +851,5 @@ def launch(orig_args: List[str]) -> None:
                  f"{os.path.abspath(nearest_existing_parent_directory)}")
 
 
-def main() -> None:
-    launch(sys.argv[1:])
-
-
 if __name__ == "__main__":
     main()
diff --git a/git_machete/client.py b/git_machete/client.py
index 7544cdd..d03517a 100644
--- a/git_machete/client.py
+++ b/git_machete/client.py
@@ -5,7 +5,7 @@ import os
 import shutil
 import sys
 from collections import OrderedDict
-from typing import Callable, Dict, Generator, List, Optional, Tuple
+from typing import Callable, Dict, Iterator, List, Optional, Tuple
 
 from git_machete import git_config_keys, utils
 from git_machete.annotation import Annotation
@@ -171,9 +171,8 @@ class MacheteClient:
                     "Slide them out from the definition file?" + get_pretty_choices("y", "e[dit]", "N"),
                     opt_yes_msg=None, opt_yes=False)
         else:
-            if len(invalid_branches) > 0:
-                print(f"Warning: sliding invalid branches: {', '.join(f'{bold(branch)}' for branch in invalid_branches)} "
-                      f"out of the definition file", file=sys.stderr)
+            print(f"Warning: sliding invalid branches: {', '.join(f'{bold(branch)}' for branch in invalid_branches)} "
+                  f"out of the definition file", file=sys.stderr)
             ans = 'y'
 
         def recursive_slide_out_invalid_branches(branch_: LocalBranchShortName) -> List[LocalBranchShortName]:
@@ -346,8 +345,7 @@ class MacheteClient:
                                         "Rebase onto the inferred upstream <b>%s</b>?" + get_pretty_choices('y', 'N')),
                 prompt_if_inferred_yes_opt_msg=("Branch <b>%s</b> not found in the tree of branch dependencies. "
                                                 "Rebasing onto the inferred upstream <b>%s</b>..."))
-            rebase_fork_point = opt_fork_point or self.fork_point(
-                current_branch, use_overrides=True, opt_no_detect_squash_merges=False)
+            rebase_fork_point = opt_fork_point or self.fork_point(current_branch, use_overrides=True)
 
             if not rebase_fork_point:
                 raise MacheteException(f"Fork point not found for branch <b>{current_branch}</b>; use `--fork-point` flag")
@@ -488,7 +486,7 @@ class MacheteClient:
             f"\nThe existing definition file will be backed up as {self._definition_file_path}~"
             if do_backup else "")
         msg = f"Save the above tree to {self._definition_file_path}?{backup_msg}" + get_pretty_choices('y', 'e[dit]', 'N')
-        opt_yes_msg = f"Saving the above tree to {self._definition_file_path}... {backup_msg}"
+        opt_yes_msg = f"Saving the above tree to {self._definition_file_path}...{backup_msg}"
         ans = self.ask_if(msg, opt_yes_msg, opt_yes=opt_yes)
         if ans in ('y', 'yes'):
             if do_backup:
@@ -578,8 +576,7 @@ class MacheteClient:
                 self.__git.merge(new_upstream, new_downstream, opt_no_edit_merge)
             else:
                 print(f"Rebasing {bold(new_downstream)} onto {bold(new_upstream)}...")
-                down_fork_point = opt_down_fork_point or \
-                    self.fork_point(new_downstream, use_overrides=True, opt_no_detect_squash_merges=False)
+                down_fork_point = opt_down_fork_point or self.fork_point(new_downstream, use_overrides=True)
                 if not down_fork_point:
                     raise MacheteException(f"Fork point not found for branch <b>{new_downstream}</b>; use `--down-fork-point` flag")
                 self.__git.rebase(
@@ -602,8 +599,7 @@ class MacheteClient:
                 not self.__is_merged_to_upstream(bd, opt_no_detect_squash_merges=False) and
                 self.__git.is_ancestor_or_equal(branch.full_name(), bd.full_name()) and
                 (self.__get_overridden_fork_point(bd) or
-                 self.__git.get_commit_hash_by_revision(branch) ==
-                 self.fork_point(bd, use_overrides=False, opt_no_detect_squash_merges=False)))
+                 self.__git.get_commit_hash_by_revision(branch) == self.fork_point(bd, use_overrides=False)))
 
         candidate_downstreams = list(filter(connected_with_green_edge, down_branches))
         if not candidate_downstreams:
@@ -754,9 +750,7 @@ class MacheteClient:
                     upstream and
                     not (self.__git.is_ancestor_or_equal(upstream.full_name(), branch.full_name()) and
                          (self.__git.get_commit_hash_by_revision(upstream) ==
-                          self.fork_point(
-                              branch, use_overrides=True,
-                              opt_no_detect_squash_merges=opt_no_detect_squash_merges)))
+                          self.fork_point(branch, use_overrides=True)))
                 )
                 if needs_parent_sync and branch in self.annotations:
                     needs_parent_sync = self.annotations[branch].qualifiers.rebase
@@ -834,7 +828,7 @@ class MacheteClient:
                             print("\nMerge in progress; stopping the traversal")
                             return
                     else:
-                        fork_point = self.fork_point(branch, use_overrides=True, opt_no_detect_squash_merges=opt_no_detect_squash_merges)
+                        fork_point = self.fork_point(branch, use_overrides=True)
                         if not fork_point:
                             raise MacheteException(f"Fork point not found for branch <b>{branch}</b>; "
                                                    f"use `git machete fork-point {branch} --override-to...`")
@@ -1288,16 +1282,14 @@ class MacheteClient:
     def fork_point(
             self,
             branch: LocalBranchShortName,
-            use_overrides: bool,
-            *,
-            opt_no_detect_squash_merges: bool
+            use_overrides: bool
     ) -> Optional[FullCommitHash]:
         hash, containing_branch_pairs = self.__fork_point_and_containing_branch_pairs(branch, use_overrides)
         return FullCommitHash.of(hash) if hash else None
 
     def diff(self, *, branch: Optional[LocalBranchShortName], opt_stat: bool) -> None:
         diff_branch = branch or self.__git.get_current_branch()
-        fork_point = self.fork_point(diff_branch, use_overrides=True, opt_no_detect_squash_merges=False)
+        fork_point = self.fork_point(diff_branch, use_overrides=True)
         if not fork_point:
             raise MacheteException(f"Fork point not found for branch <b>{diff_branch}</b>; "
                                    f"use `git machete fork-point {diff_branch} --override-to...`")
@@ -1309,7 +1301,7 @@ class MacheteClient:
             format_with_stat=opt_stat)
 
     def log(self, branch: LocalBranchShortName) -> None:
-        fork_point = self.fork_point(branch, use_overrides=True, opt_no_detect_squash_merges=False)
+        fork_point = self.fork_point(branch, use_overrides=True)
         if not fork_point:
             raise MacheteException(f"Fork point not found for branch <b>{branch}</b>; "
                                    f"use `git machete fork-point {branch} --override-to...`")
@@ -1598,15 +1590,13 @@ class MacheteClient:
         else:
             raise MacheteException(f"Invalid direction: `{param}`; expected: {allowed_directions(allow_current)}")
 
-    def __match_log_to_filtered_reflogs(self,
-                                        branch: LocalBranchShortName
-                                        ) -> Generator[Tuple[FullCommitHash, List[BranchPair]], None, None]:
+    def __match_log_to_filtered_reflogs(self, branch: LocalBranchShortName) -> Iterator[Tuple[FullCommitHash, List[BranchPair]]]:
 
         if branch not in self.__git.get_local_branches():
             raise MacheteException(f"{bold(branch)} is not a local branch")
 
         if self.__branch_pairs_by_hash_in_reflog is None:
-            def generate_entries() -> Generator[Tuple[FullCommitHash, BranchPair], None, None]:
+            def generate_entries() -> Iterator[Tuple[FullCommitHash, BranchPair]]:
                 for lb in self.__git.get_local_branches():
                     lb_hashes = set()
                     for hash_ in self.filtered_reflog(lb):
@@ -1629,7 +1619,7 @@ class MacheteClient:
                 else:
                     self.__branch_pairs_by_hash_in_reflog[hash] = [branch_pair]
 
-            def log_result() -> Generator[str, None, None]:
+            def log_result() -> Iterator[str]:
                 branch_pairs_: List[BranchPair]
                 hash_: FullCommitHash
                 assert self.__branch_pairs_by_hash_in_reflog is not None
@@ -2186,7 +2176,7 @@ class MacheteClient:
             if ignore_if_missing:
                 pr = None
             else:
-                raise MacheteException(err.parameter)
+                raise MacheteException(err.msg)
         if not pr:
             return
         debug(f'found {pr}')
@@ -2297,7 +2287,7 @@ class MacheteClient:
         description_path = self.__git.get_main_git_subpath('info', 'description')
         description: str = utils.slurp_file_or_empty(description_path)
 
-        fork_point = self.fork_point(head, use_overrides=True, opt_no_detect_squash_merges=False)
+        fork_point = self.fork_point(head, use_overrides=True)
         if not fork_point:
             raise MacheteException(f"Could not find a fork-point for branch {bold(head)}.")
         commits: List[GitLogEntry] = self.__git.get_commits_between(fork_point, head)
diff --git a/git_machete/exceptions.py b/git_machete/exceptions.py
index 0ea6a52..d2d4a90 100644
--- a/git_machete/exceptions.py
+++ b/git_machete/exceptions.py
@@ -5,10 +5,10 @@ from git_machete import utils
 
 class MacheteException(Exception):
     def __init__(self, msg: str, apply_fmt: bool = True) -> None:
-        self.parameter: str = utils.fmt(msg) if apply_fmt else msg
+        self.msg: str = utils.fmt(msg) if apply_fmt else msg
 
     def __str__(self) -> str:
-        return str(self.parameter)
+        return str(self.msg)
 
 
 class StopInteraction(Exception):
@@ -24,10 +24,7 @@ class UnprocessableEntityHTTPError(MacheteException):
     """
 
     def __init__(self, msg: str) -> None:
-        self.msg: str = msg
-
-    def __str__(self) -> str:
-        return str(self.msg)
+        super().__init__(msg, apply_fmt=False)
 
 
 class ExitCode(IntEnum):
diff --git a/git_machete/git_operations.py b/git_machete/git_operations.py
index be88185..5cdea0e 100644
--- a/git_machete/git_operations.py
+++ b/git_machete/git_operations.py
@@ -4,8 +4,8 @@ import shlex
 import string
 import sys
 from pathlib import Path
-from typing import (Any, Callable, Dict, Generator, Iterator, List, Match,
-                    NamedTuple, Optional, Set, Tuple)
+from typing import (Any, Callable, Dict, Iterator, List, Match, NamedTuple,
+                    Optional, Set, Tuple)
 
 from git_machete import git_config_keys, utils
 from git_machete.constants import (MAX_COUNT_FOR_INITIAL_LOG,
@@ -613,7 +613,7 @@ class GitContext:
     # Since getting the full history of a branch can be an expensive operation for large repositories
     # (compared to all other underlying git operations), there's a simple optimization in place:
     # we first fetch only a couple of first commits in the history, and only fetch the rest if needed.
-    def spoonfeed_log_hashes(self, branch_full_hash: FullCommitHash) -> Generator[FullCommitHash, None, None]:
+    def spoonfeed_log_hashes(self, branch_full_hash: FullCommitHash) -> Iterator[FullCommitHash]:
         if branch_full_hash not in self.__initial_log_hashes_cached:
             self.__initial_log_hashes_cached[branch_full_hash] = self.__get_log_hashes(branch_full_hash,
                                                                                        max_count=MAX_COUNT_FOR_INITIAL_LOG)
@@ -632,7 +632,7 @@ class GitContext:
         # %gs - reflog subject
         local_branches: List[str] = [branch.full_name() for branch in self.get_local_branches()]  # str to match _popen_git() input type
 
-        def get_counterpart_branches() -> Generator[str, None, None]:
+        def get_counterpart_branches() -> Iterator[str]:
             for branch in self.get_local_branches():
                 counterpart = self.get_combined_counterpart_for_fetching_of_branch(branch)
                 if counterpart:
diff --git a/requirements.isort.txt b/requirements.isort.txt
index d3cda9c..a1c333a 100644
--- a/requirements.isort.txt
+++ b/requirements.isort.txt
@@ -1,1 +1,2 @@
+autoflake==2.1.1
 isort==5.12.0
diff --git a/requirements.pep8.txt b/requirements.pep8.txt
index 30c5e1e..c18cca0 100644
--- a/requirements.pep8.txt
+++ b/requirements.pep8.txt
@@ -1,2 +1,1 @@
-autoflake==2.1.1
 autopep8==2.0.2
diff --git a/requirements.pypi-deploy.txt b/requirements.pypi-deploy.txt
new file mode 100644
index 0000000..f756713
--- /dev/null
+++ b/requirements.pypi-deploy.txt
@@ -0,0 +1,2 @@
+twine==4.0.2
+wheel==0.40.0
diff --git a/tox.ini b/tox.ini
index c5ce8c3..0bbc631 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,9 @@
 [tox]
 # Environments selected for frequent execution during development
-envlist = pep8,isort,mypy-py311
+# 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)
+# that don't seem to ever get fixed by autopep8.
+envlist = pep8,isort,pep8-check,mypy-py311
 minversion = 2.3.2
 skipsdist = True
 
@@ -23,12 +26,11 @@ deps =
   -r{toxinidir}/requirements.testenv.py36-txt
 
 [testenv:pep8]
-description = "Apply PEP8 formatting + remove unused imports"
+description = "Apply PEP8 formatting"
 deps =
   -r{toxinidir}/requirements.pep8.txt
 # E402 - Module level import not at top of file
 commands =
-  autoflake --in-place --recursive --remove-all-unused-imports .
   autopep8 --ignore=E402 --in-place --recursive .
 
 [testenv:pep8-check]
@@ -39,18 +41,21 @@ commands = flake8 --enable-extensions=FS003
 
 # Set flake8 configuration options which are used by the `flake8` command in [testenv:pep8] and [testenv:pep8-check]
 [flake8]
+# W291 trailing whitespace in multiline strings
 # W504 ignore wrap line after binary operator
-ignore = W504
+ignore = W291, W504
 show-source = True
 max-line-length = 140
 exclude = ./.*,build,dist,*egg,venv, git_machete/generated_docs.py
 import-order-style = pep8
 
 [testenv:isort]
-description = "Sort the imports in Python code"
+description = "Tidy up imports in Python code"
 deps =
   -r{toxinidir}/requirements.isort.txt
-commands = isort .
+commands =
+  autoflake --in-place --recursive --remove-all-unused-imports .
+  isort .
 
 [testenv:isort-check]
 description = "Check if imports in Python code are correctly sorted"
@@ -85,14 +90,9 @@ commands =
 
 [coverage:report]
 exclude_lines =
-  def main\(\)
   except EOFError:
   except KeyboardInterrupt:
   if __name__ == .__main__.:
-  # We're slightly cheating here, as we're skipping all lines where exceptions are raised from coverage report.
-  # Still, covering them with tests has a limited value,
-  # and they divert attention from the more important happy-path logic in coverage reports.
-  ^ *raise .*$
 
 [testenv:sphinx-html]
 description = "Build Sphinx documentation in HTML"


Test patch:
diff --git a/tests/base_test.py b/tests/base_test.py
index 2ae8fc6..a02416b 100644
--- a/tests/base_test.py
+++ b/tests/base_test.py
@@ -38,14 +38,14 @@ class GitRepositorySandbox:
         self.file_counter = 0
 
     def execute(self, command: str) -> "GitRepositorySandbox":
-        subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
+        subprocess.check_call(command, shell=True)
         return self
 
     def new_repo(self, *args: str, switch_dir_to_new_repo: bool = True) -> "GitRepositorySandbox":
         previous_dir = os.getcwd()
         os.chdir(args[0])
         opts = args[1:]
-        self.execute(f"git init {' '.join(opts)}")
+        self.execute(f"git init --quiet {' '.join(opts)}")
         if not switch_dir_to_new_repo:
             os.chdir(previous_dir)
         return self
@@ -103,13 +103,21 @@ class GitRepositorySandbox:
         self.execute(f'git remote add {remote} {url}')
         return self
 
-    def remove_remote(self, remote: str) -> "GitRepositorySandbox":
+    def remove_remote(self, remote: str = 'origin') -> "GitRepositorySandbox":
         self.execute(f'git remote remove {remote}')
         return self
 
     def get_local_branches(self) -> List[str]:
-        return subprocess.check_output("git for-each-ref refs/heads/ --format='%(refname:short)'",
-                                       stderr=subprocess.STDOUT, shell=True, encoding='utf-8').splitlines()
+        return popen("git for-each-ref refs/heads/ --format='%(refname:short)'").splitlines()
+
+    def is_ancestor(self, earlier: str, later: str) -> bool:
+        return subprocess.call(f"git merge-base --is-ancestor  '{earlier}'  '{later}'", shell=True) == 0
+
+    def get_current_commit_hash(self) -> str:
+        return self.get_commit_hash("HEAD")
+
+    def get_commit_hash(self, revision: str) -> str:
+        return popen(f"git rev-parse {revision}")
 
     def set_git_config_key(self, key: str, value: str) -> "GitRepositorySandbox":
         self.execute(f'git config {key} {value}')
@@ -127,6 +135,10 @@ class GitRepositorySandbox:
             f.write(file_content)
         return self
 
+    def remove_file(self, file_path: str) -> "GitRepositorySandbox":
+        self.execute(f"rm -rf './{file_path}'")
+        return self
+
     def set_file_executable(self, file_name: str) -> "GitRepositorySandbox":
         os.chmod(file_name, 0o700)
         return self
diff --git a/tests/mockers.py b/tests/mockers.py
index b964acf..d456b26 100644
--- a/tests/mockers.py
+++ b/tests/mockers.py
@@ -6,12 +6,14 @@ import sys
 import textwrap
 from contextlib import (_GeneratorContextManager, redirect_stderr,
                         redirect_stdout)
-from typing import Any, Callable, Iterable, Iterator, Optional
+from typing import Any, Callable, Iterable, Iterator
+
+import pytest
 
 from git_machete import cli, utils
-from git_machete.git_operations import FullCommitHash
+from git_machete.exceptions import MacheteException
 from git_machete.utils import dim
-from tests.base_test import git, popen
+from tests.base_test import git
 
 """
 Usage: mockers.py
@@ -64,7 +66,7 @@ def launch_command(*args: str) -> str:
         return out.getvalue()
 
 
-def assert_command(cmds: Iterable[str], expected_result: str) -> None:
+def assert_success(cmds: Iterable[str], expected_result: str) -> None:
     if expected_result.startswith("\n"):
         # removeprefix is only available since Python 3.9
         expected_result = expected_result[1:]
@@ -73,36 +75,36 @@ def assert_command(cmds: Iterable[str], expected_result: str) -> None:
     assert actual_result == expected_result
 
 
-def rewrite_definition_file(new_body: str, dedent: bool = True) -> None:
-    if dedent:
-        new_body = textwrap.dedent(new_body)
-    definition_file_path = git.get_main_git_subpath("machete")
-    with open(os.path.join(os.getcwd(), definition_file_path), 'w') as def_file:
-        def_file.writelines(new_body)
-
+def assert_failure(cmds: Iterable[str], expected_result: str) -> None:
+    if expected_result.startswith("\n"):
+        # removeprefix is only available since Python 3.9
+        expected_result = expected_result[1:]
+    expected_result = textwrap.dedent(expected_result)
 
-def get_current_commit_hash() -> FullCommitHash:
-    """Returns hash of the commit of the current branch head."""
-    return get_commit_hash("HEAD")
+    with pytest.raises(MacheteException) as e:
+        launch_command(*cmds)
+    assert e.value.msg == expected_result
 
 
-def get_commit_hash(revision: str) -> FullCommitHash:
-    """Returns hash of the commit pointed by the given revision."""
-    return FullCommitHash.of(popen(f"git rev-parse {revision}"))
+def rewrite_definition_file(new_body: str) -> None:
+    new_body = textwrap.dedent(new_body)
+    definition_file_path = git.get_main_git_subpath("machete")
+    with open(os.path.join(os.getcwd(), definition_file_path), 'w') as def_file:
+        def_file.writelines(new_body)
 
 
-def mock_run_cmd(cmd: str, *args: str, **kwargs: Any) -> int:
-    """Execute command in the new subprocess but redirect the stdout and stderr together to the PIPE's stdout"""
+def mock_run_cmd_and_discard_output(cmd: str, *args: str, **kwargs: Any) -> int:
+    """Execute command in the new subprocess but discard stdout and stderr"""
     completed_process: subprocess.CompletedProcess[bytes] = subprocess.run(
         [cmd] + list(args), stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
-    exit_code: int = completed_process.returncode
 
+    exit_code: int = completed_process.returncode
     if exit_code != 0:
         print(dim(f"<exit code: {exit_code}>\n"), file=sys.stderr)
     return completed_process.returncode
 
 
-def mock_run_cmd_and_forward_stdout(cmd: str, *args: str, **kwargs: Any) -> int:
+def mock_run_cmd_and_forward_output(cmd: str, *args: str, **kwargs: Any) -> int:
     """Execute command in the new subprocess but capture together process's stdout and stderr and load it into sys.stdout via
     `print(completed_process.stdout.decode('utf-8'))`. This sys.stdout is later being redirected via the `redirect_stdout` in
     `launch_command()` and gets returned by this function. Below is shown the chain of function calls that presents this mechanism:
@@ -115,30 +117,26 @@ def mock_run_cmd_and_forward_stdout(cmd: str, *args: str, **kwargs: Any) -> int:
     completed_process: subprocess.CompletedProcess[bytes] = subprocess.run(
         [cmd] + list(args), stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwargs)
     print(completed_process.stdout.decode('utf-8'))
+
     exit_code: int = completed_process.returncode
     if exit_code != 0:
         print(dim(f"<exit code: {exit_code}>\n"), file=sys.stderr)
     return exit_code
 
 
-def mock_ask_if(*args: str, **kwargs: Any) -> str:
+def mock_input_returning_y(msg: str) -> str:
+    print(msg)
     return 'y'
 
 
-def mock_ask_if_returning(answer: str) -> Callable[..., str]:
-    return lambda *args, **kwargs: answer
-
-
-def mock_should_perform_interactive_slide_out(cmd: str) -> bool:
-    return True
-
-
-def mock_exit_script(status_code: int, error: Optional[BaseException] = None) -> None:
-    if error:
-        raise error
-    else:
-        sys.exit(status_code)
+def mock_input_returning(*answers: str) -> Callable[..., str]:
+    class Wrapper(object):
+        def __init__(self) -> None:
+            self.index = -1
 
+        def __call__(self, msg: str) -> str:
+            print(msg)
+            self.index += 1
+            return answers[self.index]
 
-def mock_exit_script_no_exit(status_code: int, error: Optional[BaseException] = None) -> None:
-    return
+    return Wrapper()
diff --git a/tests/test_add.py b/tests/test_add.py
index 4a6f0b3..9573ea8 100644
--- a/tests/test_add.py
+++ b/tests/test_add.py
@@ -1,16 +1,14 @@
 from typing import Any
 
 from .base_test import BaseTest
-from .mockers import assert_command, mock_run_cmd, rewrite_definition_file
+from .mockers import (assert_failure, assert_success,
+                      mock_run_cmd_and_discard_output, rewrite_definition_file)
 
 
 class TestAdd(BaseTest):
 
     def test_add(self, mocker: Any) -> None:
-        """
-        Verify the behaviour of a 'git machete add' command.
-        """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("master")
@@ -33,7 +31,7 @@ class TestAdd(BaseTest):
         self.repo_sandbox.new_branch("bugfix/feature_fail")
 
         # Test `git machete add` without providing the branch name
-        assert_command(
+        assert_success(
             ['add', '-y'],
             'Adding bugfix/feature_fail onto the inferred upstream (parent) branch develop\n'
             'Added branch bugfix/feature_fail onto develop\n'
@@ -41,7 +39,7 @@ class TestAdd(BaseTest):
 
         self.repo_sandbox.check_out('develop')
         self.repo_sandbox.new_branch("bugfix/some_feature")
-        assert_command(
+        assert_success(
             ['add', '-y', 'bugfix/some_feature'],
             'Adding bugfix/some_feature onto the inferred upstream (parent) branch develop\n'
             'Added branch bugfix/some_feature onto develop\n'
@@ -49,7 +47,7 @@ class TestAdd(BaseTest):
 
         self.repo_sandbox.check_out('develop')
         self.repo_sandbox.new_branch("bugfix/another_feature")
-        assert_command(
+        assert_success(
             ['add', '-y', 'refs/heads/bugfix/another_feature'],
             'Adding bugfix/another_feature onto the inferred upstream (parent) branch develop\n'
             'Added branch bugfix/another_feature onto develop\n'
@@ -58,7 +56,7 @@ class TestAdd(BaseTest):
         # test with --onto option
         self.repo_sandbox.new_branch("chore/remove_indentation")
 
-        assert_command(
+        assert_success(
             ['add', '--onto=feature'],
             'Added branch chore/remove_indentation onto feature\n'
         )
@@ -67,7 +65,7 @@ class TestAdd(BaseTest):
         """
         Verify the behaviour of a 'git machete add' command in the special case when a remote branch is checked out locally.
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("master")
@@ -78,15 +76,27 @@ class TestAdd(BaseTest):
             .delete_branch("feature/foo")
         )
 
-        assert_command(
+        assert_success(
             ['add', '-y', 'foo'],
             'A local branch foo does not exist. Creating out of the current HEAD\n'
             'Added branch foo as a new root\n'
         )
 
-        assert_command(
+        assert_success(
             ['add', '-y', '--as-root', 'feature/foo'],
             'A local branch feature/foo does not exist, but a remote branch origin/feature/foo exists.\n'
             'Checking out feature/foo locally...\n'
             'Added branch feature/foo as a new root\n'
         )
+
+    def test_add_already_managed_branch(self) -> None:
+        (
+            self.repo_sandbox.new_branch("master")
+            .commit("master commit.")
+            .new_branch("develop")
+            .commit("develop commit.")
+        )
+
+        rewrite_definition_file("master\n  develop")
+
+        assert_failure(['add', 'develop'], 'Branch develop already exists in the tree of branch dependencies')
diff --git a/tests/test_advance.py b/tests/test_advance.py
index b84e78e..333b9aa 100644
--- a/tests/test_advance.py
+++ b/tests/test_advance.py
@@ -1,43 +1,61 @@
 from typing import Any
 
-import pytest
-
 from .base_test import BaseTest
-from .mockers import (get_commit_hash, get_current_commit_hash, launch_command,
-                      mock_run_cmd, rewrite_definition_file)
+from .mockers import (assert_failure, assert_success, launch_command,
+                      mock_input_returning, mock_run_cmd_and_discard_output,
+                      rewrite_definition_file)
 
 
 class TestAdvance(BaseTest):
 
     def test_advance_for_no_downstream_branches(self, mocker: Any) -> None:
-        """Verify behaviour of a 'git machete advance' command.
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
-        Verify that 'git machete advance' raises an error when current branch
-        has no downstream branches.
+        self.repo_sandbox.new_branch("root").commit()
+        rewrite_definition_file("root")
 
-        """
+        assert_failure(["advance"], "root does not have any downstream (child) branches to advance towards")
 
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+    def test_advance_for_no_applicable_downstream_branches(self, mocker: Any) -> None:
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
-            self.repo_sandbox.new_branch("root")
-                .commit()
+            self.repo_sandbox
+            .new_branch("master")
+            .commit()
+            .new_branch("develop")
+            .commit()
+            .check_out("master")
+            .commit()
         )
-        body: str = "root"
-        rewrite_definition_file(body)
+        rewrite_definition_file("master\n  develop")
 
-        with pytest.raises(SystemExit):
-            launch_command("advance")
+        assert_failure(["advance"], "No downstream (child) branch of master is connected to master with a green edge")
 
-    def test_advance_with_push_for_one_downstream_branch(self, mocker: Any) -> None:
-        """Verify behaviour of a 'git machete advance' command.
+    def test_advance_with_immediate_cancel(self, mocker: Any) -> None:
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
+        (
+            self.repo_sandbox
+            .new_branch("master")
+            .commit()
+            .new_branch("develop")
+            .commit()
+            .check_out("master")
+        )
+        rewrite_definition_file("master\n  develop")
+
+        mocker.patch("builtins.input", mock_input_returning("n"))
+        assert_success(["advance"], "Fast-forward master to match develop? (y, N) \n")
+
+    def test_advance_with_push_for_one_downstream_branch(self, mocker: Any) -> None:
+        """
         Verify that when there is only one, rebased downstream branch of a
         current branch 'git machete advance' merges commits from that branch,
         pushes the current branch and slides out child branches of the downstream branch.
-        Also, it edits the git machete discovered tree to reflect new dependencies.
+        Also, it modifies the branch layout to reflect new dependencies.
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("root")
@@ -51,14 +69,14 @@ class TestAdvance(BaseTest):
                 level-1-branch
             """
         rewrite_definition_file(body)
-        level_1_commit_hash = get_current_commit_hash()
+        level_1_commit_hash = self.repo_sandbox.get_current_commit_hash()
 
         self.repo_sandbox.check_out("root")
         self.repo_sandbox.push()
         launch_command("advance", "-y")
 
-        root_commit_hash = get_current_commit_hash()
-        origin_root_commit_hash = get_commit_hash("origin/root")
+        root_commit_hash = self.repo_sandbox.get_current_commit_hash()
+        origin_root_commit_hash = self.repo_sandbox.get_commit_hash("origin/root")
 
         assert level_1_commit_hash == root_commit_hash, \
             ("Verify that when there is only one, rebased downstream branch of a "
@@ -74,56 +92,60 @@ class TestAdvance(BaseTest):
              "tree is updated.")
 
     def test_advance_without_push_for_one_downstream_branch(self, mocker: Any) -> None:
-        """Verify behaviour of a 'git machete advance' command.
-
-        Verify that when there is only one, rebased downstream branch of a
-        current branch 'git machete advance' merges commits from that branch
-        and slides out child branches of the downstream branch.
-        Also, it edits the git machete discovered tree to reflect new dependencies.
-        """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
-            self.repo_sandbox.new_branch("root")
+            self.repo_sandbox
+            .remove_remote()
+            .new_branch("root")
             .commit()
             .new_branch("level-1-branch")
             .commit()
+            .new_branch("level-2a-branch")
+            .commit()
+            .check_out("level-1-branch")
+            .new_branch("level-2b-branch")
+            .commit()
         )
 
         body: str = \
             """
             root
                 level-1-branch
+                    level-2a-branch
+                    level-2b-branch
             """
         rewrite_definition_file(body)
-        level_1_commit_hash = get_current_commit_hash()
 
         self.repo_sandbox.check_out("root")
         launch_command("advance", "-y")
 
-        root_commit_hash = get_current_commit_hash()
+        assert self.repo_sandbox.get_commit_hash("level-1-branch") == self.repo_sandbox.get_commit_hash("root")
 
-        assert level_1_commit_hash == root_commit_hash, \
-            ("Verify that when there is only one, rebased downstream branch of a "
-             "current branch, then 'git machete advance' merges commits from that branch "
-             "and slides out child branches of the downstream branch.")
-        assert "level-1-branch" not in launch_command("status"), \
-            ("Verify that branch to which advance was performed is removed "
-             "from the git-machete tree and the structure of the git machete "
-             "tree is updated.")
+        assert_success(
+            ["status", "-l"],
+            """
+            root *
+            |
+            | Some commit message.
+            o-level-2a-branch
+            |
+            | Some commit message.
+            o-level-2b-branch
+            """
+        )
 
     def test_advance_for_a_few_possible_downstream_branches_and_yes_option(self, mocker: Any) -> None:
-        """Verify behaviour of a 'git machete advance' command.
-
+        """
         Verify that 'git machete advance -y' raises an error when current branch
         has more than one synchronized downstream branch and option '-y' is passed.
-
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("root")
             .commit()
+            .push()
             .new_branch("level-1a-branch")
             .commit()
             .check_out("root")
@@ -140,5 +162,31 @@ class TestAdvance(BaseTest):
             """
         rewrite_definition_file(body)
 
-        with pytest.raises(SystemExit):
-            launch_command("advance", '-y')
+        expected_error_message = "More than one downstream (child) branch of root " \
+                                 "is connected to root with a green edge and -y/--yes option is specified"
+        assert_failure(['advance', '-y'], expected_error_message)
+
+        mocker.patch("builtins.input", mock_input_returning("1", "N", "n"))
+        assert_success(
+            ["advance"],
+            """
+            [1] level-1a-branch
+            [2] level-1b-branch
+            Specify downstream branch towards which root is to be fast-forwarded or hit <return> to skip: 
+
+            Branch root is now fast-forwarded to match level-1a-branch. Push root to origin? (y, N) 
+
+            Branch root is now fast-forwarded to match level-1a-branch. Slide level-1a-branch out of the tree of branch dependencies? (y, N) 
+            """
+        )
+
+        assert_success(
+            ["status"],
+            """
+            root * (ahead of origin)
+            |
+            m-level-1a-branch (untracked)
+            |
+            x-level-1b-branch (untracked)
+            """
+        )
diff --git a/tests/test_anno.py b/tests/test_anno.py
index 2f9f59a..bebc627 100644
--- a/tests/test_anno.py
+++ b/tests/test_anno.py
@@ -1,5 +1,5 @@
 from .base_test import BaseTest
-from .mockers import assert_command, launch_command, rewrite_definition_file
+from .mockers import assert_success, launch_command, rewrite_definition_file
 
 
 class TestAnno(BaseTest):
@@ -29,7 +29,7 @@ class TestAnno(BaseTest):
 
         # Test `git machete anno` without providing the branch name
         launch_command('anno', 'Custom annotation for `develop` branch')
-        assert_command(
+        assert_success(
             ["status"],
             """
             master (untracked)
@@ -41,7 +41,7 @@ class TestAnno(BaseTest):
         )
 
         launch_command('anno', '-b=feature', 'Custom annotation for `feature` branch')
-        assert_command(
+        assert_success(
             ["status"],
             """
             master (untracked)
@@ -53,7 +53,7 @@ class TestAnno(BaseTest):
         )
 
         launch_command('anno', '-b=refs/heads/feature', 'Custom annotation for `feature` branch')
-        assert_command(
+        assert_success(
             ["status"],
             """
             master (untracked)
@@ -66,7 +66,7 @@ class TestAnno(BaseTest):
 
         # check if annotation qualifiers are parsed correctly and that they can be overwritten by `git machete anno`
         launch_command('anno', '-b=refs/heads/feature', 'push=no Custom annotation for `feature` branch rebase=no')
-        assert_command(
+        assert_success(
             ["status"],
             """
             master (untracked)
@@ -77,7 +77,7 @@ class TestAnno(BaseTest):
             """,
         )
         launch_command('anno', '-b=refs/heads/feature', 'Custom annotation for `feature` branch')
-        assert_command(
+        assert_success(
             ["status"],
             """
             master (untracked)
@@ -88,22 +88,22 @@ class TestAnno(BaseTest):
             """,
         )
 
-        assert_command(
+        assert_success(
             ['anno'],
             'Custom annotation for `develop` branch\n'
         )
 
-        assert_command(
+        assert_success(
             ['anno', '-b', 'feature'],
             'Custom annotation for `feature` branch\n'
         )
 
-        assert_command(
+        assert_success(
             ['anno', '-b', 'feature', ''],
             ""
         )
 
-        assert_command(
+        assert_success(
             ['anno', '-b', 'feature'],
             ""
         )
diff --git a/tests/test_clean.py b/tests/test_clean.py
index f21dd0a..7449685 100644
--- a/tests/test_clean.py
+++ b/tests/test_clean.py
@@ -1,23 +1,15 @@
-import subprocess
 from typing import Any
 
-import pytest
-
 from .base_test import BaseTest
-from .mockers import (assert_command, launch_command, mock_ask_if,
-                      mock_run_cmd, mock_should_perform_interactive_slide_out,
-                      rewrite_definition_file)
+from .mockers import (assert_success, launch_command, mock_input_returning_y,
+                      mock_run_cmd_and_discard_output, rewrite_definition_file)
 
 
 class TestClean(BaseTest):
 
     def test_clean(self, mocker: Any) -> None:
-        mocker.patch(
-            'git_machete.client.MacheteClient.should_perform_interactive_slide_out',
-            mock_should_perform_interactive_slide_out,
-        )
-        mocker.patch('git_machete.client.MacheteClient.ask_if', mock_ask_if)
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)
+        mocker.patch('builtins.input', mock_input_returning_y)
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         (
             self.repo_sandbox.new_branch('master')
                 .commit()
@@ -60,16 +52,18 @@ class TestClean(BaseTest):
 
         expected_status_output = (
             """
-            master *
-            |
-            o-bar (untracked)
-            |
-            o-foo
-            |
-            o-moo (untracked)
+            Warning: sliding invalid branches: bar2, foo2, moo2, mars out of the definition file
+              master *
+              |
+              o-bar (untracked)
+              |
+              o-foo
+              |
+              o-moo (untracked)
             """
         )
-        assert_command(['status'], expected_status_output)
+        assert_success(['status'], expected_status_output)
 
-        with pytest.raises(subprocess.CalledProcessError):
-            self.repo_sandbox.check_out("mars")
+        branches = self.repo_sandbox.get_local_branches()
+        assert 'foo' in branches
+        assert 'mars' not in branches
diff --git a/tests/test_delete_unmanaged.py b/tests/test_delete_unmanaged.py
index dfe87f4..f871f06 100644
--- a/tests/test_delete_unmanaged.py
+++ b/tests/test_delete_unmanaged.py
@@ -1,15 +1,15 @@
 from typing import Any
 
 from .base_test import BaseTest
-from .mockers import (assert_command, fixed_author_and_committer_date,
-                      launch_command, mock_ask_if_returning,
-                      mock_run_cmd_and_forward_stdout, rewrite_definition_file)
+from .mockers import (assert_success, fixed_author_and_committer_date,
+                      launch_command, mock_input_returning,
+                      mock_run_cmd_and_forward_output, rewrite_definition_file)
 
 
 class TestDeleteUnmanaged(BaseTest):
 
     def test_delete_unmanaged(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_forward_stdout)
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_forward_output)
 
         with fixed_author_and_committer_date():
             (
@@ -26,7 +26,7 @@ class TestDeleteUnmanaged(BaseTest):
             """
         rewrite_definition_file(body)
 
-        assert_command(
+        assert_success(
             ["delete-unmanaged", "--yes"],
             """
             Checking for unmanaged branches...
@@ -38,7 +38,7 @@ class TestDeleteUnmanaged(BaseTest):
         )
 
         self.repo_sandbox.check_out("master")
-        assert_command(
+        assert_success(
             ["delete-unmanaged", "-y"],
             """
             Checking for unmanaged branches...
@@ -48,7 +48,7 @@ class TestDeleteUnmanaged(BaseTest):
             """
         )
 
-        assert_command(
+        assert_success(
             ["delete-unmanaged", "-y"],
             """
             Checking for unmanaged branches...
@@ -57,6 +57,6 @@ class TestDeleteUnmanaged(BaseTest):
         )
 
         self.repo_sandbox.new_branch("foo").check_out("master")
-        mocker.patch("git_machete.client.MacheteClient.ask_if", mock_ask_if_returning("n"))
+        mocker.patch("builtins.input", mock_input_returning("n"))
         launch_command("delete-unmanaged")
         assert self.repo_sandbox.get_local_branches() == ["foo", "master"]
diff --git a/tests/test_diff.py b/tests/test_diff.py
index d53516a..beaa9f8 100644
--- a/tests/test_diff.py
+++ b/tests/test_diff.py
@@ -1,7 +1,7 @@
 from typing import Any
 
 from .base_test import BaseTest
-from .mockers import assert_command, mock_run_cmd_and_forward_stdout
+from .mockers import assert_success, mock_run_cmd_and_forward_output
 
 
 class TestDiff(BaseTest):
@@ -10,7 +10,7 @@ class TestDiff(BaseTest):
         """
         Verify behaviour of a 'git machete diff' command.
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_forward_stdout)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_forward_output)  # to hide git outputs in tests
         (
             self.repo_sandbox.new_branch("master")
                 .add_file_and_commit(message='master commit1')
@@ -43,7 +43,7 @@ class TestDiff(BaseTest):
         """
 
         # Test `git machete diff` without providing the branch name, git diff against the current working tree
-        assert_command(["diff"], expected_status_output)
+        assert_success(["diff"], expected_status_output)
 
         expected_status_output = """
         diff --git a/develop_file_name.txt b/develop_file_name.txt
@@ -55,10 +55,10 @@ class TestDiff(BaseTest):
         +Develop content
 
         """
-        assert_command(["diff", "develop"], expected_status_output)
-        assert_command(["diff", "refs/heads/develop"], expected_status_output)
+        assert_success(["diff", "develop"], expected_status_output)
+        assert_success(["diff", "refs/heads/develop"], expected_status_output)
 
-        assert_command(
+        assert_success(
             ["diff", "--stat", "refs/heads/develop"],
             "develop_file_name.txt | 1 +\n"
             "1 file changed, 1 insertion(+)\n\n"
diff --git a/tests/test_discover.py b/tests/test_discover.py
index 62afefb..9561cf8 100644
--- a/tests/test_discover.py
+++ b/tests/test_discover.py
@@ -1,5 +1,10 @@
+import os.path
+from typing import Any
+
 from .base_test import BaseTest
-from .mockers import assert_command, launch_command, rewrite_definition_file
+from .mockers import (assert_failure, assert_success, launch_command,
+                      mock_input_returning, mock_run_cmd_and_discard_output,
+                      overridden_environment, rewrite_definition_file)
 
 
 class TestDiscover(BaseTest):
@@ -31,6 +36,8 @@ class TestDiscover(BaseTest):
             """
         rewrite_definition_file(body)
         launch_command('discover', '-y')
+        assert os.path.exists(".git/machete~")
+
         expected_status_output = (
             """
             master
@@ -44,19 +51,70 @@ class TestDiscover(BaseTest):
               o-feature4 * (untracked)
             """
         )
-        assert_command(['status'], expected_status_output)
+        assert_success(['status'], expected_status_output)
+
+        expected_discover_output = (
+            """
+            Discovered tree of branch dependencies:
+
+              feature1 (untracked)
+              |
+              o-feature2 (untracked)
+
+              master
+              |
+              o-feature3  rebase=no push=no
+                |
+                o-feature4 * (untracked)
+
+            Saving the above tree to .git/machete...
+            The existing definition file will be backed up as .git/machete~
+            """
+        )
+        assert_success(['discover', '--roots=feature1,master', '-y'], expected_discover_output)
+
+        assert_failure(['discover', '--roots=feature1,lolxd'], "lolxd is not a local branch")
+
+    def test_discover_main_branch_and_edit(self, mocker: Any) -> None:
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
+
+        (
+            self.repo_sandbox
+            .remove_remote()
+            .new_branch('feature1')
+            .commit()
+            .new_branch('main')
+            .commit()
+            .new_branch('feature2')
+            .commit()
+        )
 
-        launch_command('discover', '--roots=feature1,master', '-y')
         expected_status_output = (
             """
-            feature1 (untracked)
-            |
-            o-feature2 (untracked)
+            Discovered tree of branch dependencies:
 
-            master
-            |
-            o-feature3  rebase=no push=no
+              main
               |
-              o-feature4 * (untracked)
+              o-feature2 *
+
+              feature1
+
+            Save the above tree to .git/machete?
+            The existing definition file will be backed up as .git/machete~ (y, e[dit], N) 
+            """
+        )
+
+        mocker.patch('builtins.input', mock_input_returning('e'))
+        with overridden_environment(GIT_MACHETE_EDITOR='cat'):
+            assert_success(['discover'], expected_status_output)
+
+        assert_success(
+            ['status'],
+            """
+            main
+            |
+            o-feature2 *
+
+            feature1
             """
         )
diff --git a/tests/test_edit.py b/tests/test_edit.py
index 97ad882..09dacc8 100644
--- a/tests/test_edit.py
+++ b/tests/test_edit.py
@@ -1,10 +1,36 @@
 from .base_test import BaseTest
-from .mockers import launch_command, overridden_environment
+from .mockers import assert_success, launch_command, overridden_environment
+
+dummy_editor = "bash -c 'echo foo > $1' 'ignored_$0'"
 
 
 class TestEdit(BaseTest):
 
     def test_edit_git_machete_editor(self) -> None:
-        with overridden_environment(GIT_MACHETE_EDITOR="bash -c 'echo foo > $1' 'ignored_$0'"):
-            launch_command("edit", "--debug")
-        assert self.repo_sandbox.read_file(".git/machete") == "foo\n"
+        with overridden_environment(GIT_MACHETE_EDITOR=dummy_editor):
+            launch_command("edit")
+        assert self.repo_sandbox.read_file(".git/machete").strip() == "foo"
+
+    def test_edit_git_editor(self) -> None:
+        self.repo_sandbox.set_git_config_key("advice.macheteEditorSelection", "true")
+
+        with overridden_environment(GIT_EDITOR=dummy_editor):
+            assert_success(
+                ["edit"],
+                """
+                Opening '$GIT_EDITOR' (bash -c 'echo foo > $1' 'ignored_$0').
+                To override this choice, use GIT_MACHETE_EDITOR env var, e.g. export GIT_MACHETE_EDITOR=vi.
+
+                See git machete help edit and git machete edit --debug for more details.
+
+                Use git config --global advice.macheteEditorSelection false to suppress this message.
+                """
+            )
+        assert self.repo_sandbox.read_file(".git/machete").strip() == "foo"
+
+    def test_edit_git_config_core_editor(self) -> None:
+        self.repo_sandbox.set_git_config_key("advice.macheteEditorSelection", "false")
+
+        with overridden_environment(GIT_MACHETE_EDITOR="  ", GIT_EDITOR="lolxd-this-doesnt-exist", VISUAL="", EDITOR=dummy_editor):
+            assert_success(["edit"], "")
+        assert self.repo_sandbox.read_file(".git/machete").strip() == "foo"
diff --git a/tests/test_fork_point.py b/tests/test_fork_point.py
index ba33cb1..75ce002 100644
--- a/tests/test_fork_point.py
+++ b/tests/test_fork_point.py
@@ -1,7 +1,6 @@
 from .base_test import BaseTest
-from .mockers import (assert_command, fixed_author_and_committer_date,
-                      get_current_commit_hash, launch_command,
-                      rewrite_definition_file)
+from .mockers import (assert_success, fixed_author_and_committer_date,
+                      launch_command, rewrite_definition_file)
 
 
 class TestForkPoint(BaseTest):
@@ -30,13 +29,13 @@ class TestForkPoint(BaseTest):
 
         # Test `git machete fork-point` without providing the branch name
         # hash 67007ed30def3b9b658380b895a9f62b525286e0 corresponds to the commit on develop branch
-        assert_command(["fork-point"], "03e727bb987b21acce75e404f57e9d33ca876c20\n")
-        assert_command(["fork-point", "--inferred"], "03e727bb987b21acce75e404f57e9d33ca876c20\n")
+        assert_success(["fork-point"], "03e727bb987b21acce75e404f57e9d33ca876c20\n")
+        assert_success(["fork-point", "--inferred"], "03e727bb987b21acce75e404f57e9d33ca876c20\n")
 
         # hash 515319fa0ab47f372f6159bcc8ac27b43ee8a0ed corresponds to the commit on master branch
-        assert_command(["fork-point", 'develop'], "58a3121d3ef89189eb51176c7ec5344f4aab2f84\n")
+        assert_success(["fork-point", 'develop'], "58a3121d3ef89189eb51176c7ec5344f4aab2f84\n")
 
-        assert_command(["fork-point", 'refs/heads/develop'], "58a3121d3ef89189eb51176c7ec5344f4aab2f84\n")
+        assert_success(["fork-point", 'refs/heads/develop'], "58a3121d3ef89189eb51176c7ec5344f4aab2f84\n")
 
     def test_fork_point_override_to_commit(self) -> None:
         """
@@ -46,9 +45,9 @@ class TestForkPoint(BaseTest):
             self.repo_sandbox.new_branch("master")
                 .commit("master first commit")
         )
-        master_branch_first_commit_hash = get_current_commit_hash()
+        master_branch_first_commit_hash = self.repo_sandbox.get_current_commit_hash()
         self.repo_sandbox.commit("master second commit")
-        develop_branch_fork_point = get_current_commit_hash()
+        develop_branch_fork_point = self.repo_sandbox.get_current_commit_hash()
         self.repo_sandbox.new_branch("develop").commit("develop commit")
         body: str = \
             """
@@ -86,6 +85,8 @@ class TestForkPoint(BaseTest):
         assert launch_command('fork-point').strip() == develop_branch_fork_point
         assert launch_command('fork-point', '--inferred').strip() == develop_branch_fork_point
 
+        assert_success(['fork-point', '--unset-override'], "")
+
     def test_fork_point_override_to_parent_and_inferred(self) -> None:
         with fixed_author_and_committer_date():
             (
@@ -107,7 +108,7 @@ class TestForkPoint(BaseTest):
 
         assert launch_command("fork-point").strip() == "a71ffac2c1d41b8d1592a25f0056e4dfca829608"
         assert launch_command("fork-point", "--inferred").strip() == "a71ffac2c1d41b8d1592a25f0056e4dfca829608"
-        assert_command(
+        assert_success(
             ["status", "--list-commits-with-hashes"],
             """
               master (untracked)
@@ -127,7 +128,7 @@ class TestForkPoint(BaseTest):
         launch_command("fork-point", "--override-to-parent")
         assert launch_command("fork-point").strip() == "7e6757a9e7888e8cad7e112ae4dc305966335594"
         assert launch_command("fork-point", "--inferred").strip() == "a71ffac2c1d41b8d1592a25f0056e4dfca829608"
-        assert_command(
+        assert_success(
             ["status", "-L"],
             """
               master (untracked)
@@ -141,7 +142,7 @@ class TestForkPoint(BaseTest):
         launch_command("fork-point", "--override-to-inferred")
         assert launch_command("fork-point").strip() == "a71ffac2c1d41b8d1592a25f0056e4dfca829608"
         assert launch_command("fork-point", "--inferred").strip() == "a71ffac2c1d41b8d1592a25f0056e4dfca829608"
-        assert_command(
+        assert_success(
             ["status", "-L"],
             """
               master (untracked)
diff --git a/tests/test_git_operations.py b/tests/test_git_operations.py
index 8c0c3bd..2c4a1b1 100644
--- a/tests/test_git_operations.py
+++ b/tests/test_git_operations.py
@@ -4,7 +4,7 @@ from git_machete.git_operations import (AnyRevision, FullCommitHash,
                                         LocalBranchShortName)
 
 from .base_test import BaseTest, git
-from .mockers import get_current_commit_hash, mock_run_cmd
+from .mockers import mock_run_cmd_and_discard_output
 
 
 class TestGitOperations(BaseTest):
@@ -13,12 +13,12 @@ class TestGitOperations(BaseTest):
         """
         Verify behaviour of a 'GitContext._run_git()' method
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         (
             self.repo_sandbox.new_branch("master")
                 .commit("master first commit")
         )
-        master_branch_first_commit_hash = get_current_commit_hash()
+        master_branch_first_commit_hash = self.repo_sandbox.get_current_commit_hash()
 
         assert git._run_git("rev-parse", "--verify", "--quiet", master_branch_first_commit_hash + "^{commit}", allow_non_zero=True) == 0  # noqa: FS003, E501
         assert git._run_git("rev-parse", "HEAD") == 0
@@ -31,7 +31,7 @@ class TestGitOperations(BaseTest):
             self.repo_sandbox.new_branch("master")
                 .commit("master first commit")
         )
-        master_branch_first_commit_hash = get_current_commit_hash()
+        master_branch_first_commit_hash = self.repo_sandbox.get_current_commit_hash()
         (
             self.repo_sandbox.new_branch("develop")
                 .commit("develop commit")
@@ -43,7 +43,7 @@ class TestGitOperations(BaseTest):
             return git._popen_git("rev-parse", "--verify", "--quiet", revision + "^{commit}", allow_non_zero=True).exit_code == 0  # noqa: FS003, E501
 
         assert is_commit_present_in_repository(revision=FullCommitHash(40 * 'a')) is False
-        assert is_commit_present_in_repository(revision=master_branch_first_commit_hash) is True
+        assert is_commit_present_in_repository(revision=AnyRevision(master_branch_first_commit_hash)) is True
 
         assert git.is_ancestor_or_equal(earlier_revision=LocalBranchShortName('feature'),
                                         later_revision=LocalBranchShortName('master')) is False
diff --git a/tests/test_github.py b/tests/test_github.py
index 052b99e..66b61cc 100644
--- a/tests/test_github.py
+++ b/tests/test_github.py
@@ -2,7 +2,7 @@ import json
 import os
 import re
 from http import HTTPStatus
-from subprocess import CalledProcessError, CompletedProcess
+from subprocess import CompletedProcess
 from tempfile import mkdtemp
 from textwrap import dedent
 from typing import Any, Callable, ContextManager, Dict, List, Optional, Union
@@ -10,19 +10,15 @@ from unittest.mock import mock_open
 from urllib.error import HTTPError
 from urllib.parse import ParseResult, parse_qs, urlparse
 
-import pytest
-
-from git_machete.exceptions import MacheteException
-from git_machete.git_operations import LocalBranchShortName
+from git_machete.git_operations import AnyRevision, LocalBranchShortName
 from git_machete.github import (GitHubClient, GitHubToken,
                                 RemoteAndOrganizationAndRepository)
 from git_machete.options import CommandLineOptions
 
 from .base_test import BaseTest, GitRepositorySandbox, git
-from .mockers import (assert_command, get_current_commit_hash, launch_command,
-                      mock_ask_if, mock_exit_script, mock_run_cmd,
-                      mock_should_perform_interactive_slide_out,
-                      rewrite_definition_file)
+from .mockers import (assert_failure, assert_success, launch_command,
+                      mock_input_returning, mock_input_returning_y,
+                      mock_run_cmd_and_discard_output, rewrite_definition_file)
 
 
 class FakeCommandLineOptions(CommandLineOptions):
@@ -47,18 +43,13 @@ def mock_github_remote_url_patterns(domain: str) -> List[str]:
 
 def mock_fetch_ref(cls: Any, remote: str, ref: str) -> None:
     branch: LocalBranchShortName = LocalBranchShortName.of(ref[ref.index(':') + 1:])
-    git.create_branch(branch, get_current_commit_hash(), switch_head=True)
+    git.create_branch(branch, git.get_commit_hash_by_revision(AnyRevision("HEAD")), switch_head=True)  # type: ignore[arg-type]
 
 
 def mock_derive_current_user_login(domain: str) -> str:
     return "very_complex_user_token"
 
 
-def mock_input(msg: str) -> str:
-    print(msg)
-    return '1'
-
-
 def mock_is_file_true(file: Any) -> bool:
     return True
 
@@ -386,7 +377,7 @@ class TestGitHub(BaseTest):
     )
 
     def test_github_retarget_pr(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         mocker.patch('urllib.request.Request', self.git_api_state_for_test_retarget_pr.new_request())
         mocker.patch('urllib.request.urlopen', MockContextManager)
 
@@ -422,12 +413,12 @@ class TestGitHub(BaseTest):
           |
           o-feature *  PR #15 (github_user) WRONG PR BASE or MACHETE PARENT? PR has root rebase=no push=no
         """
-        assert_command(
+        assert_success(
             ['status'],
             expected_result=expected_status_output
         )
 
-        assert_command(
+        assert_success(
             ['github', 'retarget-pr'],
             'The base branch of PR #15 has been switched to branch-1\n'
         )
@@ -439,12 +430,12 @@ class TestGitHub(BaseTest):
           |
           o-feature *  PR #15 rebase=no push=no
         """
-        assert_command(
+        assert_success(
             ['status'],
             expected_result=expected_status_output
         )
 
-        assert_command(
+        assert_success(
             ['github', 'retarget-pr'],
             'The base branch of PR #15 is already branch-1\n'
         )
@@ -461,8 +452,7 @@ class TestGitHub(BaseTest):
     )
 
     def test_github_retarget_pr_explicit_branch(self, mocker: Any) -> None:
-        mocker.patch('git_machete.cli.exit_script', mock_exit_script)
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         mocker.patch('urllib.request.Request', self.git_api_state_for_test_github_retarget_pr_explicit_branch.new_request())
         mocker.patch('urllib.request.urlopen', MockContextManager)
 
@@ -505,12 +495,12 @@ class TestGitHub(BaseTest):
         |
         o-branch-without-pr
         """
-        assert_command(
+        assert_success(
             ['status'],
             expected_result=expected_status_output
         )
 
-        assert_command(
+        assert_success(
             ['github', 'retarget-pr', '--branch', 'feature'],
             'The base branch of PR #15 has been switched to branch-1\n'
         )
@@ -524,7 +514,7 @@ class TestGitHub(BaseTest):
         |
         o-branch-without-pr
         """
-        assert_command(
+        assert_success(
             ['status'],
             expected_result=expected_status_output
         )
@@ -537,17 +527,13 @@ class TestGitHub(BaseTest):
                                   '\t3. Current auth token from the gh GitHub CLI.\n'
                                   '\t4. Current auth token from the hub GitHub CLI.\n'
                                   ' Visit https://github.com/settings/tokens to generate a new one.')
-        with pytest.raises(MacheteException) as e:
-            launch_command("github", "retarget-pr", "--branch", "branch-without-pr")
-        assert e.value.args[0] == expected_error_message, \
-            'Verify that expected error message has appeared when there is no pull request associated with that branch name.'
+        assert_failure(["github", "retarget-pr", "--branch", "branch-without-pr"], expected_error_message)
 
         launch_command('github', 'retarget-pr', '--branch', 'branch-without-pr', '--ignore-if-missing')
 
     def test_github_retarget_pr_multiple_non_origin_remotes(self, mocker: Any) -> None:
-        mocker.patch('git_machete.cli.exit_script', mock_exit_script)
         mocker.patch('git_machete.github.github_remote_url_patterns', mock_github_remote_url_patterns)
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         mocker.patch('urllib.request.Request', self.git_api_state_for_test_retarget_pr.new_request())
         mocker.patch('urllib.request.urlopen', MockContextManager)
 
@@ -561,7 +547,7 @@ class TestGitHub(BaseTest):
 
         # branch feature present in each remote, no branch tracking data
         (
-            self.repo_sandbox.remove_remote(remote='origin')
+            self.repo_sandbox.remove_remote()
                 .new_branch("root")
                 .add_remote('origin_1', origin_1_remote_path)
                 .add_remote('origin_2', origin_2_remote_path)
@@ -590,12 +576,9 @@ class TestGitHub(BaseTest):
         expected_error_message = (
             "Multiple non-origin remotes correspond to GitHub in this repository: origin_1, origin_2 -> aborting. \n"
             "You can also select the repository by providing some or all of git config keys: "
-            "`machete.github.{domain,remote,organization,repository}`.\n"  # noqa: FS003
+            "machete.github.{domain,remote,organization,repository}.\n"  # noqa: FS003
         )
-        with pytest.raises(MacheteException) as e:
-            launch_command("github", "retarget-pr")
-        assert e.value.args[0] == expected_error_message, \
-            'Verify that expected error message has appeared when given pull request to create is already created.'
+        assert_failure(["github", "retarget-pr"], expected_error_message)
 
         # branch feature_1 present in each remote, tracking data present
         (
@@ -615,7 +598,7 @@ class TestGitHub(BaseTest):
             """
         rewrite_definition_file(body)
 
-        assert_command(
+        assert_success(
             ['github', 'retarget-pr'],
             'The base branch of PR #20 has been switched to feature\n'
         )
@@ -637,10 +620,7 @@ class TestGitHub(BaseTest):
             """
         rewrite_definition_file(body)
 
-        with pytest.raises(MacheteException) as e:
-            launch_command("github", "retarget-pr")
-        assert e.value.args[0] == expected_error_message, \
-            'Verify that expected error message has appeared when given pull request to create is already created.'
+        assert_failure(["github", "retarget-pr"], expected_error_message)
 
         # branch feature_2 present in only one remote: origin_1 and there is no tracking data available -> infer the remote
         (
@@ -648,7 +628,7 @@ class TestGitHub(BaseTest):
                 .push(remote='origin_1', set_upstream=False)
         )
 
-        assert_command(
+        assert_success(
             ['github', 'retarget-pr'],
             'The base branch of PR #25 has been switched to feature\n'
         )
@@ -672,7 +652,7 @@ class TestGitHub(BaseTest):
             """
         rewrite_definition_file(body)
 
-        assert_command(
+        assert_success(
             ['github', 'retarget-pr'],
             'The base branch of PR #35 has been switched to feature_2\n'
         )
@@ -707,7 +687,7 @@ class TestGitHub(BaseTest):
     )
 
     def test_github_anno_prs(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         mocker.patch('git_machete.github.GitHubClient.derive_current_user_login', mock_derive_current_user_login)
         mocker.patch('urllib.request.urlopen', MockContextManager)
         mocker.patch('urllib.request.Request', self.git_api_state_for_test_anno_prs.new_request())
@@ -770,7 +750,7 @@ class TestGitHub(BaseTest):
         launch_command('anno', '-b=allow-ownership-link', 'rebase=no')
         launch_command('anno', '-b=build-chain', 'rebase=no push=no')
         launch_command('github', 'anno-prs')
-        assert_command(
+        assert_success(
             ["status"],
             """
             master
@@ -801,7 +781,7 @@ class TestGitHub(BaseTest):
         )
 
         launch_command('github', 'anno-prs')
-        assert_command(
+        assert_success(
             ["status"],
             """
             master
@@ -841,12 +821,11 @@ class TestGitHub(BaseTest):
     )
 
     def test_github_create_pr(self, mocker: Any) -> None:
-        mocker.patch('git_machete.cli.exit_script', mock_exit_script)
-        mocker.patch('git_machete.client.MacheteClient.ask_if', mock_ask_if)
+        mocker.patch('builtins.input', mock_input_returning_y)
         # We need to mock GITHUB_REMOTE_PATTERNS in the tests for `test_github_create_pr`
         # due to `git fetch` executed by `create-pr` subcommand.
         mocker.patch('git_machete.github.github_remote_url_patterns', mock_github_remote_url_patterns)
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         mocker.patch('git_machete.options.CommandLineOptions', FakeCommandLineOptions)
         mocker.patch('urllib.error.HTTPError', MockHTTPError)  # need to provide read() method, which does not actually reads error from url
         mocker.patch('urllib.request.Request', self.git_api_state_for_test_create_pr.new_request())
@@ -911,7 +890,7 @@ class TestGitHub(BaseTest):
 
         launch_command("github", "create-pr")
         # ahead of origin state, push is advised and accepted
-        assert_command(
+        assert_success(
             ['status'],
             """
             master
@@ -936,7 +915,7 @@ class TestGitHub(BaseTest):
         self.repo_sandbox.check_out('chore/fields')
         #  untracked state (can only create pr when branch is pushed)
         launch_command("github", "create-pr", "--draft")
-        assert_command(
+        assert_success(
             ['status'],
             """
             master
@@ -967,7 +946,7 @@ class TestGitHub(BaseTest):
 
         # diverged from and newer than origin
         launch_command("github", "create-pr")
-        assert_command(
+        assert_success(
             ['status'],
             """
             master
@@ -990,10 +969,7 @@ class TestGitHub(BaseTest):
             """,
         )
         expected_error_message = "A pull request already exists for test_repo:hotfix/add-trigger."
-        with pytest.raises(MacheteException) as e:
-            launch_command("github", "create-pr")
-        assert e.value.args[0] == expected_error_message, \
-            'Verify that expected error message has appeared when given pull request to create is already created.'
+        assert_failure(["github", "create-pr"], expected_error_message)
 
         # check against head branch is ancestor or equal to base branch
         (
@@ -1018,18 +994,12 @@ class TestGitHub(BaseTest):
 
         expected_error_message = "All commits in testing/endpoints branch are already included in develop branch.\n" \
                                  "Cannot create pull request."
-        with pytest.raises(MacheteException) as e:
-            launch_command("github", "create-pr")
-        assert e.value.parameter == expected_error_message, \
-            'Verify that expected error message has appeared when head branch is equal or ancestor of base branch.'
+        assert_failure(["github", "create-pr"], expected_error_message)
 
         self.repo_sandbox.check_out('develop')
         expected_error_message = "Branch develop does not have a parent branch (it is a root), " \
                                  "base branch for the PR cannot be established."
-        with pytest.raises(MacheteException) as e:
-            launch_command("github", "create-pr")
-        assert e.value.parameter == expected_error_message, \
-            'Verify that expected error message has appeared when creating PR from root branch.'
+        assert_failure(["github", "create-pr"], expected_error_message)
 
     git_api_state_for_test_create_pr_missing_base_branch_on_remote = MockGitHubAPIState(
         [
@@ -1045,13 +1015,13 @@ class TestGitHub(BaseTest):
     )
 
     def test_github_create_pr_missing_base_branch_on_remote(self, mocker: Any) -> None:
+        mocker.patch('builtins.input', mock_input_returning_y)
         # We need to mock GITHUB_REMOTE_PATTERNS in the tests for `test_github_create_pr`
         # due to `git fetch` executed by `create-pr` subcommand.
         mocker.patch('git_machete.github.github_remote_url_patterns', mock_github_remote_url_patterns)
         mocker.patch('git_machete.github.GitHubToken.for_domain', mock_for_domain_none)
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         mocker.patch('git_machete.options.CommandLineOptions', FakeCommandLineOptions)
-        mocker.patch('git_machete.client.MacheteClient.ask_if', mock_ask_if)
         mocker.patch('urllib.request.urlopen', MockContextManager)
         mocker.patch('urllib.request.Request', self.git_api_state_for_test_create_pr_missing_base_branch_on_remote.new_request())
 
@@ -1078,9 +1048,10 @@ class TestGitHub(BaseTest):
 
         expected_msg = ("Fetching origin...\n"
                         "Warn: Base branch for this PR (feature/api_handling) is not found on remote, pushing...\n"
+                        "Push untracked branch feature/api_handling to origin? (y, Q) \n"
                         "Creating a PR from feature/api_exception_handling to feature/api_handling... OK, see www.github.com\n")
-        assert_command(['github', 'create-pr'], expected_msg)
-        assert_command(
+        assert_success(['github', 'create-pr'], expected_msg)
+        assert_success(
             ['status'],
             """
             develop
@@ -1110,18 +1081,16 @@ class TestGitHub(BaseTest):
     )
 
     def test_github_create_pr_with_multiple_non_origin_remotes(self, mocker: Any) -> None:
-        mocker.patch('git_machete.cli.exit_script', mock_exit_script)
-        mocker.patch('git_machete.client.MacheteClient.ask_if', mock_ask_if)
+        mocker.patch('builtins.input', mock_input_returning('1', 'y', 'y'))
         # We need to mock GITHUB_REMOTE_PATTERNS in the tests for `test_github_create_pr`
         # due to `git fetch` executed by `create-pr` subcommand.
         mocker.patch('git_machete.github.github_remote_url_patterns', mock_github_remote_url_patterns)
         mocker.patch('git_machete.github.GitHubToken.for_domain', mock_for_domain_none)
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         mocker.patch('git_machete.options.CommandLineOptions', FakeCommandLineOptions)
         mocker.patch('urllib.error.HTTPError', MockHTTPError)  # need to provide read() method, which does not actually read error from url
         mocker.patch('urllib.request.Request', self.git_api_state_for_test_github_create_pr_with_multiple_non_origin_remotes.new_request())
         mocker.patch('urllib.request.urlopen', MockContextManager)
-        mocker.patch('builtins.input', mock_input)
 
         origin_1_remote_path = mkdtemp()
         origin_2_remote_path = mkdtemp()
@@ -1130,7 +1099,7 @@ class TestGitHub(BaseTest):
 
         # branch feature present in each of the remotes, no branch tracking data, remote origin_1 picked manually via mock_input()
         (
-            self.repo_sandbox.remove_remote(remote='origin')
+            self.repo_sandbox.remove_remote()
                 .new_branch("root")
                 .add_remote('origin_1', origin_1_remote_path)
                 .add_remote('origin_2', origin_2_remote_path)
@@ -1160,6 +1129,7 @@ class TestGitHub(BaseTest):
         [2] origin_2
         Select number 1..2 to specify the destination remote repository, or 'q' to quit creating pull request: 
         Branch feature is untracked, but its remote counterpart candidate origin_1/feature already exists and both branches point to the same commit.
+        Set the remote of feature to origin_1 without pushing or pulling? (y, N, q, yq, o[ther-remote]) 
 
           root
           |
@@ -1170,7 +1140,7 @@ class TestGitHub(BaseTest):
         Fetching origin_1...
         Creating a PR from feature to branch-1... OK, see www.github.com
         """  # noqa: W291, E501
-        assert_command(
+        assert_success(
             ['github', 'create-pr'],
             expected_result
         )
@@ -1184,11 +1154,12 @@ class TestGitHub(BaseTest):
         )
 
         expected_result = """
+        Add feature_1 onto the inferred upstream (parent) branch feature? (y, N) 
         Added branch feature_1 onto feature
         Fetching origin_2...
         Creating a PR from feature_1 to feature... OK, see www.github.com
         """
-        assert_command(
+        assert_success(
             ['github', 'create-pr'],
             expected_result
         )
@@ -1200,12 +1171,16 @@ class TestGitHub(BaseTest):
                 .commit('introduce feature 2')
         )
 
+        mocker.patch('builtins.input', mock_input_returning('y', '1', 'y'))
+
         expected_result = """
+        Add feature_2 onto the inferred upstream (parent) branch feature? (y, N) 
         Added branch feature_2 onto feature
         Branch feature_2 is untracked and there's no origin repository.
         [1] origin_1
         [2] origin_2
         Select number 1..2 to specify the destination remote repository, or 'q' to quit creating pull request: 
+        Push untracked branch feature_2 to origin_1? (y, Q, o[ther-remote]) 
 
           root
           |
@@ -1220,7 +1195,7 @@ class TestGitHub(BaseTest):
         Fetching origin_1...
         Creating a PR from feature_2 to feature... OK, see www.github.com
         """  # noqa: W291
-        assert_command(
+        assert_success(
             ['github', 'create-pr'],
             expected_result
         )
@@ -1233,12 +1208,14 @@ class TestGitHub(BaseTest):
                 .push(remote='origin_1', set_upstream=False)
         )
 
+        mocker.patch('builtins.input', mock_input_returning('y'))
         expected_result = """
+        Add feature_3 onto the inferred upstream (parent) branch feature_2? (y, N) 
         Added branch feature_3 onto feature_2
         Fetching origin_1...
         Creating a PR from feature_3 to feature_2... OK, see www.github.com
         """  # noqa: E501
-        assert_command(
+        assert_success(
             ['github', 'create-pr'],
             expected_result
         )
@@ -1251,13 +1228,16 @@ class TestGitHub(BaseTest):
                 .push(remote='origin_2')
         )
 
+        mocker.patch('builtins.input', mock_input_returning('y', 'y'))
         expected_result = """
+        Add feature_4 onto the inferred upstream (parent) branch feature_3? (y, N) 
         Added branch feature_4 onto feature_3
         Fetching origin_2...
         Warn: Base branch for this PR (feature_3) is not found on remote, pushing...
+        Push untracked branch feature_3 to origin_2? (y, Q) 
         Creating a PR from feature_4 to feature_3... OK, see www.github.com
         """
-        assert_command(
+        assert_success(
             ['github', 'create-pr'],
             expected_result
         )
@@ -1271,12 +1251,15 @@ class TestGitHub(BaseTest):
                 .push(remote='origin_2')
         )
 
+        mocker.patch('builtins.input', mock_input_returning('y', 'y'))
         expected_result = """
+        Add feature_5 onto the inferred upstream (parent) branch feature_3? (y, N) 
         Added branch feature_5 onto feature_3
         Fetching origin...
+        Push untracked branch feature_3 to origin? (y, Q) 
         Creating a PR from feature_5 to feature_3... OK, see www.github.com
         """
-        assert_command(
+        assert_success(
             ['github', 'create-pr'],
             expected_result
         )
@@ -1359,12 +1342,11 @@ class TestGitHub(BaseTest):
     )
 
     def test_github_checkout_prs(self, mocker: Any, tmp_path: Any) -> None:
-        mocker.patch('git_machete.cli.exit_script', mock_exit_script)
         # We need to mock GITHUB_REMOTE_PATTERNS in the tests for `test_github_checkout_prs`
         # due to `git fetch` executed by `checkout-prs` subcommand.
         mocker.patch('git_machete.github.github_remote_url_patterns', mock_github_remote_url_patterns)
         mocker.patch('git_machete.options.CommandLineOptions', FakeCommandLineOptions)
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         mocker.patch('git_machete.github.GitHubToken.for_domain', mock_for_domain_none)
         mocker.patch('urllib.request.Request', self.git_api_state_for_test_checkout_prs.new_request())
         mocker.patch('urllib.request.urlopen', MockContextManager)
@@ -1440,7 +1422,7 @@ class TestGitHub(BaseTest):
 
         # not broken chain of pull requests (root found in dependency tree)
         launch_command('github', 'checkout-prs', '18')
-        assert_command(
+        assert_success(
             ["status"],
             """
             master
@@ -1466,7 +1448,7 @@ class TestGitHub(BaseTest):
         )
         # broken chain of pull requests (add new root)
         launch_command('github', 'checkout-prs', '24')
-        assert_command(
+        assert_success(
             ["status"],
             """
             master
@@ -1499,7 +1481,7 @@ class TestGitHub(BaseTest):
 
         # broken chain of pull requests (branches already added)
         launch_command('github', 'checkout-prs', '24')
-        assert_command(
+        assert_success(
             ["status"],
             """
             master
@@ -1532,7 +1514,7 @@ class TestGitHub(BaseTest):
 
         # all PRs
         launch_command('github', 'checkout-prs', '--all')
-        assert_command(
+        assert_success(
             ["status"],
             """
             master
@@ -1571,21 +1553,15 @@ class TestGitHub(BaseTest):
                                                                       remote='origin')
         assert remote_org_repo is not None
         expected_error_message = f"PR #100 is not found in repository {remote_org_repo.organization}/{remote_org_repo.repository}"
-        with pytest.raises(MacheteException) as e:
-            launch_command('github', 'checkout-prs', '100')
-        assert e.value.parameter == expected_error_message, \
-            'Verify that expected error message has appeared when given pull request to checkout does not exists.'
+        assert_failure(['github', 'checkout-prs', '100'], expected_error_message)
 
-        with pytest.raises(MacheteException) as e:
-            launch_command('github', 'checkout-prs', '19', '100')
-        assert e.value.parameter == expected_error_message, \
-            'Verify that expected error message has appeared when one of the given pull requests to checkout does not exists.'
+        assert_failure(['github', 'checkout-prs', '19', '100'], expected_error_message)
 
         # check against user with no open pull requests
         expected_msg = ("Checking for open GitHub PRs... OK\n"
                         f"Warn: User tester has no open pull request in repository "
                         f"{remote_org_repo.organization}/{remote_org_repo.repository}\n")
-        assert_command(['github', 'checkout-prs', '--by', 'tester'], expected_msg)
+        assert_success(['github', 'checkout-prs', '--by', 'tester'], expected_msg)
 
         # Check against closed pull request with head branch deleted from remote
         local_path = tmp_path
@@ -1602,31 +1578,24 @@ class TestGitHub(BaseTest):
 
         expected_error_message = "Could not check out PR #5 because its head branch bugfix/remove-n-option " \
                                  "is already deleted from testing."
-        with pytest.raises(MacheteException) as e:
-            launch_command('github', 'checkout-prs', '5')
-        assert e.value.parameter == expected_error_message, \
-            'Verify that expected error message has appeared when given pull request to checkout ' \
-            'have already deleted branch from remote.'
+        assert_failure(['github', 'checkout-prs', '5'], expected_error_message)
 
         # Check against pr come from fork
         os.chdir(local_path)
         (self.repo_sandbox
          .new_branch('bugfix/remove-n-option')
          .commit('first commit')
-         .push()
-         )
+         .push())
         os.chdir(self.repo_sandbox.local_path)
 
         expected_msg = ("Checking for open GitHub PRs... OK\n"
                         "Warn: Pull request #5 is already closed.\n"
                         "Pull request #5 checked out at local branch bugfix/remove-n-option\n")
-
-        assert_command(['github', 'checkout-prs', '5'], expected_msg)
+        assert_success(['github', 'checkout-prs', '5'], expected_msg)
 
         # Check against multiple PRs
         expected_msg = 'Checking for open GitHub PRs... OK\n'
-
-        assert_command(['github', 'checkout-prs', '3', '12'], expected_msg)
+        assert_success(['github', 'checkout-prs', '3', '12'], expected_msg)
 
     git_api_state_for_test_github_checkout_prs_fresh_repo = MockGitHubAPIState(
         [
@@ -1667,7 +1636,7 @@ class TestGitHub(BaseTest):
     )
 
     def test_github_checkout_prs_freshly_cloned(self, mocker: Any, tmp_path: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         # We need to mock GITHUB_REMOTE_PATTERNS in the tests for `test_github_checkout_prs_freshly_cloned`
         # due to `git fetch` executed by `checkout-prs` subcommand.
         mocker.patch('git_machete.options.CommandLineOptions', FakeCommandLineOptions)
@@ -1725,12 +1694,12 @@ class TestGitHub(BaseTest):
         rewrite_definition_file("master")
         expected_msg = ("Checking for open GitHub PRs... OK\n"
                         "Pull request #2 checked out at local branch comments/add_docstrings\n")
-        assert_command(
+        assert_success(
             ['github', 'checkout-prs', '2'],
             expected_msg
         )
 
-        assert_command(
+        assert_success(
             ["status"],
             """
             master
@@ -1749,11 +1718,11 @@ class TestGitHub(BaseTest):
                         "Warn: Pull request #23 is already closed.\n"
                         "Pull request #23 checked out at local branch sphinx_export\n")
 
-        assert_command(
+        assert_success(
             ['github', 'checkout-prs', '23'],
             expected_msg
         )
-        assert_command(
+        assert_success(
             ["status"],
             """
             master
@@ -1791,7 +1760,7 @@ class TestGitHub(BaseTest):
     def test_github_checkout_prs_from_fork_with_deleted_repo(self, mocker: Any) -> None:
         mocker.patch('git_machete.git_operations.GitContext.fetch_ref', mock_fetch_ref)
         # need to mock fetch_ref due to underlying `git fetch pull/head` calls
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         mocker.patch('git_machete.options.CommandLineOptions', FakeCommandLineOptions)
         mocker.patch('git_machete.github.github_remote_url_patterns', mock_github_remote_url_patterns)
         # We need to mock GITHUB_REMOTE_PATTERNS in the tests for `test_github_checkout_prs_from_fork_with_deleted_repo`
@@ -1818,7 +1787,7 @@ class TestGitHub(BaseTest):
                         "No remote tracking data will be set up for feature/allow_checkout branch.\n"
                         "Warn: Pull request #2 is already closed.\n"
                         "Pull request #2 checked out at local branch feature/allow_checkout\n")
-        assert_command(
+        assert_success(
             ['github', 'checkout-prs', '2'],
             expected_msg
         )
@@ -1906,12 +1875,11 @@ class TestGitHub(BaseTest):
     )
 
     def test_github_checkout_prs_of_current_user_and_other_users(self, mocker: Any, tmp_path: Any) -> None:
-        mocker.patch('git_machete.cli.exit_script', mock_exit_script)
         # We need to mock GITHUB_REMOTE_PATTERNS in the tests for `test_github_checkout_prs`
         # due to `git fetch` executed by `checkout-prs` subcommand.
         mocker.patch('git_machete.github.github_remote_url_patterns', mock_github_remote_url_patterns)
         mocker.patch('git_machete.options.CommandLineOptions', FakeCommandLineOptions)
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         mocker.patch('git_machete.github.GitHubToken.for_domain', mock_for_domain_none)
         mocker.patch('urllib.request.Request',
                      self.git_api_state_for_test_github_checkout_prs_of_current_user_and_other_users.new_request())
@@ -1993,7 +1961,7 @@ class TestGitHub(BaseTest):
         # test that `checkout-prs` add `rebase=no push=no` qualifiers to branches associated with the PRs whose owner
         # is different than the current user
         launch_command('github', 'checkout-prs', '--all')
-        assert_command(
+        assert_success(
             ["status"],
             """
             master *
@@ -2029,7 +1997,7 @@ class TestGitHub(BaseTest):
         # test that `checkout-prs` doesn't overwrite annotation qualifiers but overwrites annotation text
         launch_command('anno', '-b=allow-ownership-link', 'branch_annotation rebase=no')
         launch_command('github', 'checkout-prs', '--all')
-        assert_command(
+        assert_success(
             ["status"],
             """
             master *
@@ -2076,9 +2044,8 @@ class TestGitHub(BaseTest):
     )
 
     def test_github_sync(self, mocker: Any) -> None:
-        mocker.patch('git_machete.client.MacheteClient.should_perform_interactive_slide_out', mock_should_perform_interactive_slide_out)
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)
-        mocker.patch('git_machete.client.MacheteClient.ask_if', mock_ask_if)
+        mocker.patch('builtins.input', mock_input_returning_y)
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         mocker.patch('git_machete.options.CommandLineOptions', FakeCommandLineOptions)
         mocker.patch('git_machete.github.github_remote_url_patterns', mock_github_remote_url_patterns)
         mocker.patch('git_machete.github.GitHubToken.for_domain', mock_for_domain_fake)
@@ -2133,21 +2100,23 @@ class TestGitHub(BaseTest):
 
         expected_status_output = (
             """
-            master
-            |
-            o-bar (untracked)
-            |
-            o-foo
-            |
-            o-moo (untracked)
-            |
-            o-snickers *  PR #7
+            Warning: sliding invalid branches: bar2, foo2, moo2 out of the definition file
+              master
+              |
+              o-bar (untracked)
+              |
+              o-foo
+              |
+              o-moo (untracked)
+              |
+              o-snickers *  PR #7
             """
         )
-        assert_command(['status'], expected_status_output)
+        assert_success(['status'], expected_status_output)
 
-        with pytest.raises(CalledProcessError):
-            self.repo_sandbox.check_out("mars")
+        branches = self.repo_sandbox.get_local_branches()
+        assert 'foo' in branches
+        assert 'mars' not in branches
 
     def test_github_remote_patterns(self) -> None:
         organization = 'virtuslab'
@@ -2167,12 +2136,11 @@ class TestGitHub(BaseTest):
             assert remote_and_organization_and_repository.repository == repository
 
     def test_github_api_pagination(self, mocker: Any, tmp_path: Any) -> None:
-        mocker.patch('git_machete.cli.exit_script', mock_exit_script)
         # We need to mock GITHUB_REMOTE_PATTERNS in the tests for `test_github_checkout_prs`
         # due to `git fetch` executed by `checkout-prs` subcommand.
         mocker.patch('git_machete.github.github_remote_url_patterns', mock_github_remote_url_patterns)
         mocker.patch('git_machete.options.CommandLineOptions', FakeCommandLineOptions)
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         mocker.patch('git_machete.github.GitHubToken.for_domain', mock_for_domain_none)
         mocker.patch('urllib.request.urlopen', MockContextManager)
         mocker.patch('git_machete.github.GitHubClient.derive_current_user_login', mock_derive_current_user_login)
@@ -2203,31 +2171,26 @@ class TestGitHub(BaseTest):
         launch_command('discover', '--checked-out-since=1 day ago')
         expected_status_output = 'develop *\n' + '\n'.join([f'|\no-feature_{i}  rebase=no push=no'
                                                             for i in range(number_of_pages * prs_per_page)]) + '\n'
-        assert_command(['status'], expected_status_output)
+        assert_success(['status'], expected_status_output)
 
     def test_github_enterprise_domain_fail(self, mocker: Any) -> None:
-        mocker.patch('git_machete.client.MacheteClient.should_perform_interactive_slide_out', mock_should_perform_interactive_slide_out)
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)
-        mocker.patch('git_machete.client.MacheteClient.ask_if', mock_ask_if)
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
+        mocker.patch('builtins.input', mock_input_returning("1"))
         mocker.patch('git_machete.options.CommandLineOptions', FakeCommandLineOptions)
         mocker.patch('git_machete.github.github_remote_url_patterns', mock_github_remote_url_patterns)
         mocker.patch('git_machete.github.GitHubToken.for_domain', mock_for_domain_none)
         mocker.patch('urllib.request.urlopen', MockContextManagerRaise403)
-        mocker.patch('git_machete.cli.exit_script', mock_exit_script)
 
         github_enterprise_domain = 'git.example.org'
         self.repo_sandbox.set_git_config_key('machete.github.domain', github_enterprise_domain)
 
         expected_error_message = (
-            "GitHub API returned `403` HTTP status with error message: `Forbidden`\n"
+            "GitHub API returned 403 HTTP status with error message: Forbidden\n"
             "You might not have the required permissions for this repository.\n"
-            "Provide a GitHub API token with `repo` access.\n"
-            f"Visit `https://{github_enterprise_domain}/settings/tokens` to generate a new one.\n"
-            "You can also use a different token provider, available providers can be found when running `git machete help github`.")
-
-        with pytest.raises(MacheteException) as e:
-            launch_command('github', 'checkout-prs', '--all')
-        assert e.value.args[0] == expected_error_message, 'Verify that expected error message has appeared.'
+            "Provide a GitHub API token with repo access.\n"
+            f"Visit https://{github_enterprise_domain}/settings/tokens to generate a new one.\n"
+            "You can also use a different token provider, available providers can be found when running git machete help github.")
+        assert_failure(['github', 'checkout-prs', '--all'], expected_error_message)
 
     git_api_state_for_test_github_enterprise_domain = MockGitHubAPIState(
         [
@@ -2243,15 +2206,13 @@ class TestGitHub(BaseTest):
     )
 
     def test_github_enterprise_domain(self, mocker: Any) -> None:
-        mocker.patch('git_machete.client.MacheteClient.should_perform_interactive_slide_out', mock_should_perform_interactive_slide_out)
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)
-        mocker.patch('git_machete.client.MacheteClient.ask_if', mock_ask_if)
+        mocker.patch('builtins.input', mock_input_returning("1"))
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         mocker.patch('git_machete.options.CommandLineOptions', FakeCommandLineOptions)
         mocker.patch('git_machete.github.github_remote_url_patterns', mock_github_remote_url_patterns)
         mocker.patch('git_machete.github.GitHubToken.for_domain', mock_for_domain_fake)
         mocker.patch('urllib.request.urlopen', MockContextManager)
         mocker.patch('urllib.request.Request', self.git_api_state_for_test_github_enterprise_domain.new_request())
-        mocker.patch('git_machete.cli.exit_script', mock_exit_script)
 
         github_enterprise_domain = 'git.example.org'
         (
@@ -2396,7 +2357,7 @@ class TestGitHub(BaseTest):
     )
 
     def test_local_branch_name_different_than_tracking_branch_name(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         mocker.patch('urllib.request.Request',
                      self.git_api_state_for_test_local_branch_name_different_than_tracking_branch_name.new_request())
         mocker.patch('urllib.request.urlopen', MockContextManager)
@@ -2434,7 +2395,7 @@ class TestGitHub(BaseTest):
           |
           o-feature_1 *  PR #20 (github_user) rebase=no push=no
         """
-        assert_command(
+        assert_success(
             ['status'],
             expected_result=expected_status_output
         )
diff --git a/tests/test_go.py b/tests/test_go.py
index f492102..c9390d7 100644
--- a/tests/test_go.py
+++ b/tests/test_go.py
@@ -1,7 +1,8 @@
 from typing import Any
 
 from .base_test import BaseTest
-from .mockers import launch_command, mock_run_cmd, rewrite_definition_file
+from .mockers import (launch_command, mock_run_cmd_and_discard_output,
+                      rewrite_definition_file)
 
 
 class TestGo(BaseTest):
@@ -13,7 +14,7 @@ class TestGo(BaseTest):
         parent/upstream branch of the current branch.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
@@ -49,7 +50,7 @@ class TestGo(BaseTest):
         child/downstream branch of the current branch.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
@@ -66,18 +67,13 @@ class TestGo(BaseTest):
         rewrite_definition_file(body)
         launch_command("go", "down")
 
-        assert 'level-1-branch' == launch_command("show", "current").strip(), \
-            ("Verify that 'git machete go down' performs 'git checkout' to "
-             "the child/downstream branch of the current branch."
-             )
+        assert launch_command("show", "current").strip() == 'level-1-branch'
+
         # check short command behaviour
         self.repo_sandbox.check_out("level-0-branch")
         launch_command("g", "d")
 
-        assert 'level-1-branch' == launch_command("show", "current").strip(), \
-            ("Verify that 'git machete g d' performs 'git checkout' to "
-             "the child/downstream branch of the current branch."
-             )
+        assert launch_command("show", "current").strip() == 'level-1-branch'
 
     def test_go_first_root_with_downstream(self, mocker: Any) -> None:
         """Verify behaviour of a 'git machete go first' command.
@@ -87,7 +83,7 @@ class TestGo(BaseTest):
         if root branch has any downstream branches.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
@@ -145,7 +141,7 @@ class TestGo(BaseTest):
         if root branch has no downstream.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
@@ -178,7 +174,7 @@ class TestGo(BaseTest):
         has any downstream branches.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
@@ -232,7 +228,7 @@ class TestGo(BaseTest):
         when successor branch exists within the root tree.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
@@ -278,7 +274,7 @@ class TestGo(BaseTest):
         share root with the current branch.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
@@ -320,7 +316,7 @@ class TestGo(BaseTest):
         when predecessor branch exists within the root tree.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
@@ -365,7 +361,7 @@ class TestGo(BaseTest):
         branch doesn't exist.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
@@ -403,7 +399,7 @@ class TestGo(BaseTest):
         the root of the current branch.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
diff --git a/tests/test_help.py b/tests/test_help.py
index 8956d7f..340411d 100644
--- a/tests/test_help.py
+++ b/tests/test_help.py
@@ -6,21 +6,19 @@ from git_machete.cli import commands_and_aliases
 from git_machete.exceptions import ExitCode
 
 from .base_test import BaseTest
-from .mockers import launch_command, mock_exit_script_no_exit, mock_run_cmd
+from .mockers import launch_command, mock_run_cmd_and_discard_output
 
 
 class TestHelp(BaseTest):
 
     def test_help(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         with pytest.raises(SystemExit) as e:
             launch_command()
         assert ExitCode.ARGUMENT_ERROR == e.value.code
 
-        with pytest.raises(SystemExit) as e:
-            launch_command("help")
-        assert ExitCode.SUCCESS == e.value.code
+        launch_command("help")
 
         with pytest.raises(SystemExit) as e:
             launch_command("help", "no-such-command")
@@ -30,9 +28,7 @@ class TestHelp(BaseTest):
 
         for command in commands_and_aliases:
 
-            with pytest.raises(SystemExit) as e:
-                launch_command("help", command)
-            assert ExitCode.SUCCESS == e.value.code
+            launch_command("help", command)
 
             if command not in help_topics:
                 with pytest.raises(SystemExit) as e:
@@ -43,8 +39,7 @@ class TestHelp(BaseTest):
                     launch_command(command, "--help")
                 assert ExitCode.ARGUMENT_ERROR == e.value.code
 
-    def test_help_output_has_no_ansi_codes(self, mocker: Any) -> None:
-        mocker.patch('git_machete.cli.exit_script', mock_exit_script_no_exit)
+    def test_help_output_has_no_ansi_codes(self) -> None:
         for command in commands_and_aliases:
             help_output = launch_command('help', command)
             assert '\033' not in help_output
diff --git a/tests/test_list.py b/tests/test_list.py
index 821bb76..1c7f48d 100644
--- a/tests/test_list.py
+++ b/tests/test_list.py
@@ -5,8 +5,8 @@ import pytest
 from git_machete.exceptions import ExitCode
 
 from .base_test import BaseTest
-from .mockers import (assert_command, launch_command, mock_run_cmd,
-                      rewrite_definition_file)
+from .mockers import (assert_success, launch_command,
+                      mock_run_cmd_and_discard_output, rewrite_definition_file)
 
 
 class TestList(BaseTest):
@@ -15,7 +15,7 @@ class TestList(BaseTest):
         """
         Verify behaviour of a 'git machete list' command.
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("master")
@@ -68,7 +68,7 @@ class TestList(BaseTest):
         feature_0_1
         feature_1
         """
-        assert_command(
+        assert_success(
             ['list', 'managed'],
             expected_output
         )
@@ -77,7 +77,7 @@ class TestList(BaseTest):
         feature_2
         feature_3
         """
-        assert_command(
+        assert_success(
             ['list', 'addable'],
             expected_output
         )
@@ -88,7 +88,7 @@ class TestList(BaseTest):
         feature_0_1
         feature_1
         """
-        assert_command(
+        assert_success(
             ['list', 'childless'],
             expected_output
         )
@@ -100,7 +100,7 @@ class TestList(BaseTest):
         feature_0_1
         feature_1
         """
-        assert_command(
+        assert_success(
             ['list', 'slidable'],
             expected_output
         )
@@ -108,7 +108,7 @@ class TestList(BaseTest):
         expected_output = """
         feature_0_0_0
         """
-        assert_command(
+        assert_success(
             ['list', 'slidable-after', 'feature_0_0'],
             expected_output
         )
@@ -116,7 +116,7 @@ class TestList(BaseTest):
         expected_output = """
         feature_2
         """
-        assert_command(
+        assert_success(
             ['list', 'unmanaged'],
             expected_output
         )
@@ -127,13 +127,13 @@ class TestList(BaseTest):
         expected_output = """
         feature_1
         """
-        assert_command(
+        assert_success(
             ['list', 'with-overridden-fork-point'],
             expected_output
         )
 
         launch_command('fork-point', '--unset-override')
-        assert_command(
+        assert_success(
             ['list', 'with-overridden-fork-point'],
             ""
         )
diff --git a/tests/test_log.py b/tests/test_log.py
index 55b60a1..261abd8 100644
--- a/tests/test_log.py
+++ b/tests/test_log.py
@@ -1,24 +1,23 @@
 from typing import Any
 
 from .base_test import BaseTest
-from .mockers import (get_current_commit_hash, launch_command,
-                      mock_run_cmd_and_forward_stdout)
+from .mockers import launch_command, mock_run_cmd_and_forward_output
 
 
 class TestLog(BaseTest):
 
     def test_log(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_forward_stdout)
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_forward_output)
 
         self.repo_sandbox.new_branch('root')
         self.repo_sandbox.commit()
-        roots_only_commit_hash = get_current_commit_hash()
+        roots_only_commit_hash = self.repo_sandbox.get_current_commit_hash()
 
         self.repo_sandbox.new_branch('child')
         self.repo_sandbox.commit()
-        childs_first_commit_hash = get_current_commit_hash()
+        childs_first_commit_hash = self.repo_sandbox.get_current_commit_hash()
         self.repo_sandbox.commit()
-        childs_second_commit_hash = get_current_commit_hash()
+        childs_second_commit_hash = self.repo_sandbox.get_current_commit_hash()
 
         log_contents = [launch_command('log'), launch_command('log', 'child'), launch_command('log', 'refs/heads/child')]
 
diff --git a/tests/test_reapply.py b/tests/test_reapply.py
index e1233e1..0cb39ff 100644
--- a/tests/test_reapply.py
+++ b/tests/test_reapply.py
@@ -1,9 +1,9 @@
 from typing import Any
 
 from .base_test import BaseTest
-from .mockers import (assert_command, fixed_author_and_committer_date,
-                      launch_command, mock_run_cmd, overridden_environment,
-                      rewrite_definition_file)
+from .mockers import (assert_success, fixed_author_and_committer_date,
+                      launch_command, mock_run_cmd_and_discard_output,
+                      overridden_environment, rewrite_definition_file)
 
 
 class TestReapply(BaseTest):
@@ -13,12 +13,12 @@ class TestReapply(BaseTest):
         Verify that 'git machete reapply' performs
         'git rebase' to the fork point of the current branch.
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         with fixed_author_and_committer_date():
             (
                 self.repo_sandbox
-                .remove_remote("origin")
+                .remove_remote()
                 .new_branch("level-0-branch")
                 .commit("Basic commit.")
                 .new_branch("level-1-branch")
@@ -42,7 +42,7 @@ class TestReapply(BaseTest):
         rewrite_definition_file(body)
 
         self.repo_sandbox.check_out("level-1-branch")
-        assert_command(
+        assert_success(
             ["status", "-L"],
             """
             level-0-branch
@@ -65,7 +65,7 @@ class TestReapply(BaseTest):
             with fixed_author_and_committer_date():
                 launch_command("reapply")
 
-        assert_command(
+        assert_success(
             ["status", "-L"],
             """
             level-0-branch
@@ -87,7 +87,7 @@ class TestReapply(BaseTest):
             with fixed_author_and_committer_date():
                 launch_command("reapply", "--fork-point=64f8913")
 
-        assert_command(
+        assert_success(
             ["status", "-L"],
             """
             level-0-branch
diff --git a/tests/test_show.py b/tests/test_show.py
index d641272..20d70f5 100644
--- a/tests/test_show.py
+++ b/tests/test_show.py
@@ -1,13 +1,13 @@
 from typing import Any
 
 from .base_test import BaseTest
-from .mockers import (assert_command, launch_command, mock_run_cmd,
-                      rewrite_definition_file)
+from .mockers import (assert_failure, assert_success, launch_command,
+                      mock_run_cmd_and_discard_output, rewrite_definition_file)
 
 
 class TestShow(BaseTest):
 
-    def setup_discover_standard_tree(self) -> None:
+    def setup_standard_tree(self) -> None:
         (
             self.repo_sandbox.new_branch("root")
             .commit("root")
@@ -61,7 +61,7 @@ class TestShow(BaseTest):
             """
         rewrite_definition_file(body)
 
-        assert_command(
+        assert_success(
             ["status"],
             """
             develop
@@ -83,9 +83,9 @@ class TestShow(BaseTest):
         )
 
     def test_show(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
-        self.setup_discover_standard_tree()
+        self.setup_standard_tree()
 
         assert launch_command("show", "up").strip() == "hotfix/add-trigger"
         assert launch_command("show", "up", "call-ws").strip() == "develop"
@@ -100,7 +100,7 @@ class TestShow(BaseTest):
         root tree.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
@@ -131,30 +131,31 @@ class TestShow(BaseTest):
         child/downstream branch one below current one.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
-            self.repo_sandbox.new_branch("level-0-branch")
-            .commit()
-            .new_branch("level-1-branch")
+            self.repo_sandbox
+            .new_branch("level-0-branch")
             .commit()
+            .new_branch("level-1a-branch")
+            .new_branch("level-2-branch")
+            .check_out("level-0-branch")
+            .new_branch("level-1b-branch")
             .check_out("level-0-branch")
         )
         body: str = \
             """
             level-0-branch
-                level-1-branch
+                level-1a-branch
+                    level-2-branch
+                level-1b-branch
             """
         rewrite_definition_file(body)
 
-        assert 'level-1-branch' == launch_command("show", "down").strip(), \
-            ("Verify that 'git machete show down' displays name of "
-             "a child/downstream branch one below current one."
-             )  # check short command behaviour
-        assert 'level-1-branch' == launch_command("show", "d").strip(), \
-            ("Verify that 'git machete show d' displays name of "
-             "a child/downstream branch one below current one."
-             )
+        assert launch_command("show", "down").strip() == "level-1a-branch\nlevel-1b-branch"
+        assert launch_command("show", "d").strip() == "level-1a-branch\nlevel-1b-branch"
+        assert launch_command("show", "d", "level-1a-branch").strip() == "level-2-branch"
+        assert_failure(["show", "d", "level-1b-branch"], "Branch level-1b-branch has no downstream branch")
 
     def test_show_first(self, mocker: Any) -> None:
         """Verify behaviour of a 'git machete show first' command.
@@ -164,7 +165,7 @@ class TestShow(BaseTest):
         branch has any downstream branches.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
@@ -219,7 +220,7 @@ class TestShow(BaseTest):
         branch has any downstream branches.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
@@ -268,7 +269,7 @@ class TestShow(BaseTest):
         when successor branch exists within the root tree.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
@@ -291,16 +292,9 @@ class TestShow(BaseTest):
             """
         rewrite_definition_file(body)
 
-        assert 'level-1b-branch' == launch_command("show", "next").strip(), \
-            ("Verify that 'git machete show next' displays name of "
-             "a branch right after the current one in the config file "
-             "when successor branch exists within the root tree."
-             )
-        assert 'level-1b-branch' == launch_command("show", "n").strip(), \
-            ("Verify that 'git machete show n' displays name of "
-             "a branch right after the current one in the config file "
-             "when successor branch exists within the root tree."
-             )
+        assert launch_command("show", "next").strip() == 'level-1b-branch'
+        assert launch_command("show", "n").strip() == 'level-1b-branch'
+        assert_failure(["show", "n", "level-1b-branch"], "Branch level-1b-branch has no successor")
 
     def test_show_prev(self, mocker: Any) -> None:
         """Verify behaviour of a 'git machete show prev' command.
@@ -310,7 +304,7 @@ class TestShow(BaseTest):
         when predecessor branch exists within the root tree.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
@@ -332,16 +326,9 @@ class TestShow(BaseTest):
             """
         rewrite_definition_file(body)
 
-        assert 'level-2a-branch' == launch_command("show", "prev").strip(), \
-            ("Verify that 'git machete show prev' displays name of "
-             "a branch right before the current one in the config file "
-             "when predecessor branch exists within the root tree."
-             )
-        assert 'level-2a-branch' == launch_command("show", "p").strip(), \
-            ("Verify that 'git machete show p' displays name of "
-             "a branch right before the current one in the config file "
-             "when predecessor branch exists within the root tree."
-             )
+        assert launch_command("show", "prev").strip() == 'level-2a-branch'
+        assert launch_command("show", "p").strip() == 'level-2a-branch'
+        assert_failure(["show", "p", "level-0-branch"], "Branch level-0-branch has no predecessor")
 
     def test_show_root(self, mocker: Any) -> None:
         """Verify behaviour of a 'git machete show root' command.
@@ -350,7 +337,7 @@ class TestShow(BaseTest):
         the current branch.
 
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
diff --git a/tests/test_slide_out.py b/tests/test_slide_out.py
index 75d269a..608cbb6 100644
--- a/tests/test_slide_out.py
+++ b/tests/test_slide_out.py
@@ -1,17 +1,16 @@
 from typing import Any
 
-import pytest
-
 from .base_test import BaseTest, git
-from .mockers import (assert_command, get_current_commit_hash, launch_command,
-                      mock_ask_if, mock_run_cmd,
-                      mock_run_cmd_and_forward_stdout, rewrite_definition_file)
+from .mockers import (assert_failure, assert_success,
+                      fixed_author_and_committer_date, launch_command,
+                      mock_input_returning_y, mock_run_cmd_and_discard_output,
+                      mock_run_cmd_and_forward_output, rewrite_definition_file)
 
 
 class TestSlideOut(BaseTest):
 
     def test_slide_out(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         (
             self.repo_sandbox.new_branch("develop")
             .commit("develop commit")
@@ -47,7 +46,7 @@ class TestSlideOut(BaseTest):
             """
         rewrite_definition_file(body)
 
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             develop
@@ -75,7 +74,7 @@ class TestSlideOut(BaseTest):
         launch_command("go", "up")
         launch_command("slide-out", "-n")
 
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             develop
@@ -100,7 +99,7 @@ class TestSlideOut(BaseTest):
         launch_command("go", "up")
         launch_command("go", "up")
 
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
                 develop
@@ -121,7 +120,7 @@ class TestSlideOut(BaseTest):
 
         launch_command("slide-out", "-n")
 
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             develop
@@ -138,7 +137,7 @@ class TestSlideOut(BaseTest):
         )
 
         launch_command("traverse", "-Wy")
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             develop
@@ -158,14 +157,14 @@ class TestSlideOut(BaseTest):
         # This just slices the branch off the tree.
         launch_command("go", "down")
         assert "child_d" in git.get_local_branches()
-        mocker.patch('git_machete.client.MacheteClient.ask_if', mock_ask_if)
-        assert_command(
+        mocker.patch('builtins.input', mock_input_returning_y)
+        assert_success(
             ["slide-out", "-n", "--delete"],
-            ""
+            "Delete branch child_d (unmerged to HEAD)? (y, N, q) \n"
         )
         assert "child_d" not in git.get_local_branches()
 
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             develop
@@ -179,7 +178,7 @@ class TestSlideOut(BaseTest):
         )
 
     def test_slide_out_with_valid_down_fork_point(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_forward_stdout)
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_forward_output)
 
         (
             self.repo_sandbox.new_branch('branch-0')
@@ -192,7 +191,7 @@ class TestSlideOut(BaseTest):
                 .commit()
                 .commit('Second commit on branch-3.')
         )
-        hash_of_second_commit_on_branch_3 = get_current_commit_hash()
+        hash_of_second_commit_on_branch_3 = self.repo_sandbox.get_current_commit_hash()
         self.repo_sandbox.commit("Third commit on branch-3.")
 
         body: str = \
@@ -216,22 +215,23 @@ class TestSlideOut(BaseTest):
             """
         )
 
-        assert_command(['status', '-l'], expected_status_output)
+        assert_success(['status', '-l'], expected_status_output)
 
-    def test_slide_out_with_invalid_down_fork_point(self, mocker: Any) -> None:
-        (
-            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.')
-        )
-        hash_of_commit_that_is_not_ancestor_of_branch_2 = get_current_commit_hash()
+    def test_slide_out_with_invalid_down_fork_point(self) -> None:
+        with fixed_author_and_committer_date():
+            (
+                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.')
+            )
+        hash_of_commit_that_is_not_ancestor_of_branch_2 = self.repo_sandbox.get_current_commit_hash()
 
         body: str = \
             """
@@ -242,12 +242,12 @@ class TestSlideOut(BaseTest):
             """
         rewrite_definition_file(body)
 
-        with pytest.raises(SystemExit):
-            launch_command(
-                'slide-out', '-n', 'branch-1', 'branch-2', '-d',
-                hash_of_commit_that_is_not_ancestor_of_branch_2)
+        assert_failure(
+            ['slide-out', '-n', 'branch-1', 'branch-2', '-d', hash_of_commit_that_is_not_ancestor_of_branch_2],
+            "Fork point 790f30303cca5e14d58f85c5aa1359a8d4dace8c is not ancestor of or the tip of the branch-3 branch."
+        )
 
-    def test_slide_out_with_down_fork_point_and_multiple_children_of_last_branch(self, mocker: Any) -> None:
+    def test_slide_out_with_down_fork_point_and_multiple_children_of_last_branch(self) -> None:
         (
             self.repo_sandbox.new_branch('branch-0')
                 .commit()
@@ -260,7 +260,7 @@ class TestSlideOut(BaseTest):
                 .commit()
         )
 
-        hash_of_only_commit_on_branch_2b = get_current_commit_hash()
+        hash_of_only_commit_on_branch_2b = self.repo_sandbox.get_current_commit_hash()
 
         body: str = \
             """
@@ -271,7 +271,7 @@ class TestSlideOut(BaseTest):
             """
         rewrite_definition_file(body)
 
-        with pytest.raises(SystemExit):
-            launch_command(
-                'slide-out', '-n', 'branch-1', '-d',
-                hash_of_only_commit_on_branch_2b)
+        assert_failure(
+            ['slide-out', '-n', 'branch-1', '-d', hash_of_only_commit_on_branch_2b],
+            "Last branch to slide out can't have more than one child branch if option --down-fork-point is passed."
+        )
diff --git a/tests/test_squash.py b/tests/test_squash.py
index c470f58..663f626 100644
--- a/tests/test_squash.py
+++ b/tests/test_squash.py
@@ -1,7 +1,7 @@
-import pytest
 
 from .base_test import BaseTest, popen
-from .mockers import get_current_commit_hash, launch_command
+from .mockers import (assert_failure, fixed_author_and_committer_date,
+                      launch_command)
 
 
 class TestSquash(BaseTest):
@@ -12,7 +12,7 @@ class TestSquash(BaseTest):
                 .commit("First commit.")
                 .commit("Second commit.")
         )
-        fork_point = get_current_commit_hash()
+        fork_point = self.repo_sandbox.get_current_commit_hash()
 
         (
             self.repo_sandbox.commit("Third commit.")
@@ -33,20 +33,22 @@ class TestSquash(BaseTest):
              " from one succeeding the fork-point until tip of the branch.")
 
     def test_squash_with_invalid_fork_point(self) -> None:
-        (
-            self.repo_sandbox.new_branch('branch-0')
-                .commit()
-                .new_branch('branch-1a')
-                .commit()
-        )
-        fork_point_to_branch_1a = get_current_commit_hash()
-
-        (
-            self.repo_sandbox.check_out('branch-0')
-                .new_branch('branch-1b')
-                .commit()
+        with fixed_author_and_committer_date():
+            (
+                self.repo_sandbox.new_branch('branch-0')
+                    .commit()
+                    .new_branch('branch-1a')
+                    .commit()
+            )
+            fork_point_to_branch_1a = self.repo_sandbox.get_current_commit_hash()
+
+            (
+                self.repo_sandbox.check_out('branch-0')
+                    .new_branch('branch-1b')
+                    .commit()
+            )
+
+        assert_failure(
+            ['squash', '-f', fork_point_to_branch_1a],
+            "Fork point dcd2db55125a1b67b367565e890a604639949a51 is not ancestor of or the tip of the branch-1b branch."
         )
-
-        with pytest.raises(SystemExit):
-            # First exception MacheteException is raised, followed by SystemExit.
-            launch_command('squash', '-f', fork_point_to_branch_1a)
diff --git a/tests/test_status.py b/tests/test_status.py
index ea156d9..7af65f5 100644
--- a/tests/test_status.py
+++ b/tests/test_status.py
@@ -2,20 +2,14 @@ import os
 from tempfile import mkdtemp
 from typing import Any
 
-import pytest
-
-from git_machete.exceptions import MacheteException
-
 from .base_test import BaseTest
-from .mockers import (assert_command, launch_command, mock_exit_script,
-                      mock_run_cmd, rewrite_definition_file)
+from .mockers import (assert_failure, assert_success, launch_command,
+                      mock_run_cmd_and_discard_output, rewrite_definition_file)
 
 
 class TestStatus(BaseTest):
 
-    def test_branch_reappears_in_definition(self, mocker: Any) -> None:
-        mocker.patch('git_machete.cli.exit_script', mock_exit_script)
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+    def test_branch_reappears_in_definition(self) -> None:
 
         body: str = \
             """
@@ -28,14 +22,9 @@ class TestStatus(BaseTest):
 
         expected_error_message: str = '.git/machete, line 6: branch develop re-appears in the tree definition. ' \
                                       'Edit the definition file manually with git machete edit'
+        assert_failure(['status'], expected_error_message)
 
-        with pytest.raises(MacheteException) as e:
-            launch_command('status')
-        assert e.value.parameter == expected_error_message
-
-    def test_indent_not_multiply_of_base_indent(self, mocker: Any) -> None:
-        mocker.patch('git_machete.cli.exit_script', mock_exit_script)
-
+    def test_indent_not_multiply_of_base_indent(self) -> None:
         body: str = \
             """
             master
@@ -46,15 +35,25 @@ class TestStatus(BaseTest):
 
         expected_error_message: str = '.git/machete, line 4: invalid indent <TAB><SPACE>, expected a multiply of <TAB>. ' \
                                       'Edit the definition file manually with git machete edit'
+        assert_failure(['status'], expected_error_message)
+
+    def test_indent_too_deep(self) -> None:
+        body: str = \
+            """
+            master
+            \tdevelop
+            \t\t\tfoo
+            """
+        rewrite_definition_file(body)
 
-        with pytest.raises(MacheteException) as e:
-            launch_command('status')
-        assert e.value.parameter == expected_error_message
+        expected_error_message: str = '.git/machete, line 4: too much indent (level 3, expected at most 2) for the branch foo. ' \
+                                      'Edit the definition file manually with git machete edit'
+        assert_failure(['status'], expected_error_message)
 
     def test_status_branch_hook_output(self) -> None:
         (
             self.repo_sandbox
-            .remove_remote('origin')
+            .remove_remote()
             .new_branch('master')
             .commit('master commit')
             .new_branch('develop')
@@ -69,7 +68,7 @@ class TestStatus(BaseTest):
         rewrite_definition_file(body)
 
         self.repo_sandbox.write_to_file(".git/hooks/machete-status-branch", "#!/bin/bash\ngit ls-tree $1 | wc -l | sed 's/ *//'")
-        assert_command(
+        assert_success(
             ["status"],
             """
             hint: The '.git/hooks/machete-status-branch' hook was ignored because it's not set as executable.
@@ -81,7 +80,7 @@ class TestStatus(BaseTest):
         )
 
         self.repo_sandbox.set_git_config_key("advice.ignoredHook", "false")
-        assert_command(
+        assert_success(
             ["status"],
             """
             master
@@ -91,7 +90,7 @@ class TestStatus(BaseTest):
         )
 
         self.repo_sandbox.set_file_executable(".git/hooks/machete-status-branch")
-        assert_command(
+        assert_success(
             ["status"],
             """
             master  1
@@ -100,9 +99,28 @@ class TestStatus(BaseTest):
             """
         )
 
+        self.repo_sandbox.write_to_file(".git/hooks/machete-status-branch", "#!/bin/bash\necho '    '")
+        assert_success(
+            ["status"],
+            """
+            master
+            |
+            o-develop *
+            """
+        )
+
+        self.repo_sandbox.write_to_file(".git/hooks/machete-status-branch", "#!/bin/bash\nexit 1")
+        assert_success(
+            ["status"],
+            """
+            master
+            |
+            o-develop *
+            """
+        )
+
     def test_extra_space_before_branch_name(self, mocker: Any) -> None:
-        mocker.patch('git_machete.cli.exit_script', mock_exit_script)
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox
@@ -134,7 +152,7 @@ class TestStatus(BaseTest):
                o- foo *
             """
         )
-        assert_command(['status'], expected_status_output)
+        assert_success(['status'], expected_status_output)
 
         self.repo_sandbox.set_git_config_key('machete.status.extraSpaceBeforeBranchName', 'false')
 
@@ -147,10 +165,10 @@ class TestStatus(BaseTest):
               o-foo *
             """
         )
-        assert_command(['status'], expected_status_output)
+        assert_success(['status'], expected_status_output)
 
     def test_squashed_branch_recognized_as_merged(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("root")
@@ -178,7 +196,7 @@ class TestStatus(BaseTest):
             """
         rewrite_definition_file(body)
 
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             root
@@ -205,7 +223,7 @@ class TestStatus(BaseTest):
         )
 
         # in default mode, feature is detected as "m" (merged) into develop
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             root
@@ -223,7 +241,7 @@ class TestStatus(BaseTest):
         )
 
         # but under --no-detect-squash-merges, feature is detected as "x" (behind) develop
-        assert_command(
+        assert_success(
             ["status", "-l", "--no-detect-squash-merges"],
             """
             root
@@ -245,7 +263,7 @@ class TestStatus(BaseTest):
         # traverse then slide out the feature branch
         launch_command("traverse", "-w", "-y")
 
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             root
@@ -272,7 +290,7 @@ class TestStatus(BaseTest):
         )
 
         # status before fetch will show develop as out of date
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             root
@@ -290,7 +308,7 @@ class TestStatus(BaseTest):
         # fetch-traverse will fetch upstream squash, detect, and slide out the child branch
         launch_command("traverse", "-W", "-y")
 
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             root
@@ -303,8 +321,7 @@ class TestStatus(BaseTest):
         )
 
     def test_inferring_counterpart_for_fetching_of_branch(self, mocker: Any) -> None:
-        mocker.patch('git_machete.cli.exit_script', mock_exit_script)
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         origin_1_remote_path = mkdtemp()
         self.repo_sandbox.new_repo(origin_1_remote_path)
@@ -354,10 +371,10 @@ class TestStatus(BaseTest):
                   o-mars *
             """
         )
-        assert_command(['status'], expected_status_output)
+        assert_success(['status'], expected_status_output)
 
     def test_status_when_child_branch_is_pushed_immediately_after_creation(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("master")
@@ -385,4 +402,36 @@ class TestStatus(BaseTest):
               o-bar * (ahead of origin)
             """
         )
-        assert_command(['status'], expected_status_output)
+        assert_success(['status'], expected_status_output)
+
+    def test_status_fork_point_without_reflogs(self, mocker: Any) -> None:
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
+
+        (
+            self.repo_sandbox
+            .remove_remote()
+            .new_branch("master")
+            .commit()
+            .new_branch("develop")
+            .commit()
+            .check_out("master")
+            .commit()
+        )
+        body: str = \
+            """
+            master
+                develop
+            """
+        rewrite_definition_file(body)
+
+        self.repo_sandbox.remove_file(".git/logs/")
+
+        expected_status_output = (
+            """
+            master *
+            |
+            | Some commit message.
+            x-develop
+            """
+        )
+        assert_success(['status', '-l'], expected_status_output)
diff --git a/tests/test_traverse.py b/tests/test_traverse.py
index 814830a..c6fdc42 100644
--- a/tests/test_traverse.py
+++ b/tests/test_traverse.py
@@ -1,12 +1,8 @@
 from typing import Any
 
-import pytest
-
-from git_machete.exceptions import MacheteException
-
 from .base_test import BaseTest, git
-from .mockers import (assert_command, launch_command, mock_exit_script,
-                      mock_run_cmd, rewrite_definition_file)
+from .mockers import (assert_failure, assert_success, launch_command,
+                      mock_run_cmd_and_discard_output, rewrite_definition_file)
 
 
 class TestTraverse(BaseTest):
@@ -64,7 +60,7 @@ class TestTraverse(BaseTest):
                     ignore-trailing
             """
         rewrite_definition_file(body)
-        assert_command(
+        assert_success(
             ["status"],
             """
             develop
@@ -85,12 +81,48 @@ class TestTraverse(BaseTest):
             """
         )
 
+    def test_traverse_no_remotes(self, mocker: Any) -> None:
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
+        self.setup_standard_tree()
+        self.repo_sandbox.remove_remote()
+
+        launch_command("traverse", "-Wy")
+        assert_success(
+            ["status", "-l"],
+            """
+            develop
+            |
+            | Allow ownership links
+            | 1st round of fixes
+            o-allow-ownership-link
+            | |
+            | | Build arbitrarily long chains
+            | o-build-chain
+            |
+            | Call web service
+            | 1st round of fixes
+            | 2nd round of fixes
+            o-call-ws
+              |
+              | Drop unneeded SQL constraints
+              o-drop-constraint
+
+            master
+            |
+            | HOTFIX Add the trigger (amended)
+            o-hotfix/add-trigger
+              |
+              | Ignore trailing data
+              o-ignore-trailing *
+            """
+        )
+
     def test_traverse_no_push(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         self.setup_standard_tree()
 
         launch_command("traverse", "-Wy", "--no-push")
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             develop
@@ -121,11 +153,11 @@ class TestTraverse(BaseTest):
         )
 
     def test_traverse_no_push_override(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         self.setup_standard_tree()
         self.repo_sandbox.check_out("hotfix/add-trigger")
         launch_command("t", "-Wy", "--no-push", "--push", "--start-from=here")
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             develop
@@ -157,7 +189,7 @@ class TestTraverse(BaseTest):
         self.repo_sandbox.check_out("ignore-trailing")
         self.repo_sandbox.set_git_config_key("machete.traverse.push", "false")
         launch_command("t", "-Wy")
-        assert_command(
+        assert_success(
             ["status"],
             """
             develop
@@ -179,7 +211,7 @@ class TestTraverse(BaseTest):
         )
 
         launch_command("t", "-Wy", "--push-untracked")
-        assert_command(
+        assert_success(
             ["status"],
             """
             develop
@@ -202,7 +234,7 @@ class TestTraverse(BaseTest):
 
         self.repo_sandbox.set_git_config_key("machete.traverse.push", "true")
         launch_command("t", "-Wy", "--no-push-untracked")
-        assert_command(
+        assert_success(
             ["status"],
             """
             develop
@@ -224,11 +256,11 @@ class TestTraverse(BaseTest):
         )
 
     def test_traverse_no_push_untracked(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         self.setup_standard_tree()
 
         launch_command("traverse", "-Wy", "--no-push-untracked")
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             develop
@@ -259,11 +291,11 @@ class TestTraverse(BaseTest):
         )
 
     def test_traverse_push_config_key(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         self.setup_standard_tree()
         self.repo_sandbox.set_git_config_key('machete.traverse.push', 'false')
         launch_command("traverse", "-Wy")
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             develop
@@ -294,7 +326,7 @@ class TestTraverse(BaseTest):
         )
 
     def test_traverse_no_push_no_checkout(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         (
             self.repo_sandbox.new_branch("root")
                 .commit("root")
@@ -329,7 +361,7 @@ class TestTraverse(BaseTest):
                 hotfix/add-trigger
             """
         rewrite_definition_file(body)
-        assert_command(
+        assert_success(
             ["status"],
             """
             develop
@@ -364,16 +396,16 @@ class TestTraverse(BaseTest):
         Further info under git machete traverse --help.
         Returned to the initial branch hotfix/add-trigger
         '''
-        assert_command(["traverse", "-Wy", "--no-push"],
+        assert_success(["traverse", "-Wy", "--no-push"],
                        expected_result)
 
     def test_traverse_and_squash(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         self.setup_standard_tree()
 
         self.repo_sandbox.check_out("hotfix/add-trigger")
         launch_command("traverse", "--fetch", "--start-from=root", "--return-to=here", "-y")
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             develop
@@ -405,7 +437,7 @@ class TestTraverse(BaseTest):
         assert git.get_current_branch() == "hotfix/add-trigger"
 
         launch_command("traverse", "-wy")
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             develop
@@ -439,7 +471,7 @@ class TestTraverse(BaseTest):
         for _ in range(3):
             launch_command("go", "prev")
         launch_command("squash")
-        assert_command(
+        assert_success(
             ["status", "-l"],
             """
             develop
@@ -468,7 +500,7 @@ class TestTraverse(BaseTest):
         )
 
     def test_traverse_with_merge(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         (
             self.repo_sandbox
             .new_branch("develop")
@@ -491,7 +523,7 @@ class TestTraverse(BaseTest):
             """
         rewrite_definition_file(body)
         launch_command("traverse", '-y', '-M', '--no-edit-merge')
-        assert_command(
+        assert_success(
             ["status"],
             """
             develop
@@ -503,7 +535,7 @@ class TestTraverse(BaseTest):
         )
 
     def test_traverse_qualifiers_no_push(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         self.setup_standard_tree()
 
         body: str = \
@@ -520,7 +552,7 @@ class TestTraverse(BaseTest):
         rewrite_definition_file(body)
 
         launch_command("traverse", "-Wy", "--no-push-untracked", "--push-untracked")
-        assert_command(
+        assert_success(
             ["status"],
             """
             develop
@@ -542,7 +574,7 @@ class TestTraverse(BaseTest):
         )
 
     def test_traverse_qualifiers_no_rebase(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         self.setup_standard_tree()
 
         body: str = \
@@ -559,7 +591,7 @@ class TestTraverse(BaseTest):
         rewrite_definition_file(body)
 
         launch_command("traverse", "-Wy")
-        assert_command(
+        assert_success(
             ["status"],
             """
             develop
@@ -581,7 +613,7 @@ class TestTraverse(BaseTest):
         )
 
     def test_traverse_qualifiers_no_rebase_no_push(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         self.setup_standard_tree()
 
         body: str = \
@@ -598,7 +630,7 @@ class TestTraverse(BaseTest):
         rewrite_definition_file(body)
 
         launch_command("traverse", "-Wy")
-        assert_command(
+        assert_success(
             ["status"],
             """
             develop
@@ -620,7 +652,7 @@ class TestTraverse(BaseTest):
         )
 
     def test_traverse_qualifiers_no_slide_out(self, mocker: Any) -> None:
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
         self.setup_standard_tree()
 
         body: str = \
@@ -638,7 +670,7 @@ class TestTraverse(BaseTest):
         self.repo_sandbox.check_out('develop').merge('call-ws')
 
         launch_command("traverse", "-Wy")
-        assert_command(
+        assert_success(
             ["status"],
             """
             develop *
@@ -659,10 +691,8 @@ class TestTraverse(BaseTest):
             """,
         )
 
-    def test_traverse_no_managed_branches(self, mocker: Any) -> None:
-        mocker.patch('git_machete.cli.exit_script', mock_exit_script)
+    def test_traverse_no_managed_branches(self) -> None:
 
-        with pytest.raises(MacheteException) as e:
-            launch_command("traverse")
-        assert e.value.args[0] == "No branches listed in .git/machete; " \
-                                  "use `git machete discover` or `git machete edit`, or edit .git/machete manually."
+        expected_error_message = "No branches listed in .git/machete; " \
+                                 "use git machete discover or git machete edit, or edit .git/machete manually."
+        assert_failure(["traverse"], expected_error_message)
diff --git a/tests/test_update.py b/tests/test_update.py
index 8e66ff3..8ac433f 100644
--- a/tests/test_update.py
+++ b/tests/test_update.py
@@ -1,25 +1,19 @@
 from typing import Any
 
-import pytest
-
-from git_machete.exceptions import MacheteException
-
 from .base_test import BaseTest, git, popen
-from .mockers import (get_current_commit_hash, launch_command,
-                      mock_exit_script, mock_run_cmd, overridden_environment,
-                      rewrite_definition_file)
+from .mockers import (assert_failure, fixed_author_and_committer_date,
+                      launch_command, mock_run_cmd_and_discard_output,
+                      overridden_environment, rewrite_definition_file)
 
 
 class TestUpdate(BaseTest):
 
     def test_update_with_fork_point_not_specified(self, mocker: Any) -> None:
-        """Verify behaviour of a 'git machete update --no-interactive-rebase' command.
-
+        """
         Verify that 'git machete update --no-interactive-rebase' performs
         'git rebase' to the parent branch of the current branch.
-
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
@@ -39,7 +33,7 @@ class TestUpdate(BaseTest):
             """
         rewrite_definition_file(body)
 
-        parents_new_commit_hash = get_current_commit_hash()
+        parents_new_commit_hash = self.repo_sandbox.get_current_commit_hash()
         self.repo_sandbox.check_out("level-1-branch")
         launch_command("update", "--no-interactive-rebase")
         new_fork_point_hash = launch_command("fork-point").strip()
@@ -49,23 +43,51 @@ class TestUpdate(BaseTest):
              "'git rebase' to the parent branch of the current branch."
              )
 
-    def test_update_drops_empty_commits(self, mocker: Any) -> None:
-        """Verify behaviour of a 'git machete update' command.
-
-        Verify that 'git machete update' drops effectively-empty commits if the underlying git supports that behavior.
-        """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+    def test_update_by_merge(self, mocker: Any) -> None:
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         (
             self.repo_sandbox.new_branch("level-0-branch")
             .commit("Basic commit.")
             .new_branch("level-1-branch")
-            .commit("level-1 commit")
-            .commit("level-1 commit... but to be cherry-picked onto level-0-branch")
+            .commit("Only level-1 commit.")
+            .new_branch("level-2-branch")
+            .commit("Only level-2 commit.")
             .check_out("level-0-branch")
             .commit("New commit on level-0-branch")
-            .execute("git cherry-pick level-1-branch")
         )
+        body: str = \
+            """
+            level-0-branch
+                level-1-branch
+                    level-2-branch
+            """
+        rewrite_definition_file(body)
+
+        self.repo_sandbox.check_out("level-1-branch")
+        old_level_1_commit_hash = self.repo_sandbox.get_current_commit_hash()
+        launch_command("update", "--merge", "--no-edit-merge")
+
+        assert self.repo_sandbox.is_ancestor(old_level_1_commit_hash, "level-1-branch")
+        assert self.repo_sandbox.is_ancestor("level-0-branch", "level-1-branch")
+
+    def test_update_drops_empty_commits(self, mocker: Any) -> None:
+        """
+        Verify that 'git machete update' drops effectively-empty commits if the underlying git supports that behavior.
+        """
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
+
+        with fixed_author_and_committer_date():
+            (
+                self.repo_sandbox.new_branch("level-0-branch")
+                .commit("Basic commit.")
+                .new_branch("level-1-branch")
+                .commit("level-1 commit")
+                .commit("level-1 commit... but to be cherry-picked onto level-0-branch")
+                .check_out("level-0-branch")
+                .commit("New commit on level-0-branch")
+                .execute("git cherry-pick level-1-branch")
+            )
         body: str = \
             """
             level-0-branch
@@ -73,7 +95,7 @@ class TestUpdate(BaseTest):
             """
         rewrite_definition_file(body)
 
-        parents_new_commit_hash = get_current_commit_hash()
+        parents_new_commit_hash = self.repo_sandbox.get_current_commit_hash()
         self.repo_sandbox.check_out("level-1-branch")
         # Note that `--empty=drop` is the default in NON-interactive mode.
         # We want to check if effectively empty commits are dropped in interactive mode as well.
@@ -83,11 +105,10 @@ class TestUpdate(BaseTest):
             if git.get_git_version() >= (2, 26, 0):
                 launch_command("update")
             else:
-                mocker.patch('git_machete.cli.exit_script', mock_exit_script)
-                with pytest.raises(MacheteException) as e:
-                    launch_command("update")
-                assert "git rebase --interactive --onto refs/heads/level-0-branch" in e.value.parameter
-                assert "level-1-branch returned 1" in e.value.parameter
+                with fixed_author_and_committer_date():
+                    expected_error_message = "git rebase --interactive --onto refs/heads/level-0-branch " \
+                                             "c0306cdd500fc39869505592200258055407bcc6 level-1-branch returned 1"
+                    assert_failure(["update"], expected_error_message)
                 self.repo_sandbox.execute("git rebase --continue")
 
         new_fork_point_hash = launch_command("fork-point").strip()
@@ -98,14 +119,12 @@ class TestUpdate(BaseTest):
         assert "level-1 commit... but to be cherry-picked onto level-0-branch" not in branch_history
 
     def test_update_with_fork_point_specified(self, mocker: Any) -> None:
-        """Verify behaviour of a 'git machete update --no-interactive-rebase -f <commit_hash>' cmd.
-
+        """
         Verify that 'git machete update --no-interactive-rebase -f <commit_hash>'
         performs 'git rebase' to the upstream branch and drops the commits until
         (included) fork point specified by the option '-f'.
-
         """
-        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
 
         branchs_first_commit_msg = "First commit on branch."
         branchs_second_commit_msg = "Second commit on branch."
@@ -116,13 +135,13 @@ class TestUpdate(BaseTest):
             .commit(branchs_first_commit_msg)
             .commit(branchs_second_commit_msg)
         )
-        branch_second_commit_hash = get_current_commit_hash()
+        branch_second_commit_hash = self.repo_sandbox.get_current_commit_hash()
         (
             self.repo_sandbox.commit("Third commit on branch.")
             .check_out("root")
             .commit("Second commit on root.")
         )
-        roots_second_commit_hash = get_current_commit_hash()
+        roots_second_commit_hash = self.repo_sandbox.get_current_commit_hash()
         self.repo_sandbox.check_out("branch-1")
         body: str = \
             """
@@ -152,18 +171,21 @@ class TestUpdate(BaseTest):
              )
 
     def test_update_with_invalid_fork_point(self, mocker: Any) -> None:
-        (
-            self.repo_sandbox.new_branch('branch-0')
-                .commit("Commit on branch-0.")
-                .new_branch("branch-1a")
-                .commit("Commit on branch-1a.")
-        )
-        branch_1a_hash = get_current_commit_hash()
-        (
-            self.repo_sandbox.check_out('branch-0')
-                .new_branch("branch-1b")
-                .commit("Commit on branch-1b.")
-        )
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_discard_output)
+
+        with fixed_author_and_committer_date():
+            (
+                self.repo_sandbox.new_branch('branch-0')
+                    .commit("Commit on branch-0.")
+                    .new_branch("branch-1a")
+                    .commit("Commit on branch-1a.")
+            )
+            branch_1a_hash = self.repo_sandbox.get_current_commit_hash()
+            (
+                self.repo_sandbox.check_out('branch-0')
+                    .new_branch("branch-1b")
+                    .commit("Commit on branch-1b.")
+            )
 
         body: str = \
             """
@@ -173,6 +195,6 @@ class TestUpdate(BaseTest):
             """
         rewrite_definition_file(body)
 
-        with pytest.raises(SystemExit):
-            # First exception MacheteException is raised, followed by SystemExit.
-            launch_command('update', '-f', branch_1a_hash)
+        expected_error_message = "Fork point 807bcd9f5e9c7e52e7866eedcb58c2e000526700 " \
+                                 "is not ancestor of or the tip of the branch-1b branch."
+        assert_failure(['update', '-f', branch_1a_hash], expected_error_message)
diff --git a/tests/test_version.py b/tests/test_version.py
index 65f2b70..98d3a2f 100644
--- a/tests/test_version.py
+++ b/tests/test_version.py
@@ -1,17 +1,13 @@
-from typing import Any
 
 from git_machete import __version__
 
-from .mockers import assert_command, mock_exit_script_no_exit
+from .mockers import assert_success
 
 
 class TestVersion:
 
-    def test_version(self, mocker: Any) -> None:
-        mocker.patch('git_machete.cli.exit_script', mock_exit_script_no_exit)
-        mocker.patch('sys.exit', mock_exit_script_no_exit)
-
-        assert_command(
+    def test_version(self) -> None:
+        assert_success(
             ["version"],
             f"git-machete version {__version__}\n"
         )