Sync with Git 2.47.2
Git 2.47.2

# -----BEGIN PGP SIGNATURE-----
#
# iQIzBAABCAAdFiEE4fA2sf7nIh/HeOzvsLXohpav5ssFAmdkT1sACgkQsLXohpav
# 5svdhRAAq0WoZIg+33vYNNVSTm3Ux9RJslmXs3lQuhuUJ61hK/28drSLU29GH7x7
# 3nmmjp1cegnXRVLBAfoYDdzPprNNrQFQEHQEzgG/GDZw0OXn+WTZuNyrrUYoa+sd
# QSLlElRj2qrpHIMOsMIBKBSNB+qjJHOMGdxcBAS768TfnQpGIpc1KJa24TxsVBzC
# ScP4uvrFfPyQrqFUgiUhCeqLnO/6T5i/QAn/8cS5a1+zor5ZHSlw28TZTOxN2odo
# Rulp/FtehiDEzmRowgD3M4fImAPY6Ib6VORCYASqpJFFla30tu2bQqEi6raOMTec
# hg5Ibkmj6fHFONaYvoTMRkYHmtUnNgIPU/CYPwswNk8w1+PPQfJ+TYjBXOQgdTLW
# F0azHBHh7NRmEHVydiF9CqjgNVRzjO4IEZfGqXNFPPMvR6UUzDaIkrpYbwXBFMin
# GNPV3QISeXj9ROjJoCv0nclXETwWemykjZlD6b5krXn5TaJlFb+69qJvXrCLq5WY
# EoevSqKkB9HVK9si7P8Sh1cPGOr3kfiFPmMNKFVI8l0+iDFgBywOomWNS/JEzqu1
# nN142DKdL1W/rkeMUhbX2h11CZNvHKIOy3iaA4MTOing8/eMzyUUQ73Ck7odYs4f
# rZ0tTXKJhxojPvBpTxYe9SxM0bDLREiOv0zX76+sIuhbAQCmk0o=
# =MNNf
# -----END PGP SIGNATURE-----
# gpg: Signature made Thu 19 Dec 2024 08:52:43 AM PST
# gpg:                using RSA key E1F036B1FEE7221FC778ECEFB0B5E88696AFE6CB
# gpg: Good signature from "Junio C Hamano <gitster@pobox.com>" [ultimate]
# gpg:                 aka "Junio C Hamano <junio@pobox.com>" [ultimate]
# gpg:                 aka "Junio C Hamano <jch@google.com>" [ultimate]

* tag 'v2.47.2':
  Git 2.47.2
  Git 2.46.3
  Git 2.45.3
  Git 2.44.3
  Git 2.43.6
  Git 2.42.4
  Git 2.41.3
  Git 2.40.4
  credential: disallow Carriage Returns in the protocol by default
  credential: sanitize the user prompt
  credential_format(): also encode <host>[:<port>]
  t7300: work around platform-specific behaviour with long paths on MinGW
  compat/regex: fix argument order to calloc(3)
  mingw: drop bogus (and unneeded) declaration of `_pgmptr`
  ci: remove 'Upload failed tests' directories' step from linux32 jobs
diff --git a/Documentation/RelNotes/2.40.4.txt b/Documentation/RelNotes/2.40.4.txt
new file mode 100644
index 0000000..0ff29f3
--- /dev/null
+++ b/Documentation/RelNotes/2.40.4.txt
@@ -0,0 +1,5 @@
+Git v2.40.4 Release Notes
+=========================
+
+This release lets Git refuse to accept URLs that contain control
+sequences.  This addresses CVE-2024-50349 and CVE-2024-52006.
diff --git a/Documentation/RelNotes/2.41.3.txt b/Documentation/RelNotes/2.41.3.txt
new file mode 100644
index 0000000..b5aba88
--- /dev/null
+++ b/Documentation/RelNotes/2.41.3.txt
@@ -0,0 +1,6 @@
+Git v2.41.3 Release Notes
+=========================
+
+This release merges up the fix that appears in v2.40.4 to address
+the security issues CVE-2024-50349 and CVE-2024-52006; see the
+release notes for that version for details.
diff --git a/Documentation/RelNotes/2.42.4.txt b/Documentation/RelNotes/2.42.4.txt
new file mode 100644
index 0000000..3129d76
--- /dev/null
+++ b/Documentation/RelNotes/2.42.4.txt
@@ -0,0 +1,6 @@
+Git v2.42.4 Release Notes
+=========================
+
+This release merges up the fix that appears in v2.40.4 and v2.41.3
+to address the security issues CVE-2024-50349 and CVE-2024-52006;
+see the release notes for these versions for details.
diff --git a/Documentation/RelNotes/2.43.6.txt b/Documentation/RelNotes/2.43.6.txt
new file mode 100644
index 0000000..2114b9f
--- /dev/null
+++ b/Documentation/RelNotes/2.43.6.txt
@@ -0,0 +1,7 @@
+Git v2.43.6 Release Notes
+=========================
+
+This release merges up the fix that appears in v2.40.4, v2.41.3
+and v2.42.4 to address the security issues CVE-2024-50349 and
+CVE-2024-52006; see the release notes for these versions for
+details.
diff --git a/Documentation/RelNotes/2.44.3.txt b/Documentation/RelNotes/2.44.3.txt
new file mode 100644
index 0000000..5862845
--- /dev/null
+++ b/Documentation/RelNotes/2.44.3.txt
@@ -0,0 +1,7 @@
+Git v2.44.3 Release Notes
+=========================
+
+This release merges up the fix that appears in v2.40.4, v2.41.3,
+v2.42.4 and v2.43.6 to address the security issues CVE-2024-50349
+and CVE-2024-52006; see the release notes for these versions
+for details.
diff --git a/Documentation/RelNotes/2.45.3.txt b/Documentation/RelNotes/2.45.3.txt
index 2a1e9aa..ddb3cb6 100644
--- a/Documentation/RelNotes/2.45.3.txt
+++ b/Documentation/RelNotes/2.45.3.txt
@@ -1,7 +1,12 @@
 Git v2.45.3 Release Notes
 =========================
 
-This primarily is to backport various small fixes accumulated on the
+This release merges up the fix that appears in v2.40.4, v2.41.3,
+v2.42.4, v2.43.6 and v2.44.3 to address the security issues
+CVE-2024-50349 and CVE-2024-52006; see the release notes for
+these versions for details.
+
+This version also backports various small fixes accumulated on the
 'master' front during the development towards Git 2.46, the next
 feature release.
 
diff --git a/Documentation/RelNotes/2.46.3.txt b/Documentation/RelNotes/2.46.3.txt
new file mode 100644
index 0000000..4af032b
--- /dev/null
+++ b/Documentation/RelNotes/2.46.3.txt
@@ -0,0 +1,6 @@
+Git v2.46.3 Release Notes
+=========================
+
+This release merges up the fix that appears in v2.40.4, v2.41.3, v2.42.4,
+v2.43.6, v2.44.3 and v2.45.3 to address the security issues CVE-2024-50349 and
+CVE-2024-52006; see the release notes for these versions for details.
diff --git a/Documentation/RelNotes/2.47.2.txt b/Documentation/RelNotes/2.47.2.txt
new file mode 100644
index 0000000..7a52ad8
--- /dev/null
+++ b/Documentation/RelNotes/2.47.2.txt
@@ -0,0 +1,7 @@
+Git v2.47.2 Release Notes
+=========================
+
+This release merges up the fix that appears in v2.40.4, v2.41.3,
+v2.42.4, v2.43.6, v2.44.3, v2.45.3 and v2.46.3 to address the
+security issues CVE-2024-50349 and CVE-2024-52006; see the release
+notes for these versions for details.
diff --git a/Documentation/config/credential.txt b/Documentation/config/credential.txt
index 470482f..80a7c77 100644
--- a/Documentation/config/credential.txt
+++ b/Documentation/config/credential.txt
@@ -22,6 +22,17 @@
 	or https URL to be important. Defaults to false. See
 	linkgit:gitcredentials[7] for more information.
 
+credential.sanitizePrompt::
+	By default, user names and hosts that are shown as part of the
+	password prompt are not allowed to contain control characters (they
+	will be URL-encoded by default). Configure this setting to `false` to
+	override that behavior.
+
+credential.protectProtocol::
+	By default, Carriage Return characters are not allowed in the protocol
+	that is used when Git talks to a credential helper. This setting allows
+	users to override this default.
+
 credential.username::
 	If no username is set for a network authentication, use this username
 	by default. See credential.<context>.* below, and
diff --git a/credential.c b/credential.c
index a995031..6e6e81c 100644
--- a/credential.c
+++ b/credential.c
@@ -13,7 +13,7 @@
 #include "sigchain.h"
 #include "strbuf.h"
 #include "urlmatch.h"
-#include "git-compat-util.h"
+#include "environment.h"
 #include "trace2.h"
 #include "repository.h"
 
@@ -130,6 +130,10 @@ static int credential_config_callback(const char *var, const char *value,
 	}
 	else if (!strcmp(key, "usehttppath"))
 		c->use_http_path = git_config_bool(var, value);
+	else if (!strcmp(key, "sanitizeprompt"))
+		c->sanitize_prompt = git_config_bool(var, value);
+	else if (!strcmp(key, "protectprotocol"))
+		c->protect_protocol = git_config_bool(var, value);
 
 	return 0;
 }
@@ -227,7 +231,8 @@ static void credential_format(struct credential *c, struct strbuf *out)
 		strbuf_addch(out, '@');
 	}
 	if (c->host)
-		strbuf_addstr(out, c->host);
+		strbuf_add_percentencode(out, c->host,
+					 STRBUF_ENCODE_HOST_AND_PORT);
 	if (c->path) {
 		strbuf_addch(out, '/');
 		strbuf_add_percentencode(out, c->path, 0);
@@ -241,7 +246,10 @@ static char *credential_ask_one(const char *what, struct credential *c,
 	struct strbuf prompt = STRBUF_INIT;
 	char *r;
 
-	credential_describe(c, &desc);
+	if (c->sanitize_prompt)
+		credential_format(c, &desc);
+	else
+		credential_describe(c, &desc);
 	if (desc.len)
 		strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf);
 	else
@@ -382,7 +390,8 @@ int credential_read(struct credential *c, FILE *fp,
 	return 0;
 }
 
-static void credential_write_item(FILE *fp, const char *key, const char *value,
+static void credential_write_item(const struct credential *c,
+				  FILE *fp, const char *key, const char *value,
 				  int required)
 {
 	if (!value && required)
@@ -391,6 +400,10 @@ static void credential_write_item(FILE *fp, const char *key, const char *value,
 		return;
 	if (strchr(value, '\n'))
 		die("credential value for %s contains newline", key);
+	if (c->protect_protocol && strchr(value, '\r'))
+		die("credential value for %s contains carriage return\n"
+		    "If this is intended, set `credential.protectProtocol=false`",
+		    key);
 	fprintf(fp, "%s=%s\n", key, value);
 }
 
@@ -398,34 +411,34 @@ void credential_write(const struct credential *c, FILE *fp,
 		      enum credential_op_type op_type)
 {
 	if (credential_has_capability(&c->capa_authtype, op_type))
-		credential_write_item(fp, "capability[]", "authtype", 0);
+		credential_write_item(c, fp, "capability[]", "authtype", 0);
 	if (credential_has_capability(&c->capa_state, op_type))
-		credential_write_item(fp, "capability[]", "state", 0);
+		credential_write_item(c, fp, "capability[]", "state", 0);
 
 	if (credential_has_capability(&c->capa_authtype, op_type)) {
-		credential_write_item(fp, "authtype", c->authtype, 0);
-		credential_write_item(fp, "credential", c->credential, 0);
+		credential_write_item(c, fp, "authtype", c->authtype, 0);
+		credential_write_item(c, fp, "credential", c->credential, 0);
 		if (c->ephemeral)
-			credential_write_item(fp, "ephemeral", "1", 0);
+			credential_write_item(c, fp, "ephemeral", "1", 0);
 	}
-	credential_write_item(fp, "protocol", c->protocol, 1);
-	credential_write_item(fp, "host", c->host, 1);
-	credential_write_item(fp, "path", c->path, 0);
-	credential_write_item(fp, "username", c->username, 0);
-	credential_write_item(fp, "password", c->password, 0);
-	credential_write_item(fp, "oauth_refresh_token", c->oauth_refresh_token, 0);
+	credential_write_item(c, fp, "protocol", c->protocol, 1);
+	credential_write_item(c, fp, "host", c->host, 1);
+	credential_write_item(c, fp, "path", c->path, 0);
+	credential_write_item(c, fp, "username", c->username, 0);
+	credential_write_item(c, fp, "password", c->password, 0);
+	credential_write_item(c, fp, "oauth_refresh_token", c->oauth_refresh_token, 0);
 	if (c->password_expiry_utc != TIME_MAX) {
 		char *s = xstrfmt("%"PRItime, c->password_expiry_utc);
-		credential_write_item(fp, "password_expiry_utc", s, 0);
+		credential_write_item(c, fp, "password_expiry_utc", s, 0);
 		free(s);
 	}
 	for (size_t i = 0; i < c->wwwauth_headers.nr; i++)
-		credential_write_item(fp, "wwwauth[]", c->wwwauth_headers.v[i], 0);
+		credential_write_item(c, fp, "wwwauth[]", c->wwwauth_headers.v[i], 0);
 	if (credential_has_capability(&c->capa_state, op_type)) {
 		if (c->multistage)
-			credential_write_item(fp, "continue", "1", 0);
+			credential_write_item(c, fp, "continue", "1", 0);
 		for (size_t i = 0; i < c->state_headers_to_send.nr; i++)
-			credential_write_item(fp, "state[]", c->state_headers_to_send.v[i], 0);
+			credential_write_item(c, fp, "state[]", c->state_headers_to_send.v[i], 0);
 	}
 }
 
diff --git a/credential.h b/credential.h
index 5f9e6ff..63fef3e 100644
--- a/credential.h
+++ b/credential.h
@@ -168,7 +168,9 @@ struct credential {
 		 multistage: 1,
 		 quit:1,
 		 use_http_path:1,
-		 username_from_proto:1;
+		 username_from_proto:1,
+		 sanitize_prompt:1,
+		 protect_protocol:1;
 
 	struct credential_capability capa_authtype;
 	struct credential_capability capa_state;
@@ -195,6 +197,8 @@ struct credential {
 	.wwwauth_headers = STRVEC_INIT, \
 	.state_headers = STRVEC_INIT, \
 	.state_headers_to_send = STRVEC_INIT, \
+	.sanitize_prompt = 1, \
+	.protect_protocol = 1, \
 }
 
 /* Initialize a credential structure, setting all fields to empty. */
diff --git a/strbuf.c b/strbuf.c
index 8ddd4b0..f30fdc6 100644
--- a/strbuf.c
+++ b/strbuf.c
@@ -497,7 +497,9 @@ void strbuf_add_percentencode(struct strbuf *dst, const char *src, int flags)
 		unsigned char ch = src[i];
 		if (ch <= 0x1F || ch >= 0x7F ||
 		    (ch == '/' && (flags & STRBUF_ENCODE_SLASH)) ||
-		    strchr(URL_UNSAFE_CHARS, ch))
+		    ((flags & STRBUF_ENCODE_HOST_AND_PORT) ?
+		     !isalnum(ch) && !strchr("-.:[]", ch) :
+		     !!strchr(URL_UNSAFE_CHARS, ch)))
 			strbuf_addf(dst, "%%%02X", (unsigned char)ch);
 		else
 			strbuf_addch(dst, ch);
diff --git a/strbuf.h b/strbuf.h
index 4dc05b4..6362777 100644
--- a/strbuf.h
+++ b/strbuf.h
@@ -356,6 +356,7 @@ void strbuf_expand_bad_format(const char *format, const char *command);
 void strbuf_addbuf_percentquote(struct strbuf *dst, const struct strbuf *src);
 
 #define STRBUF_ENCODE_SLASH 1
+#define STRBUF_ENCODE_HOST_AND_PORT 2
 
 /**
  * Append the contents of a string to a strbuf, percent-encoding any characters
diff --git a/t/t0300-credentials.sh b/t/t0300-credentials.sh
index 17952e5..cb3a85c 100755
--- a/t/t0300-credentials.sh
+++ b/t/t0300-credentials.sh
@@ -76,6 +76,10 @@
 	test -z "$pexpiry" || echo password_expiry_utc=$pexpiry
 	EOF
 
+	write_script git-credential-cntrl-in-username <<-\EOF &&
+	printf "username=\\007latrix Lestrange\\n"
+	EOF
+
 	PATH="$PWD:$PATH"
 '
 
@@ -696,6 +700,19 @@
 	EOF
 '
 
+test_expect_success 'match percent-encoded values in hostname' '
+	test_config "credential.https://a%20b%20c/.helper" "$HELPER" &&
+	check fill <<-\EOF
+	url=https://a b c/
+	--
+	protocol=https
+	host=a b c
+	username=foo
+	password=bar
+	--
+	EOF
+'
+
 test_expect_success 'fetch with multiple path components' '
 	test_unconfig credential.helper &&
 	test_config credential.https://example.com/foo/repo.git.helper "verbatim foo bar" &&
@@ -885,6 +902,22 @@
 	test_cmp expect stderr
 '
 
+test_expect_success 'url parser rejects embedded carriage returns' '
+	test_config credential.helper "!true" &&
+	test_must_fail git credential fill 2>stderr <<-\EOF &&
+	url=https://example%0d.com/
+	EOF
+	cat >expect <<-\EOF &&
+	fatal: credential value for host contains carriage return
+	If this is intended, set `credential.protectProtocol=false`
+	EOF
+	test_cmp expect stderr &&
+	GIT_ASKPASS=true \
+	git -c credential.protectProtocol=false credential fill <<-\EOF
+	url=https://example%0d.com/
+	EOF
+'
+
 test_expect_success 'host-less URLs are parsed as empty host' '
 	check fill "verbatim foo bar" <<-\EOF
 	url=cert:///path/to/cert.pem
@@ -994,4 +1027,20 @@
 	test_grep "skipping credential lookup for key" stderr
 '
 
+BEL="$(printf '\007')"
+
+test_expect_success 'interactive prompt is sanitized' '
+	check fill cntrl-in-username <<-EOF
+	protocol=https
+	host=example.org
+	--
+	protocol=https
+	host=example.org
+	username=${BEL}latrix Lestrange
+	password=askpass-password
+	--
+	askpass: Password for ${SQ}https://%07latrix%20Lestrange@example.org${SQ}:
+	EOF
+'
+
 test_done
diff --git a/t/t5541-http-push-smart.sh b/t/t5541-http-push-smart.sh
index 71428f3..538b603 100755
--- a/t/t5541-http-push-smart.sh
+++ b/t/t5541-http-push-smart.sh
@@ -343,7 +343,7 @@
 	git push "$HTTPD_URL"/auth/smart/test_repo.git &&
 	git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
 		log -1 --format=%s >actual &&
-	expect_askpass both user@host &&
+	expect_askpass both user%40host &&
 	test_cmp expect actual
 '
 
@@ -355,7 +355,7 @@
 	git push "$HTTPD_URL"/auth-push/smart/test_repo.git &&
 	git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/test_repo.git" \
 		log -1 --format=%s >actual &&
-	expect_askpass both user@host &&
+	expect_askpass both user%40host &&
 	test_cmp expect actual
 '
 
@@ -385,7 +385,7 @@
 	git push "$HTTPD_URL/half-auth-complete/smart/half-auth.git" &&
 	git --git-dir="$HTTPD_DOCUMENT_ROOT_PATH/half-auth.git" \
 		log -1 --format=%s >actual &&
-	expect_askpass both user@host &&
+	expect_askpass both user%40host &&
 	test_cmp expect actual
 '
 
diff --git a/t/t5550-http-fetch-dumb.sh b/t/t5550-http-fetch-dumb.sh
index 21795a1..ed0ad66 100755
--- a/t/t5550-http-fetch-dumb.sh
+++ b/t/t5550-http-fetch-dumb.sh
@@ -111,13 +111,13 @@
 test_expect_success 'http auth can use just user in URL' '
 	set_askpass wrong pass@host &&
 	git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-pass &&
-	expect_askpass pass user@host
+	expect_askpass pass user%40host
 '
 
 test_expect_success 'http auth can request both user and pass' '
 	set_askpass user@host pass@host &&
 	git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-both &&
-	expect_askpass both user@host
+	expect_askpass both user%40host
 '
 
 test_expect_success 'http auth respects credential helper config' '
@@ -135,14 +135,14 @@
 	test_config_global "credential.$HTTPD_URL.username" user@host &&
 	set_askpass wrong pass@host &&
 	git clone "$HTTPD_URL/auth/dumb/repo.git" clone-auth-user &&
-	expect_askpass pass user@host
+	expect_askpass pass user%40host
 '
 
 test_expect_success 'configured username does not override URL' '
 	test_config_global "credential.$HTTPD_URL.username" wrong &&
 	set_askpass wrong pass@host &&
 	git clone "$HTTPD_URL_USER/auth/dumb/repo.git" clone-auth-user2 &&
-	expect_askpass pass user@host
+	expect_askpass pass user%40host
 '
 
 test_expect_success 'set up repo with http submodules' '
@@ -163,7 +163,7 @@
 	set_askpass wrong pass@host &&
 	git -c "credential.$HTTPD_URL.username=user@host" \
 		clone --recursive super super-clone &&
-	expect_askpass pass user@host
+	expect_askpass pass user%40host
 '
 
 test_expect_success 'cmdline credential config passes submodule via fetch' '
@@ -174,7 +174,7 @@
 	git -C super-clone \
 	    -c "credential.$HTTPD_URL.username=user@host" \
 	    fetch --recurse-submodules &&
-	expect_askpass pass user@host
+	expect_askpass pass user%40host
 '
 
 test_expect_success 'cmdline credential config passes submodule update' '
@@ -191,7 +191,7 @@
 	git -C super-clone \
 	    -c "credential.$HTTPD_URL.username=user@host" \
 	    submodule update &&
-	expect_askpass pass user@host
+	expect_askpass pass user%40host
 '
 
 test_expect_success 'fetch changes via http' '
diff --git a/t/t5551-http-fetch-smart.sh b/t/t5551-http-fetch-smart.sh
index ceb3336..761fdfc 100755
--- a/t/t5551-http-fetch-smart.sh
+++ b/t/t5551-http-fetch-smart.sh
@@ -181,7 +181,7 @@
 	echo two >expect &&
 	set_askpass user@host pass@host &&
 	git clone --bare "$HTTPD_URL/auth/smart/repo.git" smart-auth &&
-	expect_askpass both user@host &&
+	expect_askpass both user%40host &&
 	git --git-dir=smart-auth log -1 --format=%s >actual &&
 	test_cmp expect actual
 '
@@ -221,7 +221,7 @@
 	echo two >expect &&
 	set_askpass user@host pass@host &&
 	git clone --bare "$HTTPD_URL/auth-fetch/smart/repo.git" half-auth &&
-	expect_askpass both user@host &&
+	expect_askpass both user%40host &&
 	git --git-dir=half-auth log -1 --format=%s >actual &&
 	test_cmp expect actual
 '
@@ -246,14 +246,14 @@
 	set_askpass user@host pass@host &&
 	git -c credential.useHttpPath=true \
 	  clone $HTTPD_URL/smart-redir-auth/repo.git repo-redir-auth &&
-	expect_askpass both user@host auth/smart/repo.git
+	expect_askpass both user%40host auth/smart/repo.git
 '
 
 test_expect_success 'GIT_TRACE_CURL redacts auth details' '
 	rm -rf redact-auth trace &&
 	set_askpass user@host pass@host &&
 	GIT_TRACE_CURL="$(pwd)/trace" git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth &&
-	expect_askpass both user@host &&
+	expect_askpass both user%40host &&
 
 	# Ensure that there is no "Basic" followed by a base64 string, but that
 	# the auth details are redacted
@@ -265,7 +265,7 @@
 	rm -rf redact-auth trace &&
 	set_askpass user@host pass@host &&
 	GIT_CURL_VERBOSE=1 git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth 2>trace &&
-	expect_askpass both user@host &&
+	expect_askpass both user%40host &&
 
 	# Ensure that there is no "Basic" followed by a base64 string, but that
 	# the auth details are redacted
@@ -278,7 +278,7 @@
 	set_askpass user@host pass@host &&
 	GIT_TRACE_REDACT=0 GIT_TRACE_CURL="$(pwd)/trace" \
 		git clone --bare "$HTTPD_URL/auth/smart/repo.git" redact-auth &&
-	expect_askpass both user@host &&
+	expect_askpass both user%40host &&
 
 	grep -i "Authorization: Basic [0-9a-zA-Z+/]" trace
 '
@@ -592,7 +592,7 @@
 	# the first request prompts the user...
 	set_askpass user@host pass@host &&
 	git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null &&
-	expect_askpass both user@host &&
+	expect_askpass both user%40host &&
 
 	# ...and the second one uses the stored value rather than
 	# prompting the user.
@@ -623,7 +623,7 @@
 	# us to prompt the user again.
 	set_askpass user@host pass@host &&
 	git ls-remote "$HTTPD_URL/auth/smart/repo.git" >/dev/null &&
-	expect_askpass both user@host
+	expect_askpass both user%40host
 '
 
 test_expect_success 'client falls back from v2 to v0 to match server' '