I'm using Git version 2.37.3.windows.1 on Windows 10. From reading the discussion at Git alias with positional parameters and the Git Wiki, I've learned a couple of things about Git aliases:
I can invoke a shell command using something like this. The trailing - is so that the the CLI parameters start with $1 and not $0.
example = !sh -c 'ls $2 $1' -
I can also use this form. The trailing # is to "ignore" the CLI argument, which will be repeated at the end.
example = "!ls #2 #1 #"
But that all leaves me with some additional questions. Most importantly, where is all this documented? I've read the git-config, documentation, but it only mentions a couple of things, like the use of the exclamation mark.
- In the context of Git aliases, which "shell" is being invoked by
sh -cand!? Does this invoke the OS-specific shell in use (e.g. PowerShell on Windows), or is this some Git built-in Bash shell that is allows consistent behavior across platforms? (For example, do I use PowerShell quoting rules, or Bash quoting rules?) - With
sh -c, apparently a trailing-is needed to make parameters start with$1instead of$0. But is this also needed for the!syntax? Why or why not? And where is this documented? - Where is it documented the use of the trailing
#to ignore the "duplicated" argument(s) from the command line?
Summary
Git requires a POSIX shell. That's why Git-for-Windows comes with git-bash, which is a port of bash that runs on Windows. When using Git on Windows, Git is configured to invoke that bash in its "POSIX shell" mode as much as possible.
Long
It's not. It is all left as implied. When Git expands an alias, if the alias begins with
!, Git tacks on all the arguments, as you noted. It then passes the result to/bin/shwith some flags. For the remainder of the exercise, you must know how/bin/shworks, or consult its documentation.This means that you want:
(not
#1 #2 #) or, arguably somewhat better:Quoting in such Git aliases gets very messy because:
.gitconfigfile is parsed by Git's configuration reader, which eats one layer of quotes and backslashes; and then!invokes/bin/sh, unless your Git was built withSHELL_PATHset to some other value at compile time.shinvokes whatever command the shell that invokes finds assh. Normally that would also be/bin/sh, but it depends on your$PATHsetting.In order: correct, no, and "in the /bin/sh documentation". Running:
(assuming
shinvokes/bin/shas usual) produces:What happened to
foo? It's in$0:If you run
shwithout-c, the argument in that position is treated as a file path name:Note that since there is no
-coption,$0is the literal textecho "$0", andfoohas become$1.With
sh -c, however, the argument after-cis the command to run, and then the remaining positional arguments become the$0,$1etc values. Git passes the entire expanded alias and its arguments tosh -c, so:runs:
as we can see by setting
GIT_TRACE=1(but here I replaced "ls" with "echo" and took out the comment#character):Now we can see (sort of) why we want to put double quotes around
$1and the like:$1is at this point the literal string argument one. However, using it in a command, as inecho $1, subjects$1to the shell's word splitting (based on what's in$IFSat this point). So this becomes two arguments, argument and one, to theechoor other command. By quoting$1and$2, we keep them as single arguments. That's why we want:Again, that's in the shell documentation.
Now, the reason to use a shell function, such
f, is that we get much better control. It doesn't really change anything fundamentally, it's just that any variables we set inside the function can be local to that function, if we like, and we can write other functions and call them. There's nothing you can't do without them, and for simple cases like this one, there's no actual advantage to using shell functions. But if you're going to write a complex shell script for Git, you probably want to make use of shell functions in general, and write it as a program that Git can invoke, rather than as an alias.For instance, you can create a shell script named
git-exampleand place it somewhere in your own$PATH. Runninggit example 'argument one' more argswill invoke your shell script with three arguments:$1set to argument one,$2set to more, and so on. There are no horrible quoting issues like there are with Git aliases (there are only the regular horrible quoting issues that all shell scripts have!).Note that Git alters
$PATHwhen it runs your program so that its library of executables, including shell script fragments that you cansourceor.to give you various useful shell functions, is there in$PATH. So your shell script can include the line:and get access to a bunch of useful functions. Use
git --exec-pathto see where these programs live.