66

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?

noisy
  • 6,495
  • 10
  • 50
  • 92
  • 2
    mathepic's answer is completely correct. Of course, I think it's kind of a moot point. As long as you're aliasing, why not shorten the command? Alias `co` to `!sh checkout.sh`, so you don't have to type it all out (or even tab-complete it). – Cascabel Aug 21 '10 at 22:49
  • This is unfortunate. I too would have liked this feature to override the default behavior of "git log" with a oneline format. I know you can use other aliases but as long as the default one exists, habit makes you use it and never learn your substitute command :( – Sridhar Sarnobat Nov 15 '13 at 00:04

6 Answers6

54

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.

Alex Rintt
  • 1,618
  • 1
  • 11
  • 18
alternative
  • 12,703
  • 5
  • 41
  • 41
  • 17
    git config should probably issue some kind of a warning if trying to create an alias that is identical to an internal/external command... Ironically, I can't find where to report issues for git-scm itself. The Google and Stack Overflow results all seem to be clouded by tools for managing issues WITH Git. – Daniel Hershcovich Sep 14 '11 at 19:08
  • This requires change to git source code. I can replace `libexec/git-core/git-commit` with my custom version instead. – linquize May 24 '13 at 07:27
  • @DanielHershcovich Agreed; I'd really love to commit to git (pun intended :p), but their development process seems all over the place. – DylanYoung Jul 21 '20 at 15:38
  • I agree it should issue a warning. It looks like [their mailing list is the correct place to report bugs](https://git-scm.com/community). I'm assuming that's true for enhancements as well, since they don't say anything to the contrary. You don't need to subscribe to the list in order to send to it, though. – Ian Dunn Sep 19 '20 at 15:44
44

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
}
wjandrea
  • 28,235
  • 9
  • 60
  • 81
stefansundin
  • 2,826
  • 1
  • 20
  • 28
  • If you have this Bash function in place then why do you even need a `cl` alias? – Gregory Pakosz Nov 08 '22 at 01:31
  • @GregoryPakosz It is not strictly necessary but I was using the alias before I made the bash function. You can put the arguments you need directly in the function, the only thing to keep in mind is that you need to reload your shell if you need to update the arguments. – stefansundin Nov 08 '22 at 05:22
  • @stefansundin Um.... why? Would one need to reload the shell to update the arguments, I mean? Are you saying that they'd persist between invocations? Or are you simply pointing out that if one wished to later modify the method (function) signature (the arguments accepted by the function)/behaviors that the function exhibits in response to being provided a given argument that they'd have to re-source the script? – NerdyDeeds Mar 09 '23 at 13:03
  • @NerdyDeeds Yes. – stefansundin Mar 10 '23 at 01:51
28

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.

David Mertens
  • 1,037
  • 8
  • 12
  • 7
    This should probably be the accepted answer. Replacing `git` with a hacked version is not a viable solution for many users. – tripleee Mar 29 '17 at 04:37
  • 2
    As an alternative to the alias, you can symlink `git` to your wrapper script in a directory early in your `PATH`. Make sure the script doesn't execute itself instead of the real `git`, obviously. – tripleee Mar 29 '17 at 04:43
  • 1
    Upping this for being the answer. People are most likely trying to create aliases. – Harrison Cramer Nov 04 '18 at 04:28
  • @tripleee This *is* replacing git with a hacked version, lol. It's just a question of what language you're hacking in, but it is true that many are more comfortable with BASH than C (for some unfathomable reason, probably fear of compilation). – DylanYoung Jul 21 '20 at 15:41
  • 1
    In some sense that's true, but having a `git` whose code differs from upstream's can become very problematic or outright untenable, whereas a simple wrapper which lets you pull updates from upstream at your convenience is usually reasonably robust, and avoids the inherent problems of a fork. – tripleee Jul 21 '20 at 15:57
22

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
Ciro Santilli OurBigBook.com
  • 347,512
  • 102
  • 1,199
  • 985
  • 1
    Sorry for the necropost, but I noticed a potential issue with the Mamano's perception of shell behaviour. An alias can be defeated a few ways. Do this: `$ alias ls='ls -aF'` And then compare these: `$ ls` `$ \ls` `$ /bin/ls` `$ builtin ls` First calls the alias, which points to the shell's built-in `ls` or external bin. Second bypasses the alias, directly calling the shell's built-in `ls` or external bin. Third bypasses the alias, by unambiguously calling an external binary `/bin/ls`. Fourth, presuming there is a built-in `ls`, imperatively calls that one. – masta Oct 19 '22 at 13:22
  • 1
    @masta Bro. Wow. You have no clue how long I've wanted to know this PRECISE information - or even if same was POSSIBLE. I, quite literally, stumbled across your answer here, and have already mentally updated 15 or 20 scripts that would have been MUCH better written had I this knowledge already. Thank you! I'm dead serious: THANK YOU! If you're ever in Kansas City, I owe you a beer. – NerdyDeeds Mar 08 '23 at 18:01
2

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" "$@"
Dabe Murphy
  • 101
  • 1
  • 5
1

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:

  • It is clear you are not calling the “vanilla” command.
  • If your prefix is long enough:
    • It does not modify your workflow
    • It is clear which command you’re subverting
  • The original command is still easily accessible
Cimbali
  • 11,012
  • 1
  • 39
  • 68