Merge branch 'jt/receive-pack-skip-connectivity-check'

"git receive-pack" optionally learns not to care about connectivity
check, which can be useful when the repository arranges to ensure
connectivity by some other means.

* jt/receive-pack-skip-connectivity-check:
  builtin/receive-pack: add option to skip connectivity check
  t5410: test receive-pack connectivity check
maint
Junio C Hamano 2025-05-28 07:59:56 -07:00
commit b4847a4477
5 changed files with 124 additions and 65 deletions

View File

@ -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

View File

@ -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;
@ -1938,28 +1939,30 @@ 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";

if (check_connected(iterate_receive_command_list, &data, &opt))
set_connectivity_errors(commands, si);

if (use_sideband)
finish_async(&muxer);
}

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 (use_sideband)
finish_async(&muxer);

reject_updates_to_hidden(commands);

/*
@ -2519,6 +2522,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"),

View File

@ -629,7 +629,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',

View File

@ -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 >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 >actual.haves &&
test_cmp expect actual.haves
'

test_done

87
t/t5410-receive-pack.sh Executable file
View File

@ -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 >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 >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 <out >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 <out >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