diff --git a/Documentation/git-receive-pack.adoc b/Documentation/git-receive-pack.adoc index 20aca92073d8c9d65fc78ce5f3f48a02239333f5..0956086d6117d3dbcd4e68651b53e692716cd067 100644 --- a/Documentation/git-receive-pack.adoc +++ b/Documentation/git-receive-pack.adoc @@ -46,6 +46,18 @@ OPTIONS `$GIT_URL/info/refs?service=git-receive-pack` requests. See `--http-backend-info-refs` in linkgit:git-upload-pack[1]. +--skip-connectivity-check:: + Bypasses the connectivity checks that validate the existence of all + objects in the transitive closure of reachable objects. This option is + intended for server operators that want to implement their own object + connectivity validation outside of Git. This is useful in such cases + where the server-side knows additional information about how Git is + being used and thus can rely on certain guarantees to more efficiently + compute object connectivity that Git itself cannot make. Usage of this + option without a reliable external mechanism to ensure full reachable + object connectivity risks corrupting the repository and should not be + used in the general case. + PRE-RECEIVE HOOK ---------------- Before any ref is updated, if $GIT_DIR/hooks/pre-receive file exists diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index be314879e8290894a38900a86c8ac7a40e674ec4..66674bc408ff25186c195c85d4121575c679970b 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -81,6 +81,7 @@ static int prefer_ofs_delta = 1; static int auto_update_server_info; static int auto_gc = 1; static int reject_thin; +static int skip_connectivity_check; static int stateless_rpc; static const char *service_dir; static const char *head_name; @@ -1936,27 +1937,29 @@ static void execute_commands(struct command *commands, return; } - if (use_sideband) { - memset(&muxer, 0, sizeof(muxer)); - muxer.proc = copy_to_sideband; - muxer.in = -1; - if (!start_async(&muxer)) - err_fd = muxer.in; - /* ...else, continue without relaying sideband */ - } + if (!skip_connectivity_check) { + if (use_sideband) { + memset(&muxer, 0, sizeof(muxer)); + muxer.proc = copy_to_sideband; + muxer.in = -1; + if (!start_async(&muxer)) + err_fd = muxer.in; + /* ...else, continue without relaying sideband */ + } - data.cmds = commands; - data.si = si; - opt.err_fd = err_fd; - opt.progress = err_fd && !quiet; - opt.env = tmp_objdir_env(tmp_objdir); - opt.exclude_hidden_refs_section = "receive"; + data.cmds = commands; + data.si = si; + opt.err_fd = err_fd; + opt.progress = err_fd && !quiet; + opt.env = tmp_objdir_env(tmp_objdir); + opt.exclude_hidden_refs_section = "receive"; - if (check_connected(iterate_receive_command_list, &data, &opt)) - set_connectivity_errors(commands, si); + if (check_connected(iterate_receive_command_list, &data, &opt)) + set_connectivity_errors(commands, si); - if (use_sideband) - finish_async(&muxer); + if (use_sideband) + finish_async(&muxer); + } reject_updates_to_hidden(commands); @@ -2517,6 +2520,7 @@ int cmd_receive_pack(int argc, struct option options[] = { OPT__QUIET(&quiet, N_("quiet")), + OPT_HIDDEN_BOOL(0, "skip-connectivity-check", &skip_connectivity_check, NULL), OPT_HIDDEN_BOOL(0, "stateless-rpc", &stateless_rpc, NULL), OPT_HIDDEN_BOOL(0, "http-backend-info-refs", &advertise_refs, NULL), OPT_ALIAS(0, "advertise-refs", "http-backend-info-refs"), diff --git a/t/meson.build b/t/meson.build index 43c9750b88e3ab8898c5e97f09838411c277908e..6b7c0b167bfcdfd06b6185864a84f29c69b02832 100644 --- a/t/meson.build +++ b/t/meson.build @@ -628,7 +628,7 @@ integration_tests = [ 't5407-post-rewrite-hook.sh', 't5408-send-pack-stdin.sh', 't5409-colorize-remote-messages.sh', - 't5410-receive-pack-alternates.sh', + 't5410-receive-pack.sh', 't5411-proc-receive-hook.sh', 't5500-fetch-pack.sh', 't5501-fetch-push-alternates.sh', diff --git a/t/t5410-receive-pack-alternates.sh b/t/t5410-receive-pack-alternates.sh deleted file mode 100755 index 4e82fd102e37277919dc817187db6ee9fabac884..0000000000000000000000000000000000000000 --- a/t/t5410-receive-pack-alternates.sh +++ /dev/null @@ -1,44 +0,0 @@ -#!/bin/sh - -test_description='git receive-pack with alternate ref filtering' - -GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main -export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME - -. ./test-lib.sh - -test_expect_success 'setup' ' - test_commit base && - git clone -s --bare . fork && - git checkout -b public/branch main && - test_commit public && - git checkout -b private/branch main && - test_commit private -' - -extract_haves () { - depacketize | sed -n 's/^\([^ ][^ ]*\) \.have/\1/p' -} - -test_expect_success 'with core.alternateRefsCommand' ' - write_script fork/alternate-refs <<-\EOF && - git --git-dir="$1" for-each-ref \ - --format="%(objectname)" \ - refs/heads/public/ - EOF - test_config -C fork core.alternateRefsCommand ./alternate-refs && - git rev-parse public/branch >expect && - printf "0000" | git receive-pack fork >actual && - extract_haves actual.haves && - test_cmp expect actual.haves -' - -test_expect_success 'with core.alternateRefsPrefixes' ' - test_config -C fork core.alternateRefsPrefixes "refs/heads/private" && - git rev-parse private/branch >expect && - printf "0000" | git receive-pack fork >actual && - extract_haves actual.haves && - test_cmp expect actual.haves -' - -test_done diff --git a/t/t5410-receive-pack.sh b/t/t5410-receive-pack.sh new file mode 100755 index 0000000000000000000000000000000000000000..f76a22943ef0d1787cc82bc4655e91b8bbaa5d15 --- /dev/null +++ b/t/t5410-receive-pack.sh @@ -0,0 +1,87 @@ +#!/bin/sh + +test_description='git receive-pack' + +GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main +export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit base && + git clone -s --bare . fork && + git checkout -b public/branch main && + test_commit public && + git checkout -b private/branch main && + test_commit private +' + +extract_haves () { + depacketize | sed -n 's/^\([^ ][^ ]*\) \.have/\1/p' +} + +test_expect_success 'with core.alternateRefsCommand' ' + write_script fork/alternate-refs <<-\EOF && + git --git-dir="$1" for-each-ref \ + --format="%(objectname)" \ + refs/heads/public/ + EOF + test_config -C fork core.alternateRefsCommand ./alternate-refs && + git rev-parse public/branch >expect && + printf "0000" | git receive-pack fork >actual && + extract_haves actual.haves && + test_cmp expect actual.haves +' + +test_expect_success 'with core.alternateRefsPrefixes' ' + test_config -C fork core.alternateRefsPrefixes "refs/heads/private" && + git rev-parse private/branch >expect && + printf "0000" | git receive-pack fork >actual && + extract_haves actual.haves && + test_cmp expect actual.haves +' + +test_expect_success 'receive-pack missing objects fails connectivity check' ' + test_when_finished rm -rf repo remote.git setup.git && + + git init repo && + git -C repo commit --allow-empty -m 1 && + git clone --bare repo setup.git && + git -C repo commit --allow-empty -m 2 && + + # Capture git-send-pack(1) output sent to git-receive-pack(1). + git -C repo send-pack ../setup.git --all \ + --receive-pack="tee ${SQ}$(pwd)/out${SQ} | git-receive-pack" && + + # Replay captured git-send-pack(1) output on new empty repository. + git init --bare remote.git && + git receive-pack remote.git actual 2>err && + + test_grep "missing necessary objects" actual && + test_grep "fatal: Failed to traverse parents" err && + test_must_fail git -C remote.git cat-file -e $(git -C repo rev-parse HEAD) +' + +test_expect_success 'receive-pack missing objects bypasses connectivity check' ' + test_when_finished rm -rf repo remote.git setup.git && + + git init repo && + git -C repo commit --allow-empty -m 1 && + git clone --bare repo setup.git && + git -C repo commit --allow-empty -m 2 && + + # Capture git-send-pack(1) output sent to git-receive-pack(1). + git -C repo send-pack ../setup.git --all \ + --receive-pack="tee ${SQ}$(pwd)/out${SQ} | git-receive-pack" && + + # Replay captured git-send-pack(1) output on new empty repository. + git init --bare remote.git && + git receive-pack --skip-connectivity-check remote.git actual 2>err && + + test_grep ! "missing necessary objects" actual && + test_must_be_empty err && + git -C remote.git cat-file -e $(git -C repo rev-parse HEAD) && + test_must_fail git -C remote.git rev-list $(git -C repo rev-parse HEAD) +' + +test_done