Repository: VirtusLab/git-machete
Pull request #165 (2021-08-05 13:11:24)

Fix help for `show`


Patch:
diff --git a/git_machete/cmd.py b/git_machete/cmd.py
index 276b185..e489542 100644
--- a/git_machete/cmd.py
+++ b/git_machete/cmd.py
@@ -1979,9 +1979,9 @@ def launch(orig_args: List[str]) -> None:
         sys.stderr.write(f"Python {version_str} is no longer supported. Please switch to Python 3.6 or higher.\n")
         sys.exit(1)
 
-    def parse_options(in_args: List[str], short_opts: str = "", long_opts: List[str] = [], gnu: bool = True) -> List[str]:
+    def parse_options(in_args: List[str], short_opts: str = "", long_opts: List[str] = [], allow_intermixing_options_and_params: bool = True) -> List[str]:
 
-        fun = getopt.gnu_getopt if gnu else getopt.getopt
+        fun = getopt.gnu_getopt if allow_intermixing_options_and_params else getopt.getopt
         opts, rest = fun(in_args, short_opts + "hv", long_opts + ['debug', 'help', 'verbose', 'version'])
 
         for opt, arg in opts:
@@ -2119,7 +2119,10 @@ def launch(orig_args: List[str]) -> None:
 
     try:
         cmd = None
-        cmd_and_args = parse_options(orig_args, gnu=False)
+        # Let's first extract the common options like `--help` or `--verbose` that might appear BEFORE the command,
+        # as in e.g. `git machete --verbose status`.
+        cmd_and_args = parse_options(orig_args, allow_intermixing_options_and_params=False)
+        # Subsequent calls to `parse_options` will, in turn, extract the options appearing AFTER the command.
         if not cmd_and_args:
             usage()
             sys.exit(2)
@@ -2281,8 +2284,9 @@ def launch(orig_args: List[str]) -> None:
             cb = current_branch(git)
             rebase_onto_ancestor_commit(git, cb, cli_opts.opt_fork_point or machete_client.fork_point(cb, use_overrides=True))
         elif cmd == "show":
-            param = check_required_param(args[:1], allowed_directions(allow_current=True))
-            branch = check_optional_param(args[1:])
+            args1 = parse_options(args)
+            param = check_required_param(args1[:1], allowed_directions(allow_current=True))
+            branch = check_optional_param(args1[1:])
             if param == "current" and branch is not None:
                 raise MacheteException(f'`show current` with a branch (`{branch}`) does not make sense')
             machete_client.read_definition_file(verify_branches=False)
diff --git a/git_machete/docs.py b/git_machete/docs.py
index 0e8ca96..f90d311 100644
--- a/git_machete/docs.py
+++ b/git_machete/docs.py
@@ -299,9 +299,17 @@ long_docs: Dict[str, str] = {
         <b>Usage: git machete g[o] <direction></b>
         where <direction> is one of: `d[own]`, `f[irst]`, `l[ast]`, `n[ext]`, `p[rev]`, `r[oot]`, `u[p]`
 
-        Checks out the branch specified by the given direction relative to the currently checked out branch.
+        Checks out the branch specified by the given direction relative to the current branch:
+
+        * `down`:    the direct children/downstream branch of the current branch.
+        * `first`:   the first downstream of the root branch of the current branch (like `root` followed by `next`), or the root branch itself if the root has no downstream branches.
+        * `last`:    the last branch in the definition file that has the same root as the current branch; can be the root branch itself if the root has no downstream branches.
+        * `next`:    the direct successor of the current branch in the definition file.
+        * `prev`:    the direct predecessor of the current branch in the definition file.
+        * `root`:    the root of the tree where the current branch is located. Note: this will typically be something like `develop` or `master`, since all branches are usually meant to be ultimately merged to one of those.
+        * `up`:      the direct parent/upstream branch of the current branch.
+
         Roughly equivalent to `git checkout $(git machete show <direction>)`.
-        See `git machete help show` on more details on meaning of each direction.
     """,
     "help": """
         <b>Usage: git machete help [<command>]</b>
@@ -414,18 +422,18 @@ long_docs: Dict[str, str] = {
     "show": """
         <b>Usage: git machete show <direction> [<branch>]</b>
         where <direction> is one of: `c[urrent]`, `d[own]`, `f[irst]`, `l[ast]`, `n[ext]`, `p[rev]`, `r[oot]`, `u[p]`
-        displayed relative to target <branch>, or the current checked out branch if <branch> is unspecified.
+        displayed relative to given <branch>, or the current checked out branch if <branch> is unspecified.
 
         Outputs name of the branch (or possibly multiple branches, in case of `down`) that is:
 
         * `current`: the current branch; exits with a non-zero status if none (detached HEAD)
-        * `down`:    the direct children/downstream branch of the target branch.
-        * `first`:   the first downstream of the root branch of the target branch (like `root` followed by `next`), or the root branch itself if the root has no downstream branches.
-        * `last`:    the last branch in the definition file that has the same root as the target branch; can be the root branch itself if the root has no downstream branches.
-        * `next`:    the direct successor of the target branch in the definition file.
-        * `prev`:    the direct predecessor of the target branch in the definition file.
-        * `root`:    the root of the tree where the target branch is located. Note: this will typically be something like `develop` or `master`, since all branches are usually meant to be ultimately merged to one of those.
-        * `up`:      the direct parent/upstream branch of the target branch.
+        * `down`:    the direct children/downstream branch of the given branch.
+        * `first`:   the first downstream of the root branch of the given branch (like `root` followed by `next`), or the root branch itself if the root has no downstream branches.
+        * `last`:    the last branch in the definition file that has the same root as the given branch; can be the root branch itself if the root has no downstream branches.
+        * `next`:    the direct successor of the given branch in the definition file.
+        * `prev`:    the direct predecessor of the given branch in the definition file.
+        * `root`:    the root of the tree where the given branch is located. Note: this will typically be something like `develop` or `master`, since all branches are usually meant to be ultimately merged to one of those.
+        * `up`:      the direct parent/upstream branch of the given branch.
     """,
     "slide-out": """
         <b>Usage: git machete slide-out [-d|--down-fork-point=<down-fork-point-commit>] [-M|--merge] [-n|--no-edit-merge|--no-interactive-rebase] <branch> [<branch> [<branch> ...]]</b>


Test patch:
diff --git a/git_machete/tests/functional/test_machete.py b/git_machete/tests/functional/test_machete.py
index 9229db8..236b9ec 100644
--- a/git_machete/tests/functional/test_machete.py
+++ b/git_machete/tests/functional/test_machete.py
@@ -662,3 +662,13 @@ class MacheteTester(unittest.TestCase):
         for (description, commands) in cmd.command_groups:
             for command in commands:
                 self.launch_command("help", command)
+
+                if command not in ("format", "hooks"):
+                    try:
+                        self.launch_command(command, "--help")
+                    except SystemExit as e:
+                        self.assertIs(e.code, None)
+                    except Exception as e:
+                        self.fail(f'Unexpected exception raised: {e}')
+                    else:
+                        self.fail('SystemExit expected but not raised')