diff options
Diffstat (limited to 'builtin')
47 files changed, 1456 insertions, 567 deletions
diff --git a/builtin/add.c b/builtin/add.c index 88d1d330fa..740c7c4581 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -30,6 +30,7 @@ static const char * const builtin_add_usage[] = { NULL }; static int patch_interactive, add_interactive, edit_interactive; +static struct add_p_opt add_p_opt = ADD_P_OPT_INIT; static int take_worktree_changes; static int add_renormalize; static int pathspec_file_nul; @@ -158,7 +159,7 @@ static int refresh(struct repository *repo, int verbose, const struct pathspec * int interactive_add(struct repository *repo, const char **argv, const char *prefix, - int patch) + int patch, struct add_p_opt *add_p_opt) { struct pathspec pathspec; int ret; @@ -170,9 +171,9 @@ int interactive_add(struct repository *repo, prefix, argv); if (patch) - ret = !!run_add_p(repo, ADD_P_ADD, NULL, &pathspec); + ret = !!run_add_p(repo, ADD_P_ADD, add_p_opt, NULL, &pathspec); else - ret = !!run_add_i(repo, &pathspec); + ret = !!run_add_i(repo, &pathspec, add_p_opt); clear_pathspec(&pathspec); return ret; @@ -254,6 +255,8 @@ static struct option builtin_add_options[] = { OPT_GROUP(""), OPT_BOOL('i', "interactive", &add_interactive, N_("interactive picking")), OPT_BOOL('p', "patch", &patch_interactive, N_("select hunks interactively")), + OPT_DIFF_UNIFIED(&add_p_opt.context), + OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext), OPT_BOOL('e', "edit", &edit_interactive, N_("edit current diff and apply")), OPT__FORCE(&ignored_too, N_("allow adding otherwise ignored files"), 0), OPT_BOOL('u', "update", &take_worktree_changes, N_("update tracked files")), @@ -386,6 +389,7 @@ int cmd_add(int argc, char *seen = NULL; char *ps_matched = NULL; struct lock_file lock_file = LOCK_INIT; + struct odb_transaction *transaction; repo_config(repo, add_config, NULL); @@ -395,6 +399,11 @@ int cmd_add(int argc, prepare_repo_settings(repo); repo->settings.command_requires_full_index = 0; + if (add_p_opt.context < -1) + die(_("'%s' cannot be negative"), "--unified"); + if (add_p_opt.interhunkcontext < -1) + die(_("'%s' cannot be negative"), "--inter-hunk-context"); + if (patch_interactive) add_interactive = 1; if (add_interactive) { @@ -402,7 +411,12 @@ int cmd_add(int argc, die(_("options '%s' and '%s' cannot be used together"), "--dry-run", "--interactive/--patch"); if (pathspec_from_file) die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--interactive/--patch"); - exit(interactive_add(repo, argv + 1, prefix, patch_interactive)); + exit(interactive_add(repo, argv + 1, prefix, patch_interactive, &add_p_opt)); + } else { + if (add_p_opt.context != -1) + die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch"); + if (add_p_opt.interhunkcontext != -1) + die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch"); } if (edit_interactive) { @@ -561,7 +575,7 @@ int cmd_add(int argc, string_list_clear(&only_match_skip_worktree, 0); } - begin_odb_transaction(); + transaction = begin_odb_transaction(repo->objects); ps_matched = xcalloc(pathspec.nr, 1); if (add_renormalize) @@ -580,7 +594,7 @@ int cmd_add(int argc, if (chmod_arg && pathspec.nr) exit_status |= chmod_pathspec(repo, &pathspec, chmod_arg[0], show_only); - end_odb_transaction(); + end_odb_transaction(transaction); finish: if (write_locked_index(repo->index, &lock_file, diff --git a/builtin/blame.c b/builtin/blame.c index 71efeb7c7e..2703820258 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -197,9 +197,7 @@ static void commit_info_destroy(struct commit_info *ci) strbuf_release(&ci->summary); } -static void get_commit_info(struct commit *commit, - struct commit_info *ret, - int detailed) +static void get_commit_info(struct commit *commit, struct commit_info *ret) { int len; const char *subject, *encoding; @@ -211,11 +209,6 @@ static void get_commit_info(struct commit *commit, &ret->author, &ret->author_mail, &ret->author_time, &ret->author_tz); - if (!detailed) { - repo_unuse_commit_buffer(the_repository, commit, message); - return; - } - get_ac_line(message, "\ncommitter ", &ret->committer, &ret->committer_mail, &ret->committer_time, &ret->committer_tz); @@ -263,7 +256,7 @@ static int emit_one_suspect_detail(struct blame_origin *suspect, int repeat) return 0; suspect->commit->object.flags |= METAINFO_SHOWN; - get_commit_info(suspect->commit, &ci, 1); + get_commit_info(suspect->commit, &ci); printf("author %s\n", ci.author.buf); printf("author-mail %s\n", ci.author_mail.buf); printf("author-time %"PRItime"\n", ci.author_time); @@ -420,7 +413,7 @@ static void parse_color_fields(const char *s) colorfield_nr = 0; /* Ideally this would be stripped and split at the same time? */ - string_list_split(&l, s, ',', -1); + string_list_split(&l, s, ",", -1); ALLOC_GROW(colorfield, colorfield_nr + 1, colorfield_alloc); for_each_string_list_item(item, &l) { @@ -471,7 +464,7 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); const char *default_color = NULL, *color = NULL, *reset = NULL; - get_commit_info(suspect->commit, &ci, 1); + get_commit_info(suspect->commit, &ci); oid_to_hex_r(hex, &suspect->commit->object.oid); cp = blame_nth_line(sb, ent->lno); @@ -665,7 +658,7 @@ static void find_alignment(struct blame_scoreboard *sb, int *option) if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { struct commit_info ci = COMMIT_INFO_INIT; suspect->commit->object.flags |= METAINFO_SHOWN; - get_commit_info(suspect->commit, &ci, 1); + get_commit_info(suspect->commit, &ci); if (*option & OUTPUT_SHOW_EMAIL) num = utf8_strwidth(ci.author_mail.buf); else diff --git a/builtin/cat-file.c b/builtin/cat-file.c index b9eb2bb3b9..fce0b06451 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -848,7 +848,7 @@ static void batch_each_object(struct batch_options *opt, }; struct bitmap_index *bitmap = prepare_bitmap_git(the_repository); - for_each_loose_object(batch_one_object_loose, &payload, 0); + for_each_loose_object(the_repository->objects, batch_one_object_loose, &payload, 0); if (bitmap && !for_each_bitmapped_object(bitmap, &opt->objects_filter, batch_one_object_bitmapped, &payload)) { diff --git a/builtin/checkout.c b/builtin/checkout.c index 37efde5989..f9453473fe 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -61,6 +61,8 @@ static const char * const restore_usage[] = { struct checkout_opts { int patch_mode; + int patch_context; + int patch_interhunk_context; int quiet; int merge; int force; @@ -104,7 +106,12 @@ struct checkout_opts { struct tree *source_tree; }; -#define CHECKOUT_OPTS_INIT { .conflict_style = -1, .merge = -1 } +#define CHECKOUT_OPTS_INIT { \ + .conflict_style = -1, \ + .merge = -1, \ + .patch_context = -1, \ + .patch_interhunk_context = -1, \ +} struct branch_info { char *name; /* The short name used */ @@ -320,7 +327,7 @@ static int checkout_merged(int pos, const struct checkout *state, * (it also writes the merge result to the object database even * when it may contain conflicts). */ - if (write_object_file(result_buf.ptr, result_buf.size, OBJ_BLOB, &oid)) + if (odb_write_object(the_repository->objects, result_buf.ptr, result_buf.size, OBJ_BLOB, &oid)) die(_("Unable to add merge result for '%s'"), path); free(result_buf.ptr); ce = make_transient_cache_entry(mode, &oid, path, 2, ce_mem_pool); @@ -539,6 +546,10 @@ static int checkout_paths(const struct checkout_opts *opts, if (opts->patch_mode) { enum add_p_mode patch_mode; + struct add_p_opt add_p_opt = { + .context = opts->patch_context, + .interhunkcontext = opts->patch_interhunk_context, + }; const char *rev = new_branch_info->name; char rev_oid[GIT_MAX_HEXSZ + 1]; @@ -564,8 +575,8 @@ static int checkout_paths(const struct checkout_opts *opts, else BUG("either flag must have been set, worktree=%d, index=%d", opts->checkout_worktree, opts->checkout_index); - return !!run_add_p(the_repository, patch_mode, rev, - &opts->pathspec); + return !!run_add_p(the_repository, patch_mode, &add_p_opt, + rev, &opts->pathspec); } repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); @@ -1738,6 +1749,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts, N_("checkout their version for unmerged files"), 3, PARSE_OPT_NONEG), OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")), + OPT_DIFF_UNIFIED(&opts->patch_context), + OPT_DIFF_INTERHUNK_CONTEXT(&opts->patch_interhunk_context), OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree, N_("do not limit pathspecs to sparse entries only")), OPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file), @@ -1780,6 +1793,18 @@ static int checkout_main(int argc, const char **argv, const char *prefix, argc = parse_options(argc, argv, prefix, options, usagestr, parseopt_flags); + if (opts->patch_context < -1) + die(_("'%s' cannot be negative"), "--unified"); + if (opts->patch_interhunk_context < -1) + die(_("'%s' cannot be negative"), "--inter-hunk-context"); + + if (!opts->patch_mode) { + if (opts->patch_context != -1) + die(_("the option '%s' requires '%s'"), "--unified", "--patch"); + if (opts->patch_interhunk_context != -1) + die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch"); + } + if (opts->show_progress < 0) { if (opts->quiet) opts->show_progress = 0; diff --git a/builtin/clean.c b/builtin/clean.c index a1977b92dc..38b67923a6 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -478,43 +478,39 @@ static int find_unique(const char *choice, struct menu_stuff *menu_stuff) */ static int parse_choice(struct menu_stuff *menu_stuff, int is_single, - struct strbuf input, + char *input, int **chosen) { - struct strbuf **choice_list, **ptr; + struct string_list choice = STRING_LIST_INIT_NODUP; + struct string_list_item *item; int nr = 0; int i; - if (is_single) { - choice_list = strbuf_split_max(&input, '\n', 0); - } else { - char *p = input.buf; - do { - if (*p == ',') - *p = ' '; - } while (*p++); - choice_list = strbuf_split_max(&input, ' ', 0); - } + string_list_split_in_place_f(&choice, input, + is_single ? "\n" : ", ", -1, + STRING_LIST_SPLIT_TRIM); - for (ptr = choice_list; *ptr; ptr++) { - char *p; - int choose = 1; + for_each_string_list_item(item, &choice) { + const char *string; + int choose; int bottom = 0, top = 0; int is_range, is_number; - strbuf_trim(*ptr); - if (!(*ptr)->len) + string = item->string; + if (!*string) continue; /* Input that begins with '-'; unchoose */ - if (*(*ptr)->buf == '-') { + if (string[0] == '-') { choose = 0; - strbuf_remove((*ptr), 0, 1); + string++; + } else { + choose = 1; } is_range = 0; is_number = 1; - for (p = (*ptr)->buf; *p; p++) { + for (const char *p = string; *p; p++) { if ('-' == *p) { if (!is_range) { is_range = 1; @@ -532,27 +528,27 @@ static int parse_choice(struct menu_stuff *menu_stuff, } if (is_number) { - bottom = atoi((*ptr)->buf); + bottom = atoi(string); top = bottom; } else if (is_range) { - bottom = atoi((*ptr)->buf); + bottom = atoi(string); /* a range can be specified like 5-7 or 5- */ - if (!*(strchr((*ptr)->buf, '-') + 1)) + if (!*(strchr(string, '-') + 1)) top = menu_stuff->nr; else - top = atoi(strchr((*ptr)->buf, '-') + 1); - } else if (!strcmp((*ptr)->buf, "*")) { + top = atoi(strchr(string, '-') + 1); + } else if (!strcmp(string, "*")) { bottom = 1; top = menu_stuff->nr; } else { - bottom = find_unique((*ptr)->buf, menu_stuff); + bottom = find_unique(string, menu_stuff); top = bottom; } if (top <= 0 || bottom <= 0 || top > menu_stuff->nr || bottom > top || (is_single && bottom != top)) { clean_print_color(CLEAN_COLOR_ERROR); - printf(_("Huh (%s)?\n"), (*ptr)->buf); + printf(_("Huh (%s)?\n"), string); clean_print_color(CLEAN_COLOR_RESET); continue; } @@ -561,7 +557,7 @@ static int parse_choice(struct menu_stuff *menu_stuff, (*chosen)[i-1] = choose; } - strbuf_list_free(choice_list); + string_list_clear(&choice, 0); for (i = 0; i < menu_stuff->nr; i++) nr += (*chosen)[i]; @@ -631,7 +627,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff) nr = parse_choice(stuff, opts->flags & MENU_OPTS_SINGLETON, - choice, + choice.buf, &chosen); if (opts->flags & MENU_OPTS_SINGLETON) { @@ -679,12 +675,13 @@ static int filter_by_patterns_cmd(void) { struct dir_struct dir = DIR_INIT; struct strbuf confirm = STRBUF_INIT; - struct strbuf **ignore_list; - struct string_list_item *item; struct pattern_list *pl; int changed = -1, i; for (;;) { + struct string_list ignore_list = STRING_LIST_INIT_NODUP; + struct string_list_item *item; + if (!del_list.nr) break; @@ -702,14 +699,15 @@ static int filter_by_patterns_cmd(void) break; pl = add_pattern_list(&dir, EXC_CMDL, "manual exclude"); - ignore_list = strbuf_split_max(&confirm, ' ', 0); - for (i = 0; ignore_list[i]; i++) { - strbuf_trim(ignore_list[i]); - if (!ignore_list[i]->len) - continue; + string_list_split_in_place_f(&ignore_list, confirm.buf, " ", -1, + STRING_LIST_SPLIT_TRIM); - add_pattern(ignore_list[i]->buf, "", 0, pl, -(i+1)); + for (i = 0; i < ignore_list.nr; i++) { + item = &ignore_list.items[i]; + if (!*item->string) + continue; + add_pattern(item->string, "", 0, pl, -(i+1)); } changed = 0; @@ -730,7 +728,7 @@ static int filter_by_patterns_cmd(void) clean_print_color(CLEAN_COLOR_RESET); } - strbuf_list_free(ignore_list); + string_list_clear(&ignore_list, 0); dir_clear(&dir); } diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index 4992ac146e..fe3ebaadad 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -102,14 +102,15 @@ static int graph_verify(int argc, const char **argv, const char *prefix, if (opts.progress) flags |= COMMIT_GRAPH_WRITE_PROGRESS; - source = odb_find_source(the_repository->objects, opts.obj_dir); + source = odb_find_source_or_die(the_repository->objects, opts.obj_dir); graph_name = get_commit_graph_filename(source); chain_name = get_commit_graph_chain_filename(source); if (open_commit_graph(graph_name, &fd, &st)) opened = OPENED_GRAPH; else if (errno != ENOENT) die_errno(_("Could not open commit-graph '%s'"), graph_name); - else if (open_commit_graph_chain(chain_name, &fd, &st)) + else if (open_commit_graph_chain(chain_name, &fd, &st, + the_repository->hash_algo)) opened = OPENED_CHAIN; else if (errno != ENOENT) die_errno(_("could not open commit-graph chain '%s'"), chain_name); @@ -121,15 +122,15 @@ static int graph_verify(int argc, const char **argv, const char *prefix, if (opened == OPENED_NONE) return 0; else if (opened == OPENED_GRAPH) - graph = load_commit_graph_one_fd_st(the_repository, fd, &st, source); + graph = load_commit_graph_one_fd_st(source, fd, &st); else - graph = load_commit_graph_chain_fd_st(the_repository, fd, &st, + graph = load_commit_graph_chain_fd_st(the_repository->objects, fd, &st, &incomplete_chain); if (!graph) return 1; - ret = verify_commit_graph(the_repository, graph, flags); + ret = verify_commit_graph(graph, flags); free_commit_graph(graph); if (incomplete_chain) { @@ -290,7 +291,7 @@ static int graph_write(int argc, const char **argv, const char *prefix, git_env_bool(GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS, 0)) flags |= COMMIT_GRAPH_WRITE_BLOOM_FILTERS; - source = odb_find_source(the_repository->objects, opts.obj_dir); + source = odb_find_source_or_die(the_repository->objects, opts.obj_dir); if (opts.reachable) { if (write_commit_graph_reachable(source, flags, &write_opts)) diff --git a/builtin/commit.c b/builtin/commit.c index f821fdcfcc..384d0b4e93 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -19,6 +19,7 @@ #include "environment.h" #include "diff.h" #include "commit.h" +#include "add-interactive.h" #include "gettext.h" #include "revision.h" #include "wt-status.h" @@ -122,6 +123,7 @@ static const char *edit_message, *use_message; static char *fixup_message, *fixup_commit, *squash_message; static const char *fixup_prefix; static int all, also, interactive, patch_interactive, only, amend, signoff; +static struct add_p_opt add_p_opt = ADD_P_OPT_INIT; static int edit_flag = -1; /* unspecified */ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static int config_commit_verbose = -1; /* unspecified */ @@ -354,6 +356,11 @@ static const char *prepare_index(const char **argv, const char *prefix, const char *ret; char *path = NULL; + if (add_p_opt.context < -1) + die(_("'%s' cannot be negative"), "--unified"); + if (add_p_opt.interhunkcontext < -1) + die(_("'%s' cannot be negative"), "--inter-hunk-context"); + if (is_status) refresh_flags |= REFRESH_UNMERGED; parse_pathspec(&pathspec, 0, @@ -400,7 +407,7 @@ static const char *prepare_index(const char **argv, const char *prefix, old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT)); setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1); - if (interactive_add(the_repository, argv, prefix, patch_interactive) != 0) + if (interactive_add(the_repository, argv, prefix, patch_interactive, &add_p_opt) != 0) die(_("interactive add failed")); the_repository->index_file = old_repo_index_file; @@ -424,6 +431,11 @@ static const char *prepare_index(const char **argv, const char *prefix, commit_style = COMMIT_NORMAL; ret = get_lock_file_path(&index_lock); goto out; + } else { + if (add_p_opt.context != -1) + die(_("the option '%s' requires '%s'"), "--unified", "--interactive/--patch"); + if (add_p_opt.interhunkcontext != -1) + die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--interactive/--patch"); } /* @@ -1730,6 +1742,8 @@ int cmd_commit(int argc, OPT_BOOL('i', "include", &also, N_("add specified files to index for commit")), OPT_BOOL(0, "interactive", &interactive, N_("interactively add files")), OPT_BOOL('p', "patch", &patch_interactive, N_("interactively add changes")), + OPT_DIFF_UNIFIED(&add_p_opt.context), + OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext), OPT_BOOL('o', "only", &only, N_("commit only specified files")), OPT_BOOL('n', "no-verify", &no_verify, N_("bypass pre-commit and commit-msg hooks")), OPT_BOOL(0, "dry-run", &dry_run, N_("show what would be committed")), @@ -1940,7 +1954,7 @@ int cmd_commit(int argc, "new index file. Check that disk is not full and quota is\n" "not exceeded, and then \"git restore --staged :/\" to recover.")); - git_test_write_commit_graph_or_die(); + git_test_write_commit_graph_or_die(the_repository->objects->sources); repo_rerere(the_repository, 0); run_auto_maintenance(quiet); diff --git a/builtin/count-objects.c b/builtin/count-objects.c index b1148a68fa..a61d3b46aa 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -118,7 +118,7 @@ int cmd_count_objects(int argc, report_linked_checkout_garbage(the_repository); } - for_each_loose_file_in_objdir(repo_get_object_directory(the_repository), + for_each_loose_file_in_source(the_repository->objects->sources, count_loose, count_cruft, NULL, NULL); if (verbose) { diff --git a/builtin/describe.c b/builtin/describe.c index d7dd8139de..9f4e26d7ff 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -23,6 +23,8 @@ #include "list-objects.h" #include "commit-slab.h" #include "wildmatch.h" +#include "prio-queue.h" +#include "oidset.h" #define MAX_TAGS (FLAG_BITS - 1) #define DEFAULT_CANDIDATES 10 @@ -249,36 +251,83 @@ static int compare_pt(const void *a_, const void *b_) return 0; } -static unsigned long finish_depth_computation( - struct commit_list **list, - struct possible_tag *best) +struct lazy_queue { + struct prio_queue queue; + bool get_pending; +}; + +#define LAZY_QUEUE_INIT { { compare_commits_by_commit_date }, false } + +static void *lazy_queue_get(struct lazy_queue *queue) +{ + if (queue->get_pending) + prio_queue_get(&queue->queue); + else + queue->get_pending = true; + return prio_queue_peek(&queue->queue); +} + +static void lazy_queue_put(struct lazy_queue *queue, void *thing) +{ + if (queue->get_pending) + prio_queue_replace(&queue->queue, thing); + else + prio_queue_put(&queue->queue, thing); + queue->get_pending = false; +} + +static bool lazy_queue_empty(const struct lazy_queue *queue) +{ + return queue->queue.nr == (queue->get_pending ? 1 : 0); +} + +static void lazy_queue_clear(struct lazy_queue *queue) +{ + clear_prio_queue(&queue->queue); + queue->get_pending = false; +} + +static unsigned long finish_depth_computation(struct lazy_queue *queue, + struct possible_tag *best) { unsigned long seen_commits = 0; - while (*list) { - struct commit *c = pop_commit(list); + struct oidset unflagged = OIDSET_INIT; + + for (size_t i = queue->get_pending ? 1 : 0; i < queue->queue.nr; i++) { + struct commit *commit = queue->queue.array[i].data; + if (!(commit->object.flags & best->flag_within)) + oidset_insert(&unflagged, &commit->object.oid); + } + + while (!lazy_queue_empty(queue)) { + struct commit *c = lazy_queue_get(queue); struct commit_list *parents = c->parents; seen_commits++; if (c->object.flags & best->flag_within) { - struct commit_list *a = *list; - while (a) { - struct commit *i = a->item; - if (!(i->object.flags & best->flag_within)) - break; - a = a->next; - } - if (!a) + if (!oidset_size(&unflagged)) break; - } else + } else { + oidset_remove(&unflagged, &c->object.oid); best->depth++; + } while (parents) { + unsigned seen, flag_before, flag_after; struct commit *p = parents->item; repo_parse_commit(the_repository, p); - if (!(p->object.flags & SEEN)) - commit_list_insert_by_date(p, list); + seen = p->object.flags & SEEN; + if (!seen) + lazy_queue_put(queue, p); + flag_before = p->object.flags & best->flag_within; p->object.flags |= c->object.flags; + flag_after = p->object.flags & best->flag_within; + if (!seen && !flag_after) + oidset_insert(&unflagged, &p->object.oid); + if (seen && !flag_before && flag_after) + oidset_remove(&unflagged, &p->object.oid); parents = parents->next; } } + oidset_clear(&unflagged); return seen_commits; } @@ -313,18 +362,16 @@ static void append_suffix(int depth, const struct object_id *oid, struct strbuf repo_find_unique_abbrev(the_repository, oid, abbrev)); } -static void describe_commit(struct object_id *oid, struct strbuf *dst) +static void describe_commit(struct commit *cmit, struct strbuf *dst) { - struct commit *cmit, *gave_up_on = NULL; - struct commit_list *list; + struct commit *gave_up_on = NULL; + struct lazy_queue queue = LAZY_QUEUE_INIT; struct commit_name *n; struct possible_tag all_matches[MAX_TAGS]; unsigned int match_cnt = 0, annotated_cnt = 0, cur_match; unsigned long seen_commits = 0; unsigned int unannotated_cnt = 0; - cmit = lookup_commit_reference(the_repository, oid); - n = find_commit_name(&cmit->object.oid); if (n && (tags || all || n->prio == 2)) { /* @@ -332,7 +379,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) */ append_name(n, dst); if (n->misnamed || longformat) - append_suffix(0, n->tag ? get_tagged_oid(n->tag) : oid, dst); + append_suffix(0, n->tag ? get_tagged_oid(n->tag) : &cmit->object.oid, dst); if (suffix) strbuf_addstr(dst, suffix); return; @@ -359,11 +406,10 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) have_util = 1; } - list = NULL; cmit->object.flags = SEEN; - commit_list_insert(cmit, &list); - while (list) { - struct commit *c = pop_commit(&list); + lazy_queue_put(&queue, cmit); + while (!lazy_queue_empty(&queue)) { + struct commit *c = lazy_queue_get(&queue); struct commit_list *parents = c->parents; struct commit_name **slot; @@ -397,7 +443,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) t->depth++; } /* Stop if last remaining path already covered by best candidate(s) */ - if (annotated_cnt && !list) { + if (annotated_cnt && lazy_queue_empty(&queue)) { int best_depth = INT_MAX; unsigned best_within = 0; for (cur_match = 0; cur_match < match_cnt; cur_match++) { @@ -420,7 +466,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) struct commit *p = parents->item; repo_parse_commit(the_repository, p); if (!(p->object.flags & SEEN)) - commit_list_insert_by_date(p, &list); + lazy_queue_put(&queue, p); p->object.flags |= c->object.flags; parents = parents->next; @@ -435,6 +481,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) strbuf_add_unique_abbrev(dst, cmit_oid, abbrev); if (suffix) strbuf_addstr(dst, suffix); + lazy_queue_clear(&queue); return; } if (unannotated_cnt) @@ -450,11 +497,11 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) QSORT(all_matches, match_cnt, compare_pt); if (gave_up_on) { - commit_list_insert_by_date(gave_up_on, &list); + lazy_queue_put(&queue, gave_up_on); seen_commits--; } - seen_commits += finish_depth_computation(&list, &all_matches[0]); - free_commit_list(list); + seen_commits += finish_depth_computation(&queue, &all_matches[0]); + lazy_queue_clear(&queue); if (debug) { static int label_width = -1; @@ -489,8 +536,8 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) } struct process_commit_data { - struct object_id current_commit; - struct object_id looking_for; + struct commit *current_commit; + const struct object_id *looking_for; struct strbuf *dst; struct rev_info *revs; }; @@ -498,30 +545,38 @@ struct process_commit_data { static void process_commit(struct commit *commit, void *data) { struct process_commit_data *pcd = data; - pcd->current_commit = commit->object.oid; + pcd->current_commit = commit; } static void process_object(struct object *obj, const char *path, void *data) { struct process_commit_data *pcd = data; - if (oideq(&pcd->looking_for, &obj->oid) && !pcd->dst->len) { + if (oideq(pcd->looking_for, &obj->oid) && !pcd->dst->len) { reset_revision_walk(); - describe_commit(&pcd->current_commit, pcd->dst); - strbuf_addf(pcd->dst, ":%s", path); + if (pcd->current_commit) { + describe_commit(pcd->current_commit, pcd->dst); + strbuf_addf(pcd->dst, ":%s", path); + } free_commit_list(pcd->revs->commits); pcd->revs->commits = NULL; } } -static void describe_blob(struct object_id oid, struct strbuf *dst) +static void describe_blob(const struct object_id *oid, struct strbuf *dst) { struct rev_info revs; struct strvec args = STRVEC_INIT; - struct process_commit_data pcd = { *null_oid(the_hash_algo), oid, dst, &revs}; + struct object_id head_oid; + struct process_commit_data pcd = { NULL, oid, dst, &revs}; + + if (repo_get_oid(the_repository, "HEAD", &head_oid)) + die(_("cannot search for blob '%s' on an unborn branch"), + oid_to_hex(oid)); strvec_pushl(&args, "internal: The first arg is not parsed", - "--objects", "--in-commit-order", "--reverse", "HEAD", + "--objects", "--in-commit-order", "--reverse", + oid_to_hex(&head_oid), NULL); repo_init_revisions(the_repository, &revs, NULL); @@ -535,6 +590,9 @@ static void describe_blob(struct object_id oid, struct strbuf *dst) reset_revision_walk(); release_revisions(&revs); strvec_clear(&args); + + if (!dst->len) + die(_("blob '%s' not reachable from HEAD"), oid_to_hex(oid)); } static void describe(const char *arg, int last_one) @@ -551,10 +609,10 @@ static void describe(const char *arg, int last_one) cmit = lookup_commit_reference_gently(the_repository, &oid, 1); if (cmit) - describe_commit(&oid, &sb); + describe_commit(cmit, &sb); else if (odb_read_object_info(the_repository->objects, &oid, NULL) == OBJ_BLOB) - describe_blob(oid, &sb); + describe_blob(&oid, &sb); else die(_("%s is neither a commit nor blob"), arg); diff --git a/builtin/diff.c b/builtin/diff.c index 9a89e25a98..0b23c41456 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -487,6 +487,21 @@ int cmd_diff(int argc, init_diff_ui_defaults(); repo_config(the_repository, git_diff_ui_config, NULL); + + /* + * If we are ignoring the fact that our current directory may + * be part of a working tree controlled by a Git repository to + * pretend to be a "better GNU diff", we should undo the + * effect of the setup code that did a chdir() to the top of + * the working tree. Where we came from is recorded in the + * prefix. + */ + if (no_index && prefix) { + if (chdir(prefix)) + die(_("cannot come back to cwd")); + prefix = NULL; + } + prefix = precompose_argv_prefix(argc, argv, prefix); repo_init_revisions(the_repository, &rev, prefix); diff --git a/builtin/fast-import.c b/builtin/fast-import.c index adf3994be1..2c35f9345d 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -822,11 +822,11 @@ static char *keep_pack(const char *curr_index_name) die_errno("failed to write keep file"); odb_pack_name(pack_data->repo, &name, pack_data->hash, "pack"); - if (finalize_object_file(pack_data->pack_name, name.buf)) + if (finalize_object_file(pack_data->repo, pack_data->pack_name, name.buf)) die("cannot store pack file"); odb_pack_name(pack_data->repo, &name, pack_data->hash, "idx"); - if (finalize_object_file(curr_index_name, name.buf)) + if (finalize_object_file(pack_data->repo, curr_index_name, name.buf)) die("cannot store index file"); free((void *)curr_index_name); return strbuf_detach(&name, NULL); diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index edb93c0b3a..cf4273a52c 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -1,4 +1,3 @@ -#define USE_THE_REPOSITORY_VARIABLE #include "builtin.h" #include "config.h" #include "fmt-merge-msg.h" @@ -13,12 +12,13 @@ static const char * const fmt_merge_msg_usage[] = { int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix, - struct repository *repo UNUSED) + struct repository *repo) { char *inpath = NULL; const char *message = NULL; char *into_name = NULL; int shortlog_len = -1; + int merge_log_config = -1; struct option options[] = { { .type = OPTION_INTEGER, @@ -53,7 +53,7 @@ int cmd_fmt_merge_msg(int argc, int ret; struct fmt_merge_msg_opts opts; - repo_config(the_repository, fmt_merge_msg_config, NULL); + repo_config(repo, fmt_merge_msg_config, &merge_log_config); argc = parse_options(argc, argv, prefix, options, fmt_merge_msg_usage, 0); if (argc > 0) diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index a8f7f8c762..4a2fc421db 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -2,6 +2,7 @@ #include "commit.h" #include "config.h" #include "environment.h" +#include "for-each-ref.h" #include "gettext.h" #include "object.h" #include "parse-options.h" @@ -9,18 +10,7 @@ #include "strbuf.h" #include "strvec.h" -static char const * const for_each_ref_usage[] = { - N_("git for-each-ref [<options>] [<pattern>]"), - N_("git for-each-ref [--points-at <object>]"), - N_("git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]"), - N_("git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"), - NULL -}; - -int cmd_for_each_ref(int argc, - const char **argv, - const char *prefix, - struct repository *repo) +int for_each_ref_core(int argc, const char **argv, const char *prefix, struct repository *repo, const char *const *usage) { struct ref_sorting *sorting; struct string_list sorting_options = STRING_LIST_INIT_DUP; @@ -45,6 +35,7 @@ int cmd_for_each_ref(int argc, OPT_GROUP(""), OPT_INTEGER( 0 , "count", &format.array_opts.max_count, N_("show only <n> matched refs")), OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")), + OPT_STRING( 0 , "start-after", &filter.start_after, N_("marker"), N_("start iteration after the provided marker")), OPT__COLOR(&format.use_color, N_("respect format colors")), OPT_REF_FILTER_EXCLUDE(&filter), OPT_REF_SORT(&sorting_options), @@ -68,17 +59,20 @@ int cmd_for_each_ref(int argc, /* Set default (refname) sorting */ string_list_append(&sorting_options, "refname"); - parse_options(argc, argv, prefix, opts, for_each_ref_usage, 0); + parse_options(argc, argv, prefix, opts, usage, 0); if (format.array_opts.max_count < 0) { error("invalid --count argument: `%d'", format.array_opts.max_count); - usage_with_options(for_each_ref_usage, opts); + usage_with_options(usage, opts); } if (HAS_MULTI_BITS(format.quote_style)) { error("more than one quoting style?"); - usage_with_options(for_each_ref_usage, opts); + usage_with_options(usage, opts); } if (verify_ref_format(&format)) - usage_with_options(for_each_ref_usage, opts); + usage_with_options(usage, opts); + + if (filter.start_after && sorting_options.nr > 1) + die(_("cannot use --start-after with custom sort options")); sorting = ref_sorting_options(&sorting_options); ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase); @@ -101,6 +95,9 @@ int cmd_for_each_ref(int argc, filter.name_patterns = argv; } + if (filter.start_after && filter.name_patterns && filter.name_patterns[0]) + die(_("cannot use --start-after with patterns")); + if (include_root_refs) flags |= FILTER_REFS_ROOT_REFS | FILTER_REFS_DETACHED_HEAD; @@ -112,3 +109,16 @@ int cmd_for_each_ref(int argc, strvec_clear(&vec); return 0; } + +int cmd_for_each_ref(int argc, + const char **argv, + const char *prefix, + struct repository *repo) +{ + static char const * const for_each_ref_usage[] = { + N_("git for-each-ref " COMMON_USAGE_FOR_EACH_REF), + NULL + }; + + return for_each_ref_core(argc, argv, prefix, repo, for_each_ref_usage); +} diff --git a/builtin/fsck.c b/builtin/fsck.c index 0ec6ca06e3..d2eb9d4fbe 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -393,7 +393,8 @@ static void check_connectivity(void) * and ignore any that weren't present in our earlier * traversal. */ - for_each_loose_object(mark_loose_unreachable_referents, NULL, 0); + for_each_loose_object(the_repository->objects, + mark_loose_unreachable_referents, NULL, 0); for_each_packed_object(the_repository, mark_packed_unreachable_referents, NULL, @@ -502,13 +503,12 @@ static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid, } } -static int fsck_handle_reflog_ent(struct object_id *ooid, struct object_id *noid, +static int fsck_handle_reflog_ent(const char *refname, + struct object_id *ooid, struct object_id *noid, const char *email UNUSED, timestamp_t timestamp, int tz UNUSED, - const char *message UNUSED, void *cb_data) + const char *message UNUSED, void *cb_data UNUSED) { - const char *refname = cb_data; - if (verbose) fprintf_ln(stderr, _("Checking reflog %s->%s"), oid_to_hex(ooid), oid_to_hex(noid)); @@ -525,7 +525,7 @@ static int fsck_handle_reflog(const char *logname, void *cb_data) strbuf_worktree_ref(cb_data, &refname, logname); refs_for_each_reflog_ent(get_main_ref_store(the_repository), refname.buf, fsck_handle_reflog_ent, - refname.buf); + NULL); strbuf_release(&refname); return 0; } @@ -632,7 +632,7 @@ static int fsck_loose(const struct object_id *oid, const char *path, oi.sizep = &size; oi.typep = &type; - if (read_loose_object(path, oid, &real_oid, &contents, &oi) < 0) { + if (read_loose_object(the_repository, path, oid, &real_oid, &contents, &oi) < 0) { if (contents && !oideq(&real_oid, oid)) err = error(_("%s: hash-path mismatch, found at: %s"), oid_to_hex(&real_oid), path); @@ -687,7 +687,7 @@ static int fsck_subdir(unsigned int nr, const char *path UNUSED, void *data) return 0; } -static void fsck_object_dir(const char *path) +static void fsck_source(struct odb_source *source) { struct progress *progress = NULL; struct for_each_loose_cb cb_data = { @@ -701,8 +701,8 @@ static void fsck_object_dir(const char *path) progress = start_progress(the_repository, _("Checking object directories"), 256); - for_each_loose_file_in_objdir(path, fsck_loose, fsck_cruft, fsck_subdir, - &cb_data); + for_each_loose_file_in_source(source, fsck_loose, + fsck_cruft, fsck_subdir, &cb_data); display_progress(progress, 256); stop_progress(&progress); } @@ -994,13 +994,14 @@ int cmd_fsck(int argc, fsck_refs(the_repository); if (connectivity_only) { - for_each_loose_object(mark_loose_for_connectivity, NULL, 0); + for_each_loose_object(the_repository->objects, + mark_loose_for_connectivity, NULL, 0); for_each_packed_object(the_repository, mark_packed_for_connectivity, NULL, 0); } else { odb_prepare_alternates(the_repository->objects); for (source = the_repository->objects->sources; source; source = source->next) - fsck_object_dir(source->path); + fsck_source(source); if (check_full) { struct packed_git *p; diff --git a/builtin/gc.c b/builtin/gc.c index 183798f0dc..03ae4926b2 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -312,7 +312,8 @@ struct count_reflog_entries_data { size_t limit; }; -static int count_reflog_entries(struct object_id *old_oid, struct object_id *new_oid, +static int count_reflog_entries(const char *refname UNUSED, + struct object_id *old_oid, struct object_id *new_oid, const char *committer, timestamp_t timestamp, int tz, const char *msg, void *cb_data) { @@ -346,6 +347,7 @@ static int reflog_expire_condition(struct gc_config *cfg UNUSED) count_reflog_entries, &data); reflog_expiry_cleanup(&data.policy); + reflog_clear_expire_config(&data.policy.opts); return data.count >= data.limit; } @@ -1308,7 +1310,7 @@ static int loose_object_auto_condition(struct gc_config *cfg UNUSED) if (loose_object_auto_limit < 0) return 1; - return for_each_loose_file_in_objdir(the_repository->objects->sources->path, + return for_each_loose_file_in_source(the_repository->objects->sources, loose_object_count, NULL, NULL, &count); } @@ -1343,7 +1345,7 @@ static int pack_loose(struct maintenance_run_opts *opts) * Do not start pack-objects process * if there are no loose objects. */ - if (!for_each_loose_file_in_objdir(r->objects->sources->path, + if (!for_each_loose_file_in_source(r->objects->sources, bail_on_loose, NULL, NULL, NULL)) return 0; @@ -1383,11 +1385,9 @@ static int pack_loose(struct maintenance_run_opts *opts) else if (data.batch_size > 0) data.batch_size--; /* Decrease for equality on limit. */ - for_each_loose_file_in_objdir(r->objects->sources->path, + for_each_loose_file_in_source(r->objects->sources, write_loose_object_to_stdin, - NULL, - NULL, - &data); + NULL, NULL, &data); fclose(data.in); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 53f1a7cd71..f91c301bba 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -1598,7 +1598,7 @@ static void rename_tmp_packfile(const char **final_name, if (!*final_name || strcmp(*final_name, curr_name)) { if (!*final_name) *final_name = odb_pack_name(the_repository, name, hash, ext); - if (finalize_object_file(curr_name, *final_name)) + if (finalize_object_file(the_repository, curr_name, *final_name)) die(_("unable to rename temporary '*.%s' file to '%s'"), ext, *final_name); } else if (make_read_only_if_same) { diff --git a/builtin/last-modified.c b/builtin/last-modified.c new file mode 100644 index 0000000000..886ba12cb5 --- /dev/null +++ b/builtin/last-modified.c @@ -0,0 +1,326 @@ +#include "git-compat-util.h" +#include "bloom.h" +#include "builtin.h" +#include "commit-graph.h" +#include "commit.h" +#include "config.h" +#include "environment.h" +#include "diff.h" +#include "diffcore.h" +#include "environment.h" +#include "hashmap.h" +#include "hex.h" +#include "log-tree.h" +#include "object-name.h" +#include "object.h" +#include "parse-options.h" +#include "quote.h" +#include "repository.h" +#include "revision.h" + +struct last_modified_entry { + struct hashmap_entry hashent; + struct object_id oid; + struct bloom_key key; + const char path[FLEX_ARRAY]; +}; + +static int last_modified_entry_hashcmp(const void *unused UNUSED, + const struct hashmap_entry *hent1, + const struct hashmap_entry *hent2, + const void *path) +{ + const struct last_modified_entry *ent1 = + container_of(hent1, const struct last_modified_entry, hashent); + const struct last_modified_entry *ent2 = + container_of(hent2, const struct last_modified_entry, hashent); + return strcmp(ent1->path, path ? path : ent2->path); +} + +struct last_modified { + struct hashmap paths; + struct rev_info rev; + bool recursive; + bool show_trees; +}; + +static void last_modified_release(struct last_modified *lm) +{ + struct hashmap_iter iter; + struct last_modified_entry *ent; + + hashmap_for_each_entry(&lm->paths, &iter, ent, hashent) + bloom_key_clear(&ent->key); + + hashmap_clear_and_free(&lm->paths, struct last_modified_entry, hashent); + release_revisions(&lm->rev); +} + +struct last_modified_callback_data { + struct last_modified *lm; + struct commit *commit; +}; + +static void add_path_from_diff(struct diff_queue_struct *q, + struct diff_options *opt UNUSED, void *data) +{ + struct last_modified *lm = data; + + for (int i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + struct last_modified_entry *ent; + const char *path = p->two->path; + + FLEX_ALLOC_STR(ent, path, path); + oidcpy(&ent->oid, &p->two->oid); + if (lm->rev.bloom_filter_settings) + bloom_key_fill(&ent->key, path, strlen(path), + lm->rev.bloom_filter_settings); + hashmap_entry_init(&ent->hashent, strhash(ent->path)); + hashmap_add(&lm->paths, &ent->hashent); + } +} + +static int populate_paths_from_revs(struct last_modified *lm) +{ + int num_interesting = 0; + struct diff_options diffopt; + + /* + * Create a copy of `struct diff_options`. In this copy a callback is + * set that when called adds entries to `paths` in `struct last_modified`. + * This copy is used to diff the tree of the target revision against an + * empty tree. This results in all paths in the target revision being + * listed. After `paths` is populated, we don't need this copy no more. + */ + memcpy(&diffopt, &lm->rev.diffopt, sizeof(diffopt)); + copy_pathspec(&diffopt.pathspec, &lm->rev.diffopt.pathspec); + diffopt.output_format = DIFF_FORMAT_CALLBACK; + diffopt.format_callback = add_path_from_diff; + diffopt.format_callback_data = lm; + + for (size_t i = 0; i < lm->rev.pending.nr; i++) { + struct object_array_entry *obj = lm->rev.pending.objects + i; + + if (obj->item->flags & UNINTERESTING) + continue; + + if (num_interesting++) + return error(_("last-modified can only operate on one tree at a time")); + + diff_tree_oid(lm->rev.repo->hash_algo->empty_tree, + &obj->item->oid, "", &diffopt); + diff_flush(&diffopt); + } + clear_pathspec(&diffopt.pathspec); + + return 0; +} + +static void last_modified_emit(struct last_modified *lm, + const char *path, const struct commit *commit) + +{ + if (commit->object.flags & BOUNDARY) + putchar('^'); + printf("%s\t", oid_to_hex(&commit->object.oid)); + + if (lm->rev.diffopt.line_termination) + write_name_quoted(path, stdout, '\n'); + else + printf("%s%c", path, '\0'); +} + +static void mark_path(const char *path, const struct object_id *oid, + struct last_modified_callback_data *data) +{ + struct last_modified_entry *ent; + + /* Is it even a path that we are interested in? */ + ent = hashmap_get_entry_from_hash(&data->lm->paths, strhash(path), path, + struct last_modified_entry, hashent); + if (!ent) + return; + + /* + * Is it arriving at a version of interest, or is it from a side branch + * which did not contribute to the final state? + */ + if (!oideq(oid, &ent->oid)) + return; + + last_modified_emit(data->lm, path, data->commit); + + hashmap_remove(&data->lm->paths, &ent->hashent, path); + bloom_key_clear(&ent->key); + free(ent); +} + +static void last_modified_diff(struct diff_queue_struct *q, + struct diff_options *opt UNUSED, void *cbdata) +{ + struct last_modified_callback_data *data = cbdata; + + for (int i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + switch (p->status) { + case DIFF_STATUS_DELETED: + /* + * There's no point in feeding a deletion, as it could + * not have resulted in our current state, which + * actually has the file. + */ + break; + + default: + /* + * Otherwise, we care only that we somehow arrived at + * a final oid state. Note that this covers some + * potentially controversial areas, including: + * + * 1. A rename or copy will be found, as it is the + * first time the content has arrived at the given + * path. + * + * 2. Even a non-content modification like a mode or + * type change will trigger it. + * + * We take the inclusive approach for now, and find + * anything which impacts the path. Options to tweak + * the behavior (e.g., to "--follow" the content across + * renames) can come later. + */ + mark_path(p->two->path, &p->two->oid, data); + break; + } + } +} + +static bool maybe_changed_path(struct last_modified *lm, struct commit *origin) +{ + struct bloom_filter *filter; + struct last_modified_entry *ent; + struct hashmap_iter iter; + + if (!lm->rev.bloom_filter_settings) + return true; + + if (commit_graph_generation(origin) == GENERATION_NUMBER_INFINITY) + return true; + + filter = get_bloom_filter(lm->rev.repo, origin); + if (!filter) + return true; + + hashmap_for_each_entry(&lm->paths, &iter, ent, hashent) { + if (bloom_filter_contains(filter, &ent->key, + lm->rev.bloom_filter_settings)) + return true; + } + return false; +} + +static int last_modified_run(struct last_modified *lm) +{ + struct last_modified_callback_data data = { .lm = lm }; + + lm->rev.diffopt.output_format = DIFF_FORMAT_CALLBACK; + lm->rev.diffopt.format_callback = last_modified_diff; + lm->rev.diffopt.format_callback_data = &data; + + prepare_revision_walk(&lm->rev); + + while (hashmap_get_size(&lm->paths)) { + data.commit = get_revision(&lm->rev); + if (!data.commit) + BUG("paths remaining beyond boundary in last-modified"); + + if (data.commit->object.flags & BOUNDARY) { + diff_tree_oid(lm->rev.repo->hash_algo->empty_tree, + &data.commit->object.oid, "", + &lm->rev.diffopt); + diff_flush(&lm->rev.diffopt); + + break; + } + + if (!maybe_changed_path(lm, data.commit)) + continue; + + log_tree_commit(&lm->rev, data.commit); + } + + return 0; +} + +static int last_modified_init(struct last_modified *lm, struct repository *r, + const char *prefix, int argc, const char **argv) +{ + hashmap_init(&lm->paths, last_modified_entry_hashcmp, NULL, 0); + + repo_init_revisions(r, &lm->rev, prefix); + lm->rev.def = "HEAD"; + lm->rev.combine_merges = 1; + lm->rev.show_root_diff = 1; + lm->rev.boundary = 1; + lm->rev.no_commit_id = 1; + lm->rev.diff = 1; + lm->rev.diffopt.flags.recursive = lm->recursive; + lm->rev.diffopt.flags.tree_in_recursive = lm->show_trees; + + argc = setup_revisions(argc, argv, &lm->rev, NULL); + if (argc > 1) { + error(_("unknown last-modified argument: %s"), argv[1]); + return argc; + } + + lm->rev.bloom_filter_settings = get_bloom_filter_settings(lm->rev.repo); + + if (populate_paths_from_revs(lm) < 0) + return error(_("unable to setup last-modified")); + + return 0; +} + +int cmd_last_modified(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + int ret; + struct last_modified lm = { 0 }; + + const char * const last_modified_usage[] = { + N_("git last-modified [--recursive] [--show-trees] " + "[<revision-range>] [[--] <path>...]"), + NULL + }; + + struct option last_modified_options[] = { + OPT_BOOL('r', "recursive", &lm.recursive, + N_("recurse into subtrees")), + OPT_BOOL('t', "show-trees", &lm.show_trees, + N_("show tree entries when recursing into subtrees")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, last_modified_options, + last_modified_usage, + PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN_OPT); + + repo_config(repo, git_default_config, NULL); + + ret = last_modified_init(&lm, repo, prefix, argc, argv); + if (ret > 0) + usage_with_options(last_modified_usage, + last_modified_options); + if (ret) + goto out; + + ret = last_modified_run(&lm); + if (ret) + goto out; + +out: + last_modified_release(&lm); + + return ret; +} diff --git a/builtin/log.c b/builtin/log.c index a9969ad00a..c2f8bbf863 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -391,129 +391,6 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, cmd_log_init_finish(argc, argv, prefix, rev, opt, cfg); } -/* - * This gives a rough estimate for how many commits we - * will print out in the list. - */ -static int estimate_commit_count(struct commit_list *list) -{ - int n = 0; - - while (list) { - struct commit *commit = list->item; - unsigned int flags = commit->object.flags; - list = list->next; - if (!(flags & (TREESAME | UNINTERESTING))) - n++; - } - return n; -} - -static void show_early_header(struct rev_info *rev, const char *stage, int nr) -{ - if (rev->shown_one) { - rev->shown_one = 0; - if (rev->commit_format != CMIT_FMT_ONELINE) - putchar(rev->diffopt.line_termination); - } - fprintf(rev->diffopt.file, _("Final output: %d %s\n"), nr, stage); -} - -static struct itimerval early_output_timer; - -static void log_show_early(struct rev_info *revs, struct commit_list *list) -{ - int i = revs->early_output; - int show_header = 1; - int no_free = revs->diffopt.no_free; - - revs->diffopt.no_free = 0; - sort_in_topological_order(&list, revs->sort_order); - while (list && i) { - struct commit *commit = list->item; - switch (simplify_commit(revs, commit)) { - case commit_show: - if (show_header) { - int n = estimate_commit_count(list); - show_early_header(revs, "incomplete", n); - show_header = 0; - } - log_tree_commit(revs, commit); - i--; - break; - case commit_ignore: - break; - case commit_error: - revs->diffopt.no_free = no_free; - diff_free(&revs->diffopt); - return; - } - list = list->next; - } - - /* Did we already get enough commits for the early output? */ - if (!i) { - revs->diffopt.no_free = 0; - diff_free(&revs->diffopt); - return; - } - - /* - * ..if no, then repeat it twice a second until we - * do. - * - * NOTE! We don't use "it_interval", because if the - * reader isn't listening, we want our output to be - * throttled by the writing, and not have the timer - * trigger every second even if we're blocked on a - * reader! - */ - early_output_timer.it_value.tv_sec = 0; - early_output_timer.it_value.tv_usec = 500000; - setitimer(ITIMER_REAL, &early_output_timer, NULL); -} - -static void early_output(int signal UNUSED) -{ - show_early_output = log_show_early; -} - -static void setup_early_output(void) -{ - struct sigaction sa; - - /* - * Set up the signal handler, minimally intrusively: - * we only set a single volatile integer word (not - * using sigatomic_t - trying to avoid unnecessary - * system dependencies and headers), and using - * SA_RESTART. - */ - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = early_output; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - sigaction(SIGALRM, &sa, NULL); - - /* - * If we can get the whole output in less than a - * tenth of a second, don't even bother doing the - * early-output thing.. - * - * This is a one-time-only trigger. - */ - early_output_timer.it_value.tv_sec = 0; - early_output_timer.it_value.tv_usec = 100000; - setitimer(ITIMER_REAL, &early_output_timer, NULL); -} - -static void finish_early_output(struct rev_info *rev) -{ - int n = estimate_commit_count(rev->commits); - signal(SIGALRM, SIG_IGN); - show_early_header(rev, "done", n); -} - static int cmd_log_walk_no_free(struct rev_info *rev) { struct commit *commit; @@ -521,15 +398,9 @@ static int cmd_log_walk_no_free(struct rev_info *rev) int saved_dcctc = 0; int result; - if (rev->early_output) - setup_early_output(); - if (prepare_revision_walk(rev)) die(_("revision walk setup failed")); - if (rev->early_output) - finish_early_output(rev); - /* * For --check and --exit-code, the exit code is based on CHECK_FAILED * and HAS_CHANGES being accumulated in rev->diffopt, so be careful to diff --git a/builtin/ls-files.c b/builtin/ls-files.c index c06a6f33e4..b148607f7a 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -414,14 +414,21 @@ static void show_files(struct repository *repo, struct dir_struct *dir) if (!(show_cached || show_stage || show_deleted || show_modified)) return; - if (!show_sparse_dirs) - ensure_full_index(repo->index); - for (i = 0; i < repo->index->cache_nr; i++) { const struct cache_entry *ce = repo->index->cache[i]; struct stat st; int stat_err; + if (S_ISSPARSEDIR(ce->ce_mode) && !show_sparse_dirs) { + /* + * This is the first time we've hit a sparse dir, + * so expansion will leave the first 'i' entries + * alone. + */ + ensure_full_index(repo->index); + ce = repo->index->cache[i]; + } + construct_fullname(&fullname, repo, ce); if ((dir->flags & DIR_SHOW_IGNORED) && diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 3da4ca2c8c..46775d0c79 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -155,7 +155,8 @@ int cmd_merge_file(int argc, if (object_id && !to_stdout) { struct object_id oid; if (result.size) { - if (write_object_file(result.ptr, result.size, OBJ_BLOB, &oid) < 0) + if (odb_write_object(the_repository->objects, result.ptr, + result.size, OBJ_BLOB, &oid) < 0) ret = error(_("Could not write object file")); } else { oidcpy(&oid, the_hash_algo->empty_blob); diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index 03b5100cfa..17aa4db37a 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -38,7 +38,8 @@ int cmd_merge_recursive(int argc, if (argv[0] && ends_with(argv[0], "-subtree")) o.subtree_shift = ""; - if (argc == 2 && !strcmp(argv[1], "-h")) { + if (argc == 2 && (!strcmp(argv[1], "-h") || + !strcmp(argv[1], "--help-all"))) { struct strbuf msg = STRBUF_INIT; strbuf_addf(&msg, builtin_merge_recursive_usage, argv[0]); show_usage_if_asked(argc, argv, msg.buf); diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 203f0e6456..1c063d9a41 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -619,32 +619,34 @@ int cmd_merge_tree(int argc, "--merge-base", "--stdin"); line_termination = '\0'; while (strbuf_getline_lf(&buf, stdin) != EOF) { - struct strbuf **split; + struct string_list split = STRING_LIST_INIT_NODUP; const char *input_merge_base = NULL; - split = strbuf_split(&buf, ' '); - if (!split[0] || !split[1]) + string_list_split_in_place_f(&split, buf.buf, " ", -1, + STRING_LIST_SPLIT_TRIM); + + if (split.nr < 2) die(_("malformed input line: '%s'."), buf.buf); - strbuf_rtrim(split[0]); - strbuf_rtrim(split[1]); /* parse the merge-base */ - if (!strcmp(split[1]->buf, "--")) { - input_merge_base = split[0]->buf; + if (!strcmp(split.items[1].string, "--")) { + input_merge_base = split.items[0].string; } - if (input_merge_base && split[2] && split[3] && !split[4]) { - strbuf_rtrim(split[2]); - strbuf_rtrim(split[3]); - real_merge(&o, input_merge_base, split[2]->buf, split[3]->buf, prefix); - } else if (!input_merge_base && !split[2]) { - real_merge(&o, NULL, split[0]->buf, split[1]->buf, prefix); + if (input_merge_base && split.nr == 4) { + real_merge(&o, input_merge_base, + split.items[2].string, split.items[3].string, + prefix); + } else if (!input_merge_base && split.nr == 2) { + real_merge(&o, NULL, + split.items[0].string, split.items[1].string, + prefix); } else { die(_("malformed input line: '%s'."), buf.buf); } maybe_flush_or_die(stdout, "stdout"); - strbuf_list_free(split); + string_list_clear(&split, 0); } strbuf_release(&buf); diff --git a/builtin/merge.c b/builtin/merge.c index 794cb7bb26..c421a11b0b 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -264,7 +264,7 @@ static struct option builtin_merge_options[] = { OPT_BOOL(0, "stat", &show_diffstat, N_("show a diffstat at the end of the merge")), OPT_BOOL(0, "summary", &show_diffstat, N_("(synonym to --stat)")), - OPT_CALLBACK_F(0, "compact-summary", &show_diffstat, N_("compact-summary"), + OPT_CALLBACK_F(0, "compact-summary", &show_diffstat, NULL, N_("show a compact-summary at the end of the merge"), PARSE_OPT_NOARG, option_parse_compact_summary), @@ -875,7 +875,7 @@ static void add_strategies(const char *string, unsigned attr) if (string) { struct string_list list = STRING_LIST_INIT_DUP; struct string_list_item *item; - string_list_split(&list, string, ' ', -1); + string_list_split(&list, string, " ", -1); for_each_string_list_item(item, &list) append_strategy(get_strategy(item->string)); string_list_clear(&list, 0); @@ -1374,6 +1374,7 @@ int cmd_merge(int argc, struct commit_list *remoteheads = NULL, *p; void *branch_to_free; int orig_argc = argc; + int merge_log_config = -1; show_usage_with_options_if_asked(argc, argv, builtin_merge_usage, builtin_merge_options); @@ -1395,7 +1396,7 @@ int cmd_merge(int argc, skip_prefix(branch, "refs/heads/", &branch); init_diff_ui_defaults(); - repo_config(the_repository, git_merge_config, NULL); + repo_config(the_repository, git_merge_config, &merge_log_config); if (!branch || is_null_oid(&head_oid)) head_commit = NULL; @@ -1865,7 +1866,7 @@ int cmd_merge(int argc, if (squash) { finish(head_commit, remoteheads, NULL, NULL); - git_test_write_commit_graph_or_die(); + git_test_write_commit_graph_or_die(the_repository->objects->sources); } else write_merge_state(remoteheads); diff --git a/builtin/mktag.c b/builtin/mktag.c index e078c97d03..7cf6e1230a 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -106,7 +106,7 @@ int cmd_mktag(int argc, if (verify_object_in_tag(&tagged_oid, &tagged_type) < 0) die(_("tag on stdin did not refer to a valid object")); - if (write_object_file(buf.buf, buf.len, OBJ_TAG, &result) < 0) + if (odb_write_object(the_repository->objects, buf.buf, buf.len, OBJ_TAG, &result) < 0) die(_("unable to write tag file")); strbuf_release(&buf); diff --git a/builtin/mktree.c b/builtin/mktree.c index 81df7f6099..12772303f5 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c @@ -63,7 +63,7 @@ static void write_tree(struct object_id *oid) strbuf_add(&buf, ent->oid.hash, the_hash_algo->rawsz); } - write_object_file(buf.buf, buf.len, OBJ_TREE, oid); + odb_write_object(the_repository->objects, buf.buf, buf.len, OBJ_TREE, oid); strbuf_release(&buf); } diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index d3b9e98be3..5f364aa816 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -65,12 +65,20 @@ static int parse_object_dir(const struct option *opt, const char *arg, char **value = opt->value; free(*value); if (unset) - *value = xstrdup(repo_get_object_directory(the_repository)); + *value = xstrdup(the_repository->objects->sources->path); else *value = real_pathdup(arg, 1); return 0; } +static struct odb_source *handle_object_dir_option(struct repository *repo) +{ + struct odb_source *source = odb_find_source(repo->objects, opts.object_dir); + if (!source) + source = odb_add_to_alternates_memory(repo->objects, opts.object_dir); + return source; +} + static struct option common_opts[] = { OPT_CALLBACK(0, "object-dir", &opts.object_dir, N_("directory"), @@ -140,6 +148,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, N_("refs snapshot for selecting bitmap commits")), OPT_END(), }; + struct odb_source *source; int ret; opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE; @@ -158,6 +167,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, if (argc) usage_with_options(builtin_multi_pack_index_write_usage, options); + source = handle_object_dir_option(repo); FREE_AND_NULL(options); @@ -166,7 +176,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, read_packs_from_stdin(&packs); - ret = write_midx_file_only(repo, opts.object_dir, &packs, + ret = write_midx_file_only(source, &packs, opts.preferred_pack, opts.refs_snapshot, opts.flags); @@ -177,7 +187,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv, } - ret = write_midx_file(repo, opts.object_dir, opts.preferred_pack, + ret = write_midx_file(source, opts.preferred_pack, opts.refs_snapshot, opts.flags); free(opts.refs_snapshot); @@ -194,6 +204,8 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv, N_("force progress reporting"), MIDX_PROGRESS), OPT_END(), }; + struct odb_source *source; + options = add_common_options(builtin_multi_pack_index_verify_options); trace2_cmd_mode(argv[0]); @@ -206,10 +218,11 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv, if (argc) usage_with_options(builtin_multi_pack_index_verify_usage, options); + source = handle_object_dir_option(the_repository); FREE_AND_NULL(options); - return verify_midx_file(the_repository, opts.object_dir, opts.flags); + return verify_midx_file(source, opts.flags); } static int cmd_multi_pack_index_expire(int argc, const char **argv, @@ -222,6 +235,8 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv, N_("force progress reporting"), MIDX_PROGRESS), OPT_END(), }; + struct odb_source *source; + options = add_common_options(builtin_multi_pack_index_expire_options); trace2_cmd_mode(argv[0]); @@ -234,10 +249,11 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv, if (argc) usage_with_options(builtin_multi_pack_index_expire_usage, options); + source = handle_object_dir_option(the_repository); FREE_AND_NULL(options); - return expire_midx_packs(the_repository, opts.object_dir, opts.flags); + return expire_midx_packs(source, opts.flags); } static int cmd_multi_pack_index_repack(int argc, const char **argv, @@ -252,6 +268,7 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv, N_("force progress reporting"), MIDX_PROGRESS), OPT_END(), }; + struct odb_source *source; options = add_common_options(builtin_multi_pack_index_repack_options); @@ -266,11 +283,11 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv, if (argc) usage_with_options(builtin_multi_pack_index_repack_usage, options); + source = handle_object_dir_option(the_repository); FREE_AND_NULL(options); - return midx_repack(the_repository, opts.object_dir, - (size_t)opts.batch_size, opts.flags); + return midx_repack(source, (size_t)opts.batch_size, opts.flags); } int cmd_multi_pack_index(int argc, diff --git a/builtin/notes.c b/builtin/notes.c index d2252cf534..9af602bdd7 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -229,7 +229,8 @@ static void prepare_note_data(const struct object_id *object, struct note_data * static void write_note_data(struct note_data *d, struct object_id *oid) { - if (write_object_file(d->buf.buf, d->buf.len, OBJ_BLOB, oid)) { + if (odb_write_object(the_repository->objects, d->buf.buf, + d->buf.len, OBJ_BLOB, oid)) { int status = die_message(_("unable to write note object")); if (d->edit_path) @@ -375,18 +376,19 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd) while (strbuf_getline_lf(&buf, stdin) != EOF) { struct object_id from_obj, to_obj; - struct strbuf **split; + struct string_list split = STRING_LIST_INIT_NODUP; int err; - split = strbuf_split(&buf, ' '); - if (!split[0] || !split[1]) + string_list_split_in_place_f(&split, buf.buf, " ", -1, + STRING_LIST_SPLIT_TRIM); + if (split.nr < 2) die(_("malformed input line: '%s'."), buf.buf); - strbuf_rtrim(split[0]); - strbuf_rtrim(split[1]); - if (repo_get_oid(the_repository, split[0]->buf, &from_obj)) - die(_("failed to resolve '%s' as a valid ref."), split[0]->buf); - if (repo_get_oid(the_repository, split[1]->buf, &to_obj)) - die(_("failed to resolve '%s' as a valid ref."), split[1]->buf); + if (repo_get_oid(the_repository, split.items[0].string, &from_obj)) + die(_("failed to resolve '%s' as a valid ref."), + split.items[0].string); + if (repo_get_oid(the_repository, split.items[1].string, &to_obj)) + die(_("failed to resolve '%s' as a valid ref."), + split.items[1].string); if (rewrite_cmd) err = copy_note_for_rewrite(c, &from_obj, &to_obj); @@ -396,11 +398,11 @@ static int notes_copy_from_stdin(int force, const char *rewrite_cmd) if (err) { error(_("failed to copy notes from '%s' to '%s'"), - split[0]->buf, split[1]->buf); + split.items[0].string, split.items[1].string); ret = 1; } - strbuf_list_free(split); + string_list_clear(&split, 0); } if (!rewrite_cmd) { diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 949baf1520..1494afcf3d 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1455,7 +1455,7 @@ static void write_pack_file(void) strbuf_setlen(&tmpname, tmpname_len); } - rename_tmp_packfile_idx(&tmpname, &idx_tmp_name); + rename_tmp_packfile_idx(the_repository, &tmpname, &idx_tmp_name); free(idx_tmp_name); strbuf_release(&tmpname); @@ -1706,11 +1706,19 @@ static int want_object_in_pack_mtime(const struct object_id *oid, uint32_t found_mtime) { int want; + struct odb_source *source; struct list_head *pos; - struct multi_pack_index *m; - if (!exclude && local && has_loose_object_nonlocal(oid)) - return 0; + if (!exclude && local) { + /* + * Note that we start iterating at `sources->next` so that we + * skip the local object source. + */ + struct odb_source *source = the_repository->objects->sources->next; + for (; source; source = source->next) + if (has_loose_object(source, oid)) + return 0; + } /* * If we already know the pack object lives in, start checks from that @@ -1727,9 +1735,13 @@ static int want_object_in_pack_mtime(const struct object_id *oid, *found_offset = 0; } - for (m = get_multi_pack_index(the_repository); m; m = m->next) { + odb_prepare_alternates(the_repository->objects); + + for (source = the_repository->objects->sources; source; source = source->next) { + struct multi_pack_index *m = get_multi_pack_index(source); struct pack_entry e; - if (fill_midx_entry(the_repository, oid, &e, m)) { + + if (m && fill_midx_entry(m, oid, &e)) { want = want_object_in_pack_one(e.p, oid, exclude, found_pack, found_offset, found_mtime); if (want != -1) return want; @@ -3962,7 +3974,14 @@ static void add_cruft_object_entry(const struct object_id *oid, enum object_type } else { if (!want_object_in_pack_mtime(oid, 0, &pack, &offset, mtime)) return; - if (!pack && type == OBJ_BLOB && !has_loose_object(oid)) { + if (!pack && type == OBJ_BLOB) { + struct odb_source *source = the_repository->objects->sources; + int found = 0; + + for (; !found && source; source = source->next) + if (has_loose_object(source, oid)) + found = 1; + /* * If a traversed tree has a missing blob then we want * to avoid adding that missing object to our pack. @@ -3976,7 +3995,8 @@ static void add_cruft_object_entry(const struct object_id *oid, enum object_type * limited to "ensure non-tip blobs which don't exist in * packs do exist via loose objects". Confused? */ - return; + if (!found) + return; } entry = create_object_entry(oid, type, pack_name_hash_fn(name), @@ -4364,7 +4384,7 @@ static int add_loose_object(const struct object_id *oid, const char *path, */ static void add_unreachable_loose_objects(struct rev_info *revs) { - for_each_loose_file_in_objdir(repo_get_object_directory(the_repository), + for_each_loose_file_in_source(the_repository->objects->sources, add_loose_object, NULL, NULL, revs); } @@ -4433,7 +4453,8 @@ static void loosen_unused_packed_objects(void) if (!packlist_find(&to_pack, &oid) && !has_sha1_pack_kept_or_nonlocal(&oid) && !loosened_object_can_be_discarded(&oid, p->mtime)) { - if (force_object_loose(&oid, p->mtime)) + if (force_object_loose(the_repository->objects->sources, + &oid, p->mtime)) die(_("unable to force loose object")); loosened_objects_nr++; } diff --git a/builtin/prune.c b/builtin/prune.c index d1c0ee1419..55635a891f 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -198,7 +198,7 @@ int cmd_prune(int argc, revs.exclude_promisor_objects = 1; } - for_each_loose_file_in_objdir(repo_get_object_directory(repo), + for_each_loose_file_in_source(repo->objects->sources, prune_object, prune_cruft, prune_subdir, &revs); prune_packed_objects(show_only ? PRUNE_PACKED_DRY_RUN : 0); diff --git a/builtin/pull.c b/builtin/pull.c index e6d3535c3b..5ebd529620 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -91,7 +91,8 @@ static char *opt_ff; static const char *opt_verify_signatures; static const char *opt_verify; static int opt_autostash = -1; -static int config_autostash; +static int config_rebase_autostash; +static int config_pull_autostash = -1; static int check_trust_level = 1; static struct strvec opt_strategies = STRVEC_INIT; static struct strvec opt_strategy_opts = STRVEC_INIT; @@ -368,7 +369,18 @@ static int git_pull_config(const char *var, const char *value, const struct config_context *ctx, void *cb) { if (!strcmp(var, "rebase.autostash")) { - config_autostash = git_config_bool(var, value); + /* + * run_rebase() also reads this option. The reason we handle it here is + * that when pull.rebase is true, a fast-forward may occur without + * invoking run_rebase(). We need to ensure that autostash is set even + * in the fast-forward case. + * + * run_merge() handles merge.autostash, so we don't handle it here. + */ + config_rebase_autostash = git_config_bool(var, value); + return 0; + } else if (!strcmp(var, "pull.autostash")) { + config_pull_autostash = git_config_bool(var, value); return 0; } else if (!strcmp(var, "submodule.recurse")) { recurse_submodules = git_config_bool(var, value) ? @@ -1007,6 +1019,8 @@ int cmd_pull(int argc, } argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0); + if (opt_autostash == -1) + opt_autostash = config_pull_autostash; if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT) recurse_submodules = recurse_submodules_cli; @@ -1053,7 +1067,7 @@ int cmd_pull(int argc, if (opt_rebase) { if (opt_autostash == -1) - opt_autostash = config_autostash; + opt_autostash = config_rebase_autostash; if (is_null_oid(&orig_head) && !is_index_unborn(the_repository->index)) die(_("Updating an unborn branch with changes added to the index.")); diff --git a/builtin/rebase.c b/builtin/rebase.c index 962917ec48..67c0352bf8 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -293,15 +293,6 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) &revisions, &shortrevisions)) goto cleanup; - if (init_basic_state(&replay, - opts->head_name ? opts->head_name : "detached HEAD", - opts->onto, &opts->orig_head->object.oid)) - goto cleanup; - - if (!opts->upstream && opts->squash_onto) - write_file(path_squash_onto(), "%s\n", - oid_to_hex(opts->squash_onto)); - strvec_pushl(&make_script_args, "", revisions, NULL); if (opts->restrict_revision) strvec_pushf(&make_script_args, "^%s", @@ -310,21 +301,30 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) ret = sequencer_make_script(the_repository, &todo_list.buf, make_script_args.nr, make_script_args.v, flags); - - if (ret) + if (ret) { error(_("could not generate todo list")); - else { - discard_index(the_repository->index); - if (todo_list_parse_insn_buffer(the_repository, &replay, - todo_list.buf.buf, &todo_list)) - BUG("unusable todo list"); - - ret = complete_action(the_repository, &replay, flags, - shortrevisions, opts->onto_name, opts->onto, - &opts->orig_head->object.oid, &opts->exec, - opts->autosquash, opts->update_refs, &todo_list); + goto cleanup; } + if (init_basic_state(&replay, + opts->head_name ? opts->head_name : "detached HEAD", + opts->onto, &opts->orig_head->object.oid)) + goto cleanup; + + if (!opts->upstream && opts->squash_onto) + write_file(path_squash_onto(), "%s\n", + oid_to_hex(opts->squash_onto)); + + discard_index(the_repository->index); + if (todo_list_parse_insn_buffer(the_repository, &replay, + todo_list.buf.buf, &todo_list)) + BUG("unusable todo list"); + + ret = complete_action(the_repository, &replay, flags, + shortrevisions, opts->onto_name, opts->onto, + &opts->orig_head->object.oid, &opts->exec, + opts->autosquash, opts->update_refs, &todo_list); + cleanup: replay_opts_release(&replay); free(revisions); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 82d516a42c..1113137a6f 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -760,8 +760,8 @@ static void prepare_push_cert_sha1(struct child_process *proc) int bogs /* beginning_of_gpg_sig */; already_done = 1; - if (write_object_file(push_cert.buf, push_cert.len, OBJ_BLOB, - &push_cert_oid)) + if (odb_write_object(the_repository->objects, push_cert.buf, + push_cert.len, OBJ_BLOB, &push_cert_oid)) oidclr(&push_cert_oid, the_repository->hash_algo); memset(&sigcheck, '\0', sizeof(sigcheck)); diff --git a/builtin/reflog.c b/builtin/reflog.c index 4312f3d34c..c8f6b93d60 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -3,6 +3,8 @@ #include "builtin.h" #include "config.h" #include "gettext.h" +#include "hex.h" +#include "odb.h" #include "revision.h" #include "reachable.h" #include "wildmatch.h" @@ -17,21 +19,24 @@ #define BUILTIN_REFLOG_LIST_USAGE \ N_("git reflog list") -#define BUILTIN_REFLOG_EXPIRE_USAGE \ - N_("git reflog expire [--expire=<time>] [--expire-unreachable=<time>]\n" \ - " [--rewrite] [--updateref] [--stale-fix]\n" \ - " [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]") +#define BUILTIN_REFLOG_EXISTS_USAGE \ + N_("git reflog exists <ref>") + +#define BUILTIN_REFLOG_WRITE_USAGE \ + N_("git reflog write <ref> <old-oid> <new-oid> <message>") #define BUILTIN_REFLOG_DELETE_USAGE \ N_("git reflog delete [--rewrite] [--updateref]\n" \ " [--dry-run | -n] [--verbose] <ref>@{<specifier>}...") -#define BUILTIN_REFLOG_EXISTS_USAGE \ - N_("git reflog exists <ref>") - #define BUILTIN_REFLOG_DROP_USAGE \ N_("git reflog drop [--all [--single-worktree] | <refs>...]") +#define BUILTIN_REFLOG_EXPIRE_USAGE \ + N_("git reflog expire [--expire=<time>] [--expire-unreachable=<time>]\n" \ + " [--rewrite] [--updateref] [--stale-fix]\n" \ + " [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]") + static const char *const reflog_show_usage[] = { BUILTIN_REFLOG_SHOW_USAGE, NULL, @@ -42,9 +47,14 @@ static const char *const reflog_list_usage[] = { NULL, }; -static const char *const reflog_expire_usage[] = { - BUILTIN_REFLOG_EXPIRE_USAGE, - NULL +static const char *const reflog_exists_usage[] = { + BUILTIN_REFLOG_EXISTS_USAGE, + NULL, +}; + +static const char *const reflog_write_usage[] = { + BUILTIN_REFLOG_WRITE_USAGE, + NULL, }; static const char *const reflog_delete_usage[] = { @@ -52,23 +62,24 @@ static const char *const reflog_delete_usage[] = { NULL }; -static const char *const reflog_exists_usage[] = { - BUILTIN_REFLOG_EXISTS_USAGE, - NULL, -}; - static const char *const reflog_drop_usage[] = { BUILTIN_REFLOG_DROP_USAGE, NULL, }; +static const char *const reflog_expire_usage[] = { + BUILTIN_REFLOG_EXPIRE_USAGE, + NULL +}; + static const char *const reflog_usage[] = { BUILTIN_REFLOG_SHOW_USAGE, BUILTIN_REFLOG_LIST_USAGE, - BUILTIN_REFLOG_EXPIRE_USAGE, + BUILTIN_REFLOG_EXISTS_USAGE, + BUILTIN_REFLOG_WRITE_USAGE, BUILTIN_REFLOG_DELETE_USAGE, BUILTIN_REFLOG_DROP_USAGE, - BUILTIN_REFLOG_EXISTS_USAGE, + BUILTIN_REFLOG_EXPIRE_USAGE, NULL }; @@ -283,6 +294,9 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix, &cb); free(ref); } + + reflog_clear_expire_config(&opts); + return status; } @@ -392,6 +406,59 @@ static int cmd_reflog_drop(int argc, const char **argv, const char *prefix, return ret; } +static int cmd_reflog_write(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + const struct option options[] = { + OPT_END() + }; + struct object_id old_oid, new_oid; + struct strbuf err = STRBUF_INIT; + struct ref_transaction *tx; + const char *ref, *message; + int ret; + + argc = parse_options(argc, argv, prefix, options, reflog_write_usage, 0); + if (argc != 4) + usage_with_options(reflog_write_usage, options); + + ref = argv[0]; + if (!is_root_ref(ref) && check_refname_format(ref, 0)) + die(_("invalid reference name: %s"), ref); + + ret = get_oid_hex_algop(argv[1], &old_oid, repo->hash_algo); + if (ret) + die(_("invalid old object ID: '%s'"), argv[1]); + if (!is_null_oid(&old_oid) && !odb_has_object(repo->objects, &old_oid, 0)) + die(_("old object '%s' does not exist"), argv[1]); + + ret = get_oid_hex_algop(argv[2], &new_oid, repo->hash_algo); + if (ret) + die(_("invalid new object ID: '%s'"), argv[2]); + if (!is_null_oid(&new_oid) && !odb_has_object(repo->objects, &new_oid, 0)) + die(_("new object '%s' does not exist"), argv[2]); + + message = argv[3]; + + tx = ref_store_transaction_begin(get_main_ref_store(repo), 0, &err); + if (!tx) + die(_("cannot start transaction: %s"), err.buf); + + ret = ref_transaction_update_reflog(tx, ref, &new_oid, &old_oid, + git_committer_info(0), + message, 0, &err); + if (ret) + die(_("cannot queue reflog update: %s"), err.buf); + + ret = ref_transaction_commit(tx, &err); + if (ret) + die(_("cannot commit reflog update: %s"), err.buf); + + ref_transaction_free(tx); + strbuf_release(&err); + return 0; +} + /* * main "reflog" */ @@ -404,10 +471,11 @@ int cmd_reflog(int argc, struct option options[] = { OPT_SUBCOMMAND("show", &fn, cmd_reflog_show), OPT_SUBCOMMAND("list", &fn, cmd_reflog_list), - OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire), - OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete), OPT_SUBCOMMAND("exists", &fn, cmd_reflog_exists), + OPT_SUBCOMMAND("write", &fn, cmd_reflog_write), + OPT_SUBCOMMAND("delete", &fn, cmd_reflog_delete), OPT_SUBCOMMAND("drop", &fn, cmd_reflog_drop), + OPT_SUBCOMMAND("expire", &fn, cmd_reflog_expire), OPT_END() }; diff --git a/builtin/refs.c b/builtin/refs.c index c7ad0a2963..91548783b7 100644 --- a/builtin/refs.c +++ b/builtin/refs.c @@ -6,6 +6,8 @@ #include "refs.h" #include "strbuf.h" #include "worktree.h" +#include "for-each-ref.h" +#include "refs/refs-internal.h" #define REFS_MIGRATE_USAGE \ N_("git refs migrate --ref-format=<format> [--no-reflog] [--dry-run]") @@ -13,6 +15,9 @@ #define REFS_VERIFY_USAGE \ N_("git refs verify [--strict] [--verbose]") +#define REFS_EXISTS_USAGE \ + N_("git refs exists <ref>") + static int cmd_refs_migrate(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) { @@ -101,6 +106,59 @@ static int cmd_refs_verify(int argc, const char **argv, const char *prefix, return ret; } +static int cmd_refs_list(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + static char const * const refs_list_usage[] = { + N_("git refs list " COMMON_USAGE_FOR_EACH_REF), + NULL + }; + + return for_each_ref_core(argc, argv, prefix, repo, refs_list_usage); +} + +static int cmd_refs_exists(int argc, const char **argv, const char *prefix, + struct repository *repo UNUSED) +{ + struct strbuf unused_referent = STRBUF_INIT; + struct object_id unused_oid; + unsigned int unused_type; + int failure_errno = 0; + const char *ref; + int ret = 0; + const char * const exists_usage[] = { + REFS_EXISTS_USAGE, + NULL, + }; + struct option options[] = { + OPT_END(), + }; + + argc = parse_options(argc, argv, prefix, options, exists_usage, 0); + if (argc != 1) + die(_("'git refs exists' requires a reference")); + + ref = *argv++; + if (refs_read_raw_ref(get_main_ref_store(the_repository), ref, + &unused_oid, &unused_referent, &unused_type, + &failure_errno)) { + if (failure_errno == ENOENT || failure_errno == EISDIR) { + error(_("reference does not exist")); + ret = 2; + } else { + errno = failure_errno; + error_errno(_("failed to look up reference")); + ret = 1; + } + + goto out; + } + +out: + strbuf_release(&unused_referent); + return ret; +} + int cmd_refs(int argc, const char **argv, const char *prefix, @@ -109,12 +167,16 @@ int cmd_refs(int argc, const char * const refs_usage[] = { REFS_MIGRATE_USAGE, REFS_VERIFY_USAGE, + "git refs list " COMMON_USAGE_FOR_EACH_REF, + REFS_EXISTS_USAGE, NULL, }; parse_opt_subcommand_fn *fn = NULL; struct option opts[] = { OPT_SUBCOMMAND("migrate", &fn, cmd_refs_migrate), OPT_SUBCOMMAND("verify", &fn, cmd_refs_verify), + OPT_SUBCOMMAND("list", &fn, cmd_refs_list), + OPT_SUBCOMMAND("exists", &fn, cmd_refs_exists), OPT_END(), }; diff --git a/builtin/remote.c b/builtin/remote.c index 43a122740a..8a7ed4299a 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -1,9 +1,11 @@ #define USE_THE_REPOSITORY_VARIABLE -#define DISABLE_SIGN_COMPARE_WARNINGS #include "builtin.h" +#include "advice.h" #include "config.h" +#include "date.h" #include "gettext.h" +#include "ident.h" #include "parse-options.h" #include "path.h" #include "transport.h" @@ -182,7 +184,6 @@ static int add(int argc, const char **argv, const char *prefix, struct remote *remote; struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT; const char *name, *url; - int i; int result = 0; struct option options[] = { @@ -233,7 +234,7 @@ static int add(int argc, const char **argv, const char *prefix, strbuf_addf(&buf, "remote.%s.fetch", name); if (track.nr == 0) string_list_append(&track, "*"); - for (i = 0; i < track.nr; i++) { + for (size_t i = 0; i < track.nr; i++) { add_branch(buf.buf, track.items[i].string, name, mirror, &buf2); } @@ -612,53 +613,169 @@ static int add_branch_for_removal(const char *refname, struct rename_info { const char *old_name; const char *new_name; - struct string_list *remote_branches; - uint32_t symrefs_nr; + struct ref_transaction *transaction; + struct progress *progress; + struct strbuf *err; + uint32_t progress_nr; + uint64_t index; }; -static int read_remote_branches(const char *refname, const char *referent UNUSED, - const struct object_id *oid UNUSED, - int flags UNUSED, void *cb_data) +static void compute_renamed_ref(struct rename_info *rename, + const char *refname, + struct strbuf *out) +{ + strbuf_reset(out); + strbuf_addstr(out, refname); + strbuf_splice(out, strlen("refs/remotes/"), strlen(rename->old_name), + rename->new_name, strlen(rename->new_name)); +} + +static int rename_one_reflog_entry(const char *old_refname, + struct object_id *old_oid, + struct object_id *new_oid, + const char *committer, + timestamp_t timestamp, int tz, + const char *msg, void *cb_data) { struct rename_info *rename = cb_data; - struct strbuf buf = STRBUF_INIT; - struct string_list_item *item; - int flag; - const char *symref; - - strbuf_addf(&buf, "refs/remotes/%s/", rename->old_name); - if (starts_with(refname, buf.buf)) { - item = string_list_append(rename->remote_branches, refname); - symref = refs_resolve_ref_unsafe(get_main_ref_store(the_repository), - refname, RESOLVE_REF_READING, - NULL, &flag); - if (symref && (flag & REF_ISSYMREF)) { - item->util = xstrdup(symref); - rename->symrefs_nr++; - } else { - item->util = NULL; - } + struct strbuf new_refname = STRBUF_INIT; + struct strbuf identity = STRBUF_INIT; + struct strbuf name = STRBUF_INIT; + struct strbuf mail = STRBUF_INIT; + struct ident_split ident; + const char *date; + int error; + + compute_renamed_ref(rename, old_refname, &new_refname); + + if (split_ident_line(&ident, committer, strlen(committer)) < 0) { + error = -1; + goto out; } - strbuf_release(&buf); - return 0; + strbuf_add(&name, ident.name_begin, ident.name_end - ident.name_begin); + strbuf_add(&mail, ident.mail_begin, ident.mail_end - ident.mail_begin); + + date = show_date(timestamp, tz, DATE_MODE(NORMAL)); + strbuf_addstr(&identity, fmt_ident(name.buf, mail.buf, + WANT_BLANK_IDENT, date, 0)); + + error = ref_transaction_update_reflog(rename->transaction, new_refname.buf, + new_oid, old_oid, identity.buf, msg, + rename->index++, rename->err); + +out: + strbuf_release(&new_refname); + strbuf_release(&identity); + strbuf_release(&name); + strbuf_release(&mail); + return error; +} + +static int rename_one_reflog(const char *old_refname, + const struct object_id *old_oid, + struct rename_info *rename) +{ + struct strbuf new_refname = STRBUF_INIT; + struct strbuf message = STRBUF_INIT; + int error; + + if (!refs_reflog_exists(get_main_ref_store(the_repository), old_refname)) + return 0; + + error = refs_for_each_reflog_ent(get_main_ref_store(the_repository), + old_refname, rename_one_reflog_entry, rename); + if (error < 0) + goto out; + + compute_renamed_ref(rename, old_refname, &new_refname); + + /* + * Manually write the reflog entry for the now-renamed ref. We cannot + * rely on `rename_one_ref()` to do this for us as that would screw + * over order in which reflog entries are being written. + * + * Furthermore, we only append the entry in case the reference + * resolves. Missing references shouldn't have reflogs anyway. + */ + strbuf_addf(&message, "remote: renamed %s to %s", old_refname, + new_refname.buf); + + error = ref_transaction_update_reflog(rename->transaction, new_refname.buf, + old_oid, old_oid, git_committer_info(0), + message.buf, rename->index++, rename->err); + if (error < 0) + return error; + +out: + strbuf_release(&new_refname); + strbuf_release(&message); + return error; +} + +static int rename_one_ref(const char *old_refname, const char *referent, + const struct object_id *oid, + int flags, void *cb_data) +{ + struct strbuf new_referent = STRBUF_INIT; + struct strbuf new_refname = STRBUF_INIT; + struct rename_info *rename = cb_data; + int error; + + compute_renamed_ref(rename, old_refname, &new_refname); + + if (flags & REF_ISSYMREF) { + /* + * Stupidly enough `referent` is not pointing to the immediate + * target of a symref, but it's the recursively resolved value. + * So symrefs pointing to symrefs would be misresolved, and + * unborn symrefs don't have any value for the `referent` at all. + */ + referent = refs_resolve_ref_unsafe(get_main_ref_store(the_repository), + old_refname, RESOLVE_REF_NO_RECURSE, + NULL, NULL); + compute_renamed_ref(rename, referent, &new_referent); + oid = NULL; + } + + error = ref_transaction_delete(rename->transaction, old_refname, + oid, referent, REF_NO_DEREF, NULL, rename->err); + if (error < 0) + goto out; + + error = ref_transaction_update(rename->transaction, new_refname.buf, oid, null_oid(the_hash_algo), + (flags & REF_ISSYMREF) ? new_referent.buf : NULL, NULL, + REF_SKIP_CREATE_REFLOG | REF_NO_DEREF | REF_SKIP_OID_VERIFICATION, + NULL, rename->err); + if (error < 0) + goto out; + + error = rename_one_reflog(old_refname, oid, rename); + if (error < 0) + goto out; + + display_progress(rename->progress, ++rename->progress_nr); + +out: + strbuf_release(&new_referent); + strbuf_release(&new_refname); + return error; } static int migrate_file(struct remote *remote) { struct strbuf buf = STRBUF_INIT; - int i; strbuf_addf(&buf, "remote.%s.url", remote->name); - for (i = 0; i < remote->url.nr; i++) + for (size_t i = 0; i < remote->url.nr; i++) repo_config_set_multivar(the_repository, buf.buf, remote->url.v[i], "^$", 0); strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.push", remote->name); - for (i = 0; i < remote->push.nr; i++) + for (int i = 0; i < remote->push.nr; i++) repo_config_set_multivar(the_repository, buf.buf, remote->push.items[i].raw, "^$", 0); strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.fetch", remote->name); - for (i = 0; i < remote->fetch.nr; i++) + for (int i = 0; i < remote->fetch.nr; i++) repo_config_set_multivar(the_repository, buf.buf, remote->fetch.items[i].raw, "^$", 0); #ifndef WITH_BREAKING_CHANGES if (remote->origin == REMOTE_REMOTES) @@ -730,6 +847,14 @@ static void handle_push_default(const char* old_name, const char* new_name) strbuf_release(&push_default.origin); } +static const char conflicting_remote_refs_advice[] = N_( + "The remote you are trying to rename has conflicting references in the\n" + "new target refspec. This is most likely caused by you trying to nest\n" + "a remote into itself, e.g. by renaming 'parent' into 'parent/child'\n" + "or by unnesting a remote, e.g. the other way round.\n" + "\n" + "If that is the case, you can address this by first renaming the\n" + "remote to a different name.\n"); static int mv(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) @@ -741,11 +866,11 @@ static int mv(int argc, const char **argv, const char *prefix, }; struct remote *oldremote, *newremote; struct strbuf buf = STRBUF_INIT, buf2 = STRBUF_INIT, buf3 = STRBUF_INIT, - old_remote_context = STRBUF_INIT; - struct string_list remote_branches = STRING_LIST_INIT_DUP; - struct rename_info rename; - int i, refs_renamed_nr = 0, refspec_updated = 0; - struct progress *progress = NULL; + old_remote_context = STRBUF_INIT, err = STRBUF_INIT; + struct rename_info rename = { + .err = &err, + }; + int refspecs_need_update = 0; int result = 0; argc = parse_options(argc, argv, prefix, options, @@ -756,8 +881,6 @@ static int mv(int argc, const char **argv, const char *prefix, rename.old_name = argv[0]; rename.new_name = argv[1]; - rename.remote_branches = &remote_branches; - rename.symrefs_nr = 0; oldremote = remote_get(rename.old_name); if (!remote_is_configured(oldremote, 1)) { @@ -785,19 +908,50 @@ static int mv(int argc, const char **argv, const char *prefix, goto out; } + strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old_name); + + for (int i = 0; i < oldremote->fetch.nr && !refspecs_need_update; i++) + refspecs_need_update = !!strstr(oldremote->fetch.items[i].raw, + old_remote_context.buf); + + if (refspecs_need_update) { + rename.transaction = ref_store_transaction_begin(get_main_ref_store(the_repository), + 0, &err); + if (!rename.transaction) + goto out; + + if (show_progress) + rename.progress = start_delayed_progress(the_repository, + _("Renaming remote references"), 0); + + strbuf_reset(&buf); + strbuf_addf(&buf, "refs/remotes/%s/", rename.old_name); + + result = refs_for_each_rawref_in(get_main_ref_store(the_repository), buf.buf, + rename_one_ref, &rename); + if (result < 0) + die(_("queueing remote ref renames failed: %s"), rename.err->buf); + + result = ref_transaction_prepare(rename.transaction, &err); + if (result < 0) { + error("renaming remote references failed: %s", err.buf); + if (result == REF_TRANSACTION_ERROR_NAME_CONFLICT) + advise(conflicting_remote_refs_advice); + die(NULL); + } + } + if (oldremote->fetch.nr) { strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.fetch", rename.new_name); repo_config_set_multivar(the_repository, buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE); - strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old_name); - for (i = 0; i < oldremote->fetch.nr; i++) { + for (int i = 0; i < oldremote->fetch.nr; i++) { char *ptr; strbuf_reset(&buf2); strbuf_addstr(&buf2, oldremote->fetch.items[i].raw); ptr = strstr(buf2.buf, old_remote_context.buf); if (ptr) { - refspec_updated = 1; strbuf_splice(&buf2, ptr-buf2.buf + strlen(":refs/remotes/"), strlen(rename.old_name), rename.new_name, @@ -813,7 +967,7 @@ static int mv(int argc, const char **argv, const char *prefix, } read_branches(); - for (i = 0; i < branch_list.nr; i++) { + for (size_t i = 0; i < branch_list.nr; i++) { struct string_list_item *item = branch_list.items + i; struct branch_info *info = item->util; if (info->remote_name && !strcmp(info->remote_name, rename.old_name)) { @@ -828,83 +982,23 @@ static int mv(int argc, const char **argv, const char *prefix, } } - if (!refspec_updated) - goto out; + if (refspecs_need_update) { + result = ref_transaction_commit(rename.transaction, &err); + if (result < 0) + die(_("renaming remote refs failed: %s"), rename.err->buf); - /* - * First remove symrefs, then rename the rest, finally create - * the new symrefs. - */ - refs_for_each_ref(get_main_ref_store(the_repository), - read_remote_branches, &rename); - if (show_progress) { - /* - * Count symrefs twice, since "renaming" them is done by - * deleting and recreating them in two separate passes. - */ - progress = start_progress(the_repository, - _("Renaming remote references"), - rename.remote_branches->nr + rename.symrefs_nr); - } - for (i = 0; i < remote_branches.nr; i++) { - struct string_list_item *item = remote_branches.items + i; - struct strbuf referent = STRBUF_INIT; - - if (refs_read_symbolic_ref(get_main_ref_store(the_repository), item->string, - &referent)) - continue; - if (refs_delete_ref(get_main_ref_store(the_repository), NULL, item->string, NULL, REF_NO_DEREF)) - die(_("deleting '%s' failed"), item->string); + stop_progress(&rename.progress); - strbuf_release(&referent); - display_progress(progress, ++refs_renamed_nr); + handle_push_default(rename.old_name, rename.new_name); } - for (i = 0; i < remote_branches.nr; i++) { - struct string_list_item *item = remote_branches.items + i; - - if (item->util) - continue; - strbuf_reset(&buf); - strbuf_addstr(&buf, item->string); - strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old_name), - rename.new_name, strlen(rename.new_name)); - strbuf_reset(&buf2); - strbuf_addf(&buf2, "remote: renamed %s to %s", - item->string, buf.buf); - if (refs_rename_ref(get_main_ref_store(the_repository), item->string, buf.buf, buf2.buf)) - die(_("renaming '%s' failed"), item->string); - display_progress(progress, ++refs_renamed_nr); - } - for (i = 0; i < remote_branches.nr; i++) { - struct string_list_item *item = remote_branches.items + i; - - if (!item->util) - continue; - strbuf_reset(&buf); - strbuf_addstr(&buf, item->string); - strbuf_splice(&buf, strlen("refs/remotes/"), strlen(rename.old_name), - rename.new_name, strlen(rename.new_name)); - strbuf_reset(&buf2); - strbuf_addstr(&buf2, item->util); - strbuf_splice(&buf2, strlen("refs/remotes/"), strlen(rename.old_name), - rename.new_name, strlen(rename.new_name)); - strbuf_reset(&buf3); - strbuf_addf(&buf3, "remote: renamed %s to %s", - item->string, buf.buf); - if (refs_update_symref(get_main_ref_store(the_repository), buf.buf, buf2.buf, buf3.buf)) - die(_("creating '%s' failed"), buf.buf); - display_progress(progress, ++refs_renamed_nr); - } - stop_progress(&progress); - - handle_push_default(rename.old_name, rename.new_name); out: - string_list_clear(&remote_branches, 1); + ref_transaction_free(rename.transaction); strbuf_release(&old_remote_context); strbuf_release(&buf); strbuf_release(&buf2); strbuf_release(&buf3); + strbuf_release(&err); return result; } @@ -920,7 +1014,7 @@ static int rm(int argc, const char **argv, const char *prefix, struct string_list branches = STRING_LIST_INIT_DUP; struct string_list skipped = STRING_LIST_INIT_DUP; struct branches_for_remote cb_data; - int i, result; + int result; memset(&cb_data, 0, sizeof(cb_data)); cb_data.branches = &branches; @@ -942,7 +1036,7 @@ static int rm(int argc, const char **argv, const char *prefix, for_each_remote(add_known_remote, &known_remotes); read_branches(); - for (i = 0; i < branch_list.nr; i++) { + for (size_t i = 0; i < branch_list.nr; i++) { struct string_list_item *item = branch_list.items + i; struct branch_info *info = item->util; if (info->remote_name && !strcmp(info->remote_name, remote->name)) { @@ -988,7 +1082,7 @@ static int rm(int argc, const char **argv, const char *prefix, "Note: Some branches outside the refs/remotes/ hierarchy were not removed;\n" "to delete them, use:", skipped.nr)); - for (i = 0; i < skipped.nr; i++) + for (size_t i = 0; i < skipped.nr; i++) fprintf(stderr, " git branch -d %s\n", skipped.items[i].string); } @@ -1166,7 +1260,6 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data) struct branch_info *branch_info = item->util; struct string_list *merge = &branch_info->merge; int width = show_info->width + 4; - int i; if (branch_info->rebase >= REBASE_TRUE && branch_info->merge.nr > 1) { error(_("invalid branch.%s.merge; cannot rebase onto > 1 branch"), @@ -1192,7 +1285,7 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data) } else { printf_ln(_("merges with remote %s"), merge->items[0].string); } - for (i = 1; i < merge->nr; i++) + for (size_t i = 1; i < merge->nr; i++) printf(_("%-*s and with remote %s\n"), width, "", merge->items[i].string); @@ -1277,7 +1370,6 @@ static int get_one_entry(struct remote *remote, void *priv) struct string_list *list = priv; struct strbuf remote_info_buf = STRBUF_INIT; struct strvec *url; - int i; if (remote->url.nr > 0) { struct strbuf promisor_config = STRBUF_INIT; @@ -1294,8 +1386,7 @@ static int get_one_entry(struct remote *remote, void *priv) } else string_list_append(list, remote->name)->util = NULL; url = push_url_of_remote(remote); - for (i = 0; i < url->nr; i++) - { + for (size_t i = 0; i < url->nr; i++) { strbuf_addf(&remote_info_buf, "%s (push)", url->v[i]); string_list_append(list, remote->name)->util = strbuf_detach(&remote_info_buf, NULL); @@ -1312,10 +1403,8 @@ static int show_all(void) result = for_each_remote(get_one_entry, &list); if (!result) { - int i; - string_list_sort(&list); - for (i = 0; i < list.nr; i++) { + for (size_t i = 0; i < list.nr; i++) { struct string_list_item *item = list.items + i; if (verbose) printf("%s\t%s\n", item->string, @@ -1352,7 +1441,7 @@ static int show(int argc, const char **argv, const char *prefix, query_flag = (GET_REF_STATES | GET_HEAD_NAMES | GET_PUSH_REF_STATES); for (; argc; argc--, argv++) { - int i; + size_t i; struct strvec *url; get_remote_ref_states(*argv, &info.states, query_flag); @@ -1458,7 +1547,7 @@ static void report_set_head_auto(const char *remote, const char *head_name, static int set_head(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) { - int i, opt_a = 0, opt_d = 0, result = 0, was_detached; + int opt_a = 0, opt_d = 0, result = 0, was_detached; struct strbuf b_head = STRBUF_INIT, b_remote_head = STRBUF_INIT, b_local_head = STRBUF_INIT; char *head_name = NULL; @@ -1474,10 +1563,13 @@ static int set_head(int argc, const char **argv, const char *prefix, }; argc = parse_options(argc, argv, prefix, options, builtin_remote_sethead_usage, 0); - if (argc) { - strbuf_addf(&b_head, "refs/remotes/%s/HEAD", argv[0]); - remote = remote_get(argv[0]); - } + + /* All modes require at least a remote name. */ + if (!argc) + usage_with_options(builtin_remote_sethead_usage, options); + + strbuf_addf(&b_head, "refs/remotes/%s/HEAD", argv[0]); + remote = remote_get(argv[0]); if (!opt_a && !opt_d && argc == 2) { head_name = xstrdup(argv[1]); @@ -1489,7 +1581,7 @@ static int set_head(int argc, const char **argv, const char *prefix, else if (states.heads.nr > 1) { result |= error(_("Multiple remote HEAD branches. " "Please choose one explicitly with:")); - for (i = 0; i < states.heads.nr; i++) + for (size_t i = 0; i < states.heads.nr; i++) fprintf(stderr, " git remote set-head %s %s\n", argv[0], states.heads.items[i].string); } else @@ -1714,7 +1806,7 @@ static int set_branches(int argc, const char **argv, const char *prefix, static int get_url(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) { - int i, push_mode = 0, all_mode = 0; + int push_mode = 0, all_mode = 0; const char *remotename = NULL; struct remote *remote; struct strvec *url; @@ -1742,7 +1834,7 @@ static int get_url(int argc, const char **argv, const char *prefix, url = push_mode ? push_url_of_remote(remote) : &remote->url; if (all_mode) { - for (i = 0; i < url->nr; i++) + for (size_t i = 0; i < url->nr; i++) printf_ln("%s", url->v[i]); } else { printf_ln("%s", url->v[0]); @@ -1754,7 +1846,7 @@ static int get_url(int argc, const char **argv, const char *prefix, static int set_url(int argc, const char **argv, const char *prefix, struct repository *repo UNUSED) { - int i, push_mode = 0, add_mode = 0, delete_mode = 0; + int push_mode = 0, add_mode = 0, delete_mode = 0; int matches = 0, negative_matches = 0; const char *remotename = NULL; const char *newurl = NULL; @@ -1818,7 +1910,7 @@ static int set_url(int argc, const char **argv, const char *prefix, if (regcomp(&old_regex, oldurl, REG_EXTENDED)) die(_("Invalid old URL pattern: %s"), oldurl); - for (i = 0; i < urlset->nr; i++) + for (size_t i = 0; i < urlset->nr; i++) if (!regexec(&old_regex, urlset->v[i], 0, NULL, 0)) matches++; else diff --git a/builtin/repack.c b/builtin/repack.c index ec5bc83e54..c490a51e91 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -223,9 +223,10 @@ static void mark_packs_for_deletion(struct existing_packs *existing, static void remove_redundant_pack(const char *dir_name, const char *base_name) { struct strbuf buf = STRBUF_INIT; - struct multi_pack_index *m = get_local_multi_pack_index(the_repository); + struct odb_source *source = the_repository->objects->sources; + struct multi_pack_index *m = get_multi_pack_index(source); strbuf_addf(&buf, "%s.pack", base_name); - if (m && midx_contains_pack(m, buf.buf)) + if (m && source->local && midx_contains_pack(m, buf.buf)) clear_midx_file(the_repository); strbuf_insertf(&buf, 0, "%s/", dir_name); unlink_pack_path(buf.buf, 1); @@ -1531,7 +1532,7 @@ int cmd_repack(int argc, * midx_has_unknown_packs() will make the decision for * us. */ - if (!get_local_multi_pack_index(the_repository)) + if (!get_multi_pack_index(the_repository->objects->sources)) midx_must_contain_cruft = 1; } @@ -1614,9 +1615,9 @@ int cmd_repack(int argc, string_list_sort(&names); - if (get_local_multi_pack_index(the_repository)) { + if (get_multi_pack_index(the_repository->objects->sources)) { struct multi_pack_index *m = - get_local_multi_pack_index(the_repository); + get_multi_pack_index(the_repository->objects->sources); ALLOC_ARRAY(midx_pack_names, m->num_packs + m->num_packs_in_base); @@ -1711,7 +1712,7 @@ int cmd_repack(int argc, unsigned flags = 0; if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX_WRITE_INCREMENTAL, 0)) flags |= MIDX_WRITE_INCREMENTAL; - write_midx_file(the_repository, repo_get_object_directory(the_repository), + write_midx_file(the_repository->objects->sources, NULL, NULL, flags); } diff --git a/builtin/replace.c b/builtin/replace.c index 19897ef927..900b560a77 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -489,7 +489,8 @@ static int create_graft(int argc, const char **argv, int force, int gentle) return -1; } - if (write_object_file(buf.buf, buf.len, OBJ_COMMIT, &new_oid)) { + if (odb_write_object(the_repository->objects, buf.buf, + buf.len, OBJ_COMMIT, &new_oid)) { strbuf_release(&buf); return error(_("could not write replacement commit for: '%s'"), old_ref); diff --git a/builtin/repo.c b/builtin/repo.c new file mode 100644 index 0000000000..bbb0966f2d --- /dev/null +++ b/builtin/repo.c @@ -0,0 +1,171 @@ +#define USE_THE_REPOSITORY_VARIABLE + +#include "builtin.h" +#include "environment.h" +#include "parse-options.h" +#include "quote.h" +#include "refs.h" +#include "strbuf.h" +#include "shallow.h" + +static const char *const repo_usage[] = { + "git repo info [--format=(keyvalue|nul)] [-z] [<key>...]", + NULL +}; + +typedef int get_value_fn(struct repository *repo, struct strbuf *buf); + +enum output_format { + FORMAT_KEYVALUE, + FORMAT_NUL_TERMINATED, +}; + +struct field { + const char *key; + get_value_fn *get_value; +}; + +static int get_layout_bare(struct repository *repo UNUSED, struct strbuf *buf) +{ + strbuf_addstr(buf, is_bare_repository() ? "true" : "false"); + return 0; +} + +static int get_layout_shallow(struct repository *repo, struct strbuf *buf) +{ + strbuf_addstr(buf, + is_repository_shallow(repo) ? "true" : "false"); + return 0; +} + +static int get_object_format(struct repository *repo, struct strbuf *buf) +{ + strbuf_addstr(buf, repo->hash_algo->name); + return 0; +} + +static int get_references_format(struct repository *repo, struct strbuf *buf) +{ + strbuf_addstr(buf, + ref_storage_format_to_name(repo->ref_storage_format)); + return 0; +} + +/* repo_info_fields keys must be in lexicographical order */ +static const struct field repo_info_fields[] = { + { "layout.bare", get_layout_bare }, + { "layout.shallow", get_layout_shallow }, + { "object.format", get_object_format }, + { "references.format", get_references_format }, +}; + +static int repo_info_fields_cmp(const void *va, const void *vb) +{ + const struct field *a = va; + const struct field *b = vb; + + return strcmp(a->key, b->key); +} + +static get_value_fn *get_value_fn_for_key(const char *key) +{ + const struct field search_key = { key, NULL }; + const struct field *found = bsearch(&search_key, repo_info_fields, + ARRAY_SIZE(repo_info_fields), + sizeof(*found), + repo_info_fields_cmp); + return found ? found->get_value : NULL; +} + +static int print_fields(int argc, const char **argv, + struct repository *repo, + enum output_format format) +{ + int ret = 0; + struct strbuf valbuf = STRBUF_INIT; + struct strbuf quotbuf = STRBUF_INIT; + + for (int i = 0; i < argc; i++) { + get_value_fn *get_value; + const char *key = argv[i]; + + get_value = get_value_fn_for_key(key); + + if (!get_value) { + ret = error(_("key '%s' not found"), key); + continue; + } + + strbuf_reset(&valbuf); + strbuf_reset("buf); + + get_value(repo, &valbuf); + + switch (format) { + case FORMAT_KEYVALUE: + quote_c_style(valbuf.buf, "buf, NULL, 0); + printf("%s=%s\n", key, quotbuf.buf); + break; + case FORMAT_NUL_TERMINATED: + printf("%s\n%s%c", key, valbuf.buf, '\0'); + break; + default: + BUG("not a valid output format: %d", format); + } + } + + strbuf_release(&valbuf); + strbuf_release("buf); + return ret; +} + +static int parse_format_cb(const struct option *opt, + const char *arg, int unset UNUSED) +{ + enum output_format *format = opt->value; + + if (opt->short_name == 'z') + *format = FORMAT_NUL_TERMINATED; + else if (!strcmp(arg, "nul")) + *format = FORMAT_NUL_TERMINATED; + else if (!strcmp(arg, "keyvalue")) + *format = FORMAT_KEYVALUE; + else + die(_("invalid format '%s'"), arg); + + return 0; +} + +static int repo_info(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + enum output_format format = FORMAT_KEYVALUE; + struct option options[] = { + OPT_CALLBACK_F(0, "format", &format, N_("format"), + N_("output format"), + PARSE_OPT_NONEG, parse_format_cb), + OPT_CALLBACK_F('z', NULL, &format, NULL, + N_("synonym for --format=nul"), + PARSE_OPT_NONEG | PARSE_OPT_NOARG, + parse_format_cb), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, repo_usage, 0); + + return print_fields(argc, argv, repo, format); +} + +int cmd_repo(int argc, const char **argv, const char *prefix, + struct repository *repo) +{ + parse_opt_subcommand_fn *fn = NULL; + struct option options[] = { + OPT_SUBCOMMAND("info", &fn, repo_info), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, repo_usage, 0); + + return fn(argc, argv, prefix, repo); +} diff --git a/builtin/reset.c b/builtin/reset.c index 00492c19b0..ed35802af1 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -346,6 +346,7 @@ int cmd_reset(int argc, struct object_id oid; struct pathspec pathspec; int intent_to_add = 0; + struct add_p_opt add_p_opt = ADD_P_OPT_INIT; const struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_BOOL(0, "no-refresh", &no_refresh, @@ -370,6 +371,8 @@ int cmd_reset(int argc, PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater), OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")), + OPT_DIFF_UNIFIED(&add_p_opt.context), + OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext), OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that removed paths will be added later")), OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), @@ -420,6 +423,11 @@ int cmd_reset(int argc, oidcpy(&oid, &tree->object.oid); } + if (add_p_opt.context < -1) + die(_("'%s' cannot be negative"), "--unified"); + if (add_p_opt.interhunkcontext < -1) + die(_("'%s' cannot be negative"), "--inter-hunk-context"); + prepare_repo_settings(the_repository); the_repository->settings.command_requires_full_index = 0; @@ -427,9 +435,14 @@ int cmd_reset(int argc, if (reset_type != NONE) die(_("options '%s' and '%s' cannot be used together"), "--patch", "--{hard,mixed,soft}"); trace2_cmd_mode("patch-interactive"); - update_ref_status = !!run_add_p(the_repository, ADD_P_RESET, rev, - &pathspec); + update_ref_status = !!run_add_p(the_repository, ADD_P_RESET, + &add_p_opt, rev, &pathspec); goto cleanup; + } else { + if (add_p_opt.context != -1) + die(_("the option '%s' requires '%s'"), "--unified", "--patch"); + if (add_p_opt.interhunkcontext != -1) + die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch"); } /* git reset tree [--] paths... can be used to diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 034ea0a558..99f876ba85 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -28,6 +28,14 @@ #include "quote.h" #include "strbuf.h" +struct rev_list_info { + struct rev_info *revs; + int flags; + int show_timestamp; + int hdr_termination; + const char *header_prefix; +}; + static const char rev_list_usage[] = "git rev-list [<options>] <commit>... [--] [<path>...]\n" "\n" @@ -652,17 +660,21 @@ int cmd_rev_list(int argc, * * Let "--missing" to conditionally set fetch_if_missing. */ + /* - * NEEDSWORK: These loops that attempt to find presence of - * options without understanding that the options they are - * skipping are broken (e.g., it would not know "--grep + * NEEDSWORK: The next loop is utterly broken. It tries to + * notice an option is used, but without understanding if each + * option takes an argument, which fundamentally would not + * work. It would not know "--grep * --exclude-promisor-objects" is not triggering - * "--exclude-promisor-objects" option). We really need - * setup_revisions() to have a mechanism to allow and disallow - * some sets of options for different commands (like rev-list, - * replay, etc). Such a mechanism should do an early parsing - * of options and be able to manage the `--missing=...` and - * `--exclude-promisor-objects` options below. + * "--exclude-promisor-objects" option, for example. + * + * We really need setup_revisions() to have a mechanism to + * allow and disallow some sets of options for different + * commands (like rev-list, replay, etc). Such a mechanism + * should do an early parsing of options and be able to manage + * the `--missing=...` and `--exclude-promisor-objects` + * options below. */ for (i = 1; i < argc; i++) { const char *arg = argv[i]; diff --git a/builtin/revert.c b/builtin/revert.c index b197848bb0..bedc40f368 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -112,7 +112,7 @@ static int run_sequencer(int argc, const char **argv, const char *prefix, const char * const * usage_str = revert_or_cherry_pick_usage(opts); const char *me = action_name(opts); const char *cleanup_arg = NULL; - const char sentinel_value; + const char sentinel_value = 0; /* value not important */ const char *strategy = &sentinel_value; const char *gpg_sign = &sentinel_value; enum empty_action empty_opt = EMPTY_COMMIT_UNSPECIFIED; diff --git a/builtin/stash.c b/builtin/stash.c index 821b58a14e..f5ddee5c7f 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -738,7 +738,8 @@ cleanup: return ret; } -static int reject_reflog_ent(struct object_id *ooid UNUSED, +static int reject_reflog_ent(const char *refname UNUSED, + struct object_id *ooid UNUSED, struct object_id *noid UNUSED, const char *email UNUSED, timestamp_t timestamp UNUSED, @@ -1301,7 +1302,8 @@ done: } static int stash_patch(struct stash_info *info, const struct pathspec *ps, - struct strbuf *out_patch, int quiet) + struct strbuf *out_patch, int quiet, + struct add_p_opt *add_p_opt) { int ret = 0; struct child_process cp_read_tree = CHILD_PROCESS_INIT; @@ -1326,7 +1328,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT)); setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1); - ret = !!run_add_p(the_repository, ADD_P_STASH, NULL, ps); + ret = !!run_add_p(the_repository, ADD_P_STASH, add_p_opt, NULL, ps); the_repository->index_file = old_repo_index_file; if (old_index_env && *old_index_env) @@ -1421,8 +1423,8 @@ done: } static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf, - int include_untracked, int patch_mode, int only_staged, - struct stash_info *info, struct strbuf *patch, + int include_untracked, int patch_mode, struct add_p_opt *add_p_opt, + int only_staged, struct stash_info *info, struct strbuf *patch, int quiet) { int ret = 0; @@ -1503,7 +1505,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b untracked_commit_option = 1; } if (patch_mode) { - ret = stash_patch(info, ps, patch, quiet); + ret = stash_patch(info, ps, patch, quiet, add_p_opt); if (ret < 0) { if (!quiet) fprintf_ln(stderr, _("Cannot save the current " @@ -1578,7 +1580,7 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED, if (!check_changes_tracked_files(&ps)) return 0; - ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info, + ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, NULL, 0, &info, NULL, 0); if (!ret) printf_ln("%s", oid_to_hex(&info.w_commit)); @@ -1589,7 +1591,8 @@ static int create_stash(int argc, const char **argv, const char *prefix UNUSED, } static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet, - int keep_index, int patch_mode, int include_untracked, int only_staged) + int keep_index, int patch_mode, struct add_p_opt *add_p_opt, + int include_untracked, int only_staged) { int ret = 0; struct stash_info info = STASH_INFO_INIT; @@ -1659,8 +1662,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q if (stash_msg) strbuf_addstr(&stash_msg_buf, stash_msg); - if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged, - &info, &patch, quiet)) { + if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, + add_p_opt, only_staged, &info, &patch, quiet)) { ret = -1; goto done; } @@ -1833,6 +1836,7 @@ static int push_stash(int argc, const char **argv, const char *prefix, const char *stash_msg = NULL; char *pathspec_from_file = NULL; struct pathspec ps; + struct add_p_opt add_p_opt = ADD_P_OPT_INIT; struct option options[] = { OPT_BOOL('k', "keep-index", &keep_index, N_("keep index")), @@ -1840,6 +1844,8 @@ static int push_stash(int argc, const char **argv, const char *prefix, N_("stash staged changes only")), OPT_BOOL('p', "patch", &patch_mode, N_("stash in patch mode")), + OPT_DIFF_UNIFIED(&add_p_opt.context), + OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext), OPT__QUIET(&quiet, N_("quiet mode")), OPT_BOOL('u', "include-untracked", &include_untracked, N_("include untracked files in stash")), @@ -1895,8 +1901,20 @@ static int push_stash(int argc, const char **argv, const char *prefix, die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file"); } + if (!patch_mode) { + if (add_p_opt.context != -1) + die(_("the option '%s' requires '%s'"), "--unified", "--patch"); + if (add_p_opt.interhunkcontext != -1) + die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch"); + } + + if (add_p_opt.context < -1) + die(_("'%s' cannot be negative"), "--unified"); + if (add_p_opt.interhunkcontext < -1) + die(_("'%s' cannot be negative"), "--inter-hunk-context"); + ret = do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode, - include_untracked, only_staged); + &add_p_opt, include_untracked, only_staged); clear_pathspec(&ps); free(pathspec_from_file); @@ -1921,6 +1939,7 @@ static int save_stash(int argc, const char **argv, const char *prefix, const char *stash_msg = NULL; struct pathspec ps; struct strbuf stash_msg_buf = STRBUF_INIT; + struct add_p_opt add_p_opt = ADD_P_OPT_INIT; struct option options[] = { OPT_BOOL('k', "keep-index", &keep_index, N_("keep index")), @@ -1928,6 +1947,8 @@ static int save_stash(int argc, const char **argv, const char *prefix, N_("stash staged changes only")), OPT_BOOL('p', "patch", &patch_mode, N_("stash in patch mode")), + OPT_DIFF_UNIFIED(&add_p_opt.context), + OPT_DIFF_INTERHUNK_CONTEXT(&add_p_opt.interhunkcontext), OPT__QUIET(&quiet, N_("quiet mode")), OPT_BOOL('u', "include-untracked", &include_untracked, N_("include untracked files in stash")), @@ -1946,8 +1967,22 @@ static int save_stash(int argc, const char **argv, const char *prefix, stash_msg = strbuf_join_argv(&stash_msg_buf, argc, argv, ' '); memset(&ps, 0, sizeof(ps)); + + if (add_p_opt.context < -1) + die(_("'%s' cannot be negative"), "--unified"); + if (add_p_opt.interhunkcontext < -1) + die(_("'%s' cannot be negative"), "--inter-hunk-context"); + + if (!patch_mode) { + if (add_p_opt.context != -1) + die(_("the option '%s' requires '%s'"), "--unified", "--patch"); + if (add_p_opt.interhunkcontext != -1) + die(_("the option '%s' requires '%s'"), "--inter-hunk-context", "--patch"); + } + ret = do_push_stash(&ps, stash_msg, quiet, keep_index, - patch_mode, include_untracked, only_staged); + patch_mode, &add_p_opt, include_untracked, + only_staged); strbuf_release(&stash_msg_buf); return ret; @@ -2173,7 +2208,8 @@ struct stash_entry_data { size_t count; }; -static int collect_stash_entries(struct object_id *old_oid UNUSED, +static int collect_stash_entries(const char *refname UNUSED, + struct object_id *old_oid UNUSED, struct object_id *new_oid, const char *committer UNUSED, timestamp_t timestamp UNUSED, diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 28f34f7bc1..07a1935cbe 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -32,6 +32,8 @@ #include "advice.h" #include "branch.h" #include "list-objects-filter-options.h" +#include "wildmatch.h" +#include "strbuf.h" #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -3307,6 +3309,8 @@ static void configure_added_submodule(struct add_data *add_data) char *key; struct child_process add_submod = CHILD_PROCESS_INIT; struct child_process add_gitmodules = CHILD_PROCESS_INIT; + const struct string_list *values; + int matched = 0; key = xstrfmt("submodule.%s.url", add_data->sm_name); repo_config_set_gently(the_repository, key, add_data->realrepo); @@ -3349,20 +3353,28 @@ static void configure_added_submodule(struct add_data *add_data) * is_submodule_active(), since that function needs to find * out the value of "submodule.active" again anyway. */ - if (!repo_config_get(the_repository, "submodule.active")) { + if (repo_config_get(the_repository, "submodule.active") || /* key absent */ + repo_config_get_string_multi(the_repository, "submodule.active", &values)) { /* * If the submodule being added isn't already covered by the * current configured pathspec, set the submodule's active flag */ - if (!is_submodule_active(the_repository, add_data->sm_path)) { + key = xstrfmt("submodule.%s.active", add_data->sm_name); + repo_config_set_gently(the_repository, key, "true"); + free(key); + } else { + for (size_t i = 0; i < values->nr; i++) { + const char *pat = values->items[i].string; + if (!wildmatch(pat, add_data->sm_path, 0)) { /* match found */ + matched = 1; + break; + } + } + if (!matched) { /* no pattern matched -> force-enable */ key = xstrfmt("submodule.%s.active", add_data->sm_name); repo_config_set_gently(the_repository, key, "true"); free(key); } - } else { - key = xstrfmt("submodule.%s.active", add_data->sm_name); - repo_config_set_gently(the_repository, key, "true"); - free(key); } } @@ -3423,6 +3435,9 @@ static int module_add(int argc, const char **argv, const char *prefix, struct add_data add_data = ADD_DATA_INIT; const char *ref_storage_format = NULL; char *to_free = NULL; + const struct submodule *existing; + struct strbuf buf = STRBUF_INIT; + char *sm_name_to_free = NULL; struct option options[] = { OPT_STRING('b', "branch", &add_data.branch, N_("branch"), N_("branch of repository to add as submodule")), @@ -3525,6 +3540,28 @@ static int module_add(int argc, const char **argv, const char *prefix, if(!add_data.sm_name) add_data.sm_name = add_data.sm_path; + existing = submodule_from_name(the_repository, + null_oid(the_hash_algo), + add_data.sm_name); + + if (existing && strcmp(existing->path, add_data.sm_path)) { + if (!force) { + die(_("submodule name '%s' already used for path '%s'"), + add_data.sm_name, existing->path); + } + /* --force: build <name><n> until unique */ + for (int i = 1; ; i++) { + strbuf_reset(&buf); + strbuf_addf(&buf, "%s%d", add_data.sm_name, i); + if (!submodule_from_name(the_repository, + null_oid(the_hash_algo), + buf.buf)) { + break; + } + } + add_data.sm_name = sm_name_to_free = strbuf_detach(&buf, NULL); + } + if (check_submodule_name(add_data.sm_name)) die(_("'%s' is not a valid submodule name"), add_data.sm_name); @@ -3540,6 +3577,7 @@ static int module_add(int argc, const char **argv, const char *prefix, ret = 0; cleanup: + free(sm_name_to_free); free(add_data.sm_path); free(to_free); strbuf_release(&sb); diff --git a/builtin/tag.c b/builtin/tag.c index 25f30e3f9b..f0665af3ac 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -271,8 +271,8 @@ static int build_tag_object(struct strbuf *buf, int sign, struct object_id *resu struct object_id *compat_oid = NULL, compat_oid_buf; if (sign && do_sign(buf, &compat_oid, &compat_oid_buf) < 0) return error(_("unable to sign the tag")); - if (write_object_file_flags(buf->buf, buf->len, OBJ_TAG, result, - compat_oid, 0) < 0) + if (odb_write_object_ext(the_repository->objects, buf->buf, + buf->len, OBJ_TAG, result, compat_oid, 0) < 0) return error(_("unable to write tag file")); return 0; } diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 26aa885da9..28124b324d 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -204,8 +204,8 @@ static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf) { struct object_id oid; - if (write_object_file(obj_buf->buffer, obj_buf->size, - obj->type, &oid) < 0) + if (odb_write_object(the_repository->objects, obj_buf->buffer, obj_buf->size, + obj->type, &oid) < 0) die("failed to write object %s", oid_to_hex(&obj->oid)); obj->flags |= FLAG_WRITTEN; } @@ -272,16 +272,16 @@ static void write_object(unsigned nr, enum object_type type, void *buf, unsigned long size) { if (!strict) { - if (write_object_file(buf, size, type, - &obj_list[nr].oid) < 0) + if (odb_write_object(the_repository->objects, buf, size, type, + &obj_list[nr].oid) < 0) die("failed to write object"); added_object(nr, type, buf, size); free(buf); obj_list[nr].obj = NULL; } else if (type == OBJ_BLOB) { struct blob *blob; - if (write_object_file(buf, size, type, - &obj_list[nr].oid) < 0) + if (odb_write_object(the_repository->objects, buf, size, type, + &obj_list[nr].oid) < 0) die("failed to write object"); added_object(nr, type, buf, size); free(buf); @@ -403,7 +403,8 @@ static void stream_blob(unsigned long size, unsigned nr) data.zstream = &zstream; git_inflate_init(&zstream); - if (stream_loose_object(&in_stream, size, &info->oid)) + if (stream_loose_object(the_repository->objects->sources, + &in_stream, size, &info->oid)) die(_("failed to write object in stream")); if (data.status != Z_STREAM_END) @@ -583,6 +584,7 @@ static void unpack_all(void) { int i; unsigned char *hdr = fill(sizeof(struct pack_header)); + struct odb_transaction *transaction; if (get_be32(hdr) != PACK_SIGNATURE) die("bad pack file"); @@ -598,12 +600,12 @@ static void unpack_all(void) progress = start_progress(the_repository, _("Unpacking objects"), nr_objects); CALLOC_ARRAY(obj_list, nr_objects); - begin_odb_transaction(); + transaction = begin_odb_transaction(the_repository->objects); for (i = 0; i < nr_objects; i++) { unpack_one(i); display_progress(progress, i + 1); } - end_odb_transaction(); + end_odb_transaction(transaction); stop_progress(&progress); if (delta_list) diff --git a/builtin/update-index.c b/builtin/update-index.c index 2380f3ccd6..2ba2d29c95 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -77,7 +77,7 @@ static void report(const char *fmt, ...) * objects invisible while a transaction is active, so flush the * transaction here before reporting a change made by update-index. */ - flush_odb_transaction(); + flush_odb_transaction(the_repository->objects->transaction); va_start(vp, fmt); vprintf(fmt, vp); putchar('\n'); @@ -940,6 +940,7 @@ int cmd_update_index(int argc, strbuf_getline_fn getline_fn; int parseopt_state = PARSE_OPT_UNKNOWN; struct repository *r = the_repository; + struct odb_transaction *transaction; struct option options[] = { OPT_BIT('q', NULL, &refresh_args.flags, N_("continue refresh even when index needs update"), @@ -1130,7 +1131,7 @@ int cmd_update_index(int argc, * Allow the object layer to optimize adding multiple objects in * a batch. */ - begin_odb_transaction(); + transaction = begin_odb_transaction(the_repository->objects); while (ctx.argc) { if (parseopt_state != PARSE_OPT_DONE) parseopt_state = parse_options_step(&ctx, options, @@ -1213,7 +1214,7 @@ int cmd_update_index(int argc, /* * By now we have added all of the new objects */ - end_odb_transaction(); + end_odb_transaction(transaction); if (split_index > 0) { if (repo_config_get_split_index(the_repository) == 0) diff --git a/builtin/var.c b/builtin/var.c index a2d790d453..cc3a43cde2 100644 --- a/builtin/var.c +++ b/builtin/var.c @@ -182,7 +182,7 @@ static void list_vars(void) if (ptr->multivalued && *val) { struct string_list list = STRING_LIST_INIT_DUP; - string_list_split(&list, val, '\n', -1); + string_list_split(&list, val, "\n", -1); for (size_t i = 0; i < list.nr; i++) printf("%s=%s\n", ptr->name, list.items[i].string); string_list_clear(&list, 0); |
