Skip to content

Commit afb7686

Browse files
committed
mingw: special-case arguments to sh
The MSYS2 runtime does its best to emulate the command-line wildcard expansion and de-quoting which would be performed by the calling Unix shell on Unix systems. Those Unix shell quoting rules differ from the quoting rules applying to Windows' cmd and Powershell, making it a little awkward to quote command-line parameters properly when spawning other processes. In particular, git.exe passes arguments to subprocesses that are *not* intended to be interpreted as wildcards, and if they contain backslashes, those are not to be interpreted as escape characters, e.g. when passing Windows paths. Note: this is only a problem when calling MSYS2 executables, not when calling MINGW executables such as git.exe. However, we do call MSYS2 executables frequently, most notably when setting the use_shell flag in the child_process structure. There is no elegant way to determine whether the .exe file to be executed is an MSYS2 program or a MINGW one. But since the use case of passing a command line through the shell is so prevalent, we need to work around this issue at least when executing sh.exe. Let's introduce an ugly, hard-coded test whether argv[0] is "sh", and whether it refers to the MSYS2 Bash, to determine whether we need to quote the arguments differently than usual. That still does not fix the issue completely, but at least it is something. Incidentally, this also fixes the problem where `git clone \\server\repo` failed due to incorrect handling of the backslashes when handing the path to the git-upload-pack process. We need to take care to quote not only whitespace, but also curly brackets. As aliases frequently go through the MSYS2 Bash, and as aliases frequently get parameters such as HEAD@{yesterday}, let's make sure that this does not regress by adding a test case for that. Helped-by: Kim Gybels <[email protected]> Signed-off-by: Johannes Schindelin <[email protected]>
1 parent 2b6f1cd commit afb7686

File tree

3 files changed

+73
-2
lines changed

3 files changed

+73
-2
lines changed

compat/mingw.c

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1112,7 +1112,7 @@ char *mingw_getcwd(char *pointer, int len)
11121112
* See http://msdn2.microsoft.com/en-us/library/17w5ykft(vs.71).aspx
11131113
* (Parsing C++ Command-Line Arguments)
11141114
*/
1115-
static const char *quote_arg(const char *arg)
1115+
static const char *quote_arg_msvc(const char *arg)
11161116
{
11171117
/* count chars to quote */
11181118
int len = 0, n = 0;
@@ -1167,6 +1167,37 @@ static const char *quote_arg(const char *arg)
11671167
return q;
11681168
}
11691169

1170+
#include "quote.h"
1171+
1172+
static const char *quote_arg_msys2(const char *arg)
1173+
{
1174+
struct strbuf buf = STRBUF_INIT;
1175+
const char *p2 = arg, *p;
1176+
1177+
for (p = arg; *p; p++) {
1178+
int ws = isspace(*p);
1179+
if (!ws && *p != '\\' && *p != '"' && *p != '{')
1180+
continue;
1181+
if (!buf.len)
1182+
strbuf_addch(&buf, '"');
1183+
if (p != p2)
1184+
strbuf_add(&buf, p2, p - p2);
1185+
if (!ws && *p != '{')
1186+
strbuf_addch(&buf, '\\');
1187+
p2 = p;
1188+
}
1189+
1190+
if (p == arg)
1191+
strbuf_addch(&buf, '"');
1192+
else if (!buf.len)
1193+
return arg;
1194+
else
1195+
strbuf_add(&buf, p2, p - p2),
1196+
1197+
strbuf_addch(&buf, '"');
1198+
return strbuf_detach(&buf, 0);
1199+
}
1200+
11701201
static const char *parse_interpreter(const char *cmd)
11711202
{
11721203
static char buf[100];
@@ -1477,6 +1508,34 @@ struct pinfo_t {
14771508
static struct pinfo_t *pinfo = NULL;
14781509
CRITICAL_SECTION pinfo_cs;
14791510

1511+
static int is_msys2_sh(const char *cmd)
1512+
{
1513+
if (cmd && !strcmp(cmd, "sh")) {
1514+
static int ret = -1;
1515+
char *p;
1516+
1517+
if (ret >= 0)
1518+
return ret;
1519+
1520+
p = path_lookup(cmd, 0);
1521+
if (!p)
1522+
ret = 0;
1523+
else {
1524+
size_t len = strlen(p);
1525+
ret = len > 15 &&
1526+
is_dir_sep(p[len - 15]) &&
1527+
!strncasecmp(p + len - 14, "usr", 3) &&
1528+
is_dir_sep(p[len - 11]) &&
1529+
!strncasecmp(p + len - 10, "bin", 3) &&
1530+
is_dir_sep(p[len - 7]) &&
1531+
!strcasecmp(p + len - 6, "sh.exe");
1532+
free(p);
1533+
}
1534+
return ret;
1535+
}
1536+
return 0;
1537+
}
1538+
14801539
static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaenv,
14811540
const char *dir,
14821541
int prepend_cmd, int fhin, int fhout, int fherr)
@@ -1489,6 +1548,8 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
14891548
BOOL ret;
14901549
HANDLE cons;
14911550
const char *strace_env;
1551+
const char *(*quote_arg)(const char *arg) =
1552+
is_msys2_sh(*argv) ? quote_arg_msys2 : quote_arg_msvc;
14921553

14931554
do_unset_environment_variables();
14941555

t/t0061-run-command.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,4 +178,14 @@ test_expect_success 'GIT_TRACE with environment variables' '
178178
)
179179
'
180180

181+
test_expect_success MINGW 'verify curlies are quoted properly' '
182+
: force the rev-parse through the MSYS2 Bash &&
183+
git -c alias.r="!git rev-parse" r -- a{b}c >actual &&
184+
cat >expect <<-\EOF &&
185+
--
186+
a{b}c
187+
EOF
188+
test_cmp expect actual
189+
'
190+
181191
test_done

t/t5580-clone-push-unc.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ test_expect_success clone '
4949
git clone "file://$UNCPATH" clone
5050
'
5151

52-
test_expect_failure 'clone with backslashed path' '
52+
test_expect_success 'clone with backslashed path' '
5353
BACKSLASHED="$(echo "$UNCPATH" | tr / \\\\)" &&
5454
git clone "$BACKSLASHED" backslashed
5555
'

0 commit comments

Comments
 (0)