Git Config Double Quotes in PowerShell
double-quotes, git-config, powershell
Solution
Unfortunately, an obscure workaround is required (see the bottom section for a generic solution via an installable module):
git config --global mergetool.vscode.cmd --% "\"code --wait $MERGED\""
Note: This assumes that you want to end up with the following in `~/.gitconfig`, which probably isn't your intent:
[mergetool "vscode"]
cmd = \"code --wait $MERGED\"
Instead, as one of the answers you link to suggests, only the `$MERGED` part should be double-quoted with embedded double quotes, in which case you should use (see the alternative at the bottom):
git config --global mergetool.vscode.cmd --% "code --wait \"$MERGED\""
That would give you:
[mergetool "vscode"]
cmd = code --wait \"$MERGED\"
That is, only those individual arguments in the shell command that your value constitutes that need double-quoting for the shell's sake must be enclosed in `"..."`.
Note that it is `git` that re-escapes verbatim `"` chars. in the given configuration value as `\"` in the config file. Unescaped `"` in the config file - which have syntactic function for `git` itself - are only used if you pass a value with leading and/or trailing spaces, so as to delimit the value as a whole; values with (interior) spaces do not themselves require manual double-quoting: `git` stores them as-is in the config file, except for escaping embedded `\` as `\\` and `"` as `\"`.
Passing `--% "code --wait \"$MERGED\""` from PowerShell signals the intent to pass verbatim value `code --wait "$MERGED"` to `git config --global mergetool.vscode.cmd`, which is the proper formulation of a `sh` command line[1] (`sh` is a POSIX-compatible shell that `git` uses even on Windows, via a `bash` implementation that ships with it) in which the `"..."` around (environment) variable reference `$MERGED` ensure that the expanded value is passed as-is to the target binary, `code`, even if it contains spaces, for instance.
You can verify this by querying the value afterwards:
# After running the command above:
PS> git config --global mergetool.vscode.cmd
code --wait "$MERGED"
`git config` and the configuration file format are documented here, also available locally by running `git help config` or, alternatively, on Unix-like platforms only, `man git-config`
Explanation of the workaround:
Use of `--%`, the stop-parsing symbol, allows you to control the exact quoting of the subsequent arguments passed to external programs (while also suppressing expansion of PowerShell variables, so that `$MERGED` is left as-is), whereas PowerShell by default performs re-quoting behind the scenes, after having performed its own parsing.
Leaving aside that double-quoting the entire command line is ultimately not the right approach, purely from a PowerShell syntax perspective what you tried, `'"code --wait $MERGED"'`, should work - PowerShell should automatically translate that to `"\"code --wait $MERGED\""` behind the scenes - but as of PowerShell 7.1 doesn't, due to a long-standing bug, described in detail in this answer.
While explicit `\`-escaping of embedded `"` chars.(!) is typically a better workaround and works reliably in PowerShell (Core) 7+, there are edge cases in Windows PowerShell where it doesn't, and you've hit one of them:
# Alternative workaround for *PowerShell (Core) v7+ only*
git config --global mergetool.vscode.cmd '\"code --wait $MERGED\"'
The reason this doesn't work in Windows PowerShell is that it mistakenly concludes that the `\"...\"` constitutes syntactic double-quoting and therefore doesn't enclose the argument in `"..."`; that is, instead of passing `"\"...\""`, it passes just `\"...\"` as part of the target process' command line.
Note that quoting arguments individually avoids the edge case discussed, so that explicit `\`-escaping works even in Windows PowerShell:
# Also works in Windows PowerShell, because the edge case is avoided.
git config --global mergetool.vscode.cmd 'code --wait \"$MERGED\"'
Generic solution via module `Native`:
If you want to solve quoting problems for most[2] (on Windows) / all (on Unix) calls to external programs, consider my `Native` module's `ie` function, which encapsulates all required workarounds:
# Install the module in the current user's scope.
Install-Module Native
# Simply prepend `ie` to your external-program calls, which correctly
# handles all behind-the-scenes re-quoting, allowing you to focus on
# PowerShell syntax only.
ie git config --global mergetool.vscode.cmd 'code --wait "$MERGED"'
[1] `git` appears to be invoking `sh` as follows (using `sh` syntax with placeholders for illustration): `sh -c '<config-value> "$@"' '<config-value>' <git-supplied args>` That is, the command line stored in the configuration value is invoked with `git`-supplied pass-through arguments appended. Note that the first first post `-c` argument is again the configuration value, which binds to `$0`, i.e. sets the invocation name, and is therefore not part of the array of pass-through arguments, `"$@"`. An example of a `git`-supplied pass-through argument is the path of a file to edit that is passed to the command line stored in the `core.editor` configuration value.
[2] A solution that works with all external programs is fundamentally impossible on Windows, because each program can decide for itself how it parses the string that encodes the arguments being passed. However, there are a few widespread patterns that `ie` is aware of and applies appropriately, which notably makes it work robustly with batch files and `msiexec`-like executables.
Problem
This was asked here, but no solution was provided for PowerShell. The solution given does not work in PowerShell (specifically, PowerShell 5.1 within VSCode). I have tried ``` git config --global mergetool.vscode.cmd '"code --wait $MERGED"' ``` but I lose the double-quotes (i.e. there are no double-quotes in the corresponding `.gitconfig` file). FYI, the single-quotes are necessary to pass `$MERGED` as a string literal and not have PowerShell attempt to expand it. I've also tried ``` echo '"code --wait $MERGED"' | git config --global mergetool.vscode.cmd ``` ``` git config --global mergetool.vscode.cmd '`"code --wait $MERGED`"' ``` ``` git config --global mergetool.vscode.cmd @' code --wait $MERGED '@ ``` but nothing works. Is there no way to do this from within PowerShell? Additionally, I've researched this question, and the solution of ``` git config --global mergetool.vscode.cmd '\"code --wait $MERGED\"' ``` nor ``` git config --global mergetool.vscode.cmd "\"code --wait $MERGED\"" ``` does not work either. The closest I get is using ``` git config --global mergetool.vscode.cmd '" code --wait $MERGED "' ``` but this outputs the spaces in the string as such: ``` [mergetool "vscode"] cmd = " code --wait $MERGED " ```