简体   繁体   中英

git alias fails when plain command succeeds

I have the following alias stored in my ~/.gitconfig file

reset-master = reset $(git merge-base master $(git rev-parse --abbrev-ref HEAD))

but when I run git reset-master it fails with

$ git reset-master
error: unknown option `abbrev-ref'
usage: git reset [--mixed | --soft | --hard | --merge | --keep] [-q] 
[<commit>]
   or: git reset [-q] [<tree-ish>] [--] <paths>...
   or: git reset --patch [<tree-ish>] [--] [<paths>...]

    -q, --quiet           be quiet, only report errors
    --mixed               reset HEAD and index
    --soft                reset only HEAD
    --hard                reset HEAD, index and working tree
    --merge               reset HEAD, index and working tree
    --keep                reset HEAD but keep local changes
    --recurse-submodules[=<reset>]
                          control recursive updating of submodules
    -p, --patch           select hunks interactively
    -N, --intent-to-add   record only the fact that removed paths will be added later

And running

git reset $(git merge-base master $(git rev-parse --abbrev-ref HEAD))

works perfectly fine. What am I doing wrong?

TL;DR

You need to use the ! form of a Git alias: !git reset $(...) , rather than the shorter form, !reset $(...) .

Long-ish

To understand the issue, we need to know that:

cmd1 $(cmd2 arg ...)

is actually handled by the shell , by running cmd2 with its arguments first . The output—more precisely, the standard output stream —from cmd2 goes back into the shell, which reads it and breaks it up into words, then passes those words to cmd1 . Hence:

wc $(seq -f f%g 1 3)

first runs seq -ff%g 1 3 :

$ seq -f f%g 1 3
f1
f2
f3

The shell reads these words, turns them into three arguments to pass to wc :

wc f1 f2 f3

(This example is a bit silly since we know that seq -ff%g 1 3 will always print those three names, so we could just run wc with those three names, but works for illustration.)

Git's aliases, by default, are not run through the shell:

reset-master = reset $(git merge-base master $(git rev-parse --abbrev-ref HEAD))

means that Git tries to pass the literal string $(git as the first argument to git reset . The second argument is merge-base , and so on. One of the arguments is --abbrev-ref , and that's the one git reset looks at first—it's scanning through all the arguments for options, before it tries to make sense of the rest of the line—so that's the one it complains about.

If we feed the entire string to a shell , however, we get the shell to expand each of the $(...) occurrences. The first one runs git rev-parse --abbrev-ref HEAD , which prints the name of the current branch (if any, or HEAD if we have a detached HEAD). This output gets fed back into the outer git merge-base command, to find a merge base commit between master and the named branch (or again HEAD ). With any luck, git merge-base prints one commit hash ID, which the shell substitutes in for the final command:

git reset <hash-id>

We can get Git to do this by writing:

reset-master = !git reset $(git merge-base master $(git rev-parse --abbrev-ref HEAD))

as a Git alias that starts with !means run the remaining text through the shell .

Note that if there is no merge base commit, this will run git reset , which is equivalent to git reset --mixed HEAD , which will re-set the index to match the HEAD commit. If there are multiple merge bases—this is rare but not impossible—Git will pick one at what looks like random. It might be nice to write a shell function, or a shell script, that uses git merge-base --all to verify that there is exactly one merge base, but the simpler alias will work for almost all real cases.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM