Is it possible to override git command by git alias?

11,599

Solution 1

It is NOT POSSIBLE

This is from my clone of git.git:

static int run_argv(int *argcp, const char ***argv)
{
    int done_alias = 0;

    while (1) {
        /* See if it's an internal command */
        handle_internal_command(*argcp, *argv);

        /* .. then try the external ones */
        execv_dashed_external(*argv);

        /* It could be an alias -- this works around the insanity
         * of overriding "git log" with "git show" by having
         * alias.log = show
         */
        if (done_alias || !handle_alias(argcp, argv))
            break;
        done_alias = 1;
    }

    return done_alias;
}

So its not possible. (handle_internal_command calls exit if it finds the command).

You could fix this in your sources by changing the order of the lines and making handle_alias call exit if it finds the alias.

Solution 2

I opted to solve this with a bash function. If I call git clone, it will redirect the call to git cl, which is my alias with some added switches.

function git {
  if [[ "$1" == "clone" && "$@" != *"--help"* ]]; then
    shift 1
    command git cl "$@"
  else
    command git "$@"
  fi
}

Solution 3

As already mentioned, it is not possible to use a git alias to override a git command. However, it is possible to override a git command using a shell alias. For any POSIXy shell (i.e. not MS cmd), write a simple executable script that performs the desired modified behavior and set a shell alias. In my .bashrc (Linux) and .bash_profile (Mac) I have

export PATH="~/bin:$PATH"
...
alias git='my-git'

In my ~/bin folder I have an executable Perl script called my-git that checks if the first argument (i.e. the git command) is clone. It looks essentially like this:

#!/usr/bin/env perl
use strict;
use warnings;
my $path_to_git = '/usr/local/bin/git';
exit(system($path_to_git, @ARGV))
    if @ARGV < 2 or $ARGV[0] ne 'clone';
# Override git-clone here...

Mine is a little more configurable, but you get the idea.

Solution 4

Not only not possible, but also WONTFIX

In 2009 http://git.661346.n2.nabble.com/allowing-aliases-to-override-builtins-to-support-default-options-td2438491.html

Hamano replies:

Currently git does not allow aliases to override builtins. I understand the reasoning behind this, but I wonder if it's overly conservative.

It is not.

Most shells support overriding commands with aliases, and I'm not sure why git needs to be more conservative than the shell.

Because sane shells do not expand aliases when used in a script, and gives a handy way to defeat the alias even from the command line.

$ alias ls='ls -aF'
$ echo ls >script
$ chmod +x script

and compare:

$ ./script
$ ls
$ /bin/ls

Solution 5

FWIW, I solved this (okay, "worked around it"...) by writing the following ~/bin/git wrapper, which checks for, e.g., ~/bin/git-clone, and calls that instead of the built-in.

[NOTE: I apologize for any "clever" bash-isms, but after you get past the two helper functions — one to expand symlinks and one to search your $PATH for the executable being wrapped — the actual script itself is just Three Lines of Code™... So I guess I'm not sorry after all, hehe!]

#!/usr/bin/env bash

###########################
###  UTILITY FUNCTIONS  ###  ...from my .bashrc
###########################
#
# deref "/path/with/links/to/symlink"
#   - Returns physical path for specified target
#
# __SUPER__
#   - Returns next "$0" in $PATH (that isn't me, or a symlink to me...)

deref() {
  ( # Wrap 'cd's in a sub-shell
    local target="$1"
    local counter=0

    # If the argument itself is a link [to a link, to a link...]
    # NOTE: readlink(1) is not defined by POSIX, but has been shown to
    #  work on at least MacOS X, CentOS, Ubuntu, openSUSE, and OpenBSD
    while [[ -L "$target" ]]; do
        [[ $((++counter)) -ge 30 ]] && return 1
        cd "${target%/*}"; target="$(readlink "$target")"
    done

    # Expand parent directory hierarchy
    cd "${target%/*}" 2>/dev/null \
      && echo "$(pwd -P)/${target##*/}" \
      || echo "$([[ $target != /* ]] && echo "$(pwd -P)/")$target"
  )
}

__SUPER__() {
  local cmd="${1:-${0##*/}}"
  local me="$(deref "$0")"

  # NOTE: We only consider symlinks...  We could check for hardlinks by
  #       comparing device+inode, but stat(1) has portability problems

  local IFS=":"
  for d in $PATH; do
    [[ -x "$d/$cmd" ]] && [[ "$(deref "$d/$cmd")" != "$me" ]] \
      && { echo "$d/$cmd"; return; }
  done

  # else...
  return 1
}

########################################################################

# (1) First, figure out which '$0' we *WOULD* have run...

GIT="$(__SUPER__)" || { echo "${0##*/}: command not found" >&2; exit 1; }

# (2) If we have a "~/bin/git-${command}" wrapper, then
#     prepend '.../libexec/git-core' to $PATH and run it

[[ -f "${HOME}/bin/git-$1" ]] &&
  PATH="$PATH:$( "$GIT" --exec-path )" \
    exec "${HOME}/bin/git-$1" "${@:2}"

# (3) Else fall back to the regular 'git'

exec "$GIT" "$@"
Share:
11,599
noisy
Author by

noisy

Updated on June 03, 2022

Comments

  • noisy
    noisy almost 2 years

    my ~/.gitconfig is:

    [alias]
            commit = "!sh commit.sh"
    

    However, when I type git commit, script is not called.

    Is it possible, or I have to use another alias name?