t5710: use proper file:// URIs for absolute paths

In t5710, we frequently construct local file URIs using `file://$(pwd)`. On Unix-like systems, $(pwd) returns an absolute path starting with a slash (e.g., `/tmp/repo`), resulting in a valid 3-slash URI with an empty host (`file:///tmp/repo`). However, on Windows, $(pwd) returns a path starting with a drive letter (e.g., `D:/a/repo`). This results in a 2-slash URI (`file://D:/a/repo`). Standard URI parsers misinterpret this format, treating `D:` as the host rather than part of the absolute path. This is to be expected because RFC 8089 says that the `//` prefix with an empty local host must be followed by an absolute path starting with a slash. While this hasn't broken the existing tests (because the old `promisor.acceptFromServer` logic relies entirely on strict `strcmp()` without normalizing the URLs), it will break future commits that pass these URLs through `url_normalize()` or similar functions. To future-proof the tests and ensure cross-platform URI compliance, let's introduce a $TRASH_DIRECTORY_URL helper variable that explicitly guarantees a leading slash for the path component, ensuring valid 3-slash `file:///` URIs on all operating systems. While at it, let's also introduce $ENCODED_TRASH_DIRECTORY_URL to handle some common special characters in directory paths. To be extra safe, let's skip all the tests if there are uncommon special characters in the directory path. Then let's replace all instances of `file://$(pwd)` with $TRASH_DIRECTORY_URL across the test script, and let's simplify the `sendFields` and `checkFields` tests to use $ENCODED_TRASH_DIRECTORY_URL directly. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Christian Couder committed Apr 7, 2026 at 13:52 UTC 8eb863597f630efe08f96ed12f8defbe5a5f0b1d
1 file changed +51 -28
t/t5710-promisor-remote-capability.sh
+51 -28
@@ -76,6 +76,31 @@ copy_to_lop () {
76 cp "$path" "$path2"
77 }
78
79 +# On Windows, `pwd` returns a path like 'D:/foo/bar'. Prepend '/' to turn
80 +# it into '/D:/foo/bar', which is what git expects in file:// URLs on Windows.
81 +# On Unix, the path already starts with '/', so this is a no-op.
82 +pwd_path=$(pwd)
83 +case "$pwd_path" in
84 +[a-zA-Z]:*) pwd_path="/$pwd_path" ;;
85 +esac
86 +
87 +# Allowed characters: alphanumeric, standard path/URI (_ . ~ / : -),
88 +# and those percent-encoded below (% space = , ;)
89 +rest=$(printf "%s" "$pwd_path" | tr -d 'a-zA-Z0-9_.~/:% =,;-')
90 +if test -n "$rest"
91 +then
92 + skip_all="PWD contains unsupported special characters"
93 + test_done
94 +fi
95 +
96 +TRASH_DIRECTORY_URL="file://$pwd_path"
97 +
98 +encoded_path=$(printf "%s" "$pwd_path" |
99 + sed -e 's/%/%25/g' -e 's/ /%20/g' -e 's/=/%3D/g' \
100 + -e 's/;/%3B/g' -e 's/,/%2C/g')
101 +
102 +ENCODED_TRASH_DIRECTORY_URL="file://$encoded_path"
103 +
104 test_expect_success "setup for testing promisor remote advertisement" '
105 # Create another bare repo called "lop" (for Large Object Promisor)
106 git init --bare lop &&
@@ -88,7 +113,7 @@ test_expect_success "setup for testing promisor remote advertisement" '
113 initialize_server 1 "$oid" &&
114
115 # Configure lop as promisor remote for server
91 - git -C server remote add lop "file://$(pwd)/lop" &&
116 + git -C server remote add lop "$TRASH_DIRECTORY_URL/lop" &&
117 git -C server config remote.lop.promisor true &&
118
119 git -C lop config uploadpack.allowFilter true &&
@@ -104,7 +129,7 @@ test_expect_success "clone with promisor.advertise set to 'true'" '
129 # Clone from server to create a client
130 GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
131 -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
107 - -c remote.lop.url="file://$(pwd)/lop" \
132 + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
133 -c promisor.acceptfromserver=All \
134 --no-local --filter="blob:limit=5k" server client &&
135
@@ -119,7 +144,7 @@ test_expect_success "clone with promisor.advertise set to 'false'" '
144 # Clone from server to create a client
145 GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
146 -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
122 - -c remote.lop.url="file://$(pwd)/lop" \
147 + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
148 -c promisor.acceptfromserver=All \
149 --no-local --filter="blob:limit=5k" server client &&
150
@@ -137,7 +162,7 @@ test_expect_success "clone with promisor.acceptfromserver set to 'None'" '
162 # Clone from server to create a client
163 GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
164 -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
140 - -c remote.lop.url="file://$(pwd)/lop" \
165 + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
166 -c promisor.acceptfromserver=None \
167 --no-local --filter="blob:limit=5k" server client &&
168
@@ -156,8 +181,8 @@ test_expect_success "init + fetch with promisor.advertise set to 'true'" '
181 git -C client init &&
182 git -C client config remote.lop.promisor true &&
183 git -C client config remote.lop.fetch "+refs/heads/*:refs/remotes/lop/*" &&
159 - git -C client config remote.lop.url "file://$(pwd)/lop" &&
160 - git -C client config remote.server.url "file://$(pwd)/server" &&
184 + git -C client config remote.lop.url "$TRASH_DIRECTORY_URL/lop" &&
185 + git -C client config remote.server.url "$TRASH_DIRECTORY_URL/server" &&
186 git -C client config remote.server.fetch "+refs/heads/*:refs/remotes/server/*" &&
187 git -C client config promisor.acceptfromserver All &&
188 GIT_NO_LAZY_FETCH=0 git -C client fetch --filter="blob:limit=5k" server &&
@@ -177,10 +202,10 @@ test_expect_success "clone with two promisors but only one advertised" '
202 GIT_TRACE="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \
203 -c remote.unused_lop.promisor=true \
204 -c remote.unused_lop.fetch="+refs/heads/*:refs/remotes/unused_lop/*" \
180 - -c remote.unused_lop.url="file://$(pwd)/unused_lop" \
205 + -c remote.unused_lop.url="$TRASH_DIRECTORY_URL/unused_lop" \
206 -c remote.lop.promisor=true \
207 -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
183 - -c remote.lop.url="file://$(pwd)/lop" \
208 + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
209 -c promisor.acceptfromserver=All \
210 --no-local --filter="blob:limit=5k" server client &&
211
@@ -210,11 +235,11 @@ test_expect_success "init + fetch two promisors but only one advertised" '
235 git -C client init &&
236 git -C client config remote.unused_lop.promisor true &&
237 git -C client config remote.unused_lop.fetch "+refs/heads/*:refs/remotes/unused_lop/*" &&
213 - git -C client config remote.unused_lop.url "file://$(pwd)/unused_lop" &&
238 + git -C client config remote.unused_lop.url "$TRASH_DIRECTORY_URL/unused_lop" &&
239 git -C client config remote.lop.promisor true &&
240 git -C client config remote.lop.fetch "+refs/heads/*:refs/remotes/lop/*" &&
216 - git -C client config remote.lop.url "file://$(pwd)/lop" &&
217 - git -C client config remote.server.url "file://$(pwd)/server" &&
241 + git -C client config remote.lop.url "$TRASH_DIRECTORY_URL/lop" &&
242 + git -C client config remote.server.url "$TRASH_DIRECTORY_URL/server" &&
243 git -C client config remote.server.fetch "+refs/heads/*:refs/remotes/server/*" &&
244 git -C client config promisor.acceptfromserver All &&
245
@@ -242,7 +267,7 @@ test_expect_success "clone with promisor.acceptfromserver set to 'KnownName'" '
267 # Clone from server to create a client
268 GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
269 -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
245 - -c remote.lop.url="file://$(pwd)/lop" \
270 + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
271 -c promisor.acceptfromserver=KnownName \
272 --no-local --filter="blob:limit=5k" server client &&
273
@@ -257,7 +282,7 @@ test_expect_success "clone with 'KnownName' and different remote names" '
282 # Clone from server to create a client
283 GIT_NO_LAZY_FETCH=0 git clone -c remote.serverTwo.promisor=true \
284 -c remote.serverTwo.fetch="+refs/heads/*:refs/remotes/lop/*" \
260 - -c remote.serverTwo.url="file://$(pwd)/lop" \
285 + -c remote.serverTwo.url="$TRASH_DIRECTORY_URL/lop" \
286 -c promisor.acceptfromserver=KnownName \
287 --no-local --filter="blob:limit=5k" server client &&
288
@@ -294,7 +319,7 @@ test_expect_success "clone with promisor.acceptfromserver set to 'KnownUrl'" '
319 # Clone from server to create a client
320 GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
321 -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
297 - -c remote.lop.url="file://$(pwd)/lop" \
322 + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
323 -c promisor.acceptfromserver=KnownUrl \
324 --no-local --filter="blob:limit=5k" server client &&
325
@@ -311,7 +336,7 @@ test_expect_success "clone with 'KnownUrl' and different remote urls" '
336 # Clone from server to create a client
337 GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
338 -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
314 - -c remote.lop.url="file://$(pwd)/serverTwo" \
339 + -c remote.lop.url="$TRASH_DIRECTORY_URL/serverTwo" \
340 -c promisor.acceptfromserver=KnownUrl \
341 --no-local --filter="blob:limit=5k" server client &&
342
@@ -326,7 +351,7 @@ test_expect_success "clone with 'KnownUrl' and url not configured on the server"
351 git -C server config promisor.advertise true &&
352 test_when_finished "rm -rf client" &&
353
329 - test_when_finished "git -C server config set remote.lop.url \"file://$(pwd)/lop\"" &&
354 + test_when_finished "git -C server config set remote.lop.url \"$TRASH_DIRECTORY_URL/lop\"" &&
355 git -C server config unset remote.lop.url &&
356
357 # Clone from server to create a client
@@ -335,7 +360,7 @@ test_expect_success "clone with 'KnownUrl' and url not configured on the server"
360 # missing, so the remote name will be used instead which will fail.
361 test_must_fail env GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
362 -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
338 - -c remote.lop.url="file://$(pwd)/lop" \
363 + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
364 -c promisor.acceptfromserver=KnownUrl \
365 --no-local --filter="blob:limit=5k" server client &&
366
@@ -347,7 +372,7 @@ test_expect_success "clone with 'KnownUrl' and empty url, so not advertised" '
372 git -C server config promisor.advertise true &&
373 test_when_finished "rm -rf client" &&
374
350 - test_when_finished "git -C server config set remote.lop.url \"file://$(pwd)/lop\"" &&
375 + test_when_finished "git -C server config set remote.lop.url \"$TRASH_DIRECTORY_URL/lop\"" &&
376 git -C server config set remote.lop.url "" &&
377
378 # Clone from server to create a client
@@ -356,7 +381,7 @@ test_expect_success "clone with 'KnownUrl' and empty url, so not advertised" '
381 # so the remote name will be used instead which will fail.
382 test_must_fail env GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
383 -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
359 - -c remote.lop.url="file://$(pwd)/lop" \
384 + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
385 -c promisor.acceptfromserver=KnownUrl \
386 --no-local --filter="blob:limit=5k" server client &&
387
@@ -380,13 +405,12 @@ test_expect_success "clone with promisor.sendFields" '
405 GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \
406 -c remote.lop.promisor=true \
407 -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
383 - -c remote.lop.url="file://$(pwd)/lop" \
408 + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
409 -c promisor.acceptfromserver=All \
410 --no-local --filter="blob:limit=5k" server client &&
411
412 # Check that fields are properly transmitted
388 - ENCODED_URL=$(echo "file://$(pwd)/lop" | sed -e "s/ /%20/g") &&
389 - PR1="name=lop,url=$ENCODED_URL,partialCloneFilter=blob:none" &&
413 + PR1="name=lop,url=$ENCODED_TRASH_DIRECTORY_URL/lop,partialCloneFilter=blob:none" &&
414 PR2="name=otherLop,url=https://invalid.invalid,partialCloneFilter=blob:limit=10k,token=fooBar" &&
415 test_grep "clone< promisor-remote=$PR1;$PR2" trace &&
416 test_grep "clone> promisor-remote=lop;otherLop" trace &&
@@ -411,15 +435,14 @@ test_expect_success "clone with promisor.checkFields" '
435 GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \
436 -c remote.lop.promisor=true \
437 -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
414 - -c remote.lop.url="file://$(pwd)/lop" \
438 + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
439 -c remote.lop.partialCloneFilter="blob:none" \
440 -c promisor.acceptfromserver=All \
441 -c promisor.checkFields=partialcloneFilter \
442 --no-local --filter="blob:limit=5k" server client &&
443
444 # Check that fields are properly transmitted
421 - ENCODED_URL=$(echo "file://$(pwd)/lop" | sed -e "s/ /%20/g") &&
422 - PR1="name=lop,url=$ENCODED_URL,partialCloneFilter=blob:none" &&
445 + PR1="name=lop,url=$ENCODED_TRASH_DIRECTORY_URL/lop,partialCloneFilter=blob:none" &&
446 PR2="name=otherLop,url=https://invalid.invalid,partialCloneFilter=blob:limit=10k,token=fooBar" &&
447 test_grep "clone< promisor-remote=$PR1;$PR2" trace &&
448 test_grep "clone> promisor-remote=lop" trace &&
@@ -449,7 +472,7 @@ test_expect_success "clone with promisor.storeFields=partialCloneFilter" '
472 GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \
473 -c remote.lop.promisor=true \
474 -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
452 - -c remote.lop.url="file://$(pwd)/lop" \
475 + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
476 -c remote.lop.token="fooYYY" \
477 -c remote.lop.partialCloneFilter="blob:none" \
478 -c promisor.acceptfromserver=All \
@@ -501,7 +524,7 @@ test_expect_success "clone and fetch with --filter=auto" '
524
525 GIT_TRACE_PACKET="$(pwd)/trace" GIT_NO_LAZY_FETCH=0 git clone \
526 -c remote.lop.promisor=true \
504 - -c remote.lop.url="file://$(pwd)/lop" \
527 + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
528 -c promisor.acceptfromserver=All \
529 --no-local --filter=auto server client 2>err &&
530
@@ -558,7 +581,7 @@ test_expect_success "clone with promisor.advertise set to 'true' but don't delet
581 # Clone from server to create a client
582 GIT_NO_LAZY_FETCH=0 git clone -c remote.lop.promisor=true \
583 -c remote.lop.fetch="+refs/heads/*:refs/remotes/lop/*" \
561 - -c remote.lop.url="file://$(pwd)/lop" \
584 + -c remote.lop.url="$TRASH_DIRECTORY_URL/lop" \
585 -c promisor.acceptfromserver=All \
586 --no-local --filter="blob:limit=5k" server client &&
587