Is it possible to override git command by git alias?
GitAliasGit Problem Overview
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?
Git Solutions
Solution 1 - Git
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 - Git
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 - Git
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 - Git
Not only not possible, but also WONTFIX
> > 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 - Git
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" "$@"