#include "pack.h"
#include "refs.h"
#include "pkt-line.h"
+#include "sideband.h"
#include "run-command.h"
#include "exec_cmd.h"
#include "commit.h"
#include "remote.h"
#include "transport.h"
-static const char receive_pack_usage[] = "git-receive-pack <git-dir>";
+static const char receive_pack_usage[] = "git receive-pack <git-dir>";
enum deny_action {
+ DENY_UNCONFIGURED,
DENY_IGNORE,
DENY_WARN,
DENY_REFUSE,
};
-static int deny_deletes = 0;
-static int deny_non_fast_forwards = 0;
-static enum deny_action deny_current_branch = DENY_WARN;
+static int deny_deletes;
+static int deny_non_fast_forwards;
+static enum deny_action deny_current_branch = DENY_UNCONFIGURED;
+static enum deny_action deny_delete_current = DENY_UNCONFIGURED;
static int receive_fsck_objects;
static int receive_unpack_limit = -1;
static int transfer_unpack_limit = -1;
static int unpack_limit = 100;
static int report_status;
-
-static char capabilities[] = " report-status delete-refs ";
-static int capabilities_sent;
+static int use_sideband;
+static int prefer_ofs_delta = 1;
+static int auto_update_server_info;
+static int auto_gc = 1;
+static const char *head_name;
+static int sent_capabilities;
static enum deny_action parse_deny_action(const char *var, const char *value)
{
return 0;
}
+ if (strcmp(var, "receive.denydeletecurrent") == 0) {
+ deny_delete_current = parse_deny_action(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "repack.usedeltabaseoffset") == 0) {
+ prefer_ofs_delta = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "receive.updateserverinfo") == 0) {
+ auto_update_server_info = git_config_bool(var, value);
+ return 0;
+ }
+
+ if (strcmp(var, "receive.autogc") == 0) {
+ auto_gc = git_config_bool(var, value);
+ return 0;
+ }
+
return git_default_config(var, value, cb);
}
static int show_ref(const char *path, const unsigned char *sha1, int flag, void *cb_data)
{
- if (capabilities_sent)
+ if (sent_capabilities)
packet_write(1, "%s %s\n", sha1_to_hex(sha1), path);
else
- packet_write(1, "%s %s%c%s\n",
- sha1_to_hex(sha1), path, 0, capabilities);
- capabilities_sent = 1;
+ packet_write(1, "%s %s%c%s%s\n",
+ sha1_to_hex(sha1), path, 0,
+ " report-status delete-refs side-band-64k",
+ prefer_ofs_delta ? " ofs-delta" : "");
+ sent_capabilities = 1;
return 0;
}
static void write_head_info(void)
{
for_each_ref(show_ref, NULL);
- if (!capabilities_sent)
+ if (!sent_capabilities)
show_ref("capabilities^{}", null_sha1, 0, NULL);
}
char ref_name[FLEX_ARRAY]; /* more */
};
-static struct command *commands;
-
static const char pre_receive_hook[] = "hooks/pre-receive";
static const char post_receive_hook[] = "hooks/post-receive";
-static int hook_status(int code, const char *hook_name)
+static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2)));
+static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
+
+static void report_message(const char *prefix, const char *err, va_list params)
{
- switch (code) {
- case 0:
- return 0;
- case -ERR_RUN_COMMAND_FORK:
- return error("hook fork failed");
- case -ERR_RUN_COMMAND_EXEC:
- return error("hook execute failed");
- case -ERR_RUN_COMMAND_PIPE:
- return error("hook pipe failed");
- case -ERR_RUN_COMMAND_WAITPID:
- return error("waitpid failed");
- case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
- return error("waitpid is confused");
- case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
- return error("%s died of signal", hook_name);
- case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
- return error("%s died strangely", hook_name);
- default:
- error("%s exited with error code %d", hook_name, -code);
- return -code;
+ int sz = strlen(prefix);
+ char msg[4096];
+
+ strncpy(msg, prefix, sz);
+ sz += vsnprintf(msg + sz, sizeof(msg) - sz, err, params);
+ if (sz > (sizeof(msg) - 1))
+ sz = sizeof(msg) - 1;
+ msg[sz++] = '\n';
+
+ if (use_sideband)
+ send_sideband(1, 2, msg, sz, use_sideband);
+ else
+ xwrite(2, msg, sz);
+}
+
+static void rp_warning(const char *err, ...)
+{
+ va_list params;
+ va_start(params, err);
+ report_message("warning: ", err, params);
+ va_end(params);
+}
+
+static void rp_error(const char *err, ...)
+{
+ va_list params;
+ va_start(params, err);
+ report_message("error: ", err, params);
+ va_end(params);
+}
+
+static int copy_to_sideband(int in, int out, void *arg)
+{
+ char data[128];
+ while (1) {
+ ssize_t sz = xread(in, data, sizeof(data));
+ if (sz <= 0)
+ break;
+ send_sideband(1, 2, data, sz, use_sideband);
}
+ close(in);
+ return 0;
}
-static int run_hook(const char *hook_name)
+static int run_receive_hook(struct command *commands, const char *hook_name)
{
static char buf[sizeof(commands->old_sha1) * 2 + PATH_MAX + 4];
struct command *cmd;
struct child_process proc;
+ struct async muxer;
const char *argv[2];
int have_input = 0, code;
proc.in = -1;
proc.stdout_to_stderr = 1;
+ if (use_sideband) {
+ memset(&muxer, 0, sizeof(muxer));
+ muxer.proc = copy_to_sideband;
+ muxer.in = -1;
+ code = start_async(&muxer);
+ if (code)
+ return code;
+ proc.err = muxer.in;
+ }
+
code = start_command(&proc);
- if (code)
- return hook_status(code, hook_name);
+ if (code) {
+ if (use_sideband)
+ finish_async(&muxer);
+ return code;
+ }
+
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string) {
size_t n = snprintf(buf, sizeof(buf), "%s %s %s\n",
}
}
close(proc.in);
- return hook_status(finish_command(&proc), hook_name);
+ if (use_sideband)
+ finish_async(&muxer);
+ return finish_command(&proc);
}
static int run_update_hook(struct command *cmd)
{
static const char update_hook[] = "hooks/update";
- struct child_process proc;
const char *argv[5];
+ struct child_process proc;
+ int code;
if (access(update_hook, X_OK) < 0)
return 0;
argv[4] = NULL;
memset(&proc, 0, sizeof(proc));
- proc.argv = argv;
proc.no_stdin = 1;
proc.stdout_to_stderr = 1;
+ proc.err = use_sideband ? -1 : 0;
+ proc.argv = argv;
- return hook_status(run_command(&proc), update_hook);
+ code = start_command(&proc);
+ if (code)
+ return code;
+ if (use_sideband)
+ copy_to_sideband(proc.err, -1, NULL);
+ return finish_command(&proc);
}
static int is_ref_checked_out(const char *ref)
{
- unsigned char sha1[20];
- const char *head;
-
if (is_bare_repository())
return 0;
- head = resolve_ref("HEAD", sha1, 0, NULL);
- if (!head)
+ if (!head_name)
return 0;
- return !strcmp(head, ref);
+ return !strcmp(head_name, ref);
+}
+
+static char *refuse_unconfigured_deny_msg[] = {
+ "By default, updating the current branch in a non-bare repository",
+ "is denied, because it will make the index and work tree inconsistent",
+ "with what you pushed, and will require 'git reset --hard' to match",
+ "the work tree to HEAD.",
+ "",
+ "You can set 'receive.denyCurrentBranch' configuration variable to",
+ "'ignore' or 'warn' in the remote repository to allow pushing into",
+ "its current branch; however, this is not recommended unless you",
+ "arranged to update its work tree to match what you pushed in some",
+ "other way.",
+ "",
+ "To squelch this message and still keep the default behaviour, set",
+ "'receive.denyCurrentBranch' configuration variable to 'refuse'."
+};
+
+static void refuse_unconfigured_deny(void)
+{
+ int i;
+ for (i = 0; i < ARRAY_SIZE(refuse_unconfigured_deny_msg); i++)
+ rp_error("%s", refuse_unconfigured_deny_msg[i]);
+}
+
+static char *refuse_unconfigured_deny_delete_current_msg[] = {
+ "By default, deleting the current branch is denied, because the next",
+ "'git clone' won't result in any file checked out, causing confusion.",
+ "",
+ "You can set 'receive.denyDeleteCurrent' configuration variable to",
+ "'warn' or 'ignore' in the remote repository to allow deleting the",
+ "current branch, with or without a warning message.",
+ "",
+ "To squelch this message, you can set it to 'refuse'."
+};
+
+static void refuse_unconfigured_deny_delete_current(void)
+{
+ int i;
+ for (i = 0;
+ i < ARRAY_SIZE(refuse_unconfigured_deny_delete_current_msg);
+ i++)
+ rp_error("%s", refuse_unconfigured_deny_delete_current_msg[i]);
}
static const char *update(struct command *cmd)
/* only refs/... are allowed */
if (prefixcmp(name, "refs/") || check_ref_format(name + 5)) {
- error("refusing to create funny ref '%s' remotely", name);
+ rp_error("refusing to create funny ref '%s' remotely", name);
return "funny refname";
}
- switch (deny_current_branch) {
- case DENY_IGNORE:
- break;
- case DENY_WARN:
- if (!is_ref_checked_out(name))
+ if (is_ref_checked_out(name)) {
+ switch (deny_current_branch) {
+ case DENY_IGNORE:
break;
- warning("updating the currently checked out branch; this may"
- " cause confusion,\n"
- "as the index and working tree do not reflect changes"
- " that are now in HEAD.");
- break;
- case DENY_REFUSE:
- if (!is_ref_checked_out(name))
+ case DENY_WARN:
+ rp_warning("updating the current branch");
break;
- error("refusing to update checked out branch: %s", name);
- return "branch is currently checked out";
+ case DENY_REFUSE:
+ case DENY_UNCONFIGURED:
+ rp_error("refusing to update checked out branch: %s", name);
+ if (deny_current_branch == DENY_UNCONFIGURED)
+ refuse_unconfigured_deny();
+ return "branch is currently checked out";
+ }
}
if (!is_null_sha1(new_sha1) && !has_sha1_file(new_sha1)) {
"but I can't find it!", sha1_to_hex(new_sha1));
return "bad pack";
}
- if (deny_deletes && is_null_sha1(new_sha1) &&
- !is_null_sha1(old_sha1) &&
- !prefixcmp(name, "refs/heads/")) {
- error("denying ref deletion for %s", name);
- return "deletion prohibited";
+
+ if (!is_null_sha1(old_sha1) && is_null_sha1(new_sha1)) {
+ if (deny_deletes && !prefixcmp(name, "refs/heads/")) {
+ rp_error("denying ref deletion for %s", name);
+ return "deletion prohibited";
+ }
+
+ if (!strcmp(name, head_name)) {
+ switch (deny_delete_current) {
+ case DENY_IGNORE:
+ break;
+ case DENY_WARN:
+ rp_warning("deleting the current branch");
+ break;
+ case DENY_REFUSE:
+ case DENY_UNCONFIGURED:
+ 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";
+ }
+ }
}
+
if (deny_non_fast_forwards && !is_null_sha1(new_sha1) &&
!is_null_sha1(old_sha1) &&
!prefixcmp(name, "refs/heads/")) {
break;
free_commit_list(bases);
if (!ent) {
- error("denying non-fast forward %s"
- " (you should pull first)", name);
- return "non-fast forward";
+ rp_error("denying non-fast-forward %s"
+ " (you should pull first)", name);
+ return "non-fast-forward";
}
}
if (run_update_hook(cmd)) {
- error("hook declined to update %s", name);
+ rp_error("hook declined to update %s", name);
return "hook declined";
}
if (is_null_sha1(new_sha1)) {
if (!parse_object(old_sha1)) {
- warning ("Allowing deletion of corrupt ref.");
+ rp_warning("Allowing deletion of corrupt ref.");
old_sha1 = NULL;
}
if (delete_ref(name, old_sha1, 0)) {
- error("failed to delete %s", name);
+ rp_error("failed to delete %s", name);
return "failed to delete";
}
return NULL; /* good */
else {
lock = lock_any_ref_for_update(name, old_sha1, 0);
if (!lock) {
- error("failed to lock %s", name);
+ rp_error("failed to lock %s", name);
return "failed to lock";
}
if (write_ref_sha1(lock, new_sha1, "push")) {
static char update_post_hook[] = "hooks/post-update";
-static void run_update_post_hook(struct command *cmd)
+static void run_update_post_hook(struct command *commands)
{
- struct command *cmd_p;
+ struct command *cmd;
int argc;
const char **argv;
+ struct child_process proc;
- for (argc = 0, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
- if (cmd_p->error_string)
+ for (argc = 0, cmd = commands; cmd; cmd = cmd->next) {
+ if (cmd->error_string)
continue;
argc++;
}
argv = xmalloc(sizeof(*argv) * (2 + argc));
argv[0] = update_post_hook;
- for (argc = 1, cmd_p = cmd; cmd_p; cmd_p = cmd_p->next) {
+ for (argc = 1, cmd = commands; cmd; cmd = cmd->next) {
char *p;
- if (cmd_p->error_string)
+ if (cmd->error_string)
continue;
- p = xmalloc(strlen(cmd_p->ref_name) + 1);
- strcpy(p, cmd_p->ref_name);
+ p = xmalloc(strlen(cmd->ref_name) + 1);
+ strcpy(p, cmd->ref_name);
argv[argc] = p;
argc++;
}
argv[argc] = NULL;
- run_command_v_opt(argv, RUN_COMMAND_NO_STDIN
- | RUN_COMMAND_STDOUT_TO_STDERR);
+
+ memset(&proc, 0, sizeof(proc));
+ proc.no_stdin = 1;
+ proc.stdout_to_stderr = 1;
+ proc.err = use_sideband ? -1 : 0;
+ proc.argv = argv;
+
+ if (!start_command(&proc)) {
+ if (use_sideband)
+ copy_to_sideband(proc.err, -1, NULL);
+ finish_command(&proc);
+ }
}
-static void execute_commands(const char *unpacker_error)
+static void execute_commands(struct command *commands, const char *unpacker_error)
{
- struct command *cmd = commands;
+ struct command *cmd;
+ unsigned char sha1[20];
if (unpacker_error) {
- while (cmd) {
+ for (cmd = commands; cmd; cmd = cmd->next)
cmd->error_string = "n/a (unpacker error)";
- cmd = cmd->next;
- }
return;
}
- if (run_hook(pre_receive_hook)) {
- while (cmd) {
+ if (run_receive_hook(commands, pre_receive_hook)) {
+ for (cmd = commands; cmd; cmd = cmd->next)
cmd->error_string = "pre-receive hook declined";
- cmd = cmd->next;
- }
return;
}
- while (cmd) {
+ head_name = resolve_ref("HEAD", sha1, 0, NULL);
+
+ for (cmd = commands; cmd; cmd = cmd->next)
cmd->error_string = update(cmd);
- cmd = cmd->next;
- }
}
-static void read_head_info(void)
+static struct command *read_head_info(void)
{
+ struct command *commands = NULL;
struct command **p = &commands;
for (;;) {
static char line[1000];
if (reflen + 82 < len) {
if (strstr(refname + reflen + 1, "report-status"))
report_status = 1;
+ if (strstr(refname + reflen + 1, "side-band-64k"))
+ use_sideband = LARGE_PACKET_MAX;
}
cmd = xmalloc(sizeof(struct command) + len - 80);
hashcpy(cmd->old_sha1, old_sha1);
*p = cmd;
p = &cmd->next;
}
+ return commands;
}
static const char *parse_pack_header(struct pack_header *hdr)
unpacker[i++] = hdr_arg;
unpacker[i++] = NULL;
code = run_command_v_opt(unpacker, RUN_GIT_CMD);
- switch (code) {
- case 0:
+ if (!code)
return NULL;
- case -ERR_RUN_COMMAND_FORK:
- return "unpack fork failed";
- case -ERR_RUN_COMMAND_EXEC:
- return "unpack execute failed";
- case -ERR_RUN_COMMAND_WAITPID:
- return "waitpid failed";
- case -ERR_RUN_COMMAND_WAITPID_WRONG_PID:
- return "waitpid is confused";
- case -ERR_RUN_COMMAND_WAITPID_SIGNAL:
- return "unpacker died of signal";
- case -ERR_RUN_COMMAND_WAITPID_NOEXIT:
- return "unpacker died strangely";
- default:
- return "unpacker exited with error code";
- }
+ return "unpack-objects abnormal exit";
} else {
const char *keeper[7];
int s, status, i = 0;
ip.argv = keeper;
ip.out = -1;
ip.git_cmd = 1;
- if (start_command(&ip))
+ status = start_command(&ip);
+ if (status) {
return "index-pack fork failed";
+ }
pack_lockfile = index_pack_lockfile(ip.out);
close(ip.out);
status = finish_command(&ip);
}
}
-static void report(const char *unpack_status)
+static void report(struct command *commands, const char *unpack_status)
{
struct command *cmd;
- packet_write(1, "unpack %s\n",
- unpack_status ? unpack_status : "ok");
+ struct strbuf buf = STRBUF_INIT;
+
+ packet_buf_write(&buf, "unpack %s\n",
+ unpack_status ? unpack_status : "ok");
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string)
- packet_write(1, "ok %s\n",
- cmd->ref_name);
+ packet_buf_write(&buf, "ok %s\n",
+ cmd->ref_name);
else
- packet_write(1, "ng %s %s\n",
- cmd->ref_name, cmd->error_string);
+ packet_buf_write(&buf, "ng %s %s\n",
+ cmd->ref_name, cmd->error_string);
}
- packet_flush(1);
+ packet_buf_flush(&buf);
+
+ if (use_sideband)
+ send_sideband(1, 1, buf.buf, buf.len, use_sideband);
+ else
+ safe_write(1, buf.buf, buf.len);
+ strbuf_release(&buf);
}
-static int delete_only(struct command *cmd)
+static int delete_only(struct command *commands)
{
- while (cmd) {
+ struct command *cmd;
+ for (cmd = commands; cmd; cmd = cmd->next) {
if (!is_null_sha1(cmd->new_sha1))
return 0;
- cmd = cmd->next;
}
return 1;
}
int cmd_receive_pack(int argc, const char **argv, const char *prefix)
{
+ int advertise_refs = 0;
+ int stateless_rpc = 0;
int i;
char *dir = NULL;
+ struct command *commands;
argv++;
for (i = 1; i < argc; i++) {
const char *arg = *argv++;
if (*arg == '-') {
- /* Do flag handling here */
+ if (!strcmp(arg, "--advertise-refs")) {
+ advertise_refs = 1;
+ continue;
+ }
+ if (!strcmp(arg, "--stateless-rpc")) {
+ stateless_rpc = 1;
+ continue;
+ }
+
usage(receive_pack_usage);
}
if (dir)
setup_path();
if (!enter_repo(dir, 0))
- die("'%s': unable to chdir or not a git archive", dir);
+ die("'%s' does not appear to be a git repository", dir);
if (is_repository_shallow())
die("attempt to push into a shallow repository");
else if (0 <= receive_unpack_limit)
unpack_limit = receive_unpack_limit;
- add_alternate_refs();
- write_head_info();
- clear_extra_refs();
+ if (advertise_refs || !stateless_rpc) {
+ add_alternate_refs();
+ write_head_info();
+ clear_extra_refs();
- /* EOF */
- packet_flush(1);
+ /* EOF */
+ packet_flush(1);
+ }
+ if (advertise_refs)
+ return 0;
- read_head_info();
- if (commands) {
+ if ((commands = read_head_info()) != NULL) {
const char *unpack_status = NULL;
if (!delete_only(commands))
unpack_status = unpack();
- execute_commands(unpack_status);
+ execute_commands(commands, unpack_status);
if (pack_lockfile)
- unlink(pack_lockfile);
+ unlink_or_warn(pack_lockfile);
if (report_status)
- report(unpack_status);
- run_hook(post_receive_hook);
+ report(commands, unpack_status);
+ run_receive_hook(commands, post_receive_hook);
run_update_post_hook(commands);
+ if (auto_gc) {
+ const char *argv_gc_auto[] = {
+ "gc", "--auto", "--quiet", NULL,
+ };
+ run_command_v_opt(argv_gc_auto, RUN_GIT_CMD);
+ }
+ if (auto_update_server_info)
+ update_server_info(0);
}
+ if (use_sideband)
+ packet_flush(1);
return 0;
}