JJ Zabkar
JJ Zabkar

Reputation: 3689

GIt Config Alias With Quotes and Pipes via Command Line

I want to create a sh script that configures all my git aliases. Some of the aliases have pipes (|) and doublequotes ("). The output I want to see in my ~/.gitconfig file is:

[alias]
    assume = update-index --assume-unchanged
    unassume = update-index --no-assume-unchanged
    assumed = "!git ls-files -v | grep ^h | cut -c 3-"

However, running the following three commands yields an incorrect assumed entry:

# setup git aliases per: http://blog.apiaxle.com/post/handy-git-tips-to-stop-you-getting-fired/
git config --global alias.assume "update-index --assume-unchanged"
git config --global alias.unassume "update-index --no-assume-unchanged"
git config --global alias.assumed '"!git ls-files -v | grep ^h | cut -c 3-"'

The third alias (assumed) has undesired backslashes:

assumed = \"!git ls-files -v | grep ^h | cut -c 3-\"

What is the correct syntax to configure the alias via command line?

Upvotes: 10

Views: 3597

Answers (2)

Jonathan Komar
Jonathan Komar

Reputation: 3096

Fun with escaping characters

Escaping can be strange when you introduce subshells (using "!...") as alias values inside the git config aliases. The problem is compounded when you want to write safe shell scripts that should quote things. The problem is compounded more when you want lazy/delayed execution of commands (which involves representing evaluating/expanding strings to strings, then interpreting those strings e.g. eval, subshell, or otherwise). Here is an example, albeit slightly contrived, just to demonstrate that it is not straightforward.

  • Notice that \\" will not work, because the first backslash acts as an escape character for the second backslash. This leaves a remaining " which actually is bad syntax because it closes the whole alias in INI format (it is the same as just trying to write ".
  • Notice the \\\" to achieve a single " literal in the subshell.
  • Notice the \\> to achieve a ">" character, but only when surrounded by literal quotes in the subshell, otherwise > works. (> is a special char to the shell interpreter: the redirect operator, so depending on context e.g. eval echo "\>", echo \>, and eval echo \\\> vs. a syntax error: eval echo \>)
  • Notice the \"$cmd\", whereby the quotation marks will be passed to the shell by git, then the shell will swallow them as part of the normal "lexical word splitting".

.config/git

[alias]
  escaping-characters-example = "!effect() { \
    local timestamp=$(date +'%Y-%m-%dT%H-%M-%S'); \
    local branch=${1-feat/NOISSUE/${timestamp}}; \
    local path=${2-${timestamp}}; \
    local base=${3-origin/main}; \
    local cmd='git worktree add -b \"${branch}\" \"${path}\" \"${base}\"'; \
    echo \\\"Fully surround me quotes ==\\> $cmd\\\"; \
    echo \"After arrow quoted ==> \\\"$cmd\\\"\"; \
    echo \"Suitable for lazy eval ==> $cmd\"; \
    printf \"Print unexpanded cmd to pass to eval: \" && echo $cmd; \
    printf \"Eval (expanded value; eval already applied split words lexically; quotes have been swallowed) ==> \" && eval echo $cmd; \
    }; effect"

Usage

$ git escaping-characters-example
"Fully surround me quotes ==> git worktree add -b "${branch}" "${path}" "${base}""
After arrow quoted ==> "git worktree add -b "${branch}" "${path}" "${base}""
Suitable for lazy eval ==> git worktree add -b "${branch}" "${path}" "${base}"
Print unexpanded cmd to pass to eval: git worktree add -b "${branch}" "${path}" "${base}"
Eval (expanded value; eval already applied split words lexically; quotes have been swallowed) ==> git worktree add -b feat/NOISSUE/2024-04-03T09-30-22 2024-04-03T09-30-22 origin/main

All this could be in the Git docs as a usage example or something. Because everybody uses whatever shell they want (and shells are also configurable), it gets complicated really fast. I am not a contributor there and I was just having fun.

Upvotes: 0

Alexey Ten
Alexey Ten

Reputation: 14354

You don't need double quotes in .gitconfig.

So the command is:

git config --global alias.assumed '!git ls-files -v | grep ^h | cut -c 3-'

Upvotes: 12

Related Questions