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 <peff@peff.net> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jeff King committed Oct 3, 2016 at 16:49 UTC 722ff7f876c8a2ad99c42434f58af098e61b96e8
2 files changed +76 -1
builtin/receive-pack.c
+40 -1
@@ -20,6 +20,7 @@
20 #include "gpg-interface.h"
21 #include "sigchain.h"
22 #include "fsck.h"
23 +#include "tmp-objdir.h"
24
25 static const char * const receive_pack_usage[] = {
26 N_("git receive-pack <git-dir>"),
@@ -86,6 +87,8 @@ static enum {
87 } use_keepalive;
88 static int keepalive_in_sec = 5;
89
90 +static struct tmp_objdir *tmp_objdir;
91 +
92 static enum deny_action parse_deny_action(const char *var, const char *value)
93 {
94 if (value) {
@@ -663,6 +666,9 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,
666 } else
667 argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT");
668
669 + if (tmp_objdir)
670 + argv_array_pushv(&proc.env_array, tmp_objdir_env(tmp_objdir));
671 +
672 if (use_sideband) {
673 memset(&muxer, 0, sizeof(muxer));
674 muxer.proc = copy_to_sideband;
@@ -762,6 +768,7 @@ static int run_update_hook(struct command *cmd)
768 proc.stdout_to_stderr = 1;
769 proc.err = use_sideband ? -1 : 0;
770 proc.argv = argv;
771 + proc.env = tmp_objdir_env(tmp_objdir);
772
773 code = start_command(&proc);
774 if (code)
@@ -833,6 +840,7 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
840 !delayed_reachability_test(si, i))
841 sha1_array_append(&extra, si->shallow->sha1[i]);
842
843 + opt.env = tmp_objdir_env(tmp_objdir);
844 setup_alternate_shallow(&shallow_lock, &opt.shallow_file, &extra);
845 if (check_connected(command_singleton_iterator, cmd, &opt)) {
846 rollback_lock_file(&shallow_lock);
@@ -1240,12 +1248,17 @@ static void set_connectivity_errors(struct command *commands,
1248
1249 for (cmd = commands; cmd; cmd = cmd->next) {
1250 struct command *singleton = cmd;
1251 + struct check_connected_options opt = CHECK_CONNECTED_INIT;
1252 +
1253 if (shallow_update && si->shallow_ref[cmd->index])
1254 /* to be checked in update_shallow_ref() */
1255 continue;
1256 +
1257 + opt.env = tmp_objdir_env(tmp_objdir);
1258 if (!check_connected(command_singleton_iterator, &singleton,
1247 - NULL))
1259 + &opt))
1260 continue;
1261 +
1262 cmd->error_string = "missing necessary objects";
1263 }
1264 }
@@ -1428,6 +1441,7 @@ static void execute_commands(struct command *commands,
1441 data.si = si;
1442 opt.err_fd = err_fd;
1443 opt.progress = err_fd && !quiet;
1444 + opt.env = tmp_objdir_env(tmp_objdir);
1445 if (check_connected(iterate_receive_command_list, &data, &opt))
1446 set_connectivity_errors(commands, si);
1447
@@ -1444,6 +1458,19 @@ static void execute_commands(struct command *commands,
1458 return;
1459 }
1460
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 +
1474 check_aliased_updates(commands);
1475
1476 free(head_name_to_free);
@@ -1639,6 +1666,18 @@ static const char *unpack(int err_fd, struct shallow_info *si)
1666 argv_array_push(&child.args, alt_shallow_file);
1667 }
1668
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 +
1681 if (ntohl(hdr.hdr_entries) < unpack_limit) {
1682 argv_array_pushl(&child.args, "unpack-objects", hdr_arg, NULL);
1683 if (quiet)
t/t5547-push-quarantine.sh new
+36
@@ -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