summaryrefslogtreecommitdiff
path: root/builtin/receive-pack.c
diff options
context:
space:
mode:
Diffstat (limited to 'builtin/receive-pack.c')
-rw-r--r--builtin/receive-pack.c331
1 files changed, 270 insertions, 61 deletions
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index a01ac20..e0ce78e 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1,4 +1,5 @@
#include "builtin.h"
+#include "lockfile.h"
#include "pack.h"
#include "refs.h"
#include "pkt-line.h"
@@ -25,7 +26,8 @@ enum deny_action {
DENY_UNCONFIGURED,
DENY_IGNORE,
DENY_WARN,
- DENY_REFUSE
+ DENY_REFUSE,
+ DENY_UPDATE_INSTEAD
};
static int deny_deletes;
@@ -36,9 +38,11 @@ static int receive_fsck_objects = -1;
static int transfer_fsck_objects = -1;
static int receive_unpack_limit = -1;
static int transfer_unpack_limit = -1;
+static int advertise_atomic_push = 1;
static int unpack_limit = 100;
static int report_status;
static int use_sideband;
+static int use_atomic;
static int quiet;
static int prefer_ofs_delta = 1;
static int auto_update_server_info;
@@ -65,6 +69,7 @@ static const char *NONCE_SLOP = "SLOP";
static const char *nonce_status;
static long nonce_stamp_slop;
static unsigned long nonce_stamp_slop_limit;
+static struct ref_transaction *transaction;
static enum deny_action parse_deny_action(const char *var, const char *value)
{
@@ -75,6 +80,8 @@ static enum deny_action parse_deny_action(const char *var, const char *value)
return DENY_WARN;
if (!strcasecmp(value, "refuse"))
return DENY_REFUSE;
+ if (!strcasecmp(value, "updateinstead"))
+ return DENY_UPDATE_INSTEAD;
}
if (git_config_bool(var, value))
return DENY_REFUSE;
@@ -156,6 +163,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
return 0;
}
+ if (strcmp(var, "receive.advertiseatomic") == 0) {
+ advertise_atomic_push = git_config_bool(var, value);
+ return 0;
+ }
+
return git_default_config(var, value, cb);
}
@@ -171,6 +183,8 @@ static void show_ref(const char *path, const unsigned char *sha1)
strbuf_addstr(&cap,
"report-status delete-refs side-band-64k quiet");
+ if (advertise_atomic_push)
+ strbuf_addstr(&cap, " atomic");
if (prefer_ofs_delta)
strbuf_addstr(&cap, " ofs-delta");
if (push_cert_nonce)
@@ -430,7 +444,7 @@ static const char *check_nonce(const char *buf, size_t len)
nonce_stamp_slop = (long)ostamp - (long)stamp;
if (nonce_stamp_slop_limit &&
- abs(nonce_stamp_slop) <= nonce_stamp_slop_limit) {
+ labs(nonce_stamp_slop) <= nonce_stamp_slop_limit) {
/*
* Pretend as if the received nonce (which passes the
* HMAC check, so it is not a forged by third-party)
@@ -452,7 +466,6 @@ leave:
static void prepare_push_cert_sha1(struct child_process *proc)
{
static int already_done;
- struct argv_array env = ARGV_ARRAY_INIT;
if (!push_cert.len)
return;
@@ -486,20 +499,26 @@ static void prepare_push_cert_sha1(struct child_process *proc)
nonce_status = check_nonce(push_cert.buf, bogs);
}
if (!is_null_sha1(push_cert_sha1)) {
- argv_array_pushf(&env, "GIT_PUSH_CERT=%s", sha1_to_hex(push_cert_sha1));
- argv_array_pushf(&env, "GIT_PUSH_CERT_SIGNER=%s",
+ argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT=%s",
+ sha1_to_hex(push_cert_sha1));
+ argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_SIGNER=%s",
sigcheck.signer ? sigcheck.signer : "");
- argv_array_pushf(&env, "GIT_PUSH_CERT_KEY=%s",
+ argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_KEY=%s",
sigcheck.key ? sigcheck.key : "");
- argv_array_pushf(&env, "GIT_PUSH_CERT_STATUS=%c", sigcheck.result);
+ argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_STATUS=%c",
+ sigcheck.result);
if (push_cert_nonce) {
- argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE=%s", push_cert_nonce);
- argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE_STATUS=%s", nonce_status);
+ argv_array_pushf(&proc->env_array,
+ "GIT_PUSH_CERT_NONCE=%s",
+ push_cert_nonce);
+ argv_array_pushf(&proc->env_array,
+ "GIT_PUSH_CERT_NONCE_STATUS=%s",
+ nonce_status);
if (nonce_status == NONCE_SLOP)
- argv_array_pushf(&env, "GIT_PUSH_CERT_NONCE_SLOP=%ld",
+ argv_array_pushf(&proc->env_array,
+ "GIT_PUSH_CERT_NONCE_SLOP=%ld",
nonce_stamp_slop);
}
- proc->env = env.argv;
}
}
@@ -521,8 +540,6 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
proc.in = -1;
proc.stdout_to_stderr = 1;
- prepare_push_cert_sha1(&proc);
-
if (use_sideband) {
memset(&muxer, 0, sizeof(muxer));
muxer.proc = copy_to_sideband;
@@ -533,6 +550,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
proc.err = muxer.in;
}
+ prepare_push_cert_sha1(&proc);
+
code = start_command(&proc);
if (code) {
if (use_sideband)
@@ -724,11 +743,109 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si)
return 0;
}
+static const char *push_to_deploy(unsigned char *sha1,
+ struct argv_array *env,
+ const char *work_tree)
+{
+ const char *update_refresh[] = {
+ "update-index", "-q", "--ignore-submodules", "--refresh", NULL
+ };
+ const char *diff_files[] = {
+ "diff-files", "--quiet", "--ignore-submodules", "--", NULL
+ };
+ const char *diff_index[] = {
+ "diff-index", "--quiet", "--cached", "--ignore-submodules",
+ "HEAD", "--", NULL
+ };
+ const char *read_tree[] = {
+ "read-tree", "-u", "-m", NULL, NULL
+ };
+ struct child_process child = CHILD_PROCESS_INIT;
+
+ child.argv = update_refresh;
+ child.env = env->argv;
+ child.dir = work_tree;
+ child.no_stdin = 1;
+ child.stdout_to_stderr = 1;
+ child.git_cmd = 1;
+ if (run_command(&child))
+ return "Up-to-date check failed";
+
+ /* run_command() does not clean up completely; reinitialize */
+ child_process_init(&child);
+ child.argv = diff_files;
+ child.env = env->argv;
+ child.dir = work_tree;
+ child.no_stdin = 1;
+ child.stdout_to_stderr = 1;
+ child.git_cmd = 1;
+ if (run_command(&child))
+ return "Working directory has unstaged changes";
+
+ child_process_init(&child);
+ child.argv = diff_index;
+ child.env = env->argv;
+ child.no_stdin = 1;
+ child.no_stdout = 1;
+ child.stdout_to_stderr = 0;
+ child.git_cmd = 1;
+ if (run_command(&child))
+ return "Working directory has staged changes";
+
+ read_tree[3] = sha1_to_hex(sha1);
+ child_process_init(&child);
+ child.argv = read_tree;
+ child.env = env->argv;
+ child.dir = work_tree;
+ child.no_stdin = 1;
+ child.no_stdout = 1;
+ child.stdout_to_stderr = 0;
+ child.git_cmd = 1;
+ if (run_command(&child))
+ return "Could not update working tree to new HEAD";
+
+ return NULL;
+}
+
+static const char *push_to_checkout_hook = "push-to-checkout";
+
+static const char *push_to_checkout(unsigned char *sha1,
+ struct argv_array *env,
+ const char *work_tree)
+{
+ argv_array_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree));
+ if (run_hook_le(env->argv, push_to_checkout_hook,
+ sha1_to_hex(sha1), NULL))
+ return "push-to-checkout hook declined";
+ else
+ return NULL;
+}
+
+static const char *update_worktree(unsigned char *sha1)
+{
+ const char *retval;
+ const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : "..";
+ struct argv_array env = ARGV_ARRAY_INIT;
+
+ if (is_bare_repository())
+ return "denyCurrentBranch = updateInstead needs a worktree";
+
+ argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir()));
+
+ if (!find_hook(push_to_checkout_hook))
+ retval = push_to_deploy(sha1, &env, work_tree);
+ else
+ retval = push_to_checkout(sha1, &env, work_tree);
+
+ argv_array_clear(&env);
+ return retval;
+}
+
static const char *update(struct command *cmd, struct shallow_info *si)
{
const char *name = cmd->ref_name;
struct strbuf namespaced_name_buf = STRBUF_INIT;
- const char *namespaced_name;
+ const char *namespaced_name, *ret;
unsigned char *old_sha1 = cmd->old_sha1;
unsigned char *new_sha1 = cmd->new_sha1;
@@ -754,6 +871,11 @@ static const char *update(struct command *cmd, struct shallow_info *si)
if (deny_current_branch == DENY_UNCONFIGURED)
refuse_unconfigured_deny();
return "branch is currently checked out";
+ case DENY_UPDATE_INSTEAD:
+ ret = update_worktree(new_sha1);
+ if (ret)
+ return ret;
+ break;
}
}
@@ -778,10 +900,13 @@ static const char *update(struct command *cmd, struct shallow_info *si)
break;
case DENY_REFUSE:
case DENY_UNCONFIGURED:
+ case DENY_UPDATE_INSTEAD:
if (deny_delete_current == DENY_UNCONFIGURED)
refuse_unconfigured_deny_delete_current();
rp_error("refusing to delete the current branch: %s", name);
return "deletion of the current branch prohibited";
+ default:
+ return "Invalid denyDeleteCurrent setting";
}
}
}
@@ -815,6 +940,7 @@ static const char *update(struct command *cmd, struct shallow_info *si)
}
if (is_null_sha1(new_sha1)) {
+ struct strbuf err = STRBUF_INIT;
if (!parse_object(old_sha1)) {
old_sha1 = NULL;
if (ref_exists(name)) {
@@ -824,34 +950,36 @@ static const char *update(struct command *cmd, struct shallow_info *si)
cmd->did_not_exist = 1;
}
}
- if (delete_ref(namespaced_name, old_sha1, 0)) {
- rp_error("failed to delete %s", name);
+ if (ref_transaction_delete(transaction,
+ namespaced_name,
+ old_sha1,
+ 0, old_sha1 != NULL,
+ "push", &err)) {
+ rp_error("%s", err.buf);
+ strbuf_release(&err);
return "failed to delete";
}
+ strbuf_release(&err);
return NULL; /* good */
}
else {
struct strbuf err = STRBUF_INIT;
- struct ref_transaction *transaction;
-
if (shallow_update && si->shallow_ref[cmd->index] &&
update_shallow_ref(cmd, si))
return "shallow error";
- transaction = ref_transaction_begin(&err);
- if (!transaction ||
- ref_transaction_update(transaction, namespaced_name,
- new_sha1, old_sha1, 0, 1, &err) ||
- ref_transaction_commit(transaction, "push", &err)) {
- ref_transaction_free(transaction);
-
+ if (ref_transaction_update(transaction,
+ namespaced_name,
+ new_sha1, old_sha1,
+ 0, 1, "push",
+ &err)) {
rp_error("%s", err.buf);
strbuf_release(&err);
+
return "failed to update ref";
}
-
- ref_transaction_free(transaction);
strbuf_release(&err);
+
return NULL; /* good */
}
}
@@ -907,7 +1035,7 @@ static void check_aliased_update(struct command *cmd, struct string_list *list)
int flag;
strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
- dst_name = resolve_ref_unsafe(buf.buf, sha1, 0, &flag);
+ dst_name = resolve_ref_unsafe(buf.buf, 0, sha1, &flag);
strbuf_release(&buf);
if (!(flag & REF_ISSYMREF))
@@ -957,7 +1085,7 @@ static void check_aliased_updates(struct command *commands)
string_list_append(&ref_list, cmd->ref_name);
item->util = (void *)cmd;
}
- sort_string_list(&ref_list);
+ string_list_sort(&ref_list);
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string)
@@ -1035,11 +1163,105 @@ static void reject_updates_to_hidden(struct command *commands)
}
}
+static int should_process_cmd(struct command *cmd)
+{
+ return !cmd->error_string && !cmd->skip_update;
+}
+
+static void warn_if_skipped_connectivity_check(struct command *commands,
+ struct shallow_info *si)
+{
+ struct command *cmd;
+ int checked_connectivity = 1;
+
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (should_process_cmd(cmd) && si->shallow_ref[cmd->index]) {
+ error("BUG: connectivity check has not been run on ref %s",
+ cmd->ref_name);
+ checked_connectivity = 0;
+ }
+ }
+ if (!checked_connectivity)
+ die("BUG: connectivity check skipped???");
+}
+
+static void execute_commands_non_atomic(struct command *commands,
+ struct shallow_info *si)
+{
+ struct command *cmd;
+ struct strbuf err = STRBUF_INIT;
+
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!should_process_cmd(cmd))
+ continue;
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction) {
+ rp_error("%s", err.buf);
+ strbuf_reset(&err);
+ cmd->error_string = "transaction failed to start";
+ continue;
+ }
+
+ cmd->error_string = update(cmd, si);
+
+ if (!cmd->error_string
+ && ref_transaction_commit(transaction, &err)) {
+ rp_error("%s", err.buf);
+ strbuf_reset(&err);
+ cmd->error_string = "failed to update ref";
+ }
+ ref_transaction_free(transaction);
+ }
+ strbuf_release(&err);
+}
+
+static void execute_commands_atomic(struct command *commands,
+ struct shallow_info *si)
+{
+ struct command *cmd;
+ struct strbuf err = STRBUF_INIT;
+ const char *reported_error = "atomic push failure";
+
+ transaction = ref_transaction_begin(&err);
+ if (!transaction) {
+ rp_error("%s", err.buf);
+ strbuf_reset(&err);
+ reported_error = "transaction failed to start";
+ goto failure;
+ }
+
+ for (cmd = commands; cmd; cmd = cmd->next) {
+ if (!should_process_cmd(cmd))
+ continue;
+
+ cmd->error_string = update(cmd, si);
+
+ if (cmd->error_string)
+ goto failure;
+ }
+
+ if (ref_transaction_commit(transaction, &err)) {
+ rp_error("%s", err.buf);
+ reported_error = "atomic transaction failed";
+ goto failure;
+ }
+ goto cleanup;
+
+failure:
+ for (cmd = commands; cmd; cmd = cmd->next)
+ if (!cmd->error_string)
+ cmd->error_string = reported_error;
+
+cleanup:
+ ref_transaction_free(transaction);
+ strbuf_release(&err);
+}
+
static void execute_commands(struct command *commands,
const char *unpacker_error,
struct shallow_info *si)
{
- int checked_connectivity;
struct command *cmd;
unsigned char sha1[20];
struct iterate_data data;
@@ -1068,29 +1290,15 @@ static void execute_commands(struct command *commands,
check_aliased_updates(commands);
free(head_name_to_free);
- head_name = head_name_to_free = resolve_refdup("HEAD", sha1, 0, NULL);
+ head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL);
- checked_connectivity = 1;
- for (cmd = commands; cmd; cmd = cmd->next) {
- if (cmd->error_string)
- continue;
-
- if (cmd->skip_update)
- continue;
-
- cmd->error_string = update(cmd, si);
- if (shallow_update && !cmd->error_string &&
- si->shallow_ref[cmd->index]) {
- error("BUG: connectivity check has not been run on ref %s",
- cmd->ref_name);
- checked_connectivity = 0;
- }
- }
+ if (use_atomic)
+ execute_commands_atomic(commands, si);
+ else
+ execute_commands_non_atomic(commands, si);
- if (shallow_update && !checked_connectivity)
- error("BUG: run 'git fsck' for safety.\n"
- "If there are errors, try to remove "
- "the reported refs above");
+ if (shallow_update)
+ warn_if_skipped_connectivity_check(commands, si);
}
static struct command **queue_command(struct command **tail,
@@ -1172,6 +1380,9 @@ static struct command *read_head_info(struct sha1_array *shallow)
use_sideband = LARGE_PACKET_MAX;
if (parse_feature_request(feature_list, "quiet"))
quiet = 1;
+ if (advertise_atomic_push
+ && parse_feature_request(feature_list, "atomic"))
+ use_atomic = 1;
}
if (!strcmp(line, "push-cert")) {
@@ -1229,7 +1440,6 @@ static const char *pack_lockfile;
static const char *unpack(int err_fd, struct shallow_info *si)
{
struct pack_header hdr;
- struct argv_array av = ARGV_ARRAY_INIT;
const char *hdr_err;
int status;
char hdr_arg[38];
@@ -1252,16 +1462,16 @@ static const char *unpack(int err_fd, struct shallow_info *si)
if (si->nr_ours || si->nr_theirs) {
alt_shallow_file = setup_temporary_shallow(si->shallow);
- argv_array_pushl(&av, "--shallow-file", alt_shallow_file, NULL);
+ argv_array_push(&child.args, "--shallow-file");
+ argv_array_push(&child.args, alt_shallow_file);
}
if (ntohl(hdr.hdr_entries) < unpack_limit) {
- argv_array_pushl(&av, "unpack-objects", hdr_arg, NULL);
+ argv_array_pushl(&child.args, "unpack-objects", hdr_arg, NULL);
if (quiet)
- argv_array_push(&av, "-q");
+ argv_array_push(&child.args, "-q");
if (fsck_objects)
- argv_array_push(&av, "--strict");
- child.argv = av.argv;
+ argv_array_push(&child.args, "--strict");
child.no_stdout = 1;
child.err = err_fd;
child.git_cmd = 1;
@@ -1276,13 +1486,12 @@ static const char *unpack(int err_fd, struct shallow_info *si)
if (gethostname(keep_arg + s, sizeof(keep_arg) - s))
strcpy(keep_arg + s, "localhost");
- argv_array_pushl(&av, "index-pack",
+ argv_array_pushl(&child.args, "index-pack",
"--stdin", hdr_arg, keep_arg, NULL);
if (fsck_objects)
- argv_array_push(&av, "--strict");
+ argv_array_push(&child.args, "--strict");
if (fix_thin)
- argv_array_push(&av, "--fix-thin");
- child.argv = av.argv;
+ argv_array_push(&child.args, "--fix-thin");
child.out = -1;
child.err = err_fd;
child.git_cmd = 1;