builtin/receive-pack: stage incoming objects via ODB transactions

Objects received by git-receive-pack(1) are quarantined in a temporary "incoming" directory and migrated into the object database prior to the reference updates. The quarantine is currently managed through `tmp_objdir` directly. In a pluggable ODB future, how exactly an object gets written to a transaction may vary for a given ODB source. Refactor git-receive-pack(1) to use the ODB transaction interfaces to manage the object staging area in a more agnostic manner accordingly. Note that the ODB transaction is now responsible for managing the primary and alternate ODBs for the repository. One small change as a result is that the temporary directory is now applied as the primary ODB in the main process instead of an alternate. This does not change anything for git-receive-pack(1) though because it only needs access to the newly written objects and doesn't care how exactly it is set up. Signed-off-by: Justin Tobler <jltobler@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Justin Tobler committed Jul 10, 2026 at 11:37 UTC bdee7b30135f51f406d71c565cf29bb7db64f1cd
1 file changed +35 -33
builtin/receive-pack.c
+35 -33
@@ -37,7 +37,6 @@
37 #include "sigchain.h"
38 #include "string-list.h"
39 #include "strvec.h"
40 -#include "tmp-objdir.h"
40 #include "trace.h"
41 #include "trace2.h"
42 #include "version.h"
@@ -112,8 +111,6 @@ static enum {
111 } use_keepalive;
112 static int keepalive_in_sec = 5;
113
115 -static struct tmp_objdir *tmp_objdir;
116 -
114 static struct proc_receive_ref {
115 unsigned int want_add:1,
116 want_delete:1,
@@ -926,6 +923,7 @@ static void receive_hook_feed_state_free(void *data)
923 static int run_receive_hook(struct command *commands,
924 const char *hook_name,
925 int skip_broken,
926 + struct odb_transaction *transaction,
927 const struct string_list *push_options)
928 {
929 struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT;
@@ -959,8 +957,8 @@ static int run_receive_hook(struct command *commands,
957 strvec_push(&opt.env, "GIT_PUSH_OPTION_COUNT");
958 }
959
962 - if (tmp_objdir)
963 - strvec_pushv(&opt.env, tmp_objdir_env(tmp_objdir));
960 + if (transaction)
961 + odb_transaction_env(transaction, &opt.env);
962
963 prepare_push_cert_sha1(&opt);
964
@@ -1789,24 +1787,30 @@ static const struct object_id *command_singleton_iterator(void *cb_data)
1787 }
1788
1789 static void set_connectivity_errors(struct command *commands,
1792 - struct shallow_info *si)
1790 + struct shallow_info *si,
1791 + struct odb_transaction *transaction)
1792 {
1793 struct command *cmd;
1794
1795 for (cmd = commands; cmd; cmd = cmd->next) {
1796 struct command *singleton = cmd;
1797 struct check_connected_options opt = CHECK_CONNECTED_INIT;
1798 + struct strvec env = STRVEC_INIT;
1799
1800 if (shallow_update && si->shallow_ref[cmd->index])
1801 /* to be checked in update_shallow_ref() */
1802 continue;
1803
1804 - opt.env = tmp_objdir_env(tmp_objdir);
1804 + odb_transaction_env(transaction, &env);
1805 + opt.env = env.v;
1806 +
1807 if (!check_connected(command_singleton_iterator, &singleton,
1808 &opt))
1809 continue;
1810
1811 cmd->error_string = "missing necessary objects";
1812 +
1813 + strvec_clear(&env);
1814 }
1815 }
1816
@@ -2027,6 +2031,7 @@ cleanup:
2031 static void execute_commands(struct command *commands,
2032 const char *unpacker_error,
2033 struct shallow_info *si,
2034 + struct odb_transaction *transaction,
2035 const struct string_list *push_options)
2036 {
2037 struct check_connected_options opt = CHECK_CONNECTED_INIT;
@@ -2043,6 +2048,8 @@ static void execute_commands(struct command *commands,
2048 }
2049
2050 if (!skip_connectivity_check) {
2051 + struct strvec env = STRVEC_INIT;
2052 +
2053 if (use_sideband) {
2054 memset(&muxer, 0, sizeof(muxer));
2055 muxer.proc = copy_to_sideband;
@@ -2056,14 +2063,17 @@ static void execute_commands(struct command *commands,
2063 data.si = si;
2064 opt.err_fd = err_fd;
2065 opt.progress = err_fd && !quiet;
2059 - opt.env = tmp_objdir_env(tmp_objdir);
2066 + odb_transaction_env(transaction, &env);
2067 + opt.env = env.v;
2068 opt.exclude_hidden_refs_section = "receive";
2069
2070 if (check_connected(iterate_receive_command_list, &data, &opt))
2063 - set_connectivity_errors(commands, si);
2071 + set_connectivity_errors(commands, si, transaction);
2072
2073 if (use_sideband)
2074 finish_async(&muxer);
2075 +
2076 + strvec_clear(&env);
2077 }
2078
2079 reject_updates_to_hidden(commands);
@@ -2084,7 +2094,7 @@ static void execute_commands(struct command *commands,
2094 }
2095 }
2096
2087 - if (run_receive_hook(commands, "pre-receive", 0, push_options)) {
2097 + if (run_receive_hook(commands, "pre-receive", 0, transaction, push_options)) {
2098 for (cmd = commands; cmd; cmd = cmd->next) {
2099 if (!cmd->error_string)
2100 cmd->error_string = "pre-receive hook declined";
@@ -2105,14 +2115,13 @@ static void execute_commands(struct command *commands,
2115 * Now we'll start writing out refs, which means the objects need
2116 * to be in their final positions so that other processes can see them.
2117 */
2108 - if (tmp_objdir_migrate(tmp_objdir) < 0) {
2118 + if (odb_transaction_commit(transaction)) {
2119 for (cmd = commands; cmd; cmd = cmd->next) {
2120 if (!cmd->error_string)
2121 cmd->error_string = "unable to migrate objects to permanent storage";
2122 }
2123 return;
2124 }
2115 - tmp_objdir = NULL;
2125
2126 check_aliased_updates(commands);
2127
@@ -2325,7 +2334,8 @@ static void push_header_arg(struct strvec *args, struct pack_header *hdr)
2334 ntohl(hdr->hdr_version), ntohl(hdr->hdr_entries));
2335 }
2336
2328 -static const char *unpack(int err_fd, struct shallow_info *si)
2337 +static const char *unpack(int err_fd, struct shallow_info *si,
2338 + struct odb_transaction *transaction)
2339 {
2340 struct pack_header hdr;
2341 const char *hdr_err;
@@ -2350,20 +2360,7 @@ static const char *unpack(int err_fd, struct shallow_info *si)
2360 strvec_push(&child.args, alt_shallow_file);
2361 }
2362
2353 - tmp_objdir = tmp_objdir_create(the_repository, "incoming");
2354 - if (!tmp_objdir) {
2355 - if (err_fd > 0)
2356 - close(err_fd);
2357 - return "unable to create temporary object directory";
2358 - }
2359 - strvec_pushv(&child.env, tmp_objdir_env(tmp_objdir));
2360 -
2361 - /*
2362 - * Normally we just pass the tmp_objdir environment to the child
2363 - * processes that do the heavy lifting, but we may need to see these
2364 - * objects ourselves to set up shallow information.
2365 - */
2366 - tmp_objdir_add_as_alternate(tmp_objdir);
2363 + odb_transaction_env(transaction, &child.env);
2364
2365 if (ntohl(hdr.hdr_entries) < unpack_limit) {
2366 strvec_push(&child.args, "unpack-objects");
@@ -2430,13 +2427,14 @@ static const char *unpack(int err_fd, struct shallow_info *si)
2427 return NULL;
2428 }
2429
2433 -static const char *unpack_with_sideband(struct shallow_info *si)
2430 +static const char *unpack_with_sideband(struct shallow_info *si,
2431 + struct odb_transaction *transaction)
2432 {
2433 struct async muxer;
2434 const char *ret;
2435
2436 if (!use_sideband)
2439 - return unpack(0, si);
2437 + return unpack(0, si, transaction);
2438
2439 use_keepalive = KEEPALIVE_AFTER_NUL;
2440 memset(&muxer, 0, sizeof(muxer));
@@ -2445,7 +2443,7 @@ static const char *unpack_with_sideband(struct shallow_info *si)
2443 if (start_async(&muxer))
2444 return NULL;
2445
2448 - ret = unpack(muxer.in, si);
2446 + ret = unpack(muxer.in, si, transaction);
2447
2448 finish_async(&muxer);
2449 return ret;
@@ -2622,6 +2620,7 @@ int cmd_receive_pack(int argc,
2620 struct oid_array ref = OID_ARRAY_INIT;
2621 struct shallow_info si;
2622 struct packet_reader reader;
2623 + struct odb_transaction *transaction = NULL;
2624
2625 struct option options[] = {
2626 OPT__QUIET(&quiet, N_("quiet")),
@@ -2706,11 +2705,14 @@ int cmd_receive_pack(int argc,
2705 if (!si.nr_ours && !si.nr_theirs)
2706 shallow_update = 0;
2707 if (!delete_only(commands)) {
2709 - unpack_status = unpack_with_sideband(&si);
2708 + if (odb_transaction_begin(the_repository->objects, &transaction, ODB_TRANSACTION_RECEIVE))
2709 + unpack_status = "unable to start object transaction";
2710 + else
2711 + unpack_status = unpack_with_sideband(&si, transaction);
2712 update_shallow_info(commands, &si, &ref);
2713 }
2714 use_keepalive = KEEPALIVE_ALWAYS;
2713 - execute_commands(commands, unpack_status, &si,
2715 + execute_commands(commands, unpack_status, &si, transaction,
2716 &push_options);
2717 delete_tempfile(&pack_lockfile);
2718 sigchain_push(SIGPIPE, SIG_IGN);
@@ -2719,7 +2721,7 @@ int cmd_receive_pack(int argc,
2721 else if (report_status)
2722 report(commands, unpack_status);
2723 sigchain_pop(SIGPIPE);
2722 - run_receive_hook(commands, "post-receive", 1,
2724 + run_receive_hook(commands, "post-receive", 1, NULL,
2725 &push_options);
2726 run_update_post_hook(commands);
2727 free_commands(commands);