From b1be3953e5ff5c85853e184d16cb213e8f9c4623 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 3 Feb 2025 07:29:31 +0100 Subject: t5504: modernize test by moving heredocs into test bodies We have several heredocs in t5504 located outside of any particular test bodies. Move these into the test bodies to match our modern coding style. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- t/t5504-fetch-receive-strict.sh | 35 ++++++++++++++++------------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/t/t5504-fetch-receive-strict.sh b/t/t5504-fetch-receive-strict.sh index e273ab29c7..58074506c5 100755 --- a/t/t5504-fetch-receive-strict.sh +++ b/t/t5504-fetch-receive-strict.sh @@ -64,12 +64,6 @@ test_expect_success 'fetch with transfer.fsckobjects' ' ) ' -cat >exp <exp <<-\EOF && + To dst + ! refs/heads/main:refs/heads/test [remote rejected] (missing necessary objects) + Done + EOF test_must_fail git push --porcelain dst main:refs/heads/test >act && test_cmp exp act ' @@ -94,11 +93,6 @@ test_expect_success 'push with !receive.fsckobjects' ' test_cmp exp act ' -cat >exp <exp <<-\EOF && + To dst + ! refs/heads/main:refs/heads/test [remote rejected] (unpacker error) + EOF test_must_fail git push --porcelain dst main:refs/heads/test >act && test_cmp exp act ' @@ -129,15 +127,14 @@ test_expect_success 'repair the "corrupt or missing" object' ' git fsck ' -cat >bogus-commit < 1234567890 +0000 - -This commit object intentionally broken -EOF - test_expect_success 'setup bogus commit' ' + cat >bogus-commit <<-EOF && + tree $EMPTY_TREE + author Bugs Bunny 1234567890 +0000 + committer Bugs Bunny 1234567890 +0000 + + This commit object intentionally broken + EOF commit="$(git hash-object --literally -t commit -w --stdin Date: Mon, 3 Feb 2025 07:29:32 +0100 Subject: t5548: refactor to reuse setup_upstream() function Refactor the function setup_upstream_and_workbench(), extracting create_upstream_template() and setup_upstream() from it. The former is used to create the upstream repository template, while the latter is used to rebuild the upstream repository and will be reused in subsequent commits. To ensure that setup_upstream() works properly in both local and HTTP protocols, the HTTP settings have been moved to the setup_upstream() and setup_upstream_and_workbench() functions. Signed-off-by: Jiang Xin Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- t/t5548-push-porcelain.sh | 83 ++++++++++++++++++++++++++++++----------------- 1 file changed, 53 insertions(+), 30 deletions(-) diff --git a/t/t5548-push-porcelain.sh b/t/t5548-push-porcelain.sh index 6282728eaf..1bf4d48cd9 100755 --- a/t/t5548-push-porcelain.sh +++ b/t/t5548-push-porcelain.sh @@ -54,29 +54,65 @@ format_and_save_expect () { sed -e 's/^> //' -e 's/Z$//' >expect } +create_upstream_template () { + git init --bare upstream-template.git && + git clone upstream-template.git tmp_work_dir && + create_commits_in tmp_work_dir A B && + ( + cd tmp_work_dir && + git push origin \ + $B:refs/heads/main \ + $A:refs/heads/foo \ + $A:refs/heads/bar \ + $A:refs/heads/baz + ) && + rm -rf tmp_work_dir +} + +setup_upstream () { + if test $# -ne 1 + then + BUG "location of upstream repository is not provided" + fi && + rm -rf "$1" && + if ! test -d upstream-template.git + then + create_upstream_template + fi && + git clone --mirror upstream-template.git "$1" && + # The upstream repository provides services using the HTTP protocol. + if ! test "$1" = "upstream.git" + then + git -C "$1" config http.receivepack true + fi +} + setup_upstream_and_workbench () { + if test $# -ne 1 + then + BUG "location of upstream repository is not provided" + fi + upstream="$1" + # Upstream after setup : main(B) foo(A) bar(A) baz(A) # Workbench after setup : main(A) test_expect_success "setup upstream repository and workbench" ' - rm -rf upstream.git workbench && - git init --bare upstream.git && - git init workbench && - create_commits_in workbench A B && + setup_upstream "$upstream" && + rm -rf workbench && + git clone "$upstream" workbench && ( cd workbench && + git update-ref refs/heads/main $A && # Try to make a stable fixed width for abbreviated commit ID, # this fixed-width oid will be replaced with "". git config core.abbrev 7 && - git remote add origin ../upstream.git && - git update-ref refs/heads/main $A && - git push origin \ - $B:refs/heads/main \ - $A:refs/heads/foo \ - $A:refs/heads/bar \ - $A:refs/heads/baz + git config advice.pushUpdateRejected false ) && - git -C "workbench" config advice.pushUpdateRejected false && - upstream=upstream.git + # The upstream repository provides services using the HTTP protocol. + if ! test "$upstream" = "upstream.git" + then + git -C workbench remote set-url origin "$HTTPD_URL/smart/upstream.git" + fi ' } @@ -88,7 +124,7 @@ run_git_push_porcelain_output_test() { ;; file) PROTOCOL="builtin protocol" - URL_PREFIX="\.\." + URL_PREFIX=".*" ;; esac @@ -247,10 +283,8 @@ run_git_push_porcelain_output_test() { ' } -# Initialize the upstream repository and local workbench. -setup_upstream_and_workbench +setup_upstream_and_workbench upstream.git -# Run git-push porcelain test on builtin protocol run_git_push_porcelain_output_test file ROOT_PATH="$PWD" @@ -258,21 +292,10 @@ ROOT_PATH="$PWD" . "$TEST_DIRECTORY"/lib-httpd.sh . "$TEST_DIRECTORY"/lib-terminal.sh start_httpd - -# Re-initialize the upstream repository and local workbench. -setup_upstream_and_workbench - -test_expect_success "setup for http" ' - git -C upstream.git config http.receivepack true && - upstream="$HTTPD_DOCUMENT_ROOT_PATH/upstream.git" && - mv upstream.git "$upstream" && - - git -C workbench remote set-url origin $HTTPD_URL/smart/upstream.git -' - setup_askpass_helper -# Run git-push porcelain test on HTTP protocol +setup_upstream_and_workbench "$HTTPD_DOCUMENT_ROOT_PATH/upstream.git" + run_git_push_porcelain_output_test http test_done -- cgit v1.2.3 From bc0f5939a58fda328bdba70eb64a19c969b9f8d8 Mon Sep 17 00:00:00 2001 From: Jiang Xin Date: Mon, 3 Feb 2025 07:29:33 +0100 Subject: t5548: refactor test cases by resetting upstream Refactor the test cases with the following changes: - Calling setup_upstream() to reset upstream after running each test case. - Change the initial branch tips of the workspace to reduce the branch setup operations in the workspace. - Reduced the two steps of setting up and cleaning up the pre-receive hook by moving the operations into the corresponding test case, Signed-off-by: Jiang Xin Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- t/t5548-push-porcelain.sh | 149 +++++++++++++++++++++------------------------- 1 file changed, 67 insertions(+), 82 deletions(-) diff --git a/t/t5548-push-porcelain.sh b/t/t5548-push-porcelain.sh index 1bf4d48cd9..f9aeb5a9d9 100755 --- a/t/t5548-push-porcelain.sh +++ b/t/t5548-push-porcelain.sh @@ -94,8 +94,8 @@ setup_upstream_and_workbench () { fi upstream="$1" - # Upstream after setup : main(B) foo(A) bar(A) baz(A) - # Workbench after setup : main(A) + # Upstream after setup: main(B) foo(A) bar(A) baz(A) + # Workbench after setup: main(A) baz(A) next(A) test_expect_success "setup upstream repository and workbench" ' setup_upstream "$upstream" && rm -rf workbench && @@ -103,6 +103,8 @@ setup_upstream_and_workbench () { ( cd workbench && git update-ref refs/heads/main $A && + git update-ref refs/heads/baz $A && + git update-ref refs/heads/next $A && # Try to make a stable fixed width for abbreviated commit ID, # this fixed-width oid will be replaced with "". git config core.abbrev 7 && @@ -131,19 +133,14 @@ run_git_push_porcelain_output_test() { # Refs of upstream : main(B) foo(A) bar(A) baz(A) # Refs of workbench: main(A) baz(A) next(A) # git-push : main(A) NULL (B) baz(A) next(A) - test_expect_success "porcelain output of successful git-push ($PROTOCOL)" ' - ( - cd workbench && - git update-ref refs/heads/main $A && - git update-ref refs/heads/baz $A && - git update-ref refs/heads/next $A && - git push --porcelain --force origin \ - main \ - :refs/heads/foo \ - $B:bar \ - baz \ - next - ) >out && + test_expect_success ".. git-push --porcelain --force ($PROTOCOL)" ' + test_when_finished "setup_upstream \"$upstream\"" && + git -C workbench push --porcelain --force origin \ + main \ + :refs/heads/foo \ + $B:bar \ + baz \ + next >out && make_user_friendly_and_stable_output actual && format_and_save_expect <<-EOF && > To @@ -167,115 +164,103 @@ run_git_push_porcelain_output_test() { test_cmp expect actual ' - # Refs of upstream : main(A) bar(B) baz(A) next(A) - # Refs of workbench: main(B) bar(A) baz(A) next(A) - # git-push : main(B) bar(A) NULL next(A) - test_expect_success "atomic push failed ($PROTOCOL)" ' - ( - cd workbench && - git update-ref refs/heads/main $B && - git update-ref refs/heads/bar $A && - test_must_fail git push --atomic --porcelain origin \ - main \ - bar \ - :baz \ - next - ) >out && + # Refs of upstream : main(B) foo(A) bar(A) baz(A) + # Refs of workbench: main(A) baz(A) next(A) + # git-push : main(A) NULL (B) baz(A) next(A) + test_expect_success ".. git push --porcelain --atomic ($PROTOCOL)" ' + test_when_finished "setup_upstream \"$upstream\"" && + test_must_fail git -C workbench push --porcelain --atomic origin \ + main \ + :refs/heads/foo \ + $B:bar \ + baz \ + next >out && make_user_friendly_and_stable_output actual && format_and_save_expect <<-EOF && - To - > = refs/heads/next:refs/heads/next [up to date] - > ! refs/heads/bar:refs/heads/bar [rejected] (non-fast-forward) - > ! (delete):refs/heads/baz [rejected] (atomic push failed) - > ! refs/heads/main:refs/heads/main [rejected] (atomic push failed) - Done + > To + > = refs/heads/baz:refs/heads/baz [up to date] + > ! :refs/heads/bar [rejected] (atomic push failed) + > ! (delete):refs/heads/foo [rejected] (atomic push failed) + > ! refs/heads/main:refs/heads/main [rejected] (non-fast-forward) + > ! refs/heads/next:refs/heads/next [rejected] (atomic push failed) + > Done EOF test_cmp expect actual && git -C "$upstream" show-ref >out && make_user_friendly_and_stable_output actual && cat >expect <<-EOF && - refs/heads/bar + refs/heads/bar refs/heads/baz - refs/heads/main - refs/heads/next + refs/heads/foo + refs/heads/main EOF test_cmp expect actual ' - test_expect_success "prepare pre-receive hook ($PROTOCOL)" ' - test_hook --setup -C "$upstream" pre-receive <<-EOF - exit 1 + # Refs of upstream : main(B) foo(A) bar(A) baz(A) + # Refs of workbench: main(A) baz(A) next(A) + # git-push : main(A) NULL (B) baz(A) next(A) + test_expect_success ".. pre-receive hook declined ($PROTOCOL)" ' + test_when_finished "rm -f \"$upstream/hooks/pre-receive\" && + setup_upstream \"$upstream\"" && + test_hook --setup -C "$upstream" pre-receive <<-EOF && + exit 1 EOF - ' - - # Refs of upstream : main(A) bar(B) baz(A) next(A) - # Refs of workbench: main(B) bar(A) baz(A) next(A) - # git-push : main(B) bar(A) NULL next(A) - test_expect_success "pre-receive hook declined ($PROTOCOL)" ' - ( - cd workbench && - git update-ref refs/heads/main $B && - git update-ref refs/heads/bar $A && - test_must_fail git push --porcelain --force origin \ - main \ - bar \ - :baz \ - next - ) >out && + test_must_fail git -C workbench push --porcelain --force origin \ + main \ + :refs/heads/foo \ + $B:bar \ + baz \ + next >out && make_user_friendly_and_stable_output actual && format_and_save_expect <<-EOF && - To - > = refs/heads/next:refs/heads/next [up to date] - > ! refs/heads/bar:refs/heads/bar [remote rejected] (pre-receive hook declined) - > ! :refs/heads/baz [remote rejected] (pre-receive hook declined) + > To + > = refs/heads/baz:refs/heads/baz [up to date] + > ! :refs/heads/bar [remote rejected] (pre-receive hook declined) + > ! :refs/heads/foo [remote rejected] (pre-receive hook declined) > ! refs/heads/main:refs/heads/main [remote rejected] (pre-receive hook declined) - Done + > ! refs/heads/next:refs/heads/next [remote rejected] (pre-receive hook declined) + > Done EOF test_cmp expect actual && git -C "$upstream" show-ref >out && make_user_friendly_and_stable_output actual && cat >expect <<-EOF && - refs/heads/bar + refs/heads/bar refs/heads/baz - refs/heads/main - refs/heads/next + refs/heads/foo + refs/heads/main EOF test_cmp expect actual ' - test_expect_success "remove pre-receive hook ($PROTOCOL)" ' - rm "$upstream/hooks/pre-receive" - ' - - # Refs of upstream : main(A) bar(B) baz(A) next(A) - # Refs of workbench: main(B) bar(A) baz(A) next(A) - # git-push : main(B) bar(A) NULL next(A) - test_expect_success "non-fastforward push ($PROTOCOL)" ' + # Refs of upstream : main(B) foo(A) bar(A) baz(A) + # Refs of workbench: main(A) baz(A) next(A) + # git-push : main(A) next(A) + test_expect_success ".. non-fastforward push ($PROTOCOL)" ' ( cd workbench && test_must_fail git push --porcelain origin \ main \ - bar \ - :baz \ next ) >out && make_user_friendly_and_stable_output actual && format_and_save_expect <<-EOF && - To - > = refs/heads/next:refs/heads/next [up to date] - > - :refs/heads/baz [deleted] - > refs/heads/main:refs/heads/main .. - > ! refs/heads/bar:refs/heads/bar [rejected] (non-fast-forward) - Done + > To + > * refs/heads/next:refs/heads/next [new branch] + > ! refs/heads/main:refs/heads/main [rejected] (non-fast-forward) + > Done EOF test_cmp expect actual && git -C "$upstream" show-ref >out && make_user_friendly_and_stable_output actual && cat >expect <<-EOF && - refs/heads/bar + refs/heads/bar + refs/heads/baz + refs/heads/foo refs/heads/main refs/heads/next EOF -- cgit v1.2.3 From 2329b6b461d8290f4706658ff080a888b74e9aef Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 3 Feb 2025 07:29:34 +0100 Subject: t5548: add new porcelain test cases Add two more test cases exercising git-push(1) with `--procelain`, one exercising a non-atomic and one exercising an atomic push. Based-on-patch-by: Jiang Xin Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- t/t5548-push-porcelain.sh | 68 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/t/t5548-push-porcelain.sh b/t/t5548-push-porcelain.sh index f9aeb5a9d9..5d724145d2 100755 --- a/t/t5548-push-porcelain.sh +++ b/t/t5548-push-porcelain.sh @@ -130,6 +130,40 @@ run_git_push_porcelain_output_test() { ;; esac + # Refs of upstream : main(B) foo(A) bar(A) baz(A) + # Refs of workbench: main(A) baz(A) next(A) + # git-push : main(A) NULL (B) baz(A) next(A) + test_expect_success ".. git-push --porcelain ($PROTOCOL)" ' + test_when_finished "setup_upstream \"$upstream\"" && + test_must_fail git -C workbench push --porcelain origin \ + main \ + :refs/heads/foo \ + $B:bar \ + baz \ + next >out && + make_user_friendly_and_stable_output actual && + format_and_save_expect <<-\EOF && + > To + > = refs/heads/baz:refs/heads/baz [up to date] + > :refs/heads/bar .. + > - :refs/heads/foo [deleted] + > * refs/heads/next:refs/heads/next [new branch] + > ! refs/heads/main:refs/heads/main [rejected] (non-fast-forward) + > Done + EOF + test_cmp expect actual && + + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output actual && + cat >expect <<-EOF && + refs/heads/bar + refs/heads/baz + refs/heads/main + refs/heads/next + EOF + test_cmp expect actual + ' + # Refs of upstream : main(B) foo(A) bar(A) baz(A) # Refs of workbench: main(A) baz(A) next(A) # git-push : main(A) NULL (B) baz(A) next(A) @@ -240,6 +274,7 @@ run_git_push_porcelain_output_test() { # Refs of workbench: main(A) baz(A) next(A) # git-push : main(A) next(A) test_expect_success ".. non-fastforward push ($PROTOCOL)" ' + test_when_finished "setup_upstream \"$upstream\"" && ( cd workbench && test_must_fail git push --porcelain origin \ @@ -266,6 +301,39 @@ run_git_push_porcelain_output_test() { EOF test_cmp expect actual ' + + # Refs of upstream : main(B) foo(A) bar(A) baz(A) + # Refs of workbench: main(A) baz(A) next(A) + # git-push : main(A) NULL (B) baz(A) next(A) + test_expect_success ".. git push --porcelain --atomic --force ($PROTOCOL)" ' + git -C workbench push --porcelain --atomic --force origin \ + main \ + :refs/heads/foo \ + $B:bar \ + baz \ + next >out && + make_user_friendly_and_stable_output actual && + format_and_save_expect <<-\EOF && + > To + > = refs/heads/baz:refs/heads/baz [up to date] + > :refs/heads/bar .. + > - :refs/heads/foo [deleted] + > + refs/heads/main:refs/heads/main ... (forced update) + > * refs/heads/next:refs/heads/next [new branch] + > Done + EOF + test_cmp expect actual && + + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output actual && + cat >expect <<-EOF && + refs/heads/bar + refs/heads/baz + refs/heads/main + refs/heads/next + EOF + test_cmp expect actual + ' } setup_upstream_and_workbench upstream.git -- cgit v1.2.3 From dd69a12e6a6a4c55b7827238d7267fc2e75684d1 Mon Sep 17 00:00:00 2001 From: Jiang Xin Date: Mon, 3 Feb 2025 07:29:35 +0100 Subject: t5548: add porcelain push test cases for dry-run mode New dry-run test cases: - git push --porcelain --dry-run - git push --porcelain --dry-run --force - git push --porcelain --dry-run --atomic - git push --porcelain --dry-run --atomic --force Signed-off-by: Jiang Xin Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- t/t5548-push-porcelain.sh | 153 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/t/t5548-push-porcelain.sh b/t/t5548-push-porcelain.sh index 5d724145d2..4c19404ebe 100755 --- a/t/t5548-push-porcelain.sh +++ b/t/t5548-push-porcelain.sh @@ -336,10 +336,159 @@ run_git_push_porcelain_output_test() { ' } +run_git_push_dry_run_porcelain_output_test() { + case $1 in + http) + PROTOCOL="HTTP protocol" + URL_PREFIX="http://.*" + ;; + file) + PROTOCOL="builtin protocol" + URL_PREFIX=".*" + ;; + esac + + # Refs of upstream : main(B) foo(A) bar(A) baz(A) + # Refs of workbench: main(A) baz(A) next(A) + # git-push : main(A) NULL (B) baz(A) next(A) + test_expect_success ".. git-push --porcelain --dry-run ($PROTOCOL)" ' + test_must_fail git -C workbench push --porcelain --dry-run origin \ + main \ + :refs/heads/foo \ + $B:bar \ + baz \ + next >out && + make_user_friendly_and_stable_output actual && + format_and_save_expect <<-EOF && + > To + > = refs/heads/baz:refs/heads/baz [up to date] + > :refs/heads/bar .. + > - :refs/heads/foo [deleted] + > * refs/heads/next:refs/heads/next [new branch] + > ! refs/heads/main:refs/heads/main [rejected] (non-fast-forward) + > Done + EOF + test_cmp expect actual && + + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output actual && + cat >expect <<-EOF && + refs/heads/bar + refs/heads/baz + refs/heads/foo + refs/heads/main + EOF + test_cmp expect actual + ' + + # Refs of upstream : main(B) foo(A) bar(A) baz(A) + # Refs of workbench: main(A) baz(A) next(A) + # push : main(A) NULL (B) baz(A) next(A) + test_expect_success ".. git-push --porcelain --dry-run --force ($PROTOCOL)" ' + git -C workbench push --porcelain --dry-run --force origin \ + main \ + :refs/heads/foo \ + $B:bar \ + baz \ + next >out && + make_user_friendly_and_stable_output actual && + format_and_save_expect <<-EOF && + > To + > = refs/heads/baz:refs/heads/baz [up to date] + > :refs/heads/bar .. + > - :refs/heads/foo [deleted] + > + refs/heads/main:refs/heads/main ... (forced update) + > * refs/heads/next:refs/heads/next [new branch] + > Done + EOF + test_cmp expect actual && + + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output actual && + cat >expect <<-EOF && + refs/heads/bar + refs/heads/baz + refs/heads/foo + refs/heads/main + EOF + test_cmp expect actual + ' + + # Refs of upstream : main(B) foo(A) bar(A) baz(A) + # Refs of workbench: main(A) baz(A) next(A) + # git-push : main(A) NULL (B) baz(A) next(A) + test_expect_success ".. git-push --porcelain --dry-run --atomic ($PROTOCOL)" ' + test_must_fail git -C workbench push --porcelain --dry-run --atomic origin \ + main \ + :refs/heads/foo \ + $B:bar \ + baz \ + next >out && + make_user_friendly_and_stable_output actual && + format_and_save_expect <<-EOF && + > To + > = refs/heads/baz:refs/heads/baz [up to date] + > ! :refs/heads/bar [rejected] (atomic push failed) + > ! (delete):refs/heads/foo [rejected] (atomic push failed) + > ! refs/heads/main:refs/heads/main [rejected] (non-fast-forward) + > ! refs/heads/next:refs/heads/next [rejected] (atomic push failed) + > Done + EOF + test_cmp expect actual && + + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output actual && + cat >expect <<-EOF && + refs/heads/bar + refs/heads/baz + refs/heads/foo + refs/heads/main + EOF + test_cmp expect actual + ' + + # Refs of upstream : main(B) foo(A) bar(A) baz(A) + # Refs of workbench: main(A) baz(A) next(A) + # push : main(A) NULL (B) baz(A) next(A) + test_expect_success ".. git-push --porcelain --dry-run --atomic --force ($PROTOCOL)" ' + git -C workbench push --porcelain --dry-run --atomic --force origin \ + main \ + :refs/heads/foo \ + $B:bar \ + baz \ + next >out && + make_user_friendly_and_stable_output actual && + format_and_save_expect <<-EOF && + > To + > = refs/heads/baz:refs/heads/baz [up to date] + > :refs/heads/bar .. + > - :refs/heads/foo [deleted] + > + refs/heads/main:refs/heads/main ... (forced update) + > * refs/heads/next:refs/heads/next [new branch] + > Done + EOF + test_cmp expect actual && + + git -C "$upstream" show-ref >out && + make_user_friendly_and_stable_output actual && + cat >expect <<-EOF && + refs/heads/bar + refs/heads/baz + refs/heads/foo + refs/heads/main + EOF + test_cmp expect actual + ' +} + setup_upstream_and_workbench upstream.git run_git_push_porcelain_output_test file +setup_upstream_and_workbench upstream.git + +run_git_push_dry_run_porcelain_output_test file + ROOT_PATH="$PWD" . "$TEST_DIRECTORY"/lib-gpg.sh . "$TEST_DIRECTORY"/lib-httpd.sh @@ -351,4 +500,8 @@ setup_upstream_and_workbench "$HTTPD_DOCUMENT_ROOT_PATH/upstream.git" run_git_push_porcelain_output_test http +setup_upstream_and_workbench "$HTTPD_DOCUMENT_ROOT_PATH/upstream.git" + +run_git_push_dry_run_porcelain_output_test http + test_done -- cgit v1.2.3 From 3028db4af289560e670b9f362aea16eaf3d1825e Mon Sep 17 00:00:00 2001 From: Jiang Xin Date: Mon, 3 Feb 2025 07:29:36 +0100 Subject: send-pack: new return code "ERROR_SEND_PACK_BAD_REF_STATUS" The "push_refs" function in the transport_vtable is the handler for git-push operation. All the "push_refs" functions for different transports (protocols) should have the same behavior, but the behavior of "git_transport_push()" function for builtin_smart_vtable in "transport.c" (which calls "send_pack()" in "send-pack.c") differs from the handler of the HTTP protocol. The "push_refs()" function for the HTTP protocol which calls the "push_refs_with_push()" function in "transport-helper.c" will return 0 even when a bad REF_STATUS (such as REF_STATUS_REJECT_NONFASTFORWARD) was found. But "send_pack()" for Git smart protocol will return -1 for a bad REF_STATUS. We cannot ignore bad REF_STATUS directly in the "send_pack()" function, because the function is also used in "builtin/send-pack.c". So we add a new non-zero error code "SEND_PACK_ERROR_REF_STATUS" for "send_pack()". Ignore the specific error code in the "git_transport_push()" function to have the same behavior as "push_refs()" for HTTP protocol. Note that even though we ignore the error here, we'll ultimately still end up detecting that a subset of refs was not pushed in `transport_push()` because we eventually call `push_had_errors()` on the remote refs. Signed-off-by: Jiang Xin Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- send-pack.c | 9 ++------- send-pack.h | 13 +++++++++++++ transport.c | 7 +++++++ 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/send-pack.c b/send-pack.c index 772c7683a0..4448c081cc 100644 --- a/send-pack.c +++ b/send-pack.c @@ -632,7 +632,7 @@ int send_pack(struct repository *r, reject_atomic_push(remote_refs, args->send_mirror); error("atomic push failed for ref %s. status: %d", ref->name, ref->status); - ret = args->porcelain ? 0 : -1; + ret = ERROR_SEND_PACK_BAD_REF_STATUS; goto out; } /* else fallthrough */ @@ -763,11 +763,6 @@ int send_pack(struct repository *r, if (ret < 0) goto out; - if (args->porcelain) { - ret = 0; - goto out; - } - for (ref = remote_refs; ref; ref = ref->next) { switch (ref->status) { case REF_STATUS_NONE: @@ -775,7 +770,7 @@ int send_pack(struct repository *r, case REF_STATUS_OK: break; default: - ret = -1; + ret = ERROR_SEND_PACK_BAD_REF_STATUS; goto out; } } diff --git a/send-pack.h b/send-pack.h index d256715681..c5ded2d200 100644 --- a/send-pack.h +++ b/send-pack.h @@ -13,6 +13,9 @@ struct repository; #define SEND_PACK_PUSH_CERT_IF_ASKED 1 #define SEND_PACK_PUSH_CERT_ALWAYS 2 +/* At least one reference has been rejected by the remote side. */ +#define ERROR_SEND_PACK_BAD_REF_STATUS 1 + struct send_pack_args { const char *url; unsigned verbose:1, @@ -36,6 +39,16 @@ struct option; int option_parse_push_signed(const struct option *opt, const char *arg, int unset); +/* + * Compute a packfile and write it to a file descriptor. The `fd` array needs + * to contain two file descriptors: `fd[0]` is the file descriptor used as + * input for the packet reader, whereas `fd[1]` is the file descriptor the + * packfile will be written to. + * + * Returns 0 on success, non-zero otherwise. Negative return values indicate a + * generic error, whereas positive return values indicate specific error + * conditions as documented with the `ERROR_SEND_PACK_*` constants. + */ int send_pack(struct repository *r, struct send_pack_args *args, int fd[], struct child_process *conn, struct ref *remote_refs, struct oid_array *extra_have); diff --git a/transport.c b/transport.c index 81ae8243b9..d064aff33e 100644 --- a/transport.c +++ b/transport.c @@ -934,6 +934,13 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re case protocol_v0: ret = send_pack(the_repository, &args, data->fd, data->conn, remote_refs, &data->extra_have); + /* + * Ignore the specific error code to maintain consistent behavior + * with the "push_refs()" function across different transports, + * such as "push_refs_with_push()" for HTTP protocol. + */ + if (ret == ERROR_SEND_PACK_BAD_REF_STATUS) + ret = 0; break; case protocol_unknown_version: BUG("unknown protocol version"); -- cgit v1.2.3 From 60c208db584c5a1558acaef9c2ba2fdf15999bc9 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 3 Feb 2025 07:29:37 +0100 Subject: t5543: atomic push reports exit code failure Add new test cases in t5543 to avoid ignoring the exit code of git-receive-pack(1) during atomic push with "--porcelain" flag. We'd typically notice this case because the refs would have their error message set. But there is an edge case when pushing refs succeeds, but git-receive-pack(1) exits with a non-zero exit code at a later point in time due to another error. An atomic git-push(1) would ignore that error code, and consequently it would return successfully and not print any error message at all. Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- t/t5543-atomic-push.sh | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh index 04b47ad84a..32181b9afb 100755 --- a/t/t5543-atomic-push.sh +++ b/t/t5543-atomic-push.sh @@ -280,4 +280,34 @@ test_expect_success 'atomic push reports (reject by non-ff)' ' test_cmp expect actual ' +test_expect_failure 'atomic push reports exit code failure' ' + write_script receive-pack-wrapper <<-\EOF && + git-receive-pack "$@" + exit 1 + EOF + test_must_fail git -C workbench push --atomic \ + --receive-pack="${SQ}$(pwd)${SQ}/receive-pack-wrapper" \ + up HEAD:refs/heads/no-conflict 2>err && + cat >expect <<-EOF && + To ../upstream + * [new branch] HEAD -> no-conflict + error: failed to push some refs to ${SQ}../upstream${SQ} + EOF + test_cmp expect err +' + +test_expect_failure 'atomic push reports exit code failure with porcelain' ' + write_script receive-pack-wrapper <<-\EOF && + git-receive-pack "$@" + exit 1 + EOF + test_must_fail git -C workbench push --atomic --porcelain \ + --receive-pack="${SQ}$(pwd)${SQ}/receive-pack-wrapper" \ + up HEAD:refs/heads/no-conflict-porcelain 2>err && + cat >expect <<-EOF && + error: failed to push some refs to ${SQ}../upstream${SQ} + EOF + test_cmp expect err +' + test_done -- cgit v1.2.3 From b81f8c8dd3ec81a8d622e2d3d6b2af426ca53f05 Mon Sep 17 00:00:00 2001 From: Jiang Xin Date: Mon, 3 Feb 2025 07:29:38 +0100 Subject: send-pack: gracefully close the connection for atomic push Patrick reported an issue that the exit code of git-receive-pack(1) is ignored during atomic push with "--porcelain" flag, and added new test cases in t5543. This issue originated from commit 7dcbeaa0df (send-pack: fix inconsistent porcelain output, 2020-04-17). At that time, I chose to ignore the exit code of "finish_connect()" without investigating the root cause of the abnormal termination of git-receive-pack. That was an incorrect solution. The root cause is that an atomic push operation terminates early without sending a flush packet to git-receive-pack. As a result, git-receive-pack continues waiting for commands without exiting. By sending a flush packet at the appropriate location in "send_pack()", we ensure that the git-receive-pack process closes properly, avoiding an erroneous exit code for git-push. At the same time, revert the changes to the "transport.c" file made in commit 7dcbeaa0df. Reported-by: Patrick Steinhardt Signed-off-by: Jiang Xin Signed-off-by: Patrick Steinhardt Signed-off-by: Junio C Hamano --- send-pack.c | 1 + t/t5543-atomic-push.sh | 4 ++-- transport.c | 10 +--------- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/send-pack.c b/send-pack.c index 4448c081cc..856a65d5f5 100644 --- a/send-pack.c +++ b/send-pack.c @@ -633,6 +633,7 @@ int send_pack(struct repository *r, error("atomic push failed for ref %s. status: %d", ref->name, ref->status); ret = ERROR_SEND_PACK_BAD_REF_STATUS; + packet_flush(out); goto out; } /* else fallthrough */ diff --git a/t/t5543-atomic-push.sh b/t/t5543-atomic-push.sh index 32181b9afb..3a700b0676 100755 --- a/t/t5543-atomic-push.sh +++ b/t/t5543-atomic-push.sh @@ -280,7 +280,7 @@ test_expect_success 'atomic push reports (reject by non-ff)' ' test_cmp expect actual ' -test_expect_failure 'atomic push reports exit code failure' ' +test_expect_success 'atomic push reports exit code failure' ' write_script receive-pack-wrapper <<-\EOF && git-receive-pack "$@" exit 1 @@ -296,7 +296,7 @@ test_expect_failure 'atomic push reports exit code failure' ' test_cmp expect err ' -test_expect_failure 'atomic push reports exit code failure with porcelain' ' +test_expect_success 'atomic push reports exit code failure with porcelain' ' write_script receive-pack-wrapper <<-\EOF && git-receive-pack "$@" exit 1 diff --git a/transport.c b/transport.c index d064aff33e..b0c6c339f4 100644 --- a/transport.c +++ b/transport.c @@ -948,15 +948,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re close(data->fd[1]); close(data->fd[0]); - /* - * Atomic push may abort the connection early and close the pipe, - * which may cause an error for `finish_connect()`. Ignore this error - * for atomic git-push. - */ - if (ret || args.atomic) - finish_connect(data->conn); - else - ret = finish_connect(data->conn); + ret |= finish_connect(data->conn); data->conn = NULL; data->finished_handshake = 0; -- cgit v1.2.3