Repository: VirtusLab/git-machete
Pull request #530 (2022-05-18 15:35:05)

Release v3.10.0


Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 987cc58..9f3504c 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -26,6 +26,12 @@ Use `.tox/venv/bin/python` as a reference `python` interpreter in your IDE.
 
 To run tests, execute `tox`.
 
+It's also possible to execute tests graphically in pycharm (right-clicking on the test file
+or clicking the green triangle on the left of the test case name), which can be very useful when debugging a single test case.
+To do so, make sure that following libraries are installed (preferably via pip):
+* pytest
+* pytest-mock
+* pytest-xdist
 
 ## Generate sandbox repositories
 
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index d450bef..64c9190 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,5 +1,10 @@
 # Release notes
 
+## New in git-machete 3.10.0
+
+- added: boolean git config key `machete.status.extraSpaceBeforeBranchName` that enable configurable rendering of `status` command
+- added: 3 git config keys `machete.github.{remote,organization,repository}` that enable `git machete github *` subcommands to work with custom GitHub URLs
+
 ## New in git-machete 3.9.1
 
 - fixed: better rendering of edge junctions in `status`
diff --git a/docs/source/cli_help/anno.rst b/docs/source/cli_help/anno.rst
index aaa85c4..57f775a 100644
--- a/docs/source/cli_help/anno.rst
+++ b/docs/source/cli_help/anno.rst
@@ -23,6 +23,7 @@ If invoked with ``-H`` or ``--sync-github-prs``, annotates the branches based on
 Any existing annotations are overwritten for the branches that have an opened PR; annotations for the other branches remain untouched.
 
 .. include:: github_api_access.rst
+.. include:: github_config_keys.rst
 
 In any other case, sets the annotation for the given/current branch to the given <annotation text>.
 If multiple <annotation text>'s are passed to the command, they are concatenated with a single space.
diff --git a/docs/source/cli_help/clean.rst b/docs/source/cli_help/clean.rst
index c77c182..063df51 100644
--- a/docs/source/cli_help/clean.rst
+++ b/docs/source/cli_help/clean.rst
@@ -17,6 +17,7 @@ No branch will be deleted unless explicitly confirmed by the user (or unless ``-
 Equivalent of ``git machete github sync`` if invoked with ``-H`` or ``--checkout-my-github-prs``.
 
 .. include:: github_api_access.rst
+.. include:: github_config_keys.rst
 
 **Options:**
 
diff --git a/docs/source/cli_help/github.rst b/docs/source/cli_help/github.rst
index 10efed2..31fafa3 100644
--- a/docs/source/cli_help/github.rst
+++ b/docs/source/cli_help/github.rst
@@ -13,6 +13,7 @@ where ``<subcommand>`` is one of: ``anno-prs``, ``checkout-prs``, ``create-pr``,
 Creates, checks out and manages GitHub PRs while keeping them reflected in branch definition file.
 
 .. include:: github_api_access.rst
+.. include:: github_config_keys.rst
 
 ``anno-prs``:
 
@@ -20,6 +21,7 @@ Creates, checks out and manages GitHub PRs while keeping them reflected in branc
   Any existing annotations are overwritten for the branches that have an opened PR; annotations for the other branches remain untouched.
   Equivalent to ``git machete anno --sync-github-prs``.
 
+
 ``checkout-prs [--all | --by=<github-login> | --mine | <PR-number-1> ... <PR-number-N>]``:
 
   Check out the head branch of the given pull requests (specified by numbers or by a flag),
diff --git a/docs/source/cli_help/status.rst b/docs/source/cli_help/status.rst
index c7c924c..7bed8f0 100644
--- a/docs/source/cli_help/status.rst
+++ b/docs/source/cli_help/status.rst
@@ -79,3 +79,29 @@ With ``--color=always``, git machete always emits colors and with ``--color=auto
 -L, --list-commits-with-hashes    Additionally list the short hashes and messages of commits introduced on each branch.
 
 --no-detect-squash-merges         Only consider *strict* (fast-forward or 2-parent) merges, rather than rebase/squash merges, when detecting if a branch is merged into its upstream (parent).
+
+ .. note::
+
+    To make it easier to select branch name from the ``status`` output on certain terminals
+    (e.g. `Alacritty <https://github.com/alacritty/alacritty>`_), you can add an extra
+    space between ``└─`` and ``branch name`` by setting ``git config machete.status.extraSpaceBeforeBranchName true``.
+
+    For example, by default it's:
+
+    .. code-block::
+
+      develop
+      │
+      ├─feature_branch1
+      │
+      └─feature_branch2
+
+    With ``machete.status.extraSpaceBeforeBranchName`` config set to ``true``:
+
+    .. code-block::
+
+       develop
+       │
+       ├─ feature_branch1
+       │
+       └─ feature_branch2
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 4bf76a9..340151c 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -47,7 +47,8 @@ exclude_patterns = [
     'completion.rst',
     'description.rst',
     'learning_materials.rst',
-    'github_api_access.rst'
+    'github_api_access.rst',
+    'github_config_keys.rst'
 ]
 
 
diff --git a/docs/source/github_config_keys.rst b/docs/source/github_config_keys.rst
new file mode 100644
index 0000000..4558d64
--- /dev/null
+++ b/docs/source/github_config_keys.rst
@@ -0,0 +1,23 @@
+.. _github_config_keys:
+
+ .. note::
+
+    GitHub API server URL will be inferred from ``git remote``.
+    You can override this by setting the following git config keys:
+        Remote name
+            E.g. ``machete.github.remote`` = ``origin``
+
+        Organization name
+            E.g. ``machete.github.organization`` = ``VirtusLab``
+
+        Repository name
+            E.g. ``machete.github.repository`` = ``git-machete``
+
+    To do this, run ``git config --local --edit`` and add the following section:
+
+    .. code-block:: ini
+
+        [machete "github"]
+            organization = <organization_name>
+            repository = <repo_name>
+            remote = <remote_name>
diff --git a/git_machete/__init__.py b/git_machete/__init__.py
index d028244..3f3750c 100644
--- a/git_machete/__init__.py
+++ b/git_machete/__init__.py
@@ -1,1 +1,1 @@
-__version__ = '3.9.1'
+__version__ = '3.10.0'
diff --git a/git_machete/client.py b/git_machete/client.py
index 3f3a23a..57f8cac 100644
--- a/git_machete/client.py
+++ b/git_machete/client.py
@@ -20,7 +20,7 @@ from git_machete.github import (
     add_assignees_to_pull_request, add_reviewers_to_pull_request,
     create_pull_request, checkout_pr_refs, derive_pull_request_by_head, derive_pull_requests,
     get_github_token_possible_providers, get_parsed_github_remote_url, get_pull_request_by_number_or_none, GitHubPullRequest,
-    is_github_remote_url, set_base_of_pull_request, set_milestone_of_pull_request)
+    is_github_remote_url, RemoteAndOrganizationAndRepository, set_base_of_pull_request, set_milestone_of_pull_request)
 from git_machete.utils import (
     get_pretty_choices, flat_map, excluding, fmt, tupled, warn, debug, bold,
     colored, underline, dim, get_second, AnsiEscapeCodes)
@@ -934,13 +934,15 @@ class MacheteClient:
         hook_path = self.__git.get_hook_path("machete-status-branch")
         hook_executable = self.__git.check_hook_executable(hook_path)
 
+        maybe_space_before_branch_name = ' ' if self.__git.get_boolean_config_attr('machete.status.extraSpaceBeforeBranchName') else ''
+
         def print_line_prefix(branch_: LocalBranchShortName, suffix: str) -> None:
-            out.write("  ")
+            out.write("  " + maybe_space_before_branch_name)
             for sibling in next_sibling_of_ancestor[:-1]:
                 if not sibling:
-                    out.write("  ")
+                    out.write("  " + maybe_space_before_branch_name)
                 else:
-                    out.write(colored(f"{utils.get_vertical_bar()} ", edge_color[sibling]))
+                    out.write(colored(f"{utils.get_vertical_bar()} " + maybe_space_before_branch_name, edge_color[sibling]))
             out.write(colored(suffix, edge_color[branch_]))
 
         next_sibling_of_ancestor: List[Optional[LocalBranchShortName]]
@@ -994,11 +996,11 @@ class MacheteClient:
                         # in case `edge_color[next_sibling_of_branch] != edge_color[branch]`,
                         # at the expense of a little gap to the elbow/turnstile below.
                         junction = u"└─"
-                print_line_prefix(branch, junction)
+                print_line_prefix(branch, junction + maybe_space_before_branch_name)
             else:
                 if branch != dfs_res[0][0]:
                     out.write("\n")
-                out.write("  ")
+                out.write("  " + maybe_space_before_branch_name)
 
             if branch in (currently_checked_out_branch, currently_rebased_branch):  # i.e. if branch is the current branch (checked out or being rebased)
                 if branch == currently_rebased_branch:
@@ -1384,33 +1386,7 @@ class MacheteClient:
         return result
 
     def sync_annotations_to_github_prs(self) -> None:
-
-        url_for_remote: Dict[str, str] = {remote: self.__git.get_url_of_remote(remote) for remote in
-                                          self.__git.get_remotes()}
-        if not url_for_remote:
-            raise MacheteException(fmt('No remotes defined for this repository (see `git remote`)'))
-
-        optional_org_name_for_github_remote: Dict[str, Optional[Tuple[str, str]]] = {
-            remote: get_parsed_github_remote_url(url) for remote, url in url_for_remote.items()}
-        org_name_for_github_remote: Dict[str, Tuple[str, str]] = {remote: org_name for remote, org_name in
-                                                                  optional_org_name_for_github_remote.items() if
-                                                                  org_name}
-        if not org_name_for_github_remote:
-            raise MacheteException(
-                fmt('Remotes are defined for this repository, but none of them '
-                    'corresponds to GitHub (see `git remote -v` for details)'))
-
-        org: str = ''
-        repo: str = ''
-        if len(org_name_for_github_remote) == 1:
-            org, repo = list(org_name_for_github_remote.values())[0]
-        elif len(org_name_for_github_remote) > 1:
-            if 'origin' in org_name_for_github_remote:
-                org, repo = org_name_for_github_remote['origin']
-            else:
-                raise MacheteException(
-                    f'Multiple non-origin remotes correspond to GitHub in this repository: '
-                    f'{", ".join(org_name_for_github_remote.keys())}, aborting')
+        remote, org, repo = self.__derive_remote_and_github_org_and_repo()
         current_user: Optional[str] = git_machete.github.derive_current_user_login()
         debug('Current GitHub user is ' + (current_user or '<none>'))
         pr: GitHubPullRequest
@@ -1878,7 +1854,7 @@ class MacheteClient:
         org: str
         repo: str
         remote: str
-        remote, (org, repo) = self.__derive_remote_and_github_org_and_repo()
+        remote, org, repo = self.__derive_remote_and_github_org_and_repo()
         current_user: Optional[str] = git_machete.github.derive_current_user_login()
         if not current_user and my_opened_prs:
             msg = ("Could not determine current user name, please check that the GitHub API token provided by one of the: "
@@ -2028,14 +2004,14 @@ class MacheteClient:
         for remote, url in url_for_remote.items():
             url = url if url.endswith('.git') else url + '.git'
             remote_url = remote_url if remote_url.endswith('.git') else remote_url + '.git'
-            if is_github_remote_url(url) and get_parsed_github_remote_url(url) == get_parsed_github_remote_url(remote_url):
+            if is_github_remote_url(url) and get_parsed_github_remote_url(url, remote) == get_parsed_github_remote_url(remote_url, remote):
                 return remote
         return None
 
     def retarget_github_pr(self, head: LocalBranchShortName) -> None:
         org: str
         repo: str
-        _, (org, repo) = self.__derive_remote_and_github_org_and_repo()
+        _, org, repo = self.__derive_remote_and_github_org_and_repo()
 
         debug(f'organization is {org}, repository is {repo}')
 
@@ -2058,30 +2034,44 @@ class MacheteClient:
         else:
             print(fmt(f'The base branch of PR #{pr.number} is already `{new_base}`'))
 
-    def __derive_remote_and_github_org_and_repo(self) -> Tuple[str, Tuple[str, str]]:
+    def __derive_remote_and_github_org_and_repo(self) -> RemoteAndOrganizationAndRepository:
+        remote_and_organization_and_repository_for_custom_url = self.__get_remote_and_organization_and_repository_name_for_custom_url()
+        if all(remote_and_organization_and_repository_for_custom_url):
+            return remote_and_organization_and_repository_for_custom_url
+
         url_for_remote: Dict[str, str] = {
             remote: self.__git.get_url_of_remote(remote) for remote in self.__git.get_remotes()
         }
         if not url_for_remote:
             raise MacheteException(fmt('No remotes defined for this repository (see `git remote`)'))
 
-        org_and_repo_for_github_remote: Dict[str, Tuple[str, str]] = {
-            remote: get_parsed_github_remote_url(url) for remote, url in url_for_remote.items() if is_github_remote_url(url)
+        remote_and_github_org_and_repo: Dict[str, RemoteAndOrganizationAndRepository] = {
+            remote: get_parsed_github_remote_url(url, remote) for remote, url in url_for_remote.items() if is_github_remote_url(url)
         }
-        if not org_and_repo_for_github_remote:
+        if not remote_and_github_org_and_repo:
             raise MacheteException(
                 fmt('Remotes are defined for this repository, but none of them '
-                    'corresponds to GitHub (see `git remote -v` for details)\n'))
+                    'corresponds to GitHub (see `git remote -v` for details). \n'
+                    'It is possible that you are using custom GitHub URL.\n'
+                    'If that is the case, you can provide repository information explicitly, via these 3 git config keys: '
+                    '`machete.github.{remote,organization,repository}`\n'))
 
-        if len(org_and_repo_for_github_remote) == 1:
-            return list(org_and_repo_for_github_remote.items())[0]
+        if len(remote_and_github_org_and_repo) == 1:
+            return remote_and_github_org_and_repo[list(remote_and_github_org_and_repo.keys())[0]]
 
-        if 'origin' in org_and_repo_for_github_remote:
-            return 'origin', org_and_repo_for_github_remote['origin']
+        if 'origin' in remote_and_github_org_and_repo:
+            return remote_and_github_org_and_repo['origin']
 
         raise MacheteException(
             f'Multiple non-origin remotes correspond to GitHub in this repository: '
-            f'{", ".join(org_and_repo_for_github_remote.keys())}, aborting')
+            f'{", ".join(remote_and_github_org_and_repo.keys())}, aborting. \n'
+            f'You can also select the repository by providing 3 git config keys: '
+            '`machete.github.{remote,organization,repository}`\n')
+
+    def __get_remote_and_organization_and_repository_name_for_custom_url(self) -> RemoteAndOrganizationAndRepository:
+        return RemoteAndOrganizationAndRepository(remote=self.__git.get_config_attr_or_none("machete.github.remote"),
+                                                  organization=self.__git.get_config_attr_or_none("machete.github.organization"),
+                                                  repository=self.__git.get_config_attr_or_none("machete.github.repository"))
 
     def create_github_pr(
             self, *, head: LocalBranchShortName, opt_draft: bool, opt_onto: Optional[LocalBranchShortName]) -> None:
@@ -2094,7 +2084,7 @@ class MacheteClient:
             raise MacheteException(f'Could not determine base branch for PR. Branch `{head}` is a root branch.')
         org: str
         repo: str
-        remote, (org, repo) = self.__derive_remote_and_github_org_and_repo()
+        remote, org, repo = self.__derive_remote_and_github_org_and_repo()
         print(f"Fetching {remote}...")
         self.__git.fetch_remote(remote)
         if '/'.join([remote, base]) not in self.__git.get_remote_branches():
diff --git a/git_machete/docs.py b/git_machete/docs.py
index 9ea2cb6..ab16b25 100644
--- a/git_machete/docs.py
+++ b/git_machete/docs.py
@@ -1,3 +1,4 @@
+import textwrap
 from typing import Dict
 from git_machete.constants import DISCOVER_DEFAULT_FRESH_BRANCH_COUNT
 
@@ -30,6 +31,29 @@ short_docs: Dict[str, str] = {
     "version": "Display the version and exit"
 }
 
+github_api_access = '''To allow GitHub API access for private repositories (and also to perform side-effecting actions like opening a PR,
+even in case of public repositories), a GitHub API token with `repo` scope is required, see https://github.com/settings/tokens.
+This will be resolved from the first of:
+    1. `GITHUB_TOKEN` env var,
+    2. content of the `.github-token` file in the home directory (`~`),
+    3. current auth token from the `gh` GitHub CLI,
+    4. current auth token from the `hub` GitHub CLI.'''
+
+github_config_keys = '''GitHub API server URL will be inferred from `git remote`.
+You can override this by setting the following git config keys:
+    Remote name
+        E.g. `machete.github.remote` = `origin`
+    Organization name
+        E.g. `machete.github.organization` = `VirtusLab`
+    Repository name
+        E.g. `machete.github.repository` = `git-machete`
+
+To do this, run `git config --local --edit` and add the following section:
+    [machete "github"]
+        organization = <organization_name>
+        repository = <repo_name>
+        remote = <remote_name>'''
+
 long_docs: Dict[str, str] = {
     "add": """
         <b>Usage: git machete add [-o|--onto=<target-upstream-branch>] [-R|--as-root] [-y|--yes] [<branch>]</b>
@@ -112,7 +136,7 @@ long_docs: Dict[str, str] = {
           <b>-y, --yes</b>         Don't ask for confirmation whether to fast-forward the current branch or whether to slide-out the downstream.
                             Fails if the current branch has more than one green-edge downstream branch.
     """,
-    "anno": """
+    "anno": f"""
         <b>Usage:
           git machete anno [-b|--branch=<branch>] [<annotation text>]
           git machete anno -H|--sync-github-prs</b>
@@ -126,12 +150,9 @@ long_docs: Dict[str, str] = {
         If invoked with `-H` or `--sync-github-prs`, annotates the branches based on their corresponding GitHub PR numbers and authors.
         Any existing annotations are overwritten for the branches that have an opened PR; annotations for the other branches remain untouched.
 
-        To allow GitHub API access for private repositories (and also to perform side-effecting actions like opening a PR, even in case of public repositories),
-        a GitHub API token with `repo` scope is required, see `https://github.com/settings/tokens`. This will be resolved from the first of:
-        1. `GITHUB_TOKEN` env var,
-        2. content of the `.github-token` file in the home directory (`~`),
-        3. current auth token from the `gh` GitHub CLI,
-        4. current auth token from the `hub` GitHub CLI.
+{textwrap.indent(github_api_access, "          ")}
+
+{textwrap.indent(github_config_keys, "          ")}
 
         In any other case, sets the annotation for the given/current branch to the given argument.
         If multiple arguments are passed to the command, they are concatenated with a single space.
@@ -142,9 +163,8 @@ long_docs: Dict[str, str] = {
           <b>-b, --branch=<branch></b>      Branch to set the annotation for.
           <b>-H, --sync-github-prs</b>      Annotate with GitHub PR numbers and authors where applicable.
     """,
-    "clean": """
-        <b>Usage:
-          git machete clean [-c|--checkout-my-github-prs] [-y|--yes]
+    "clean": f"""
+        <b>Usage: git machete clean [-c|--checkout-my-github-prs] [-y|--yes]</b>
 
         Synchronizes with the remote repository:
             1. if invoked with `-H` or `--checkout-my-github-prs`, checks out open PRs for the current user associated with the GitHub token and also traverses the chain of pull requests upwards, adding branches one by one to git-machete and checks them out locally as well,
@@ -154,13 +174,9 @@ long_docs: Dict[str, str] = {
         No branch will be deleted unless explicitly confirmed by the user (or unless `-y/--yes` option is passed).
         Equivalent of `git machete github sync` if invoked with `-H` or `--checkout-my-github-prs`.
 
-        To allow GitHub API access for private repositories (and also to perform side-effecting actions like opening a PR, even in case of public repositories),
-        a GitHub API token with `repo` scope is required, see https://github.com/settings/tokens. This will be resolved from the first of:
+{textwrap.indent(github_api_access, "          ")}
 
-            1. `GITHUB_TOKEN` env var,
-            2. content of the `.github-token` file in the home directory (`~`),
-            3. current auth token from the `gh` GitHub CLI,
-            4. current auth token from the `hub` GitHub CLI.
+{textwrap.indent(github_config_keys, "          ")}
 
         **Options:**
           <b>--c, --checkout-my-github-prs</b>     Checkout your open PRs into local branches.
@@ -327,18 +343,16 @@ long_docs: Dict[str, str] = {
         Tabs or any number of spaces can be used as indentation.
         It's only important to be consistent wrt. the sequence of characters used for indentation between all lines.
     """,
-    "github": """
+    "github": f"""
         <b>Usage: git machete github <subcommand></b>
         where <subcommand> is one of: `anno-prs`, `checkout-prs`, `create-pr`, `retarget-pr`, `sync`.
 
         Creates, checks out and manages GitHub PRs while keeping them reflected in branch definition file.
 
-        To allow GitHub API access for private repositories (and also to perform side-effecting actions like opening a PR, even in case of public repositories),
-        a GitHub API token with `repo` scope is required, see `https://github.com/settings/tokens`. This will be resolved from the first of:
-        1. `GITHUB_TOKEN` env var,
-        2. content of the .github-token file in the home directory (`~`),
-        3. current auth token from the `gh` GitHub CLI,
-        4. current auth token from the `hub` GitHub CLI.
+{textwrap.indent(github_api_access, "          ")}
+
+{textwrap.indent(github_config_keys, "          ")}
+
 
         <b>`anno-prs`:</b>
 
@@ -662,6 +676,24 @@ long_docs: Dict[str, str] = {
 
           <b>--no-detect-squash-merges</b>         Only consider "strict" (fast-forward or 2-parent) merges, rather than rebase/squash merges,
                                             when detecting if a branch is merged into its upstream (parent).
+
+        <b>Config keys:</b>
+        To make it easier to select branch name from the `status` output on certain terminals (e.g. Alacritty), you can add an extra
+        space between └─ and branch name by setting `git config machete.status.extraSpaceBeforeBranchName true`.
+
+        For example, by default it's:
+            develop
+            │
+            ├─feature_branch1
+            │
+            └─feature_branch2
+
+        With `machete.status.extraSpaceBeforeBranchName` config set to `true`:
+            develop
+            │
+            ├─ feature_branch1
+            │
+            └─ feature_branch2
     """,
     "traverse": """
         <b>Usage: git machete t[raverse] [-F|--fetch] [-l|--list-commits] [-M|--merge]
diff --git a/git_machete/git_operations.py b/git_machete/git_operations.py
index 2d69e69..10a43a1 100644
--- a/git_machete/git_operations.py
+++ b/git_machete/git_operations.py
@@ -314,6 +314,10 @@ class GitContext:
         self.__ensure_config_loaded()
         return self.__config_cached.get(key.lower())
 
+    def get_boolean_config_attr(self, key: str) -> bool:
+        self.__ensure_config_loaded()
+        return self.__config_cached.get(key.lower()) == 'true'
+
     def set_config_attr(self, key: str, value: str) -> None:
         self._run_git("config", "--", key, value)
         self.__ensure_config_loaded()
@@ -335,7 +339,7 @@ class GitContext:
         return self.__remotes_cached
 
     def get_url_of_remote(self, remote: str) -> str:
-        return self._popen_git("config", "--get", f"remote.{remote}.url").strip()  # 'git remote get-url' method has only been added in git v2.5.1
+        return self.get_config_attr_or_none(f"remote.{remote}.url").strip()  # 'git remote get-url' method has only been added in git v2.5.1
 
     def fetch_remote(self, remote: str) -> None:
         if remote not in self.__fetch_done_for:
diff --git a/git_machete/github.py b/git_machete/github.py
index 72af595..139bd83 100644
--- a/git_machete/github.py
+++ b/git_machete/github.py
@@ -7,7 +7,7 @@ import re
 import shutil
 import subprocess
 from pathlib import Path
-from typing import Any, Dict, List, Optional, Tuple
+from typing import Any, Dict, List, NamedTuple, Optional
 import urllib.request
 import urllib.error
 
@@ -15,7 +15,6 @@ from git_machete.utils import debug, fmt
 from git_machete.exceptions import MacheteException, UnprocessableEntityHTTPError
 from git_machete.git_operations import GitContext, LocalBranchShortName
 
-
 GITHUB_TOKEN_ENV_VAR = 'GITHUB_TOKEN'
 # GitHub Enterprise deployments use alternate domains.
 # The logic in this module will need to be expanded to detect
@@ -42,6 +41,12 @@ class GitHubPullRequest(object):
         return f"PR #{self.number} by {self.user}: {self.head} -> {self.base}"
 
 
+class RemoteAndOrganizationAndRepository(NamedTuple):
+    remote: str
+    organization: str
+    repository: str
+
+
 def __parse_pr_json(pr_json: Any) -> GitHubPullRequest:
     return GitHubPullRequest(number=int(pr_json['number']),
                              user=pr_json['user']['login'],
@@ -254,12 +259,14 @@ def is_github_remote_url(url: str) -> bool:
     return any((re.match(pattern, url) for pattern in GITHUB_REMOTE_PATTERNS))
 
 
-def get_parsed_github_remote_url(url: str) -> Optional[Tuple[str, str]]:
+def get_parsed_github_remote_url(url: str, remote: str) -> Optional[RemoteAndOrganizationAndRepository]:
 
     for pattern in GITHUB_REMOTE_PATTERNS:
         match = re.match(pattern, url)
         if match:
-            return match.group(1), match.group(2)
+            return RemoteAndOrganizationAndRepository(remote=remote,
+                                                      organization=match.group(1),
+                                                      repository=match.group(2))
     return None
 
 


Test patch:
diff --git a/tests/mockers.py b/tests/mockers.py
index 809a48e..7e76788 100644
--- a/tests/mockers.py
+++ b/tests/mockers.py
@@ -103,6 +103,14 @@ class GitRepositorySandbox:
         self.execute(f'git remote add {remote} {url}')
         return self
 
+    def remove_remote(self, remote: str) -> "GitRepositorySandbox":
+        self.execute(f'git remote remove {remote}')
+        return self
+
+    def add_git_config_key(self, key: str, value: str) -> "GitRepositorySandbox":
+        self.execute(f'git config {key} {value}')
+        return self
+
 
 class MockGitHubAPIState:
     def __init__(self, pulls: List[Dict[str, Any]], issues: List[Dict[str, Any]] = None) -> None:
diff --git a/tests/test_github.py b/tests/test_github.py
index a3621fd..3af713a 100644
--- a/tests/test_github.py
+++ b/tests/test_github.py
@@ -202,6 +202,38 @@ class TestGithub:
             """,
         )
 
+        # Test anno-prs using custom remote URL provided by git config keys
+        (
+            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')
+        )
+
+        launch_command("discover", "-y")
+        launch_command('github', 'anno-prs')
+        assert_command(
+            ["status"],
+            """
+            master
+            |
+            o-hotfix/add-trigger (diverged from origin)
+              |
+              o-ignore-trailing *  PR #3 (github_user) (diverged from & older than origin)
+
+            develop
+            |
+            x-allow-ownership-link  PR #7 (github_user) (ahead of origin)
+            | |
+            | x-build-chain (untracked)
+            |
+            o-call-ws  PR #31 (github_user) (ahead of origin)
+              |
+              x-drop-constraint (untracked)
+            """,
+        )
+
     git_api_state_for_test_create_pr = MockGitHubAPIState(
         [
             {
@@ -705,7 +737,7 @@ class TestGithub:
         # check against wrong pr number
         repo: str
         org: str
-        (org, repo) = get_parsed_github_remote_url(self.repo_sandbox.remote_path)
+        _, org, repo = get_parsed_github_remote_url(self.repo_sandbox.remote_path, remote='origin')
         expected_error_message = f"PR #100 is not found in repository `{org}/{repo}`"
         with pytest.raises(MacheteException) as e:
             launch_command('github', 'checkout-prs', '100')
diff --git a/tests/test_status.py b/tests/test_status.py
index a47add1..4cad6c7 100644
--- a/tests/test_status.py
+++ b/tests/test_status.py
@@ -4,7 +4,7 @@ import pytest
 
 from git_machete.exceptions import MacheteException
 
-from .mockers import (GitRepositorySandbox, launch_command, mock_exit_script,
+from .mockers import (assert_command, GitRepositorySandbox, launch_command, mock_exit_script,
                       mock_run_cmd, rewrite_definition_file)
 
 
@@ -46,3 +46,44 @@ class TestStatus:
         if e:
             assert e.value.parameter == expected_error_message, \
                 'Verify that expected error message has appeared if a branch re-appears in tree definition.'
+
+    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
+
+        (
+            self.repo_sandbox
+                .new_branch('master')
+                .commit()
+                .push()
+                .new_branch('bar')
+                .commit()
+                .push()
+                .new_branch('foo')
+                .commit()
+                .push()
+                .add_git_config_key('machete.status.extraSpaceBeforeBranchName', 'true')
+        )
+        launch_command('discover', '-y')
+
+        expected_status_output = (
+"""   master
+   |
+   o- bar
+      |
+      o- foo *
+"""  # noqa: E122
+        )
+        assert_command(['status'], expected_status_output.replace('|', '| ', 2), strip_indentation=False)
+
+        self.repo_sandbox.add_git_config_key('machete.status.extraSpaceBeforeBranchName', 'false')
+
+        expected_status_output = (
+"""  master
+ |
+ o-bar
+   |
+   o-foo *
+"""  # noqa: E122
+        )
+        assert_command(['status'], expected_status_output)