Reputation: 6783
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?
Upvotes: 69
Views: 15899
Reputation: 13022
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.
Upvotes: 58
Reputation: 11395
Here’s yet another workaround, that does not rely on actually overriding anything, but relies on abusing autocomplete instead. This feels less dangerous and more transparent than wrapping git.
As I never type the full command names, it is enough to define an alias that is a prefix of the command to override.
For example, to override git show-branch
with an alias, and knowing I usually type git show-<Tab>
, I define the show-br
alias to customise the show-branch
behaviour.
For OP’s example git commit
I usually type git com<Tab>
so git comm
would be correct workaround.
This strategy has several advantages:
Upvotes: 1
Reputation: 111
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" "$@"
Upvotes: 3
Reputation: 383050
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
Upvotes: 22
Reputation: 3044
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
}
Upvotes: 46
Reputation: 1047
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.
Upvotes: 29