Repository: VirtusLab/git-machete
Pull request #916 (2023-04-29 11:06:48)

Add missing tests for yellow-edge cases, among others

codecov-commenter: ## [Codecov](https://codecov.io/gh/VirtusLab/git-machete/pull/916?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) Report
> Merging [#916](https://codecov.io/gh/VirtusLab/git-machete/pull/916?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) (0dcd782) into [develop](https://codecov.io/gh/VirtusLab/git-machete/commit/da84a5f03d0f6f273b9473cf871a73195b66d0fc?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) (da84a5f) will **increase** coverage by `0.55%`.
> The diff coverage is `100.00%`.

```diff
@@             Coverage Diff             @@
##           develop     #916      +/-   ##
===========================================
+ Coverage    75.62%   76.17%   +0.55%     
===========================================
  Files           12       12              
  Lines         3245     3245              
  Branches       746      746              
===========================================
+ Hits          2454     2472      +18     
+ Misses         522      505      -17     
+ Partials       269      268       -1     
```


| [Impacted Files](https://codecov.io/gh/VirtusLab/git-machete/pull/916?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab) | Coverage Δ | |
|---|---|---|
| [git\_machete/client.py](https://codecov.io/gh/VirtusLab/git-machete/pull/916?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=VirtusLab#diff-Z2l0X21hY2hldGUvY2xpZW50LnB5) | `70.75% <100.00%> (+1.16%)` | :arrow_up: |

... and [1 file with indirect coverage changes](https://codecov.io/gh/VirtusLab/git-machete/pull/916/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/git_machete/cli.py b/git_machete/cli.py
index ff9ff8c..79305d2 100644
--- a/git_machete/cli.py
+++ b/git_machete/cli.py
@@ -19,8 +19,6 @@ from git_machete.utils import bold, excluding, fmt, underline, warn
 
 T = TypeVar('T')
 
-initial_current_directory: Optional[str] = utils.get_current_directory_or_none() or os.getenv('PWD')
-
 alias_by_command: Dict[str, str] = {
     "diff": "d",
     "edit": "e",
@@ -47,21 +45,17 @@ command_groups: List[Tuple[str, List[str]]] = [
      ["github"])
 ]
 
-commands_and_aliases = list(long_docs.keys()) + list(alias_by_command.keys())
+commands_and_aliases = list(long_docs.keys()) + list(command_by_alias.keys())
 
 
 def get_help_description(display_help_topics: bool, command: Optional[str] = None) -> str:
     usage_str = ''
-    if command and command in commands_and_aliases:
-        if command in long_docs:
-            usage_str += fmt(textwrap.dedent(long_docs[command]))
-        elif command in command_by_alias:
-            usage_str += fmt(textwrap.dedent(long_docs[command_by_alias[command]]))
+    if command in long_docs:
+        usage_str += fmt(textwrap.dedent(long_docs[command]))
+    elif command in command_by_alias:
+        usage_str += fmt(textwrap.dedent(long_docs[command_by_alias[command]]))
     else:
-        usage_str += get_short_general_usage() + '\n'
-        if command:
-            usage_str += f"\nUnknown command: '{command}'"
-        usage_str += fmt(
+        usage_str += get_short_general_usage() + '\n' + fmt(
             "\n<u>TL;DR tip</u>\n\n"
             "    Get familiar with the help for <b>format</b>, <b>edit</b>,"
             " <b>status</b> and <b>update</b>, in this order.\n\n")
@@ -480,8 +474,7 @@ def update_cli_options_using_config_keys(
 
 def set_utils_global_variables(
         cli_opts: git_machete.options.CommandLineOptions) -> None:
-    if cli_opts.opt_color:
-        utils.ascii_only = (cli_opts.opt_color == "never" or (cli_opts.opt_color == "auto" and not sys.stdout.isatty()))
+    utils.ascii_only = (cli_opts.opt_color == "never" or (cli_opts.opt_color == "auto" and not sys.stdout.isatty()))
     utils.debug_mode = cli_opts.opt_debug
     utils.verbose_mode = cli_opts.opt_verbose
 
@@ -511,6 +504,7 @@ def exit_script(status_code: int, error: Optional[BaseException] = None) -> None
 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()
@@ -735,6 +729,8 @@ def launch(orig_args: List[str]) -> None:
                     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))
@@ -859,6 +855,10 @@ def launch(orig_args: List[str]) -> None:
     except StopInteraction:
         pass
     finally:
+        # Note that this problem (current directory no longer existing due to e.g. underlying git checkouts)
+        # seems to be specific to Linuxes (or to older versions of git).
+        # Under macOS 13.2 + git 2.40.0, the directory isn't removed during a checkout
+        # if it's a current working directory of the git process.
         if initial_current_directory and not utils.does_directory_exist(initial_current_directory):
             nearest_existing_parent_directory = initial_current_directory
             while not utils.does_directory_exist(nearest_existing_parent_directory):
diff --git a/git_machete/client.py b/git_machete/client.py
index 0b9148e..7544cdd 100644
--- a/git_machete/client.py
+++ b/git_machete/client.py
@@ -206,7 +206,7 @@ class MacheteClient:
 
     def render_tree(self) -> List[str]:
         if not self.__indent:
-            self.__indent = "\t"
+            self.__indent = "  "
 
         def render_dfs(branch: LocalBranchShortName, depth: int) -> List[str]:
             annotation = self.__annotations[branch].get_unformatted_text() if branch in self.__annotations else ""
@@ -384,7 +384,7 @@ class MacheteClient:
                 self.__roots += [LocalBranchShortName.of("develop")]
         self.__down_branches = {}
         self.up_branch = {}
-        self.__indent = "\t"
+        self.__indent = "  "
         for branch in self.annotations.keys():
             self.annotations[branch].text_without_qualifiers = ''
 
@@ -1132,7 +1132,7 @@ class MacheteClient:
             yellow_edge_branch: LocalBranchShortName = branches_in_sync_but_fork_point_off[0]
             if len(branches_in_sync_but_fork_point_off) == 1:
                 first_part = (f"yellow edge indicates that fork point for {bold(str(yellow_edge_branch))} "
-                              f"is probably incorrectly inferred,\n or that some extra branch should be between "
+                              f"is probably incorrectly inferred,\nor that some extra branch should be between "
                               f"{bold(str(self.up_branch[yellow_edge_branch]))} and "
                               f"{bold(str(yellow_edge_branch))}")
             else:
@@ -1176,7 +1176,7 @@ class MacheteClient:
                 msg_core_suffix = '' if is_merged_to_remote else f', but not merged to {bold(remote_branch)}'  # type: ignore[arg-type]
                 msg_core = f"{bold(branch)} (merged to HEAD{msg_core_suffix})"
                 msg = f"Delete branch {msg_core}?" + get_pretty_choices('y', 'N', 'q')
-                opt_yes_msg = f"Deleting branch {msg_core}"
+                opt_yes_msg = f"Deleting branch {msg_core}..."
                 ans = self.ask_if(msg, opt_yes_msg, opt_yes=opt_yes)
                 if ans in ('y', 'yes'):
                     self.__git.delete_branch(branch, force=is_merged_to_remote)
@@ -1187,7 +1187,7 @@ class MacheteClient:
             for branch in branches_to_delete_unmerged_to_head:
                 msg_core = f"{bold(branch)} (unmerged to HEAD)"
                 msg = f"Delete branch {msg_core}?" + get_pretty_choices('y', 'N', 'q')
-                opt_yes_msg = f"Deleting branch {msg_core}"
+                opt_yes_msg = f"Deleting branch {msg_core}..."
                 ans = self.ask_if(msg, opt_yes_msg, opt_yes=opt_yes)
                 if ans in ('y', 'yes'):
                     self.__git.delete_branch(branch, force=True)
diff --git a/mypy.ini b/mypy.ini
index 59bd045..32bad6c 100644
--- a/mypy.ini
+++ b/mypy.ini
@@ -1,15 +1,15 @@
 [mypy]
-follow_imports = normal
+check_untyped_defs = True
+disallow_any_generics = True
 disallow_incomplete_defs = True
-disallow_untyped_defs = True
-strict_optional = True
+disallow_subclassing_any = True
 disallow_untyped_calls = True
 disallow_untyped_decorators = True
+disallow_untyped_defs = True
+follow_imports = normal
 no_implicit_optional = True
+strict_optional = True
+warn_redundant_casts = True
 warn_return_any = True
 warn_unused_configs = True
-disallow_subclassing_any = True
-disallow_any_generics = True
-check_untyped_defs = True
-warn_redundant_casts = True
 warn_unused_ignores = True
diff --git a/requirements.pep8.txt b/requirements.pep8.txt
index c18cca0..30c5e1e 100644
--- a/requirements.pep8.txt
+++ b/requirements.pep8.txt
@@ -1,1 +1,2 @@
+autoflake==2.1.1
 autopep8==2.0.2
diff --git a/tox.ini b/tox.ini
index 2c3c6b7..a163feb 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,6 +1,6 @@
 [tox]
 # Environments selected for frequent execution during development
-envlist = isort,mypy-py311,pep8
+envlist = pep8,isort,mypy-py311
 minversion = 2.3.2
 skipsdist = True
 
@@ -16,18 +16,20 @@ deps =
 allowlist_externals = mkdir
 commands =
   mkdir -p test-results/
-  pytest --numprocesses=auto --junitxml=test-results/testenv-{envname}.xml {posargs}
+  pytest -rP --numprocesses=auto --junitxml=test-results/testenv-{envname}.xml {posargs}
 
 [testenv:py36]
 deps =
   -r{toxinidir}/requirements.testenv.py36-txt
 
 [testenv:pep8]
-description = "Apply PEP8 formatting"
+description = "Apply PEP8 formatting + remove unused imports"
 deps =
   -r{toxinidir}/requirements.pep8.txt
 # E402 - Module level import not at top of file
-commands = autopep8 --ignore=E402 --in-place --recursive .
+commands =
+  autoflake --in-place --recursive --remove-all-unused-imports .
+  autopep8 --ignore=E402 --in-place --recursive .
 
 [testenv:pep8-check]
 description = "Check if formatting conforms to PEP 8"
@@ -72,7 +74,8 @@ deps =
 commands =
   coverage erase
   # Generates .coverage binary file, used as the source of truth for the subsequent reports
-  coverage run --branch --include='git_machete/*' --omit='git_machete/tests/*' -m pytest --junitxml=test-results/testenv-{envname}.xml
+  # Note that coverage (as of v7.2.3) doesn't support parallel running (`--numprocesses=...`) via python-xdist
+  coverage run --branch --omit='tests/*' -m pytest --junitxml=test-results/testenv-{envname}.xml
   # Prints a report to console
   coverage report
   # Saves a report to htmlcov/
@@ -80,6 +83,17 @@ commands =
   # Saves a report to coverage.xml (uploaded to Codecov from CI, as Codecov apparently does not accept .coverage binary files)
   coverage xml
 
+[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"
 allowlist_externals = bash


Test patch:
diff --git a/tests/base_test.py b/tests/base_test.py
index b5784ad..2ae8fc6 100644
--- a/tests/base_test.py
+++ b/tests/base_test.py
@@ -1,10 +1,8 @@
 import os
-import random
-import string
 import subprocess
 import time
 from tempfile import mkdtemp
-from typing import Optional
+from typing import List, Optional
 
 from git_machete.git_operations import GitContext
 
@@ -37,6 +35,7 @@ class GitRepositorySandbox:
     def __init__(self) -> None:
         self.remote_path = mkdtemp()
         self.local_path = mkdtemp()
+        self.file_counter = 0
 
     def execute(self, command: str) -> "GitRepositorySandbox":
         subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
@@ -64,16 +63,17 @@ class GitRepositorySandbox:
         return self
 
     def commit(self, message: str = "Some commit message.") -> "GitRepositorySandbox":
-        f = f'{"".join(random.choice(string.ascii_letters) for _ in range(20))}.txt'
+        f = f'{self.file_counter}.txt'
+        self.file_counter += 1
         self.execute(f"touch {f}")
         self.execute(f"git add {f}")
         self.execute(f'git commit -m "{message}"')
         return self
 
-    def add_file_with_content_and_commit(self, file_name: str = 'file_name.txt', file_content: str = 'Some file content\n',
-                                         message: str = "Some commit message.") -> "GitRepositorySandbox":
-        self.write_to_file(file_name=file_name, file_content=file_content)
-        self.execute(f"git add {file_name}")
+    def add_file_and_commit(self, file_path: str = 'file_name.txt', file_content: str = 'Some file content\n',
+                            message: str = "Some commit message.") -> "GitRepositorySandbox":
+        self.write_to_file(file_path=file_path, file_content=file_content)
+        self.execute(f"git add {file_path}")
         self.execute(f'git commit -m "{message}"')
         return self
 
@@ -87,8 +87,8 @@ class GitRepositorySandbox:
         self.execute(f"git push {'--set-upstream' if set_upstream else ''} {remote} {branch}:{tracking_branch}")
         return self
 
-    def sleep(self, seconds: int) -> "GitRepositorySandbox":
-        time.sleep(seconds)
+    def merge(self, branch_name: str) -> "GitRepositorySandbox":
+        self.execute(f'git merge {branch_name}')
         return self
 
     def reset_to(self, revision: str) -> "GitRepositorySandbox":
@@ -107,15 +107,30 @@ class GitRepositorySandbox:
         self.execute(f'git remote remove {remote}')
         return self
 
-    def add_git_config_key(self, key: str, value: str) -> "GitRepositorySandbox":
+    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()
+
+    def set_git_config_key(self, key: str, value: str) -> "GitRepositorySandbox":
         self.execute(f'git config {key} {value}')
         return self
 
-    def write_to_file(self, file_name: str, file_content: str) -> "GitRepositorySandbox":
-        with open(file_name, 'w') as f:
+    def read_file(self, file_name: str) -> str:
+        with open(file_name) as f:
+            return f.read()
+
+    def write_to_file(self, file_path: str, file_content: str) -> "GitRepositorySandbox":
+        dirname = os.path.dirname(file_path)
+        if dirname:
+            os.makedirs(dirname, exist_ok=True)
+        with open(file_path, 'w') as f:
             f.write(file_content)
         return self
 
-    def merge(self, branch_name: str) -> "GitRepositorySandbox":
-        self.execute(f'git merge {branch_name}')
+    def set_file_executable(self, file_name: str) -> "GitRepositorySandbox":
+        os.chmod(file_name, 0o700)
+        return self
+
+    def sleep(self, seconds: int) -> "GitRepositorySandbox":
+        time.sleep(seconds)
         return self
diff --git a/tests/mockers.py b/tests/mockers.py
index e11cdad..b964acf 100644
--- a/tests/mockers.py
+++ b/tests/mockers.py
@@ -1,16 +1,12 @@
+import contextlib
 import io
-import json
 import os
-import re
 import subprocess
 import sys
 import textwrap
-from contextlib import redirect_stderr, redirect_stdout
-from http import HTTPStatus
-from tempfile import mkdtemp
-from typing import Any, Dict, Iterable, List, Optional, Union
-from urllib.error import HTTPError
-from urllib.parse import ParseResult, parse_qs, urlparse
+from contextlib import (_GeneratorContextManager, redirect_stderr,
+                        redirect_stdout)
+from typing import Any, Callable, Iterable, Iterator, Optional
 
 from git_machete import cli, utils
 from git_machete.git_operations import FullCommitHash
@@ -36,225 +32,25 @@ Tips on when and why to use mocking functions:
 """
 
 
-class MockGitHubAPIState:
-    def __init__(self, pulls: List[Dict[str, Any]], issues: Optional[List[Dict[str, Any]]] = None) -> None:
-        self.pulls: List[Dict[str, Any]] = pulls
-        self.user: Dict[str, str] = {'login': 'other_user', 'type': 'User', 'company': 'VirtusLab'}
-        # login must be different from the one used in pull requests, otherwise pull request author will not be annotated
-        self.issues: List[Dict[str, Any]] = issues or []
-
-    def new_request(self) -> "MockGitHubAPIRequest":
-        return MockGitHubAPIRequest(self)
-
-    def get_issue(self, issue_no: str) -> Optional[Dict[str, Any]]:
-        for issue in self.issues:
-            if issue['number'] == issue_no:
-                return issue
-        return None
-
-    def get_pull(self, pull_no: str) -> Optional[Dict[str, Any]]:
-        for pull in self.pulls:
-            if pull['number'] == pull_no:
-                return pull
-        return None
-
-
-class MockGitHubAPIResponse:
-    def __init__(self, status_code: int, response_data: Union[List[Dict[str, Any]], Dict[str, Any]]) -> None:
-        self.response_data: Union[List[Dict[str, Any]], Dict[str, Any]] = response_data
-        self.status_code: int = status_code
-
-    def read(self) -> bytes:
-        return json.dumps(self.response_data).encode()
-
-    def info(self) -> Dict[str, Any]:
-        return {"link": None}
-
-
-class MockGitHubAPIRequest:
-    def __init__(self, github_api_state: MockGitHubAPIState) -> None:
-        self.github_api_state: MockGitHubAPIState = github_api_state
-
-    def __call__(self, url: str, headers: Dict[str, str] = {}, data: Union[str, bytes, None] = None,
-                 method: str = '') -> "MockGitHubAPIResponse":
-        self.parsed_url: ParseResult = urlparse(url, allow_fragments=True)
-        self.parsed_query: Dict[str, List[str]] = parse_qs(self.parsed_url.query)
-        self.json_data: Union[str, bytes, None] = data
-        self.headers: Dict[str, str] = headers
-        return self.handle_method(method)
-
-    def handle_method(self, method: str) -> "MockGitHubAPIResponse":
-        if method == "GET":
-            return self.handle_get()
-        elif method == "PATCH":
-            return self.handle_patch()
-        elif method == "POST":
-            return self.handle_post()
-        else:
-            return self.make_response_object(HTTPStatus.METHOD_NOT_ALLOWED, [])
-
-    def handle_get(self) -> "MockGitHubAPIResponse":
-        if 'pulls' in self.parsed_url.path:
-            full_head_name: Optional[List[str]] = self.parsed_query.get('head')
-            number: Optional[str] = self.find_number(self.parsed_url.path, 'pulls')
-            if number:
-                for pr in self.github_api_state.pulls:
-                    if pr['number'] == number:
-                        return self.make_response_object(HTTPStatus.OK, pr)
-                return self.make_response_object(HTTPStatus.NOT_FOUND, [])
-            elif full_head_name:
-                head: str = full_head_name[0].split(':')[1]
-                for pr in self.github_api_state.pulls:
-                    if pr['head']['ref'] == head:
-                        return self.make_response_object(HTTPStatus.OK, [pr])
-                return self.make_response_object(HTTPStatus.NOT_FOUND, [])
-            else:
-                return self.make_response_object(HTTPStatus.OK, [pull for pull in self.github_api_state.pulls if pull['state'] == 'open'])
-        elif self.parsed_url.path.endswith('user'):
-            return self.make_response_object(HTTPStatus.OK, self.github_api_state.user)
-        else:
-            return self.make_response_object(HTTPStatus.NOT_FOUND, [])
-
-    def handle_patch(self) -> "MockGitHubAPIResponse":
-        if 'issues' in self.parsed_url.path:
-            return self.update_issue()
-        elif 'pulls' in self.parsed_url.path:
-            return self.update_pull_request()
-        else:
-            return self.make_response_object(HTTPStatus.NOT_FOUND, [])
-
-    def handle_post(self) -> "MockGitHubAPIResponse":
-        assert not self.parsed_query
-        if 'issues' in self.parsed_url.path:
-            return self.update_issue()
-        elif 'pulls' in self.parsed_url.path:
-            return self.update_pull_request()
-        else:
-            return self.make_response_object(HTTPStatus.NOT_FOUND, [])
-
-    def update_pull_request(self) -> "MockGitHubAPIResponse":
-        pull_no = self.find_number(self.parsed_url.path, 'pulls')
-        if not pull_no:
-            if self.is_pull_created():
-                head = json.loads(self.json_data)["head"]  # type: ignore[arg-type]
-                return self.make_response_object(HTTPStatus.UNPROCESSABLE_ENTITY, {'message': 'Validation Failed', 'errors': [
-                    {'message': f'A pull request already exists for test_repo:{head}.'}]})
-            return self.create_pull_request()
-        pull = self.github_api_state.get_pull(pull_no)
-        assert pull is not None
-        return self.fill_pull_request_data(json.loads(self.json_data), pull)  # type: ignore[arg-type]
-
-    def create_pull_request(self) -> "MockGitHubAPIResponse":
-        pull = {'number': self.get_next_free_number(self.github_api_state.pulls),
-                'user': {'login': 'github_user'},
-                'html_url': 'www.github.com',
-                'state': 'open',
-                'head': {'ref': "", 'repo': {'full_name': 'testing:checkout_prs', 'html_url': mkdtemp()}},
-                'base': {'ref': ""}}
-        return self.fill_pull_request_data(json.loads(self.json_data), pull)  # type: ignore[arg-type]
-
-    def fill_pull_request_data(self, data: Dict[str, Any], pull: Dict[str, Any]) -> "MockGitHubAPIResponse":
-        index = self.get_index_or_none(pull, self.github_api_state.issues)
-        for key in data.keys():
-            if key in ('base', 'head'):
-                pull[key]['ref'] = json.loads(self.json_data)[key]  # type: ignore[arg-type]
-            else:
-                pull[key] = json.loads(self.json_data)[key]  # type: ignore[arg-type]
-        if index:
-            self.github_api_state.pulls[index] = pull
-        else:
-            self.github_api_state.pulls.append(pull)
-        return self.make_response_object(HTTPStatus.CREATED, pull)
-
-    def update_issue(self) -> "MockGitHubAPIResponse":
-        issue_no = self.find_number(self.parsed_url.path, 'issues')
-        if not issue_no:
-            return self.create_issue()
-        issue = self.github_api_state.get_issue(issue_no)
-        assert issue is not None
-        return self.fill_issue_data(json.loads(self.json_data), issue)  # type: ignore[arg-type]
-
-    def create_issue(self) -> "MockGitHubAPIResponse":
-        issue = {'number': self.get_next_free_number(self.github_api_state.issues)}
-        return self.fill_issue_data(json.loads(self.json_data), issue)  # type: ignore[arg-type]
-
-    def fill_issue_data(self, data: Dict[str, Any], issue: Dict[str, Any]) -> "MockGitHubAPIResponse":
-        index = self.get_index_or_none(issue, self.github_api_state.issues)
-        for key in data.keys():
-            issue[key] = data[key]
-        if index is not None:
-            self.github_api_state.issues[index] = issue
-        else:
-            self.github_api_state.issues.append(issue)
-        return self.make_response_object(HTTPStatus.CREATED, issue)
-
-    def is_pull_created(self) -> bool:
-        deserialized_json_data = json.loads(self.json_data)  # type: ignore[arg-type]
-        head: str = deserialized_json_data['head']
-        base: str = deserialized_json_data['base']
-        for pull in self.github_api_state.pulls:
-            pull_head: str = pull['head']['ref']
-            pull_base: str = pull['base']['ref']
-            if (head, base) == (pull_head, pull_base):
-                return True
-        return False
-
-    @staticmethod
-    def get_index_or_none(entity: Dict[str, Any], base: List[Dict[str, Any]]) -> Optional[int]:
-        try:
-            return base.index(entity)
-        except ValueError:
-            return None
-
-    @staticmethod
-    def make_response_object(status_code: int, response_data: Union[List[Dict[str, Any]], Dict[str, Any]]) -> "MockGitHubAPIResponse":
-        return MockGitHubAPIResponse(status_code, response_data)
-
-    @staticmethod
-    def find_number(url: str, entity: str) -> Optional[str]:
-        m = re.search(f'{entity}/(\\d+)', url)
-        if m:
-            return m.group(1)
-        return None
-
-    @staticmethod
-    def get_next_free_number(entities: List[Dict[str, Any]]) -> str:
-        numbers = [int(item['number']) for item in entities]
-        return str(max(numbers) + 1)
-
-
-class MockHTTPError(HTTPError):
-    from email.message import Message
-
-    def __init__(self, url: str, code: int, msg: Any, hdrs: Message, fp: Any) -> None:
-        super().__init__(url, code, msg, hdrs, fp)
-        self.msg = msg
-
-    def read(self, n: int = 1) -> bytes:  # noqa: F841
-        return json.dumps(self.msg).encode()
-
-
-class MockContextManager:
-    def __init__(self, obj: MockGitHubAPIResponse) -> None:
-        self.obj = obj
-
-    def __enter__(self) -> MockGitHubAPIResponse:
-        if self.obj.status_code == HTTPStatus.NOT_FOUND:
-            raise HTTPError("http://example.org", 404, 'Not found', None, None)  # type: ignore[arg-type]
-        elif self.obj.status_code == HTTPStatus.UNPROCESSABLE_ENTITY:
-            raise MockHTTPError("http://example.org", 422, self.obj.response_data, None, None)  # type: ignore[arg-type]
-        return self.obj
-
-    def __exit__(self, *args: Any) -> None:
-        pass
-
-
-class MockContextManagerRaise403(MockContextManager):
-    def __init__(self, obj: MockGitHubAPIResponse) -> None:
-        super().__init__(obj)
-
-    def __enter__(self) -> MockGitHubAPIResponse:
-        raise HTTPError("http://example.org", 403, 'Forbidden', None, None)  # type: ignore[arg-type]
+@contextlib.contextmanager
+def overridden_environment(**environ: str) -> Iterator[None]:
+    old_environ = dict(os.environ)
+    os.environ.update(environ)
+    try:
+        yield
+    finally:
+        os.environ.clear()
+        os.environ.update(old_environ)
+
+
+def fixed_author_and_committer_date() -> _GeneratorContextManager:  # type: ignore[type-arg]
+    # It doesn't matter WHAT this fixed timestamp is, as long as it's fixed
+    # (and hence, commit hashes are fixed).
+    fixed_committer_and_author_date = 'Mon 20 Aug 2018 20:19:19 +0200'
+    return overridden_environment(
+        GIT_COMMITTER_DATE=fixed_committer_and_author_date,
+        GIT_AUTHOR_DATE=fixed_committer_and_author_date
+    )
 
 
 def launch_command(*args: str) -> str:
@@ -329,6 +125,10 @@ def mock_ask_if(*args: str, **kwargs: Any) -> str:
     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
 
diff --git a/tests/test_delete_unmanaged.py b/tests/test_delete_unmanaged.py
index bf95903..dfe87f4 100644
--- a/tests/test_delete_unmanaged.py
+++ b/tests/test_delete_unmanaged.py
@@ -1,22 +1,25 @@
+from typing import Any
+
 from .base_test import BaseTest
-from .mockers import assert_command, rewrite_definition_file
+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)
 
 
 class TestDeleteUnmanaged(BaseTest):
 
-    def test_delete_unmanaged(self) -> None:
-        """
-        Verify behaviour of a 'git machete delete-unmanaged' command.
-        """
-
-        (
-            self.repo_sandbox.new_branch("master")
-                .commit("master commit.")
-                .new_branch("develop")
-                .commit("develop commit.")
-                .new_branch("feature")
-                .commit("feature commit.")
-        )
+    def test_delete_unmanaged(self, mocker: Any) -> None:
+        mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_forward_stdout)
+
+        with fixed_author_and_committer_date():
+            (
+                self.repo_sandbox.new_branch("master")
+                    .commit("master commit.")
+                    .new_branch("develop")
+                    .commit("develop commit.")
+                    .new_branch("feature")
+                    .commit("feature commit.")
+            )
         body: str = \
             """
             master
@@ -28,7 +31,9 @@ class TestDeleteUnmanaged(BaseTest):
             """
             Checking for unmanaged branches...
             Skipping current branch feature
-            Deleting branch develop (merged to HEAD)
+            Deleting branch develop (merged to HEAD)...
+            Deleted branch develop (was 03e727b).
+
             """
         )
 
@@ -37,7 +42,9 @@ class TestDeleteUnmanaged(BaseTest):
             ["delete-unmanaged", "-y"],
             """
             Checking for unmanaged branches...
-            Deleting branch feature (unmerged to HEAD)
+            Deleting branch feature (unmerged to HEAD)...
+            Deleted branch feature (was 87e00e9).
+
             """
         )
 
@@ -48,3 +55,8 @@ class TestDeleteUnmanaged(BaseTest):
             No branches to delete
             """
         )
+
+        self.repo_sandbox.new_branch("foo").check_out("master")
+        mocker.patch("git_machete.client.MacheteClient.ask_if", mock_ask_if_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 5dbec70..d53516a 100644
--- a/tests/test_diff.py
+++ b/tests/test_diff.py
@@ -13,14 +13,14 @@ class TestDiff(BaseTest):
         mocker.patch('git_machete.utils.run_cmd', mock_run_cmd_and_forward_stdout)  # to hide git outputs in tests
         (
             self.repo_sandbox.new_branch("master")
-                .add_file_with_content_and_commit(message='master commit1')
+                .add_file_and_commit(message='master commit1')
                 .push()
                 .new_branch("develop")
-                .add_file_with_content_and_commit(file_name='develop_file_name.txt',
-                                                  file_content='Develop content\n',
-                                                  message='develop commit')
+                .add_file_and_commit(file_path='develop_file_name.txt',
+                                     file_content='Develop content\n',
+                                     message='develop commit')
                 .push()
-                .write_to_file(file_name='file_name.txt',
+                .write_to_file(file_path='file_name.txt',
                                file_content='Content not committed\n')
         )
 
@@ -55,7 +55,11 @@ class TestDiff(BaseTest):
         +Develop content
 
         """
-
         assert_command(["diff", "develop"], expected_status_output)
-
         assert_command(["diff", "refs/heads/develop"], expected_status_output)
+
+        assert_command(
+            ["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 7c1ceb2..62afefb 100644
--- a/tests/test_discover.py
+++ b/tests/test_discover.py
@@ -45,3 +45,18 @@ class TestDiscover(BaseTest):
             """
         )
         assert_command(['status'], expected_status_output)
+
+        launch_command('discover', '--roots=feature1,master', '-y')
+        expected_status_output = (
+            """
+            feature1 (untracked)
+            |
+            o-feature2 (untracked)
+
+            master
+            |
+            o-feature3  rebase=no push=no
+              |
+              o-feature4 * (untracked)
+            """
+        )
diff --git a/tests/test_edit.py b/tests/test_edit.py
new file mode 100644
index 0000000..97ad882
--- /dev/null
+++ b/tests/test_edit.py
@@ -0,0 +1,10 @@
+from .base_test import BaseTest
+from .mockers import launch_command, overridden_environment
+
+
+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"
diff --git a/tests/test_file.py b/tests/test_file.py
index 0ddadd7..a5f6041 100644
--- a/tests/test_file.py
+++ b/tests/test_file.py
@@ -39,7 +39,7 @@ class TestFile(BaseTest):
 
             # check git machete definition file path when inside a worktree
             # but with the `machete.worktree.useTopLevelMacheteFile` key set to `False`
-            self.repo_sandbox.add_git_config_key('machete.worktree.useTopLevelMacheteFile', 'false')
+            self.repo_sandbox.set_git_config_key('machete.worktree.useTopLevelMacheteFile', 'false')
             self.repo_sandbox.execute("git worktree add -f -b mars_feature mars_worktree develop")
             os.chdir('mars_worktree')
             definition_file_full_path = launch_command("file")
diff --git a/tests/test_fork_point.py b/tests/test_fork_point.py
index 7499909..ba33cb1 100644
--- a/tests/test_fork_point.py
+++ b/tests/test_fork_point.py
@@ -1,32 +1,24 @@
-import os
-
 from .base_test import BaseTest
-from .mockers import (assert_command, get_current_commit_hash, launch_command,
+from .mockers import (assert_command, fixed_author_and_committer_date,
+                      get_current_commit_hash, launch_command,
                       rewrite_definition_file)
 
 
 class TestForkPoint(BaseTest):
 
-    def test_fork_point(self) -> None:
+    def test_fork_point_get(self) -> None:
         """
         Verify behaviour of a 'git machete fork-point' command.
         """
-        fixed_committer_and_author_date = 'Mon 20 Aug 2018 20:19:19 +0200'
-        os.environ['GIT_COMMITTER_DATE'] = fixed_committer_and_author_date
-        os.environ['GIT_AUTHOR_DATE'] = fixed_committer_and_author_date
-        try:
+        with fixed_author_and_committer_date():
             (
                 self.repo_sandbox.new_branch("master")
-                    .add_file_with_content_and_commit(message="master commit.")
+                    .commit(message="master commit.")
                     .new_branch("develop")
-                    .add_file_with_content_and_commit(file_content='develop content', message="develop commit.")
+                    .commit(message="develop commit.")
                     .new_branch("feature")
                     .commit('feature commit.')
             )
-        finally:
-            # Clean-up environment variables
-            os.environ.pop('GIT_COMMITTER_DATE', None)
-            os.environ.pop('GIT_AUTHOR_DATE', None)
 
         body: str = \
             """
@@ -38,14 +30,15 @@ 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", "--inferred"], "67007ed30def3b9b658380b895a9f62b525286e0\n")
+        assert_command(["fork-point"], "03e727bb987b21acce75e404f57e9d33ca876c20\n")
+        assert_command(["fork-point", "--inferred"], "03e727bb987b21acce75e404f57e9d33ca876c20\n")
 
         # hash 515319fa0ab47f372f6159bcc8ac27b43ee8a0ed corresponds to the commit on master branch
-        assert_command(["fork-point", 'develop'], "515319fa0ab47f372f6159bcc8ac27b43ee8a0ed\n")
+        assert_command(["fork-point", 'develop'], "58a3121d3ef89189eb51176c7ec5344f4aab2f84\n")
 
-        assert_command(["fork-point", 'refs/heads/develop'], "515319fa0ab47f372f6159bcc8ac27b43ee8a0ed\n")
+        assert_command(["fork-point", 'refs/heads/develop'], "58a3121d3ef89189eb51176c7ec5344f4aab2f84\n")
 
-    def test_fork_point_override(self) -> None:
+    def test_fork_point_override_to_commit(self) -> None:
         """
         Verify behaviour of a 'git machete fork-point' command with fork-point being overridden by config key.
         """
@@ -65,19 +58,19 @@ class TestForkPoint(BaseTest):
         rewrite_definition_file(body)
 
         # invalid fork point with length not equal to 40
-        self.repo_sandbox.add_git_config_key('machete.overrideForkPoint.develop.to', 39 * 'a')
+        self.repo_sandbox.set_git_config_key('machete.overrideForkPoint.develop.to', 39 * 'a')
         assert launch_command('fork-point').strip() == develop_branch_fork_point
 
         # invalid, non-hexadecimal alphanumeric characters present in the fork point
-        self.repo_sandbox.add_git_config_key('machete.overrideForkPoint.develop.to', 20 * 'g1')
+        self.repo_sandbox.set_git_config_key('machete.overrideForkPoint.develop.to', 20 * 'g1')
         assert launch_command('fork-point').strip() == develop_branch_fork_point
 
         # invalid, non-hexadecimal special characters present in the fork point
-        self.repo_sandbox.add_git_config_key('machete.overrideForkPoint.develop.to', 40 * '#')
+        self.repo_sandbox.set_git_config_key('machete.overrideForkPoint.develop.to', 40 * '#')
         assert launch_command('fork-point').strip() == develop_branch_fork_point
 
         # valid commit hash but not present in the repository
-        self.repo_sandbox.add_git_config_key('machete.overrideForkPoint.develop.to', 40 * 'a')
+        self.repo_sandbox.set_git_config_key('machete.overrideForkPoint.develop.to', 40 * 'a')
         assert launch_command('fork-point').strip() == (
                "Warn: since branch develop is no longer a descendant of commit aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa, the fork point override to this commit no longer applies.\n"  # noqa: E501
                "Consider running:\n"
@@ -87,6 +80,73 @@ class TestForkPoint(BaseTest):
         # valid fork-point override commit hash
         launch_command('fork-point', f'--override-to={master_branch_first_commit_hash}')
         assert launch_command('fork-point').strip() == master_branch_first_commit_hash
+        assert launch_command('fork-point', '--inferred').strip() == develop_branch_fork_point
 
         launch_command('fork-point', '--unset-override')
         assert launch_command('fork-point').strip() == develop_branch_fork_point
+        assert launch_command('fork-point', '--inferred').strip() == develop_branch_fork_point
+
+    def test_fork_point_override_to_parent_and_inferred(self) -> None:
+        with fixed_author_and_committer_date():
+            (
+                self.repo_sandbox
+                .new_branch("master")
+                .commit("master  commit")
+                .new_branch("in-between")
+                .commit("in-between commit")
+                .new_branch("develop")
+                .commit("develop commit")
+            )
+
+        body: str = \
+            """
+            master
+                develop
+            """
+        rewrite_definition_file(body)
+
+        assert launch_command("fork-point").strip() == "a71ffac2c1d41b8d1592a25f0056e4dfca829608"
+        assert launch_command("fork-point", "--inferred").strip() == "a71ffac2c1d41b8d1592a25f0056e4dfca829608"
+        assert_command(
+            ["status", "--list-commits-with-hashes"],
+            """
+              master (untracked)
+              |
+              | a71ffac  in-between commit -> fork point ??? this commit seems to be a part of the unique history of in-between
+              | 4aed40c  develop commit
+              ?-develop * (untracked)
+
+            Warn: yellow edge indicates that fork point for develop is probably incorrectly inferred,
+            or that some extra branch should be between master and develop.
+
+            Consider using git machete fork-point --override-to=<revision>|--override-to-inferred|--override-to-parent develop,
+            or reattaching develop under a different parent branch.
+            """
+        )
+
+        launch_command("fork-point", "--override-to-parent")
+        assert launch_command("fork-point").strip() == "7e6757a9e7888e8cad7e112ae4dc305966335594"
+        assert launch_command("fork-point", "--inferred").strip() == "a71ffac2c1d41b8d1592a25f0056e4dfca829608"
+        assert_command(
+            ["status", "-L"],
+            """
+              master (untracked)
+              |
+              | a71ffac  in-between commit
+              | 4aed40c  develop commit
+              o-develop * (untracked)
+            """
+        )
+
+        launch_command("fork-point", "--override-to-inferred")
+        assert launch_command("fork-point").strip() == "a71ffac2c1d41b8d1592a25f0056e4dfca829608"
+        assert launch_command("fork-point", "--inferred").strip() == "a71ffac2c1d41b8d1592a25f0056e4dfca829608"
+        assert_command(
+            ["status", "-L"],
+            """
+              master (untracked)
+              |
+              | 4aed40c  develop commit
+              o-develop * (untracked)
+            """
+        )
diff --git a/tests/test_github.py b/tests/test_github.py
index b40744b..052b99e 100644
--- a/tests/test_github.py
+++ b/tests/test_github.py
@@ -1,10 +1,14 @@
 import json
 import os
+import re
+from http import HTTPStatus
 from subprocess import CalledProcessError, CompletedProcess
 from tempfile import mkdtemp
 from textwrap import dedent
-from typing import Any, Callable, Dict, List, Optional
+from typing import Any, Callable, ContextManager, Dict, List, Optional, Union
 from unittest.mock import mock_open
+from urllib.error import HTTPError
+from urllib.parse import ParseResult, parse_qs, urlparse
 
 import pytest
 
@@ -15,10 +19,8 @@ from git_machete.github import (GitHubClient, GitHubToken,
 from git_machete.options import CommandLineOptions
 
 from .base_test import BaseTest, GitRepositorySandbox, git
-from .mockers import (MockContextManager, MockContextManagerRaise403,
-                      MockGitHubAPIState, MockHTTPError, assert_command,
-                      get_current_commit_hash, launch_command, mock_ask_if,
-                      mock_exit_script, mock_run_cmd,
+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)
 
@@ -129,6 +131,227 @@ def mock_info(x: Any) -> Dict[str, Any]:
 mock_info.counter = mock_read.counter = 0  # type: ignore[attr-defined]
 
 
+class MockGitHubAPIState:
+    def __init__(self, pulls: List[Dict[str, Any]], issues: Optional[List[Dict[str, Any]]] = None) -> None:
+        self.pulls: List[Dict[str, Any]] = pulls
+        self.user: Dict[str, str] = {'login': 'other_user', 'type': 'User', 'company': 'VirtusLab'}
+        # login must be different from the one used in pull requests, otherwise pull request author will not be annotated
+        self.issues: List[Dict[str, Any]] = issues or []
+
+    def new_request(self) -> "MockGitHubAPIRequest":
+        return MockGitHubAPIRequest(self)
+
+    def get_issue(self, issue_no: str) -> Optional[Dict[str, Any]]:
+        for issue in self.issues:
+            if issue['number'] == issue_no:
+                return issue
+        return None
+
+    def get_pull(self, pull_no: str) -> Optional[Dict[str, Any]]:
+        for pull in self.pulls:
+            if pull['number'] == pull_no:
+                return pull
+        return None
+
+
+class MockGitHubAPIResponse:
+    def __init__(self, status_code: int, response_data: Union[List[Dict[str, Any]], Dict[str, Any]]) -> None:
+        self.response_data: Union[List[Dict[str, Any]], Dict[str, Any]] = response_data
+        self.status_code: int = status_code
+
+    def read(self) -> bytes:
+        return json.dumps(self.response_data).encode()
+
+    def info(self) -> Dict[str, Any]:
+        return {"link": None}
+
+
+class MockGitHubAPIRequest:
+    def __init__(self, github_api_state: MockGitHubAPIState) -> None:
+        self.github_api_state: MockGitHubAPIState = github_api_state
+
+    def __call__(self, url: str, headers: Dict[str, str] = {}, data: Union[str, bytes, None] = None,
+                 method: str = '') -> "MockGitHubAPIResponse":
+        self.parsed_url: ParseResult = urlparse(url, allow_fragments=True)
+        self.parsed_query: Dict[str, List[str]] = parse_qs(self.parsed_url.query)
+        self.json_data: Union[str, bytes, None] = data
+        self.headers: Dict[str, str] = headers
+        return self.handle_method(method)
+
+    def handle_method(self, method: str) -> "MockGitHubAPIResponse":
+        if method == "GET":
+            return self.handle_get()
+        elif method == "PATCH":
+            return self.handle_patch()
+        elif method == "POST":
+            return self.handle_post()
+        else:
+            return self.make_response_object(HTTPStatus.METHOD_NOT_ALLOWED, [])
+
+    def handle_get(self) -> "MockGitHubAPIResponse":
+        if 'pulls' in self.parsed_url.path:
+            full_head_name: Optional[List[str]] = self.parsed_query.get('head')
+            number: Optional[str] = self.find_number(self.parsed_url.path, 'pulls')
+            if number:
+                for pr in self.github_api_state.pulls:
+                    if pr['number'] == number:
+                        return self.make_response_object(HTTPStatus.OK, pr)
+                return self.make_response_object(HTTPStatus.NOT_FOUND, [])
+            elif full_head_name:
+                head: str = full_head_name[0].split(':')[1]
+                for pr in self.github_api_state.pulls:
+                    if pr['head']['ref'] == head:
+                        return self.make_response_object(HTTPStatus.OK, [pr])
+                return self.make_response_object(HTTPStatus.NOT_FOUND, [])
+            else:
+                return self.make_response_object(HTTPStatus.OK, [pull for pull in self.github_api_state.pulls if pull['state'] == 'open'])
+        elif self.parsed_url.path.endswith('user'):
+            return self.make_response_object(HTTPStatus.OK, self.github_api_state.user)
+        else:
+            return self.make_response_object(HTTPStatus.NOT_FOUND, [])
+
+    def handle_patch(self) -> "MockGitHubAPIResponse":
+        if 'issues' in self.parsed_url.path:
+            return self.update_issue()
+        elif 'pulls' in self.parsed_url.path:
+            return self.update_pull_request()
+        else:
+            return self.make_response_object(HTTPStatus.NOT_FOUND, [])
+
+    def handle_post(self) -> "MockGitHubAPIResponse":
+        assert not self.parsed_query
+        if 'issues' in self.parsed_url.path:
+            return self.update_issue()
+        elif 'pulls' in self.parsed_url.path:
+            return self.update_pull_request()
+        else:
+            return self.make_response_object(HTTPStatus.NOT_FOUND, [])
+
+    def update_pull_request(self) -> "MockGitHubAPIResponse":
+        pull_no = self.find_number(self.parsed_url.path, 'pulls')
+        if not pull_no:
+            if self.is_pull_created():
+                head = json.loads(self.json_data)["head"]  # type: ignore[arg-type]
+                return self.make_response_object(HTTPStatus.UNPROCESSABLE_ENTITY, {'message': 'Validation Failed', 'errors': [
+                    {'message': f'A pull request already exists for test_repo:{head}.'}]})
+            return self.create_pull_request()
+        pull = self.github_api_state.get_pull(pull_no)
+        assert pull is not None
+        return self.fill_pull_request_data(json.loads(self.json_data), pull)  # type: ignore[arg-type]
+
+    def create_pull_request(self) -> "MockGitHubAPIResponse":
+        pull = {'number': self.get_next_free_number(self.github_api_state.pulls),
+                'user': {'login': 'github_user'},
+                'html_url': 'www.github.com',
+                'state': 'open',
+                'head': {'ref': "", 'repo': {'full_name': 'testing:checkout_prs', 'html_url': mkdtemp()}},
+                'base': {'ref': ""}}
+        return self.fill_pull_request_data(json.loads(self.json_data), pull)  # type: ignore[arg-type]
+
+    def fill_pull_request_data(self, data: Dict[str, Any], pull: Dict[str, Any]) -> "MockGitHubAPIResponse":
+        index = self.get_index_or_none(pull, self.github_api_state.issues)
+        for key in data.keys():
+            if key in ('base', 'head'):
+                pull[key]['ref'] = json.loads(self.json_data)[key]  # type: ignore[arg-type]
+            else:
+                pull[key] = json.loads(self.json_data)[key]  # type: ignore[arg-type]
+        if index:
+            self.github_api_state.pulls[index] = pull
+        else:
+            self.github_api_state.pulls.append(pull)
+        return self.make_response_object(HTTPStatus.CREATED, pull)
+
+    def update_issue(self) -> "MockGitHubAPIResponse":
+        issue_no = self.find_number(self.parsed_url.path, 'issues')
+        if not issue_no:
+            return self.create_issue()
+        issue = self.github_api_state.get_issue(issue_no)
+        assert issue is not None
+        return self.fill_issue_data(json.loads(self.json_data), issue)  # type: ignore[arg-type]
+
+    def create_issue(self) -> "MockGitHubAPIResponse":
+        issue = {'number': self.get_next_free_number(self.github_api_state.issues)}
+        return self.fill_issue_data(json.loads(self.json_data), issue)  # type: ignore[arg-type]
+
+    def fill_issue_data(self, data: Dict[str, Any], issue: Dict[str, Any]) -> "MockGitHubAPIResponse":
+        index = self.get_index_or_none(issue, self.github_api_state.issues)
+        for key in data.keys():
+            issue[key] = data[key]
+        if index is not None:
+            self.github_api_state.issues[index] = issue
+        else:
+            self.github_api_state.issues.append(issue)
+        return self.make_response_object(HTTPStatus.CREATED, issue)
+
+    def is_pull_created(self) -> bool:
+        deserialized_json_data = json.loads(self.json_data)  # type: ignore[arg-type]
+        head: str = deserialized_json_data['head']
+        base: str = deserialized_json_data['base']
+        for pull in self.github_api_state.pulls:
+            pull_head: str = pull['head']['ref']
+            pull_base: str = pull['base']['ref']
+            if (head, base) == (pull_head, pull_base):
+                return True
+        return False
+
+    @staticmethod
+    def get_index_or_none(entity: Dict[str, Any], base: List[Dict[str, Any]]) -> Optional[int]:
+        try:
+            return base.index(entity)
+        except ValueError:
+            return None
+
+    @staticmethod
+    def make_response_object(status_code: int, response_data: Union[List[Dict[str, Any]], Dict[str, Any]]) -> "MockGitHubAPIResponse":
+        return MockGitHubAPIResponse(status_code, response_data)
+
+    @staticmethod
+    def find_number(url: str, entity: str) -> Optional[str]:
+        m = re.search(f'{entity}/(\\d+)', url)
+        if m:
+            return m.group(1)
+        return None
+
+    @staticmethod
+    def get_next_free_number(entities: List[Dict[str, Any]]) -> str:
+        numbers = [int(item['number']) for item in entities]
+        return str(max(numbers) + 1)
+
+
+class MockHTTPError(HTTPError):
+    from email.message import Message
+
+    def __init__(self, url: str, code: int, msg: Any, hdrs: Message, fp: Any) -> None:
+        super().__init__(url, code, msg, hdrs, fp)
+        self.msg = msg
+
+    def read(self, n: int = 1) -> bytes:  # noqa: F841
+        return json.dumps(self.msg).encode()
+
+
+class MockContextManager(ContextManager[MockGitHubAPIResponse]):
+    def __init__(self, obj: MockGitHubAPIResponse) -> None:
+        self.obj = obj
+
+    def __enter__(self) -> MockGitHubAPIResponse:
+        if self.obj.status_code == HTTPStatus.NOT_FOUND:
+            raise HTTPError("http://example.org", 404, 'Not found', None, None)  # type: ignore[arg-type]
+        elif self.obj.status_code == HTTPStatus.UNPROCESSABLE_ENTITY:
+            raise MockHTTPError("http://example.org", 422, self.obj.response_data, None, None)  # type: ignore[arg-type]
+        return self.obj
+
+    def __exit__(self, *args: Any) -> None:
+        pass
+
+
+class MockContextManagerRaise403(MockContextManager):
+    def __init__(self, obj: MockGitHubAPIResponse) -> None:
+        super().__init__(obj)
+
+    def __enter__(self) -> MockGitHubAPIResponse:
+        raise HTTPError("http://example.org", 403, 'Forbidden', None, None)  # type: ignore[arg-type]
+
+
 class TestGitHub(BaseTest):
     mock_repository_info: Dict[str, str] = {'full_name': 'testing/checkout_prs',
                                             'html_url': 'https://github.com/tester/repo_sandbox.git'}
@@ -316,9 +539,8 @@ class TestGitHub(BaseTest):
                                   ' 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")
-        if e:
-            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 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.'
 
         launch_command('github', 'retarget-pr', '--branch', 'branch-without-pr', '--ignore-if-missing')
 
@@ -372,9 +594,8 @@ class TestGitHub(BaseTest):
         )
         with pytest.raises(MacheteException) as e:
             launch_command("github", "retarget-pr")
-        if e:
-            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 e.value.args[0] == expected_error_message, \
+            'Verify that expected error message has appeared when given pull request to create is already created.'
 
         # branch feature_1 present in each remote, tracking data present
         (
@@ -418,9 +639,8 @@ class TestGitHub(BaseTest):
 
         with pytest.raises(MacheteException) as e:
             launch_command("github", "retarget-pr")
-        if e:
-            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 e.value.args[0] == expected_error_message, \
+            'Verify that expected error message has appeared when given pull request to create is already created.'
 
         # branch feature_2 present in only one remote: origin_1 and there is no tracking data available -> infer the remote
         (
@@ -575,9 +795,9 @@ class TestGitHub(BaseTest):
         (
             self.repo_sandbox
                 .remove_remote('new_origin')
-                .add_git_config_key('machete.github.remote', 'custom_origin')
-                .add_git_config_key('machete.github.organization', 'custom_user')
-                .add_git_config_key('machete.github.repository', 'custom_repo')
+                .set_git_config_key('machete.github.remote', 'custom_origin')
+                .set_git_config_key('machete.github.organization', 'custom_user')
+                .set_git_config_key('machete.github.repository', 'custom_repo')
         )
 
         launch_command('github', 'anno-prs')
@@ -772,9 +992,8 @@ 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")
-        if e:
-            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 e.value.args[0] == expected_error_message, \
+            'Verify that expected error message has appeared when given pull request to create is already created.'
 
         # check against head branch is ancestor or equal to base branch
         (
@@ -801,18 +1020,16 @@ class TestGitHub(BaseTest):
                                  "Cannot create pull request."
         with pytest.raises(MacheteException) as e:
             launch_command("github", "create-pr")
-        if e:
-            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 e.value.parameter == expected_error_message, \
+            'Verify that expected error message has appeared when head branch is equal or ancestor of base branch.'
 
         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")
-        if e:
-            assert e.value.parameter == expected_error_message, \
-                'Verify that expected error message has appeared when creating PR from root branch.'
+        assert e.value.parameter == expected_error_message, \
+            'Verify that expected error message has appeared when creating PR from root branch.'
 
     git_api_state_for_test_create_pr_missing_base_branch_on_remote = MockGitHubAPIState(
         [
@@ -1356,15 +1573,13 @@ class TestGitHub(BaseTest):
         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')
-        if e:
-            assert e.value.parameter == expected_error_message, \
-                'Verify that expected error message has appeared when given pull request to checkout does not exists.'
+        assert e.value.parameter == expected_error_message, \
+            'Verify that expected error message has appeared when given pull request to checkout does not exists.'
 
         with pytest.raises(MacheteException) as e:
             launch_command('github', 'checkout-prs', '19', '100')
-        if e:
-            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 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.'
 
         # check against user with no open pull requests
         expected_msg = ("Checking for open GitHub PRs... OK\n"
@@ -1389,10 +1604,9 @@ class TestGitHub(BaseTest):
                                  "is already deleted from testing."
         with pytest.raises(MacheteException) as e:
             launch_command('github', 'checkout-prs', '5')
-        if e:
-            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 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.'
 
         # Check against pr come from fork
         os.chdir(local_path)
@@ -1963,8 +2177,8 @@ class TestGitHub(BaseTest):
         mocker.patch('urllib.request.urlopen', MockContextManager)
         mocker.patch('git_machete.github.GitHubClient.derive_current_user_login', mock_derive_current_user_login)
         mocker.patch('urllib.request.Request', MockGitHubAPIState([]).new_request())
-        mocker.patch('tests.mockers.MockGitHubAPIResponse.info', mock_info)
-        mocker.patch('tests.mockers.MockGitHubAPIResponse.read', mock_read)
+        mocker.patch('tests.test_github.MockGitHubAPIResponse.info', mock_info)
+        mocker.patch('tests.test_github.MockGitHubAPIResponse.read', mock_read)
 
         global prs_per_page
         (
@@ -2002,7 +2216,7 @@ class TestGitHub(BaseTest):
         mocker.patch('git_machete.cli.exit_script', mock_exit_script)
 
         github_enterprise_domain = 'git.example.org'
-        self.repo_sandbox.add_git_config_key('machete.github.domain', github_enterprise_domain)
+        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"
@@ -2013,8 +2227,7 @@ class TestGitHub(BaseTest):
 
         with pytest.raises(MacheteException) as e:
             launch_command('github', 'checkout-prs', '--all')
-        if e:
-            assert e.value.args[0] == expected_error_message, 'Verify that expected error message has appeared.'
+        assert e.value.args[0] == expected_error_message, 'Verify that expected error message has appeared.'
 
     git_api_state_for_test_github_enterprise_domain = MockGitHubAPIState(
         [
@@ -2050,7 +2263,7 @@ class TestGitHub(BaseTest):
             .push()
             .check_out("develop")
             .delete_branch("snickers")
-            .add_git_config_key('machete.github.domain', github_enterprise_domain)
+            .set_git_config_key('machete.github.domain', github_enterprise_domain)
         )
         launch_command('github', 'checkout-prs', '--all')
 
diff --git a/tests/test_help.py b/tests/test_help.py
index 221037c..8956d7f 100644
--- a/tests/test_help.py
+++ b/tests/test_help.py
@@ -14,34 +14,34 @@ 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
 
-        help_topics: List[str] = ['config', 'format', 'hooks']
+        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, \
-            "Verify that `git machete help` causes SystemExit with " \
-            f"{ExitCode.SUCCESS} exit code."
+        assert ExitCode.SUCCESS == e.value.code
+
+        with pytest.raises(SystemExit) as e:
+            launch_command("help", "no-such-command")
+        assert ExitCode.ARGUMENT_ERROR == e.value.code
+
+        help_topics: List[str] = ['config', 'format', 'hooks']
 
         for command in commands_and_aliases:
 
             with pytest.raises(SystemExit) as e:
                 launch_command("help", command)
-            assert ExitCode.SUCCESS == e.value.code, \
-                f"Verify that `git machete help {command}` causes SystemExit" \
-                f" with {ExitCode.SUCCESS} exit code."
+            assert ExitCode.SUCCESS == e.value.code
 
             if command not in help_topics:
                 with pytest.raises(SystemExit) as e:
                     launch_command(command, "--help")
-                assert ExitCode.SUCCESS == e.value.code, \
-                    f"Verify that `git machete {command} --help` causes " \
-                    f"SystemExit with {ExitCode.SUCCESS} exit code."
+                assert ExitCode.SUCCESS == e.value.code
             else:
                 with pytest.raises(SystemExit) as e:
                     launch_command(command, "--help")
-                assert ExitCode.ARGUMENT_ERROR == e.value.code, \
-                    f"Verify that `git machete {command} --help` causes " \
-                    f"SystemExit with {ExitCode.ARGUMENT_ERROR} exit code."
+                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)
diff --git a/tests/test_list.py b/tests/test_list.py
index 5df2a11..821bb76 100644
--- a/tests/test_list.py
+++ b/tests/test_list.py
@@ -1,5 +1,9 @@
 from typing import Any
 
+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)
@@ -45,9 +49,14 @@ class TestList(BaseTest):
         rewrite_definition_file(body)
 
         (
-            self.repo_sandbox.check_out("develop")
-                             .new_branch("feature_2")
-                             .commit("feature_2 commit.")
+            self.repo_sandbox
+                .check_out("develop")
+                .new_branch("feature_2")
+                .commit("feature_2 commit.")
+                .new_branch("feature_3")
+                .push()
+                .check_out("feature_2")
+                .delete_branch("feature_3")
         )
 
         expected_output = """
@@ -66,6 +75,7 @@ class TestList(BaseTest):
 
         expected_output = """
         feature_2
+        feature_3
         """
         assert_command(
             ['list', 'addable'],
@@ -121,3 +131,13 @@ class TestList(BaseTest):
             ['list', 'with-overridden-fork-point'],
             expected_output
         )
+
+        launch_command('fork-point', '--unset-override')
+        assert_command(
+            ['list', 'with-overridden-fork-point'],
+            ""
+        )
+
+        with pytest.raises(SystemExit) as e:
+            launch_command('list', 'no-such-category')
+        assert ExitCode.ARGUMENT_ERROR == e.value.code
diff --git a/tests/test_reapply.py b/tests/test_reapply.py
index 7ef3472..e1233e1 100644
--- a/tests/test_reapply.py
+++ b/tests/test_reapply.py
@@ -1,9 +1,9 @@
-import os
 from typing import Any
 
 from .base_test import BaseTest
-from .mockers import (assert_command, get_current_commit_hash, launch_command,
-                      mock_run_cmd, rewrite_definition_file)
+from .mockers import (assert_command, fixed_author_and_committer_date,
+                      launch_command, mock_run_cmd, overridden_environment,
+                      rewrite_definition_file)
 
 
 class TestReapply(BaseTest):
@@ -15,18 +15,23 @@ class TestReapply(BaseTest):
         """
         mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
 
-        self.repo_sandbox.new_branch("level-0-branch").commit("Basic commit.")
-        parents_old_commit_hash = get_current_commit_hash()
-        (
-            self.repo_sandbox
-            .new_branch("level-1-branch")
-            .commit("First level-1 commit.")
-            .commit("Second 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")
-        )
+        with fixed_author_and_committer_date():
+            (
+                self.repo_sandbox
+                .remove_remote("origin")
+                .new_branch("level-0-branch")
+                .commit("Basic commit.")
+                .new_branch("level-1-branch")
+                .commit("First level-1 commit.")
+                .commit("Second level-1 commit.")
+                .new_branch("level-2-branch")
+                .commit("First level-2 commit.")
+                .commit("Second level-2 commit.")
+                .commit("Third level-2 commit.")
+                .check_out("level-0-branch")
+                .commit("New commit on level-0-branch")
+            )
+
         body: str = \
             """
             level-0-branch
@@ -38,39 +43,61 @@ class TestReapply(BaseTest):
 
         self.repo_sandbox.check_out("level-1-branch")
         assert_command(
-            ["status", "-l"],
+            ["status", "-L"],
             """
-            level-0-branch (untracked)
+            level-0-branch
             |
-            | First level-1 commit.
-            | Second level-1 commit.
-            x-level-1-branch * (untracked)
+            | b98ae42  First level-1 commit.
+            | 1b657a1  Second level-1 commit.
+            x-level-1-branch *
               |
-              | Only level-2 commit.
-              o-level-2-branch (untracked)
+              | 64f8913  First level-2 commit.
+              | a6b9ae5  Second level-2 commit.
+              | 958f91f  Third level-2 commit.
+              o-level-2-branch
             """
         )
-        assert launch_command("fork-point").strip() == parents_old_commit_hash
+        assert launch_command("fork-point", "level-1-branch").strip() == "c0306cdd500fc39869505592200258055407bcc6"
 
-        try:
-            # Let's substitute the editor opened by git for interactive rebase to-do list
-            # so that the test can run in a fully automated manner.
-            os.environ["GIT_SEQUENCE_EDITOR"] = "sed -i.bak '2s/^pick /fixup /'"
+        # Let's substitute the editor opened by git for interactive rebase to-do list
+        # so that the test can run in a fully automated manner.
+        with overridden_environment(GIT_SEQUENCE_EDITOR="sed -i.bak '2s/^pick /fixup /'"):
+            with fixed_author_and_committer_date():
+                launch_command("reapply")
+
+        assert_command(
+            ["status", "-L"],
+            """
+            level-0-branch
+            |
+            | 887182d  First level-1 commit.
+            x-level-1-branch *
+              |
+              | 64f8913  First level-2 commit.
+              | a6b9ae5  Second level-2 commit.
+              | 958f91f  Third level-2 commit.
+              x-level-2-branch
+            """
+        )
+        assert launch_command("fork-point", "level-1-branch").strip() == "c0306cdd500fc39869505592200258055407bcc6"
 
-            launch_command("reapply")
-        finally:
-            del os.environ["GIT_SEQUENCE_EDITOR"]
+        self.repo_sandbox.check_out("level-2-branch")
+        assert launch_command("fork-point", "level-2-branch").strip() == "1b657a15fa4c619fcb4e871176d1471cdbce9093"
+        with overridden_environment(GIT_SEQUENCE_EDITOR="sed -i.bak '2s/^pick /fixup /'"):
+            with fixed_author_and_committer_date():
+                launch_command("reapply", "--fork-point=64f8913")
 
         assert_command(
-            ["status", "-l"],
+            ["status", "-L"],
             """
-            level-0-branch (untracked)
+            level-0-branch
             |
-            | First level-1 commit.
-            x-level-1-branch * (untracked)
+            | 887182d  First level-1 commit.
+            x-level-1-branch
               |
-              | Only level-2 commit.
-              x-level-2-branch (untracked)
+              | 64f8913  First level-2 commit.
+              | a5fcbb0  Second level-2 commit.
+              x-level-2-branch *
             """
         )
-        assert launch_command("fork-point").strip() == parents_old_commit_hash
+        assert launch_command("fork-point", "level-2-branch").strip() == "1b657a15fa4c619fcb4e871176d1471cdbce9093"
diff --git a/tests/test_status.py b/tests/test_status.py
index 52fb42f..ea156d9 100644
--- a/tests/test_status.py
+++ b/tests/test_status.py
@@ -18,23 +18,87 @@ class TestStatus(BaseTest):
         mocker.patch('git_machete.utils.run_cmd', mock_run_cmd)  # to hide git outputs in tests
 
         body: str = \
-            """master
+            """
+            master
             \tdevelop
             \t\n
             develop
             """
+        rewrite_definition_file(body)
+
+        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'
+
+        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)
 
-        self.repo_sandbox.new_branch("root")
+        body: str = \
+            """
+            master
+            \tdevelop
+            \t foo
+            """
         rewrite_definition_file(body)
 
-        expected_error_message: str = '.git/machete, line 5: branch develop re-appears in the tree definition. ' \
+        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'
 
         with pytest.raises(MacheteException) as e:
             launch_command('status')
-        if e:
-            assert e.value.parameter == expected_error_message, \
-                'Verify that expected error message has appeared if a branch re-appears in tree definition.'
+        assert e.value.parameter == expected_error_message
+
+    def test_status_branch_hook_output(self) -> None:
+        (
+            self.repo_sandbox
+            .remove_remote('origin')
+            .new_branch('master')
+            .commit('master commit')
+            .new_branch('develop')
+            .commit('develop commit')
+        )
+
+        body: str = \
+            """
+            master
+              develop
+            """
+        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(
+            ["status"],
+            """
+            hint: The '.git/hooks/machete-status-branch' hook was ignored because it's not set as executable.
+            hint: You can disable this warning with `git config advice.ignoredHook false`.
+              master
+              |
+              o-develop *
+            """
+        )
+
+        self.repo_sandbox.set_git_config_key("advice.ignoredHook", "false")
+        assert_command(
+            ["status"],
+            """
+            master
+            |
+            o-develop *
+            """
+        )
+
+        self.repo_sandbox.set_file_executable(".git/hooks/machete-status-branch")
+        assert_command(
+            ["status"],
+            """
+            master  1
+            |
+            o-develop *  2
+            """
+        )
 
     def test_extra_space_before_branch_name(self, mocker: Any) -> None:
         mocker.patch('git_machete.cli.exit_script', mock_exit_script)
@@ -51,7 +115,7 @@ class TestStatus(BaseTest):
                 .new_branch('foo')
                 .commit()
                 .push()
-                .add_git_config_key('machete.status.extraSpaceBeforeBranchName', 'true')
+                .set_git_config_key('machete.status.extraSpaceBeforeBranchName', 'true')
         )
         body: str = \
             """
@@ -72,7 +136,7 @@ class TestStatus(BaseTest):
         )
         assert_command(['status'], expected_status_output)
 
-        self.repo_sandbox.add_git_config_key('machete.status.extraSpaceBeforeBranchName', 'false')
+        self.repo_sandbox.set_git_config_key('machete.status.extraSpaceBeforeBranchName', 'false')
 
         expected_status_output = (
             """
diff --git a/tests/test_traverse.py b/tests/test_traverse.py
index d158441..814830a 100644
--- a/tests/test_traverse.py
+++ b/tests/test_traverse.py
@@ -1,13 +1,17 @@
 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_run_cmd,
-                      rewrite_definition_file)
+from .mockers import (assert_command, launch_command, mock_exit_script,
+                      mock_run_cmd, rewrite_definition_file)
 
 
 class TestTraverse(BaseTest):
 
-    def setup_discover_standard_tree(self) -> None:
+    def setup_standard_tree(self) -> None:
         (
             self.repo_sandbox.new_branch("root")
             .commit("root")
@@ -83,7 +87,7 @@ class TestTraverse(BaseTest):
 
     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
-        self.setup_discover_standard_tree()
+        self.setup_standard_tree()
 
         launch_command("traverse", "-Wy", "--no-push")
         assert_command(
@@ -118,7 +122,7 @@ 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
-        self.setup_discover_standard_tree()
+        self.setup_standard_tree()
         self.repo_sandbox.check_out("hotfix/add-trigger")
         launch_command("t", "-Wy", "--no-push", "--push", "--start-from=here")
         assert_command(
@@ -151,40 +155,77 @@ class TestTraverse(BaseTest):
             """,
         )
         self.repo_sandbox.check_out("ignore-trailing")
-        launch_command("t", "-Wy", "--no-push", "--push")
+        self.repo_sandbox.set_git_config_key("machete.traverse.push", "false")
+        launch_command("t", "-Wy")
         assert_command(
-            ["status", "-l"],
+            ["status"],
+            """
+            develop
+            |
+            o-allow-ownership-link (diverged from origin)
+            | |
+            | o-build-chain (untracked)
+            |
+            o-call-ws (ahead of origin)
+              |
+              o-drop-constraint (untracked)
+
+            master
+            |
+            o-hotfix/add-trigger
+              |
+              o-ignore-trailing *
+            """,
+        )
+
+        launch_command("t", "-Wy", "--push-untracked")
+        assert_command(
+            ["status"],
+            """
+            develop
+            |
+            o-allow-ownership-link (diverged from origin)
+            | |
+            | o-build-chain
+            |
+            o-call-ws (ahead of origin)
+              |
+              o-drop-constraint
+
+            master
+            |
+            o-hotfix/add-trigger
+              |
+              o-ignore-trailing *
+            """,
+        )
+
+        self.repo_sandbox.set_git_config_key("machete.traverse.push", "true")
+        launch_command("t", "-Wy", "--no-push-untracked")
+        assert_command(
+            ["status"],
             """
             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 (amended)
               o-ignore-trailing *
             """,
         )
 
     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
-        self.setup_discover_standard_tree()
+        self.setup_standard_tree()
 
         launch_command("traverse", "-Wy", "--no-push-untracked")
         assert_command(
@@ -219,8 +260,8 @@ 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
-        self.setup_discover_standard_tree()
-        self.repo_sandbox.add_git_config_key('machete.traverse.push', 'false')
+        self.setup_standard_tree()
+        self.repo_sandbox.set_git_config_key('machete.traverse.push', 'false')
         launch_command("traverse", "-Wy")
         assert_command(
             ["status", "-l"],
@@ -326,9 +367,9 @@ class TestTraverse(BaseTest):
         assert_command(["traverse", "-Wy", "--no-push"],
                        expected_result)
 
-    def test_discover_traverse_squash(self, mocker: Any) -> None:
+    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
-        self.setup_discover_standard_tree()
+        self.setup_standard_tree()
 
         self.repo_sandbox.check_out("hotfix/add-trigger")
         launch_command("traverse", "--fetch", "--start-from=root", "--return-to=here", "-y")
@@ -449,7 +490,7 @@ class TestTraverse(BaseTest):
                     snickers
             """
         rewrite_definition_file(body)
-        launch_command("traverse", '-y', '-M', '-n')
+        launch_command("traverse", '-y', '-M', '--no-edit-merge')
         assert_command(
             ["status"],
             """
@@ -463,7 +504,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
-        self.setup_discover_standard_tree()
+        self.setup_standard_tree()
 
         body: str = \
             """
@@ -478,7 +519,7 @@ class TestTraverse(BaseTest):
             """
         rewrite_definition_file(body)
 
-        launch_command("traverse", "-Wy")
+        launch_command("traverse", "-Wy", "--no-push-untracked", "--push-untracked")
         assert_command(
             ["status"],
             """
@@ -502,7 +543,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
-        self.setup_discover_standard_tree()
+        self.setup_standard_tree()
 
         body: str = \
             """
@@ -541,7 +582,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
-        self.setup_discover_standard_tree()
+        self.setup_standard_tree()
 
         body: str = \
             """
@@ -580,7 +621,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
-        self.setup_discover_standard_tree()
+        self.setup_standard_tree()
 
         body: str = \
             """
@@ -617,3 +658,11 @@ class TestTraverse(BaseTest):
               o-ignore-trailing
             """,
         )
+
+    def test_traverse_no_managed_branches(self, mocker: Any) -> None:
+        mocker.patch('git_machete.cli.exit_script', mock_exit_script)
+
+        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."
diff --git a/tests/test_update.py b/tests/test_update.py
index a099954..8e66ff3 100644
--- a/tests/test_update.py
+++ b/tests/test_update.py
@@ -1,4 +1,3 @@
-import os
 from typing import Any
 
 import pytest
@@ -7,7 +6,8 @@ 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, rewrite_definition_file)
+                      mock_exit_script, mock_run_cmd, overridden_environment,
+                      rewrite_definition_file)
 
 
 class TestUpdate(BaseTest):
@@ -75,25 +75,20 @@ class TestUpdate(BaseTest):
 
         parents_new_commit_hash = get_current_commit_hash()
         self.repo_sandbox.check_out("level-1-branch")
-        try:
-            # 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.
-            # Let's substitute the editor opened by git for interactive rebase to-do list
-            # so that the test can run in a fully automated manner.
-            os.environ["GIT_SEQUENCE_EDITOR"] = ":"
+        # 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.
+        # Let's substitute the editor opened by git for interactive rebase to-do list
+        # so that the test can run in a fully automated manner.
+        with overridden_environment(GIT_SEQUENCE_EDITOR=":"):
             if git.get_git_version() >= (2, 26, 0):
                 launch_command("update")
             else:
                 mocker.patch('git_machete.cli.exit_script', mock_exit_script)
-                try:
+                with pytest.raises(MacheteException) as e:
                     launch_command("update")
-                except MacheteException:
-                    pass
-                else:
-                    raise Exception("`git machete update` was supposed to fail")
+                assert "git rebase --interactive --onto refs/heads/level-0-branch" in e.value.parameter
+                assert "level-1-branch returned 1" in e.value.parameter
                 self.repo_sandbox.execute("git rebase --continue")
-        finally:
-            del os.environ["GIT_SEQUENCE_EDITOR"]
 
         new_fork_point_hash = launch_command("fork-point").strip()
         assert parents_new_commit_hash == new_fork_point_hash, \