245

I am trying to create an alias that uses both multiple Git commands and positional parameters. There are Stackoverflow pages for each, and it would appear painfully obvious to do both, but I am having trouble.

As an example, I want to switch to branch foo and perform a status. So in my .gitconfig, I have:

  [alias] 
     chs = !sh -c 'git checkout $0 && git status'

which doesn't work. Whereas something like this will work.

chs = !sh -c 'git checkout $0'

echoes = !sh -c 'echo hi && echo bye'

Any insight would be appreciated.

montrealist
  • 5,593
  • 12
  • 46
  • 68
Stella
  • 2,453
  • 2
  • 14
  • 5

12 Answers12

196

This will work (tested with zsh and bash):

[alias] chs = !git checkout $1 && git status
Olivier Verdier
  • 46,998
  • 29
  • 98
  • 90
  • 2
    No it won't. Git will transform `git chs foo` into `git checkout && git status foo` – Lily Ballard Sep 23 '11 at 20:13
  • 2
    When I try that, I get pathspec error ('$1' did not match any file(s) known to git) – Stella Sep 23 '11 at 20:13
  • I tested it, it works perfectly... perhaps it depends on your shell? (I use zsh) – Olivier Verdier Sep 23 '11 at 20:19
  • 31
    Interesting, git actually does fill in the positional variables now in shell aliases. But it's still broken, because it also tacks them on as arguments. An alias of `echo $1 && echo done`, when invoked with the argument 'foo', will output both "foo" and "done foo". – Lily Ballard Sep 23 '11 at 20:37
  • 4
    Wow... Unfortunately, this was all a version problem. I was using Git 1.7.3, and neither of these methods worked. I updated to 1.7.6 and voila, everything worked. Thanks guys! – Stella Sep 23 '11 at 20:50
  • 1
    Works for me also, git version 1.7.4.4 – bitops Feb 05 '12 at 01:23
  • 9
    What is the preceding exclamation point for on the first invocation of `git`? – Elijah Lynn Jul 29 '13 at 22:44
  • 39
    @ElijahLynn: In a git alias, a leading `!` means pass the whole thing to the shell. Otherwise, it assumes you're trying to run another git command and passes it as arguments to the `git` binary. – Lily Ballard Feb 13 '14 at 19:32
  • 2
    Comment also added as an answer, please shout if I should remove one post or the other. In a windows environmend (cmd line, or msysgit bash) you can append `&& :` to your alias, and that will consume the additional insertion of the parameter (turns it into a location tag, which isn't printed.) Don't know whether this, or some equivalent will work in other environments. – Brondahl Sep 18 '14 at 14:24
  • 3
    @Brondahl Clever. I'd recommend `;:` instead of `&& :` though. And that works just fine on unix as well. – Lily Ballard May 01 '15 at 23:23
130

This targets Windows batch / msysgit bash; might not work on other environments.

As Olivier Verdier and Lily Ballard have said

[alias] chs = !git checkout $1 && git status

almost works, but gives a spurious extra insertion of the argument ...

git chs demo -> git checkout demo && git status demo

But if you add && : to the end of your alias, then the spurious argument is consumed into a location tag.

So

[alias] chs = !git checkout $1 && git status && :

gives the correct output ... git chs demo -> git checkout demo && git status

Brondahl
  • 7,402
  • 5
  • 45
  • 74
  • 17
    The `&& :` is gold and makes this solution work for commands where the extra argument would be problematic. – Clay Feb 26 '15 at 23:24
  • 6
    @Clay (or anyone else) - Could someone explain to the BASH-challenged what `&& :` does? – Justin Morgan - On strike Jun 07 '16 at 14:45
  • 13
    @JustinMorgan && means if the previous command turns 0 (success), then run the command after &&. ':', the colon, is a shell builtin command, which does nothing beyond expanding arguments and performing redirections and return the status 0. Here're some usages: 1. `a=123;$a` errors, but `a=123; : $a` does not. 2. `: > hello.txt` empties hello.txt. 3. `if [ "$a" = "hello" ];then : ;fi` runs okay but errors without ':'. It's like `pass` in python. 4. `: this is a comment`, the colon followed by space works as `#` in a comment line. – ElpieKay Jun 08 '16 at 09:33
  • 6
    this is such a hack... git should accept multiple command mode – kchoi Sep 17 '16 at 00:02
  • 1
    @kchoi So true :) And yet it appears to be a well-valued hack by the community :D – Brondahl Mar 23 '17 at 11:03
  • 1
    if you use quotes, ending an alias with a comment is better: `[alias] chs = !"git checkout $1 && git status #"` - it will turn extra arguments into comments that won't do a thing – Bernardo Dal Corno Nov 14 '19 at 06:35
  • 1
    Thanks @ElpieKay for the explanation. My local git alias for one proejct was `co = !git checkout public/compiled && git checkout $1` and when I'd do `git co master` I'd get the message `error: pathspec 'master' did not match any file(s) known to git.`. I didn't think this answer would be useful for me because the first thing it mentions is Windows and I'm on Linux. But you explained why. – Tyler Collier Feb 07 '20 at 21:04
  • Does git v2.2x still have these issues? – Devin Rhode Feb 08 '21 at 16:55
  • @DevinGRhode No idea. Why don't you test it and tell us? – Brondahl Feb 08 '21 at 16:56
94

You can define a shell function.

[alias] chs = "!f(){ git checkout \"$1\" && git status; };f"
Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • I saw this on another stackoverflow page, but my cygwin terminal says that the function is not recognized when I try to run it. – Stella Sep 23 '11 at 20:16
  • @Stella: I left a closing quote in there that's not useful this config file syntax. Make sure you didn't have it. – Lily Ballard Sep 23 '11 at 20:36
  • 2
    Wow... Unfortunately, this was all a version problem. I was using Git 1.7.3, and neither of these methods worked. I updated to 1.7.6 and voila, everything worked. Thanks guys! – Stella Sep 23 '11 at 20:49
  • @Stella: That sounds very strange. AFAIK the git alias stuff hasn't changed in quite a long time. I can't think of any reason whatsoever why upgrading from 1.7.3 to 1.7.6 would cause a simple shell alias to start working. But I guess I shouldn't question something that worked ;) – Lily Ballard Sep 23 '11 at 22:12
  • 5
    if using Windows I think you have to surround the shell function definition with double-quotes `"` – drzaus Feb 13 '14 at 15:05
  • 5
    Olivier's answer didn't work for me using parameters (OSX). This worked perfectly. – Ohad Schneider Nov 06 '14 at 11:45
  • Huh, I wonder why it differs. I'll go ahead and update my answer to include the quotes. And it looks like `git config` does quote the value by default when it writes it in the first place. – Lily Ballard May 01 '15 at 23:21
  • Great,thanks, other solutions don't work, but this does: [alias] reset-hard = "!f(){ git reset --hard origin/master && git clean -fd; };f" – boardtc Nov 17 '22 at 14:32
  • Man, I just lost 20 minutes to solving this. The point is to use SINGLE ' and not double " quotes to make it work. – Vedran Mandić Dec 23 '22 at 12:18
  • The command I was trying to run gave `cannot update paths and switch to branch at the same time` when I used Oliver or Brondal's answers, but this worked fine. – CoatedMoose May 08 '23 at 20:54
38

I was able to create multi-line and quite complex git aliases. They work fine on Windows but I assume they'd work elsewhere too, for example:

safereset = "!f() { \
                trap 'echo ERROR: Operation failed; return' ERR; \
                echo Making sure there are no changes...; \
                last_status=$(git status --porcelain);\
                if [[ $last_status != \"\" ]]; then\
                    echo There are dirty files:;\
                    echo \"$last_status\";\
                    echo;\
                    echo -n \"Enter Y if you would like to DISCARD these changes or W to commit them as WIP: \";\
                    read dirty_operation;\
                    if [ \"$dirty_operation\" == \"Y\" ]; then \
                        echo Resetting...;\
                        git reset --hard;\
                    elif [ \"$dirty_operation\" == \"W\" ]; then\
                        echo Comitting WIP...;\
                        git commit -a --message='WIP' > /dev/null && echo WIP Comitted;\
                    else\
                        echo Operation cancelled;\
                        exit 1;\
                    fi;\
                fi;\
            }; \
            f"

I wrote a post and have a few more examples here.

VitalyB
  • 12,397
  • 9
  • 72
  • 94
  • 3
    one imrovement to this would be to add `!f() { : reset` to get completions from reset command https://github.com/git/git/blob/master/contrib/completion/git-completion.bash#L25 – a user May 01 '15 at 19:19
  • Great job! What licence is that article published under? Would you mind if I translate parts of it for StackOverflow in Russian? – Nick Volynkin Jul 24 '15 at 09:36
  • @NickVolynkin Sorry about late reply. Thank you and of course, go ahead :) – VitalyB Sep 30 '15 at 15:14
  • @NickVolynkin, did you translate it eventually? – Alexander Malakhov Feb 05 '21 at 10:11
  • For something like this, it may be easier to maintain as a shell script. Then, your alias would be something like `safereset = !bash ~/safereset.sh` where `bash` is whatever shell you want, or just `safereset = !~/safereset.sh` if the script is executable and you want to run it in your user's login shell. Using an absolute path is probably a good idea, though `~` appears to be handled just fine. – Shibumi Aug 09 '22 at 15:58
28
[alias]
chs = !git branch && git status
FractalSpace
  • 5,577
  • 3
  • 42
  • 47
  • 13
    What is the `!` for? – AutonomousApps Feb 12 '18 at 18:20
  • 5
    I couldn't find doc for the `!` but as far as I can see git by default will assume that the alias is a git command so `t = status` will work. However if you try `t = git status` it won't work (will say "git" command not found). So that where the `!` comes in, it tells it to run the command as if it was a shell, and normally you need this when you have multiple git commands per alias, so you can have `t = !git status && git log` for example and it will work. – laurent Jun 05 '20 at 10:15
  • 1
    Here's a StackOverflow question that deals with exclamation points (!) in Git aliases: https://stackoverflow.com/questions/21083933/what-does-the-exclamation-mark-mean-in-git-config-alias. – Jacob Lockard Jul 08 '20 at 13:46
  • 2
    If the alias expansion is **prefixed with an exclamation point**, it will be treated as a **shell command**. https://git-scm.com/docs/git-config – Rohim Chou Oct 27 '20 at 01:25
11
[alias]
      chs = !git checkout && git status
      ac = !git add . && git commit -m 

what is ! ?

If the alias expansion is prefixed with an exclamation point, it will be treated as a shell command. What does the exclamation mark mean in git config alias?

To call alias from .gitconfig file

git chs
git ac "write_your_commit_message" 

alias is more useful for add and commit in git you can do more fast Gif show more datails

Eng_Farghly
  • 1,987
  • 1
  • 23
  • 34
7

Try this one:

[alias]
    chs = "!sh -c 'git checkout \"$0\" && git status'"

Call it like this: git chs master

brocksamson
  • 812
  • 1
  • 9
  • 17
7

It's possible to have multiline git alias by appending \ at the end of each line.

[alias] 
   chs = "!git checkout $1 \ 
          ; git status     \
         "
gmarik
  • 156
  • 1
  • 2
  • 2
    Thanks! I actually had no problem using `"!git fetch --prune --all; git pull --rebase upstream master; git push origin master"` for my alias. – yurisich Jul 09 '14 at 19:57
5

The problem here is that the positional parameters seem to be getting sent to the shell command twice (as of git 1.9.2). To see what I mean, try this:

[alias]
  test = !git echo $*

Then, do git test this is my testing string. You should observe the following output (last two lines edited here for clarity):

03:41:24 (release) ~/Projects/iOS$ git test this is my testing string
this is my testing string this is my testing string
^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
          #1                         #2

One way to work around this would be to

[alias]
  chs = !git checkout $1 && git status && git echo x >/dev/null

This will consume the extra positional parameter as it gets applied to that last echo command and have no effect on the results.

Ben Collins
  • 20,538
  • 18
  • 127
  • 187
  • Your example will expand to: git echo this is my testing string this is my testing string Adding a # to the end will fix it (don't forget the double quotes around the command alias). – imme Sep 14 '20 at 18:05
  • in .gitconfig: a0 = "!echo $*" and a1 = "!echo $* #" . To test them: git a0 hallo daar ; git a1 hallo daar – imme Sep 14 '20 at 18:07
  • @imme What does the # at the end do? It worked for me but I'd like to understand it. – Kramer Mar 16 '22 at 16:23
  • See this "demo" https://stackoverflow.com/a/63890372/165330 (it seems that git adds the arguments after replacing the alias with the defined alias, using $* in the alias already puts the arguments in there, so the rest should be ignored, which happens with the # at the end, making the rest of the command a comment. – imme Mar 16 '22 at 16:32
3

An example for people who want to try out what different aliases do.

Putting this in the alias-section of GIT's configuration-file (e.g. ~/.gitconfig) :

[alias]
    a0 = "!echo $*"
    a1 = "!echo $* #"
    a2 = "!f () { echo \"$*\"; }; f "
    a3 = "!f () { echo \"$*\"; }; f #"
    a4 = "!f () { echo \"$*\"; }; f \"$*\" #"
    a5 = "!f () { echo \"$*\"; }; f \"$@\" #"
    a6 = "!f () { echo \"$*\"; }; f \"$1\" #"

And then executing this command:

cat ~/.gitconfig | grep --extended-regexp -- '(a[0-9])|(alias)' ; \
echo "" ; \
export CMD ; \
for I in {0..6} ; \
do \
    CMD="a""${I}" ; \
    echo -n "Executing alias.${CMD} = " ; \
    git config --global alias."${CMD}" ; \
    git $CMD 'hoi daar' en nu ; \
    git $CMD hoi daar en nu ; \
    echo "" ; \
done ; \
unset CMD ;

Should give this as output:

[alias]
    a0 = "!echo $*"
    a1 = "!echo $* #"
    a2 = "!f () { echo \"$*\"; }; f "
    a3 = "!f () { echo \"$*\"; }; f #"
    a4 = "!f () { echo \"$*\"; }; f \"$*\" #"
    a5 = "!f () { echo \"$*\"; }; f \"$@\" #"
    a6 = "!f () { echo \"$*\"; }; f \"$1\" #"

Executing alias.a0 = !echo $*
hoi daar en nu hoi daar en nu
hoi daar en nu hoi daar en nu

Executing alias.a1 = !echo $* #
hoi daar en nu
hoi daar en nu

Executing alias.a2 = !f () { echo "$*"; }; f 
hoi daar en nu
hoi daar en nu

Executing alias.a3 = !f () { echo "$*"; }; f #



Executing alias.a4 = !f () { echo "$*"; }; f "$*" #
hoi daar en nu
hoi daar en nu

Executing alias.a5 = !f () { echo "$*"; }; f "$@" #
hoi daar en nu
hoi daar en nu

Executing alias.a6 = !f () { echo "$*"; }; f "$1" #
hoi daar
hoi
Devin Rhode
  • 23,026
  • 8
  • 58
  • 72
imme
  • 598
  • 1
  • 9
  • 22
1

This is an old post, but no one has provided what I think is the simplest solution. This works in my *nix-based CLI, your mileage may vary.

[alias]
    chs = "!git checkout $1; git status"

A simple semi-colon (;) is all you need to run multiple git commands.

Mageician
  • 2,918
  • 9
  • 43
  • 69
0

As far as Lily Ballard have a good answer with a shell function, it looks better when we pipe all the alias arguments:

[alias] chs = "!f(){ git checkout \"${@:1}\" && git status; };f"
crowx
  • 1
  • 1