By user400575


2010-07-23 19:03:23 8 Comments

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?

7 comments

@Tom Hale 2016-09-16 03:54:26

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 /;)

@Ed Randall 2017-03-08 08:08:52

For the simplest cases this is the right answer, there's really no need to complicate by wrapping it in a function or sh -c.

@Tom Hale 2017-03-09 01:18:48

Yes, ! already implies sh -c (shown when prepending GIT_TRACE=2), so there's no need to run a another sub-shell. What issues do you see in more complicated cases?

@gib 2017-08-12 19:17:22

Does this work if you want to set default arguments? e.g. I want to do this to fetch a Github PR: fp = "! 1=${1:-$(git headBranch)}; 2=${2:-up}; git fetch -fu $2 pull/$1/head:$1; git checkout $1; git branch -u $2 #". This works great without the first two statements, but falls down if you use them. (I have headBranch = symbolic-ref --short HEAD as well).

@gib 2017-08-12 19:31:14

Worked it out, it works if you set new params, so this is fine: fp = "! a=${1:-$(git headBranch)}; b=${2:-up}; git fetch -fu $b pull/$a/head:$a; git checkout $a; git branch -u $b #".

@Eugen Konkov 2018-03-04 13:22:55

why " quotes are required?

@Tom Hale 2018-11-26 07:05:22

@EugenKonkov Quotes are needed since the expansion of variables could contain spaces, and we want to keep them as a single shell token.

@akuhn 2019-02-22 04:12:01

This is amazing, thank you Tom! I just used this to define my on git cat command, see stackoverflow.com/a/54819889/24468

@Pierre-Olivier Vares 2014-02-26 10:47:26

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'

@waldyrious 2016-04-04 17:43:33

I'm having trouble with this as well (commands being run at the root of the repository) but this solution doesn't seem to do anything. (If it matters, I'm using OS X.)

@Pierre-Olivier Vares 2016-04-06 13:45:11

Oops... git alias is an alias I made.

@Pierre-Olivier Vares 2016-04-06 13:46:53

(since git 1.8.2) git config --set alias.alias = '! git config --global alias.$1 "$2"'

@waldyrious 2016-04-06 14:52:27

This is what ended up working for me: "prefix your git aliases (that run shell commands and need the right pwd) with cd ${GIT_PREFIX:-.} &&." (source: stackoverflow.com/a/21929373/266309)

@mirabilos 2018-03-02 23:53:49

Do quote this. !cd "${GIT_PREFIX:-.}" && ls -al

@Cascabel 2010-07-23 21:13:56

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

@user400575 2010-07-23 23:20:42

Thanks, this looks exactly right: [alias] files = "!f() { echo $3 $2 $1; }; f" ; $ git files a b c => c b a

@Kohányi Róbert 2011-10-19 07:38:17

@jefromi @mipadi I'm not a hotshot shell script writer; can you elaborate on why the ! is needed at beginning of the function definition? Thanks!

@Cascabel 2011-10-19 15:04:34

@KohányiRóbert: That's actually not a shell script question; that's a particular of git config. 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.

@Kohányi Róbert 2011-10-20 06:33:59

@Jefromi Ah! That's really good to know! I've stumbled upon this problem, where creating a new git alias for an already defined alias (like sta = status and st = sta) would fail with some error message like: "xy isn't a git command". Thanks for the info!

@blockloop 2013-02-11 15:29:33

HA. I came here trying to accomplish your "files" function. Wish I could up vote twice since you answered my initial question as well as the one I didn't even ask...

@Drealmer 2013-08-08 16:28:42

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.

@void.pointer 2014-06-23 13:52:48

This solution breaks tab completion for branch names.

@Cascabel 2014-06-23 16:08:32

@RobertDailey It doesn't break it, it just doesn't implement it. See stackoverflow.com/questions/342969/… for how to add it.

@Tom Hale 2016-09-16 03:56:47

Note: This doesn't quote arguments (which is dangerous in general). Also, a function is unnecessary. See my answer for more explanation.

@petre 2018-12-11 17:36:07

With no arguments this borks fatal: bad revision '^'. What you would expect it do is a matter of taste. Arguably, you'd want it run on HEAD. This would achieve that: files = "!f() { git diff --name-status \"${1:-HEAD}\"^ \"${1:-HEAD}\"; }; f". Alternatively, and possibly saner, you want to run it against the working tree: files = "!f() { [ -n \"$1\" ] && git diff --name-status \"$1\"^ \"$1\" || git diff --name-status; }; f".

@mipadi 2010-07-24 21:22:48

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.)

@user400575 2010-07-26 16:19:57

...are there any tangible benefits as compared to jefromi's answer? I mean: the function def in his answer is "alias-local" and means you don't have bash calling bash, right? In any case thanks for the alternative implementation.

@mipadi 2010-07-26 16:56:39

I don't think either solution really has benefits over the other -- just two different ways to do the same thing.

@Jay Levitt 2011-11-17 18:46:38

I suspect jefromi's solution is better if you want to share the aliases, or use multiple shells; what if sh doesn't call your current shell?

@nomothetis 2012-12-06 16:20:21

If you're sharing the command, you probably want to use sh, since that is in itself a shell, and it's available on the vast majority of systems. Using the default shell only works if the command works as written for all shells.

@bsb 2013-08-26 00:28:45

I prefer -- to - as it's more familiar and less likely to accidentally mean stdin at some point. ("An argument of - is equivalent to --" in bash(1) is ungoogleable)

@user456814 2014-06-24 01:31:42

@user151841 2015-10-29 13:55:32

I've tried both - and -- and gotten the two-line error sh: -c: line 0: unexpected EOF while looking for matching '' sh: -c: line 1: syntax error: unexpected end of file with bash 4.2.1 on cygwin.

@Zitrax 2016-04-21 13:24:39

What is the exact meaning of the ending '-' and where is it documented ?

@Tom Hale 2016-09-16 04:07:35

Note: This doesn't quote arguments (which is dangerous in general). Creating a sub-shell (with sh -c) is also unnecessary. See my answer for an alternative.

@mirabilos 2018-03-02 23:48:19

Note that this creates an extra copy of the shell (so two in total); additionally, FreeBSD’s (and their derivatives’) sh has broken -- handling (and - is plain wrong) for compatibility reasons. I’d agree your best bet is to write the alias in the form from the accepted answer and assume the user’s shell is POSIX sh compatible enough.

@mirabilos 2018-03-02 23:54:11

Edit: this answer beats them all, though. Do also mind this caveat.

@Daniel Kaplan 2014-06-21 03:27:21

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

@Hassek 2017-05-11 22:57:45

I like this a lot more too, but I haven't been able to figure out how to use the autocomplete I want with this approach. On aliases you can do this: '!f() { : git branch ; ... }; f' and it will autocomplete the alias as a branch which is super handy.

@thecoshman 2018-11-05 16:11:38

Yeah, I think I prefer having the non-trivial things done as individual script files on the path. The down side though is yes, you loose automatic completion of things like references. You can though fix this up by manually configuring your own auto-completion. Again though, I like that you can just drop a script into a folder on the path and it will start working, but for the auto-completion, you need to 'load' it, so usually it's in my .bashrc file that I source. But I don't think I change how I auto-complete arguments to a script as much as the script itself, and it'd only be during dev.

@bsb 2013-08-26 01:27:01

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 "[email protected]"' '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 "[email protected]" parameters in different ways (see with GIT_TRACE). Appending "#" to an alias would also allow positional parameters without leaving the trailing ones.

@user2291758 2015-04-21 12:19:11

thanks for the explanations: those commands work for me on the original problem, following your advice: files = "!git diff --name-status $1^ $1 #" files = "!git diff --name-status $1^"

@sdaau 2013-04-22 20:10:40

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-"[email protected]"/ "
  fechargs = "! f() { echo 0[[\"$0\"]] 1-\"$1\"/ A-"[email protected]"/ ; }; 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"/ [email protected]/ ]] 1-word1/ A-word1 word2/ word1 word2

$ git fechargs word1 word2
0[[ f() { echo 0[["$0"]] 1-"$1"/ [email protected]/ ; }; 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 [email protected]", where [email protected] 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.

@albfan 2013-04-27 12:11:58

nice test. A quick way to check all possibilities!

@bsb 2013-08-25 22:41:27

fail is trying to run a command called "echo 'A' 'B" (ie. 10 chars long). Same error from sh -c "'echo a b'" and same cause, too many layers of quotes

Related Questions

Sponsored Content

30 Answered Questions

[SOLVED] How do I check out a remote Git branch?

42 Answered Questions

[SOLVED] How do I revert a Git repository to a previous commit?

42 Answered Questions

[SOLVED] How do I force "git pull" to overwrite local files?

39 Answered Questions

[SOLVED] How do I delete a Git branch locally and remotely?

45 Answered Questions

[SOLVED] What is the difference between 'git pull' and 'git fetch'?

81 Answered Questions

[SOLVED] How do I undo the most recent local commits in Git?

35 Answered Questions

[SOLVED] How to remove local (untracked) files from the current Git working tree

  • 2008-09-14 09:06:10
  • Readonly
  • 2149275 View
  • 6576 Score
  • 35 Answer
  • Tags:   git branch git-branch

30 Answered Questions

[SOLVED] How do I rename a local Git branch?

33 Answered Questions

[SOLVED] How do I undo 'git add' before commit?

13 Answered Questions

[SOLVED] How do I show the changes which have been staged?

Sponsored Content