diff -Nru git-2.36.1/add-interactive.c git-2.37.2/add-interactive.c --- git-2.36.1/add-interactive.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/add-interactive.c 2022-08-11 05:06:00.000000000 +0000 @@ -568,8 +568,7 @@ run_diff_files(&rev, 0); } - if (ps) - clear_pathspec(&rev.prune_data); + release_revisions(&rev); } hashmap_clear_and_free(&s.file_map, struct pathname_entry, ent); if (unmerged_count) @@ -698,8 +697,16 @@ for (i = 0; i < files->items.nr; i++) { const char *name = files->items.items[i].string; - if (files->selected[i] && - add_file_to_index(s->r->index, name, 0) < 0) { + struct stat st; + + if (!files->selected[i]) + continue; + if (lstat(name, &st) && is_missing_file_error(errno)) { + if (remove_file_from_index(s->r->index, name) < 0) { + res = error(_("could not stage '%s'"), name); + break; + } + } else if (add_file_to_index(s->r->index, name, 0) < 0) { res = error(_("could not stage '%s'"), name); break; } diff -Nru git-2.36.1/add-patch.c git-2.37.2/add-patch.c --- git-2.36.1/add-patch.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/add-patch.c 2022-08-11 05:06:00.000000000 +0000 @@ -305,7 +305,7 @@ va_end(ap); cp->git_cmd = 1; - strvec_pushf(&cp->env_array, + strvec_pushf(&cp->env, INDEX_ENVIRONMENT "=%s", s->s.r->index_file); } diff -Nru git-2.36.1/alloc.c git-2.37.2/alloc.c --- git-2.36.1/alloc.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/alloc.c 2022-08-11 05:06:00.000000000 +0000 @@ -27,7 +27,6 @@ }; struct alloc_state { - int count; /* total number of nodes allocated */ int nr; /* number of nodes left in current allocation */ void *p; /* first free node in current allocation */ @@ -63,7 +62,6 @@ s->slabs[s->slab_nr++] = s->p; } s->nr--; - s->count++; ret = s->p; s->p = (char *)s->p + node_size; memset(ret, 0, node_size); @@ -122,22 +120,3 @@ init_commit_node(c); return c; } - -static void report(const char *name, unsigned int count, size_t size) -{ - fprintf(stderr, "%10s: %8u (%"PRIuMAX" kB)\n", - name, count, (uintmax_t) size); -} - -#define REPORT(name, type) \ - report(#name, r->parsed_objects->name##_state->count, \ - r->parsed_objects->name##_state->count * sizeof(type) >> 10) - -void alloc_report(struct repository *r) -{ - REPORT(blob, struct blob); - REPORT(tree, struct tree); - REPORT(commit, struct commit); - REPORT(tag, struct tag); - REPORT(object, union any_object); -} diff -Nru git-2.36.1/alloc.h git-2.37.2/alloc.h --- git-2.36.1/alloc.h 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/alloc.h 2022-08-11 05:06:00.000000000 +0000 @@ -13,7 +13,6 @@ void *alloc_commit_node(struct repository *r); void *alloc_tag_node(struct repository *r); void *alloc_object_node(struct repository *r); -void alloc_report(struct repository *r); struct alloc_state *allocate_alloc_state(void); void clear_alloc_state(struct alloc_state *s); diff -Nru git-2.36.1/apply.c git-2.37.2/apply.c --- git-2.36.1/apply.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/apply.c 2022-08-11 05:06:00.000000000 +0000 @@ -3274,11 +3274,11 @@ { struct string_list_item *item; - if (name == NULL) + if (!name) return NULL; item = string_list_lookup(&state->fn_table, name); - if (item != NULL) + if (item) return (struct patch *)item->util; return NULL; @@ -3318,7 +3318,7 @@ * This should cover the cases for normal diffs, * file creations and copies */ - if (patch->new_name != NULL) { + if (patch->new_name) { item = string_list_insert(&state->fn_table, patch->new_name); item->util = patch; } diff -Nru git-2.36.1/archive.c git-2.37.2/archive.c --- git-2.36.1/archive.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/archive.c 2022-08-11 05:06:00.000000000 +0000 @@ -9,6 +9,7 @@ #include "parse-options.h" #include "unpack-trees.h" #include "dir.h" +#include "quote.h" static char const * const archive_usage[] = { N_("git archive [] [...]"), @@ -263,6 +264,7 @@ struct extra_file_info { char *base; struct stat stat; + void *content; }; int write_archive_entries(struct archiver_args *args, @@ -331,19 +333,27 @@ put_be64(fake_oid.hash, i + 1); - strbuf_reset(&path_in_archive); - if (info->base) - strbuf_addstr(&path_in_archive, info->base); - strbuf_addstr(&path_in_archive, basename(path)); - - strbuf_reset(&content); - if (strbuf_read_file(&content, path, info->stat.st_size) < 0) - err = error_errno(_("cannot read '%s'"), path); - else - err = write_entry(args, &fake_oid, path_in_archive.buf, - path_in_archive.len, - info->stat.st_mode, - content.buf, content.len); + if (!info->content) { + strbuf_reset(&path_in_archive); + if (info->base) + strbuf_addstr(&path_in_archive, info->base); + strbuf_addstr(&path_in_archive, basename(path)); + + strbuf_reset(&content); + if (strbuf_read_file(&content, path, info->stat.st_size) < 0) + err = error_errno(_("cannot read '%s'"), path); + else + err = write_entry(args, &fake_oid, path_in_archive.buf, + path_in_archive.len, + canon_mode(info->stat.st_mode), + content.buf, content.len); + } else { + err = write_entry(args, &fake_oid, + path, strlen(path), + canon_mode(info->stat.st_mode), + info->content, info->stat.st_size); + } + if (err) break; } @@ -465,7 +475,7 @@ } tree = parse_tree_indirect(&oid); - if (tree == NULL) + if (!tree) die(_("not a tree object: %s"), oid_to_hex(&oid)); if (prefix) { @@ -493,6 +503,7 @@ { struct extra_file_info *info = util; free(info->base); + free(info->content); free(info); } @@ -514,14 +525,49 @@ if (!arg) return -1; - path = prefix_filename(args->prefix, arg); - item = string_list_append_nodup(&args->extra_files, path); - item->util = info = xmalloc(sizeof(*info)); + info = xmalloc(sizeof(*info)); info->base = xstrdup_or_null(base); - if (stat(path, &info->stat)) - die(_("File not found: %s"), path); - if (!S_ISREG(info->stat.st_mode)) - die(_("Not a regular file: %s"), path); + + if (!strcmp(opt->long_name, "add-file")) { + path = prefix_filename(args->prefix, arg); + if (stat(path, &info->stat)) + die(_("File not found: %s"), path); + if (!S_ISREG(info->stat.st_mode)) + die(_("Not a regular file: %s"), path); + info->content = NULL; /* read the file later */ + } else if (!strcmp(opt->long_name, "add-virtual-file")) { + struct strbuf buf = STRBUF_INIT; + const char *p = arg; + + if (*p != '"') + p = strchr(p, ':'); + else if (unquote_c_style(&buf, p, &p) < 0) + die(_("unclosed quote: '%s'"), arg); + + if (!p || *p != ':') + die(_("missing colon: '%s'"), arg); + + if (p == arg) + die(_("empty file name: '%s'"), arg); + + path = buf.len ? + strbuf_detach(&buf, NULL) : xstrndup(arg, p - arg); + + if (args->prefix) { + char *save = path; + path = prefix_filename(args->prefix, path); + free(save); + } + memset(&info->stat, 0, sizeof(info->stat)); + info->stat.st_mode = S_IFREG | 0644; + info->content = xstrdup(p + 1); + info->stat.st_size = strlen(info->content); + } else { + BUG("add_file_cb() called for %s", opt->long_name); + } + item = string_list_append_nodup(&args->extra_files, path); + item->util = info; + return 0; } @@ -554,6 +600,9 @@ { OPTION_CALLBACK, 0, "add-file", args, N_("file"), N_("add untracked file to archive"), 0, add_file_cb, (intptr_t)&base }, + { OPTION_CALLBACK, 0, "add-virtual-file", args, + N_("path:content"), N_("add untracked file to archive"), 0, + add_file_cb, (intptr_t)&base }, OPT_STRING('o', "output", &output, N_("file"), N_("write the archive to this file")), OPT_BOOL(0, "worktree-attributes", &worktree_attributes, diff -Nru git-2.36.1/bisect.c git-2.37.2/bisect.c --- git-2.36.1/bisect.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/bisect.c 2022-08-11 05:06:00.000000000 +0000 @@ -884,6 +884,7 @@ /* Clean up objects used, as they will be reused. */ clear_commit_marks_many(rev_nr, rev, ALL_REV_FLAGS); + release_revisions(&revs); return res; } @@ -964,6 +965,7 @@ setup_revisions(ARRAY_SIZE(argv) - 1, argv, &opt, NULL); log_tree_commit(&opt, commit); + release_revisions(&opt); } /* @@ -1008,7 +1010,7 @@ */ enum bisect_error bisect_next_all(struct repository *r, const char *prefix) { - struct rev_info revs; + struct rev_info revs = REV_INFO_INIT; struct commit_list *tried; int reaches = 0, all = 0, nr, steps; enum bisect_error res = BISECT_OK; @@ -1033,7 +1035,7 @@ res = check_good_are_ancestors_of_bad(r, prefix, no_checkout); if (res) - return res; + goto cleanup; bisect_rev_setup(r, &revs, prefix, "%s", "^%s", 1); @@ -1058,14 +1060,16 @@ term_good, term_bad); - return BISECT_FAILED; + res = BISECT_FAILED; + goto cleanup; } if (!all) { fprintf(stderr, _("No testable commit found.\n" "Maybe you started with bad path arguments?\n")); - return BISECT_NO_TESTABLE_COMMIT; + res = BISECT_NO_TESTABLE_COMMIT; + goto cleanup; } bisect_rev = &revs.commits->item->object.oid; @@ -1085,7 +1089,8 @@ * for negative return values for early returns up * until the cmd_bisect__helper() caller. */ - return BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND; + res = BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND; + goto cleanup; } nr = all - reaches - 1; @@ -1104,7 +1109,10 @@ /* Clean up objects used, as they will be reused. */ repo_clear_commit_marks(r, ALL_REV_FLAGS); - return bisect_checkout(bisect_rev, no_checkout); + res = bisect_checkout(bisect_rev, no_checkout); +cleanup: + release_revisions(&revs); + return res; } static inline int log2i(int n) diff -Nru git-2.36.1/bisect.h git-2.37.2/bisect.h --- git-2.36.1/bisect.h 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/bisect.h 2022-08-11 05:06:00.000000000 +0000 @@ -62,6 +62,15 @@ BISECT_INTERNAL_SUCCESS_MERGE_BASE = -11 }; +/* + * Stores how many good/bad commits we have stored for a bisect. nr_bad can + * only be 0 or 1. + */ +struct bisect_state { + unsigned int nr_good; + unsigned int nr_bad; +}; + enum bisect_error bisect_next_all(struct repository *r, const char *prefix); int estimate_bisect_steps(int all); diff -Nru git-2.36.1/blame.c git-2.37.2/blame.c --- git-2.36.1/blame.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/blame.c 2022-08-11 05:06:00.000000000 +0000 @@ -1072,7 +1072,7 @@ if (p1->s_lno <= p2->s_lno) { do { tail = &p1->next; - if ((p1 = *tail) == NULL) { + if (!(p1 = *tail)) { *tail = p2; return list1; } @@ -1082,7 +1082,7 @@ *tail = p2; do { tail = &p2->next; - if ((p2 = *tail) == NULL) { + if (!(p2 = *tail)) { *tail = p1; return list1; } @@ -1090,7 +1090,7 @@ *tail = p1; do { tail = &p1->next; - if ((p1 = *tail) == NULL) { + if (!(p1 = *tail)) { *tail = p2; return list1; } diff -Nru git-2.36.1/bloom.c git-2.37.2/bloom.c --- git-2.36.1/bloom.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/bloom.c 2022-08-11 05:06:00.000000000 +0000 @@ -30,10 +30,9 @@ static int load_bloom_filter_from_graph(struct commit_graph *g, struct bloom_filter *filter, - struct commit *c) + uint32_t graph_pos) { uint32_t lex_pos, start_index, end_index; - uint32_t graph_pos = commit_graph_position(c); while (graph_pos < g->num_commits_in_base) g = g->base_graph; @@ -203,9 +202,10 @@ filter = bloom_filter_slab_at(&bloom_filters, c); if (!filter->data) { - load_commit_graph_info(r, c); - if (commit_graph_position(c) != COMMIT_NOT_FROM_GRAPH) - load_bloom_filter_from_graph(r->objects->commit_graph, filter, c); + uint32_t graph_pos; + if (repo_find_commit_pos_in_graph(r, c, &graph_pos)) + load_bloom_filter_from_graph(r->objects->commit_graph, + filter, graph_pos); } if (filter->data && filter->len) diff -Nru git-2.36.1/branch.c git-2.37.2/branch.c --- git-2.36.1/branch.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/branch.c 2022-08-11 05:06:00.000000000 +0000 @@ -44,9 +44,9 @@ string_list_clear(tracking->srcs, 0); break; } + /* remote_find_tracking() searches by src if present */ tracking->spec.src = NULL; } - return 0; } @@ -264,15 +264,23 @@ if (!tracking.matches) switch (track) { + /* If ref is not remote, still use local */ case BRANCH_TRACK_ALWAYS: case BRANCH_TRACK_EXPLICIT: case BRANCH_TRACK_OVERRIDE: + /* Remote matches not evaluated */ case BRANCH_TRACK_INHERIT: break; + /* Otherwise, if no remote don't track */ default: goto cleanup; } + /* + * This check does not apply to BRANCH_TRACK_INHERIT; + * that supports multiple entries in tracking_srcs but + * leaves tracking.matches at 0. + */ if (tracking.matches > 1) { int status = die_message(_("not tracking: ambiguous information for ref '%s'"), orig_ref); @@ -307,6 +315,21 @@ exit(status); } + if (track == BRANCH_TRACK_SIMPLE) { + /* + * Only track if remote branch name matches. + * Reaching into items[0].string is safe because + * we know there is at least one and not more than + * one entry (because only BRANCH_TRACK_INHERIT can + * produce more than one entry). + */ + const char *tracked_branch; + if (!skip_prefix(tracking.srcs->items[0].string, + "refs/heads/", &tracked_branch) || + strcmp(tracked_branch, new_ref)) + return; + } + if (tracking.srcs->nr < 1) string_list_append(tracking.srcs, orig_ref); if (install_branch_config_multiple_remotes(config_flags, new_ref, @@ -466,7 +489,7 @@ break; } - if ((commit = lookup_commit_reference(r, &oid)) == NULL) + if (!(commit = lookup_commit_reference(r, &oid))) die(_("not a valid branch point: '%s'"), start_name); if (out_real_ref) { *out_real_ref = real_ref; @@ -564,7 +587,7 @@ child.err = -1; child.stdout_to_stderr = 1; - prepare_other_repo_env(&child.env_array, r->gitdir); + prepare_other_repo_env(&child.env, r->gitdir); /* * submodule_create_branch() is indirectly invoked by "git * branch", but we cannot invoke "git branch" in the child @@ -603,6 +626,8 @@ /* Default for "git checkout". Do not pass --track. */ case BRANCH_TRACK_REMOTE: /* Default for "git branch". Do not pass --track. */ + case BRANCH_TRACK_SIMPLE: + /* Config-driven only. Do not pass --track. */ break; } @@ -653,7 +678,7 @@ * be created in every submodule. */ for (i = 0; i < submodule_entry_list.entry_nr; i++) { - if (submodule_entry_list.entries[i].repo == NULL) { + if (!submodule_entry_list.entries[i].repo) { int code = die_message( _("submodule '%s': unable to find submodule"), submodule_entry_list.entries[i].submodule->name); diff -Nru git-2.36.1/branch.h git-2.37.2/branch.h --- git-2.36.1/branch.h 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/branch.h 2022-08-11 05:06:00.000000000 +0000 @@ -12,6 +12,7 @@ BRANCH_TRACK_EXPLICIT, BRANCH_TRACK_OVERRIDE, BRANCH_TRACK_INHERIT, + BRANCH_TRACK_SIMPLE, }; extern enum branch_track git_branch_track; diff -Nru git-2.36.1/builtin/add.c git-2.37.2/builtin/add.c --- git-2.36.1/builtin/add.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/add.c 2022-08-11 05:06:00.000000000 +0000 @@ -141,8 +141,17 @@ rev.diffopt.format_callback_data = &data; rev.diffopt.flags.override_submodule_config = 1; rev.max_count = 0; /* do not compare unmerged paths with stage #2 */ + + /* + * Use an ODB transaction to optimize adding multiple objects. + * This function is invoked from commands other than 'add', which + * may not have their own transaction active. + */ + begin_odb_transaction(); run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); - clear_pathspec(&rev.prune_data); + end_odb_transaction(); + + release_revisions(&rev); return !!data.add_errors; } @@ -236,17 +245,12 @@ int use_builtin_add_i = git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1); - if (use_builtin_add_i < 0) { - int experimental; - if (!git_config_get_bool("add.interactive.usebuiltin", - &use_builtin_add_i)) - ; /* ok */ - else if (!git_config_get_bool("feature.experimental", &experimental) && - experimental) - use_builtin_add_i = 1; - } + if (use_builtin_add_i < 0 && + git_config_get_bool("add.interactive.usebuiltin", + &use_builtin_add_i)) + use_builtin_add_i = 1; - if (use_builtin_add_i == 1) { + if (use_builtin_add_i != 0) { enum add_p_mode mode; if (!patch_mode) @@ -340,6 +344,7 @@ unlink(file); free(file); + release_revisions(&rev); return 0; } @@ -670,7 +675,7 @@ string_list_clear(&only_match_skip_worktree, 0); } - plug_bulk_checkin(); + begin_odb_transaction(); if (add_renormalize) exit_status |= renormalize_tracked_files(&pathspec, flags); @@ -682,7 +687,7 @@ if (chmod_arg && pathspec.nr) exit_status |= chmod_pathspec(&pathspec, chmod_arg[0], show_only); - unplug_bulk_checkin(); + end_odb_transaction(); finish: if (write_locked_index(&the_index, &lock_file, diff -Nru git-2.36.1/builtin/am.c git-2.37.2/builtin/am.c --- git-2.36.1/builtin/am.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/am.c 2022-08-11 05:06:00.000000000 +0000 @@ -1397,6 +1397,7 @@ add_pending_object(&rev_info, &commit->object, ""); diff_setup_done(&rev_info.diffopt); log_tree_commit(&rev_info, commit); + release_revisions(&rev_info); } /** @@ -1429,6 +1430,7 @@ add_pending_object(&rev_info, &tree->object, ""); diff_setup_done(&rev_info.diffopt); run_diff_index(&rev_info, 1); + release_revisions(&rev_info); } /** @@ -1582,6 +1584,7 @@ add_pending_oid(&rev_info, "HEAD", &our_tree, 0); diff_setup_done(&rev_info.diffopt); run_diff_index(&rev_info, 1); + release_revisions(&rev_info); } if (run_apply(state, index_path)) diff -Nru git-2.36.1/builtin/apply.c git-2.37.2/builtin/apply.c --- git-2.36.1/builtin/apply.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/apply.c 2022-08-11 05:06:00.000000000 +0000 @@ -1,7 +1,6 @@ #include "cache.h" #include "builtin.h" #include "parse-options.h" -#include "lockfile.h" #include "apply.h" static const char * const apply_usage[] = { diff -Nru git-2.36.1/builtin/bisect--helper.c git-2.37.2/builtin/bisect--helper.c --- git-2.36.1/builtin/bisect--helper.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/bisect--helper.c 2022-08-11 05:06:00.000000000 +0000 @@ -329,12 +329,12 @@ return 0; } -static int mark_good(const char *refname, const struct object_id *oid, - int flag, void *cb_data) +static int inc_nr(const char *refname, const struct object_id *oid, + int flag, void *cb_data) { - int *m_good = (int *)cb_data; - *m_good = 0; - return 1; + unsigned int *nr = (unsigned int *)cb_data; + (*nr)++; + return 0; } static const char need_bad_and_good_revision_warning[] = @@ -384,23 +384,64 @@ vocab_good, vocab_bad, vocab_good, vocab_bad); } -static int bisect_next_check(const struct bisect_terms *terms, - const char *current_term) +static void bisect_status(struct bisect_state *state, + const struct bisect_terms *terms) { - int missing_good = 1, missing_bad = 1; char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad); char *good_glob = xstrfmt("%s-*", terms->term_good); if (ref_exists(bad_ref)) - missing_bad = 0; + state->nr_bad = 1; - for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/", - (void *) &missing_good); + for_each_glob_ref_in(inc_nr, good_glob, "refs/bisect/", + (void *) &state->nr_good); free(good_glob); free(bad_ref); +} + +__attribute__((format (printf, 1, 2))) +static void bisect_log_printf(const char *fmt, ...) +{ + struct strbuf buf = STRBUF_INIT; + va_list ap; + + va_start(ap, fmt); + strbuf_vaddf(&buf, fmt, ap); + va_end(ap); + + printf("%s", buf.buf); + append_to_file(git_path_bisect_log(), "# %s", buf.buf); + + strbuf_release(&buf); +} + +static void bisect_print_status(const struct bisect_terms *terms) +{ + struct bisect_state state = { 0 }; - return decide_next(terms, current_term, missing_good, missing_bad); + bisect_status(&state, terms); + + /* If we had both, we'd already be started, and shouldn't get here. */ + if (state.nr_good && state.nr_bad) + return; + + if (!state.nr_good && !state.nr_bad) + bisect_log_printf(_("status: waiting for both good and bad commits\n")); + else if (state.nr_good) + bisect_log_printf(Q_("status: waiting for bad commit, %d good commit known\n", + "status: waiting for bad commit, %d good commits known\n", + state.nr_good), state.nr_good); + else + bisect_log_printf(_("status: waiting for good commit(s), bad commit known\n")); +} + +static int bisect_next_check(const struct bisect_terms *terms, + const char *current_term) +{ + struct bisect_state state = { 0 }; + bisect_status(&state, terms); + return decide_next(terms, current_term, !state.nr_good, !state.nr_bad); } static int get_terms(struct bisect_terms *terms) @@ -433,7 +474,7 @@ if (get_terms(terms)) return error(_("no terms defined")); - if (option == NULL) { + if (!option) { printf(_("Your current terms are %s for the old state\n" "and %s for the new state.\n"), terms->term_good, terms->term_bad); @@ -555,6 +596,7 @@ reset_revision_walk(); strbuf_release(&commit_name); + release_revisions(&revs); fclose(fp); return 0; } @@ -606,8 +648,10 @@ static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix) { - if (bisect_next_check(terms, NULL)) + if (bisect_next_check(terms, NULL)) { + bisect_print_status(terms); return BISECT_OK; + } return bisect_next(terms, prefix); } @@ -1041,6 +1085,7 @@ oid_to_hex(&commit->object.oid)); reset_revision_walk(); + release_revisions(&revs); } else { strvec_push(&argv_state, argv[i]); } diff -Nru git-2.36.1/builtin/blame.c git-2.37.2/builtin/blame.c --- git-2.36.1/builtin/blame.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/blame.c 2022-08-11 05:06:00.000000000 +0000 @@ -898,6 +898,7 @@ unsigned int range_i; long anchor; const int hexsz = the_hash_algo->hexsz; + long num_lines = 0; setup_default_color_by_age(); git_config(git_blame_config, &output_option); @@ -1129,7 +1130,10 @@ for (range_i = ranges.nr; range_i > 0; --range_i) { const struct range *r = &ranges.ranges[range_i - 1]; ent = blame_entry_prepend(ent, r->start, r->end, o); + num_lines += (r->end - r->start); } + if (!num_lines) + num_lines = sb.num_lines; o->suspects = ent; prio_queue_put(&sb.commits, o->commit); @@ -1158,7 +1162,7 @@ sb.found_guilty_entry = &found_guilty_entry; sb.found_guilty_entry_data = π if (show_progress) - pi.progress = start_delayed_progress(_("Blaming lines"), sb.num_lines); + pi.progress = start_delayed_progress(_("Blaming lines"), num_lines); assign_blame(&sb, opt); @@ -1167,7 +1171,7 @@ if (!incremental) setup_pager(); else - return 0; + goto cleanup; blame_sort_final(&sb); @@ -1201,6 +1205,8 @@ printf("num commits: %d\n", sb.num_commits); } +cleanup: cleanup_scoreboard(&sb); + release_revisions(&revs); return 0; } diff -Nru git-2.36.1/builtin/checkout.c git-2.37.2/builtin/checkout.c --- git-2.36.1/builtin/checkout.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/checkout.c 2022-08-11 05:06:00.000000000 +0000 @@ -417,7 +417,7 @@ mem_pool_discard(&ce_mem_pool, should_validate_cache_entries()); remove_marked_cache_entries(&the_index, 1); remove_scheduled_dirs(); - errs |= finish_delayed_checkout(&state, &nr_checkouts, opts->show_progress); + errs |= finish_delayed_checkout(&state, opts->show_progress); if (opts->count_checkout_paths) { if (nr_unmerged) @@ -629,7 +629,7 @@ diff_setup_done(&rev.diffopt); add_pending_object(&rev, head, NULL); run_diff_index(&rev, 0); - object_array_clear(&rev.pending); + release_revisions(&rev); } static void describe_detached_head(const char *msg, struct commit *commit) @@ -834,7 +834,7 @@ if (ret) return ret; o.ancestor = old_branch_info->name; - if (old_branch_info->name == NULL) { + if (!old_branch_info->name) { strbuf_add_unique_abbrev(&old_commit_shortname, &old_branch_info->commit->object.oid, DEFAULT_ABBREV); @@ -1082,6 +1082,7 @@ /* Clean up objects used, as they will be reused. */ repo_clear_commit_marks(the_repository, ALL_REV_FLAGS); + release_revisions(&revs); } static int switch_branches(const struct checkout_opts *opts, diff -Nru git-2.36.1/builtin/clone.c git-2.37.2/builtin/clone.c --- git-2.36.1/builtin/clone.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/clone.c 2022-08-11 05:06:00.000000000 +0000 @@ -606,7 +606,7 @@ } static void update_head(const struct ref *our, const struct ref *remote, - const char *msg) + const char *unborn, const char *msg) { const char *head; if (our && skip_prefix(our->name, "refs/heads/", &head)) { @@ -632,6 +632,15 @@ */ update_ref(msg, "HEAD", &remote->old_oid, NULL, REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR); + } else if (unborn && skip_prefix(unborn, "refs/heads/", &head)) { + /* + * Unborn head from remote; same as "our" case above except + * that we have no ref to update. + */ + if (create_symref("HEAD", unborn, NULL) < 0) + die(_("unable to update HEAD")); + if (!option_bare) + install_branch_config(0, head, remote_name, unborn); } } @@ -672,7 +681,7 @@ head = resolve_refdup("HEAD", RESOLVE_REF_READING, &oid, NULL); if (!head) { warning(_("remote HEAD refers to nonexistent ref, " - "unable to checkout.\n")); + "unable to checkout")); return 0; } if (!strcmp(head, "HEAD")) { @@ -876,6 +885,7 @@ const struct ref *refs, *remote_head; struct ref *remote_head_points_at = NULL; const struct ref *our_head_points_at; + char *unborn_head = NULL; struct ref *mapped_refs = NULL; const struct ref *ref; struct strbuf key = STRBUF_INIT; @@ -1106,10 +1116,12 @@ * apply the remote name provided by --origin only after this second * call to git_config, to ensure it overrides all config-based values. */ - if (option_origin != NULL) + if (option_origin) { + free(remote_name); remote_name = xstrdup(option_origin); + } - if (remote_name == NULL) + if (!remote_name) remote_name = xstrdup("origin"); if (!valid_remote_name(remote_name)) @@ -1264,51 +1276,49 @@ if (transport_fetch_refs(transport, mapped_refs)) die(_("remote transport reported error")); } - - remote_head = find_ref_by_name(refs, "HEAD"); - remote_head_points_at = - guess_remote_head(remote_head, mapped_refs, 0); - - if (option_branch) { - our_head_points_at = - find_remote_branch(mapped_refs, option_branch); - - if (!our_head_points_at) - die(_("Remote branch %s not found in upstream %s"), - option_branch, remote_name); - } - else - our_head_points_at = remote_head_points_at; } - else { - const char *branch; - const char *ref; - char *ref_free = NULL; - if (option_branch) - die(_("Remote branch %s not found in upstream %s"), - option_branch, remote_name); + remote_head = find_ref_by_name(refs, "HEAD"); + remote_head_points_at = guess_remote_head(remote_head, mapped_refs, 0); - warning(_("You appear to have cloned an empty repository.")); + if (option_branch) { + our_head_points_at = find_remote_branch(mapped_refs, option_branch); + if (!our_head_points_at) + die(_("Remote branch %s not found in upstream %s"), + option_branch, remote_name); + } else if (remote_head_points_at) { + our_head_points_at = remote_head_points_at; + } else if (remote_head) { our_head_points_at = NULL; - remote_head_points_at = NULL; - remote_head = NULL; - option_no_checkout = 1; + } else { + const char *branch; + + if (!mapped_refs) { + warning(_("You appear to have cloned an empty repository.")); + option_no_checkout = 1; + } if (transport_ls_refs_options.unborn_head_target && skip_prefix(transport_ls_refs_options.unborn_head_target, "refs/heads/", &branch)) { - ref = transport_ls_refs_options.unborn_head_target; - create_symref("HEAD", ref, reflog_msg.buf); + unborn_head = xstrdup(transport_ls_refs_options.unborn_head_target); } else { branch = git_default_branch_name(0); - ref_free = xstrfmt("refs/heads/%s", branch); - ref = ref_free; + unborn_head = xstrfmt("refs/heads/%s", branch); } - if (!option_bare) - install_branch_config(0, branch, remote_name, ref); - free(ref_free); + /* + * We may have selected a local default branch name "foo", + * and even though the remote's HEAD does not point there, + * it may still have a "foo" branch. If so, set it up so + * that we can follow the usual checkout code later. + * + * Note that for an empty repo we'll already have set + * option_no_checkout above, which would work against us here. + * But for an empty repo, find_remote_branch() can never find + * a match. + */ + our_head_points_at = find_remote_branch(mapped_refs, branch); } write_refspec_config(src_ref_prefix, our_head_points_at, @@ -1328,7 +1338,7 @@ branch_top.buf, reflog_msg.buf, transport, !is_local); - update_head(our_head_points_at, remote_head, reflog_msg.buf); + update_head(our_head_points_at, remote_head, unborn_head, reflog_msg.buf); /* * We want to show progress for recursive submodule clones iff @@ -1355,6 +1365,7 @@ strbuf_release(&key); free_refs(mapped_refs); free_refs(remote_head_points_at); + free(unborn_head); free(dir); free(path); UNLEAK(repo); diff -Nru git-2.36.1/builtin/commit.c git-2.37.2/builtin/commit.c --- git-2.36.1/builtin/commit.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/commit.c 2022-08-11 05:06:00.000000000 +0000 @@ -861,7 +861,7 @@ } s->fp = fopen_for_writing(git_path_commit_editmsg()); - if (s->fp == NULL) + if (!s->fp) die_errno(_("could not open '%s'"), git_path_commit_editmsg()); /* Ignore status.displayCommentPrefix: we do need comments in COMMIT_EDITMSG. */ @@ -1100,7 +1100,6 @@ struct rev_info revs; struct commit *commit; struct strbuf buf = STRBUF_INIT; - struct string_list mailmap = STRING_LIST_INIT_NODUP; const char *av[20]; int ac = 0; @@ -1111,7 +1110,8 @@ av[++ac] = buf.buf; av[++ac] = NULL; setup_revisions(ac, av, &revs, NULL); - revs.mailmap = &mailmap; + revs.mailmap = xmalloc(sizeof(struct string_list)); + string_list_init_nodup(revs.mailmap); read_mailmap(revs.mailmap); if (prepare_revision_walk(&revs)) @@ -1122,7 +1122,7 @@ ctx.date_mode.type = DATE_NORMAL; strbuf_release(&buf); format_commit_message(commit, "%aN <%aE>", &buf, &ctx); - clear_mailmap(&mailmap); + release_revisions(&revs); return strbuf_detach(&buf, NULL); } die(_("--author '%s' is not 'Name ' and matches no existing author"), name); @@ -1687,6 +1687,7 @@ struct commit *current_head = NULL; struct commit_extra_header *extra = NULL; struct strbuf err = STRBUF_INIT; + int ret = 0; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_commit_usage, builtin_commit_options); @@ -1721,8 +1722,9 @@ running hooks, writing the trees, and interacting with the user. */ if (!prepare_to_commit(index_file, prefix, current_head, &s, &author_ident)) { + ret = 1; rollback_index_files(); - return 1; + goto cleanup; } /* Determine parents */ @@ -1820,7 +1822,6 @@ rollback_index_files(); die(_("failed to write commit object")); } - strbuf_release(&author_ident); free_commit_extra_headers(extra); if (update_head_with_reflog(current_head, &oid, reflog_msg, &sb, @@ -1862,7 +1863,9 @@ apply_autostash(git_path_merge_autostash(the_repository)); +cleanup: + UNLEAK(author_ident); UNLEAK(err); UNLEAK(sb); - return 0; + return ret; } diff -Nru git-2.36.1/builtin/describe.c git-2.37.2/builtin/describe.c --- git-2.36.1/builtin/describe.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/describe.c 2022-08-11 05:06:00.000000000 +0000 @@ -517,6 +517,7 @@ traverse_commit_list(&revs, process_commit, process_object, &pcd); reset_revision_walk(); + release_revisions(&revs); } static void describe(const char *arg, int last_one) @@ -667,6 +668,7 @@ suffix = NULL; else suffix = dirty; + release_revisions(&revs); } describe("HEAD", 1); } else if (dirty) { diff -Nru git-2.36.1/builtin/diff.c git-2.37.2/builtin/diff.c --- git-2.36.1/builtin/diff.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/diff.c 2022-08-11 05:06:00.000000000 +0000 @@ -352,7 +352,7 @@ othercount++; continue; } - if (map == NULL) + if (!map) map = bitmap_new(); bitmap_set(map, i); } @@ -594,7 +594,7 @@ result = diff_result_code(&rev.diffopt, result); if (1 < rev.diffopt.skip_stat_unmatch) refresh_index_quietly(); - UNLEAK(rev); + release_revisions(&rev); UNLEAK(ent); UNLEAK(blob); return result; diff -Nru git-2.36.1/builtin/diff-files.c git-2.37.2/builtin/diff-files.c --- git-2.36.1/builtin/diff-files.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/diff-files.c 2022-08-11 05:06:00.000000000 +0000 @@ -77,8 +77,12 @@ if (read_cache_preload(&rev.diffopt.pathspec) < 0) { perror("read_cache_preload"); - return -1; + result = -1; + goto cleanup; } result = run_diff_files(&rev, options); - return diff_result_code(&rev.diffopt, result); + result = diff_result_code(&rev.diffopt, result); +cleanup: + release_revisions(&rev); + return result; } diff -Nru git-2.36.1/builtin/diff-index.c git-2.37.2/builtin/diff-index.c --- git-2.36.1/builtin/diff-index.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/diff-index.c 2022-08-11 05:06:00.000000000 +0000 @@ -70,6 +70,7 @@ return -1; } result = run_diff_index(&rev, option); - UNLEAK(rev); - return diff_result_code(&rev.diffopt, result); + result = diff_result_code(&rev.diffopt, result); + release_revisions(&rev); + return result; } diff -Nru git-2.36.1/builtin/difftool.c git-2.37.2/builtin/difftool.c --- git-2.36.1/builtin/difftool.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/difftool.c 2022-08-11 05:06:00.000000000 +0000 @@ -217,7 +217,7 @@ update_index.use_shell = 0; update_index.clean_on_exit = 1; update_index.dir = workdir; - strvec_pushf(&update_index.env_array, "GIT_INDEX_FILE=%s", index_path); + strvec_pushf(&update_index.env, "GIT_INDEX_FILE=%s", index_path); /* Ignore any errors of update-index */ run_command(&update_index); @@ -230,7 +230,7 @@ diff_files.clean_on_exit = 1; diff_files.out = -1; diff_files.dir = workdir; - strvec_pushf(&diff_files.env_array, "GIT_INDEX_FILE=%s", index_path); + strvec_pushf(&diff_files.env, "GIT_INDEX_FILE=%s", index_path); if (start_command(&diff_files)) die("could not obtain raw diff"); fp = xfdopen(diff_files.out, "r"); @@ -675,7 +675,7 @@ child->git_cmd = 1; child->dir = prefix; - strvec_pushv(&child->env_array, env); + strvec_pushv(&child->env, env); return run_command(child); } diff -Nru git-2.36.1/builtin/fast-export.c git-2.37.2/builtin/fast-export.c --- git-2.36.1/builtin/fast-export.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/fast-export.c 2022-08-11 05:06:00.000000000 +0000 @@ -1276,6 +1276,7 @@ printf("done\n"); refspec_clear(&refspecs); + release_revisions(&revs); return 0; } diff -Nru git-2.36.1/builtin/fast-import.c git-2.37.2/builtin/fast-import.c --- git-2.36.1/builtin/fast-import.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/fast-import.c 2022-08-11 05:06:00.000000000 +0000 @@ -3465,7 +3465,7 @@ pack_idx_opts.version = indexversion_value; if (pack_idx_opts.version > 2) git_die_config("pack.indexversion", - "bad pack.indexversion=%"PRIu32, pack_idx_opts.version); + "bad pack.indexVersion=%"PRIu32, pack_idx_opts.version); } if (!git_config_get_ulong("pack.packsizelimit", &packsizelimit_value)) max_packsize = packsizelimit_value; diff -Nru git-2.36.1/builtin/fetch.c git-2.37.2/builtin/fetch.c --- git-2.36.1/builtin/fetch.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/fetch.c 2022-08-11 05:06:00.000000000 +0000 @@ -1440,6 +1440,7 @@ const struct worktree *wt; for (; ref_map; ref_map = ref_map->next) if (ref_map->peer_ref && + starts_with(ref_map->peer_ref->name, "refs/heads/") && (wt = find_shared_symref(worktrees, "HEAD", ref_map->peer_ref->name)) && !wt->is_bare) @@ -2187,6 +2188,10 @@ else if (argc > 1) die(_("fetch --all does not make sense with refspecs")); (void) for_each_remote(get_one_remote_for_fetch, &list); + + /* do not do fetch_multiple() of one */ + if (list.nr == 1) + remote = remote_get(list.items[0].string); } else if (argc == 0) { /* No arguments -- use default remote */ remote = remote_get(NULL); @@ -2261,7 +2266,17 @@ result = fetch_multiple(&list, max_children); } - if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) { + + /* + * This is only needed after fetch_one(), which does not fetch + * submodules by itself. + * + * When we fetch from multiple remotes, fetch_multiple() has + * already updated submodules to grab commits necessary for + * the fetched history from each remote, so there is no need + * to fetch submodules from here. + */ + if (!result && remote && (recurse_submodules != RECURSE_SUBMODULES_OFF)) { struct strvec options = STRVEC_INIT; int max_children = max_jobs; diff -Nru git-2.36.1/builtin/fsck.c git-2.37.2/builtin/fsck.c --- git-2.36.1/builtin/fsck.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/fsck.c 2022-08-11 05:06:00.000000000 +0000 @@ -19,6 +19,7 @@ #include "decorate.h" #include "packfile.h" #include "object-store.h" +#include "resolve-undo.h" #include "run-command.h" #include "worktree.h" @@ -757,6 +758,43 @@ return err; } +static int fsck_resolve_undo(struct index_state *istate) +{ + struct string_list_item *item; + struct string_list *resolve_undo = istate->resolve_undo; + + if (!resolve_undo) + return 0; + + for_each_string_list_item(item, resolve_undo) { + const char *path = item->string; + struct resolve_undo_info *ru = item->util; + int i; + + if (!ru) + continue; + for (i = 0; i < 3; i++) { + struct object *obj; + + if (!ru->mode[i] || !S_ISREG(ru->mode[i])) + continue; + + obj = parse_object(the_repository, &ru->oid[i]); + if (!obj) { + error(_("%s: invalid sha1 pointer in resolve-undo"), + oid_to_hex(&ru->oid[i])); + errors_found |= ERROR_REFS; + continue; + } + obj->flags |= USED; + fsck_put_object_name(&fsck_walk_options, &ru->oid[i], + ":(%d):%s", i, path); + mark_object_reachable(obj); + } + } + return 0; +} + static void mark_object_for_connectivity(const struct object_id *oid) { struct object *obj = lookup_unknown_object(the_repository, oid); @@ -938,6 +976,7 @@ } if (active_cache_tree) fsck_cache_tree(active_cache_tree); + fsck_resolve_undo(&the_index); } check_connectivity(); diff -Nru git-2.36.1/builtin/fsmonitor--daemon.c git-2.37.2/builtin/fsmonitor--daemon.c --- git-2.36.1/builtin/fsmonitor--daemon.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/fsmonitor--daemon.c 2022-08-11 05:06:00.000000000 +0000 @@ -3,6 +3,7 @@ #include "parse-options.h" #include "fsmonitor.h" #include "fsmonitor-ipc.h" +#include "compat/fsmonitor/fsm-health.h" #include "compat/fsmonitor/fsm-listen.h" #include "fsmonitor--daemon.h" #include "simple-ipc.h" @@ -1136,6 +1137,18 @@ pthread_mutex_unlock(&state->main_lock); } +static void *fsm_health__thread_proc(void *_state) +{ + struct fsmonitor_daemon_state *state = _state; + + trace2_thread_start("fsm-health"); + + fsm_health__loop(state); + + trace2_thread_exit(); + return NULL; +} + static void *fsm_listen__thread_proc(void *_state) { struct fsmonitor_daemon_state *state = _state; @@ -1174,6 +1187,9 @@ */ .uds_disallow_chdir = 0 }; + int health_started = 0; + int listener_started = 0; + int err = 0; /* * Start the IPC thread pool before the we've started the file @@ -1181,11 +1197,11 @@ * before we need it. */ if (ipc_server_run_async(&state->ipc_server_data, - fsmonitor_ipc__get_path(), &ipc_opts, + state->path_ipc.buf, &ipc_opts, handle_client, state)) return error_errno( _("could not start IPC thread pool on '%s'"), - fsmonitor_ipc__get_path()); + state->path_ipc.buf); /* * Start the fsmonitor listener thread to collect filesystem @@ -1194,15 +1210,31 @@ if (pthread_create(&state->listener_thread, NULL, fsm_listen__thread_proc, state) < 0) { ipc_server_stop_async(state->ipc_server_data); - ipc_server_await(state->ipc_server_data); + err = error(_("could not start fsmonitor listener thread")); + goto cleanup; + } + listener_started = 1; - return error(_("could not start fsmonitor listener thread")); + /* + * Start the health thread to watch over our process. + */ + if (pthread_create(&state->health_thread, NULL, + fsm_health__thread_proc, state) < 0) { + ipc_server_stop_async(state->ipc_server_data); + err = error(_("could not start fsmonitor health thread")); + goto cleanup; } + health_started = 1; /* * The daemon is now fully functional in background threads. + * Our primary thread should now just wait while the threads + * do all the work. + */ +cleanup: + /* * Wait for the IPC thread pool to shutdown (whether by client - * request or from filesystem activity). + * request, from filesystem activity, or an error). */ ipc_server_await(state->ipc_server_data); @@ -1211,15 +1243,29 @@ * event from the IPC thread pool, but it doesn't hurt to tell * it again. And wait for it to shutdown. */ - fsm_listen__stop_async(state); - pthread_join(state->listener_thread, NULL); - - return state->error_code; + if (listener_started) { + fsm_listen__stop_async(state); + pthread_join(state->listener_thread, NULL); + } + + if (health_started) { + fsm_health__stop_async(state); + pthread_join(state->health_thread, NULL); + } + + if (err) + return err; + if (state->listen_error_code) + return state->listen_error_code; + if (state->health_error_code) + return state->health_error_code; + return 0; } static int fsmonitor_run_daemon(void) { struct fsmonitor_daemon_state state; + const char *home; int err; memset(&state, 0, sizeof(state)); @@ -1227,7 +1273,8 @@ hashmap_init(&state.cookies, cookies_cmp, NULL, 0); pthread_mutex_init(&state.main_lock, NULL); pthread_cond_init(&state.cookies_cond, NULL); - state.error_code = 0; + state.listen_error_code = 0; + state.health_error_code = 0; state.current_token_data = fsmonitor_new_token_data(); /* Prepare to (recursively) watch the directory. */ @@ -1290,6 +1337,15 @@ strbuf_addch(&state.path_cookie_prefix, '/'); /* + * We create a named-pipe or unix domain socket inside of the + * ".git" directory. (Well, on Windows, we base our named + * pipe in the NPFS on the absolute path of the git + * directory.) + */ + strbuf_init(&state.path_ipc, 0); + strbuf_addstr(&state.path_ipc, absolute_path(fsmonitor_ipc__get_path())); + + /* * Confirm that we can create platform-specific resources for the * filesystem listener before we bother starting all the threads. */ @@ -1298,18 +1354,42 @@ goto done; } + if (fsm_health__ctor(&state)) { + err = error(_("could not initialize health thread")); + goto done; + } + + /* + * CD out of the worktree root directory. + * + * The common Git startup mechanism causes our CWD to be the + * root of the worktree. On Windows, this causes our process + * to hold a locked handle on the CWD. This prevents the + * worktree from being moved or deleted while the daemon is + * running. + * + * We assume that our FS and IPC listener threads have either + * opened all of the handles that they need or will do + * everything using absolute paths. + */ + home = getenv("HOME"); + if (home && *home && chdir(home)) + die_errno(_("could not cd home '%s'"), home); + err = fsmonitor_run_daemon_1(&state); done: pthread_cond_destroy(&state.cookies_cond); pthread_mutex_destroy(&state.main_lock); fsm_listen__dtor(&state); + fsm_health__dtor(&state); ipc_server_free(state.ipc_server_data); strbuf_release(&state.path_worktree_watch); strbuf_release(&state.path_gitdir_watch); strbuf_release(&state.path_cookie_prefix); + strbuf_release(&state.path_ipc); return err; } @@ -1423,6 +1503,7 @@ int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix) { const char *subcmd; + enum fsmonitor_reason reason; int detach_console = 0; struct option options[] = { @@ -1449,6 +1530,23 @@ die(_("invalid 'ipc-threads' value (%d)"), fsmonitor__ipc_threads); + prepare_repo_settings(the_repository); + /* + * If the repo is fsmonitor-compatible, explicitly set IPC-mode + * (without bothering to load the `core.fsmonitor` config settings). + * + * If the repo is not compatible, the repo-settings will be set to + * incompatible rather than IPC, so we can use one of the __get + * routines to detect the discrepancy. + */ + fsm_settings__set_ipc(the_repository); + + reason = fsm_settings__get_reason(the_repository); + if (reason > FSMONITOR_REASON_OK) + die("%s", + fsm_settings__get_incompatible_msg(the_repository, + reason)); + if (!strcmp(subcmd, "start")) return !!try_to_start_background_daemon(); diff -Nru git-2.36.1/builtin/gc.c git-2.37.2/builtin/gc.c --- git-2.36.1/builtin/gc.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/gc.c 2022-08-11 05:06:00.000000000 +0000 @@ -42,6 +42,7 @@ static int pack_refs = 1; static int prune_reflogs = 1; +static int cruft_packs = 0; static int aggressive_depth = 50; static int aggressive_window = 250; static int gc_auto_threshold = 6700; @@ -152,6 +153,7 @@ git_config_get_int("gc.auto", &gc_auto_threshold); git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit); git_config_get_bool("gc.autodetach", &detach_auto); + git_config_get_bool("gc.cruftpacks", &cruft_packs); git_config_get_expiry("gc.pruneexpire", &prune_expire); git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire); git_config_get_expiry("gc.logexpiry", &gc_log_expire); @@ -331,7 +333,11 @@ { if (prune_expire && !strcmp(prune_expire, "now")) strvec_push(&repack, "-a"); - else { + else if (cruft_packs) { + strvec_push(&repack, "--cruft"); + if (prune_expire) + strvec_pushf(&repack, "--cruft-expiration=%s", prune_expire); + } else { strvec_push(&repack, "-A"); if (prune_expire) strvec_pushf(&repack, "--unpack-unreachable=%s", prune_expire); @@ -446,7 +452,7 @@ fscanf(fp, scan_fmt, &pid, locking_host) == 2 && /* be gentle to concurrent "gc" on remote hosts */ (strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM); - if (fp != NULL) + if (fp) fclose(fp); if (should_exit) { if (fd >= 0) @@ -551,6 +557,7 @@ { OPTION_STRING, 0, "prune", &prune_expire, N_("date"), N_("prune unreferenced objects"), PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire }, + OPT_BOOL(0, "cruft", &cruft_packs, N_("pack unreferenced objects separately")), OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")), OPT_BOOL_F(0, "auto", &auto_gc, N_("enable auto-gc mode"), PARSE_OPT_NOCOMPLETE), @@ -574,7 +581,7 @@ /* default expiry time, overwritten in gc_config */ gc_config(); if (parse_expiry_date(gc_log_expire, &gc_log_expire_time)) - die(_("failed to parse gc.logexpiry value %s"), gc_log_expire); + die(_("failed to parse gc.logExpiry value %s"), gc_log_expire); if (pack_refs < 0) pack_refs = !is_bare_repository(); @@ -670,6 +677,7 @@ die(FAILED_RUN, repack.v[0]); if (prune_expire) { + /* run `git prune` even if using cruft packs */ strvec_push(&prune, prune_expire); if (quiet) strvec_push(&prune, "--no-progress"); @@ -2238,7 +2246,7 @@ goto error; } file = fopen_or_warn(filename, "w"); - if (file == NULL) + if (!file) goto error; unit = "# This file was created and is maintained by Git.\n" @@ -2267,7 +2275,7 @@ filename = xdg_config_home_systemd("git-maintenance@.service"); file = fopen_or_warn(filename, "w"); - if (file == NULL) + if (!file) goto error; unit = "# This file was created and is maintained by Git.\n" diff -Nru git-2.36.1/builtin/index-pack.c git-2.37.2/builtin/index-pack.c --- git-2.36.1/builtin/index-pack.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/index-pack.c 2022-08-11 05:06:00.000000000 +0000 @@ -1575,7 +1575,7 @@ if (!strcmp(k, "pack.indexversion")) { opts->version = git_config_int(k, v); if (opts->version > 2) - die(_("bad pack.indexversion=%"PRIu32), opts->version); + die(_("bad pack.indexVersion=%"PRIu32), opts->version); return 0; } if (!strcmp(k, "pack.threads")) { @@ -1942,11 +1942,11 @@ free(objects); strbuf_release(&index_name_buf); strbuf_release(&rev_index_name_buf); - if (pack_name == NULL) + if (!pack_name) free((void *) curr_pack); - if (index_name == NULL) + if (!index_name) free((void *) curr_index); - if (rev_index_name == NULL) + if (!rev_index_name) free((void *) curr_rev_index); /* diff -Nru git-2.36.1/builtin/log.c git-2.37.2/builtin/log.c --- git-2.36.1/builtin/log.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/log.c 2022-08-11 05:06:00.000000000 +0000 @@ -231,7 +231,8 @@ } if (mailmap) { - rev->mailmap = xcalloc(1, sizeof(struct string_list)); + rev->mailmap = xmalloc(sizeof(struct string_list)); + string_list_init_nodup(rev->mailmap); read_mailmap(rev->mailmap); } @@ -294,6 +295,12 @@ cmd_log_init_finish(argc, argv, prefix, rev, opt); } +static int cmd_log_deinit(int ret, struct rev_info *rev) +{ + release_revisions(rev); + return ret; +} + /* * This gives a rough estimate for how many commits we * will print out in the list. @@ -565,7 +572,7 @@ cmd_log_init(argc, argv, prefix, &rev, &opt); if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; - return cmd_log_walk(&rev); + return cmd_log_deinit(cmd_log_walk(&rev), &rev); } static void show_tagger(const char *buf, struct rev_info *rev) @@ -669,6 +676,11 @@ init_log_defaults(); git_config(git_log_config, NULL); + if (the_repository->gitdir) { + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + } + memset(&match_all, 0, sizeof(match_all)); repo_init_revisions(the_repository, &rev, prefix); git_config(grep_config, &rev.grep_filter); @@ -684,7 +696,7 @@ cmd_log_init(argc, argv, prefix, &rev, &opt); if (!rev.no_walk) - return cmd_log_walk(&rev); + return cmd_log_deinit(cmd_log_walk(&rev), &rev); count = rev.pending.nr; objects = rev.pending.objects; @@ -744,8 +756,7 @@ rev.diffopt.no_free = 0; diff_free(&rev.diffopt); - free(objects); - return ret; + return cmd_log_deinit(ret, &rev); } /* @@ -773,7 +784,7 @@ rev.always_show_header = 1; cmd_log_init_finish(argc, argv, prefix, &rev, &opt); - return cmd_log_walk(&rev); + return cmd_log_deinit(cmd_log_walk(&rev), &rev); } static void log_setup_revisions_tweak(struct rev_info *rev, @@ -804,7 +815,7 @@ opt.revarg_opt = REVARG_COMMITTISH; opt.tweak = log_setup_revisions_tweak; cmd_log_init(argc, argv, prefix, &rev, &opt); - return cmd_log_walk(&rev); + return cmd_log_deinit(cmd_log_walk(&rev), &rev); } /* format-patch */ @@ -1025,7 +1036,7 @@ if (!quiet) printf("%s\n", filename.buf + outdir_offset); - if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL) { + if (!(rev->diffopt.file = fopen(filename.buf, "w"))) { error_errno(_("cannot open patch file %s"), filename.buf); strbuf_release(&filename); return -1; @@ -1759,6 +1770,7 @@ struct commit *commit; struct commit **list = NULL; struct rev_info rev; + char *to_free = NULL; struct setup_revision_opt s_r_opt; int nr = 0, total, i; int use_stdout = 0; @@ -1960,7 +1972,7 @@ strbuf_addch(&buf, '\n'); } - rev.extra_headers = strbuf_detach(&buf, NULL); + rev.extra_headers = to_free = strbuf_detach(&buf, NULL); if (from) { if (split_ident_line(&rev.from_ident, from, strlen(from))) @@ -2181,8 +2193,10 @@ prepare_bases(&bases, base, list, nr); } - if (in_reply_to || thread || cover_letter) - rev.ref_message_ids = xcalloc(1, sizeof(struct string_list)); + if (in_reply_to || thread || cover_letter) { + rev.ref_message_ids = xmalloc(sizeof(*rev.ref_message_ids)); + string_list_init_nodup(rev.ref_message_ids); + } if (in_reply_to) { const char *msgid = clean_message_id(in_reply_to); string_list_append(rev.ref_message_ids, msgid); @@ -2289,8 +2303,11 @@ strbuf_release(&rdiff1); strbuf_release(&rdiff2); strbuf_release(&rdiff_title); - UNLEAK(rev); - return 0; + free(to_free); + if (rev.ref_message_ids) + string_list_clear(rev.ref_message_ids, 0); + free(rev.ref_message_ids); + return cmd_log_deinit(0, &rev); } static int add_pending_commit(const char *arg, struct rev_info *revs, int flags) diff -Nru git-2.36.1/builtin/ls-remote.c git-2.37.2/builtin/ls-remote.c --- git-2.36.1/builtin/ls-remote.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/ls-remote.c 2022-08-11 05:06:00.000000000 +0000 @@ -114,7 +114,7 @@ } transport = transport_get(remote, NULL); - if (uploadpack != NULL) + if (uploadpack) transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack); if (server_options.nr) transport->server_options = &server_options; diff -Nru git-2.36.1/builtin/mailsplit.c git-2.37.2/builtin/mailsplit.c --- git-2.36.1/builtin/mailsplit.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/mailsplit.c 2022-08-11 05:06:00.000000000 +0000 @@ -120,7 +120,7 @@ for (sub = subs; *sub; ++sub) { free(name); name = xstrfmt("%s/%s", path, *sub); - if ((dir = opendir(name)) == NULL) { + if (!(dir = opendir(name))) { if (errno == ENOENT) continue; error_errno("cannot opendir %s", name); diff -Nru git-2.36.1/builtin/merge.c git-2.37.2/builtin/merge.c --- git-2.36.1/builtin/merge.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/merge.c 2022-08-11 05:06:00.000000000 +0000 @@ -443,6 +443,7 @@ } write_file_buf(git_path_squash_msg(the_repository), out.buf, out.len); strbuf_release(&out); + release_revisions(&rev); } static void finish(struct commit *head_commit, @@ -998,6 +999,7 @@ */ cnt += count_unmerged_entries(); + release_revisions(&rev); return cnt; } diff -Nru git-2.36.1/builtin/mktree.c git-2.37.2/builtin/mktree.c --- git-2.36.1/builtin/mktree.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/mktree.c 2022-08-11 05:06:00.000000000 +0000 @@ -74,6 +74,7 @@ unsigned mode; enum object_type mode_type; /* object type derived from mode */ enum object_type obj_type; /* object type derived from sha */ + struct object_info oi = OBJECT_INFO_INIT; char *path, *to_free = NULL; struct object_id oid; @@ -116,8 +117,14 @@ path, ptr, type_name(mode_type)); } - /* Check the type of object identified by sha1 */ - obj_type = oid_object_info(the_repository, &oid, NULL); + /* Check the type of object identified by oid without fetching objects */ + oi.typep = &obj_type; + if (oid_object_info_extended(the_repository, &oid, &oi, + OBJECT_INFO_LOOKUP_REPLACE | + OBJECT_INFO_QUICK | + OBJECT_INFO_SKIP_FETCH_OBJECT) < 0) + obj_type = -1; + if (obj_type < 0) { if (allow_missing) { ; /* no problem - missing objects are presumed to be of the right type */ diff -Nru git-2.36.1/builtin/multi-pack-index.c git-2.37.2/builtin/multi-pack-index.c --- git-2.36.1/builtin/multi-pack-index.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/multi-pack-index.c 2022-08-11 05:06:00.000000000 +0000 @@ -44,7 +44,7 @@ }; static struct opts_multi_pack_index { - const char *object_dir; + char *object_dir; const char *preferred_pack; const char *refs_snapshot; unsigned long batch_size; @@ -52,9 +52,23 @@ int stdin_packs; } opts; + +static int parse_object_dir(const struct option *opt, const char *arg, + int unset) +{ + free(opts.object_dir); + if (unset) + opts.object_dir = xstrdup(get_object_directory()); + else + opts.object_dir = real_pathdup(arg, 1); + return 0; +} + static struct option common_opts[] = { - OPT_FILENAME(0, "object-dir", &opts.object_dir, - N_("object directory containing set of packfile and pack-index pairs")), + OPT_CALLBACK(0, "object-dir", &opts.object_dir, + N_("directory"), + N_("object directory containing set of packfile and pack-index pairs"), + parse_object_dir), OPT_END(), }; @@ -232,31 +246,40 @@ int cmd_multi_pack_index(int argc, const char **argv, const char *prefix) { + int res; struct option *builtin_multi_pack_index_options = common_opts; git_config(git_default_config, NULL); + if (the_repository && + the_repository->objects && + the_repository->objects->odb) + opts.object_dir = xstrdup(the_repository->objects->odb->path); + argc = parse_options(argc, argv, prefix, builtin_multi_pack_index_options, builtin_multi_pack_index_usage, PARSE_OPT_STOP_AT_NON_OPTION); - if (!opts.object_dir) - opts.object_dir = get_object_directory(); - if (!argc) goto usage; if (!strcmp(argv[0], "repack")) - return cmd_multi_pack_index_repack(argc, argv); + res = cmd_multi_pack_index_repack(argc, argv); else if (!strcmp(argv[0], "write")) - return cmd_multi_pack_index_write(argc, argv); + res = cmd_multi_pack_index_write(argc, argv); else if (!strcmp(argv[0], "verify")) - return cmd_multi_pack_index_verify(argc, argv); + res = cmd_multi_pack_index_verify(argc, argv); else if (!strcmp(argv[0], "expire")) - return cmd_multi_pack_index_expire(argc, argv); + res = cmd_multi_pack_index_expire(argc, argv); + else { + error(_("unrecognized subcommand: %s"), argv[0]); + goto usage; + } + + free(opts.object_dir); + return res; - error(_("unrecognized subcommand: %s"), argv[0]); usage: usage_with_options(builtin_multi_pack_index_usage, builtin_multi_pack_index_options); diff -Nru git-2.36.1/builtin/name-rev.c git-2.37.2/builtin/name-rev.c --- git-2.36.1/builtin/name-rev.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/name-rev.c 2022-08-11 05:06:00.000000000 +0000 @@ -577,7 +577,7 @@ N_("ignore refs matching ")), OPT_GROUP(""), OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")), - OPT_BOOL(0, "stdin", &transform_stdin, N_("deprecated: use annotate-stdin instead")), + OPT_BOOL(0, "stdin", &transform_stdin, N_("deprecated: use --annotate-stdin instead")), OPT_BOOL(0, "annotate-stdin", &annotate_stdin, N_("annotate text from stdin")), OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")), OPT_BOOL(0, "always", &always, diff -Nru git-2.36.1/builtin/pack-objects.c git-2.37.2/builtin/pack-objects.c --- git-2.36.1/builtin/pack-objects.c 2022-05-05 21:46:18.000000000 +0000 +++ git-2.37.2/builtin/pack-objects.c 2022-08-11 05:06:00.000000000 +0000 @@ -36,6 +36,7 @@ #include "trace2.h" #include "shallow.h" #include "promisor-remote.h" +#include "pack-mtimes.h" /* * Objects we are going to pack are collected in the `to_pack` structure. @@ -194,6 +195,8 @@ static int keep_unreachable, unpack_unreachable, include_tag; static timestamp_t unpack_unreachable_expiration; static int pack_loose_unreachable; +static int cruft; +static timestamp_t cruft_expiration; static int local; static int have_non_local_packs; static int incremental; @@ -1260,9 +1263,13 @@ &to_pack, written_list, nr_written); } + if (cruft) + pack_idx_opts.flags |= WRITE_MTIMES; + stage_tmp_packfiles(&tmpname, pack_tmp_name, written_list, nr_written, - &pack_idx_opts, hash, &idx_tmp_name); + &to_pack, &pack_idx_opts, hash, + &idx_tmp_name); if (write_bitmap_index) { size_t tmpname_len = tmpname.len; @@ -1357,6 +1364,9 @@ if (incremental) return 0; + if (!is_pack_valid(p)) + return -1; + /* * When asked to do --local (do not include an object that appears in a * pack we borrow from elsewhere) or --honor-pack-keep (do not include @@ -1472,6 +1482,9 @@ want = want_found_object(oid, exclude, *found_pack); if (want != -1) return want; + + *found_pack = NULL; + *found_offset = 0; } for (m = get_multi_pack_index(the_repository); m; m = m->next) { @@ -1515,13 +1528,13 @@ return 1; } -static void create_object_entry(const struct object_id *oid, - enum object_type type, - uint32_t hash, - int exclude, - int no_try_delta, - struct packed_git *found_pack, - off_t found_offset) +static struct object_entry *create_object_entry(const struct object_id *oid, + enum object_type type, + uint32_t hash, + int exclude, + int no_try_delta, + struct packed_git *found_pack, + off_t found_offset) { struct object_entry *entry; @@ -1538,6 +1551,8 @@ } entry->no_try_delta = no_try_delta; + + return entry; } static const char no_closure_warning[] = N_( @@ -3155,7 +3170,7 @@ if (!strcmp(k, "pack.indexversion")) { pack_idx_opts.version = git_config_int(k, v); if (pack_idx_opts.version > 2) - die(_("bad pack.indexversion=%"PRIu32), + die(_("bad pack.indexVersion=%"PRIu32), pack_idx_opts.version); return 0; } @@ -3201,10 +3216,8 @@ uint32_t pos, void *_data) { - struct rev_info *revs = _data; - struct object_info oi = OBJECT_INFO_INIT; off_t ofs; - enum object_type type; + enum object_type type = OBJ_NONE; display_progress(progress_state, ++nr_seen); @@ -3215,19 +3228,24 @@ if (!want_object_in_pack(oid, 0, &p, &ofs)) return 0; - oi.typep = &type; - if (packed_object_info(the_repository, p, ofs, &oi) < 0) - die(_("could not get type of object %s in pack %s"), - oid_to_hex(oid), p->pack_name); - else if (type == OBJ_COMMIT) { - /* - * commits in included packs are used as starting points for the - * subsequent revision walk - */ - add_pending_oid(revs, NULL, oid, 0); - } + if (p) { + struct rev_info *revs = _data; + struct object_info oi = OBJECT_INFO_INIT; + + oi.typep = &type; + if (packed_object_info(the_repository, p, ofs, &oi) < 0) { + die(_("could not get type of object %s in pack %s"), + oid_to_hex(oid), p->pack_name); + } else if (type == OBJ_COMMIT) { + /* + * commits in included packs are used as starting points for the + * subsequent revision walk + */ + add_pending_oid(revs, NULL, oid, 0); + } - stdin_packs_found_nr++; + stdin_packs_found_nr++; + } create_object_entry(oid, type, 0, 0, 0, p, ofs); @@ -3346,6 +3364,8 @@ struct packed_git *p = item->util; if (!p) die(_("could not find pack '%s'"), item->string); + if (!is_pack_valid(p)) + die(_("packfile %s cannot be accessed"), p->pack_name); } /* @@ -3369,8 +3389,6 @@ for_each_string_list_item(item, &include_packs) { struct packed_git *p = item->util; - if (!p) - die(_("could not find pack '%s'"), item->string); for_each_object_in_pack(p, add_object_entry_from_pack, &revs, @@ -3394,6 +3412,217 @@ string_list_clear(&exclude_packs, 0); } +static void add_cruft_object_entry(const struct object_id *oid, enum object_type type, + struct packed_git *pack, off_t offset, + const char *name, uint32_t mtime) +{ + struct object_entry *entry; + + display_progress(progress_state, ++nr_seen); + + entry = packlist_find(&to_pack, oid); + if (entry) { + if (name) { + entry->hash = pack_name_hash(name); + entry->no_try_delta = no_try_delta(name); + } + } else { + if (!want_object_in_pack(oid, 0, &pack, &offset)) + return; + if (!pack && type == OBJ_BLOB && !has_loose_object(oid)) { + /* + * If a traversed tree has a missing blob then we want + * to avoid adding that missing object to our pack. + * + * This only applies to missing blobs, not trees, + * because the traversal needs to parse sub-trees but + * not blobs. + * + * Note we only perform this check when we couldn't + * already find the object in a pack, so we're really + * limited to "ensure non-tip blobs which don't exist in + * packs do exist via loose objects". Confused? + */ + return; + } + + entry = create_object_entry(oid, type, pack_name_hash(name), + 0, name && no_try_delta(name), + pack, offset); + } + + if (mtime > oe_cruft_mtime(&to_pack, entry)) + oe_set_cruft_mtime(&to_pack, entry, mtime); + return; +} + +static void show_cruft_object(struct object *obj, const char *name, void *data) +{ + /* + * if we did not record it earlier, it's at least as old as our + * expiration value. Rather than find it exactly, just use that + * value. This may bump it forward from its real mtime, but it + * will still be "too old" next time we run with the same + * expiration. + * + * if obj does appear in the packing list, this call is a noop (or may + * set the namehash). + */ + add_cruft_object_entry(&obj->oid, obj->type, NULL, 0, name, cruft_expiration); +} + +static void show_cruft_commit(struct commit *commit, void *data) +{ + show_cruft_object((struct object*)commit, NULL, data); +} + +static int cruft_include_check_obj(struct object *obj, void *data) +{ + return !has_object_kept_pack(&obj->oid, IN_CORE_KEEP_PACKS); +} + +static int cruft_include_check(struct commit *commit, void *data) +{ + return cruft_include_check_obj((struct object*)commit, data); +} + +static void set_cruft_mtime(const struct object *object, + struct packed_git *pack, + off_t offset, time_t mtime) +{ + add_cruft_object_entry(&object->oid, object->type, pack, offset, NULL, + mtime); +} + +static void mark_pack_kept_in_core(struct string_list *packs, unsigned keep) +{ + struct string_list_item *item = NULL; + for_each_string_list_item(item, packs) { + struct packed_git *p = item->util; + if (!p) + die(_("could not find pack '%s'"), item->string); + p->pack_keep_in_core = keep; + } +} + +static void add_unreachable_loose_objects(void); +static void add_objects_in_unpacked_packs(void); + +static void enumerate_cruft_objects(void) +{ + if (progress) + progress_state = start_progress(_("Enumerating cruft objects"), 0); + + add_objects_in_unpacked_packs(); + add_unreachable_loose_objects(); + + stop_progress(&progress_state); +} + +static void enumerate_and_traverse_cruft_objects(struct string_list *fresh_packs) +{ + struct packed_git *p; + struct rev_info revs; + int ret; + + repo_init_revisions(the_repository, &revs, NULL); + + revs.tag_objects = 1; + revs.tree_objects = 1; + revs.blob_objects = 1; + + revs.include_check = cruft_include_check; + revs.include_check_obj = cruft_include_check_obj; + + revs.ignore_missing_links = 1; + + if (progress) + progress_state = start_progress(_("Enumerating cruft objects"), 0); + ret = add_unseen_recent_objects_to_traversal(&revs, cruft_expiration, + set_cruft_mtime, 1); + stop_progress(&progress_state); + + if (ret) + die(_("unable to add cruft objects")); + + /* + * Re-mark only the fresh packs as kept so that objects in + * unknown packs do not halt the reachability traversal early. + */ + for (p = get_all_packs(the_repository); p; p = p->next) + p->pack_keep_in_core = 0; + mark_pack_kept_in_core(fresh_packs, 1); + + if (prepare_revision_walk(&revs)) + die(_("revision walk setup failed")); + if (progress) + progress_state = start_progress(_("Traversing cruft objects"), 0); + nr_seen = 0; + traverse_commit_list(&revs, show_cruft_commit, show_cruft_object, NULL); + + stop_progress(&progress_state); +} + +static void read_cruft_objects(void) +{ + struct strbuf buf = STRBUF_INIT; + struct string_list discard_packs = STRING_LIST_INIT_DUP; + struct string_list fresh_packs = STRING_LIST_INIT_DUP; + struct packed_git *p; + + ignore_packed_keep_in_core = 1; + + while (strbuf_getline(&buf, stdin) != EOF) { + if (!buf.len) + continue; + + if (*buf.buf == '-') + string_list_append(&discard_packs, buf.buf + 1); + else + string_list_append(&fresh_packs, buf.buf); + strbuf_reset(&buf); + } + + string_list_sort(&discard_packs); + string_list_sort(&fresh_packs); + + for (p = get_all_packs(the_repository); p; p = p->next) { + const char *pack_name = pack_basename(p); + struct string_list_item *item; + + item = string_list_lookup(&fresh_packs, pack_name); + if (!item) + item = string_list_lookup(&discard_packs, pack_name); + + if (item) { + item->util = p; + } else { + /* + * This pack wasn't mentioned in either the "fresh" or + * "discard" list, so the caller didn't know about it. + * + * Mark it as kept so that its objects are ignored by + * add_unseen_recent_objects_to_traversal(). We'll + * unmark it before starting the traversal so it doesn't + * halt the traversal early. + */ + p->pack_keep_in_core = 1; + } + } + + mark_pack_kept_in_core(&fresh_packs, 1); + mark_pack_kept_in_core(&discard_packs, 0); + + if (cruft_expiration) + enumerate_and_traverse_cruft_objects(&fresh_packs); + else + enumerate_cruft_objects(); + + strbuf_release(&buf); + string_list_clear(&discard_packs, 0); + string_list_clear(&fresh_packs, 0); +} + static void read_object_list_from_stdin(void) { char line[GIT_MAX_HEXSZ + 1 + PATH_MAX + 2]; @@ -3526,7 +3755,24 @@ uint32_t pos, void *_data) { - add_object_entry(oid, OBJ_NONE, "", 0); + if (cruft) { + off_t offset; + time_t mtime; + + if (pack->is_cruft) { + if (load_pack_mtimes(pack) < 0) + die(_("could not load cruft pack .mtimes")); + mtime = nth_packed_mtime(pack, pos); + } else { + mtime = pack->mtime; + } + offset = nth_packed_object_offset(pack, pos); + + add_cruft_object_entry(oid, OBJ_NONE, pack, offset, + NULL, mtime); + } else { + add_object_entry(oid, OBJ_NONE, "", 0); + } return 0; } @@ -3550,7 +3796,19 @@ return 0; } - add_object_entry(oid, type, "", 0); + if (cruft) { + struct stat st; + if (stat(path, &st) < 0) { + if (errno == ENOENT) + return 0; + return error_errno("unable to stat %s", oid_to_hex(oid)); + } + + add_cruft_object_entry(oid, type, NULL, 0, NULL, + st.st_mtime); + } else { + add_object_entry(oid, type, "", 0); + } return 0; } @@ -3790,7 +4048,7 @@ if (unpack_unreachable_expiration) { revs->ignore_missing_links = 1; if (add_unseen_recent_objects_to_traversal(revs, - unpack_unreachable_expiration)) + unpack_unreachable_expiration, NULL, 0)) die(_("unable to add recent objects")); if (prepare_revision_walk(revs)) die(_("revision walk setup failed")); @@ -3867,6 +4125,20 @@ return 0; } +static int option_parse_cruft_expiration(const struct option *opt, + const char *arg, int unset) +{ + if (unset) { + cruft = 0; + cruft_expiration = 0; + } else { + cruft = 1; + if (arg) + cruft_expiration = approxidate(arg); + } + return 0; +} + struct po_filter_data { unsigned have_revs:1; struct rev_info revs; @@ -3956,6 +4228,10 @@ OPT_CALLBACK_F(0, "unpack-unreachable", NULL, N_("time"), N_("unpack unreachable objects newer than