0

It is well documented that I can add a new command to git by placing the script/program git-foobar on my $PATH, which I can then invoke as git foobar.

But what if I want to extend an existing git command? For example, the git worktree command has verbs add,list,lock,etc.. I have a very specific way in which I organize where I keep and how I name worktrees. I have written a script `git-worktree' that has a "main" function that looks like this:

declare action=$1
shift;

if [[ "$action" =~ -h ]]
then
    script-usage
    return  0
fi

case $action in
    create | delete )
        if [[ "$1" =~ -h ]]
        then
            script-usage
            return  0
        else
            "git-worktree-${action}" "$@"
            return $?
        fi
        ;;
    *)
        "${git_bin}/git-worktree" "$@"
        return $?
        ;;
esac

If I type 'create' or 'delete' I call functions defined elsewhere in my script. For anything else, I just pass along to the real git worktree command.

Except that it doesn't quite work. Typing git-worktree executes my script. Typing git worktree does NOT run my script.

Is there something I am missing or can I not extend existing git commands in the same way in which I can add new commands?

0

3 Answers 3

3

You would need to follow the pattern used by hub where you have an alternative script that you alias in your shell as git. That alternative script would intercept the relevant built-in commands and exec your custom override script.

#!/usr/bin/env bash

# my-git.sh

declare action=$1
shift;

case $action in
    worktree )
        exec git-worktree "$@"
        ;;
    *)
        exec git "$action" "$@"
    ;;
esac

Assuming the above script is in your path somewhere as my-git.sh, you can just alias git=my-git.sh and it should work

Sign up to request clarification or add additional context in comments.

1 Comment

Yep. That's the conclusion I have come to. I made it a bit more difficult by insisting on naming my hub script as 'git', not my-git or some other name. In my profile, I have to remove my local path, find git and the git exec path on the PATH that remains, push those two values into the environment and then use them in my 'git' dispatch script later on.
1

I came to the same conclusion as Brian Philips' answer - you need to do your own dispatching to extend existing git commands. Unlike Brian, I made the task more difficult by insisting that the dispatching program still be called 'git'. :-)

In .profile, find and record the real git locations

## PERSONALBIN is my local bin dir
tempath=$(echo $PATH | sed "s|;$PERSONALBIN:|;|")
REALGIT=$(PATH=$tempath; hash -r ; which git)
export REALGIT

As an alternative, if you don't keep track of your personal bin in an envvar and you are sure that just beyond your git in PATH is the system git, you could replace the REALGIT setting above with:

REALGIT=$(hash -r; which -a git | head -2 | tail -1 )

In $PERSONALBIN/git, call my script or real git:

declare command=$1; shift
declare extend_script="git-${command}"
if which "$extend_script" 1>/dev/null 2>/dev/null
then
  echo "--- Dispatched to a local git- script"
  "$extend_script" "$@"
else
  echo "--- Dispatched to git"
  "${REALGIT} "${command}" "$@"
fi
exit $?

If this was a new command git foobar, I could write $PERSONALBIN/git-foobar to just do what I want. However, since I am extending an existing command, I have to dispatch anything that my command doesn't handle. So, in $PERSONALBIN/git-worktree:

declare action=$1
shift;

if [[ "$action" =~ -h ]]
then
    script-usage
    exit 0
fi

action_func_name="git-worktree-${action}"
if declare -F "$action_func_name" >/dev/null
then
    "$action_func_name" "$@"
else
    ## Dispatch to real git worktree
    "${REALGIT} worktree "$action" "$@"
fi
exit $?

I leave it as an exercise to the reader to extract this bit into a generic function that you can plug in to multiple places changing only the args.

Comments

-1

You won't be able to override built-in commands.

Per the code at github.com/git/git/blob/master/git.c#L606, git will call the built-in implementations using the compiled implementations for matching commands and not shell out to an override.

If it can't find the command you specify (either as a built-in or as an external) it will try to find a declared alias and then try and run THAT.

So your only option are with a custom command with a name NOT used as a built-in.

3 Comments

Joe, see above.
That's true. I wasn't really considering intercepting git itself as in-scope for the question but that's true. I still stand by this answer that, within the confines of the git implementation, this isn't supported (in a way that adding external extension commands is)
Fair enough. Thanks.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.