diff options
Diffstat (limited to 'builtin')
-rw-r--r-- | builtin/am.c | 2 | ||||
-rw-r--r-- | builtin/clone.c | 2 | ||||
-rw-r--r-- | builtin/commit-graph.c | 63 | ||||
-rw-r--r-- | builtin/commit.c | 2 | ||||
-rw-r--r-- | builtin/fetch.c | 6 | ||||
-rw-r--r-- | builtin/gc.c | 337 | ||||
-rw-r--r-- | builtin/init-db.c | 6 | ||||
-rw-r--r-- | builtin/merge.c | 2 | ||||
-rw-r--r-- | builtin/rebase.c | 4 | ||||
-rw-r--r-- | builtin/receive-pack.c | 472 | ||||
-rw-r--r-- | builtin/send-pack.c | 19 |
11 files changed, 883 insertions, 32 deletions
diff --git a/builtin/am.c b/builtin/am.c index b5c63ddf1d..7259186408 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1813,7 +1813,7 @@ next: if (!state->rebasing) { am_destroy(state); close_object_store(the_repository->objects); - run_auto_gc(state->quiet); + run_auto_maintenance(state->quiet); } } diff --git a/builtin/clone.c b/builtin/clone.c index fbfd6568cd..391aa41075 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -1233,7 +1233,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) * Now that we know what algorithm the remote side is using, * let's set ours to the same thing. */ - initialize_repository_version(hash_algo); + initialize_repository_version(hash_algo, 1); repo_set_hash_algo(the_repository, hash_algo); mapped_refs = wanted_peer_refs(refs, &remote->fetch); diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index 523501f217..988445abdf 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -13,7 +13,8 @@ static char const * const builtin_commit_graph_usage[] = { N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"), N_("git commit-graph write [--object-dir <objdir>] [--append] " "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] " - "[--changed-paths] [--[no-]progress] <split options>"), + "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] " + "<split options>"), NULL }; @@ -25,7 +26,8 @@ static const char * const builtin_commit_graph_verify_usage[] = { static const char * const builtin_commit_graph_write_usage[] = { N_("git commit-graph write [--object-dir <objdir>] [--append] " "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] " - "[--changed-paths] [--[no-]progress] <split options>"), + "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] " + "<split options>"), NULL }; @@ -106,7 +108,7 @@ static int graph_verify(int argc, const char **argv) FREE_AND_NULL(graph_name); if (open_ok) - graph = load_commit_graph_one_fd_st(fd, &st, odb); + graph = load_commit_graph_one_fd_st(the_repository, fd, &st, odb); else graph = read_commit_graph_one(the_repository, odb); @@ -119,7 +121,7 @@ static int graph_verify(int argc, const char **argv) } extern int read_replace_refs; -static struct split_commit_graph_opts split_opts; +static struct commit_graph_opts write_opts; static int write_option_parse_split(const struct option *opt, const char *arg, int unset) @@ -162,6 +164,35 @@ static int read_one_commit(struct oidset *commits, struct progress *progress, return 0; } +static int write_option_max_new_filters(const struct option *opt, + const char *arg, + int unset) +{ + int *to = opt->value; + if (unset) + *to = -1; + else { + const char *s; + *to = strtol(arg, (char **)&s, 10); + if (*s) + return error(_("%s expects a numerical value"), + optname(opt, opt->flags)); + } + return 0; +} + +static int git_commit_graph_write_config(const char *var, const char *value, + void *cb) +{ + if (!strcmp(var, "commitgraph.maxnewfilters")) + write_opts.max_new_filters = git_config_int(var, value); + /* + * No need to fall-back to 'git_default_config', since this was already + * called in 'cmd_commit_graph()'. + */ + return 0; +} + static int graph_write(int argc, const char **argv) { struct string_list pack_indexes = STRING_LIST_INIT_NODUP; @@ -187,27 +218,33 @@ static int graph_write(int argc, const char **argv) OPT_BOOL(0, "changed-paths", &opts.enable_changed_paths, N_("enable computation for changed paths")), OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")), - OPT_CALLBACK_F(0, "split", &split_opts.flags, NULL, + OPT_CALLBACK_F(0, "split", &write_opts.split_flags, NULL, N_("allow writing an incremental commit-graph file"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, write_option_parse_split), - OPT_INTEGER(0, "max-commits", &split_opts.max_commits, + OPT_INTEGER(0, "max-commits", &write_opts.max_commits, N_("maximum number of commits in a non-base split commit-graph")), - OPT_INTEGER(0, "size-multiple", &split_opts.size_multiple, + OPT_INTEGER(0, "size-multiple", &write_opts.size_multiple, N_("maximum ratio between two levels of a split commit-graph")), - OPT_EXPIRY_DATE(0, "expire-time", &split_opts.expire_time, + OPT_EXPIRY_DATE(0, "expire-time", &write_opts.expire_time, N_("only expire files older than a given date-time")), + OPT_CALLBACK_F(0, "max-new-filters", &write_opts.max_new_filters, + NULL, N_("maximum number of changed-path Bloom filters to compute"), + 0, write_option_max_new_filters), OPT_END(), }; opts.progress = isatty(2); opts.enable_changed_paths = -1; - split_opts.size_multiple = 2; - split_opts.max_commits = 0; - split_opts.expire_time = 0; + write_opts.size_multiple = 2; + write_opts.max_commits = 0; + write_opts.expire_time = 0; + write_opts.max_new_filters = -1; trace2_cmd_mode("write"); + git_config(git_commit_graph_write_config, &opts); + argc = parse_options(argc, argv, NULL, builtin_commit_graph_write_options, builtin_commit_graph_write_usage, 0); @@ -232,7 +269,7 @@ static int graph_write(int argc, const char **argv) odb = find_odb(the_repository, opts.obj_dir); if (opts.reachable) { - if (write_commit_graph_reachable(odb, flags, &split_opts)) + if (write_commit_graph_reachable(odb, flags, &write_opts)) return 1; return 0; } @@ -261,7 +298,7 @@ static int graph_write(int argc, const char **argv) opts.stdin_packs ? &pack_indexes : NULL, opts.stdin_commits ? &commits : NULL, flags, - &split_opts)) + &write_opts)) result = 1; cleanup: diff --git a/builtin/commit.c b/builtin/commit.c index 5d91b13a5c..42b964e0ca 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -1700,7 +1700,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) git_test_write_commit_graph_or_die(); repo_rerere(the_repository, 0); - run_auto_gc(quiet); + run_auto_maintenance(quiet); run_commit_hook(use_editor, get_index_file(), "post-commit", NULL); if (amend && !no_post_rewrite) { commit_post_rewrite(the_repository, current_head, &oid); diff --git a/builtin/fetch.c b/builtin/fetch.c index 1b00c2bee9..d5bf526277 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -200,8 +200,10 @@ static struct option builtin_fetch_options[] = { OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"), N_("report that we have only objects reachable from this object")), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), + OPT_BOOL(0, "auto-maintenance", &enable_auto_gc, + N_("run 'maintenance --auto' after fetching")), OPT_BOOL(0, "auto-gc", &enable_auto_gc, - N_("run 'gc --auto' after fetching")), + N_("run 'maintenance --auto' after fetching")), OPT_BOOL(0, "show-forced-updates", &fetch_show_forced_updates, N_("check for forced-updates on all updated branches")), OPT_BOOL(0, "write-commit-graph", &fetch_write_commit_graph, @@ -1925,7 +1927,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) close_object_store(the_repository->objects); if (enable_auto_gc) - run_auto_gc(verbosity < 0); + run_auto_maintenance(verbosity < 0); return result; } diff --git a/builtin/gc.c b/builtin/gc.c index aafa0946f5..090959350e 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -28,6 +28,7 @@ #include "blob.h" #include "tree.h" #include "promisor-remote.h" +#include "refs.h" #define FAILED_RUN "failed to run %s" @@ -699,3 +700,339 @@ int cmd_gc(int argc, const char **argv, const char *prefix) return 0; } + +static const char * const builtin_maintenance_run_usage[] = { + N_("git maintenance run [--auto] [--[no-]quiet] [--task=<task>]"), + NULL +}; + +struct maintenance_run_opts { + int auto_flag; + int quiet; +}; + +/* Remember to update object flag allocation in object.h */ +#define SEEN (1u<<0) + +struct cg_auto_data { + int num_not_in_graph; + int limit; +}; + +static int dfs_on_ref(const char *refname, + const struct object_id *oid, int flags, + void *cb_data) +{ + struct cg_auto_data *data = (struct cg_auto_data *)cb_data; + int result = 0; + struct object_id peeled; + struct commit_list *stack = NULL; + struct commit *commit; + + if (!peel_ref(refname, &peeled)) + oid = &peeled; + if (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT) + return 0; + + commit = lookup_commit(the_repository, oid); + if (!commit) + return 0; + if (parse_commit(commit)) + return 0; + + commit_list_append(commit, &stack); + + while (!result && stack) { + struct commit_list *parent; + + commit = pop_commit(&stack); + + for (parent = commit->parents; parent; parent = parent->next) { + if (parse_commit(parent->item) || + commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH || + parent->item->object.flags & SEEN) + continue; + + parent->item->object.flags |= SEEN; + data->num_not_in_graph++; + + if (data->num_not_in_graph >= data->limit) { + result = 1; + break; + } + + commit_list_append(parent->item, &stack); + } + } + + free_commit_list(stack); + return result; +} + +static int should_write_commit_graph(void) +{ + int result; + struct cg_auto_data data; + + data.num_not_in_graph = 0; + data.limit = 100; + git_config_get_int("maintenance.commit-graph.auto", + &data.limit); + + if (!data.limit) + return 0; + if (data.limit < 0) + return 1; + + result = for_each_ref(dfs_on_ref, &data); + + clear_commit_marks_all(SEEN); + + return result; +} + +static int run_write_commit_graph(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = 1; + strvec_pushl(&child.args, "commit-graph", "write", + "--split", "--reachable", NULL); + + if (opts->quiet) + strvec_push(&child.args, "--no-progress"); + + return !!run_command(&child); +} + +static int maintenance_task_commit_graph(struct maintenance_run_opts *opts) +{ + close_object_store(the_repository->objects); + if (run_write_commit_graph(opts)) { + error(_("failed to write commit-graph")); + return 1; + } + + return 0; +} + +static int maintenance_task_gc(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = 1; + strvec_push(&child.args, "gc"); + + if (opts->auto_flag) + strvec_push(&child.args, "--auto"); + if (opts->quiet) + strvec_push(&child.args, "--quiet"); + else + strvec_push(&child.args, "--no-quiet"); + + close_object_store(the_repository->objects); + return run_command(&child); +} + +typedef int maintenance_task_fn(struct maintenance_run_opts *opts); + +/* + * An auto condition function returns 1 if the task should run + * and 0 if the task should NOT run. See needs_to_gc() for an + * example. + */ +typedef int maintenance_auto_fn(void); + +struct maintenance_task { + const char *name; + maintenance_task_fn *fn; + maintenance_auto_fn *auto_condition; + unsigned enabled:1; + + /* -1 if not selected. */ + int selected_order; +}; + +enum maintenance_task_label { + TASK_GC, + TASK_COMMIT_GRAPH, + + /* Leave as final value */ + TASK__COUNT +}; + +static struct maintenance_task tasks[] = { + [TASK_GC] = { + "gc", + maintenance_task_gc, + need_to_gc, + 1, + }, + [TASK_COMMIT_GRAPH] = { + "commit-graph", + maintenance_task_commit_graph, + should_write_commit_graph, + }, +}; + +static int compare_tasks_by_selection(const void *a_, const void *b_) +{ + const struct maintenance_task *a, *b; + + a = (const struct maintenance_task *)&a_; + b = (const struct maintenance_task *)&b_; + + return b->selected_order - a->selected_order; +} + +static int maintenance_run_tasks(struct maintenance_run_opts *opts) +{ + int i, found_selected = 0; + int result = 0; + struct lock_file lk; + struct repository *r = the_repository; + char *lock_path = xstrfmt("%s/maintenance", r->objects->odb->path); + + if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) { + /* + * Another maintenance command is running. + * + * If --auto was provided, then it is likely due to a + * recursive process stack. Do not report an error in + * that case. + */ + if (!opts->auto_flag && !opts->quiet) + warning(_("lock file '%s' exists, skipping maintenance"), + lock_path); + free(lock_path); + return 0; + } + free(lock_path); + + for (i = 0; !found_selected && i < TASK__COUNT; i++) + found_selected = tasks[i].selected_order >= 0; + + if (found_selected) + QSORT(tasks, TASK__COUNT, compare_tasks_by_selection); + + for (i = 0; i < TASK__COUNT; i++) { + if (found_selected && tasks[i].selected_order < 0) + continue; + + if (!found_selected && !tasks[i].enabled) + continue; + + if (opts->auto_flag && + (!tasks[i].auto_condition || + !tasks[i].auto_condition())) + continue; + + trace2_region_enter("maintenance", tasks[i].name, r); + if (tasks[i].fn(opts)) { + error(_("task '%s' failed"), tasks[i].name); + result = 1; + } + trace2_region_leave("maintenance", tasks[i].name, r); + } + + rollback_lock_file(&lk); + return result; +} + +static void initialize_task_config(void) +{ + int i; + struct strbuf config_name = STRBUF_INIT; + gc_config(); + + for (i = 0; i < TASK__COUNT; i++) { + int config_value; + + strbuf_setlen(&config_name, 0); + strbuf_addf(&config_name, "maintenance.%s.enabled", + tasks[i].name); + + if (!git_config_get_bool(config_name.buf, &config_value)) + tasks[i].enabled = config_value; + } + + strbuf_release(&config_name); +} + +static int task_option_parse(const struct option *opt, + const char *arg, int unset) +{ + int i, num_selected = 0; + struct maintenance_task *task = NULL; + + BUG_ON_OPT_NEG(unset); + + for (i = 0; i < TASK__COUNT; i++) { + if (tasks[i].selected_order >= 0) + num_selected++; + if (!strcasecmp(tasks[i].name, arg)) { + task = &tasks[i]; + } + } + + if (!task) { + error(_("'%s' is not a valid task"), arg); + return 1; + } + + if (task->selected_order >= 0) { + error(_("task '%s' cannot be selected multiple times"), arg); + return 1; + } + + task->selected_order = num_selected + 1; + + return 0; +} + +static int maintenance_run(int argc, const char **argv, const char *prefix) +{ + int i; + struct maintenance_run_opts opts; + struct option builtin_maintenance_run_options[] = { + OPT_BOOL(0, "auto", &opts.auto_flag, + N_("run tasks based on the state of the repository")), + OPT_BOOL(0, "quiet", &opts.quiet, + N_("do not report progress or other information over stderr")), + OPT_CALLBACK_F(0, "task", NULL, N_("task"), + N_("run a specific task"), + PARSE_OPT_NONEG, task_option_parse), + OPT_END() + }; + memset(&opts, 0, sizeof(opts)); + + opts.quiet = !isatty(2); + initialize_task_config(); + + for (i = 0; i < TASK__COUNT; i++) + tasks[i].selected_order = -1; + + argc = parse_options(argc, argv, prefix, + builtin_maintenance_run_options, + builtin_maintenance_run_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (argc != 0) + usage_with_options(builtin_maintenance_run_usage, + builtin_maintenance_run_options); + return maintenance_run_tasks(&opts); +} + +static const char builtin_maintenance_usage[] = N_("git maintenance run [<options>]"); + +int cmd_maintenance(int argc, const char **argv, const char *prefix) +{ + if (argc < 2 || + (argc == 2 && !strcmp(argv[1], "-h"))) + usage(builtin_maintenance_usage); + + if (!strcmp(argv[1], "run")) + return maintenance_run(argc - 1, argv + 1, prefix); + + die(_("invalid subcommand: %s"), argv[1]); +} diff --git a/builtin/init-db.c b/builtin/init-db.c index cd3e760541..01bc648d41 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -179,7 +179,7 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree) return 1; } -void initialize_repository_version(int hash_algo) +void initialize_repository_version(int hash_algo, int reinit) { char repo_version_string[10]; int repo_version = GIT_REPO_VERSION; @@ -195,6 +195,8 @@ void initialize_repository_version(int hash_algo) if (hash_algo != GIT_HASH_SHA1) git_config_set("extensions.objectformat", hash_algos[hash_algo].name); + else if (reinit) + git_config_set_gently("extensions.objectformat", NULL); } static int create_default_files(const char *template_path, @@ -277,7 +279,7 @@ static int create_default_files(const char *template_path, free(ref); } - initialize_repository_version(fmt->hash_algo); + initialize_repository_version(fmt->hash_algo, 0); /* Check filemode trustability */ path = git_path_buf(&buf, "config"); diff --git a/builtin/merge.c b/builtin/merge.c index 032a8f5434..9d5359edc2 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -456,7 +456,7 @@ static void finish(struct commit *head_commit, * user should see them. */ close_object_store(the_repository->objects); - run_auto_gc(verbosity < 0); + run_auto_maintenance(verbosity < 0); } } if (new_head && show_diffstat) { diff --git a/builtin/rebase.c b/builtin/rebase.c index c4ff2039ef..eeca53382f 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -734,10 +734,10 @@ static int finish_rebase(struct rebase_options *opts) apply_autostash(state_dir_path("autostash", opts)); close_object_store(the_repository->objects); /* - * We ignore errors in 'gc --auto', since the + * We ignore errors in 'git maintenance run --auto', since the * user should see them. */ - run_auto_gc(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE))); + run_auto_maintenance(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE))); if (opts->type == REBASE_MERGE) { struct replay_opts replay = REPLAY_OPTS_INIT; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 439f29d6c7..bb9909c52e 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -57,6 +57,7 @@ static int advertise_push_options; static int unpack_limit = 100; static off_t max_input_size; static int report_status; +static int report_status_v2; static int use_sideband; static int use_atomic; static int use_push_options; @@ -97,6 +98,17 @@ static int keepalive_in_sec = 5; static struct tmp_objdir *tmp_objdir; +static struct proc_receive_ref { + unsigned int want_add:1, + want_delete:1, + want_modify:1, + negative_ref:1; + char *ref_prefix; + struct proc_receive_ref *next; +} *proc_receive_ref; + +static void proc_receive_ref_append(const char *prefix); + static enum deny_action parse_deny_action(const char *var, const char *value) { if (value) { @@ -229,6 +241,13 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.procreceiverefs") == 0) { + if (!value) + return config_error_nonbool(var); + proc_receive_ref_append(value); + return 0; + } + return git_default_config(var, value, cb); } @@ -240,7 +259,7 @@ static void show_ref(const char *path, const struct object_id *oid) struct strbuf cap = STRBUF_INIT; strbuf_addstr(&cap, - "report-status delete-refs side-band-64k quiet"); + "report-status report-status-v2 delete-refs side-band-64k quiet"); if (advertise_atomic_push) strbuf_addstr(&cap, " atomic"); if (prefer_ofs_delta) @@ -310,17 +329,94 @@ static void write_head_info(void) packet_flush(1); } +#define RUN_PROC_RECEIVE_SCHEDULED 1 +#define RUN_PROC_RECEIVE_RETURNED 2 struct command { struct command *next; const char *error_string; + struct ref_push_report *report; unsigned int skip_update:1, - did_not_exist:1; + did_not_exist:1, + run_proc_receive:2; int index; struct object_id old_oid; struct object_id new_oid; char ref_name[FLEX_ARRAY]; /* more */ }; +static void proc_receive_ref_append(const char *prefix) +{ + struct proc_receive_ref *ref_pattern; + char *p; + int len; + + ref_pattern = xcalloc(1, sizeof(struct proc_receive_ref)); + p = strchr(prefix, ':'); + if (p) { + while (prefix < p) { + if (*prefix == 'a') + ref_pattern->want_add = 1; + else if (*prefix == 'd') + ref_pattern->want_delete = 1; + else if (*prefix == 'm') + ref_pattern->want_modify = 1; + else if (*prefix == '!') + ref_pattern->negative_ref = 1; + prefix++; + } + prefix++; + } else { + ref_pattern->want_add = 1; + ref_pattern->want_delete = 1; + ref_pattern->want_modify = 1; + } + len = strlen(prefix); + while (len && prefix[len - 1] == '/') + len--; + ref_pattern->ref_prefix = xmemdupz(prefix, len); + if (!proc_receive_ref) { + proc_receive_ref = ref_pattern; + } else { + struct proc_receive_ref *end; + + end = proc_receive_ref; + while (end->next) + end = end->next; + end->next = ref_pattern; + } +} + +static int proc_receive_ref_matches(struct command *cmd) +{ + struct proc_receive_ref *p; + + if (!proc_receive_ref) + return 0; + + for (p = proc_receive_ref; p; p = p->next) { + const char *match = p->ref_prefix; + const char *remains; + + if (!p->want_add && is_null_oid(&cmd->old_oid)) + continue; + else if (!p->want_delete && is_null_oid(&cmd->new_oid)) + continue; + else if (!p->want_modify && + !is_null_oid(&cmd->old_oid) && + !is_null_oid(&cmd->new_oid)) + continue; + + if (skip_prefix(cmd->ref_name, match, &remains) && + (!*remains || *remains == '/')) { + if (!p->negative_ref) + return 1; + } else if (p->negative_ref) { + return 1; + } + } + return 0; +} + static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2))); static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2))); @@ -692,6 +788,7 @@ static void prepare_push_cert_sha1(struct child_process *proc) struct receive_hook_feed_state { struct command *cmd; + struct ref_push_report *report; int skip_broken; struct strbuf buf; const struct string_list *push_options; @@ -779,11 +876,31 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep) cmd = cmd->next; if (!cmd) return -1; /* EOF */ + if (!bufp) + return 0; /* OK, can feed something. */ strbuf_reset(&state->buf); - strbuf_addf(&state->buf, "%s %s %s\n", - oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid), - cmd->ref_name); - state->cmd = cmd->next; + if (!state->report) + state->report = cmd->report; + if (state->report) { + struct object_id *old_oid; + struct object_id *new_oid; + const char *ref_name; + + old_oid = state->report->old_oid ? state->report->old_oid : &cmd->old_oid; + new_oid = state->report->new_oid ? state->report->new_oid : &cmd->new_oid; + ref_name = state->report->ref_name ? state->report->ref_name : cmd->ref_name; + strbuf_addf(&state->buf, "%s %s %s\n", + oid_to_hex(old_oid), oid_to_hex(new_oid), + ref_name); + state->report = state->report->next; + if (!state->report) + state->cmd = cmd->next; + } else { + strbuf_addf(&state->buf, "%s %s %s\n", + oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid), + cmd->ref_name); + state->cmd = cmd->next; + } if (bufp) { *bufp = state->buf.buf; *sizep = state->buf.len; @@ -802,6 +919,7 @@ static int run_receive_hook(struct command *commands, strbuf_init(&state.buf, 0); state.cmd = commands; state.skip_broken = skip_broken; + state.report = NULL; if (feed_receive_hook(&state, NULL, NULL)) return 0; state.cmd = commands; @@ -840,6 +958,268 @@ static int run_update_hook(struct command *cmd) return finish_command(&proc); } +static struct command *find_command_by_refname(struct command *list, + const char *refname) +{ + for (; list; list = list->next) + if (!strcmp(list->ref_name, refname)) + return list; + return NULL; +} + +static int read_proc_receive_report(struct packet_reader *reader, + struct command *commands, + struct strbuf *errmsg) +{ + struct command *cmd; + struct command *hint = NULL; + struct ref_push_report *report = NULL; + int new_report = 0; + int code = 0; + int once = 0; + + for (;;) { + struct object_id old_oid, new_oid; + const char *head; + const char *refname; + char *p; + + if (packet_reader_read(reader) != PACKET_READ_NORMAL) + break; + + head = reader->line; + p = strchr(head, ' '); + if (!p) { + strbuf_addf(errmsg, "proc-receive reported incomplete status line: '%s'\n", head); + code = -1; + continue; + } + *p++ = '\0'; + if (!strcmp(head, "option")) { + const char *key, *val; + + if (!hint || !(report || new_report)) { + if (!once++) + strbuf_addstr(errmsg, "proc-receive reported 'option' without a matching 'ok/ng' directive\n"); + code = -1; + continue; + } + if (new_report) { + if (!hint->report) { + hint->report = xcalloc(1, sizeof(struct ref_push_report)); + report = hint->report; + } else { + report = hint->report; + while (report->next) + report = report->next; + report->next = xcalloc(1, sizeof(struct ref_push_report)); + report = report->next; + } + new_report = 0; + } + key = p; + p = strchr(key, ' '); + if (p) + *p++ = '\0'; + val = p; + if (!strcmp(key, "refname")) + report->ref_name = xstrdup_or_null(val); + else if (!strcmp(key, "old-oid") && val && + !parse_oid_hex(val, &old_oid, &val)) + report->old_oid = oiddup(&old_oid); + else if (!strcmp(key, "new-oid") && val && + !parse_oid_hex(val, &new_oid, &val)) + report->new_oid = oiddup(&new_oid); + else if (!strcmp(key, "forced-update")) + report->forced_update = 1; + else if (!strcmp(key, "fall-through")) + /* Fall through, let 'receive-pack' to execute it. */ + hint->run_proc_receive = 0; + continue; + } + + report = NULL; + new_report = 0; + refname = p; + p = strchr(refname, ' '); + if (p) + *p++ = '\0'; + if (strcmp(head, "ok") && strcmp(head, "ng")) { + strbuf_addf(errmsg, "proc-receive reported bad status '%s' on ref '%s'\n", + head, refname); + code = -1; + continue; + } + + /* first try searching at our hint, falling back to all refs */ + if (hint) + hint = find_command_by_refname(hint, refname); + if (!hint) + hint = find_command_by_refname(commands, refname); + if (!hint) { + strbuf_addf(errmsg, "proc-receive reported status on unknown ref: %s\n", + refname); + code = -1; + continue; + } + if (!hint->run_proc_receive) { + strbuf_addf(errmsg, "proc-receive reported status on unexpected ref: %s\n", + refname); + code = -1; + continue; + } + hint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED; + if (!strcmp(head, "ng")) { + if (p) + hint->error_string = xstrdup(p); + else + hint->error_string = "failed"; + code = -1; + continue; + } + new_report = 1; + } + + for (cmd = commands; cmd; cmd = cmd->next) + if (cmd->run_proc_receive && !cmd->error_string && + !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) { + cmd->error_string = "proc-receive failed to report status"; + code = -1; + } + return code; +} + +static int run_proc_receive_hook(struct command *commands, + const struct string_list *push_options) +{ + struct child_process proc = CHILD_PROCESS_INIT; + struct async muxer; + struct command *cmd; + const char *argv[2]; + struct packet_reader reader; + struct strbuf cap = STRBUF_INIT; + struct strbuf errmsg = STRBUF_INIT; + int hook_use_push_options = 0; + int version = 0; + int code; + + argv[0] = find_hook("proc-receive"); + if (!argv[0]) { + rp_error("cannot find hook 'proc-receive'"); + return -1; + } + argv[1] = NULL; + + proc.argv = argv; + proc.in = -1; + proc.out = -1; + proc.trace2_hook_name = "proc-receive"; + + 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; + } else { + proc.err = 0; + } + + code = start_command(&proc); + if (code) { + if (use_sideband) + finish_async(&muxer); + return code; + } + + sigchain_push(SIGPIPE, SIG_IGN); + + /* Version negotiaton */ + packet_reader_init(&reader, proc.out, NULL, 0, + PACKET_READ_CHOMP_NEWLINE | + PACKET_READ_GENTLE_ON_EOF); + if (use_atomic) + strbuf_addstr(&cap, " atomic"); + if (use_push_options) + strbuf_addstr(&cap, " push-options"); + if (cap.len) { + packet_write_fmt(proc.in, "version=1%c%s\n", '\0', cap.buf + 1); + strbuf_release(&cap); + } else { + packet_write_fmt(proc.in, "version=1\n"); + } + packet_flush(proc.in); + + for (;;) { + int linelen; + + if (packet_reader_read(&reader) != PACKET_READ_NORMAL) + break; + + if (reader.pktlen > 8 && starts_with(reader.line, "version=")) { + version = atoi(reader.line + 8); + linelen = strlen(reader.line); + if (linelen < reader.pktlen) { + const char *feature_list = reader.line + linelen + 1; + if (parse_feature_request(feature_list, "push-options")) + hook_use_push_options = 1; + } + } + } + + if (version != 1) { + strbuf_addf(&errmsg, "proc-receive version '%d' is not supported", + version); + code = -1; + goto cleanup; + } + + /* Send commands */ + for (cmd = commands; cmd; cmd = cmd->next) { + if (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string) + continue; + packet_write_fmt(proc.in, "%s %s %s", + oid_to_hex(&cmd->old_oid), + oid_to_hex(&cmd->new_oid), + cmd->ref_name); + } + packet_flush(proc.in); + + /* Send push options */ + if (hook_use_push_options) { + struct string_list_item *item; + + for_each_string_list_item(item, push_options) + packet_write_fmt(proc.in, "%s", item->string); + packet_flush(proc.in); + } + + /* Read result from proc-receive */ + code = read_proc_receive_report(&reader, commands, &errmsg); + +cleanup: + close(proc.in); + close(proc.out); + if (use_sideband) + finish_async(&muxer); + if (finish_command(&proc)) + code = -1; + if (errmsg.len >0) { + char *p = errmsg.buf; + + p += errmsg.len - 1; + if (*p == '\n') + *p = '\0'; + rp_error("%s", errmsg.buf); + strbuf_release(&errmsg); + } + sigchain_pop(SIGPIPE); + + return code; +} + static char *refuse_unconfigured_deny_msg = N_("By default, updating the current branch in a non-bare repository\n" "is denied, because it will make the index and work tree inconsistent\n" @@ -1415,7 +1795,7 @@ static void execute_commands_non_atomic(struct command *commands, struct strbuf err = STRBUF_INIT; for (cmd = commands; cmd; cmd = cmd->next) { - if (!should_process_cmd(cmd)) + if (!should_process_cmd(cmd) || cmd->run_proc_receive) continue; transaction = ref_transaction_begin(&err); @@ -1455,7 +1835,7 @@ static void execute_commands_atomic(struct command *commands, } for (cmd = commands; cmd; cmd = cmd->next) { - if (!should_process_cmd(cmd)) + if (!should_process_cmd(cmd) || cmd->run_proc_receive) continue; cmd->error_string = update(cmd, si); @@ -1491,6 +1871,7 @@ static void execute_commands(struct command *commands, struct iterate_data data; struct async muxer; int err_fd = 0; + int run_proc_receive = 0; if (unpacker_error) { for (cmd = commands; cmd; cmd = cmd->next) @@ -1520,6 +1901,22 @@ static void execute_commands(struct command *commands, reject_updates_to_hidden(commands); + /* + * Try to find commands that have special prefix in their reference names, + * and mark them to run an external "proc-receive" hook later. + */ + if (proc_receive_ref) { + for (cmd = commands; cmd; cmd = cmd->next) { + if (!should_process_cmd(cmd)) + continue; + + if (proc_receive_ref_matches(cmd)) { + cmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED; + run_proc_receive = 1; + } + } + } + if (run_receive_hook(commands, "pre-receive", 0, push_options)) { for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) @@ -1546,6 +1943,14 @@ static void execute_commands(struct command *commands, free(head_name_to_free); head_name = head_name_to_free = resolve_refdup("HEAD", 0, NULL, NULL); + if (run_proc_receive && + run_proc_receive_hook(commands, push_options)) + for (cmd = commands; cmd; cmd = cmd->next) + if (!cmd->error_string && + !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED) && + (cmd->run_proc_receive || use_atomic)) + cmd->error_string = "fail to run proc-receive hook"; + if (use_atomic) execute_commands_atomic(commands, si); else @@ -1629,6 +2034,8 @@ static struct command *read_head_info(struct packet_reader *reader, int len = 0; if (parse_feature_request(feature_list, "report-status")) report_status = 1; + if (parse_feature_request(feature_list, "report-status-v2")) + report_status_v2 = 1; if (parse_feature_request(feature_list, "side-band-64k")) use_sideband = LARGE_PACKET_MAX; if (parse_feature_request(feature_list, "quiet")) @@ -1947,6 +2354,51 @@ static void report(struct command *commands, const char *unpack_status) strbuf_release(&buf); } +static void report_v2(struct command *commands, const char *unpack_status) +{ + struct command *cmd; + struct strbuf buf = STRBUF_INIT; + struct ref_push_report *report; + + packet_buf_write(&buf, "unpack %s\n", + unpack_status ? unpack_status : "ok"); + for (cmd = commands; cmd; cmd = cmd->next) { + int count = 0; + + if (cmd->error_string) { + packet_buf_write(&buf, "ng %s %s\n", + cmd->ref_name, + cmd->error_string); + continue; + } + packet_buf_write(&buf, "ok %s\n", + cmd->ref_name); + for (report = cmd->report; report; report = report->next) { + if (count++ > 0) + packet_buf_write(&buf, "ok %s\n", + cmd->ref_name); + if (report->ref_name) + packet_buf_write(&buf, "option refname %s\n", + report->ref_name); + if (report->old_oid) + packet_buf_write(&buf, "option old-oid %s\n", + oid_to_hex(report->old_oid)); + if (report->new_oid) + packet_buf_write(&buf, "option new-oid %s\n", + oid_to_hex(report->new_oid)); + if (report->forced_update) + packet_buf_write(&buf, "option forced-update\n"); + } + } + packet_buf_flush(&buf); + + if (use_sideband) + send_sideband(1, 1, buf.buf, buf.len, use_sideband); + else + write_or_die(1, buf.buf, buf.len); + strbuf_release(&buf); +} + static int delete_only(struct command *commands) { struct command *cmd; @@ -2055,7 +2507,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) &push_options); if (pack_lockfile) unlink_or_warn(pack_lockfile); - if (report_status) + if (report_status_v2) + report_v2(commands, unpack_status); + else if (report_status) report(commands, unpack_status); run_receive_hook(commands, "post-receive", 1, &push_options); diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 2b9610f121..7af148d733 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -29,10 +29,12 @@ static struct send_pack_args args; static void print_helper_status(struct ref *ref) { struct strbuf buf = STRBUF_INIT; + struct ref_push_report *report; for (; ref; ref = ref->next) { const char *msg = NULL; const char *res; + int count = 0; switch(ref->status) { case REF_STATUS_NONE: @@ -94,6 +96,23 @@ static void print_helper_status(struct ref *ref) } strbuf_addch(&buf, '\n'); + if (ref->status == REF_STATUS_OK) { + for (report = ref->report; report; report = report->next) { + if (count++ > 0) + strbuf_addf(&buf, "ok %s\n", ref->name); + if (report->ref_name) + strbuf_addf(&buf, "option refname %s\n", + report->ref_name); + if (report->old_oid) + strbuf_addf(&buf, "option old-oid %s\n", + oid_to_hex(report->old_oid)); + if (report->new_oid) + strbuf_addf(&buf, "option new-oid %s\n", + oid_to_hex(report->new_oid)); + if (report->forced_update) + strbuf_addstr(&buf, "option forced-update\n"); + } + } write_or_die(1, buf.buf, buf.len); } strbuf_release(&buf); |