Git alias with positional parameters

2018-12-31 19:16发布

问题:

Basically I\'m trying to alias:

git files 9fa3

...to execute the command:

git diff --name-status 9fa3^ 9fa3

but git doesn\'t appear to pass positional parameters to the alias command. I have tried:

[alias]
    files = \"!git diff --name-status $1^ $1\"
    files = \"!git diff --name-status {1}^ {1}\"

...and a few others but those didn\'t work.

The degenerate case would be:

$ git echo_reverse_these_params a b c d e
e d c b a

...how can I make this work?

回答1:

The most obvious way is to use a shell function:

[alias]
    files = \"!f() { git diff --name-status \\\"$1^\\\" \\\"$1\\\"; }; f\"

An alias without ! is treated as a Git command; e.g. commit-all = commit -a.

With the !, it\'s run as its own command in the shell, letting you use stronger magic like this.

UPD
Because commands are executed at the root of repository you may use ${GIT_PREFIX} variable when referring to the file names in commands



回答2:

You can also reference sh directly (instead of creating a function):

[alias]
        files = !sh -c \'git diff --name-status $1^ $1\' -

(Note the dash at the end of the line -- you\'ll need that.)



回答3:

The alias you are looking for is:

files = \"!git diff --name-status \\\"$1\\\"^ \\\"$1\\\" #\"

With argument validation:

files = \"!cd -- \\\"${GIT_PREFIX:-.}\\\" && [ x$# != x1 ] && echo commit-ish required >&2 || git diff --name-status \\\"$1\\\"^ \\\"$1\\\" #\"

The final # is important - it prevents all the user-supplied arguments from being processed by the shell (it comments them out).

Note: git puts all user-supplied arguments at the end of the command line. To see this in action, try: GIT_TRACE=2 git files a b c d

The escaped (due to nesting) quotes are important for filenames containing spaces or \"; rm -rf --no-preserve-root /;)



回答4:

Use GIT_TRACE=1 described on the git man page to make the alias processing transparent:

$ git config alias.files
!git diff --name-status $1^ $1
$ GIT_TRACE=1 git files 1d49ec0
trace: exec: \'git-files\' \'1d49ec0\'
trace: run_command: \'git-files\' \'1d49ec0\'
trace: run_command: \'git diff --name-status $1^ $1\' \'1d49ec0\'
trace: exec: \'/bin/sh\' \'-c\' \'git diff --name-status $1^ $1 \"$@\"\' \'git diff --name-status $1^ $1\' \'1d49ec0\'
trace: built-in: git \'diff\' \'--name-status\' \'1d49ec0^\' \'1d49ec0\' \'1d49ec0\'
trace: run_command: \'less -R\'
trace: exec: \'/bin/sh\' \'-c\' \'less -R\' \'less -R\'
MM      TODO

Your original commands work with git version 1.8.3.4 (Eimantas noted this changed in 1.8.2.1).

The sh -c \'..\' -- and f() {..}; f options both cleanly handle the \"$@\" parameters in different ways (see with GIT_TRACE). Appending \"#\" to an alias would also allow positional parameters without leaving the trailing ones.



回答5:

As stated by Drealmer above:

« Be careful, ! will run at the root of the repository, so using relative paths when calling your alias will not give the results you might expect. – Drealmer Aug 8 \'13 at 16:28 »

GIT_PREFIX being set by git to the subdirectory you\'re in, you can circumvent this by first changing the directory :

git config --global alias.ls \'!cd \"${GIT_PREFIX:-.}\"; ls -al\'



回答6:

I wanted to do this with an alias that does this:

git checkout $1;
git merge --ff-only $2;
git branch -d $2;

In the end, I created a shell script named git-m that has this content:

#!/bin/bash -x
set -e

#by naming this git-m and putting it in your PATH, git will be able to run it when you type \"git m ...\"

if [ \"$#\" -ne 2 ]
then
  echo \"Wrong number of arguments. Should be 2, was $#\";
  exit 1;
fi

git checkout $1;
git merge --ff-only $2;
git branch -d $2;

This has the benefit that it\'s much more legible because it\'s on multiple lines. Plus I like being able to call bash with -x and set -e. You can probably do this whole thing as an alias, but it would be super ugly and difficult to maintain.

Because the file is named git-m you can run it like this: git m foo bar



回答7:

Just bumped into something similar; hope it\'s oK to post my notes. One thing that confuses me about git aliases with arguments, probably comes from the git help config (I have git version 1.7.9.5):

If the alias expansion is prefixed with an exclamation point, it will be treated as a shell command. For example, defining \"alias.new = !gitk --all --not ORIG_HEAD\", the invocation \"git new\" is equivalent to running the shell command \"gitk --all --not ORIG_HEAD\". Note that shell commands will be executed from the top-level directory of a repository, which may not necessarily be the current directory. [...]

The way I see it - if an alias \"will be treated as a shell command\" when prefixed with exclamation point - why would I need to use a function, or sh -c with arguments; why not just write my command as-is?

I still don\'t know the answer - but I think actually there is a slight difference in outcome. Here\'s a little test - throw this in your .git/config or your ~/.gitconfig:

[alias]
  # ...
  ech = \"! echo rem: \"
  shech = \"! sh -c \'echo rem:\' \"
  fech = \"! f() { echo rem: ; }; f \" # must have ; after echo!
  echargs = \"! echo 0[[\\\"$0\\\"]] 1-\\\"$1\\\"/ A-\"$@\"/ \"
  fechargs = \"! f() { echo 0[[\\\"$0\\\"]] 1-\\\"$1\\\"/ A-\"$@\"/ ; }; f \"

Here is what I get running these aliases:

$ git ech word1 word2
rem: word1 word2

$ git shech word1 word2
rem:

$ git fech word1 word2
rem:

$ git echargs word1 word2
0[[ echo 0[[\"$0\"]] 1-\"$1\"/ A-$@/ ]] 1-word1/ A-word1 word2/ word1 word2

$ git fechargs word1 word2
0[[ f() { echo 0[[\"$0\"]] 1-\"$1\"/ A-$@/ ; }; f ]] 1-word1/ A-word1 word2/

... or: when you\'re using a \"plain\" command after the ! \"as-is\" in a git alias - then git automatically appends the arguments list to that command! A way to avoid it, is indeed, to call your script as either a function - or as argument to sh -c.

Another interesting thing here (for me), is that in a shell script, one typically expects the automatic variable $0 to be the filename of the script. But for a git alias function, the $0 argument is, basically, the content of the entire string specifying that command (as entered in the config file).

Which is why, I guess, if you happen to misquote - in the below case, that would be escaping the outer double quotes:

[alias]
  # ...
  fail = ! \\\"echo \'A\' \'B\'\\\"

... - then git would fail with (for me, at least) somewhat cryptic message:

$ git fail
 \"echo \'A\' \'B\'\": 1: echo \'A\' \'B\': not found
fatal: While expanding alias \'fail\': \' \"echo \'A\' \'B\'\"\': No such file or directory

I think, since git \"saw\" a whole string as only one argument to ! - it tried to run it as an executable file; and correspondingly it failed finding \"echo \'A\' \'B\'\" as a file.

In any case, in context of the git help config quote above, I\'d speculate that it\'s more accurate to state something like: \" ... the invocation \"git new\" is equivalent to running the shell command \"gitk --all --not ORIG_HEAD $@\", where $@ are the arguments passed to the git command alias from command line at runtime. ... \". I think that would also explain, why the \"direct\" approach in OP doesn\'t work with positional parameters.