Repository: VirtusLab/git-machete
Pull request #143 (2021-07-02 04:10:18)

Enable passing branch to `show` command
Add branch as optional parameter to show command, 
providing a target for the relative direction other than the current branch.

This provides support for querying the current machete structure without changing the current branch pointer.
Used while, for example, scanning machete branches for current upstream status.

Expanded completion scripts, help, tests, and README.

PawelLipski: Nice, I thought about adding that as well :)

Lemme first see what's wrong with the link checker...

```
ci/nixpkgs-pr/build-context/commit-message.md
  16:14-16:101  warning  Link to https://github.com/NixOS/nixpkgs/blob/master/.github/CONTRIBUTING.md is dead  no-dead-urls  remark-lint

ci/nixpkgs-pr/build-context/pr-description.md
  16:14-16:101  warning  Link to https://github.com/NixOS/nixpkgs/blob/master/.github/CONTRIBUTING.md is dead  no-dead-urls  remark-lint
```
PawelLipski: Ok! I've pushed the fix to `develop`
asford: Sounds great. I'm going to update this one more time to use an optional-positional parameter, like `fork-point`, `is-managed`, and `add`, so temporarily shifting to "draft" status.

Patch:
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 3572bb4..1e02871 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -1,5 +1,8 @@
 # Release notes
 
+## New in git-machete 3.3.0
+- improved: `show` can accept a target branch other than the current branch (contributed by @asford)
+
 ## New in git-machete 3.2.1
 
 - fixed: newly created branches were sometimes incorrectly recognized as merged to parent
diff --git a/completion/git-machete.completion.zsh b/completion/git-machete.completion.zsh
index d88c6ee..129feaf 100644
--- a/completion/git-machete.completion.zsh
+++ b/completion/git-machete.completion.zsh
@@ -79,7 +79,9 @@ _git-machete() {
                     && ret=0
                     ;;
                 (show)
-                    _arguments '1:: :__git_machete_directions_show' && ret=0
+                    _arguments '1:: :__git_machete_directions_show' \
+                      '2:: :__git_machete_list_managed' \
+                      && ret=0
                     ;;
                 (slide-out)
                     _arguments \
diff --git a/git_machete/cmd.py b/git_machete/cmd.py
index e629860..aeb4d48 100644
--- a/git_machete/cmd.py
+++ b/git_machete/cmd.py
@@ -484,6 +484,34 @@ def save_definition_file() -> None:
         f.write("\n".join(render_tree()) + "\n")
 
 
+# Allowed parameter values for show/go command
+def allowed_directions(allow_current: bool) -> str:
+    current = "c[urrent]|" if allow_current else ""
+    return current + "d[own]|f[irst]|l[ast]|n[ext]|p[rev]|r[oot]|u[p]"
+
+
+# Parse and evaluate direction against current branch for show/go commands
+def parse_direction(cli_ctxt: CommandLineContext, param: str, b: str, allow_current: bool, down_pick_mode: bool) -> str:
+    if param in ("c", "current") and allow_current:
+        return current_branch(cli_ctxt)  # throws in case of detached HEAD, as in the spec
+    elif param in ("d", "down"):
+        return down(b, pick_mode=down_pick_mode)
+    elif param in ("f", "first"):
+        return first_branch(b)
+    elif param in ("l", "last"):
+        return last_branch(b)
+    elif param in ("n", "next"):
+        return next_branch(b)
+    elif param in ("p", "prev"):
+        return prev_branch(b)
+    elif param in ("r", "root"):
+        return root_branch(b, if_unmanaged=PICK_FIRST_ROOT)
+    elif param in ("u", "up"):
+        return up(cli_ctxt, b, prompt_if_inferred_msg=None, prompt_if_inferred_yes_opt_msg=None)
+    else:
+        raise MacheteException(f"Invalid direction: `{param}` expected: {allowed_directions(allow_current)}")
+
+
 def down(b: str, pick_mode: bool) -> str:
     expect_in_managed_branches(b)
     dbs = down_branches.get(b)
@@ -2748,7 +2776,7 @@ short_docs: Dict[str, str] = {
     "list": "List all branches that fall into one of pre-defined categories (mostly for internal use)",
     "log": "Log the part of history specific to the given branch",
     "reapply": "Rebase the current branch onto its computed fork point",
-    "show": "Show name(s) of the branch(es) relative to the position of the current branch, accepts down/first/last/next/root/prev/up argument",
+    "show": "Show name(s) of the branch(es) relative to the position of a branch, accepts down/first/last/next/root/prev/up argument",
     "slide-out": "Slide out the current branch and sync its downstream (child) branches with its upstream (parent) branch via rebase or merge",
     "squash": "Squash the unique history of the current branch into a single commit",
     "status": "Display formatted tree of branch dependencies, including info on their sync with upstream branch and with remote",
@@ -3141,19 +3169,20 @@ long_docs: Dict[str, str] = {
           <b>-f, --fork-point=<fork-point-commit></b>    Specifies the alternative fork point commit after which the rebased part of history is meant to start.
     """,
     "show": """
-        <b>Usage: git machete show <direction></b>
+        <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.
 
         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 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.
+        * `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.
     """,
     "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>
@@ -3578,30 +3607,6 @@ def launch(orig_args: List[str]) -> None:
                     raise MacheteException(
                         f"{definition_file_path} is a directory rather than a regular file, aborting")
 
-        def allowed_directions(allow_current: bool) -> str:
-            current = "c[urrent]|" if allow_current else ""
-            return current + "d[own]|f[irst]|l[ast]|n[ext]|p[rev]|r[oot]|u[p]"
-
-        def parse_direction(b: str, allow_current: bool, down_pick_mode: bool) -> str:
-            if param in ("c", "current") and allow_current:
-                return current_branch(cli_ctxt)  # throws in case of detached HEAD, as in the spec
-            elif param in ("d", "down"):
-                return down(b, pick_mode=down_pick_mode)
-            elif param in ("f", "first"):
-                return first_branch(b)
-            elif param in ("l", "last"):
-                return last_branch(b)
-            elif param in ("n", "next"):
-                return next_branch(b)
-            elif param in ("p", "prev"):
-                return prev_branch(b)
-            elif param in ("r", "root"):
-                return root_branch(b, if_unmanaged=PICK_FIRST_ROOT)
-            elif param in ("u", "up"):
-                return up(cli_ctxt, b, prompt_if_inferred_msg=None, prompt_if_inferred_yes_opt_msg=None)
-            else:
-                raise MacheteException(f"Usage: git machete {cmd} {allowed_directions(allow_current)}")
-
         if cmd == "add":
             param = check_optional_param(parse_options(args, "o:Ry", ["onto=", "as-root", "yes"]))
             read_definition_file(cli_ctxt)
@@ -3675,7 +3680,7 @@ def launch(orig_args: List[str]) -> None:
             read_definition_file(cli_ctxt)
             expect_no_operation_in_progress(cli_ctxt)
             cb = current_branch(cli_ctxt)
-            dest = parse_direction(cb, allow_current=False, down_pick_mode=True)
+            dest = parse_direction(cli_ctxt, param, cb, allow_current=False, down_pick_mode=True)
             if dest != cb:
                 go(cli_ctxt, dest)
         elif cmd == "help":
@@ -3744,9 +3749,10 @@ def launch(orig_args: List[str]) -> None:
             cb = current_branch(cli_ctxt)
             rebase_onto_ancestor_commit(cli_ctxt, cb, cli_ctxt.opt_fork_point or fork_point(cli_ctxt, cb, use_overrides=True))
         elif cmd == "show":
-            param = check_required_param(parse_options(args), allowed_directions(allow_current=True))
+            param = check_required_param(args[:1], allowed_directions(allow_current=True))
+            branch = check_optional_param(args[1:])
             read_definition_file(cli_ctxt, verify_branches=False)
-            print(parse_direction(current_branch(cli_ctxt), allow_current=True, down_pick_mode=False))
+            print(parse_direction(cli_ctxt, param, branch or current_branch(cli_ctxt), allow_current=True, down_pick_mode=False))
         elif cmd == "slide-out":
             params = parse_options(args, "d:Mn", ["down-fork-point=", "merge", "no-edit-merge", "no-interactive-rebase"])
             read_definition_file(cli_ctxt)


Test patch:
diff --git a/git_machete/tests/functional/test_machete.py b/git_machete/tests/functional/test_machete.py
index 8382f26..7130270 100644
--- a/git_machete/tests/functional/test_machete.py
+++ b/git_machete/tests/functional/test_machete.py
@@ -525,3 +525,80 @@ class MacheteTester(unittest.TestCase):
         for (description, commands) in cmd.command_groups:
             for command in commands:
                 self.launch_command("help", command)
+
+    def test_show(self) -> None:
+        (
+            self.setup.new_branch("root")
+            .commit("root")
+            .new_branch("develop")
+            .commit("develop commit")
+            .new_branch("allow-ownership-link")
+            .commit("Allow ownership links")
+            .push()
+            .new_branch("build-chain")
+            .commit("Build arbitrarily long chains")
+            .check_out("allow-ownership-link")
+            .commit("1st round of fixes")
+            .check_out("develop")
+            .commit("Other develop commit")
+            .push()
+            .new_branch("call-ws")
+            .commit("Call web service")
+            .commit("1st round of fixes")
+            .push()
+            .new_branch("drop-constraint")
+            .commit("Drop unneeded SQL constraints")
+            .check_out("call-ws")
+            .commit("2nd round of fixes")
+            .check_out("root")
+            .new_branch("master")
+            .commit("Master commit")
+            .push()
+            .new_branch("hotfix/add-trigger")
+            .commit("HOTFIX Add the trigger")
+            .push()
+            .commit_amend("HOTFIX Add the trigger (amended)")
+            .new_branch("ignore-trailing")
+            .commit("Ignore trailing data")
+            .sleep(1)
+            .commit_amend("Ignore trailing data (amended)")
+            .push()
+            .reset_to("ignore-trailing@{1}")
+            .delete_branch("root")
+        )
+
+        self.launch_command("discover", "-y", "--roots=develop,master")
+        self.assert_command(
+            ["status"],
+            """
+            develop
+            |
+            x-allow-ownership-link (ahead of origin)
+            | |
+            | x-build-chain (untracked)
+            |
+            o-call-ws (ahead of origin)
+              |
+              x-drop-constraint (untracked)
+
+            master
+            |
+            o-hotfix/add-trigger (diverged from origin)
+              |
+              o-ignore-trailing * (diverged from & older than origin)
+            """,
+        )
+
+        self.assertEqual(
+            self.launch_command(
+                "show", "up",
+            ).strip(),
+            "hotfix/add-trigger"
+        )
+
+        self.assertEqual(
+            self.launch_command(
+                "show", "up", "call-ws",
+            ).strip(),
+            "develop"
+        )