Skip to content

Commit 722ff7f

Browse files
peffgitster
authored andcommitted
receive-pack: quarantine objects until pre-receive accepts
When a client pushes objects to us, index-pack checks the objects themselves and then installs them into place. If we then reject the push due to a pre-receive hook, we cannot just delete the packfile; other processes may be depending on it. We have to do a normal reachability check at this point via `git gc`. But such objects may hang around for weeks due to the gc.pruneExpire grace period. And worse, during that time they may be exploded from the pack into inefficient loose objects. Instead, this patch teaches receive-pack to put the new objects into a "quarantine" temporary directory. We make these objects available to the connectivity check and to the pre-receive hook, and then install them into place only if it is successful (and otherwise remove them as tempfiles). Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 2564d99 commit 722ff7f

File tree

2 files changed

+76
-1
lines changed

2 files changed

+76
-1
lines changed

builtin/receive-pack.c

+40-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "gpg-interface.h"
2121
#include "sigchain.h"
2222
#include "fsck.h"
23+
#include "tmp-objdir.h"
2324

2425
static const char * const receive_pack_usage[] = {
2526
N_("git receive-pack <git-dir>"),
@@ -86,6 +87,8 @@ static enum {
8687
} use_keepalive;
8788
static int keepalive_in_sec = 5;
8889

90+
static struct tmp_objdir *tmp_objdir;
91+
8992
static enum deny_action parse_deny_action(const char *var, const char *value)
9093
{
9194
if (value) {
@@ -663,6 +666,9 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,
663666
} else
664667
argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT");
665668

669+
if (tmp_objdir)
670+
argv_array_pushv(&proc.env_array, tmp_objdir_env(tmp_objdir));
671+
666672
if (use_sideband) {
667673
memset(&muxer, 0, sizeof(muxer));
668674
muxer.proc = copy_to_sideband;
@@ -762,6 +768,7 @@ static int run_update_hook(struct command *cmd)
762768
proc.stdout_to_stderr = 1;
763769
proc.err = use_sideband ? -1 : 0;
764770
proc.argv = argv;
771+
proc.env = tmp_objdir_env(tmp_objdir);
765772

766773
code = start_command(&proc);
767774
if (code)
@@ -833,6 +840,7 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
833840
!delayed_reachability_test(si, i))
834841
sha1_array_append(&extra, si->shallow->sha1[i]);
835842

843+
opt.env = tmp_objdir_env(tmp_objdir);
836844
setup_alternate_shallow(&shallow_lock, &opt.shallow_file, &extra);
837845
if (check_connected(command_singleton_iterator, cmd, &opt)) {
838846
rollback_lock_file(&shallow_lock);
@@ -1240,12 +1248,17 @@ static void set_connectivity_errors(struct command *commands,
12401248

12411249
for (cmd = commands; cmd; cmd = cmd->next) {
12421250
struct command *singleton = cmd;
1251+
struct check_connected_options opt = CHECK_CONNECTED_INIT;
1252+
12431253
if (shallow_update && si->shallow_ref[cmd->index])
12441254
/* to be checked in update_shallow_ref() */
12451255
continue;
1256+
1257+
opt.env = tmp_objdir_env(tmp_objdir);
12461258
if (!check_connected(command_singleton_iterator, &singleton,
1247-
NULL))
1259+
&opt))
12481260
continue;
1261+
12491262
cmd->error_string = "missing necessary objects";
12501263
}
12511264
}
@@ -1428,6 +1441,7 @@ static void execute_commands(struct command *commands,
14281441
data.si = si;
14291442
opt.err_fd = err_fd;
14301443
opt.progress = err_fd && !quiet;
1444+
opt.env = tmp_objdir_env(tmp_objdir);
14311445
if (check_connected(iterate_receive_command_list, &data, &opt))
14321446
set_connectivity_errors(commands, si);
14331447

@@ -1444,6 +1458,19 @@ static void execute_commands(struct command *commands,
14441458
return;
14451459
}
14461460

1461+
/*
1462+
* Now we'll start writing out refs, which means the objects need
1463+
* to be in their final positions so that other processes can see them.
1464+
*/
1465+
if (tmp_objdir_migrate(tmp_objdir) < 0) {
1466+
for (cmd = commands; cmd; cmd = cmd->next) {
1467+
if (!cmd->error_string)
1468+
cmd->error_string = "unable to migrate objects to permanent storage";
1469+
}
1470+
return;
1471+
}
1472+
tmp_objdir = NULL;
1473+
14471474
check_aliased_updates(commands);
14481475

14491476
free(head_name_to_free);
@@ -1639,6 +1666,18 @@ static const char *unpack(int err_fd, struct shallow_info *si)
16391666
argv_array_push(&child.args, alt_shallow_file);
16401667
}
16411668

1669+
tmp_objdir = tmp_objdir_create();
1670+
if (!tmp_objdir)
1671+
return "unable to create temporary object directory";
1672+
child.env = tmp_objdir_env(tmp_objdir);
1673+
1674+
/*
1675+
* Normally we just pass the tmp_objdir environment to the child
1676+
* processes that do the heavy lifting, but we may need to see these
1677+
* objects ourselves to set up shallow information.
1678+
*/
1679+
tmp_objdir_add_as_alternate(tmp_objdir);
1680+
16421681
if (ntohl(hdr.hdr_entries) < unpack_limit) {
16431682
argv_array_pushl(&child.args, "unpack-objects", hdr_arg, NULL);
16441683
if (quiet)

t/t5547-push-quarantine.sh

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/bin/sh
2+
3+
test_description='check quarantine of objects during push'
4+
. ./test-lib.sh
5+
6+
test_expect_success 'create picky dest repo' '
7+
git init --bare dest.git &&
8+
write_script dest.git/hooks/pre-receive <<-\EOF
9+
while read old new ref; do
10+
test "$(git log -1 --format=%s $new)" = reject && exit 1
11+
done
12+
exit 0
13+
EOF
14+
'
15+
16+
test_expect_success 'accepted objects work' '
17+
test_commit ok &&
18+
git push dest.git HEAD &&
19+
commit=$(git rev-parse HEAD) &&
20+
git --git-dir=dest.git cat-file commit $commit
21+
'
22+
23+
test_expect_success 'rejected objects are not installed' '
24+
test_commit reject &&
25+
commit=$(git rev-parse HEAD) &&
26+
test_must_fail git push dest.git reject &&
27+
test_must_fail git --git-dir=dest.git cat-file commit $commit
28+
'
29+
30+
test_expect_success 'rejected objects are removed' '
31+
echo "incoming-*" >expect &&
32+
(cd dest.git/objects && echo incoming-*) >actual &&
33+
test_cmp expect actual
34+
'
35+
36+
test_done

0 commit comments

Comments
 (0)