Git fork
1#!/bin/sh
2#
3# Copyright (c) 2020 Jiang Xin
4#
5test_description='Test git push porcelain output'
6
7. ./test-lib.sh
8
9# Create commits in <repo> and assign each commit's oid to shell variables
10# given in the arguments (A, B, and C). E.g.:
11#
12# create_commits_in <repo> A B C
13#
14# NOTE: Never calling this function from a subshell since variable
15# assignments will disappear when subshell exits.
16create_commits_in () {
17 repo="$1" && test -d "$repo" ||
18 error "Repository $repo does not exist."
19 shift &&
20 while test $# -gt 0
21 do
22 name=$1 &&
23 shift &&
24 test_commit -C "$repo" --no-tag "$name" &&
25 eval $name=$(git -C "$repo" rev-parse HEAD)
26 done
27}
28
29get_abbrev_oid () {
30 oid=$1 &&
31 suffix=${oid#???????} &&
32 oid=${oid%$suffix} &&
33 if test -n "$oid"
34 then
35 echo "$oid"
36 else
37 echo "undefined-oid"
38 fi
39}
40
41# Format the output of git-push, git-show-ref and other commands to make a
42# user-friendly and stable text. We can easily prepare the expect text
43# without having to worry about future changes of the commit ID and spaces
44# of the output.
45make_user_friendly_and_stable_output () {
46 sed \
47 -e "s/$(get_abbrev_oid $A)[0-9a-f]*/<COMMIT-A>/g" \
48 -e "s/$(get_abbrev_oid $B)[0-9a-f]*/<COMMIT-B>/g" \
49 -e "s/$ZERO_OID/<ZERO-OID>/g" \
50 -e "s#To $URL_PREFIX/upstream.git#To <URL/of/upstream.git>#"
51}
52
53format_and_save_expect () {
54 sed -e 's/^> //' -e 's/Z$//' >expect
55}
56
57create_upstream_template () {
58 git init --bare upstream-template.git &&
59 git clone upstream-template.git tmp_work_dir &&
60 create_commits_in tmp_work_dir A B &&
61 (
62 cd tmp_work_dir &&
63 git push origin \
64 $B:refs/heads/main \
65 $A:refs/heads/foo \
66 $A:refs/heads/bar \
67 $A:refs/heads/baz
68 ) &&
69 rm -rf tmp_work_dir
70}
71
72setup_upstream () {
73 if test $# -ne 1
74 then
75 BUG "location of upstream repository is not provided"
76 fi &&
77 rm -rf "$1" &&
78 if ! test -d upstream-template.git
79 then
80 create_upstream_template
81 fi &&
82 git clone --mirror upstream-template.git "$1" &&
83 # The upstream repository provides services using the HTTP protocol.
84 if ! test "$1" = "upstream.git"
85 then
86 git -C "$1" config http.receivepack true
87 fi
88}
89
90setup_upstream_and_workbench () {
91 if test $# -ne 1
92 then
93 BUG "location of upstream repository is not provided"
94 fi
95 upstream="$1"
96
97 # Upstream after setup: main(B) foo(A) bar(A) baz(A)
98 # Workbench after setup: main(A) baz(A) next(A)
99 test_expect_success "setup upstream repository and workbench" '
100 setup_upstream "$upstream" &&
101 rm -rf workbench &&
102 git clone "$upstream" workbench &&
103 (
104 cd workbench &&
105 git update-ref refs/heads/main $A &&
106 git update-ref refs/heads/baz $A &&
107 git update-ref refs/heads/next $A &&
108 # Try to make a stable fixed width for abbreviated commit ID,
109 # this fixed-width oid will be replaced with "<OID>".
110 git config core.abbrev 7 &&
111 git config advice.pushUpdateRejected false
112 ) &&
113 # The upstream repository provides services using the HTTP protocol.
114 if ! test "$upstream" = "upstream.git"
115 then
116 git -C workbench remote set-url origin "$HTTPD_URL/smart/upstream.git"
117 fi
118 '
119}
120
121run_git_push_porcelain_output_test() {
122 case $1 in
123 http)
124 PROTOCOL="HTTP protocol"
125 URL_PREFIX="http://.*"
126 ;;
127 file)
128 PROTOCOL="builtin protocol"
129 URL_PREFIX=".*"
130 ;;
131 esac
132
133 # Refs of upstream : main(B) foo(A) bar(A) baz(A)
134 # Refs of workbench: main(A) baz(A) next(A)
135 # git-push : main(A) NULL (B) baz(A) next(A)
136 test_expect_success ".. git-push --porcelain ($PROTOCOL)" '
137 test_when_finished "setup_upstream \"$upstream\"" &&
138 test_must_fail git -C workbench push --porcelain origin \
139 main \
140 :refs/heads/foo \
141 $B:bar \
142 baz \
143 next >out &&
144 make_user_friendly_and_stable_output <out >actual &&
145 format_and_save_expect <<-\EOF &&
146 > To <URL/of/upstream.git>
147 > = refs/heads/baz:refs/heads/baz [up to date]
148 > <COMMIT-B>:refs/heads/bar <COMMIT-A>..<COMMIT-B>
149 > - :refs/heads/foo [deleted]
150 > * refs/heads/next:refs/heads/next [new branch]
151 > ! refs/heads/main:refs/heads/main [rejected] (non-fast-forward)
152 > Done
153 EOF
154 test_cmp expect actual &&
155
156 git -C "$upstream" show-ref >out &&
157 make_user_friendly_and_stable_output <out >actual &&
158 cat >expect <<-EOF &&
159 <COMMIT-B> refs/heads/bar
160 <COMMIT-A> refs/heads/baz
161 <COMMIT-B> refs/heads/main
162 <COMMIT-A> refs/heads/next
163 EOF
164 test_cmp expect actual
165 '
166
167 # Refs of upstream : main(B) foo(A) bar(A) baz(A)
168 # Refs of workbench: main(A) baz(A) next(A)
169 # git-push : main(A) NULL (B) baz(A) next(A)
170 test_expect_success ".. git-push --porcelain --force ($PROTOCOL)" '
171 test_when_finished "setup_upstream \"$upstream\"" &&
172 git -C workbench push --porcelain --force origin \
173 main \
174 :refs/heads/foo \
175 $B:bar \
176 baz \
177 next >out &&
178 make_user_friendly_and_stable_output <out >actual &&
179 format_and_save_expect <<-EOF &&
180 > To <URL/of/upstream.git>
181 > = refs/heads/baz:refs/heads/baz [up to date]
182 > <COMMIT-B>:refs/heads/bar <COMMIT-A>..<COMMIT-B>
183 > - :refs/heads/foo [deleted]
184 > + refs/heads/main:refs/heads/main <COMMIT-B>...<COMMIT-A> (forced update)
185 > * refs/heads/next:refs/heads/next [new branch]
186 > Done
187 EOF
188 test_cmp expect actual &&
189
190 git -C "$upstream" show-ref >out &&
191 make_user_friendly_and_stable_output <out >actual &&
192 cat >expect <<-EOF &&
193 <COMMIT-B> refs/heads/bar
194 <COMMIT-A> refs/heads/baz
195 <COMMIT-A> refs/heads/main
196 <COMMIT-A> refs/heads/next
197 EOF
198 test_cmp expect actual
199 '
200
201 # Refs of upstream : main(B) foo(A) bar(A) baz(A)
202 # Refs of workbench: main(A) baz(A) next(A)
203 # git-push : main(A) NULL (B) baz(A) next(A)
204 test_expect_success ".. git push --porcelain --atomic ($PROTOCOL)" '
205 test_when_finished "setup_upstream \"$upstream\"" &&
206 test_must_fail git -C workbench push --porcelain --atomic origin \
207 main \
208 :refs/heads/foo \
209 $B:bar \
210 baz \
211 next >out &&
212 make_user_friendly_and_stable_output <out >actual &&
213 format_and_save_expect <<-EOF &&
214 > To <URL/of/upstream.git>
215 > = refs/heads/baz:refs/heads/baz [up to date]
216 > ! <COMMIT-B>:refs/heads/bar [rejected] (atomic push failed)
217 > ! (delete):refs/heads/foo [rejected] (atomic push failed)
218 > ! refs/heads/main:refs/heads/main [rejected] (non-fast-forward)
219 > ! refs/heads/next:refs/heads/next [rejected] (atomic push failed)
220 > Done
221 EOF
222 test_cmp expect actual &&
223
224 git -C "$upstream" show-ref >out &&
225 make_user_friendly_and_stable_output <out >actual &&
226 cat >expect <<-EOF &&
227 <COMMIT-A> refs/heads/bar
228 <COMMIT-A> refs/heads/baz
229 <COMMIT-A> refs/heads/foo
230 <COMMIT-B> refs/heads/main
231 EOF
232 test_cmp expect actual
233 '
234
235 # Refs of upstream : main(B) foo(A) bar(A) baz(A)
236 # Refs of workbench: main(A) baz(A) next(A)
237 # git-push : main(A) NULL (B) baz(A) next(A)
238 test_expect_success ".. pre-receive hook declined ($PROTOCOL)" '
239 test_when_finished "rm -f \"$upstream/hooks/pre-receive\" &&
240 setup_upstream \"$upstream\"" &&
241 test_hook --setup -C "$upstream" pre-receive <<-EOF &&
242 exit 1
243 EOF
244 test_must_fail git -C workbench push --porcelain --force origin \
245 main \
246 :refs/heads/foo \
247 $B:bar \
248 baz \
249 next >out &&
250 make_user_friendly_and_stable_output <out >actual &&
251 format_and_save_expect <<-EOF &&
252 > To <URL/of/upstream.git>
253 > = refs/heads/baz:refs/heads/baz [up to date]
254 > ! <COMMIT-B>:refs/heads/bar [remote rejected] (pre-receive hook declined)
255 > ! :refs/heads/foo [remote rejected] (pre-receive hook declined)
256 > ! refs/heads/main:refs/heads/main [remote rejected] (pre-receive hook declined)
257 > ! refs/heads/next:refs/heads/next [remote rejected] (pre-receive hook declined)
258 > Done
259 EOF
260 test_cmp expect actual &&
261
262 git -C "$upstream" show-ref >out &&
263 make_user_friendly_and_stable_output <out >actual &&
264 cat >expect <<-EOF &&
265 <COMMIT-A> refs/heads/bar
266 <COMMIT-A> refs/heads/baz
267 <COMMIT-A> refs/heads/foo
268 <COMMIT-B> refs/heads/main
269 EOF
270 test_cmp expect actual
271 '
272
273 # Refs of upstream : main(B) foo(A) bar(A) baz(A)
274 # Refs of workbench: main(A) baz(A) next(A)
275 # git-push : main(A) next(A)
276 test_expect_success ".. non-fastforward push ($PROTOCOL)" '
277 test_when_finished "setup_upstream \"$upstream\"" &&
278 (
279 cd workbench &&
280 test_must_fail git push --porcelain origin \
281 main \
282 next
283 ) >out &&
284 make_user_friendly_and_stable_output <out >actual &&
285 format_and_save_expect <<-EOF &&
286 > To <URL/of/upstream.git>
287 > * refs/heads/next:refs/heads/next [new branch]
288 > ! refs/heads/main:refs/heads/main [rejected] (non-fast-forward)
289 > Done
290 EOF
291 test_cmp expect actual &&
292
293 git -C "$upstream" show-ref >out &&
294 make_user_friendly_and_stable_output <out >actual &&
295 cat >expect <<-EOF &&
296 <COMMIT-A> refs/heads/bar
297 <COMMIT-A> refs/heads/baz
298 <COMMIT-A> refs/heads/foo
299 <COMMIT-B> refs/heads/main
300 <COMMIT-A> refs/heads/next
301 EOF
302 test_cmp expect actual
303 '
304
305 # Refs of upstream : main(B) foo(A) bar(A) baz(A)
306 # Refs of workbench: main(A) baz(A) next(A)
307 # git-push : main(A) NULL (B) baz(A) next(A)
308 test_expect_success ".. git push --porcelain --atomic --force ($PROTOCOL)" '
309 git -C workbench push --porcelain --atomic --force origin \
310 main \
311 :refs/heads/foo \
312 $B:bar \
313 baz \
314 next >out &&
315 make_user_friendly_and_stable_output <out >actual &&
316 format_and_save_expect <<-\EOF &&
317 > To <URL/of/upstream.git>
318 > = refs/heads/baz:refs/heads/baz [up to date]
319 > <COMMIT-B>:refs/heads/bar <COMMIT-A>..<COMMIT-B>
320 > - :refs/heads/foo [deleted]
321 > + refs/heads/main:refs/heads/main <COMMIT-B>...<COMMIT-A> (forced update)
322 > * refs/heads/next:refs/heads/next [new branch]
323 > Done
324 EOF
325 test_cmp expect actual &&
326
327 git -C "$upstream" show-ref >out &&
328 make_user_friendly_and_stable_output <out >actual &&
329 cat >expect <<-EOF &&
330 <COMMIT-B> refs/heads/bar
331 <COMMIT-A> refs/heads/baz
332 <COMMIT-A> refs/heads/main
333 <COMMIT-A> refs/heads/next
334 EOF
335 test_cmp expect actual
336 '
337}
338
339run_git_push_dry_run_porcelain_output_test() {
340 case $1 in
341 http)
342 PROTOCOL="HTTP protocol"
343 URL_PREFIX="http://.*"
344 ;;
345 file)
346 PROTOCOL="builtin protocol"
347 URL_PREFIX=".*"
348 ;;
349 esac
350
351 # Refs of upstream : main(B) foo(A) bar(A) baz(A)
352 # Refs of workbench: main(A) baz(A) next(A)
353 # git-push : main(A) NULL (B) baz(A) next(A)
354 test_expect_success ".. git-push --porcelain --dry-run ($PROTOCOL)" '
355 test_must_fail git -C workbench push --porcelain --dry-run origin \
356 main \
357 :refs/heads/foo \
358 $B:bar \
359 baz \
360 next >out &&
361 make_user_friendly_and_stable_output <out >actual &&
362 format_and_save_expect <<-EOF &&
363 > To <URL/of/upstream.git>
364 > = refs/heads/baz:refs/heads/baz [up to date]
365 > <COMMIT-B>:refs/heads/bar <COMMIT-A>..<COMMIT-B>
366 > - :refs/heads/foo [deleted]
367 > * refs/heads/next:refs/heads/next [new branch]
368 > ! refs/heads/main:refs/heads/main [rejected] (non-fast-forward)
369 > Done
370 EOF
371 test_cmp expect actual &&
372
373 git -C "$upstream" show-ref >out &&
374 make_user_friendly_and_stable_output <out >actual &&
375 cat >expect <<-EOF &&
376 <COMMIT-A> refs/heads/bar
377 <COMMIT-A> refs/heads/baz
378 <COMMIT-A> refs/heads/foo
379 <COMMIT-B> refs/heads/main
380 EOF
381 test_cmp expect actual
382 '
383
384 # Refs of upstream : main(B) foo(A) bar(A) baz(A)
385 # Refs of workbench: main(A) baz(A) next(A)
386 # push : main(A) NULL (B) baz(A) next(A)
387 test_expect_success ".. git-push --porcelain --dry-run --force ($PROTOCOL)" '
388 git -C workbench push --porcelain --dry-run --force origin \
389 main \
390 :refs/heads/foo \
391 $B:bar \
392 baz \
393 next >out &&
394 make_user_friendly_and_stable_output <out >actual &&
395 format_and_save_expect <<-EOF &&
396 > To <URL/of/upstream.git>
397 > = refs/heads/baz:refs/heads/baz [up to date]
398 > <COMMIT-B>:refs/heads/bar <COMMIT-A>..<COMMIT-B>
399 > - :refs/heads/foo [deleted]
400 > + refs/heads/main:refs/heads/main <COMMIT-B>...<COMMIT-A> (forced update)
401 > * refs/heads/next:refs/heads/next [new branch]
402 > Done
403 EOF
404 test_cmp expect actual &&
405
406 git -C "$upstream" show-ref >out &&
407 make_user_friendly_and_stable_output <out >actual &&
408 cat >expect <<-EOF &&
409 <COMMIT-A> refs/heads/bar
410 <COMMIT-A> refs/heads/baz
411 <COMMIT-A> refs/heads/foo
412 <COMMIT-B> refs/heads/main
413 EOF
414 test_cmp expect actual
415 '
416
417 # Refs of upstream : main(B) foo(A) bar(A) baz(A)
418 # Refs of workbench: main(A) baz(A) next(A)
419 # git-push : main(A) NULL (B) baz(A) next(A)
420 test_expect_success ".. git-push --porcelain --dry-run --atomic ($PROTOCOL)" '
421 test_must_fail git -C workbench push --porcelain --dry-run --atomic origin \
422 main \
423 :refs/heads/foo \
424 $B:bar \
425 baz \
426 next >out &&
427 make_user_friendly_and_stable_output <out >actual &&
428 format_and_save_expect <<-EOF &&
429 > To <URL/of/upstream.git>
430 > = refs/heads/baz:refs/heads/baz [up to date]
431 > ! <COMMIT-B>:refs/heads/bar [rejected] (atomic push failed)
432 > ! (delete):refs/heads/foo [rejected] (atomic push failed)
433 > ! refs/heads/main:refs/heads/main [rejected] (non-fast-forward)
434 > ! refs/heads/next:refs/heads/next [rejected] (atomic push failed)
435 > Done
436 EOF
437 test_cmp expect actual &&
438
439 git -C "$upstream" show-ref >out &&
440 make_user_friendly_and_stable_output <out >actual &&
441 cat >expect <<-EOF &&
442 <COMMIT-A> refs/heads/bar
443 <COMMIT-A> refs/heads/baz
444 <COMMIT-A> refs/heads/foo
445 <COMMIT-B> refs/heads/main
446 EOF
447 test_cmp expect actual
448 '
449
450 # Refs of upstream : main(B) foo(A) bar(A) baz(A)
451 # Refs of workbench: main(A) baz(A) next(A)
452 # push : main(A) NULL (B) baz(A) next(A)
453 test_expect_success ".. git-push --porcelain --dry-run --atomic --force ($PROTOCOL)" '
454 git -C workbench push --porcelain --dry-run --atomic --force origin \
455 main \
456 :refs/heads/foo \
457 $B:bar \
458 baz \
459 next >out &&
460 make_user_friendly_and_stable_output <out >actual &&
461 format_and_save_expect <<-EOF &&
462 > To <URL/of/upstream.git>
463 > = refs/heads/baz:refs/heads/baz [up to date]
464 > <COMMIT-B>:refs/heads/bar <COMMIT-A>..<COMMIT-B>
465 > - :refs/heads/foo [deleted]
466 > + refs/heads/main:refs/heads/main <COMMIT-B>...<COMMIT-A> (forced update)
467 > * refs/heads/next:refs/heads/next [new branch]
468 > Done
469 EOF
470 test_cmp expect actual &&
471
472 git -C "$upstream" show-ref >out &&
473 make_user_friendly_and_stable_output <out >actual &&
474 cat >expect <<-EOF &&
475 <COMMIT-A> refs/heads/bar
476 <COMMIT-A> refs/heads/baz
477 <COMMIT-A> refs/heads/foo
478 <COMMIT-B> refs/heads/main
479 EOF
480 test_cmp expect actual
481 '
482}
483
484setup_upstream_and_workbench upstream.git
485
486run_git_push_porcelain_output_test file
487
488setup_upstream_and_workbench upstream.git
489
490run_git_push_dry_run_porcelain_output_test file
491
492ROOT_PATH="$PWD"
493. "$TEST_DIRECTORY"/lib-gpg.sh
494. "$TEST_DIRECTORY"/lib-httpd.sh
495. "$TEST_DIRECTORY"/lib-terminal.sh
496start_httpd
497setup_askpass_helper
498
499setup_upstream_and_workbench "$HTTPD_DOCUMENT_ROOT_PATH/upstream.git"
500
501run_git_push_porcelain_output_test http
502
503setup_upstream_and_workbench "$HTTPD_DOCUMENT_ROOT_PATH/upstream.git"
504
505run_git_push_dry_run_porcelain_output_test http
506
507test_done