Basically I\'m trying to alias:
git files 9fa3
...to execute the command:
git diff --name-status 9fa3^ 9fa3
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.
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.
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.)
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
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'
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 /;
)