4

I am writing a shell script to add a "review" sub command to git, to make it easier to do code reviews. It's basically a fancy wrapper for git diff and git difftool commands, but requires less typing.

Some example usages:

# Lists added, deleted, renamed and modified files between master and current branch
git review master -l

# Opens difftool for files modified between master and current branch
git review -m

I would like to enable branch auto completion in my shell script, but cannot for the life of me figure out how to do it. Here is what I'm really after:

git review ma<tab><tab>

And then it should behave like git checkout ma<tab><tab>.

My shell script:

#!/bin/env bash

function print_usage {
    echo "Usage: git review <branch> <-a|-d|-m|-l> [-- paths/to filter/by]"
    echo ""
    echo "    -a: Only review added files"
    echo "    -d: Only review delete files"
    echo "    -m: Only review modified files"
    echo "    -l: List files and and type of modification"
}

if [ -z "$1" ] || [ -z "$2" ]; then
    print_usage
    exit 1
fi

git_branch=$1
review_command=$2
path_filters=""

shift
shift

if [ "$1" = "--" ]; then
    path_filters="$@"
fi

case $review_command in
    "-a")
        echo "Reviewing added files..."

        if [ -z "$path_filters" ]; then
            git difftool $git_branch -- $(git diff --name-status $git_branch | grep -E '^A' | awk '{print $2}')
        else
            git difftool $git_branch -- $(git diff --name-status $git_branch -- $path_filters | grep -E '^A' | awk '{print $2}')
        fi
        ;;
    "-d")
        echo "Reviewing deleted files..."

        if [ -z "$path_filters" ]; then
            git difftool $git_branch -- $(git diff --name-status $git_branch | grep -E '^D' | awk '{print $2}')
        else
            git difftool $git_branch -- $(git diff --name-status $git_branch -- $path_filters | grep -E '^D' | awk '{print $2}')
        fi
        ;;
    "-m")
        echo "Reviewing modified files..."

        if [ -z "$path_filters" ]; then
            git difftool $git_branch -- $(git diff --name-status $git_branch | grep -E '^M' | awk '{print $2}')
        else
            git difftool $git_branch -- $(git diff --name-status $git_branch -- $path_filters | grep -E '^M' | awk '{print $2}')
        fi
        ;;
    "-l")
        echo "Differences between $git_branch and $(git mybranch)..."

        if [ -z "$path_filters" ]; then
            git diff --name-status $git_branch
        else
            git diff --name-status $git_branch -- $path_filters
        fi
        ;;
esac

echo ""
echo "Review finished."

Really, since I'm in the process of typing the command in, I doubt my shell script will have anything to do with the solution.

Some other useful info:

  • Windows 10
  • Git v2.18.0.windows.1
  • Shell: GNU bash, version 4.4.19(2)-release (x86_64-pc-msys)

Is there a way to add git branch auto completion to a custom git extension command?


An interesting related question for Linux: custom git command autocompletion.

Greg Burghardt
  • 17,900
  • 9
  • 49
  • 92

1 Answers1

5

Using an alias

Let's assume a minimal version of your script, something like

#!/usr/bin/env bash

git diff "$@"

This is executable and somewhere in your $PATH. Let's call it gitreview.

To get an alias for it, you add it like this in your .gitconfig:

[alias]
    review = "!f() { gitreview \"$@\"; }; f"

This gets you completion for review when you enter git. Now, to make it behave the same as git checkout, you can use a null command, like this:

review = "!f() { : git checkout ; gitreview \"$@\"; }; f"

and it'll complete the same as git checkout! Notice that the blank between checkout and ; is required.


This is mentioned in the comments of git-completion.bash:

# If you use complex aliases of form '!f() { ... }; f', you can use the null
# command ':' as the first command in the function body to declare the desired
# completion style.  For example '!f() { : git commit ; ... }; f' will
# tell the completion to use commit completion.  This also works with aliases
# of form "!sh -c '...'".  For example, "!sh -c ': git commit ; ... '".

Using an external command

Alternatively, you can make your script a proper external command by naming it git-review and leave it somewhere in your $PATH. To get completion for it (assuming you have both Bash and Git completion), add this to your ~/.bash_completion file (just create it if it doesn't exist):

_git_review() { _git_checkout; }

Git completion (for Git 2.18 or newer) checks for completion functions that are named _git_$command, and by doing this, you say "just call _git_checkout instead".

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
  • That's interesting. Right now my shell script is named `git-review` to follow the naming convention allowing you to extend git with new commands. So your answer basically means the shell script should **not** follow the naming convention for git command extensions, and instead defines a special alias in Git that tricks it into thinking my `git review` command should be tab-completed as if it were the `git checkout` command? Diabolical. – Greg Burghardt Aug 08 '19 at 17:48
  • This is pretty cool. Works like a charm! I actually looked through the git-completion.bash script before posting this question and got lost in the 3,000 lines of code. The comment you quoted is right at the beginning. – Greg Burghardt Aug 08 '19 at 17:54
  • @GregBurghardt Ah, I actually wondered how you'd integrate a custom command as I'm only aware of aliases. But if it works, it works ¯\\_(ツ)_/¯ – Benjamin W. Aug 08 '19 at 17:56
  • I did verbatim what you posted as an answer. Worked the first time on Git v2.18.0.windows.1. – Greg Burghardt Aug 08 '19 at 17:57
  • @GregBurghardt [This Q&A](https://stackoverflow.com/questions/41307313/custom-git-command-autocompletion) seems to have a way to get completion for a "proper" custom command. – Benjamin W. Aug 08 '19 at 18:03
  • @GregBurghardt I've found a way to do it with a custom command as well (similar to other Q&A), let me add that... – Benjamin W. Aug 08 '19 at 18:52