Skip to content

Commit

Permalink
receive-pack: quarantine objects until pre-receive accepts
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
peff authored and gitster committed Oct 10, 2016
1 parent 2564d99 commit 722ff7f
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 1 deletion.
41 changes: 40 additions & 1 deletion builtin/receive-pack.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include "gpg-interface.h"
#include "sigchain.h"
#include "fsck.h"
#include "tmp-objdir.h"

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

static struct tmp_objdir *tmp_objdir;

static enum deny_action parse_deny_action(const char *var, const char *value)
{
if (value) {
Expand Down Expand Up @@ -663,6 +666,9 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,
} else
argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT");

if (tmp_objdir)
argv_array_pushv(&proc.env_array, tmp_objdir_env(tmp_objdir));

if (use_sideband) {
memset(&muxer, 0, sizeof(muxer));
muxer.proc = copy_to_sideband;
Expand Down Expand Up @@ -762,6 +768,7 @@ static int run_update_hook(struct command *cmd)
proc.stdout_to_stderr = 1;
proc.err = use_sideband ? -1 : 0;
proc.argv = argv;
proc.env = tmp_objdir_env(tmp_objdir);

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

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

for (cmd = commands; cmd; cmd = cmd->next) {
struct command *singleton = cmd;
struct check_connected_options opt = CHECK_CONNECTED_INIT;

if (shallow_update && si->shallow_ref[cmd->index])
/* to be checked in update_shallow_ref() */
continue;

opt.env = tmp_objdir_env(tmp_objdir);
if (!check_connected(command_singleton_iterator, &singleton,
NULL))
&opt))
continue;

cmd->error_string = "missing necessary objects";
}
}
Expand Down Expand Up @@ -1428,6 +1441,7 @@ static void execute_commands(struct command *commands,
data.si = si;
opt.err_fd = err_fd;
opt.progress = err_fd && !quiet;
opt.env = tmp_objdir_env(tmp_objdir);
if (check_connected(iterate_receive_command_list, &data, &opt))
set_connectivity_errors(commands, si);

Expand All @@ -1444,6 +1458,19 @@ static void execute_commands(struct command *commands,
return;
}

/*
* Now we'll start writing out refs, which means the objects need
* to be in their final positions so that other processes can see them.
*/
if (tmp_objdir_migrate(tmp_objdir) < 0) {
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string)
cmd->error_string = "unable to migrate objects to permanent storage";
}
return;
}
tmp_objdir = NULL;

check_aliased_updates(commands);

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

tmp_objdir = tmp_objdir_create();
if (!tmp_objdir)
return "unable to create temporary object directory";
child.env = tmp_objdir_env(tmp_objdir);

/*
* Normally we just pass the tmp_objdir environment to the child
* processes that do the heavy lifting, but we may need to see these
* objects ourselves to set up shallow information.
*/
tmp_objdir_add_as_alternate(tmp_objdir);

if (ntohl(hdr.hdr_entries) < unpack_limit) {
argv_array_pushl(&child.args, "unpack-objects", hdr_arg, NULL);
if (quiet)
Expand Down
36 changes: 36 additions & 0 deletions t/t5547-push-quarantine.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/bin/sh

test_description='check quarantine of objects during push'
. ./test-lib.sh

test_expect_success 'create picky dest repo' '
git init --bare dest.git &&
write_script dest.git/hooks/pre-receive <<-\EOF
while read old new ref; do
test "$(git log -1 --format=%s $new)" = reject && exit 1
done
exit 0
EOF
'

test_expect_success 'accepted objects work' '
test_commit ok &&
git push dest.git HEAD &&
commit=$(git rev-parse HEAD) &&
git --git-dir=dest.git cat-file commit $commit
'

test_expect_success 'rejected objects are not installed' '
test_commit reject &&
commit=$(git rev-parse HEAD) &&
test_must_fail git push dest.git reject &&
test_must_fail git --git-dir=dest.git cat-file commit $commit
'

test_expect_success 'rejected objects are removed' '
echo "incoming-*" >expect &&
(cd dest.git/objects && echo incoming-*) >actual &&
test_cmp expect actual
'

test_done

0 comments on commit 722ff7f

Please sign in to comment.