user541686
user541686

Reputation: 210427

Why does quoting arguments not do anything in git aliases?

I have the following ~/.gitconfig:

[alias]
    print = !printf %s\\\\n a b c

It behaves as follows:

$ git print
a
b
c

But now I change it to the following ~/.gitconfig:

[alias]
    print = !printf %s\\\\n "a b" c

and yet the a and b still undergo word splitting:

$ git print
a
b
c

If the "" quotes aren't preventing word-splitting then what are they doing?
How are the commands parsed?

Upvotes: 1

Views: 307

Answers (1)

torek
torek

Reputation: 488103

Git's configuration-language parser uses double quotes for its own purposes:

... Doublequote " and backslash can be included by escaping them as \" and \\, respectively. Backslashes preceding other characters are dropped when reading; for example, \t is read as t and \0 is read as 0 ...

While this particular paragraph is discussing subsection names, the double-quote and double-backslash rules apply outside subsection names as well. The rest of these quotes apply everywhere, except for the \t=t kind of thing in subsection names:

Inside double quotes, double quote " and backslash \ characters must be escaped: use \" for " and \\ for \.

The following escape sequences (beside \" and \\) are recognized: \n for newline character (NL), \t for horizontal tabulation (HT, TAB) and \b for backspace (BS). Other char escape sequences (including octal escape sequences) are invalid.

Note that none of this has anything to do with aliases specifically: these rules apply to all lines in a .gitconfig or .git/config file. (This matters in, e.g., submodule paths, though one wouldn't generally encode a double quote in one.)

Now, after the configuration parser is done reading the .git/config file, if the line is in an alias section, it defines an alias. Within that alias, double quotes do prevent word-splitting:

[alias]
    foo = log \"a b\"

$ git foo
fatal: ambiguous argument 'a b': unknown revision or path not in the working tree.

Note that to get past the configuration parser in the first place, these had to be given with backslashes. To avoid that particular annoyance we can use single quotes instead:

[alias]
    foo = log 'a b'

produces the same result.

If the alias is a shell alias (prefixed with !), the entire string (minus the exclamation point of course) is fed to the shell. To observe that, use GIT_TRACE:

[alias]
    foo = !printf %s\\\\n 'a b'

$ GIT_TRACE=1 git foo
23:15:46.947801 git.c:670               trace: exec: git-foo
23:15:46.948153 run-command.c:643       trace: run_command: git-foo
23:15:46.948883 run-command.c:643       trace: run_command: 'printf %s\\n '\''a b'\'''
a b

If we get double quotes past the configuration parser, they have the same effect:

$ GIT_TRACE=1 git foo
23:16:24.919042 git.c:670               trace: exec: git-foo
23:16:24.919402 run-command.c:643       trace: run_command: git-foo
23:16:24.920114 run-command.c:643       trace: run_command: 'printf %s\\n "a b"'
a b

The reason we need four backslashes is, of course, that the Git configuration parser turned them into two backslashes (as shown in the GIT_TRACE=1 output) and the shell itself then turned them into one backslash, which printf combined with the n to make a newline.

Attempting the alias:

    foo = !printf %s\\\\n "a b

fails with:

fatal: bad config line 30 in file [path]/.gitconfig

because Git itself is running its line-by-line parser and is unhappy with the unclosed quote.

Upvotes: 7

Related Questions