diff -Nru git-2.25.1/abspath.c git-2.34.1/abspath.c --- git-2.25.1/abspath.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/abspath.c 2021-11-24 19:29:21.000000000 +0000 @@ -67,19 +67,15 @@ #endif /* - * Return the real path (i.e., absolute path, with symlinks resolved - * and extra slashes removed) equivalent to the specified path. (If - * you want an absolute path but don't mind links, use - * absolute_path().) Places the resolved realpath in the provided strbuf. - * - * The directory part of path (i.e., everything up to the last - * dir_sep) must denote a valid, existing directory, but the last - * component need not exist. If die_on_error is set, then die with an - * informative error message if there is a problem. Otherwise, return - * NULL on errors (without generating any output). + * If set, any number of trailing components may be missing; otherwise, only one + * may be. */ -char *strbuf_realpath(struct strbuf *resolved, const char *path, - int die_on_error) +#define REALPATH_MANY_MISSING (1 << 0) +/* Should we die if there's an error? */ +#define REALPATH_DIE_ON_ERROR (1 << 1) + +static char *strbuf_realpath_1(struct strbuf *resolved, const char *path, + int flags) { struct strbuf remaining = STRBUF_INIT; struct strbuf next = STRBUF_INIT; @@ -89,7 +85,7 @@ struct stat st; if (!*path) { - if (die_on_error) + if (flags & REALPATH_DIE_ON_ERROR) die("The empty string is not a valid path"); else goto error_out; @@ -101,7 +97,7 @@ if (!resolved->len) { /* relative path; can use CWD as the initial resolved path */ if (strbuf_getcwd(resolved)) { - if (die_on_error) + if (flags & REALPATH_DIE_ON_ERROR) die_errno("unable to get current working directory"); else goto error_out; @@ -129,8 +125,9 @@ if (lstat(resolved->buf, &st)) { /* error out unless this was the last component */ - if (errno != ENOENT || remaining.len) { - if (die_on_error) + if (errno != ENOENT || + (!(flags & REALPATH_MANY_MISSING) && remaining.len)) { + if (flags & REALPATH_DIE_ON_ERROR) die_errno("Invalid path '%s'", resolved->buf); else @@ -143,7 +140,7 @@ if (num_symlinks++ > MAXSYMLINKS) { errno = ELOOP; - if (die_on_error) + if (flags & REALPATH_DIE_ON_ERROR) die("More than %d nested symlinks " "on path '%s'", MAXSYMLINKS, path); else @@ -153,7 +150,7 @@ len = strbuf_readlink(&symlink, resolved->buf, st.st_size); if (len < 0) { - if (die_on_error) + if (flags & REALPATH_DIE_ON_ERROR) die_errno("Invalid symlink '%s'", resolved->buf); else @@ -203,19 +200,34 @@ } /* - * Resolve `path` into an absolute, cleaned-up path. The return value - * comes from a shared buffer. + * Return the real path (i.e., absolute path, with symlinks resolved + * and extra slashes removed) equivalent to the specified path. (If + * you want an absolute path but don't mind links, use + * absolute_path().) Places the resolved realpath in the provided strbuf. + * + * The directory part of path (i.e., everything up to the last + * dir_sep) must denote a valid, existing directory, but the last + * component need not exist. If die_on_error is set, then die with an + * informative error message if there is a problem. Otherwise, return + * NULL on errors (without generating any output). */ -const char *real_path(const char *path) +char *strbuf_realpath(struct strbuf *resolved, const char *path, + int die_on_error) { - static struct strbuf realpath = STRBUF_INIT; - return strbuf_realpath(&realpath, path, 1); + return strbuf_realpath_1(resolved, path, + die_on_error ? REALPATH_DIE_ON_ERROR : 0); } -const char *real_path_if_valid(const char *path) +/* + * Just like strbuf_realpath, but allows an arbitrary number of path + * components to be missing. + */ +char *strbuf_realpath_forgiving(struct strbuf *resolved, const char *path, + int die_on_error) { - static struct strbuf realpath = STRBUF_INIT; - return strbuf_realpath(&realpath, path, 0); + return strbuf_realpath_1(resolved, path, + ((die_on_error ? REALPATH_DIE_ON_ERROR : 0) | + REALPATH_MANY_MISSING)); } char *real_pathdup(const char *path, int die_on_error) @@ -233,7 +245,7 @@ /* * Use this to get an absolute path from a relative one. If you want - * to resolve links, you should use real_path. + * to resolve links, you should use strbuf_realpath. */ const char *absolute_path(const char *path) { diff -Nru git-2.25.1/add-interactive.c git-2.34.1/add-interactive.c --- git-2.25.1/add-interactive.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/add-interactive.c 2021-11-24 19:29:21.000000000 +0000 @@ -9,12 +9,13 @@ #include "lockfile.h" #include "dir.h" #include "run-command.h" +#include "prompt.h" static void init_color(struct repository *r, struct add_i_state *s, - const char *slot_name, char *dst, + const char *section_and_slot, char *dst, const char *default_color) { - char *key = xstrfmt("color.interactive.%s", slot_name); + char *key = xstrfmt("color.%s", section_and_slot); const char *value; if (!s->use_color) @@ -39,19 +40,44 @@ git_config_colorbool("color.interactive", value); s->use_color = want_color(s->use_color); - init_color(r, s, "header", s->header_color, GIT_COLOR_BOLD); - init_color(r, s, "help", s->help_color, GIT_COLOR_BOLD_RED); - init_color(r, s, "prompt", s->prompt_color, GIT_COLOR_BOLD_BLUE); - init_color(r, s, "error", s->error_color, GIT_COLOR_BOLD_RED); - init_color(r, s, "reset", s->reset_color, GIT_COLOR_RESET); - init_color(r, s, "fraginfo", s->fraginfo_color, + init_color(r, s, "interactive.header", s->header_color, GIT_COLOR_BOLD); + init_color(r, s, "interactive.help", s->help_color, GIT_COLOR_BOLD_RED); + init_color(r, s, "interactive.prompt", s->prompt_color, + GIT_COLOR_BOLD_BLUE); + init_color(r, s, "interactive.error", s->error_color, + GIT_COLOR_BOLD_RED); + + init_color(r, s, "diff.frag", s->fraginfo_color, diff_get_color(s->use_color, DIFF_FRAGINFO)); - init_color(r, s, "context", s->context_color, - diff_get_color(s->use_color, DIFF_CONTEXT)); - init_color(r, s, "old", s->file_old_color, + init_color(r, s, "diff.context", s->context_color, "fall back"); + if (!strcmp(s->context_color, "fall back")) + init_color(r, s, "diff.plain", s->context_color, + diff_get_color(s->use_color, DIFF_CONTEXT)); + init_color(r, s, "diff.old", s->file_old_color, diff_get_color(s->use_color, DIFF_FILE_OLD)); - init_color(r, s, "new", s->file_new_color, + init_color(r, s, "diff.new", s->file_new_color, diff_get_color(s->use_color, DIFF_FILE_NEW)); + + strlcpy(s->reset_color, + s->use_color ? GIT_COLOR_RESET : "", COLOR_MAXLEN); + + FREE_AND_NULL(s->interactive_diff_filter); + git_config_get_string("interactive.difffilter", + &s->interactive_diff_filter); + + FREE_AND_NULL(s->interactive_diff_algorithm); + git_config_get_string("diff.algorithm", + &s->interactive_diff_algorithm); + + git_config_get_bool("interactive.singlekey", &s->use_single_key); +} + +void clear_add_i_state(struct add_i_state *s) +{ + FREE_AND_NULL(s->interactive_diff_filter); + FREE_AND_NULL(s->interactive_diff_algorithm); + memset(s, 0, sizeof(*s)); + s->use_color = -1; } /* @@ -76,8 +102,12 @@ int *selected; /* for multi-selections */ size_t min_length, max_length; }; -#define PREFIX_ITEM_LIST_INIT \ - { STRING_LIST_INIT_DUP, STRING_LIST_INIT_NODUP, NULL, 1, 4 } +#define PREFIX_ITEM_LIST_INIT { \ + .items = STRING_LIST_INIT_DUP, \ + .sorted = STRING_LIST_INIT_NODUP, \ + .min_length = 1, \ + .max_length = 4, \ +} static void prefix_item_list_clear(struct prefix_item_list *list) { @@ -175,7 +205,8 @@ else if (index + 1 < list->sorted.nr && starts_with(list->sorted.items[index + 1].string, string)) return -1; - else if (index < list->sorted.nr) + else if (index < list->sorted.nr && + starts_with(list->sorted.items[index].string, string)) item = list->sorted.items[index].util; else return -1; @@ -271,13 +302,12 @@ fputs(singleton ? "> " : ">> ", stdout); fflush(stdout); - if (strbuf_getline(&input, stdin) == EOF) { + if (git_read_line_interactively(&input) == EOF) { putchar('\n'); if (immediate) res = LIST_AND_CHOOSE_QUIT; break; } - strbuf_trim(&input); if (!input.len) break; @@ -326,7 +356,10 @@ if (endp == p + sep) to = from + 1; else if (*endp == '-') { - to = strtoul(++endp, &endp, 10); + if (isdigit(*(++endp))) + to = strtoul(endp, &endp, 10); + else + to = items->items.nr; /* extra characters after the range? */ if (endp != p + sep) from = -1; @@ -343,7 +376,7 @@ if (from < 0 || from >= items->items.nr || (singleton && from + 1 != to)) { - color_fprintf_ln(stdout, s->error_color, + color_fprintf_ln(stderr, s->error_color, _("Huh (%s)?"), p); break; } else if (singleton) { @@ -384,7 +417,7 @@ static void add_file_item(struct string_list *files, const char *name) { - struct file_item *item = xcalloc(sizeof(*item), 1); + struct file_item *item = xcalloc(1, sizeof(*item)); string_list_append(files, name)->util = item; } @@ -447,7 +480,7 @@ add_file_item(s->files, name); - entry = xcalloc(sizeof(*entry), 1); + CALLOC_ARRAY(entry, 1); hashmap_entry_init(&entry->ent, hash); entry->name = s->files->items[s->files->nr - 1].string; entry->item = s->files->items[s->files->nr - 1].util; @@ -536,7 +569,7 @@ if (ps) clear_pathspec(&rev.prune_data); } - hashmap_free_entries(&s.file_map, struct pathname_entry, ent); + hashmap_clear_and_free(&s.file_map, struct pathname_entry, ent); if (unmerged_count) *unmerged_count = s.unmerged_count; if (binary_count) @@ -913,19 +946,19 @@ opts->prompt = N_("Patch update"); count = list_and_choose(s, files, opts); - if (count >= 0) { - struct argv_array args = ARGV_ARRAY_INIT; + if (count > 0) { + struct strvec args = STRVEC_INIT; struct pathspec ps_selected = { 0 }; for (i = 0; i < files->items.nr; i++) if (files->selected[i]) - argv_array_push(&args, - files->items.items[i].string); + strvec_push(&args, + files->items.items[i].string); parse_pathspec(&ps_selected, PATHSPEC_ALL_MAGIC & ~PATHSPEC_LITERAL, - PATHSPEC_LITERAL_PATH, "", args.argv); - res = run_add_p(s->r, &ps_selected); - argv_array_clear(&args); + PATHSPEC_LITERAL_PATH, "", args.v); + res = run_add_p(s->r, ADD_P_ADD, NULL, &ps_selected); + strvec_clear(&args); clear_pathspec(&ps_selected); } @@ -954,19 +987,19 @@ opts->flags = IMMEDIATE; count = list_and_choose(s, files, opts); opts->flags = 0; - if (count >= 0) { - struct argv_array args = ARGV_ARRAY_INIT; + if (count > 0) { + struct strvec args = STRVEC_INIT; - argv_array_pushl(&args, "git", "diff", "-p", "--cached", - oid_to_hex(!is_initial ? &oid : - s->r->hash_algo->empty_tree), - "--", NULL); + strvec_pushl(&args, "git", "diff", "-p", "--cached", + oid_to_hex(!is_initial ? &oid : + s->r->hash_algo->empty_tree), + "--", NULL); for (i = 0; i < files->items.nr; i++) if (files->selected[i]) - argv_array_push(&args, - files->items.items[i].string); - res = run_command_v_opt(args.argv, 0); - argv_array_clear(&args); + strvec_push(&args, + files->items.items[i].string); + res = run_command_v_opt(args.v, 0); + strvec_clear(&args); } putchar('\n'); @@ -1091,7 +1124,7 @@ int res = 0; for (i = 0; i < ARRAY_SIZE(command_list); i++) { - struct command_item *util = xcalloc(sizeof(*util), 1); + struct command_item *util = xcalloc(1, sizeof(*util)); util->command = command_list[i].command; string_list_append(&commands.items, command_list[i].string) ->util = util; @@ -1110,7 +1143,7 @@ print_file_item_data.color = data.color; print_file_item_data.reset = data.reset; - strbuf_addstr(&header, " "); + strbuf_addstr(&header, " "); strbuf_addf(&header, print_file_item_data.modified_fmt, _("staged"), _("unstaged"), _("path")); opts.list_opts.header = header.buf; @@ -1149,6 +1182,7 @@ strbuf_release(&print_file_item_data.worktree); strbuf_release(&header); prefix_item_list_clear(&commands); + clear_add_i_state(&s); return res; } diff -Nru git-2.25.1/add-interactive.h git-2.34.1/add-interactive.h --- git-2.25.1/add-interactive.h 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/add-interactive.h 2021-11-24 19:29:21.000000000 +0000 @@ -15,13 +15,27 @@ char context_color[COLOR_MAXLEN]; char file_old_color[COLOR_MAXLEN]; char file_new_color[COLOR_MAXLEN]; + + int use_single_key; + char *interactive_diff_filter, *interactive_diff_algorithm; }; void init_add_i_state(struct add_i_state *s, struct repository *r); +void clear_add_i_state(struct add_i_state *s); struct repository; struct pathspec; int run_add_i(struct repository *r, const struct pathspec *ps); -int run_add_p(struct repository *r, const struct pathspec *ps); + +enum add_p_mode { + ADD_P_ADD, + ADD_P_STASH, + ADD_P_RESET, + ADD_P_CHECKOUT, + ADD_P_WORKTREE, +}; + +int run_add_p(struct repository *r, enum add_p_mode mode, + const char *revision, const struct pathspec *ps); #endif diff -Nru git-2.25.1/add-patch.c git-2.34.1/add-patch.c --- git-2.25.1/add-patch.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/add-patch.c 2021-11-24 19:29:21.000000000 +0000 @@ -2,19 +2,232 @@ #include "add-interactive.h" #include "strbuf.h" #include "run-command.h" -#include "argv-array.h" +#include "strvec.h" #include "pathspec.h" #include "color.h" #include "diff.h" +#include "compat/terminal.h" +#include "prompt.h" enum prompt_mode_type { - PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_HUNK + PROMPT_MODE_CHANGE = 0, PROMPT_DELETION, PROMPT_ADDITION, PROMPT_HUNK, + PROMPT_MODE_MAX, /* must be last */ }; -static const char *prompt_mode[] = { - N_("Stage mode change [y,n,a,q,d%s,?]? "), - N_("Stage deletion [y,n,a,q,d%s,?]? "), - N_("Stage this hunk [y,n,a,q,d%s,?]? ") +struct patch_mode { + /* + * The magic constant 4 is chosen such that all patch modes + * provide enough space for three command-line arguments followed by a + * trailing `NULL`. + */ + const char *diff_cmd[4], *apply_args[4], *apply_check_args[4]; + unsigned is_reverse:1, index_only:1, apply_for_checkout:1; + const char *prompt_mode[PROMPT_MODE_MAX]; + const char *edit_hunk_hint, *help_patch_text; +}; + +static struct patch_mode patch_mode_add = { + .diff_cmd = { "diff-files", NULL }, + .apply_args = { "--cached", NULL }, + .apply_check_args = { "--cached", NULL }, + .prompt_mode = { + N_("Stage mode change [y,n,q,a,d%s,?]? "), + N_("Stage deletion [y,n,q,a,d%s,?]? "), + N_("Stage addition [y,n,q,a,d%s,?]? "), + N_("Stage this hunk [y,n,q,a,d%s,?]? ") + }, + .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " + "will immediately be marked for staging."), + .help_patch_text = + N_("y - stage this hunk\n" + "n - do not stage this hunk\n" + "q - quit; do not stage this hunk or any of the remaining " + "ones\n" + "a - stage this hunk and all later hunks in the file\n" + "d - do not stage this hunk or any of the later hunks in " + "the file\n") +}; + +static struct patch_mode patch_mode_stash = { + .diff_cmd = { "diff-index", "HEAD", NULL }, + .apply_args = { "--cached", NULL }, + .apply_check_args = { "--cached", NULL }, + .prompt_mode = { + N_("Stash mode change [y,n,q,a,d%s,?]? "), + N_("Stash deletion [y,n,q,a,d%s,?]? "), + N_("Stash addition [y,n,q,a,d%s,?]? "), + N_("Stash this hunk [y,n,q,a,d%s,?]? "), + }, + .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " + "will immediately be marked for stashing."), + .help_patch_text = + N_("y - stash this hunk\n" + "n - do not stash this hunk\n" + "q - quit; do not stash this hunk or any of the remaining " + "ones\n" + "a - stash this hunk and all later hunks in the file\n" + "d - do not stash this hunk or any of the later hunks in " + "the file\n"), +}; + +static struct patch_mode patch_mode_reset_head = { + .diff_cmd = { "diff-index", "--cached", NULL }, + .apply_args = { "-R", "--cached", NULL }, + .apply_check_args = { "-R", "--cached", NULL }, + .is_reverse = 1, + .index_only = 1, + .prompt_mode = { + N_("Unstage mode change [y,n,q,a,d%s,?]? "), + N_("Unstage deletion [y,n,q,a,d%s,?]? "), + N_("Unstage addition [y,n,q,a,d%s,?]? "), + N_("Unstage this hunk [y,n,q,a,d%s,?]? "), + }, + .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " + "will immediately be marked for unstaging."), + .help_patch_text = + N_("y - unstage this hunk\n" + "n - do not unstage this hunk\n" + "q - quit; do not unstage this hunk or any of the remaining " + "ones\n" + "a - unstage this hunk and all later hunks in the file\n" + "d - do not unstage this hunk or any of the later hunks in " + "the file\n"), +}; + +static struct patch_mode patch_mode_reset_nothead = { + .diff_cmd = { "diff-index", "-R", "--cached", NULL }, + .apply_args = { "--cached", NULL }, + .apply_check_args = { "--cached", NULL }, + .index_only = 1, + .prompt_mode = { + N_("Apply mode change to index [y,n,q,a,d%s,?]? "), + N_("Apply deletion to index [y,n,q,a,d%s,?]? "), + N_("Apply addition to index [y,n,q,a,d%s,?]? "), + N_("Apply this hunk to index [y,n,q,a,d%s,?]? "), + }, + .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " + "will immediately be marked for applying."), + .help_patch_text = + N_("y - apply this hunk to index\n" + "n - do not apply this hunk to index\n" + "q - quit; do not apply this hunk or any of the remaining " + "ones\n" + "a - apply this hunk and all later hunks in the file\n" + "d - do not apply this hunk or any of the later hunks in " + "the file\n"), +}; + +static struct patch_mode patch_mode_checkout_index = { + .diff_cmd = { "diff-files", NULL }, + .apply_args = { "-R", NULL }, + .apply_check_args = { "-R", NULL }, + .is_reverse = 1, + .prompt_mode = { + N_("Discard mode change from worktree [y,n,q,a,d%s,?]? "), + N_("Discard deletion from worktree [y,n,q,a,d%s,?]? "), + N_("Discard addition from worktree [y,n,q,a,d%s,?]? "), + N_("Discard this hunk from worktree [y,n,q,a,d%s,?]? "), + }, + .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " + "will immediately be marked for discarding."), + .help_patch_text = + N_("y - discard this hunk from worktree\n" + "n - do not discard this hunk from worktree\n" + "q - quit; do not discard this hunk or any of the remaining " + "ones\n" + "a - discard this hunk and all later hunks in the file\n" + "d - do not discard this hunk or any of the later hunks in " + "the file\n"), +}; + +static struct patch_mode patch_mode_checkout_head = { + .diff_cmd = { "diff-index", NULL }, + .apply_for_checkout = 1, + .apply_check_args = { "-R", NULL }, + .is_reverse = 1, + .prompt_mode = { + N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "), + N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "), + N_("Discard addition from index and worktree [y,n,q,a,d%s,?]? "), + N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "), + }, + .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " + "will immediately be marked for discarding."), + .help_patch_text = + N_("y - discard this hunk from index and worktree\n" + "n - do not discard this hunk from index and worktree\n" + "q - quit; do not discard this hunk or any of the remaining " + "ones\n" + "a - discard this hunk and all later hunks in the file\n" + "d - do not discard this hunk or any of the later hunks in " + "the file\n"), +}; + +static struct patch_mode patch_mode_checkout_nothead = { + .diff_cmd = { "diff-index", "-R", NULL }, + .apply_for_checkout = 1, + .apply_check_args = { NULL }, + .prompt_mode = { + N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "), + N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "), + N_("Apply addition to index and worktree [y,n,q,a,d%s,?]? "), + N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "), + }, + .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " + "will immediately be marked for applying."), + .help_patch_text = + N_("y - apply this hunk to index and worktree\n" + "n - do not apply this hunk to index and worktree\n" + "q - quit; do not apply this hunk or any of the remaining " + "ones\n" + "a - apply this hunk and all later hunks in the file\n" + "d - do not apply this hunk or any of the later hunks in " + "the file\n"), +}; + +static struct patch_mode patch_mode_worktree_head = { + .diff_cmd = { "diff-index", NULL }, + .apply_args = { "-R", NULL }, + .apply_check_args = { "-R", NULL }, + .is_reverse = 1, + .prompt_mode = { + N_("Discard mode change from index and worktree [y,n,q,a,d%s,?]? "), + N_("Discard deletion from index and worktree [y,n,q,a,d%s,?]? "), + N_("Discard addition from index and worktree [y,n,q,a,d%s,?]? "), + N_("Discard this hunk from index and worktree [y,n,q,a,d%s,?]? "), + }, + .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " + "will immediately be marked for discarding."), + .help_patch_text = + N_("y - discard this hunk from worktree\n" + "n - do not discard this hunk from worktree\n" + "q - quit; do not discard this hunk or any of the remaining " + "ones\n" + "a - discard this hunk and all later hunks in the file\n" + "d - do not discard this hunk or any of the later hunks in " + "the file\n"), +}; + +static struct patch_mode patch_mode_worktree_nothead = { + .diff_cmd = { "diff-index", "-R", NULL }, + .apply_args = { NULL }, + .apply_check_args = { NULL }, + .prompt_mode = { + N_("Apply mode change to index and worktree [y,n,q,a,d%s,?]? "), + N_("Apply deletion to index and worktree [y,n,q,a,d%s,?]? "), + N_("Apply addition to index and worktree [y,n,q,a,d%s,?]? "), + N_("Apply this hunk to index and worktree [y,n,q,a,d%s,?]? "), + }, + .edit_hunk_hint = N_("If the patch applies cleanly, the edited hunk " + "will immediately be marked for applying."), + .help_patch_text = + N_("y - apply this hunk to worktree\n" + "n - do not apply this hunk to worktree\n" + "q - quit; do not apply this hunk or any of the remaining " + "ones\n" + "a - apply this hunk and all later hunks in the file\n" + "d - do not apply this hunk or any of the later hunks in " + "the file\n"), }; struct hunk_header { @@ -44,11 +257,30 @@ struct hunk head; struct hunk *hunk; size_t hunk_nr, hunk_alloc; - unsigned deleted:1, mode_change:1,binary:1; + unsigned deleted:1, added:1, mode_change:1,binary:1; } *file_diff; size_t file_diff_nr; + + /* patch mode */ + struct patch_mode *mode; + const char *revision; }; +static void add_p_state_clear(struct add_p_state *s) +{ + size_t i; + + strbuf_release(&s->answer); + strbuf_release(&s->buf); + strbuf_release(&s->plain); + strbuf_release(&s->colored); + for (i = 0; i < s->file_diff_nr; i++) + free(s->file_diff[i].hunk); + free(s->file_diff); + clear_add_i_state(&s->s); +} + +__attribute__((format (printf, 2, 3))) static void err(struct add_p_state *s, const char *fmt, ...) { va_list args; @@ -69,12 +301,12 @@ va_start(ap, cp); while ((arg = va_arg(ap, const char *))) - argv_array_push(&cp->args, arg); + strvec_push(&cp->args, arg); va_end(ap); cp->git_cmd = 1; - argv_array_pushf(&cp->env_array, - INDEX_ENVIRONMENT "=%s", s->s.r->index_file); + strvec_pushf(&cp->env_array, + INDEX_ENVIRONMENT "=%s", s->s.r->index_file); } static int parse_range(const char **p, @@ -153,7 +385,8 @@ static int parse_diff(struct add_p_state *s, const struct pathspec *ps) { - struct argv_array args = ARGV_ARRAY_INIT; + struct strvec args = STRVEC_INIT; + const char *diff_algorithm = s->s.interactive_diff_algorithm; struct strbuf *plain = &s->plain, *colored = NULL; struct child_process cp = CHILD_PROCESS_INIT; char *p, *pend, *colored_p = NULL, *colored_pend = NULL, marker = '\0'; @@ -162,41 +395,71 @@ struct hunk *hunk = NULL; int res; + strvec_pushv(&args, s->mode->diff_cmd); + if (diff_algorithm) + strvec_pushf(&args, "--diff-algorithm=%s", diff_algorithm); + if (s->revision) { + struct object_id oid; + strvec_push(&args, + /* could be on an unborn branch */ + !strcmp("HEAD", s->revision) && + get_oid("HEAD", &oid) ? + empty_tree_oid_hex() : s->revision); + } + color_arg_index = args.nr; /* Use `--no-color` explicitly, just in case `diff.color = always`. */ - argv_array_pushl(&args, "diff-files", "-p", "--no-color", "--", NULL); - color_arg_index = args.argc - 2; + strvec_pushl(&args, "--no-color", "-p", "--", NULL); for (i = 0; i < ps->nr; i++) - argv_array_push(&args, ps->items[i].original); + strvec_push(&args, ps->items[i].original); setup_child_process(s, &cp, NULL); - cp.argv = args.argv; + cp.argv = args.v; res = capture_command(&cp, plain, 0); if (res) { - argv_array_clear(&args); + strvec_clear(&args); return error(_("could not parse diff")); } if (!plain->len) { - argv_array_clear(&args); + strvec_clear(&args); return 0; } strbuf_complete_line(plain); if (want_color_fd(1, -1)) { struct child_process colored_cp = CHILD_PROCESS_INIT; + const char *diff_filter = s->s.interactive_diff_filter; setup_child_process(s, &colored_cp, NULL); - xsnprintf((char *)args.argv[color_arg_index], 8, "--color"); - colored_cp.argv = args.argv; + xsnprintf((char *)args.v[color_arg_index], 8, "--color"); + colored_cp.argv = args.v; colored = &s->colored; res = capture_command(&colored_cp, colored, 0); - argv_array_clear(&args); + strvec_clear(&args); if (res) return error(_("could not parse colored diff")); + + if (diff_filter) { + struct child_process filter_cp = CHILD_PROCESS_INIT; + + setup_child_process(s, &filter_cp, + diff_filter, NULL); + filter_cp.git_cmd = 0; + filter_cp.use_shell = 1; + strbuf_reset(&s->buf); + if (pipe_command(&filter_cp, + colored->buf, colored->len, + &s->buf, colored->len, + NULL, 0) < 0) + return error(_("failed to run '%s'"), + diff_filter); + strbuf_swap(colored, &s->buf); + } + strbuf_complete_line(colored); colored_p = colored->buf; colored_pend = colored_p + colored->len; } - argv_array_clear(&args); + strvec_clear(&args); /* parse files and hunks */ p = plain->buf; @@ -209,11 +472,9 @@ eol = pend; if (starts_with(p, "diff ")) { - s->file_diff_nr++; - ALLOC_GROW(s->file_diff, s->file_diff_nr, + ALLOC_GROW_BY(s->file_diff, s->file_diff_nr, 1, file_diff_alloc); file_diff = s->file_diff + s->file_diff_nr - 1; - memset(file_diff, 0, sizeof(*file_diff)); hunk = &file_diff->head; hunk->start = p - plain->buf; if (colored_p) @@ -226,7 +487,7 @@ ; /* keep the rest of the file in a single "hunk" */ else if (starts_with(p, "@@ ") || (hunk == &file_diff->head && - skip_prefix(p, "deleted file", &deleted))) { + (skip_prefix(p, "deleted file", &deleted)))) { if (marker == '-' || marker == '+') /* * Should not happen; previous hunk did not end @@ -234,11 +495,9 @@ */ hunk->splittable_into++; - file_diff->hunk_nr++; - ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr, + ALLOC_GROW_BY(file_diff->hunk, file_diff->hunk_nr, 1, file_diff->hunk_alloc); hunk = file_diff->hunk + file_diff->hunk_nr - 1; - memset(hunk, 0, sizeof(*hunk)); hunk->start = p - plain->buf; if (colored) @@ -255,12 +514,15 @@ */ marker = *p; } else if (hunk == &file_diff->head && + starts_with(p, "new file")) { + file_diff->added = 1; + } else if (hunk == &file_diff->head && skip_prefix(p, "old mode ", &mode_change) && is_octal(mode_change, eol - mode_change)) { if (file_diff->mode_change) BUG("double mode change?\n\n%.*s", (int)(eol - plain->buf), plain->buf); - if (file_diff->hunk_nr++) + if (file_diff->hunk_nr) BUG("mode change in the middle?\n\n%.*s", (int)(eol - plain->buf), plain->buf); @@ -269,9 +531,8 @@ * is _part of_ the header "hunk". */ file_diff->mode_change = 1; - ALLOC_GROW(file_diff->hunk, file_diff->hunk_nr, + ALLOC_GROW_BY(file_diff->hunk, file_diff->hunk_nr, 1, file_diff->hunk_alloc); - memset(file_diff->hunk, 0, sizeof(struct hunk)); file_diff->hunk->start = p - plain->buf; if (colored_p) file_diff->hunk->colored_start = @@ -298,8 +559,10 @@ starts_with(p, "Binary files ")) file_diff->binary = 1; - if (file_diff->deleted && file_diff->mode_change) - BUG("diff contains delete *and* a mode change?!?\n%.*s", + if (!!file_diff->deleted + !!file_diff->added + + !!file_diff->mode_change > 1) + BUG("diff can only contain delete *or* add *or* a " + "mode change?!?\n%.*s", (int)(eol - (plain->buf + file_diff->head.start)), plain->buf + file_diff->head.start); @@ -316,6 +579,9 @@ colored_pend - colored_p); if (colored_eol) colored_p = colored_eol + 1; + else if (p != pend) + /* colored shorter than non-colored? */ + goto mismatched_output; else colored_p = colored_pend; @@ -340,6 +606,15 @@ */ hunk->splittable_into++; + /* non-colored shorter than colored? */ + if (colored_p != colored_pend) { +mismatched_output: + error(_("mismatched output from interactive.diffFilter")); + advise(_("Your filter must maintain a one-to-one correspondence\n" + "between its input and output lines.")); + return -1; + } + return 0; } @@ -382,15 +657,23 @@ - header->colored_extra_start; } - new_offset += delta; + if (s->mode->is_reverse) + old_offset -= delta; + else + new_offset += delta; + + strbuf_addf(out, "@@ -%lu", old_offset); + if (header->old_count != 1) + strbuf_addf(out, ",%lu", header->old_count); + strbuf_addf(out, " +%lu", new_offset); + if (header->new_count != 1) + strbuf_addf(out, ",%lu", header->new_count); + strbuf_addstr(out, " @@"); - strbuf_addf(out, "@@ -%lu,%lu +%lu,%lu @@", - old_offset, header->old_count, - new_offset, header->new_count); if (len) strbuf_add(out, p, len); else if (colored) - strbuf_addf(out, "%s\n", GIT_COLOR_RESET); + strbuf_addf(out, "%s\n", s->s.reset_color); else strbuf_addch(out, '\n'); } @@ -783,7 +1066,7 @@ s->s.file_new_color : s->s.context_color); strbuf_add(&s->colored, plain + current, eol - current); - strbuf_addstr(&s->colored, GIT_COLOR_RESET); + strbuf_addstr(&s->colored, s->s.reset_color); if (next > eol) strbuf_add(&s->colored, plain + eol, next - eol); current = next; @@ -805,11 +1088,10 @@ "(context).\n" "To remove '%c' lines, delete them.\n" "Lines starting with %c will be removed.\n"), - '-', '+', comment_line_char); - strbuf_commented_addf(&s->buf, - _("If the patch applies cleanly, the edited hunk " - "will immediately be\n" - "marked for staging.\n")); + s->mode->is_reverse ? '+' : '-', + s->mode->is_reverse ? '-' : '+', + comment_line_char); + strbuf_commented_addf(&s->buf, "%s", _(s->mode->edit_hunk_hint)); /* * TRANSLATORS: 'it' refers to the patch mentioned in the previous * messages. @@ -890,21 +1172,34 @@ reassemble_patch(s, file_diff, 1, &s->buf); setup_child_process(s, &cp, - "apply", "--cached", "--check", NULL); + "apply", "--check", NULL); + strvec_pushv(&cp.args, s->mode->apply_check_args); if (pipe_command(&cp, s->buf.buf, s->buf.len, NULL, 0, NULL, 0)) return error(_("'git apply --cached' failed")); return 0; } +static int read_single_character(struct add_p_state *s) +{ + if (s->s.use_single_key) { + int res = read_key_without_echo(&s->answer); + printf("%s\n", res == EOF ? "" : s->answer.buf); + return res; + } + + if (git_read_line_interactively(&s->answer) == EOF) + return EOF; + return 0; +} + static int prompt_yesno(struct add_p_state *s, const char *prompt) { for (;;) { color_fprintf(stdout, s->s.prompt_color, "%s", _(prompt)); fflush(stdout); - if (strbuf_getline(&s->answer, stdin) == EOF) + if (read_single_character(s) == EOF) return -1; - strbuf_trim_trailing_newline(&s->answer); switch (tolower(s->answer.buf[0])) { case 'n': return 0; case 'y': return 1; @@ -923,7 +1218,7 @@ for (;;) { int res = edit_hunk_manually(s, hunk); if (res == 0) { - /* abandonded */ + /* abandoned */ *hunk = backup; return -1; } @@ -957,6 +1252,57 @@ } } +static int apply_for_checkout(struct add_p_state *s, struct strbuf *diff, + int is_reverse) +{ + const char *reverse = is_reverse ? "-R" : NULL; + struct child_process check_index = CHILD_PROCESS_INIT; + struct child_process check_worktree = CHILD_PROCESS_INIT; + struct child_process apply_index = CHILD_PROCESS_INIT; + struct child_process apply_worktree = CHILD_PROCESS_INIT; + int applies_index, applies_worktree; + + setup_child_process(s, &check_index, + "apply", "--cached", "--check", reverse, NULL); + applies_index = !pipe_command(&check_index, diff->buf, diff->len, + NULL, 0, NULL, 0); + + setup_child_process(s, &check_worktree, + "apply", "--check", reverse, NULL); + applies_worktree = !pipe_command(&check_worktree, diff->buf, diff->len, + NULL, 0, NULL, 0); + + if (applies_worktree && applies_index) { + setup_child_process(s, &apply_index, + "apply", "--cached", reverse, NULL); + pipe_command(&apply_index, diff->buf, diff->len, + NULL, 0, NULL, 0); + + setup_child_process(s, &apply_worktree, + "apply", reverse, NULL); + pipe_command(&apply_worktree, diff->buf, diff->len, + NULL, 0, NULL, 0); + + return 1; + } + + if (!applies_index) { + err(s, _("The selected hunks do not apply to the index!")); + if (prompt_yesno(s, _("Apply them to the worktree " + "anyway? ")) > 0) { + setup_child_process(s, &apply_worktree, + "apply", reverse, NULL); + return pipe_command(&apply_worktree, diff->buf, + diff->len, NULL, 0, NULL, 0); + } + err(s, _("Nothing was applied.\n")); + } else + /* As a last resort, show the diff to the user */ + fwrite(diff->buf, diff->len, 1, stderr); + + return 0; +} + #define SUMMARY_HEADER_WIDTH 20 #define SUMMARY_LINE_WIDTH 80 static void summarize_hunk(struct add_p_state *s, struct hunk *hunk, @@ -1005,13 +1351,6 @@ return end_index; } -static const char help_patch_text[] = -N_("y - stage this hunk\n" - "n - do not stage this hunk\n" - "q - quit; do not stage this hunk or any of the remaining ones\n" - "a - stage this and all the remaining hunks\n" - "d - do not stage this hunk nor any of the remaining hunks\n"); - static const char help_patch_remainder[] = N_("j - leave this hunk undecided, see next undecided hunk\n" "J - leave this hunk undecided, see next hunk\n" @@ -1033,8 +1372,18 @@ struct child_process cp = CHILD_PROCESS_INIT; int colored = !!s->colored.len, quit = 0; enum prompt_mode_type prompt_mode_type; + enum { + ALLOW_GOTO_PREVIOUS_HUNK = 1 << 0, + ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK = 1 << 1, + ALLOW_GOTO_NEXT_HUNK = 1 << 2, + ALLOW_GOTO_NEXT_UNDECIDED_HUNK = 1 << 3, + ALLOW_SEARCH_AND_GOTO = 1 << 4, + ALLOW_SPLIT = 1 << 5, + ALLOW_EDIT = 1 << 6 + } permitted = 0; - if (!file_diff->hunk_nr) + /* Empty added files have no hunks */ + if (!file_diff->hunk_nr && !file_diff->added) return 0; strbuf_reset(&s->buf); @@ -1043,21 +1392,25 @@ for (;;) { if (hunk_index >= file_diff->hunk_nr) hunk_index = 0; - hunk = file_diff->hunk + hunk_index; - + hunk = file_diff->hunk_nr + ? file_diff->hunk + hunk_index + : &file_diff->head; undecided_previous = -1; - for (i = hunk_index - 1; i >= 0; i--) - if (file_diff->hunk[i].use == UNDECIDED_HUNK) { - undecided_previous = i; - break; - } - undecided_next = -1; - for (i = hunk_index + 1; i < file_diff->hunk_nr; i++) - if (file_diff->hunk[i].use == UNDECIDED_HUNK) { - undecided_next = i; - break; - } + + if (file_diff->hunk_nr) { + for (i = hunk_index - 1; i >= 0; i--) + if (file_diff->hunk[i].use == UNDECIDED_HUNK) { + undecided_previous = i; + break; + } + + for (i = hunk_index + 1; i < file_diff->hunk_nr; i++) + if (file_diff->hunk[i].use == UNDECIDED_HUNK) { + undecided_next = i; + break; + } + } /* Everything decided? */ if (undecided_previous < 0 && undecided_next < 0 && @@ -1065,43 +1418,62 @@ break; strbuf_reset(&s->buf); - render_hunk(s, hunk, 0, colored, &s->buf); - fputs(s->buf.buf, stdout); - - strbuf_reset(&s->buf); - if (undecided_previous >= 0) - strbuf_addstr(&s->buf, ",k"); - if (hunk_index) - strbuf_addstr(&s->buf, ",K"); - if (undecided_next >= 0) - strbuf_addstr(&s->buf, ",j"); - if (hunk_index + 1 < file_diff->hunk_nr) - strbuf_addstr(&s->buf, ",J"); - if (file_diff->hunk_nr > 1) - strbuf_addstr(&s->buf, ",g,/"); - if (hunk->splittable_into > 1) - strbuf_addstr(&s->buf, ",s"); - if (hunk_index + 1 > file_diff->mode_change && - !file_diff->deleted) - strbuf_addstr(&s->buf, ",e"); - + if (file_diff->hunk_nr) { + render_hunk(s, hunk, 0, colored, &s->buf); + fputs(s->buf.buf, stdout); + + strbuf_reset(&s->buf); + if (undecided_previous >= 0) { + permitted |= ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK; + strbuf_addstr(&s->buf, ",k"); + } + if (hunk_index) { + permitted |= ALLOW_GOTO_PREVIOUS_HUNK; + strbuf_addstr(&s->buf, ",K"); + } + if (undecided_next >= 0) { + permitted |= ALLOW_GOTO_NEXT_UNDECIDED_HUNK; + strbuf_addstr(&s->buf, ",j"); + } + if (hunk_index + 1 < file_diff->hunk_nr) { + permitted |= ALLOW_GOTO_NEXT_HUNK; + strbuf_addstr(&s->buf, ",J"); + } + if (file_diff->hunk_nr > 1) { + permitted |= ALLOW_SEARCH_AND_GOTO; + strbuf_addstr(&s->buf, ",g,/"); + } + if (hunk->splittable_into > 1) { + permitted |= ALLOW_SPLIT; + strbuf_addstr(&s->buf, ",s"); + } + if (hunk_index + 1 > file_diff->mode_change && + !file_diff->deleted) { + permitted |= ALLOW_EDIT; + strbuf_addstr(&s->buf, ",e"); + } + } if (file_diff->deleted) prompt_mode_type = PROMPT_DELETION; + else if (file_diff->added) + prompt_mode_type = PROMPT_ADDITION; else if (file_diff->mode_change && !hunk_index) prompt_mode_type = PROMPT_MODE_CHANGE; else prompt_mode_type = PROMPT_HUNK; - color_fprintf(stdout, s->s.prompt_color, - "(%"PRIuMAX"/%"PRIuMAX") ", + printf("%s(%"PRIuMAX"/%"PRIuMAX") ", s->s.prompt_color, (uintmax_t)hunk_index + 1, - (uintmax_t)file_diff->hunk_nr); - color_fprintf(stdout, s->s.prompt_color, - _(prompt_mode[prompt_mode_type]), s->buf.buf); + (uintmax_t)(file_diff->hunk_nr + ? file_diff->hunk_nr + : 1)); + printf(_(s->mode->prompt_mode[prompt_mode_type]), + s->buf.buf); + if (*s->s.reset_color) + fputs(s->s.reset_color, stdout); fflush(stdout); - if (strbuf_getline(&s->answer, stdin) == EOF) + if (read_single_character(s) == EOF) break; - strbuf_trim_trailing_newline(&s->answer); if (!s->answer.len) continue; @@ -1115,38 +1487,46 @@ hunk->use = SKIP_HUNK; goto soft_increment; } else if (ch == 'a') { - for (; hunk_index < file_diff->hunk_nr; hunk_index++) { - hunk = file_diff->hunk + hunk_index; - if (hunk->use == UNDECIDED_HUNK) - hunk->use = USE_HUNK; + if (file_diff->hunk_nr) { + for (; hunk_index < file_diff->hunk_nr; hunk_index++) { + hunk = file_diff->hunk + hunk_index; + if (hunk->use == UNDECIDED_HUNK) + hunk->use = USE_HUNK; + } + } else if (hunk->use == UNDECIDED_HUNK) { + hunk->use = USE_HUNK; } } else if (ch == 'd' || ch == 'q') { - for (; hunk_index < file_diff->hunk_nr; hunk_index++) { - hunk = file_diff->hunk + hunk_index; - if (hunk->use == UNDECIDED_HUNK) - hunk->use = SKIP_HUNK; + if (file_diff->hunk_nr) { + for (; hunk_index < file_diff->hunk_nr; hunk_index++) { + hunk = file_diff->hunk + hunk_index; + if (hunk->use == UNDECIDED_HUNK) + hunk->use = SKIP_HUNK; + } + } else if (hunk->use == UNDECIDED_HUNK) { + hunk->use = SKIP_HUNK; } if (ch == 'q') { quit = 1; break; } } else if (s->answer.buf[0] == 'K') { - if (hunk_index) + if (permitted & ALLOW_GOTO_PREVIOUS_HUNK) hunk_index--; else err(s, _("No previous hunk")); } else if (s->answer.buf[0] == 'J') { - if (hunk_index + 1 < file_diff->hunk_nr) + if (permitted & ALLOW_GOTO_NEXT_HUNK) hunk_index++; else err(s, _("No next hunk")); } else if (s->answer.buf[0] == 'k') { - if (undecided_previous >= 0) + if (permitted & ALLOW_GOTO_PREVIOUS_UNDECIDED_HUNK) hunk_index = undecided_previous; else err(s, _("No previous hunk")); } else if (s->answer.buf[0] == 'j') { - if (undecided_next >= 0) + if (permitted & ALLOW_GOTO_NEXT_UNDECIDED_HUNK) hunk_index = undecided_next; else err(s, _("No next hunk")); @@ -1154,7 +1534,7 @@ char *pend; unsigned long response; - if (file_diff->hunk_nr < 2) { + if (!(permitted & ALLOW_SEARCH_AND_GOTO)) { err(s, _("No other hunks to goto")); continue; } @@ -1191,7 +1571,7 @@ regex_t regex; int ret; - if (file_diff->hunk_nr < 2) { + if (!(permitted & ALLOW_SEARCH_AND_GOTO)) { err(s, _("No other hunks to search")); continue; } @@ -1236,7 +1616,7 @@ hunk_index = i; } else if (s->answer.buf[0] == 's') { size_t splittable_into = hunk->splittable_into; - if (splittable_into < 2) + if (!(permitted & ALLOW_SPLIT)) err(s, _("Sorry, cannot split this hunk")); else if (!split_hunk(s, file_diff, hunk - file_diff->hunk)) @@ -1244,7 +1624,7 @@ _("Split into %d hunks."), (int)splittable_into); } else if (s->answer.buf[0] == 'e') { - if (hunk_index + 1 == file_diff->mode_change) + if (!(permitted & ALLOW_EDIT)) err(s, _("Sorry, cannot edit this hunk")); else if (edit_hunk_loop(s, file_diff, hunk) >= 0) { hunk->use = USE_HUNK; @@ -1254,7 +1634,7 @@ const char *p = _(help_patch_remainder), *eol = p; color_fprintf(stdout, s->s.help_color, "%s", - _(help_patch_text)); + _(s->mode->help_patch_text)); /* * Show only those lines of the remainder that are @@ -1282,17 +1662,24 @@ if (file_diff->hunk[i].use == USE_HUNK) break; - if (i < file_diff->hunk_nr) { + if (i < file_diff->hunk_nr || + (!file_diff->hunk_nr && file_diff->head.use == USE_HUNK)) { /* At least one hunk selected: apply */ strbuf_reset(&s->buf); reassemble_patch(s, file_diff, 0, &s->buf); discard_index(s->s.r->index); - setup_child_process(s, &cp, "apply", "--cached", NULL); - if (pipe_command(&cp, s->buf.buf, s->buf.len, - NULL, 0, NULL, 0)) - error(_("'git apply --cached' failed")); - if (!repo_read_index(s->s.r)) + if (s->mode->apply_for_checkout) + apply_for_checkout(s, &s->buf, + s->mode->is_reverse); + else { + setup_child_process(s, &cp, "apply", NULL); + strvec_pushv(&cp.args, s->mode->apply_args); + if (pipe_command(&cp, s->buf.buf, s->buf.len, + NULL, 0, NULL, 0)) + error(_("'git apply' failed")); + } + if (repo_read_index(s->s.r) >= 0) repo_refresh_and_write_index(s->s.r, REFRESH_QUIET, 0, 1, NULL, NULL, NULL); } @@ -1301,7 +1688,8 @@ return quit; } -int run_add_p(struct repository *r, const struct pathspec *ps) +int run_add_p(struct repository *r, enum add_p_mode mode, + const char *revision, const struct pathspec *ps) { struct add_p_state s = { { r }, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT @@ -1310,12 +1698,45 @@ init_add_i_state(&s.s, r); + if (mode == ADD_P_STASH) + s.mode = &patch_mode_stash; + else if (mode == ADD_P_RESET) { + /* + * NEEDSWORK: Instead of comparing to the literal "HEAD", + * compare the commit objects instead so that other ways of + * saying the same thing (such as "@") are also handled + * appropriately. + * + * This applies to the cases below too. + */ + if (!revision || !strcmp(revision, "HEAD")) + s.mode = &patch_mode_reset_head; + else + s.mode = &patch_mode_reset_nothead; + } else if (mode == ADD_P_CHECKOUT) { + if (!revision) + s.mode = &patch_mode_checkout_index; + else if (!strcmp(revision, "HEAD")) + s.mode = &patch_mode_checkout_head; + else + s.mode = &patch_mode_checkout_nothead; + } else if (mode == ADD_P_WORKTREE) { + if (!revision) + s.mode = &patch_mode_checkout_index; + else if (!strcmp(revision, "HEAD")) + s.mode = &patch_mode_worktree_head; + else + s.mode = &patch_mode_worktree_nothead; + } else + s.mode = &patch_mode_add; + s.revision = revision; + if (discard_index(r->index) < 0 || repo_read_index(r) < 0 || - repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1, - NULL, NULL, NULL) < 0 || + (!s.mode->index_only && + repo_refresh_and_write_index(r, REFRESH_QUIET, 0, 1, + NULL, NULL, NULL) < 0) || parse_diff(&s, ps) < 0) { - strbuf_release(&s.plain); - strbuf_release(&s.colored); + add_p_state_clear(&s); return -1; } @@ -1330,9 +1751,6 @@ else if (binary_count == s.file_diff_nr) fprintf(stderr, _("Only binary files changed.\n")); - strbuf_release(&s.answer); - strbuf_release(&s.buf); - strbuf_release(&s.plain); - strbuf_release(&s.colored); + add_p_state_clear(&s); return 0; } diff -Nru git-2.25.1/advice.c git-2.34.1/advice.c --- git-2.25.1/advice.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/advice.c 2021-11-24 19:29:21.000000000 +0000 @@ -2,35 +2,7 @@ #include "config.h" #include "color.h" #include "help.h" - -int advice_fetch_show_forced_updates = 1; -int advice_push_update_rejected = 1; -int advice_push_non_ff_current = 1; -int advice_push_non_ff_matching = 1; -int advice_push_already_exists = 1; -int advice_push_fetch_first = 1; -int advice_push_needs_force = 1; -int advice_push_unqualified_ref_name = 1; -int advice_status_hints = 1; -int advice_status_u_option = 1; -int advice_status_ahead_behind_warning = 1; -int advice_commit_before_merge = 1; -int advice_reset_quiet_warning = 1; -int advice_resolve_conflict = 1; -int advice_sequencer_in_use = 1; -int advice_implicit_identity = 1; -int advice_detached_head = 1; -int advice_set_upstream_failure = 1; -int advice_object_name_warning = 1; -int advice_amworkdir = 1; -int advice_rm_hints = 1; -int advice_add_embedded_repo = 1; -int advice_ignored_hook = 1; -int advice_waiting_for_editor = 1; -int advice_graft_file_deprecated = 1; -int advice_checkout_ambiguous_remote_branch_name = 1; -int advice_nested_tag = 1; -int advice_submodule_alternate_error_strategy_die = 1; +#include "string-list.h" static int advice_use_color = -1; static char advice_colors[][COLOR_MAXLEN] = { @@ -60,51 +32,62 @@ } static struct { - const char *name; - int *preference; -} advice_config[] = { - { "fetchShowForcedUpdates", &advice_fetch_show_forced_updates }, - { "pushUpdateRejected", &advice_push_update_rejected }, - { "pushNonFFCurrent", &advice_push_non_ff_current }, - { "pushNonFFMatching", &advice_push_non_ff_matching }, - { "pushAlreadyExists", &advice_push_already_exists }, - { "pushFetchFirst", &advice_push_fetch_first }, - { "pushNeedsForce", &advice_push_needs_force }, - { "pushUnqualifiedRefName", &advice_push_unqualified_ref_name }, - { "statusHints", &advice_status_hints }, - { "statusUoption", &advice_status_u_option }, - { "statusAheadBehindWarning", &advice_status_ahead_behind_warning }, - { "commitBeforeMerge", &advice_commit_before_merge }, - { "resetQuiet", &advice_reset_quiet_warning }, - { "resolveConflict", &advice_resolve_conflict }, - { "sequencerInUse", &advice_sequencer_in_use }, - { "implicitIdentity", &advice_implicit_identity }, - { "detachedHead", &advice_detached_head }, - { "setupStreamFailure", &advice_set_upstream_failure }, - { "objectNameWarning", &advice_object_name_warning }, - { "amWorkDir", &advice_amworkdir }, - { "rmHints", &advice_rm_hints }, - { "addEmbeddedRepo", &advice_add_embedded_repo }, - { "ignoredHook", &advice_ignored_hook }, - { "waitingForEditor", &advice_waiting_for_editor }, - { "graftFileDeprecated", &advice_graft_file_deprecated }, - { "checkoutAmbiguousRemoteBranchName", &advice_checkout_ambiguous_remote_branch_name }, - { "nestedTag", &advice_nested_tag }, - { "submoduleAlternateErrorStrategyDie", &advice_submodule_alternate_error_strategy_die }, + const char *key; + int enabled; +} advice_setting[] = { + [ADVICE_ADD_EMBEDDED_REPO] = { "addEmbeddedRepo", 1 }, + [ADVICE_ADD_EMPTY_PATHSPEC] = { "addEmptyPathspec", 1 }, + [ADVICE_ADD_IGNORED_FILE] = { "addIgnoredFile", 1 }, + [ADVICE_AM_WORK_DIR] = { "amWorkDir", 1 }, + [ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME] = { "checkoutAmbiguousRemoteBranchName", 1 }, + [ADVICE_COMMIT_BEFORE_MERGE] = { "commitBeforeMerge", 1 }, + [ADVICE_DETACHED_HEAD] = { "detachedHead", 1 }, + [ADVICE_FETCH_SHOW_FORCED_UPDATES] = { "fetchShowForcedUpdates", 1 }, + [ADVICE_GRAFT_FILE_DEPRECATED] = { "graftFileDeprecated", 1 }, + [ADVICE_IGNORED_HOOK] = { "ignoredHook", 1 }, + [ADVICE_IMPLICIT_IDENTITY] = { "implicitIdentity", 1 }, + [ADVICE_NESTED_TAG] = { "nestedTag", 1 }, + [ADVICE_OBJECT_NAME_WARNING] = { "objectNameWarning", 1 }, + [ADVICE_PUSH_ALREADY_EXISTS] = { "pushAlreadyExists", 1 }, + [ADVICE_PUSH_FETCH_FIRST] = { "pushFetchFirst", 1 }, + [ADVICE_PUSH_NEEDS_FORCE] = { "pushNeedsForce", 1 }, + [ADVICE_PUSH_REF_NEEDS_UPDATE] = { "pushRefNeedsUpdate", 1 }, /* make this an alias for backward compatibility */ - { "pushNonFastForward", &advice_push_update_rejected } + [ADVICE_PUSH_UPDATE_REJECTED_ALIAS] = { "pushNonFastForward", 1 }, + + [ADVICE_PUSH_NON_FF_CURRENT] = { "pushNonFFCurrent", 1 }, + [ADVICE_PUSH_NON_FF_MATCHING] = { "pushNonFFMatching", 1 }, + [ADVICE_PUSH_UNQUALIFIED_REF_NAME] = { "pushUnqualifiedRefName", 1 }, + [ADVICE_PUSH_UPDATE_REJECTED] = { "pushUpdateRejected", 1 }, + [ADVICE_RESET_QUIET_WARNING] = { "resetQuiet", 1 }, + [ADVICE_RESOLVE_CONFLICT] = { "resolveConflict", 1 }, + [ADVICE_RM_HINTS] = { "rmHints", 1 }, + [ADVICE_SEQUENCER_IN_USE] = { "sequencerInUse", 1 }, + [ADVICE_SET_UPSTREAM_FAILURE] = { "setUpstreamFailure", 1 }, + [ADVICE_SKIPPED_CHERRY_PICKS] = { "skippedCherryPicks", 1 }, + [ADVICE_STATUS_AHEAD_BEHIND_WARNING] = { "statusAheadBehindWarning", 1 }, + [ADVICE_STATUS_HINTS] = { "statusHints", 1 }, + [ADVICE_STATUS_U_OPTION] = { "statusUoption", 1 }, + [ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE] = { "submoduleAlternateErrorStrategyDie", 1 }, + [ADVICE_UPDATE_SPARSE_PATH] = { "updateSparsePath", 1 }, + [ADVICE_WAITING_FOR_EDITOR] = { "waitingForEditor", 1 }, }; -void advise(const char *advice, ...) +static const char turn_off_instructions[] = +N_("\n" + "Disable this message with \"git config advice.%s false\""); + +static void vadvise(const char *advice, int display_instructions, + const char *key, va_list params) { struct strbuf buf = STRBUF_INIT; - va_list params; const char *cp, *np; - va_start(params, advice); strbuf_vaddf(&buf, advice, params); - va_end(params); + + if (display_instructions) + strbuf_addf(&buf, turn_off_instructions, key); for (cp = buf.buf; *cp; cp = np) { np = strchrnul(cp, '\n'); @@ -118,6 +101,37 @@ strbuf_release(&buf); } +void advise(const char *advice, ...) +{ + va_list params; + va_start(params, advice); + vadvise(advice, 0, "", params); + va_end(params); +} + +int advice_enabled(enum advice_type type) +{ + switch(type) { + case ADVICE_PUSH_UPDATE_REJECTED: + return advice_setting[ADVICE_PUSH_UPDATE_REJECTED].enabled && + advice_setting[ADVICE_PUSH_UPDATE_REJECTED_ALIAS].enabled; + default: + return advice_setting[type].enabled; + } +} + +void advise_if_enabled(enum advice_type type, const char *advice, ...) +{ + va_list params; + + if (!advice_enabled(type)) + return; + + va_start(params, advice); + vadvise(advice, 1, advice_setting[type].key, params); + va_end(params); +} + int git_default_advice_config(const char *var, const char *value) { const char *k, *slot_name; @@ -140,10 +154,10 @@ if (!skip_prefix(var, "advice.", &k)) return 0; - for (i = 0; i < ARRAY_SIZE(advice_config); i++) { - if (strcasecmp(k, advice_config[i].name)) + for (i = 0; i < ARRAY_SIZE(advice_setting); i++) { + if (strcasecmp(k, advice_setting[i].key)) continue; - *advice_config[i].preference = git_config_bool(var, value); + advice_setting[i].enabled = git_config_bool(var, value); return 0; } @@ -154,8 +168,8 @@ { int i; - for (i = 0; i < ARRAY_SIZE(advice_config); i++) - list_config_item(list, prefix, advice_config[i].name); + for (i = 0; i < ARRAY_SIZE(advice_setting); i++) + list_config_item(list, prefix, advice_setting[i].key); } int error_resolve_conflict(const char *me) @@ -174,7 +188,7 @@ error(_("It is not possible to %s because you have unmerged files."), me); - if (advice_resolve_conflict) + if (advice_enabled(ADVICE_RESOLVE_CONFLICT)) /* * Message used both when 'git commit' fails and when * other commands doing a merge do. @@ -193,11 +207,35 @@ void NORETURN die_conclude_merge(void) { error(_("You have not concluded your merge (MERGE_HEAD exists).")); - if (advice_resolve_conflict) + if (advice_enabled(ADVICE_RESOLVE_CONFLICT)) advise(_("Please, commit your changes before merging.")); die(_("Exiting because of unfinished merge.")); } +void NORETURN die_ff_impossible(void) +{ + die(_("Not possible to fast-forward, aborting.")); +} + +void advise_on_updating_sparse_paths(struct string_list *pathspec_list) +{ + struct string_list_item *item; + + if (!pathspec_list->nr) + return; + + fprintf(stderr, _("The following paths and/or pathspecs matched paths that exist\n" + "outside of your sparse-checkout definition, so will not be\n" + "updated in the index:\n")); + for_each_string_list_item(item, pathspec_list) + fprintf(stderr, "%s\n", item->string); + + advise_if_enabled(ADVICE_UPDATE_SPARSE_PATH, + _("If you intend to update such entries, try one of the following:\n" + "* Use the --sparse option.\n" + "* Disable or modify the sparsity rules.")); +} + void detach_advice(const char *new_name) { const char *fmt = diff -Nru git-2.25.1/advice.h git-2.34.1/advice.h --- git-2.25.1/advice.h 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/advice.h 2021-11-24 19:29:21.000000000 +0000 @@ -3,41 +3,73 @@ #include "git-compat-util.h" -extern int advice_fetch_show_forced_updates; -extern int advice_push_update_rejected; -extern int advice_push_non_ff_current; -extern int advice_push_non_ff_matching; -extern int advice_push_already_exists; -extern int advice_push_fetch_first; -extern int advice_push_needs_force; -extern int advice_push_unqualified_ref_name; -extern int advice_status_hints; -extern int advice_status_u_option; -extern int advice_status_ahead_behind_warning; -extern int advice_commit_before_merge; -extern int advice_reset_quiet_warning; -extern int advice_resolve_conflict; -extern int advice_sequencer_in_use; -extern int advice_implicit_identity; -extern int advice_detached_head; -extern int advice_set_upstream_failure; -extern int advice_object_name_warning; -extern int advice_amworkdir; -extern int advice_rm_hints; -extern int advice_add_embedded_repo; -extern int advice_ignored_hook; -extern int advice_waiting_for_editor; -extern int advice_graft_file_deprecated; -extern int advice_checkout_ambiguous_remote_branch_name; -extern int advice_nested_tag; -extern int advice_submodule_alternate_error_strategy_die; +struct string_list; + +/* + * To add a new advice, you need to: + * Define a new advice_type. + * Add a new entry to advice_setting array. + * Add the new config variable to Documentation/config/advice.txt. + * Call advise_if_enabled to print your advice. + */ + enum advice_type { + ADVICE_ADD_EMBEDDED_REPO, + ADVICE_ADD_EMPTY_PATHSPEC, + ADVICE_ADD_IGNORED_FILE, + ADVICE_AM_WORK_DIR, + ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME, + ADVICE_COMMIT_BEFORE_MERGE, + ADVICE_DETACHED_HEAD, + ADVICE_FETCH_SHOW_FORCED_UPDATES, + ADVICE_GRAFT_FILE_DEPRECATED, + ADVICE_IGNORED_HOOK, + ADVICE_IMPLICIT_IDENTITY, + ADVICE_NESTED_TAG, + ADVICE_OBJECT_NAME_WARNING, + ADVICE_PUSH_ALREADY_EXISTS, + ADVICE_PUSH_FETCH_FIRST, + ADVICE_PUSH_NEEDS_FORCE, + ADVICE_PUSH_NON_FF_CURRENT, + ADVICE_PUSH_NON_FF_MATCHING, + ADVICE_PUSH_UNQUALIFIED_REF_NAME, + ADVICE_PUSH_UPDATE_REJECTED_ALIAS, + ADVICE_PUSH_UPDATE_REJECTED, + ADVICE_PUSH_REF_NEEDS_UPDATE, + ADVICE_RESET_QUIET_WARNING, + ADVICE_RESOLVE_CONFLICT, + ADVICE_RM_HINTS, + ADVICE_SEQUENCER_IN_USE, + ADVICE_SET_UPSTREAM_FAILURE, + ADVICE_STATUS_AHEAD_BEHIND_WARNING, + ADVICE_STATUS_HINTS, + ADVICE_STATUS_U_OPTION, + ADVICE_SUBMODULE_ALTERNATE_ERROR_STRATEGY_DIE, + ADVICE_UPDATE_SPARSE_PATH, + ADVICE_WAITING_FOR_EDITOR, + ADVICE_SKIPPED_CHERRY_PICKS, +}; int git_default_advice_config(const char *var, const char *value); __attribute__((format (printf, 1, 2))) void advise(const char *advice, ...); + +/** + * Checks if advice type is enabled (can be printed to the user). + * Should be called before advise(). + */ +int advice_enabled(enum advice_type type); + +/** + * Checks the visibility of the advice before printing. + */ +__attribute__((format (printf, 2, 3))) +void advise_if_enabled(enum advice_type type, const char *advice, ...); + int error_resolve_conflict(const char *me); void NORETURN die_resolve_conflict(const char *me); void NORETURN die_conclude_merge(void); +void NORETURN die_ff_impossible(void); +void advise_on_updating_sparse_paths(struct string_list *pathspec_list); void detach_advice(const char *new_name); #endif /* ADVICE_H */ diff -Nru git-2.25.1/alloc.c git-2.34.1/alloc.c --- git-2.25.1/alloc.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/alloc.c 2021-11-24 19:29:21.000000000 +0000 @@ -99,23 +99,27 @@ return obj; } -static unsigned int alloc_commit_index(struct repository *r) +/* + * The returned count is to be used as an index into commit slabs, + * that are *NOT* maintained per repository, and that is why a single + * global counter is used. + */ +static unsigned int alloc_commit_index(void) { - return r->parsed_objects->commit_count++; + static unsigned int parsed_commits_count; + return parsed_commits_count++; } -void init_commit_node(struct repository *r, struct commit *c) +void init_commit_node(struct commit *c) { c->object.type = OBJ_COMMIT; - c->index = alloc_commit_index(r); - c->graph_pos = COMMIT_NOT_FROM_GRAPH; - c->generation = GENERATION_NUMBER_INFINITY; + c->index = alloc_commit_index(); } void *alloc_commit_node(struct repository *r) { struct commit *c = alloc_node(r->parsed_objects->commit_state, sizeof(struct commit)); - init_commit_node(r, c); + init_commit_node(c); return c; } diff -Nru git-2.25.1/alloc.h git-2.34.1/alloc.h --- git-2.25.1/alloc.h 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/alloc.h 2021-11-24 19:29:21.000000000 +0000 @@ -9,7 +9,7 @@ void *alloc_blob_node(struct repository *r); void *alloc_tree_node(struct repository *r); -void init_commit_node(struct repository *r, struct commit *c); +void init_commit_node(struct commit *c); void *alloc_commit_node(struct repository *r); void *alloc_tag_node(struct repository *r); void *alloc_object_node(struct repository *r); diff -Nru git-2.25.1/apply.c git-2.34.1/apply.c --- git-2.25.1/apply.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/apply.c 2021-11-24 19:29:21.000000000 +0000 @@ -21,6 +21,7 @@ #include "quote.h" #include "rerere.h" #include "apply.h" +#include "entry.h" struct gitdiff_data { struct strbuf *root; @@ -30,8 +31,8 @@ static void git_apply_config(void) { - git_config_get_string_const("apply.whitespace", &apply_default_whitespace); - git_config_get_string_const("apply.ignorewhitespace", &apply_default_ignorewhitespace); + git_config_get_string("apply.whitespace", &apply_default_whitespace); + git_config_get_string("apply.ignorewhitespace", &apply_default_ignorewhitespace); git_config(git_xmerge_config, NULL); } @@ -100,9 +101,9 @@ state->ws_error_action = warn_on_ws_error; state->ws_ignore_action = ignore_ws_none; state->linenr = 1; - string_list_init(&state->fn_table, 0); - string_list_init(&state->limit_by_name, 0); - string_list_init(&state->symlink_changes, 0); + string_list_init_nodup(&state->fn_table); + string_list_init_nodup(&state->limit_by_name); + string_list_init_nodup(&state->symlink_changes); strbuf_init(&state->root, 0); git_apply_config(); @@ -133,8 +134,6 @@ if (state->apply_with_reject && state->threeway) return error(_("--reject and --3way cannot be used together.")); - if (state->cached && state->threeway) - return error(_("--cached and --3way cannot be used together.")); if (state->threeway) { if (is_not_gitdir) return error(_("--3way outside a repository")); @@ -1781,7 +1780,7 @@ struct fragment *fragment; int len; - fragment = xcalloc(1, sizeof(*fragment)); + CALLOC_ARRAY(fragment, 1); fragment->linenr = state->linenr; len = parse_fragment(state, line, size, patch, fragment); if (len <= 0) { @@ -1918,6 +1917,7 @@ state->linenr++; buffer += llen; + size -= llen; while (1) { int byte_length, max_byte_length, newsize; llen = linelen(buffer, size); @@ -1959,7 +1959,7 @@ size -= llen; } - frag = xcalloc(1, sizeof(*frag)); + CALLOC_ARRAY(frag, 1); frag->patch = inflate_it(data, hunk_size, origlen); frag->free_patch = 1; if (!frag->patch) @@ -3157,7 +3157,8 @@ * See if the old one matches what the patch * applies to. */ - hash_object_file(img->buf, img->len, blob_type, &oid); + hash_object_file(the_hash_algo, img->buf, img->len, blob_type, + &oid); if (strcmp(oid_to_hex(&oid), patch->old_oid_prefix)) return error(_("the patch applies to '%s' (%s), " "which does not match the " @@ -3177,7 +3178,7 @@ return 0; /* deletion patch */ } - if (has_object_file(&oid)) { + if (has_object(the_repository, &oid, 0)) { /* We already have the postimage */ enum object_type type; unsigned long size; @@ -3202,7 +3203,8 @@ name); /* verify that the result matches */ - hash_object_file(img->buf, img->len, blob_type, &oid); + hash_object_file(the_hash_algo, img->buf, img->len, blob_type, + &oid); if (strcmp(oid_to_hex(&oid), patch->new_oid_prefix)) return error(_("binary patch to '%s' creates incorrect result (expecting %s, got %s)"), name, patch->new_oid_prefix, oid_to_hex(&oid)); @@ -3466,6 +3468,21 @@ return 0; } +static int resolve_to(struct image *image, const struct object_id *result_id) +{ + unsigned long size; + enum object_type type; + + clear_image(image); + + image->buf = read_object_file(result_id, &type, &size); + if (!image->buf || type != OBJ_BLOB) + die("unable to read blob object %s", oid_to_hex(result_id)); + image->len = size; + + return 0; +} + static int three_way_merge(struct apply_state *state, struct image *image, char *path, @@ -3477,6 +3494,12 @@ mmbuffer_t result = { NULL }; int status; + /* resolve trivial cases first */ + if (oideq(base, ours)) + return resolve_to(image, theirs); + else if (oideq(base, theirs) || oideq(ours, theirs)) + return resolve_to(image, ours); + read_mmblob(&base_file, base); read_mmblob(&our_file, ours); read_mmblob(&their_file, theirs); @@ -3567,10 +3590,10 @@ write_object_file("", 0, blob_type, &pre_oid); else if (get_oid(patch->old_oid_prefix, &pre_oid) || read_blob_object(&buf, &pre_oid, patch->old_mode)) - return error(_("repository lacks the necessary blob to fall back on 3-way merge.")); + return error(_("repository lacks the necessary blob to perform 3-way merge.")); - if (state->apply_verbosity > verbosity_silent) - fprintf(stderr, _("Falling back to three-way merge...\n")); + if (state->apply_verbosity > verbosity_silent && patch->direct_to_threeway) + fprintf(stderr, _("Performing three-way merge...\n")); img = strbuf_detach(&buf, &len); prepare_image(&tmp_image, img, len, 1); @@ -3602,7 +3625,7 @@ if (status < 0) { if (state->apply_verbosity > verbosity_silent) fprintf(stderr, - _("Failed to fall back on three-way merge...\n")); + _("Failed to perform three-way merge...\n")); return status; } @@ -3635,10 +3658,13 @@ if (load_preimage(state, &image, patch, st, ce) < 0) return -1; - if (patch->direct_to_threeway || - apply_fragments(state, &image, patch) < 0) { + if (!state->threeway || try_threeway(state, &image, patch, st, ce) < 0) { + if (state->apply_verbosity > verbosity_silent && + state->threeway && !patch->direct_to_threeway) + fprintf(stderr, _("Falling back to direct application...\n")); + /* Note: with --reject, apply_fragments() returns 0 */ - if (!state->threeway || try_threeway(state, &image, patch, st, ce) < 0) + if (patch->direct_to_threeway || apply_fragments(state, &image, patch) < 0) return -1; } patch->result = image.buf; @@ -3738,6 +3764,7 @@ #define EXISTS_IN_INDEX 1 #define EXISTS_IN_WORKTREE 2 +#define EXISTS_IN_INDEX_AS_ITA 3 static int check_to_create(struct apply_state *state, const char *new_name, @@ -3745,10 +3772,23 @@ { struct stat nst; - if (state->check_index && - index_name_pos(state->repo->index, new_name, strlen(new_name)) >= 0 && - !ok_if_exists) - return EXISTS_IN_INDEX; + if (state->check_index && (!ok_if_exists || !state->cached)) { + int pos; + + pos = index_name_pos(state->repo->index, new_name, strlen(new_name)); + if (pos >= 0) { + struct cache_entry *ce = state->repo->index->cache[pos]; + + /* allow ITA, as they do not yet exist in the index */ + if (!ok_if_exists && !(ce->ce_flags & CE_INTENT_TO_ADD)) + return EXISTS_IN_INDEX; + + /* ITA entries can never match working tree files */ + if (!state->cached && (ce->ce_flags & CE_INTENT_TO_ADD)) + return EXISTS_IN_INDEX_AS_ITA; + } + } + if (state->cached) return 0; @@ -3932,7 +3972,8 @@ break; /* happy */ case EXISTS_IN_INDEX: return error(_("%s: already exists in index"), new_name); - break; + case EXISTS_IN_INDEX_AS_ITA: + return error(_("%s: does not match index"), new_name); case EXISTS_IN_WORKTREE: return error(_("%s: already exists in working directory"), new_name); @@ -4347,7 +4388,7 @@ if (fd < 0) return 1; - if (convert_to_working_tree(state->repo->index, path, buf, size, &nbuf)) { + if (convert_to_working_tree(state->repo->index, path, buf, size, &nbuf, NULL)) { size = nbuf.len; buf = nbuf.buf; } @@ -4390,7 +4431,7 @@ return 0; if (errno == ENOENT) { - if (safe_create_leading_directories(path)) + if (safe_create_leading_directories_no_share(path)) return 0; res = try_create_file(state, path, mode, buf, size); if (res < 0) @@ -4629,7 +4670,12 @@ } string_list_clear(&cpath, 0); - repo_rerere(state->repo, 0); + /* + * rerere relies on the partially merged result being in the working + * tree with conflict markers, but that isn't written with --cached. + */ + if (!state->cached) + repo_rerere(state->repo, 0); } return errs; @@ -4664,7 +4710,7 @@ struct patch *patch; int nr; - patch = xcalloc(1, sizeof(*patch)); + CALLOC_ARRAY(patch, 1); patch->inaccurate_eof = !!(options & APPLY_OPT_INACCURATE_EOF); patch->recount = !!(options & APPLY_OPT_RECOUNT); nr = parse_chunk(state, buf.buf + offset, buf.len - offset, patch); @@ -4680,8 +4726,13 @@ reverse_patches(patch); if (use_patch(state, patch)) { patch_stats(state, patch); - *listp = patch; - listp = &patch->next; + if (!list || !state->apply_in_reverse) { + *listp = patch; + listp = &patch->next; + } else { + patch->next = list; + list = patch; + } if ((patch->new_name && ends_with_path_components(patch->new_name, @@ -4962,15 +5013,15 @@ const char * const *apply_usage) { struct option builtin_apply_options[] = { - { OPTION_CALLBACK, 0, "exclude", state, N_("path"), + OPT_CALLBACK_F(0, "exclude", state, N_("path"), N_("don't apply changes matching the given path"), - PARSE_OPT_NONEG, apply_option_parse_exclude }, - { OPTION_CALLBACK, 0, "include", state, N_("path"), + PARSE_OPT_NONEG, apply_option_parse_exclude), + OPT_CALLBACK_F(0, "include", state, N_("path"), N_("apply changes matching the given path"), - PARSE_OPT_NONEG, apply_option_parse_include }, - { OPTION_CALLBACK, 'p', NULL, state, N_("num"), + PARSE_OPT_NONEG, apply_option_parse_include), + OPT_CALLBACK('p', NULL, state, N_("num"), N_("remove leading slashes from traditional diff paths"), - 0, apply_option_parse_p }, + apply_option_parse_p), OPT_BOOL(0, "no-add", &state->no_add, N_("ignore additions made by the patch")), OPT_BOOL(0, "stat", &state->diffstat, @@ -4995,7 +5046,7 @@ OPT_BOOL(0, "apply", force_apply, N_("also apply the patch (use with --stat/--summary/--check)")), OPT_BOOL('3', "3way", &state->threeway, - N_( "attempt three-way merge if a patch does not apply")), + N_( "attempt three-way merge, fall back on normal patch if that fails")), OPT_FILENAME(0, "build-fake-ancestor", &state->fake_ancestor, N_("build a temporary index based on embedded index information")), /* Think twice before adding "--nul" synonym to this */ @@ -5003,15 +5054,15 @@ N_("paths are separated with NUL character"), '\0'), OPT_INTEGER('C', NULL, &state->p_context, N_("ensure at least lines of context match")), - { OPTION_CALLBACK, 0, "whitespace", state, N_("action"), + OPT_CALLBACK(0, "whitespace", state, N_("action"), N_("detect new or modified lines that have whitespace errors"), - 0, apply_option_parse_whitespace }, - { OPTION_CALLBACK, 0, "ignore-space-change", state, NULL, + apply_option_parse_whitespace), + OPT_CALLBACK_F(0, "ignore-space-change", state, NULL, N_("ignore changes in whitespace when finding context"), - PARSE_OPT_NOARG, apply_option_parse_space_change }, - { OPTION_CALLBACK, 0, "ignore-whitespace", state, NULL, + PARSE_OPT_NOARG, apply_option_parse_space_change), + OPT_CALLBACK_F(0, "ignore-whitespace", state, NULL, N_("ignore changes in whitespace when finding context"), - PARSE_OPT_NOARG, apply_option_parse_space_change }, + PARSE_OPT_NOARG, apply_option_parse_space_change), OPT_BOOL('R', "reverse", &state->apply_in_reverse, N_("apply the patch in reverse")), OPT_BOOL(0, "unidiff-zero", &state->unidiff_zero, @@ -5027,9 +5078,9 @@ OPT_BIT(0, "recount", options, N_("do not trust the line counts in the hunk headers"), APPLY_OPT_RECOUNT), - { OPTION_CALLBACK, 0, "directory", state, N_("root"), + OPT_CALLBACK(0, "directory", state, N_("root"), N_("prepend to all filenames"), - 0, apply_option_parse_directory }, + apply_option_parse_directory), OPT_END() }; diff -Nru git-2.25.1/archive.c git-2.34.1/archive.c --- git-2.25.1/archive.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/archive.c 2021-11-24 19:29:21.000000000 +0000 @@ -37,13 +37,10 @@ static void format_subst(const struct commit *commit, const char *src, size_t len, - struct strbuf *buf) + struct strbuf *buf, struct pretty_print_context *ctx) { char *to_free = NULL; struct strbuf fmt = STRBUF_INIT; - struct pretty_print_context ctx = {0}; - ctx.date_mode.type = DATE_NORMAL; - ctx.abbrev = DEFAULT_ABBREV; if (src == buf->buf) to_free = strbuf_detach(buf, NULL); @@ -61,7 +58,7 @@ strbuf_add(&fmt, b + 8, c - b - 8); strbuf_add(buf, src, b - src); - format_commit_message(commit, fmt.buf, buf, &ctx); + format_commit_message(commit, fmt.buf, buf, ctx); len -= c + 1 - src; src = c + 1; } @@ -70,13 +67,20 @@ free(to_free); } -void *object_file_to_archive(const struct archiver_args *args, - const char *path, const struct object_id *oid, - unsigned int mode, enum object_type *type, - unsigned long *sizep) +static void *object_file_to_archive(const struct archiver_args *args, + const char *path, + const struct object_id *oid, + unsigned int mode, + enum object_type *type, + unsigned long *sizep) { void *buffer; const struct commit *commit = args->convert ? args->commit : NULL; + struct checkout_metadata meta; + + init_checkout_metadata(&meta, args->refname, + args->commit_oid ? args->commit_oid : + (args->tree ? &args->tree->object.oid : NULL), oid); path += args->baselen; buffer = read_object_file(oid, type, sizep); @@ -85,9 +89,9 @@ size_t size = 0; strbuf_attach(&buf, buffer, *sizep, *sizep + 1); - convert_to_working_tree(args->repo->index, path, buf.buf, buf.len, &buf); + convert_to_working_tree(args->repo->index, path, buf.buf, buf.len, &buf, &meta); if (commit) - format_subst(commit, buf.buf, buf.len, &buf); + format_subst(commit, buf.buf, buf.len, &buf, args->pretty_ctx); buffer = strbuf_detach(&buf, &size); *sizep = size; } @@ -100,7 +104,6 @@ struct object_id oid; int baselen, len; unsigned mode; - int stage; char path[FLEX_ARRAY]; }; @@ -131,7 +134,7 @@ } static int write_archive_entry(const struct object_id *oid, const char *base, - int baselen, const char *filename, unsigned mode, int stage, + int baselen, const char *filename, unsigned mode, void *context) { static struct strbuf path = STRBUF_INIT; @@ -140,6 +143,9 @@ write_archive_entry_fn_t write_entry = c->write_entry; int err; const char *path_without_prefix; + unsigned long size; + void *buffer; + enum object_type type; args->convert = 0; strbuf_reset(&path); @@ -162,7 +168,7 @@ if (S_ISDIR(mode) || S_ISGITLINK(mode)) { if (args->verbose) fprintf(stderr, "%.*s\n", (int)path.len, path.buf); - err = write_entry(args, oid, path.buf, path.len, mode); + err = write_entry(args, oid, path.buf, path.len, mode, NULL, 0); if (err) return err; return (S_ISDIR(mode) ? READ_TREE_RECURSIVE : 0); @@ -170,12 +176,24 @@ if (args->verbose) fprintf(stderr, "%.*s\n", (int)path.len, path.buf); - return write_entry(args, oid, path.buf, path.len, mode); + + /* Stream it? */ + if (S_ISREG(mode) && !args->convert && + oid_object_info(args->repo, oid, &size) == OBJ_BLOB && + size > big_file_threshold) + return write_entry(args, oid, path.buf, path.len, mode, NULL, size); + + buffer = object_file_to_archive(args, path.buf, oid, mode, &type, &size); + if (!buffer) + return error(_("cannot read %s"), oid_to_hex(oid)); + err = write_entry(args, oid, path.buf, path.len, mode, buffer, size); + free(buffer); + return err; } -static void queue_directory(const unsigned char *sha1, +static void queue_directory(const struct object_id *oid, struct strbuf *base, const char *filename, - unsigned mode, int stage, struct archiver_context *c) + unsigned mode, struct archiver_context *c) { struct directory *d; size_t len = st_add4(base->len, 1, strlen(filename), 1); @@ -183,10 +201,9 @@ d->up = c->bottom; d->baselen = base->len; d->mode = mode; - d->stage = stage; c->bottom = d; d->len = xsnprintf(d->path, len, "%.*s%s/", (int)base->len, base->buf, filename); - hashcpy(d->oid.hash, sha1); + oidcpy(&d->oid, oid); } static int write_directory(struct archiver_context *c) @@ -202,14 +219,14 @@ write_directory(c) || write_archive_entry(&d->oid, d->path, d->baselen, d->path + d->baselen, d->mode, - d->stage, c) != READ_TREE_RECURSIVE; + c) != READ_TREE_RECURSIVE; free(d); return ret ? -1 : 0; } static int queue_or_write_archive_entry(const struct object_id *oid, struct strbuf *base, const char *filename, - unsigned mode, int stage, void *context) + unsigned mode, void *context) { struct archiver_context *c = context; @@ -233,17 +250,21 @@ if (check_attr_export_ignore(check)) return 0; - queue_directory(oid->hash, base, filename, - mode, stage, c); + queue_directory(oid, base, filename, mode, c); return READ_TREE_RECURSIVE; } if (write_directory(c)) return -1; return write_archive_entry(oid, base->buf, base->len, filename, mode, - stage, context); + context); } +struct extra_file_info { + char *base; + struct stat stat; +}; + int write_archive_entries(struct archiver_args *args, write_archive_entry_fn_t write_entry) { @@ -251,6 +272,12 @@ struct unpack_trees_options opts; struct tree_desc t; int err; + struct strbuf path_in_archive = STRBUF_INIT; + struct strbuf content = STRBUF_INIT; + struct object_id fake_oid; + int i; + + oidcpy(&fake_oid, null_oid()); if (args->baselen > 0 && args->base[args->baselen - 1] == '/') { size_t len = args->baselen; @@ -260,7 +287,7 @@ if (args->verbose) fprintf(stderr, "%.*s\n", (int)len, args->base); err = write_entry(args, &args->tree->object.oid, args->base, - len, 040777); + len, 040777, NULL, 0); if (err) return err; } @@ -285,10 +312,10 @@ git_attr_set_direction(GIT_ATTR_INDEX); } - err = read_tree_recursive(args->repo, args->tree, "", - 0, 0, &args->pathspec, - queue_or_write_archive_entry, - &context); + err = read_tree(args->repo, args->tree, + &args->pathspec, + queue_or_write_archive_entry, + &context); if (err == READ_TREE_RECURSIVE) err = 0; while (context.bottom) { @@ -296,6 +323,33 @@ free(context.bottom); context.bottom = next; } + + for (i = 0; i < args->extra_files.nr; i++) { + struct string_list_item *item = args->extra_files.items + i; + char *path = item->string; + struct extra_file_info *info = item->util; + + 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(_("could not 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 (err) + break; + } + strbuf_release(&path_in_archive); + strbuf_release(&content); + return err; } @@ -320,7 +374,7 @@ static int reject_entry(const struct object_id *oid, struct strbuf *base, const char *filename, unsigned mode, - int stage, void *context) + void *context) { int ret = -1; struct path_exists_context *ctx = context; @@ -347,9 +401,9 @@ ctx.args = args; parse_pathspec(&ctx.pathspec, 0, 0, "", paths); ctx.pathspec.recursive = 1; - ret = read_tree_recursive(args->repo, args->tree, "", - 0, 0, &ctx.pathspec, - reject_entry, &ctx); + ret = read_tree(args->repo, args->tree, + &ctx.pathspec, + reject_entry, &ctx); clear_pathspec(&ctx.pathspec); return ret != 0; } @@ -385,16 +439,17 @@ struct tree *tree; const struct commit *commit; struct object_id oid; + char *ref = NULL; /* Remotes are only allowed to fetch actual refs */ if (remote && !remote_allow_unreachable) { - char *ref = NULL; const char *colon = strchrnul(name, ':'); int refnamelen = colon - name; - if (!dwim_ref(name, refnamelen, &oid, &ref)) + if (!dwim_ref(name, refnamelen, &oid, &ref, 0)) die(_("no such ref: %.*s"), refnamelen, name); - free(ref); + } else { + dwim_ref(name, strlen(name), &oid, &ref, 0); } if (get_oid(name, &oid)) @@ -427,16 +482,55 @@ tree = parse_tree_indirect(&tree_oid); } + ar_args->refname = ref; ar_args->tree = tree; ar_args->commit_oid = commit_oid; ar_args->commit = commit; ar_args->time = archive_time; } -#define OPT__COMPR(s, v, h, p) \ - OPT_SET_INT_F(s, NULL, v, h, p, PARSE_OPT_NONEG) -#define OPT__COMPR_HIDDEN(s, v, p) \ - OPT_SET_INT_F(s, NULL, v, "", p, PARSE_OPT_NONEG | PARSE_OPT_HIDDEN) +static void extra_file_info_clear(void *util, const char *str) +{ + struct extra_file_info *info = util; + free(info->base); + free(info); +} + +static int add_file_cb(const struct option *opt, const char *arg, int unset) +{ + struct archiver_args *args = opt->value; + const char **basep = (const char **)opt->defval; + const char *base = *basep; + char *path; + struct string_list_item *item; + struct extra_file_info *info; + + if (unset) { + string_list_clear_func(&args->extra_files, + extra_file_info_clear); + return 0; + } + + 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->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); + return 0; +} + +static int number_callback(const struct option *opt, const char *arg, int unset) +{ + BUG_ON_OPT_NEG(unset); + *(int *)opt->value = strtol(arg, NULL, 10); + return 0; +} static int parse_archive_args(int argc, const char **argv, const struct archiver **ar, struct archiver_args *args, @@ -457,21 +551,16 @@ OPT_STRING(0, "format", &format, N_("fmt"), N_("archive format")), OPT_STRING(0, "prefix", &base, N_("prefix"), N_("prepend prefix to each pathname in the archive")), + { OPTION_CALLBACK, 0, "add-file", args, N_("file"), + 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, N_("read .gitattributes in working directory")), OPT__VERBOSE(&verbose, N_("report archived files on stderr")), - OPT__COMPR('0', &compression_level, N_("store only"), 0), - OPT__COMPR('1', &compression_level, N_("compress faster"), 1), - OPT__COMPR_HIDDEN('2', &compression_level, 2), - OPT__COMPR_HIDDEN('3', &compression_level, 3), - OPT__COMPR_HIDDEN('4', &compression_level, 4), - OPT__COMPR_HIDDEN('5', &compression_level, 5), - OPT__COMPR_HIDDEN('6', &compression_level, 6), - OPT__COMPR_HIDDEN('7', &compression_level, 7), - OPT__COMPR_HIDDEN('8', &compression_level, 8), - OPT__COMPR('9', &compression_level, N_("compress better"), 9), + OPT_NUMBER_CALLBACK(&compression_level, + N_("set compression level"), number_callback), OPT_GROUP(""), OPT_BOOL('l', "list", &list, N_("list supported archive formats")), @@ -491,6 +580,8 @@ die(_("Option --exec can only be used together with --remote")); if (output) die(_("Unexpected option --output")); + if (is_remote && args->extra_files.nr) + die(_("Options --add-file and --remote cannot be used together")); if (!base) base = ""; @@ -516,7 +607,9 @@ args->compression_level = Z_DEFAULT_COMPRESSION; if (compression_level != -1) { - if ((*ar)->flags & ARCHIVER_WANT_COMPRESSION_LEVELS) + int levels_ok = (*ar)->flags & ARCHIVER_WANT_COMPRESSION_LEVELS; + int high_ok = (*ar)->flags & ARCHIVER_HIGH_COMPRESSION_LEVELS; + if (levels_ok && (compression_level <= 9 || high_ok)) args->compression_level = compression_level; else { die(_("Argument not supported for format '%s': -%d"), @@ -536,12 +629,22 @@ const char *name_hint, int remote) { const struct archiver *ar = NULL; + struct pretty_print_describe_status describe_status = {0}; + struct pretty_print_context ctx = {0}; struct archiver_args args; + int rc; git_config_get_bool("uploadarchive.allowunreachable", &remote_allow_unreachable); git_config(git_default_config, NULL); + describe_status.max_invocations = 1; + ctx.date_mode.type = DATE_NORMAL; + ctx.abbrev = DEFAULT_ABBREV; + ctx.describe_status = &describe_status; + args.pretty_ctx = &ctx; args.repo = repo; + args.prefix = prefix; + string_list_init_dup(&args.extra_files); argc = parse_archive_args(argc, argv, &ar, &args, name_hint, remote); if (!startup_info->have_repository) { /* @@ -555,7 +658,12 @@ parse_treeish_arg(argv, &args, prefix, remote); parse_pathspec_arg(argv + 1, &args); - return ar->write_archive(ar, &args); + rc = ar->write_archive(ar, &args); + + string_list_clear_func(&args.extra_files, extra_file_info_clear); + free(args.refname); + + return rc; } static int match_extension(const char *filename, const char *ext) diff -Nru git-2.25.1/archive.h git-2.34.1/archive.h --- git-2.25.1/archive.h 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/archive.h 2021-11-24 19:29:21.000000000 +0000 @@ -5,9 +5,12 @@ #include "pathspec.h" struct repository; +struct pretty_print_context; struct archiver_args { struct repository *repo; + char *refname; + const char *prefix; const char *base; size_t baselen; struct tree *tree; @@ -19,6 +22,8 @@ unsigned int worktree_attributes : 1; unsigned int convert : 1; int compression_level; + struct string_list extra_files; + struct pretty_print_context *pretty_ctx; }; /* main api */ @@ -33,6 +38,7 @@ #define ARCHIVER_WANT_COMPRESSION_LEVELS 1 #define ARCHIVER_REMOTE 2 +#define ARCHIVER_HIGH_COMPRESSION_LEVELS 4 struct archiver { const char *name; int (*write_archive)(const struct archiver *, struct archiver_args *); @@ -48,12 +54,9 @@ typedef int (*write_archive_entry_fn_t)(struct archiver_args *args, const struct object_id *oid, const char *path, size_t pathlen, - unsigned int mode); + unsigned int mode, + void *buffer, unsigned long size); int write_archive_entries(struct archiver_args *args, write_archive_entry_fn_t write_entry); -void *object_file_to_archive(const struct archiver_args *args, - const char *path, const struct object_id *oid, - unsigned int mode, enum object_type *type, - unsigned long *sizep); #endif /* ARCHIVE_H */ diff -Nru git-2.25.1/archive-tar.c git-2.34.1/archive-tar.c --- git-2.25.1/archive-tar.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/archive-tar.c 2021-11-24 19:29:21.000000000 +0000 @@ -112,7 +112,7 @@ * queues up writes, so that all our write(2) calls write exactly one * full block; pads writes to RECORDSIZE */ -static int stream_blocked(const struct object_id *oid) +static int stream_blocked(struct repository *r, const struct object_id *oid) { struct git_istream *st; enum object_type type; @@ -120,7 +120,7 @@ char buf[BLOCKSIZE]; ssize_t readlen; - st = open_istream(oid, &type, &sz, NULL); + st = open_istream(r, oid, &type, &sz, NULL); if (!st) return error(_("cannot stream blob %s"), oid_to_hex(oid)); for (;;) { @@ -242,13 +242,12 @@ static int write_tar_entry(struct archiver_args *args, const struct object_id *oid, const char *path, size_t pathlen, - unsigned int mode) + unsigned int mode, + void *buffer, unsigned long size) { struct ustar_header header; struct strbuf ext_header = STRBUF_INIT; - unsigned int old_mode = mode; - unsigned long size, size_in_header; - void *buffer; + unsigned long size_in_header; int err = 0; memset(&header, 0, sizeof(header)); @@ -282,20 +281,6 @@ } else memcpy(header.name, path, pathlen); - if (S_ISREG(mode) && !args->convert && - oid_object_info(args->repo, oid, &size) == OBJ_BLOB && - size > big_file_threshold) - buffer = NULL; - else if (S_ISLNK(mode) || S_ISREG(mode)) { - enum object_type type; - buffer = object_file_to_archive(args, path, oid, old_mode, &type, &size); - if (!buffer) - return error(_("cannot read %s"), oid_to_hex(oid)); - } else { - buffer = NULL; - size = 0; - } - if (S_ISLNK(mode)) { if (size > sizeof(header.linkname)) { xsnprintf(header.linkname, sizeof(header.linkname), @@ -324,9 +309,8 @@ if (buffer) write_blocked(buffer, size); else - err = stream_blocked(oid); + err = stream_blocked(args->repo, oid); } - free(buffer); return err; } @@ -364,7 +348,7 @@ static int nr_tar_filters; static int alloc_tar_filters; -static struct archiver *find_tar_filter(const char *name, int len) +static struct archiver *find_tar_filter(const char *name, size_t len) { int i; for (i = 0; i < nr_tar_filters; i++) { @@ -380,17 +364,18 @@ struct archiver *ar; const char *name; const char *type; - int namelen; + size_t namelen; if (parse_config_key(var, "tar", &name, &namelen, &type) < 0 || !name) return 0; ar = find_tar_filter(name, namelen); if (!ar) { - ar = xcalloc(1, sizeof(*ar)); + CALLOC_ARRAY(ar, 1); ar->name = xmemdupz(name, namelen); ar->write_archive = write_tar_filter_archive; - ar->flags = ARCHIVER_WANT_COMPRESSION_LEVELS; + ar->flags = ARCHIVER_WANT_COMPRESSION_LEVELS | + ARCHIVER_HIGH_COMPRESSION_LEVELS; ALLOC_GROW(tar_filters, nr_tar_filters + 1, alloc_tar_filters); tar_filters[nr_tar_filters++] = ar; } diff -Nru git-2.25.1/archive-zip.c git-2.34.1/archive-zip.c --- git-2.25.1/archive-zip.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/archive-zip.c 2021-11-24 19:29:21.000000000 +0000 @@ -285,7 +285,8 @@ static int write_zip_entry(struct archiver_args *args, const struct object_id *oid, const char *path, size_t pathlen, - unsigned int mode) + unsigned int mode, + void *buffer, unsigned long size) { struct zip_local_header header; uintmax_t offset = zip_offset; @@ -299,10 +300,8 @@ enum zip_method method; unsigned char *out; void *deflated = NULL; - void *buffer; struct git_istream *stream = NULL; unsigned long flags = 0; - unsigned long size; int is_binary = -1; const char *path_without_prefix = path + args->baselen; unsigned int creator_version = 0; @@ -328,13 +327,8 @@ method = ZIP_METHOD_STORE; attr2 = 16; out = NULL; - size = 0; compressed_size = 0; - buffer = NULL; } else if (S_ISREG(mode) || S_ISLNK(mode)) { - enum object_type type = oid_object_info(args->repo, oid, - &size); - method = ZIP_METHOD_STORE; attr2 = S_ISLNK(mode) ? ((mode | 0777) << 16) : (mode & 0111) ? ((mode) << 16) : 0; @@ -343,20 +337,16 @@ if (S_ISREG(mode) && args->compression_level != 0 && size > 0) method = ZIP_METHOD_DEFLATE; - if (S_ISREG(mode) && type == OBJ_BLOB && !args->convert && - size > big_file_threshold) { - stream = open_istream(oid, &type, &size, NULL); + if (!buffer) { + enum object_type type; + stream = open_istream(args->repo, oid, &type, &size, + NULL); if (!stream) return error(_("cannot stream blob %s"), oid_to_hex(oid)); flags |= ZIP_STREAM; - out = buffer = NULL; + out = NULL; } else { - buffer = object_file_to_archive(args, path, oid, mode, - &type, &size); - if (!buffer) - return error(_("cannot read %s"), - oid_to_hex(oid)); crc = crc32(crc, buffer, size); is_binary = entry_is_binary(args->repo->index, path_without_prefix, @@ -510,7 +500,6 @@ } free(deflated); - free(buffer); if (compressed_size > 0xffffffff || size > 0xffffffff || offset > 0xffffffff) { diff -Nru git-2.25.1/argv-array.c git-2.34.1/argv-array.c --- git-2.25.1/argv-array.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/argv-array.c 1970-01-01 00:00:00.000000000 +0000 @@ -1,109 +0,0 @@ -#include "cache.h" -#include "argv-array.h" -#include "strbuf.h" - -const char *empty_argv[] = { NULL }; - -void argv_array_init(struct argv_array *array) -{ - array->argv = empty_argv; - array->argc = 0; - array->alloc = 0; -} - -static void argv_array_push_nodup(struct argv_array *array, const char *value) -{ - if (array->argv == empty_argv) - array->argv = NULL; - - ALLOC_GROW(array->argv, array->argc + 2, array->alloc); - array->argv[array->argc++] = value; - array->argv[array->argc] = NULL; -} - -const char *argv_array_push(struct argv_array *array, const char *value) -{ - argv_array_push_nodup(array, xstrdup(value)); - return array->argv[array->argc - 1]; -} - -const char *argv_array_pushf(struct argv_array *array, const char *fmt, ...) -{ - va_list ap; - struct strbuf v = STRBUF_INIT; - - va_start(ap, fmt); - strbuf_vaddf(&v, fmt, ap); - va_end(ap); - - argv_array_push_nodup(array, strbuf_detach(&v, NULL)); - return array->argv[array->argc - 1]; -} - -void argv_array_pushl(struct argv_array *array, ...) -{ - va_list ap; - const char *arg; - - va_start(ap, array); - while ((arg = va_arg(ap, const char *))) - argv_array_push(array, arg); - va_end(ap); -} - -void argv_array_pushv(struct argv_array *array, const char **argv) -{ - for (; *argv; argv++) - argv_array_push(array, *argv); -} - -void argv_array_pop(struct argv_array *array) -{ - if (!array->argc) - return; - free((char *)array->argv[array->argc - 1]); - array->argv[array->argc - 1] = NULL; - array->argc--; -} - -void argv_array_split(struct argv_array *array, const char *to_split) -{ - while (isspace(*to_split)) - to_split++; - for (;;) { - const char *p = to_split; - - if (!*p) - break; - - while (*p && !isspace(*p)) - p++; - argv_array_push_nodup(array, xstrndup(to_split, p - to_split)); - - while (isspace(*p)) - p++; - to_split = p; - } -} - -void argv_array_clear(struct argv_array *array) -{ - if (array->argv != empty_argv) { - int i; - for (i = 0; i < array->argc; i++) - free((char *)array->argv[i]); - free(array->argv); - } - argv_array_init(array); -} - -const char **argv_array_detach(struct argv_array *array) -{ - if (array->argv == empty_argv) - return xcalloc(1, sizeof(const char *)); - else { - const char **ret = array->argv; - argv_array_init(array); - return ret; - } -} diff -Nru git-2.25.1/argv-array.h git-2.34.1/argv-array.h --- git-2.25.1/argv-array.h 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/argv-array.h 1970-01-01 00:00:00.000000000 +0000 @@ -1,89 +0,0 @@ -#ifndef ARGV_ARRAY_H -#define ARGV_ARRAY_H - -/** - * The argv-array API allows one to dynamically build and store - * NULL-terminated lists. An argv-array maintains the invariant that the - * `argv` member always points to a non-NULL array, and that the array is - * always NULL-terminated at the element pointed to by `argv[argc]`. This - * makes the result suitable for passing to functions expecting to receive - * argv from main(). - * - * The string-list API (documented in string-list.h) is similar, but cannot be - * used for these purposes; instead of storing a straight string pointer, - * it contains an item structure with a `util` field that is not compatible - * with the traditional argv interface. - * - * Each `argv_array` manages its own memory. Any strings pushed into the - * array are duplicated, and all memory is freed by argv_array_clear(). - */ - -extern const char *empty_argv[]; - -/** - * A single array. This should be initialized by assignment from - * `ARGV_ARRAY_INIT`, or by calling `argv_array_init`. The `argv` - * member contains the actual array; the `argc` member contains the - * number of elements in the array, not including the terminating - * NULL. - */ -struct argv_array { - const char **argv; - int argc; - int alloc; -}; - -#define ARGV_ARRAY_INIT { empty_argv, 0, 0 } - -/** - * Initialize an array. This is no different than assigning from - * `ARGV_ARRAY_INIT`. - */ -void argv_array_init(struct argv_array *); - -/* Push a copy of a string onto the end of the array. */ -const char *argv_array_push(struct argv_array *, const char *); - -/** - * Format a string and push it onto the end of the array. This is a - * convenience wrapper combining `strbuf_addf` and `argv_array_push`. - */ -__attribute__((format (printf,2,3))) -const char *argv_array_pushf(struct argv_array *, const char *fmt, ...); - -/** - * Push a list of strings onto the end of the array. The arguments - * should be a list of `const char *` strings, terminated by a NULL - * argument. - */ -LAST_ARG_MUST_BE_NULL -void argv_array_pushl(struct argv_array *, ...); - -/* Push a null-terminated array of strings onto the end of the array. */ -void argv_array_pushv(struct argv_array *, const char **); - -/** - * Remove the final element from the array. If there are no - * elements in the array, do nothing. - */ -void argv_array_pop(struct argv_array *); - -/* Splits by whitespace; does not handle quoted arguments! */ -void argv_array_split(struct argv_array *, const char *); - -/** - * Free all memory associated with the array and return it to the - * initial, empty state. - */ -void argv_array_clear(struct argv_array *); - -/** - * Disconnect the `argv` member from the `argv_array` struct and - * return it. The caller is responsible for freeing the memory used - * by the array, and by the strings it references. After detaching, - * the `argv_array` is in a reinitialized state and can be pushed - * into again. - */ -const char **argv_array_detach(struct argv_array *); - -#endif /* ARGV_ARRAY_H */ diff -Nru git-2.25.1/attr.c git-2.34.1/attr.c --- git-2.25.1/attr.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/attr.c 2021-11-24 19:29:21.000000000 +0000 @@ -14,6 +14,7 @@ #include "utf8.h" #include "quote.h" #include "thread-utils.h" +#include "dir.h" const char git_attr__true[] = "(builtin)true"; const char git_attr__false[] = "\0(builtin)false"; @@ -52,13 +53,6 @@ pthread_mutex_unlock(&map->mutex); } -/* - * The global dictionary of all interned attributes. This - * is a singleton object which is shared between threads. - * Access to this dictionary must be surrounded with a mutex. - */ -static struct attr_hashmap g_attr_hashmap; - /* The container for objects stored in "struct attr_hashmap" */ struct attr_hash_entry { struct hashmap_entry ent; @@ -80,11 +74,14 @@ return (a->keylen != b->keylen) || strncmp(a->key, b->key, a->keylen); } -/* Initialize an 'attr_hashmap' object */ -static void attr_hashmap_init(struct attr_hashmap *map) -{ - hashmap_init(&map->map, attr_hash_entry_cmp, NULL, 0); -} +/* + * The global dictionary of all interned attributes. This + * is a singleton object which is shared between threads. + * Access to this dictionary must be surrounded with a mutex. + */ +static struct attr_hashmap g_attr_hashmap = { + HASHMAP_INIT(attr_hash_entry_cmp, NULL) +}; /* * Retrieve the 'value' stored in a hashmap given the provided 'key'. @@ -96,9 +93,6 @@ struct attr_hash_entry k; struct attr_hash_entry *e; - if (!map->map.tablesize) - attr_hashmap_init(map); - hashmap_entry_init(&k.ent, memhash(key, keylen)); k.key = key; k.keylen = keylen; @@ -114,9 +108,6 @@ { struct attr_hash_entry *e; - if (!map->map.tablesize) - attr_hashmap_init(map); - e = xmalloc(sizeof(struct attr_hash_entry)); hashmap_entry_init(&e->ent, memhash(key, keylen)); e->key = key; @@ -288,6 +279,10 @@ static const char blank[] = " \t\r\n"; +/* Flags usable in read_attr() and parse_attr_line() family of functions. */ +#define READ_ATTR_MACRO_OK (1<<0) +#define READ_ATTR_NOFOLLOW (1<<1) + /* * Parse a whitespace-delimited attribute state (i.e., "attr", * "-attr", "!attr", or "attr=value") from the string starting at src. @@ -341,7 +336,7 @@ } static struct match_attr *parse_attr_line(const char *line, const char *src, - int lineno, int macro_ok) + int lineno, unsigned flags) { int namelen; int num_attr, i; @@ -365,7 +360,7 @@ if (strlen(ATTRIBUTE_MACRO_PREFIX) < namelen && starts_with(name, ATTRIBUTE_MACRO_PREFIX)) { - if (!macro_ok) { + if (!(flags & READ_ATTR_MACRO_OK)) { fprintf_ln(stderr, _("%s not allowed: %s:%d"), name, src, lineno); goto fail_return; @@ -579,7 +574,7 @@ check = attr_check_alloc(); check->nr = cnt; check->alloc = cnt; - check->items = xcalloc(cnt, sizeof(struct attr_check_item)); + CALLOC_ARRAY(check->items, cnt); check->items[0].attr = git_attr(one); va_start(params, one); @@ -663,11 +658,11 @@ const char *line, const char *src, int lineno, - int macro_ok) + unsigned flags) { struct match_attr *a; - a = parse_attr_line(line, src, lineno, macro_ok); + a = parse_attr_line(line, src, lineno, flags); if (!a) return; ALLOC_GROW(res->attrs, res->num_matches + 1, res->alloc); @@ -680,9 +675,10 @@ const char *line; int lineno = 0; - res = xcalloc(1, sizeof(*res)); + CALLOC_ARRAY(res, 1); while ((line = *(list++)) != NULL) - handle_attr_line(res, line, "[builtin]", ++lineno, 1); + handle_attr_line(res, line, "[builtin]", ++lineno, + READ_ATTR_MACRO_OK); return res; } @@ -690,7 +686,7 @@ * Callers into the attribute system assume there is a single, system-wide * global state where attributes are read from and when the state is flipped by * calling git_attr_set_direction(), the stack frames that have been - * constructed need to be discarded so so that subsequent calls into the + * constructed need to be discarded so that subsequent calls into the * attribute system will lazily read from the right place. Since changing * direction causes a global paradigm shift, it should not ever be called while * another thread could potentially be calling into the attribute system. @@ -708,29 +704,39 @@ direction = new_direction; } -static struct attr_stack *read_attr_from_file(const char *path, int macro_ok) +static struct attr_stack *read_attr_from_file(const char *path, unsigned flags) { - FILE *fp = fopen_or_warn(path, "r"); + int fd; + FILE *fp; struct attr_stack *res; char buf[2048]; int lineno = 0; - if (!fp) + if (flags & READ_ATTR_NOFOLLOW) + fd = open_nofollow(path, O_RDONLY); + else + fd = open(path, O_RDONLY); + + if (fd < 0) { + warn_on_fopen_errors(path); return NULL; - res = xcalloc(1, sizeof(*res)); + } + fp = xfdopen(fd, "r"); + + CALLOC_ARRAY(res, 1); while (fgets(buf, sizeof(buf), fp)) { char *bufp = buf; if (!lineno) skip_utf8_bom(&bufp, strlen(bufp)); - handle_attr_line(res, bufp, path, ++lineno, macro_ok); + handle_attr_line(res, bufp, path, ++lineno, flags); } fclose(fp); return res; } -static struct attr_stack *read_attr_from_index(const struct index_state *istate, +static struct attr_stack *read_attr_from_index(struct index_state *istate, const char *path, - int macro_ok) + unsigned flags) { struct attr_stack *res; char *buf, *sp; @@ -739,11 +745,25 @@ if (!istate) return NULL; + /* + * The .gitattributes file only applies to files within its + * parent directory. In the case of cone-mode sparse-checkout, + * the .gitattributes file is sparse if and only if all paths + * within that directory are also sparse. Thus, don't load the + * .gitattributes file since it will not matter. + * + * In the case of a sparse index, it is critical that we don't go + * looking for a .gitattributes file, as doing so would cause the + * index to expand. + */ + if (!path_in_cone_mode_sparse_checkout(path, istate)) + return NULL; + buf = read_blob_data_from_index(istate, path, NULL); if (!buf) return NULL; - res = xcalloc(1, sizeof(*res)); + CALLOC_ARRAY(res, 1); for (sp = buf; *sp; ) { char *ep; int more; @@ -751,27 +771,27 @@ ep = strchrnul(sp, '\n'); more = (*ep == '\n'); *ep = '\0'; - handle_attr_line(res, sp, path, ++lineno, macro_ok); + handle_attr_line(res, sp, path, ++lineno, flags); sp = ep + more; } free(buf); return res; } -static struct attr_stack *read_attr(const struct index_state *istate, - const char *path, int macro_ok) +static struct attr_stack *read_attr(struct index_state *istate, + const char *path, unsigned flags) { struct attr_stack *res = NULL; if (direction == GIT_ATTR_INDEX) { - res = read_attr_from_index(istate, path, macro_ok); + res = read_attr_from_index(istate, path, flags); } else if (!is_bare_repository()) { if (direction == GIT_ATTR_CHECKOUT) { - res = read_attr_from_index(istate, path, macro_ok); + res = read_attr_from_index(istate, path, flags); if (!res) - res = read_attr_from_file(path, macro_ok); + res = read_attr_from_file(path, flags); } else if (direction == GIT_ATTR_CHECKIN) { - res = read_attr_from_file(path, macro_ok); + res = read_attr_from_file(path, flags); if (!res) /* * There is no checked out .gitattributes file @@ -779,12 +799,12 @@ * We allow operation in a sparsely checked out * work tree, so read from it. */ - res = read_attr_from_index(istate, path, macro_ok); + res = read_attr_from_index(istate, path, flags); } } if (!res) - res = xcalloc(1, sizeof(*res)); + CALLOC_ARRAY(res, 1); return res; } @@ -850,10 +870,11 @@ } } -static void bootstrap_attr_stack(const struct index_state *istate, +static void bootstrap_attr_stack(struct index_state *istate, struct attr_stack **stack) { struct attr_stack *e; + unsigned flags = READ_ATTR_MACRO_OK; if (*stack) return; @@ -864,31 +885,31 @@ /* system-wide frame */ if (git_attr_system()) { - e = read_attr_from_file(git_etc_gitattributes(), 1); + e = read_attr_from_file(git_etc_gitattributes(), flags); push_stack(stack, e, NULL, 0); } /* home directory */ if (get_home_gitattributes()) { - e = read_attr_from_file(get_home_gitattributes(), 1); + e = read_attr_from_file(get_home_gitattributes(), flags); push_stack(stack, e, NULL, 0); } /* root directory */ - e = read_attr(istate, GITATTRIBUTES_FILE, 1); + e = read_attr(istate, GITATTRIBUTES_FILE, flags | READ_ATTR_NOFOLLOW); push_stack(stack, e, xstrdup(""), 0); /* info frame */ if (startup_info->have_repository) - e = read_attr_from_file(git_path_info_attributes(), 1); + e = read_attr_from_file(git_path_info_attributes(), flags); else e = NULL; if (!e) - e = xcalloc(1, sizeof(struct attr_stack)); + CALLOC_ARRAY(e, 1); push_stack(stack, e, NULL, 0); } -static void prepare_attr_stack(const struct index_state *istate, +static void prepare_attr_stack(struct index_state *istate, const char *path, int dirlen, struct attr_stack **stack) { @@ -966,7 +987,7 @@ strbuf_add(&pathbuf, path + pathbuf.len, (len - pathbuf.len)); strbuf_addf(&pathbuf, "/%s", GITATTRIBUTES_FILE); - next = read_attr(istate, pathbuf.buf, 0); + next = read_attr(istate, pathbuf.buf, READ_ATTR_NOFOLLOW); /* reset the pathbuf to not include "/.gitattributes" */ strbuf_setlen(&pathbuf, len); @@ -1088,7 +1109,7 @@ * If check->check_nr is non-zero, only attributes in check[] are collected. * Otherwise all attributes are collected. */ -static void collect_some_attrs(const struct index_state *istate, +static void collect_some_attrs(struct index_state *istate, const char *path, struct attr_check *check) { @@ -1117,7 +1138,7 @@ fill(path, pathlen, basename_offset, check->stack, check->all_attrs, rem); } -void git_check_attr(const struct index_state *istate, +void git_check_attr(struct index_state *istate, const char *path, struct attr_check *check) { @@ -1134,7 +1155,7 @@ } } -void git_all_attrs(const struct index_state *istate, +void git_all_attrs(struct index_state *istate, const char *path, struct attr_check *check) { int i; diff -Nru git-2.25.1/attr.h git-2.34.1/attr.h --- git-2.25.1/attr.h 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/attr.h 2021-11-24 19:29:21.000000000 +0000 @@ -190,14 +190,14 @@ */ const char *git_attr_name(const struct git_attr *); -void git_check_attr(const struct index_state *istate, +void git_check_attr(struct index_state *istate, const char *path, struct attr_check *check); /* * Retrieve all attributes that apply to the specified path. * check holds the attributes and their values. */ -void git_all_attrs(const struct index_state *istate, +void git_all_attrs(struct index_state *istate, const char *path, struct attr_check *check); enum git_attr_direction { diff -Nru git-2.25.1/azure-pipelines.yml git-2.34.1/azure-pipelines.yml --- git-2.25.1/azure-pipelines.yml 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/azure-pipelines.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,545 +0,0 @@ -variables: - Agent.Source.Git.ShallowFetchDepth: 1 - -jobs: -- job: windows_build - displayName: Windows Build - condition: succeeded() - pool: Hosted - timeoutInMinutes: 240 - steps: - - powershell: | - if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { - net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no - cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\ - } - displayName: 'Mount test-cache' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - - powershell: | - $urlbase = "https://dev.azure.com/git-for-windows/git/_apis/build/builds" - $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=22&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id - $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[1].resource.downloadUrl - (New-Object Net.WebClient).DownloadFile($downloadUrl,"git-sdk-64-minimal.zip") - Expand-Archive git-sdk-64-minimal.zip -DestinationPath . -Force - Remove-Item git-sdk-64-minimal.zip - - # Let Git ignore the SDK and the test-cache - "/git-sdk-64-minimal/`n/test-cache/`n" | Out-File -NoNewLine -Encoding ascii -Append "$(Build.SourcesDirectory)\.git\info\exclude" - displayName: 'Download git-sdk-64-minimal' - - powershell: | - & git-sdk-64-minimal\usr\bin\bash.exe -lc @" - ci/make-test-artifacts.sh artifacts - "@ - if (!$?) { exit(1) } - displayName: Build - env: - HOME: $(Build.SourcesDirectory) - MSYSTEM: MINGW64 - DEVELOPER: 1 - NO_PERL: 1 - - task: PublishPipelineArtifact@0 - displayName: 'Publish Pipeline Artifact: test artifacts' - inputs: - artifactName: 'windows-artifacts' - targetPath: '$(Build.SourcesDirectory)\artifacts' - - task: PublishPipelineArtifact@0 - displayName: 'Publish Pipeline Artifact: git-sdk-64-minimal' - inputs: - artifactName: 'git-sdk-64-minimal' - targetPath: '$(Build.SourcesDirectory)\git-sdk-64-minimal' - - powershell: | - if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { - cmd /c rmdir "$(Build.SourcesDirectory)\test-cache" - } - displayName: 'Unmount test-cache' - condition: true - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - -- job: windows_test - displayName: Windows Test - dependsOn: windows_build - condition: succeeded() - pool: Hosted - timeoutInMinutes: 240 - strategy: - parallel: 10 - steps: - - powershell: | - if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { - net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no - cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\ - } - displayName: 'Mount test-cache' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - - task: DownloadPipelineArtifact@0 - displayName: 'Download Pipeline Artifact: test artifacts' - inputs: - artifactName: 'windows-artifacts' - targetPath: '$(Build.SourcesDirectory)' - - task: DownloadPipelineArtifact@0 - displayName: 'Download Pipeline Artifact: git-sdk-64-minimal' - inputs: - artifactName: 'git-sdk-64-minimal' - targetPath: '$(Build.SourcesDirectory)\git-sdk-64-minimal' - - powershell: | - & git-sdk-64-minimal\usr\bin\bash.exe -lc @" - test -f artifacts.tar.gz || { - echo No test artifacts found\; skipping >&2 - exit 0 - } - tar xf artifacts.tar.gz || exit 1 - - # Let Git ignore the SDK and the test-cache - printf '%s\n' /git-sdk-64-minimal/ /test-cache/ >>.git/info/exclude - - ci/run-test-slice.sh `$SYSTEM_JOBPOSITIONINPHASE `$SYSTEM_TOTALJOBSINPHASE || { - ci/print-test-failures.sh - exit 1 - } - "@ - if (!$?) { exit(1) } - displayName: 'Test (parallel)' - env: - HOME: $(Build.SourcesDirectory) - MSYSTEM: MINGW64 - NO_SVN_TESTS: 1 - GIT_TEST_SKIP_REBASE_P: 1 - - powershell: | - if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { - cmd /c rmdir "$(Build.SourcesDirectory)\test-cache" - } - displayName: 'Unmount test-cache' - condition: true - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - - task: PublishTestResults@2 - displayName: 'Publish Test Results **/TEST-*.xml' - inputs: - mergeTestResults: true - testRunTitle: 'windows' - platform: Windows - publishRunAttachments: false - condition: succeededOrFailed() - - task: PublishBuildArtifacts@1 - displayName: 'Publish trash directories of failed tests' - condition: failed() - inputs: - PathtoPublish: t/failed-test-artifacts - ArtifactName: failed-test-artifacts - -- job: vs_build - displayName: Visual Studio Build - condition: succeeded() - pool: Hosted VS2017 - timeoutInMinutes: 240 - steps: - - powershell: | - if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { - net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no - cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\ - } - displayName: 'Mount test-cache' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - - powershell: | - $urlbase = "https://dev.azure.com/git-for-windows/git/_apis/build/builds" - $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=22&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id - $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[1].resource.downloadUrl - (New-Object Net.WebClient).DownloadFile($downloadUrl,"git-sdk-64-minimal.zip") - Expand-Archive git-sdk-64-minimal.zip -DestinationPath . -Force - Remove-Item git-sdk-64-minimal.zip - - # Let Git ignore the SDK and the test-cache - "/git-sdk-64-minimal/`n/test-cache/`n" | Out-File -NoNewLine -Encoding ascii -Append "$(Build.SourcesDirectory)\.git\info\exclude" - displayName: 'Download git-sdk-64-minimal' - - powershell: | - & git-sdk-64-minimal\usr\bin\bash.exe -lc @" - make NDEBUG=1 DEVELOPER=1 vcxproj - "@ - if (!$?) { exit(1) } - displayName: Generate Visual Studio Solution - env: - HOME: $(Build.SourcesDirectory) - MSYSTEM: MINGW64 - DEVELOPER: 1 - NO_PERL: 1 - GIT_CONFIG_PARAMETERS: "'user.name=CI' 'user.email=ci@git'" - - powershell: | - $urlbase = "https://dev.azure.com/git/git/_apis/build/builds" - $id = ((Invoke-WebRequest -UseBasicParsing "${urlbase}?definitions=9&statusFilter=completed&resultFilter=succeeded&`$top=1").content | ConvertFrom-JSON).value[0].id - $downloadUrl = ((Invoke-WebRequest -UseBasicParsing "${urlbase}/$id/artifacts").content | ConvertFrom-JSON).value[0].resource.downloadUrl - (New-Object Net.WebClient).DownloadFile($downloadUrl, "compat.zip") - Expand-Archive compat.zip -DestinationPath . -Force - Remove-Item compat.zip - displayName: 'Download vcpkg artifacts' - - task: MSBuild@1 - inputs: - solution: git.sln - platform: x64 - configuration: Release - maximumCpuCount: 4 - - powershell: | - & compat\vcbuild\vcpkg_copy_dlls.bat release - if (!$?) { exit(1) } - & git-sdk-64-minimal\usr\bin\bash.exe -lc @" - mkdir -p artifacts && - eval \"`$(make -n artifacts-tar INCLUDE_DLLS_IN_ARTIFACTS=YesPlease ARTIFACTS_DIRECTORY=artifacts | grep ^tar)\" - "@ - if (!$?) { exit(1) } - displayName: Bundle artifact tar - env: - HOME: $(Build.SourcesDirectory) - MSYSTEM: MINGW64 - DEVELOPER: 1 - NO_PERL: 1 - MSVC: 1 - VCPKG_ROOT: $(Build.SourcesDirectory)\compat\vcbuild\vcpkg - - powershell: | - $tag = (Invoke-WebRequest -UseBasicParsing "https://gitforwindows.org/latest-tag.txt").content - $version = (Invoke-WebRequest -UseBasicParsing "https://gitforwindows.org/latest-version.txt").content - $url = "https://github.com/git-for-windows/git/releases/download/${tag}/PortableGit-${version}-64-bit.7z.exe" - (New-Object Net.WebClient).DownloadFile($url,"PortableGit.exe") - & .\PortableGit.exe -y -oartifacts\PortableGit - # Wait until it is unpacked - while (-not @(Remove-Item -ErrorAction SilentlyContinue PortableGit.exe; $?)) { sleep 1 } - displayName: Download & extract portable Git - - task: PublishPipelineArtifact@0 - displayName: 'Publish Pipeline Artifact: MSVC test artifacts' - inputs: - artifactName: 'vs-artifacts' - targetPath: '$(Build.SourcesDirectory)\artifacts' - - powershell: | - if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { - cmd /c rmdir "$(Build.SourcesDirectory)\test-cache" - } - displayName: 'Unmount test-cache' - condition: true - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - -- job: vs_test - displayName: Visual Studio Test - dependsOn: vs_build - condition: succeeded() - pool: Hosted - timeoutInMinutes: 240 - strategy: - parallel: 10 - steps: - - powershell: | - if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { - net use s: \\gitfileshare.file.core.windows.net\test-cache "$GITFILESHAREPWD" /user:AZURE\gitfileshare /persistent:no - cmd /c mklink /d "$(Build.SourcesDirectory)\test-cache" S:\ - } - displayName: 'Mount test-cache' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - - task: DownloadPipelineArtifact@0 - displayName: 'Download Pipeline Artifact: VS test artifacts' - inputs: - artifactName: 'vs-artifacts' - targetPath: '$(Build.SourcesDirectory)' - - powershell: | - & PortableGit\git-cmd.exe --command=usr\bin\bash.exe -lc @" - test -f artifacts.tar.gz || { - echo No test artifacts found\; skipping >&2 - exit 0 - } - tar xf artifacts.tar.gz || exit 1 - - # Let Git ignore the SDK and the test-cache - printf '%s\n' /PortableGit/ /test-cache/ >>.git/info/exclude - - cd t && - PATH=\"`$PWD/helper:`$PATH\" && - test-tool.exe run-command testsuite --jobs=10 -V -x --write-junit-xml \ - `$(test-tool.exe path-utils slice-tests \ - `$SYSTEM_JOBPOSITIONINPHASE `$SYSTEM_TOTALJOBSINPHASE t[0-9]*.sh) - "@ - if (!$?) { exit(1) } - displayName: 'Test (parallel)' - env: - HOME: $(Build.SourcesDirectory) - MSYSTEM: MINGW64 - NO_SVN_TESTS: 1 - GIT_TEST_SKIP_REBASE_P: 1 - - powershell: | - if ("$GITFILESHAREPWD" -ne "" -and "$GITFILESHAREPWD" -ne "`$`(gitfileshare.pwd)") { - cmd /c rmdir "$(Build.SourcesDirectory)\test-cache" - } - displayName: 'Unmount test-cache' - condition: true - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - - task: PublishTestResults@2 - displayName: 'Publish Test Results **/TEST-*.xml' - inputs: - mergeTestResults: true - testRunTitle: 'vs' - platform: Windows - publishRunAttachments: false - condition: succeededOrFailed() - - task: PublishBuildArtifacts@1 - displayName: 'Publish trash directories of failed tests' - condition: failed() - inputs: - PathtoPublish: t/failed-test-artifacts - ArtifactName: failed-vs-test-artifacts - -- job: linux_clang - displayName: linux-clang - condition: succeeded() - pool: Hosted Ubuntu 1604 - steps: - - bash: | - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - - sudo apt-get update && - sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev apache2-bin && - - export CC=clang || exit 1 - - ci/install-dependencies.sh || exit 1 - ci/run-build-and-tests.sh || { - ci/print-test-failures.sh - exit 1 - } - - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 - displayName: 'ci/run-build-and-tests.sh' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - - task: PublishTestResults@2 - displayName: 'Publish Test Results **/TEST-*.xml' - inputs: - mergeTestResults: true - testRunTitle: 'linux-clang' - platform: Linux - publishRunAttachments: false - condition: succeededOrFailed() - - task: PublishBuildArtifacts@1 - displayName: 'Publish trash directories of failed tests' - condition: failed() - inputs: - PathtoPublish: t/failed-test-artifacts - ArtifactName: failed-test-artifacts - -- job: linux_gcc - displayName: linux-gcc - condition: succeeded() - pool: Hosted Ubuntu 1604 - steps: - - bash: | - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - - sudo add-apt-repository ppa:ubuntu-toolchain-r/test && - sudo apt-get update && - sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev apache2 language-pack-is git-svn gcc-8 || exit 1 - - ci/install-dependencies.sh || exit 1 - ci/run-build-and-tests.sh || { - ci/print-test-failures.sh - exit 1 - } - - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 - displayName: 'ci/run-build-and-tests.sh' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - - task: PublishTestResults@2 - displayName: 'Publish Test Results **/TEST-*.xml' - inputs: - mergeTestResults: true - testRunTitle: 'linux-gcc' - platform: Linux - publishRunAttachments: false - condition: succeededOrFailed() - - task: PublishBuildArtifacts@1 - displayName: 'Publish trash directories of failed tests' - condition: failed() - inputs: - PathtoPublish: t/failed-test-artifacts - ArtifactName: failed-test-artifacts - -- job: osx_clang - displayName: osx-clang - condition: succeeded() - pool: Hosted macOS - steps: - - bash: | - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - - export CC=clang - - ci/install-dependencies.sh || exit 1 - ci/run-build-and-tests.sh || { - ci/print-test-failures.sh - exit 1 - } - - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || umount "$HOME/test-cache" || exit 1 - displayName: 'ci/run-build-and-tests.sh' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - - task: PublishTestResults@2 - displayName: 'Publish Test Results **/TEST-*.xml' - inputs: - mergeTestResults: true - testRunTitle: 'osx-clang' - platform: macOS - publishRunAttachments: false - condition: succeededOrFailed() - - task: PublishBuildArtifacts@1 - displayName: 'Publish trash directories of failed tests' - condition: failed() - inputs: - PathtoPublish: t/failed-test-artifacts - ArtifactName: failed-test-artifacts - -- job: osx_gcc - displayName: osx-gcc - condition: succeeded() - pool: Hosted macOS - steps: - - bash: | - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - - ci/install-dependencies.sh || exit 1 - ci/run-build-and-tests.sh || { - ci/print-test-failures.sh - exit 1 - } - - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || umount "$HOME/test-cache" || exit 1 - displayName: 'ci/run-build-and-tests.sh' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - - task: PublishTestResults@2 - displayName: 'Publish Test Results **/TEST-*.xml' - inputs: - mergeTestResults: true - testRunTitle: 'osx-gcc' - platform: macOS - publishRunAttachments: false - condition: succeededOrFailed() - - task: PublishBuildArtifacts@1 - displayName: 'Publish trash directories of failed tests' - condition: failed() - inputs: - PathtoPublish: t/failed-test-artifacts - ArtifactName: failed-test-artifacts - -- job: gettext_poison - displayName: GETTEXT_POISON - condition: succeeded() - pool: Hosted Ubuntu 1604 - steps: - - bash: | - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - - sudo apt-get update && - sudo apt-get -y install git gcc make libssl-dev libcurl4-openssl-dev libexpat-dev tcl tk gettext git-email zlib1g-dev && - - export jobname=GETTEXT_POISON || exit 1 - - ci/run-build-and-tests.sh || { - ci/print-test-failures.sh - exit 1 - } - - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 - displayName: 'ci/run-build-and-tests.sh' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - - task: PublishTestResults@2 - displayName: 'Publish Test Results **/TEST-*.xml' - inputs: - mergeTestResults: true - testRunTitle: 'gettext-poison' - platform: Linux - publishRunAttachments: false - condition: succeededOrFailed() - - task: PublishBuildArtifacts@1 - displayName: 'Publish trash directories of failed tests' - condition: failed() - inputs: - PathtoPublish: t/failed-test-artifacts - ArtifactName: failed-test-artifacts - -- job: linux32 - displayName: Linux32 - condition: succeeded() - pool: Hosted Ubuntu 1604 - steps: - - bash: | - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - - res=0 - sudo AGENT_OS="$AGENT_OS" BUILD_BUILDNUMBER="$BUILD_BUILDNUMBER" BUILD_REPOSITORY_URI="$BUILD_REPOSITORY_URI" BUILD_SOURCEBRANCH="$BUILD_SOURCEBRANCH" BUILD_SOURCEVERSION="$BUILD_SOURCEVERSION" SYSTEM_PHASENAME="$SYSTEM_PHASENAME" SYSTEM_TASKDEFINITIONSURI="$SYSTEM_TASKDEFINITIONSURI" SYSTEM_TEAMPROJECT="$SYSTEM_TEAMPROJECT" CC=$CC MAKEFLAGS="$MAKEFLAGS" bash -lxc ci/run-linux32-docker.sh || res=1 - - sudo chmod a+r t/out/TEST-*.xml - test ! -d t/failed-test-artifacts || sudo chmod a+r t/failed-test-artifacts - - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || res=1 - exit $res - displayName: 'ci/run-linux32-docker.sh' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - - task: PublishTestResults@2 - displayName: 'Publish Test Results **/TEST-*.xml' - inputs: - mergeTestResults: true - testRunTitle: 'linux32' - platform: Linux - publishRunAttachments: false - condition: succeededOrFailed() - - task: PublishBuildArtifacts@1 - displayName: 'Publish trash directories of failed tests' - condition: failed() - inputs: - PathtoPublish: t/failed-test-artifacts - ArtifactName: failed-test-artifacts - -- job: static_analysis - displayName: StaticAnalysis - condition: succeeded() - pool: Hosted Ubuntu 1604 - steps: - - bash: | - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - - sudo apt-get update && - sudo apt-get install -y coccinelle libcurl4-openssl-dev libssl-dev libexpat-dev gettext && - - export jobname=StaticAnalysis && - - ci/run-static-analysis.sh || exit 1 - - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 - displayName: 'ci/run-static-analysis.sh' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) - -- job: documentation - displayName: Documentation - condition: succeeded() - pool: Hosted Ubuntu 1604 - steps: - - bash: | - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || ci/mount-fileshare.sh //gitfileshare.file.core.windows.net/test-cache gitfileshare "$GITFILESHAREPWD" "$HOME/test-cache" || exit 1 - - sudo apt-get update && - sudo apt-get install -y asciidoc xmlto asciidoctor docbook-xsl-ns && - - export ALREADY_HAVE_ASCIIDOCTOR=yes. && - export jobname=Documentation && - - ci/test-documentation.sh || exit 1 - - test "$GITFILESHAREPWD" = '$(gitfileshare.pwd)' || sudo umount "$HOME/test-cache" || exit 1 - displayName: 'ci/test-documentation.sh' - env: - GITFILESHAREPWD: $(gitfileshare.pwd) diff -Nru git-2.25.1/banned.h git-2.34.1/banned.h --- git-2.25.1/banned.h 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/banned.h 2021-11-24 19:29:21.000000000 +0000 @@ -29,4 +29,17 @@ #define vsprintf(buf,fmt,arg) BANNED(vsprintf) #endif +#undef gmtime +#define gmtime(t) BANNED(gmtime) +#undef localtime +#define localtime(t) BANNED(localtime) +#undef ctime +#define ctime(t) BANNED(ctime) +#undef ctime_r +#define ctime_r(t, buf) BANNED(ctime_r) +#undef asctime +#define asctime(t) BANNED(asctime) +#undef asctime_r +#define asctime_r(t, buf) BANNED(asctime_r) + #endif /* BANNED_H */ diff -Nru git-2.25.1/bisect.c git-2.34.1/bisect.c --- git-2.25.1/bisect.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/bisect.c 2021-11-24 19:29:21.000000000 +0000 @@ -6,15 +6,16 @@ #include "refs.h" #include "list-objects.h" #include "quote.h" -#include "sha1-lookup.h" +#include "hash-lookup.h" #include "run-command.h" #include "log-tree.h" #include "bisect.h" -#include "sha1-array.h" -#include "argv-array.h" +#include "oid-array.h" +#include "strvec.h" #include "commit-slab.h" #include "commit-reach.h" #include "object-store.h" +#include "dir.h" static struct oid_array good_revs; static struct oid_array skipped_revs; @@ -22,7 +23,6 @@ static struct object_id *current_bad_oid; static const char *argv_checkout[] = {"checkout", "-q", NULL, "--", NULL}; -static const char *argv_show_branch[] = {"show-branch", NULL, NULL}; static const char *term_bad; static const char *term_good; @@ -88,21 +88,24 @@ **commit_weight_at(&commit_weight, elem->item) = weight; } -static int count_interesting_parents(struct commit *commit) +static int count_interesting_parents(struct commit *commit, unsigned bisect_flags) { struct commit_list *p; int count; for (count = 0, p = commit->parents; p; p = p->next) { - if (p->item->object.flags & UNINTERESTING) - continue; - count++; + if (!(p->item->object.flags & UNINTERESTING)) + count++; + if (bisect_flags & FIND_BISECTION_FIRST_PARENT_ONLY) + break; } return count; } -static inline int halfway(struct commit_list *p, int nr) +static inline int approx_halfway(struct commit_list *p, int nr) { + int diff; + /* * Don't short-cut something we are not going to return! */ @@ -111,13 +114,22 @@ if (DEBUG_BISECT) return 0; /* - * 2 and 3 are halfway of 5. + * For small number of commits 2 and 3 are halfway of 5, and * 3 is halfway of 6 but 2 and 4 are not. */ - switch (2 * weight(p) - nr) { + diff = 2 * weight(p) - nr; + switch (diff) { case -1: case 0: case 1: return 1; default: + /* + * For large number of commits we are not so strict, it's + * good enough if it's within ~0.1% of the halfway point, + * e.g. 5000 is exactly halfway of 10000, but we consider + * the values [4996, 5004] as halfway as well. + */ + if (abs(diff) < nr / 1024) + return 1; return 0; } } @@ -135,7 +147,7 @@ for (p = list; p; p = p->next) { struct commit_list *pp; struct commit *commit = p->item; - unsigned flags = commit->object.flags; + unsigned commit_flags = commit->object.flags; enum object_type type; unsigned long size; char *buf = read_object_file(&commit->object.oid, &type, @@ -144,9 +156,9 @@ int subject_len; fprintf(stderr, "%c%c%c ", - (flags & TREESAME) ? ' ' : 'T', - (flags & UNINTERESTING) ? 'U' : ' ', - (flags & COUNTED) ? 'C' : ' '); + (commit_flags & TREESAME) ? ' ' : 'T', + (commit_flags & UNINTERESTING) ? 'U' : ' ', + (commit_flags & COUNTED) ? 'C' : ' '); if (*commit_weight_at(&commit_weight, p->item)) fprintf(stderr, "%3d", weight(p)); else @@ -171,9 +183,9 @@ best = list; for (p = list; p; p = p->next) { int distance; - unsigned flags = p->item->object.flags; + unsigned commit_flags = p->item->object.flags; - if (flags & TREESAME) + if (commit_flags & TREESAME) continue; distance = weight(p); if (nr - distance < distance) @@ -212,9 +224,9 @@ for (p = list, cnt = 0; p; p = p->next) { int distance; - unsigned flags = p->item->object.flags; + unsigned commit_flags = p->item->object.flags; - if (flags & TREESAME) + if (commit_flags & TREESAME) continue; distance = weight(p); if (nr - distance < distance) @@ -259,7 +271,7 @@ */ static struct commit_list *do_find_bisection(struct commit_list *list, int nr, int *weights, - int find_all) + unsigned bisect_flags) { int n, counted; struct commit_list *p; @@ -268,12 +280,12 @@ for (n = 0, p = list; p; p = p->next) { struct commit *commit = p->item; - unsigned flags = commit->object.flags; + unsigned commit_flags = commit->object.flags; *commit_weight_at(&commit_weight, p->item) = &weights[n++]; - switch (count_interesting_parents(commit)) { + switch (count_interesting_parents(commit, bisect_flags)) { case 0: - if (!(flags & TREESAME)) { + if (!(commit_flags & TREESAME)) { weight_set(p, 1); counted++; show_list("bisection 2 count one", @@ -314,11 +326,14 @@ continue; if (weight(p) != -2) continue; + if (bisect_flags & FIND_BISECTION_FIRST_PARENT_ONLY) + BUG("shouldn't be calling count-distance in fp mode"); weight_set(p, count_distance(p)); clear_distance(list); - /* Does it happen to be at exactly half-way? */ - if (!find_all && halfway(p, nr)) + /* Does it happen to be at half-way? */ + if (!(bisect_flags & FIND_BISECTION_ALL) && + approx_halfway(p, nr)) return p; counted++; } @@ -328,11 +343,14 @@ while (counted < nr) { for (p = list; p; p = p->next) { struct commit_list *q; - unsigned flags = p->item->object.flags; + unsigned commit_flags = p->item->object.flags; if (0 <= weight(p)) continue; - for (q = p->item->parents; q; q = q->next) { + + for (q = p->item->parents; + q; + q = bisect_flags & FIND_BISECTION_FIRST_PARENT_ONLY ? NULL : q->next) { if (q->item->object.flags & UNINTERESTING) continue; if (0 <= weight(q)) @@ -346,7 +364,7 @@ * add one for p itself if p is to be counted, * otherwise inherit it from q directly. */ - if (!(flags & TREESAME)) { + if (!(commit_flags & TREESAME)) { weight_set(p, weight(q)+1); counted++; show_list("bisection 2 count one", @@ -355,22 +373,23 @@ else weight_set(p, weight(q)); - /* Does it happen to be at exactly half-way? */ - if (!find_all && halfway(p, nr)) + /* Does it happen to be at half-way? */ + if (!(bisect_flags & FIND_BISECTION_ALL) && + approx_halfway(p, nr)) return p; } } show_list("bisection 2 counted all", counted, nr, list); - if (!find_all) + if (!(bisect_flags & FIND_BISECTION_ALL)) return best_bisection(list, nr); else return best_bisection_sorted(list, nr); } void find_bisection(struct commit_list **commit_list, int *reaches, - int *all, int find_all) + int *all, unsigned bisect_flags) { int nr, on_list; struct commit_list *list, *p, *best, *next, *last; @@ -386,16 +405,16 @@ for (nr = on_list = 0, last = NULL, p = *commit_list; p; p = next) { - unsigned flags = p->item->object.flags; + unsigned commit_flags = p->item->object.flags; next = p->next; - if (flags & UNINTERESTING) { + if (commit_flags & UNINTERESTING) { free(p); continue; } p->next = last; last = p; - if (!(flags & TREESAME)) + if (!(commit_flags & TREESAME)) nr++; on_list++; } @@ -403,12 +422,12 @@ show_list("bisection 2 sorted", 0, nr, list); *all = nr; - weights = xcalloc(on_list, sizeof(*weights)); + CALLOC_ARRAY(weights, on_list); /* Do the real work of finding bisection commit. */ - best = do_find_bisection(list, nr, weights, find_all); + best = do_find_bisection(list, nr, weights, bisect_flags); if (best) { - if (!find_all) { + if (!(bisect_flags & FIND_BISECTION_ALL)) { list->item = best->item; free_commit_list(list->next); best = list; @@ -454,9 +473,10 @@ static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START") static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG") static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS") +static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT") static GIT_PATH_FUNC(git_path_head_name, "head-name") -static void read_bisect_paths(struct argv_array *array) +static void read_bisect_paths(struct strvec *array) { struct strbuf str = STRBUF_INIT; const char *filename = git_path_bisect_names(); @@ -464,7 +484,7 @@ while (strbuf_getline_lf(&str, fp) != EOF) { strbuf_trim(&str); - if (sq_dequote_to_argv_array(str.buf, array)) + if (sq_dequote_to_strvec(str.buf, array)) die(_("Badly quoted content in file '%s': %s"), filename, str.buf); } @@ -473,7 +493,7 @@ fclose(fp); } -static char *join_sha1_array_hex(struct oid_array *array, char delim) +static char *join_oid_array_hex(struct oid_array *array, char delim) { struct strbuf joined_hexs = STRBUF_INIT; int i; @@ -572,7 +592,7 @@ { float d, x = val; - if (val == 0) + if (!val) return 0; do { @@ -632,7 +652,7 @@ const char *bad_format, const char *good_format, int read_paths) { - struct argv_array rev_argv = ARGV_ARRAY_INIT; + struct strvec rev_argv = STRVEC_INIT; int i; repo_init_revisions(r, revs, prefix); @@ -640,16 +660,16 @@ revs->commit_format = CMIT_FMT_UNSPECIFIED; /* rev_argv.argv[0] will be ignored by setup_revisions */ - argv_array_push(&rev_argv, "bisect_rev_setup"); - argv_array_pushf(&rev_argv, bad_format, oid_to_hex(current_bad_oid)); + strvec_push(&rev_argv, "bisect_rev_setup"); + strvec_pushf(&rev_argv, bad_format, oid_to_hex(current_bad_oid)); for (i = 0; i < good_revs.nr; i++) - argv_array_pushf(&rev_argv, good_format, - oid_to_hex(good_revs.oid + i)); - argv_array_push(&rev_argv, "--"); + strvec_pushf(&rev_argv, good_format, + oid_to_hex(good_revs.oid + i)); + strvec_push(&rev_argv, "--"); if (read_paths) read_bisect_paths(&rev_argv); - setup_revisions(rev_argv.argc, rev_argv.argv, revs, NULL); + setup_revisions(rev_argv.nr, rev_argv.v, revs, NULL); /* XXX leak rev_argv, as "revs" may still be pointing to it */ } @@ -661,11 +681,11 @@ mark_edges_uninteresting(revs, NULL, 0); } -static void exit_if_skipped_commits(struct commit_list *tried, +static enum bisect_error error_if_skipped_commits(struct commit_list *tried, const struct object_id *bad) { if (!tried) - return; + return BISECT_OK; printf("There are only 'skip'ped commits left to test.\n" "The first %s commit could be any of:\n", term_bad); @@ -676,7 +696,8 @@ if (bad) printf("%s\n", oid_to_hex(bad)); printf(_("We cannot bisect more!\n")); - exit(2); + + return BISECT_ONLY_SKIPPED_LEFT; } static int is_expected_rev(const struct object_id *oid) @@ -703,11 +724,14 @@ return res; } -static int bisect_checkout(const struct object_id *bisect_rev, int no_checkout) +static enum bisect_error bisect_checkout(const struct object_id *bisect_rev, int no_checkout) { char bisect_rev_hex[GIT_MAX_HEXSZ + 1]; + struct commit *commit; + struct pretty_print_context pp = {0}; + struct strbuf commit_msg = STRBUF_INIT; - memcpy(bisect_rev_hex, oid_to_hex(bisect_rev), the_hash_algo->hexsz + 1); + oid_to_hex_r(bisect_rev_hex, bisect_rev); update_ref(NULL, "BISECT_EXPECTED_REV", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR); argv_checkout[2] = bisect_rev_hex; @@ -715,14 +739,21 @@ update_ref(NULL, "BISECT_HEAD", bisect_rev, NULL, 0, UPDATE_REFS_DIE_ON_ERR); } else { - int res; - res = run_command_v_opt(argv_checkout, RUN_GIT_CMD); - if (res) - exit(res); + if (run_command_v_opt(argv_checkout, RUN_GIT_CMD)) + /* + * Errors in `run_command()` itself, signaled by res < 0, + * and errors in the child process, signaled by res > 0 + * can both be treated as regular BISECT_FAILED (-1). + */ + return BISECT_FAILED; } - argv_show_branch[1] = bisect_rev_hex; - return run_command_v_opt(argv_show_branch, RUN_GIT_CMD); + commit = lookup_commit_reference(the_repository, bisect_rev); + format_commit_message(commit, "[%H] %s%n", &commit_msg, &pp); + fputs(commit_msg.buf, stdout); + strbuf_release(&commit_msg); + + return BISECT_OK; } static struct commit *get_commit_reference(struct repository *r, @@ -749,11 +780,11 @@ return rev; } -static void handle_bad_merge_base(void) +static enum bisect_error handle_bad_merge_base(void) { if (is_expected_rev(current_bad_oid)) { char *bad_hex = oid_to_hex(current_bad_oid); - char *good_hex = join_sha1_array_hex(&good_revs, ' '); + char *good_hex = join_oid_array_hex(&good_revs, ' '); if (!strcmp(term_bad, "bad") && !strcmp(term_good, "good")) { fprintf(stderr, _("The merge base %s is bad.\n" "This means the bug has been fixed " @@ -770,21 +801,21 @@ "between %s and [%s].\n"), bad_hex, term_bad, term_good, bad_hex, good_hex); } - exit(3); + return BISECT_MERGE_BASE_CHECK; } fprintf(stderr, _("Some %s revs are not ancestors of the %s rev.\n" "git bisect cannot work properly in this case.\n" "Maybe you mistook %s and %s revs?\n"), term_good, term_bad, term_good, term_bad); - exit(1); + return BISECT_FAILED; } static void handle_skipped_merge_base(const struct object_id *mb) { char *mb_hex = oid_to_hex(mb); char *bad_hex = oid_to_hex(current_bad_oid); - char *good_hex = join_sha1_array_hex(&good_revs, ' '); + char *good_hex = join_oid_array_hex(&good_revs, ' '); warning(_("the merge base between %s and [%s] " "must be skipped.\n" @@ -799,13 +830,18 @@ * "check_merge_bases" checks that merge bases are not "bad" (or "new"). * * - If one is "bad" (or "new"), it means the user assumed something wrong - * and we must exit with a non 0 error code. + * and we must return error with a non 0 error code. * - If one is "good" (or "old"), that's good, we have nothing to do. * - If one is "skipped", we can't know but we should warn. * - If we don't know, we should check it out and ask the user to test. + * - If a merge base must be tested, on success return + * BISECT_INTERNAL_SUCCESS_MERGE_BASE (-11) a special condition + * for early success, this will be converted back to 0 in + * check_good_are_ancestors_of_bad(). */ -static void check_merge_bases(int rev_nr, struct commit **rev, int no_checkout) +static enum bisect_error check_merge_bases(int rev_nr, struct commit **rev, int no_checkout) { + enum bisect_error res = BISECT_OK; struct commit_list *result; result = get_merge_bases_many(rev[0], rev_nr - 1, rev + 1); @@ -813,18 +849,24 @@ for (; result; result = result->next) { const struct object_id *mb = &result->item->object.oid; if (oideq(mb, current_bad_oid)) { - handle_bad_merge_base(); + res = handle_bad_merge_base(); + break; } else if (0 <= oid_array_lookup(&good_revs, mb)) { continue; } else if (0 <= oid_array_lookup(&skipped_revs, mb)) { handle_skipped_merge_base(mb); } else { printf(_("Bisecting: a merge base must be tested\n")); - exit(bisect_checkout(mb, no_checkout)); + res = bisect_checkout(mb, no_checkout); + if (!res) + /* indicate early success */ + res = BISECT_INTERNAL_SUCCESS_MERGE_BASE; + break; } } free_commit_list(result); + return res; } static int check_ancestors(struct repository *r, int rev_nr, @@ -850,43 +892,58 @@ * * If that's not the case, we need to check the merge bases. * If a merge base must be tested by the user, its source code will be - * checked out to be tested by the user and we will exit. + * checked out to be tested by the user and we will return. */ -static void check_good_are_ancestors_of_bad(struct repository *r, + +static enum bisect_error check_good_are_ancestors_of_bad(struct repository *r, const char *prefix, int no_checkout) { - char *filename = git_pathdup("BISECT_ANCESTORS_OK"); + char *filename; struct stat st; int fd, rev_nr; + enum bisect_error res = BISECT_OK; struct commit **rev; if (!current_bad_oid) - die(_("a %s revision is needed"), term_bad); + return error(_("a %s revision is needed"), term_bad); + + filename = git_pathdup("BISECT_ANCESTORS_OK"); /* Check if file BISECT_ANCESTORS_OK exists. */ if (!stat(filename, &st) && S_ISREG(st.st_mode)) goto done; /* Bisecting with no good rev is ok. */ - if (good_revs.nr == 0) + if (!good_revs.nr) goto done; /* Check if all good revs are ancestor of the bad rev. */ + rev = get_bad_and_good_commits(r, &rev_nr); if (check_ancestors(r, rev_nr, rev, prefix)) - check_merge_bases(rev_nr, rev, no_checkout); + res = check_merge_bases(rev_nr, rev, no_checkout); free(rev); - /* Create file BISECT_ANCESTORS_OK. */ - fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600); - if (fd < 0) - warning_errno(_("could not create file '%s'"), - filename); - else - close(fd); + if (!res) { + /* Create file BISECT_ANCESTORS_OK. */ + fd = open(filename, O_CREAT | O_TRUNC | O_WRONLY, 0600); + if (fd < 0) + /* + * BISECT_ANCESTORS_OK file is not absolutely necessary, + * the bisection process will continue at the next + * bisection step. + * So, just signal with a warning that something + * might be wrong. + */ + warning_errno(_("could not create file '%s'"), + filename); + else + close(fd); + } done: free(filename); + return res; } /* @@ -938,64 +995,96 @@ } /* - * We use the convention that exiting with an exit code 10 means that + * We use the convention that return BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND (-10) means * the bisection process finished successfully. - * In this case the calling shell script should exit 0. + * In this case the calling function or command should not turn a + * BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND return code into an error or a non zero exit code. * - * If no_checkout is non-zero, the bisection process does not - * checkout the trial commit but instead simply updates BISECT_HEAD. + * Checking BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND + * in bisect_helper::bisect_next() and only transforming it to 0 at + * the end of bisect_helper::cmd_bisect__helper() helps bypassing + * all the code related to finding a commit to test. */ -int bisect_next_all(struct repository *r, const char *prefix, int no_checkout) +enum bisect_error bisect_next_all(struct repository *r, const char *prefix) { struct rev_info revs; struct commit_list *tried; int reaches = 0, all = 0, nr, steps; + enum bisect_error res = BISECT_OK; struct object_id *bisect_rev; char *steps_msg; + /* + * If no_checkout is non-zero, the bisection process does not + * checkout the trial commit but instead simply updates BISECT_HEAD. + */ + int no_checkout = ref_exists("BISECT_HEAD"); + unsigned bisect_flags = 0; read_bisect_terms(&term_bad, &term_good); if (read_bisect_refs()) die(_("reading bisect refs failed")); - check_good_are_ancestors_of_bad(r, prefix, no_checkout); + if (file_exists(git_path_bisect_first_parent())) + bisect_flags |= FIND_BISECTION_FIRST_PARENT_ONLY; + + if (skipped_revs.nr) + bisect_flags |= FIND_BISECTION_ALL; + + res = check_good_are_ancestors_of_bad(r, prefix, no_checkout); + if (res) + return res; bisect_rev_setup(r, &revs, prefix, "%s", "^%s", 1); + + revs.first_parent_only = !!(bisect_flags & FIND_BISECTION_FIRST_PARENT_ONLY); revs.limited = 1; bisect_common(&revs); - find_bisection(&revs.commits, &reaches, &all, !!skipped_revs.nr); + find_bisection(&revs.commits, &reaches, &all, bisect_flags); revs.commits = managed_skipped(revs.commits, &tried); if (!revs.commits) { /* - * We should exit here only if the "bad" + * We should return error here only if the "bad" * commit is also a "skip" commit. */ - exit_if_skipped_commits(tried, NULL); - + res = error_if_skipped_commits(tried, NULL); + if (res < 0) + return res; printf(_("%s was both %s and %s\n"), oid_to_hex(current_bad_oid), term_good, term_bad); - exit(1); + + return BISECT_FAILED; } if (!all) { fprintf(stderr, _("No testable commit found.\n" - "Maybe you started with bad path parameters?\n")); - exit(4); + "Maybe you started with bad path arguments?\n")); + + return BISECT_NO_TESTABLE_COMMIT; } bisect_rev = &revs.commits->item->object.oid; if (oideq(bisect_rev, current_bad_oid)) { - exit_if_skipped_commits(tried, current_bad_oid); + res = error_if_skipped_commits(tried, current_bad_oid); + if (res) + return res; printf("%s is the first %s commit\n", oid_to_hex(bisect_rev), term_bad); + show_diff_tree(r, prefix, revs.commits->item); - /* This means the bisection process succeeded. */ - exit(10); + /* + * This means the bisection process succeeded. + * Using BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND (-10) + * so that the call chain can simply check + * for negative return values for early returns up + * until the cmd_bisect__helper() caller. + */ + return BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND; } nr = all - reaches - 1; @@ -1011,6 +1100,8 @@ "Bisecting: %d revisions left to test after this %s\n", nr), nr, steps_msg); free(steps_msg); + /* Clean up objects used, as they will be reused. */ + repo_clear_commit_marks(r, ALL_REV_FLAGS); return bisect_checkout(bisect_rev, no_checkout); } @@ -1080,6 +1171,7 @@ unlink_or_warn(git_path_bisect_names()); unlink_or_warn(git_path_bisect_run()); unlink_or_warn(git_path_bisect_terms()); + unlink_or_warn(git_path_bisect_first_parent()); /* Cleanup head-name if it got left by an old version of git-bisect */ unlink_or_warn(git_path_head_name()); /* diff -Nru git-2.25.1/bisect.h git-2.34.1/bisect.h --- git-2.25.1/bisect.h 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/bisect.h 2021-11-24 19:29:21.000000000 +0000 @@ -12,7 +12,7 @@ * best commit, as chosen by `find_all`. */ void find_bisection(struct commit_list **list, int *reaches, int *all, - int find_all); + unsigned bisect_flags); struct commit_list *filter_skipped(struct commit_list *list, struct commit_list **tried, @@ -23,6 +23,9 @@ #define BISECT_SHOW_ALL (1<<0) #define REV_LIST_QUIET (1<<1) +#define FIND_BISECTION_ALL (1u<<0) +#define FIND_BISECTION_FIRST_PARENT_ONLY (1u<<1) + struct rev_list_info { struct rev_info *revs; int flags; @@ -31,9 +34,34 @@ const char *header_prefix; }; -int bisect_next_all(struct repository *r, - const char *prefix, - int no_checkout); +/* + * enum bisect_error represents the following return codes: + * BISECT_OK: success code. Internally, it means that next + * commit has been found (and possibly checked out) and it + * should be tested. + * BISECT_FAILED error code: default error code. + * BISECT_ONLY_SKIPPED_LEFT error code: only skipped + * commits left to be tested. + * BISECT_MERGE_BASE_CHECK error code: merge base check failed. + * BISECT_NO_TESTABLE_COMMIT error code: no testable commit found. + * BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND early success code: + * first term_bad commit found. + * BISECT_INTERNAL_SUCCESS_MERGE_BASE early success + * code: found merge base that should be tested. + * Early success codes BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND and + * BISECT_INTERNAL_SUCCESS_MERGE_BASE should be only internal codes. + */ +enum bisect_error { + BISECT_OK = 0, + BISECT_FAILED = -1, + BISECT_ONLY_SKIPPED_LEFT = -2, + BISECT_MERGE_BASE_CHECK = -3, + BISECT_NO_TESTABLE_COMMIT = -4, + BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND = -10, + BISECT_INTERNAL_SUCCESS_MERGE_BASE = -11 +}; + +enum bisect_error bisect_next_all(struct repository *r, const char *prefix); int estimate_bisect_steps(int all); diff -Nru git-2.25.1/blame.c git-2.34.1/blame.c --- git-2.25.1/blame.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/blame.c 2021-11-24 19:29:21.000000000 +0000 @@ -9,6 +9,8 @@ #include "blame.h" #include "alloc.h" #include "commit-slab.h" +#include "bloom.h" +#include "commit-graph.h" define_commit_slab(blame_suspects, struct blame_origin *); static struct blame_suspects blame_suspects; @@ -240,7 +242,7 @@ switch (st.st_mode & S_IFMT) { case S_IFREG: if (opt->flags.allow_textconv && - textconv_object(r, read_from, mode, &null_oid, 0, &buf_ptr, &buf_len)) + textconv_object(r, read_from, mode, null_oid(), 0, &buf_ptr, &buf_len)) strbuf_attach(&buf, buf_ptr, buf_len, buf_len + 1); else if (strbuf_read_file(&buf, read_from, st.st_size) != st.st_size) die_errno("cannot open or read '%s'", read_from); @@ -433,7 +435,7 @@ static void free_fingerprint(struct fingerprint *f) { - hashmap_free(&f->map); + hashmap_clear(&f->map); free(f->entries); } @@ -949,13 +951,13 @@ max_search_distance_b = ((2 * max_search_distance_a + 1) * length_b - 1) / length_a; - result = xcalloc(sizeof(int), length_b); - second_best_result = xcalloc(sizeof(int), length_b); - certainties = xcalloc(sizeof(int), length_b); + CALLOC_ARRAY(result, length_b); + CALLOC_ARRAY(second_best_result, length_b); + CALLOC_ARRAY(certainties, length_b); /* See get_similarity() for details of similarities. */ similarity_count = length_b * (max_search_distance_a * 2 + 1); - similarities = xcalloc(sizeof(int), similarity_count); + CALLOC_ARRAY(similarities, similarity_count); for (i = 0; i < length_b; ++i) { result[i] = -1; @@ -993,7 +995,7 @@ return; o->num_lines = find_line_starts(&line_starts, o->file.ptr, o->file.size); - o->fingerprints = xcalloc(sizeof(struct fingerprint), o->num_lines); + CALLOC_ARRAY(o->fingerprints, o->num_lines); get_line_fingerprints(o->fingerprints, o->file.ptr, line_starts, 0, o->num_lines); free(line_starts); @@ -1182,6 +1184,7 @@ for (ent = sb->ent; ent && (next = ent->next); ent = next) { if (ent->suspect == next->suspect && ent->s_lno + ent->num_lines == next->s_lno && + ent->lno + ent->num_lines == next->lno && ent->ignored == next->ignored && ent->unblamable == next->unblamable) { ent->num_lines += next->num_lines; @@ -1246,13 +1249,74 @@ return -1; } +struct blame_bloom_data { + /* + * Changed-path Bloom filter keys. These can help prevent + * computing diffs against first parents, but we need to + * expand the list as code is moved or files are renamed. + */ + struct bloom_filter_settings *settings; + struct bloom_key **keys; + int nr; + int alloc; +}; + +static int bloom_count_queries = 0; +static int bloom_count_no = 0; +static int maybe_changed_path(struct repository *r, + struct blame_origin *origin, + struct blame_bloom_data *bd) +{ + int i; + struct bloom_filter *filter; + + if (!bd) + return 1; + + if (commit_graph_generation(origin->commit) == GENERATION_NUMBER_INFINITY) + return 1; + + filter = get_bloom_filter(r, origin->commit); + + if (!filter) + return 1; + + bloom_count_queries++; + for (i = 0; i < bd->nr; i++) { + if (bloom_filter_contains(filter, + bd->keys[i], + bd->settings)) + return 1; + } + + bloom_count_no++; + return 0; +} + +static void add_bloom_key(struct blame_bloom_data *bd, + const char *path) +{ + if (!bd) + return; + + if (bd->nr >= bd->alloc) { + bd->alloc *= 2; + REALLOC_ARRAY(bd->keys, bd->alloc); + } + + bd->keys[bd->nr] = xmalloc(sizeof(struct bloom_key)); + fill_bloom_key(path, strlen(path), bd->keys[bd->nr], bd->settings); + bd->nr++; +} + /* * We have an origin -- check if the same path exists in the * parent and return an origin structure to represent it. */ static struct blame_origin *find_origin(struct repository *r, struct commit *parent, - struct blame_origin *origin) + struct blame_origin *origin, + struct blame_bloom_data *bd) { struct blame_origin *porigin; struct diff_options diff_opts; @@ -1286,10 +1350,18 @@ if (is_null_oid(&origin->commit->object.oid)) do_diff_cache(get_commit_tree_oid(parent), &diff_opts); - else - diff_tree_oid(get_commit_tree_oid(parent), - get_commit_tree_oid(origin->commit), - "", &diff_opts); + else { + int compute_diff = 1; + if (origin->commit->parents && + oideq(&parent->object.oid, + &origin->commit->parents->item->object.oid)) + compute_diff = maybe_changed_path(r, origin, bd); + + if (compute_diff) + diff_tree_oid(get_commit_tree_oid(parent), + get_commit_tree_oid(origin->commit), + "", &diff_opts); + } diffcore_std(&diff_opts); if (!diff_queued_diff.nr) { @@ -1341,7 +1413,8 @@ */ static struct blame_origin *find_rename(struct repository *r, struct commit *parent, - struct blame_origin *origin) + struct blame_origin *origin, + struct blame_bloom_data *bd) { struct blame_origin *porigin = NULL; struct diff_options diff_opts; @@ -1366,6 +1439,7 @@ struct diff_filepair *p = diff_queued_diff.queue[i]; if ((p->status == 'R' || p->status == 'C') && !strcmp(p->two->path, origin->path)) { + add_bloom_key(bd, p->one->path); porigin = get_origin(parent, p->one->path); oidcpy(&porigin->blob_oid, &p->one->oid); porigin->mode = p->one->mode; @@ -1779,8 +1853,7 @@ diffp = NULL; if (ignore_diffs && same - tlno > 0) { - line_blames = xcalloc(sizeof(struct blame_line_tracker), - same - tlno); + CALLOC_ARRAY(line_blames, same - tlno); guess_line_blames(parent, target, tlno, offset, same, parent_len, line_blames); } @@ -2142,7 +2215,7 @@ for (e = unblamed, num_ents = 0; e; e = e->next) num_ents++; if (num_ents) { - blame_list = xcalloc(num_ents, sizeof(struct blame_list)); + CALLOC_ARRAY(blame_list, num_ents); for (e = unblamed, i = 0; e; e = e->next) blame_list[i++].ent = e; } @@ -2332,6 +2405,11 @@ #define MAXSG 16 +typedef struct blame_origin *(*blame_find_alg)(struct repository *, + struct commit *, + struct blame_origin *, + struct blame_bloom_data *); + static void pass_blame(struct blame_scoreboard *sb, struct blame_origin *origin, int opt) { struct rev_info *revs = sb->revs; @@ -2349,15 +2427,14 @@ else if (num_sg < ARRAY_SIZE(sg_buf)) memset(sg_buf, 0, sizeof(sg_buf)); else - sg_origin = xcalloc(num_sg, sizeof(*sg_origin)); + CALLOC_ARRAY(sg_origin, num_sg); /* * The first pass looks for unrenamed path to optimize for * common cases, then we look for renames in the second pass. */ for (pass = 0; pass < 2 - sb->no_whole_file_rename; pass++) { - struct blame_origin *(*find)(struct repository *, struct commit *, struct blame_origin *); - find = pass ? find_rename : find_origin; + blame_find_alg find = pass ? find_rename : find_origin; for (i = 0, sg = first_scapegoat(revs, commit, sb->reverse); i < num_sg && sg; @@ -2369,7 +2446,7 @@ continue; if (parse_commit(p)) continue; - porigin = find(sb->repo, p, origin); + porigin = find(sb->repo, p, origin, sb->bloom_data); if (!porigin) continue; if (oideq(&porigin->blob_oid, &origin->blob_oid)) { @@ -2592,7 +2669,7 @@ if (obj->flags & UNINTERESTING) continue; obj = deref_tag(revs->repo, obj, NULL, 0); - if (obj->type != OBJ_COMMIT) + if (!obj || obj->type != OBJ_COMMIT) die("Non commit %s?", revs->pending.objects[i].name); if (found) die("More than one commit to dig from %s and %s?", @@ -2623,7 +2700,7 @@ /* Is that sole rev a committish? */ obj = revs->pending.objects[0].item; obj = deref_tag(revs->repo, obj, NULL, 0); - if (obj->type != OBJ_COMMIT) + if (!obj || obj->type != OBJ_COMMIT) return NULL; /* Do we have HEAD? */ @@ -2659,7 +2736,7 @@ if (!(obj->flags & UNINTERESTING)) continue; obj = deref_tag(revs->repo, obj, NULL, 0); - if (obj->type != OBJ_COMMIT) + if (!obj || obj->type != OBJ_COMMIT) die("Non commit %s?", revs->pending.objects[i].name); if (found) die("More than one commit to dig up from, %s and %s?", @@ -2686,7 +2763,6 @@ } void setup_scoreboard(struct blame_scoreboard *sb, - const char *path, struct blame_origin **orig) { const char *final_commit_name = NULL; @@ -2725,7 +2801,7 @@ setup_work_tree(); sb->final = fake_working_tree_commit(sb->repo, &sb->revs->diffopt, - path, sb->contents_from); + sb->path, sb->contents_from); add_pending_object(sb->revs, &(sb->final->object), ":"); } @@ -2768,12 +2844,12 @@ sb->final_buf_size = o->file.size; } else { - o = get_origin(sb->final, path); + o = get_origin(sb->final, sb->path); if (fill_blob_sha1_and_mode(sb->repo, o)) - die(_("no such path %s in %s"), path, final_commit_name); + die(_("no such path %s in %s"), sb->path, final_commit_name); if (sb->revs->diffopt.flags.allow_textconv && - textconv_object(sb->repo, path, o->mode, &o->blob_oid, 1, (char **) &sb->final_buf, + textconv_object(sb->repo, sb->path, o->mode, &o->blob_oid, 1, (char **) &sb->final_buf, &sb->final_buf_size)) ; else @@ -2783,7 +2859,7 @@ if (!sb->final_buf) die(_("cannot read blob %s for path %s"), oid_to_hex(&o->blob_oid), - path); + sb->path); } sb->num_read_blob++; prepare_lines(sb); @@ -2809,3 +2885,46 @@ blame_origin_incref(o); return new_head; } + +void setup_blame_bloom_data(struct blame_scoreboard *sb) +{ + struct blame_bloom_data *bd; + struct bloom_filter_settings *bs; + + if (!sb->repo->objects->commit_graph) + return; + + bs = get_bloom_filter_settings(sb->repo); + if (!bs) + return; + + bd = xmalloc(sizeof(struct blame_bloom_data)); + + bd->settings = bs; + + bd->alloc = 4; + bd->nr = 0; + ALLOC_ARRAY(bd->keys, bd->alloc); + + add_bloom_key(bd, sb->path); + + sb->bloom_data = bd; +} + +void cleanup_scoreboard(struct blame_scoreboard *sb) +{ + if (sb->bloom_data) { + int i; + for (i = 0; i < sb->bloom_data->nr; i++) { + free(sb->bloom_data->keys[i]->hashes); + free(sb->bloom_data->keys[i]); + } + free(sb->bloom_data->keys); + FREE_AND_NULL(sb->bloom_data); + + trace2_data_intmax("blame", sb->repo, + "bloom/queries", bloom_count_queries); + trace2_data_intmax("blame", sb->repo, + "bloom/response-no", bloom_count_no); + } +} diff -Nru git-2.25.1/blame.h git-2.34.1/blame.h --- git-2.25.1/blame.h 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/blame.h 2021-11-24 19:29:21.000000000 +0000 @@ -16,6 +16,8 @@ #define BLAME_DEFAULT_MOVE_SCORE 20 #define BLAME_DEFAULT_COPY_SCORE 40 +struct fingerprint; + /* * One blob in a commit that is being suspected */ @@ -52,7 +54,7 @@ struct blame_entry *suspects; mmfile_t file; int num_lines; - void *fingerprints; + struct fingerprint *fingerprints; struct object_id blob_oid; unsigned short mode; /* guilty gets set when shipping any suspects to the final @@ -98,6 +100,8 @@ int unblamable; }; +struct blame_bloom_data; + /* * The current state of the blame assignment. */ @@ -154,6 +158,7 @@ void(*found_guilty_entry)(struct blame_entry *, void *); void *found_guilty_entry_data; + struct blame_bloom_data *bloom_data; }; /* @@ -176,8 +181,9 @@ void init_scoreboard(struct blame_scoreboard *sb); void setup_scoreboard(struct blame_scoreboard *sb, - const char *path, struct blame_origin **orig); +void setup_blame_bloom_data(struct blame_scoreboard *sb); +void cleanup_scoreboard(struct blame_scoreboard *sb); struct blame_entry *blame_entry_prepend(struct blame_entry *head, long start, long end, diff -Nru git-2.25.1/blob.c git-2.34.1/blob.c --- git-2.25.1/blob.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/blob.c 2021-11-24 19:29:21.000000000 +0000 @@ -10,7 +10,7 @@ struct object *obj = lookup_object(r, oid); if (!obj) return create_object(r, oid, alloc_blob_node(r)); - return object_as_type(r, obj, OBJ_BLOB, 0); + return object_as_type(obj, OBJ_BLOB, 0); } int parse_blob_buffer(struct blob *item, void *buffer, unsigned long size) diff -Nru git-2.25.1/block-sha1/sha1.c git-2.34.1/block-sha1/sha1.c --- git-2.25.1/block-sha1/sha1.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/block-sha1/sha1.c 2021-11-24 19:29:21.000000000 +0000 @@ -70,7 +70,7 @@ * the input data, the next mix it from the 512-bit array. */ #define SHA_SRC(t) get_be32((unsigned char *) block + (t)*4) -#define SHA_MIX(t) SHA_ROL(W((t)+13) ^ W((t)+8) ^ W((t)+2) ^ W(t), 1); +#define SHA_MIX(t) SHA_ROL(W((t)+13) ^ W((t)+8) ^ W((t)+2) ^ W(t), 1) #define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do { \ unsigned int TEMP = input(t); setW(t, TEMP); \ @@ -203,7 +203,7 @@ ctx->H[4] = 0xc3d2e1f0; } -void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, unsigned long len) +void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *data, size_t len) { unsigned int lenW = ctx->size & 63; diff -Nru git-2.25.1/block-sha1/sha1.h git-2.34.1/block-sha1/sha1.h --- git-2.25.1/block-sha1/sha1.h 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/block-sha1/sha1.h 2021-11-24 19:29:21.000000000 +0000 @@ -13,7 +13,7 @@ } blk_SHA_CTX; void blk_SHA1_Init(blk_SHA_CTX *ctx); -void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, unsigned long len); +void blk_SHA1_Update(blk_SHA_CTX *ctx, const void *dataIn, size_t len); void blk_SHA1_Final(unsigned char hashout[20], blk_SHA_CTX *ctx); #define platform_SHA_CTX blk_SHA_CTX diff -Nru git-2.25.1/bloom.c git-2.34.1/bloom.c --- git-2.25.1/bloom.c 1970-01-01 00:00:00.000000000 +0000 +++ git-2.34.1/bloom.c 2021-11-24 19:29:21.000000000 +0000 @@ -0,0 +1,327 @@ +#include "git-compat-util.h" +#include "bloom.h" +#include "diff.h" +#include "diffcore.h" +#include "revision.h" +#include "hashmap.h" +#include "commit-graph.h" +#include "commit.h" + +define_commit_slab(bloom_filter_slab, struct bloom_filter); + +static struct bloom_filter_slab bloom_filters; + +struct pathmap_hash_entry { + struct hashmap_entry entry; + const char path[FLEX_ARRAY]; +}; + +static uint32_t rotate_left(uint32_t value, int32_t count) +{ + uint32_t mask = 8 * sizeof(uint32_t) - 1; + count &= mask; + return ((value << count) | (value >> ((-count) & mask))); +} + +static inline unsigned char get_bitmask(uint32_t pos) +{ + return ((unsigned char)1) << (pos & (BITS_PER_WORD - 1)); +} + +static int load_bloom_filter_from_graph(struct commit_graph *g, + struct bloom_filter *filter, + struct commit *c) +{ + 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; + + /* The commit graph commit 'c' lives in doesn't carry Bloom filters. */ + if (!g->chunk_bloom_indexes) + return 0; + + lex_pos = graph_pos - g->num_commits_in_base; + + end_index = get_be32(g->chunk_bloom_indexes + 4 * lex_pos); + + if (lex_pos > 0) + start_index = get_be32(g->chunk_bloom_indexes + 4 * (lex_pos - 1)); + else + start_index = 0; + + filter->len = end_index - start_index; + filter->data = (unsigned char *)(g->chunk_bloom_data + + sizeof(unsigned char) * start_index + + BLOOMDATA_CHUNK_HEADER_SIZE); + + return 1; +} + +/* + * Calculate the murmur3 32-bit hash value for the given data + * using the given seed. + * Produces a uniformly distributed hash value. + * Not considered to be cryptographically secure. + * Implemented as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm + */ +uint32_t murmur3_seeded(uint32_t seed, const char *data, size_t len) +{ + const uint32_t c1 = 0xcc9e2d51; + const uint32_t c2 = 0x1b873593; + const uint32_t r1 = 15; + const uint32_t r2 = 13; + const uint32_t m = 5; + const uint32_t n = 0xe6546b64; + int i; + uint32_t k1 = 0; + const char *tail; + + int len4 = len / sizeof(uint32_t); + + uint32_t k; + for (i = 0; i < len4; i++) { + uint32_t byte1 = (uint32_t)data[4*i]; + uint32_t byte2 = ((uint32_t)data[4*i + 1]) << 8; + uint32_t byte3 = ((uint32_t)data[4*i + 2]) << 16; + uint32_t byte4 = ((uint32_t)data[4*i + 3]) << 24; + k = byte1 | byte2 | byte3 | byte4; + k *= c1; + k = rotate_left(k, r1); + k *= c2; + + seed ^= k; + seed = rotate_left(seed, r2) * m + n; + } + + tail = (data + len4 * sizeof(uint32_t)); + + switch (len & (sizeof(uint32_t) - 1)) { + case 3: + k1 ^= ((uint32_t)tail[2]) << 16; + /*-fallthrough*/ + case 2: + k1 ^= ((uint32_t)tail[1]) << 8; + /*-fallthrough*/ + case 1: + k1 ^= ((uint32_t)tail[0]) << 0; + k1 *= c1; + k1 = rotate_left(k1, r1); + k1 *= c2; + seed ^= k1; + break; + } + + seed ^= (uint32_t)len; + seed ^= (seed >> 16); + seed *= 0x85ebca6b; + seed ^= (seed >> 13); + seed *= 0xc2b2ae35; + seed ^= (seed >> 16); + + return seed; +} + +void fill_bloom_key(const char *data, + size_t len, + struct bloom_key *key, + const struct bloom_filter_settings *settings) +{ + int i; + const uint32_t seed0 = 0x293ae76f; + const uint32_t seed1 = 0x7e646e2c; + const uint32_t hash0 = murmur3_seeded(seed0, data, len); + const uint32_t hash1 = murmur3_seeded(seed1, data, len); + + key->hashes = (uint32_t *)xcalloc(settings->num_hashes, sizeof(uint32_t)); + for (i = 0; i < settings->num_hashes; i++) + key->hashes[i] = hash0 + i * hash1; +} + +void clear_bloom_key(struct bloom_key *key) +{ + FREE_AND_NULL(key->hashes); +} + +void add_key_to_filter(const struct bloom_key *key, + struct bloom_filter *filter, + const struct bloom_filter_settings *settings) +{ + int i; + uint64_t mod = filter->len * BITS_PER_WORD; + + for (i = 0; i < settings->num_hashes; i++) { + uint64_t hash_mod = key->hashes[i] % mod; + uint64_t block_pos = hash_mod / BITS_PER_WORD; + + filter->data[block_pos] |= get_bitmask(hash_mod); + } +} + +void init_bloom_filters(void) +{ + init_bloom_filter_slab(&bloom_filters); +} + +static int pathmap_cmp(const void *hashmap_cmp_fn_data, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, + const void *keydata) +{ + const struct pathmap_hash_entry *e1, *e2; + + e1 = container_of(eptr, const struct pathmap_hash_entry, entry); + e2 = container_of(entry_or_key, const struct pathmap_hash_entry, entry); + + return strcmp(e1->path, e2->path); +} + +static void init_truncated_large_filter(struct bloom_filter *filter) +{ + filter->data = xmalloc(1); + filter->data[0] = 0xFF; + filter->len = 1; +} + +struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, + struct commit *c, + int compute_if_not_present, + const struct bloom_filter_settings *settings, + enum bloom_filter_computed *computed) +{ + struct bloom_filter *filter; + int i; + struct diff_options diffopt; + + if (computed) + *computed = BLOOM_NOT_COMPUTED; + + if (!bloom_filters.slab_size) + return NULL; + + 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); + } + + if (filter->data && filter->len) + return filter; + if (!compute_if_not_present) + return NULL; + + repo_diff_setup(r, &diffopt); + diffopt.flags.recursive = 1; + diffopt.detect_rename = 0; + diffopt.max_changes = settings->max_changed_paths; + diff_setup_done(&diffopt); + + /* ensure commit is parsed so we have parent information */ + repo_parse_commit(r, c); + + if (c->parents) + diff_tree_oid(&c->parents->item->object.oid, &c->object.oid, "", &diffopt); + else + diff_tree_oid(NULL, &c->object.oid, "", &diffopt); + diffcore_std(&diffopt); + + if (diff_queued_diff.nr <= settings->max_changed_paths) { + struct hashmap pathmap = HASHMAP_INIT(pathmap_cmp, NULL); + struct pathmap_hash_entry *e; + struct hashmap_iter iter; + + for (i = 0; i < diff_queued_diff.nr; i++) { + const char *path = diff_queued_diff.queue[i]->two->path; + + /* + * Add each leading directory of the changed file, i.e. for + * 'dir/subdir/file' add 'dir' and 'dir/subdir' as well, so + * the Bloom filter could be used to speed up commands like + * 'git log dir/subdir', too. + * + * Note that directories are added without the trailing '/'. + */ + do { + char *last_slash = strrchr(path, '/'); + + FLEX_ALLOC_STR(e, path, path); + hashmap_entry_init(&e->entry, strhash(path)); + + if (!hashmap_get(&pathmap, &e->entry, NULL)) + hashmap_add(&pathmap, &e->entry); + else + free(e); + + if (!last_slash) + last_slash = (char*)path; + *last_slash = '\0'; + + } while (*path); + + diff_free_filepair(diff_queued_diff.queue[i]); + } + + if (hashmap_get_size(&pathmap) > settings->max_changed_paths) { + init_truncated_large_filter(filter); + if (computed) + *computed |= BLOOM_TRUNC_LARGE; + goto cleanup; + } + + filter->len = (hashmap_get_size(&pathmap) * settings->bits_per_entry + BITS_PER_WORD - 1) / BITS_PER_WORD; + if (!filter->len) { + if (computed) + *computed |= BLOOM_TRUNC_EMPTY; + filter->len = 1; + } + CALLOC_ARRAY(filter->data, filter->len); + + hashmap_for_each_entry(&pathmap, &iter, e, entry) { + struct bloom_key key; + fill_bloom_key(e->path, strlen(e->path), &key, settings); + add_key_to_filter(&key, filter, settings); + clear_bloom_key(&key); + } + + cleanup: + hashmap_clear_and_free(&pathmap, struct pathmap_hash_entry, entry); + } else { + for (i = 0; i < diff_queued_diff.nr; i++) + diff_free_filepair(diff_queued_diff.queue[i]); + init_truncated_large_filter(filter); + + if (computed) + *computed |= BLOOM_TRUNC_LARGE; + } + + if (computed) + *computed |= BLOOM_COMPUTED; + + free(diff_queued_diff.queue); + DIFF_QUEUE_CLEAR(&diff_queued_diff); + + return filter; +} + +int bloom_filter_contains(const struct bloom_filter *filter, + const struct bloom_key *key, + const struct bloom_filter_settings *settings) +{ + int i; + uint64_t mod = filter->len * BITS_PER_WORD; + + if (!mod) + return -1; + + for (i = 0; i < settings->num_hashes; i++) { + uint64_t hash_mod = key->hashes[i] % mod; + uint64_t block_pos = hash_mod / BITS_PER_WORD; + if (!(filter->data[block_pos] & get_bitmask(hash_mod))) + return 0; + } + + return 1; +} diff -Nru git-2.25.1/bloom.h git-2.34.1/bloom.h --- git-2.25.1/bloom.h 1970-01-01 00:00:00.000000000 +0000 +++ git-2.34.1/bloom.h 2021-11-24 19:29:21.000000000 +0000 @@ -0,0 +1,112 @@ +#ifndef BLOOM_H +#define BLOOM_H + +struct commit; +struct repository; + +struct bloom_filter_settings { + /* + * The version of the hashing technique being used. + * We currently only support version = 1 which is + * the seeded murmur3 hashing technique implemented + * in bloom.c. + */ + uint32_t hash_version; + + /* + * The number of times a path is hashed, i.e. the + * number of bit positions tht cumulatively + * determine whether a path is present in the + * Bloom filter. + */ + uint32_t num_hashes; + + /* + * The minimum number of bits per entry in the Bloom + * filter. If the filter contains 'n' entries, then + * filter size is the minimum number of 8-bit words + * that contain n*b bits. + */ + uint32_t bits_per_entry; + + /* + * The maximum number of changed paths per commit + * before declaring a Bloom filter to be too-large. + * + * Not written to the commit-graph file. + */ + uint32_t max_changed_paths; +}; + +#define DEFAULT_BLOOM_MAX_CHANGES 512 +#define DEFAULT_BLOOM_FILTER_SETTINGS { 1, 7, 10, DEFAULT_BLOOM_MAX_CHANGES } +#define BITS_PER_WORD 8 +#define BLOOMDATA_CHUNK_HEADER_SIZE 3 * sizeof(uint32_t) + +/* + * A bloom_filter struct represents a data segment to + * use when testing hash values. The 'len' member + * dictates how many entries are stored in + * 'data'. + */ +struct bloom_filter { + unsigned char *data; + size_t len; +}; + +/* + * A bloom_key represents the k hash values for a + * given string. These can be precomputed and + * stored in a bloom_key for re-use when testing + * against a bloom_filter. The number of hashes is + * given by the Bloom filter settings and is the same + * for all Bloom filters and keys interacting with + * the loaded version of the commit graph file and + * the Bloom data chunks. + */ +struct bloom_key { + uint32_t *hashes; +}; + +/* + * Calculate the murmur3 32-bit hash value for the given data + * using the given seed. + * Produces a uniformly distributed hash value. + * Not considered to be cryptographically secure. + * Implemented as described in https://en.wikipedia.org/wiki/MurmurHash#Algorithm + */ +uint32_t murmur3_seeded(uint32_t seed, const char *data, size_t len); + +void fill_bloom_key(const char *data, + size_t len, + struct bloom_key *key, + const struct bloom_filter_settings *settings); +void clear_bloom_key(struct bloom_key *key); + +void add_key_to_filter(const struct bloom_key *key, + struct bloom_filter *filter, + const struct bloom_filter_settings *settings); + +void init_bloom_filters(void); + +enum bloom_filter_computed { + BLOOM_NOT_COMPUTED = (1 << 0), + BLOOM_COMPUTED = (1 << 1), + BLOOM_TRUNC_LARGE = (1 << 2), + BLOOM_TRUNC_EMPTY = (1 << 3), +}; + +struct bloom_filter *get_or_compute_bloom_filter(struct repository *r, + struct commit *c, + int compute_if_not_present, + const struct bloom_filter_settings *settings, + enum bloom_filter_computed *computed); + +#define get_bloom_filter(r, c) get_or_compute_bloom_filter( \ + (r), (c), 0, NULL, NULL) + +int bloom_filter_contains(const struct bloom_filter *filter, + const struct bloom_key *key, + const struct bloom_filter_settings *settings); + +#endif diff -Nru git-2.25.1/branch.c git-2.34.1/branch.c --- git-2.25.1/branch.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/branch.c 2021-11-24 19:29:21.000000000 +0000 @@ -271,7 +271,7 @@ real_ref = NULL; if (get_oid_mb(start_name, &oid)) { if (explicit_tracking) { - if (advice_set_upstream_failure) { + if (advice_enabled(ADVICE_SET_UPSTREAM_FAILURE)) { error(_(upstream_missing), start_name); advise(_(upstream_advice)); exit(1); @@ -281,7 +281,7 @@ die(_("Not a valid object name: '%s'."), start_name); } - switch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref)) { + switch (dwim_ref(start_name, strlen(start_name), &oid, &real_ref, 0)) { case 0: /* Not branching from any existing branch */ if (explicit_tracking) @@ -294,7 +294,7 @@ if (explicit_tracking) die(_(upstream_not_branch), start_name); else - real_ref = NULL; + FREE_AND_NULL(real_ref); } break; default: @@ -322,7 +322,7 @@ transaction = ref_transaction_begin(&err); if (!transaction || ref_transaction_update(transaction, ref.buf, - &oid, forcing ? NULL : &null_oid, + &oid, forcing ? NULL : null_oid(), 0, msg, &err) || ref_transaction_commit(transaction, &err)) die("%s", err.buf); @@ -344,6 +344,8 @@ unlink(git_path_merge_rr(r)); unlink(git_path_merge_msg(r)); unlink(git_path_merge_mode(r)); + unlink(git_path_auto_merge(r)); + save_autostash(git_path_merge_autostash(r)); } void remove_branch_state(struct repository *r, int verbose) @@ -369,7 +371,7 @@ const char *logmsg) { int ret = 0; - struct worktree **worktrees = get_worktrees(0); + struct worktree **worktrees = get_worktrees(); int i; for (i = 0; worktrees[i]; i++) { diff -Nru git-2.25.1/builtin/add.c git-2.34.1/builtin/add.c --- git-2.25.1/builtin/add.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/add.c 2021-11-24 19:29:21.000000000 +0000 @@ -18,7 +18,7 @@ #include "diffcore.h" #include "revision.h" #include "bulk-checkin.h" -#include "argv-array.h" +#include "strvec.h" #include "submodule.h" #include "add-interactive.h" @@ -30,26 +30,41 @@ static int take_worktree_changes; static int add_renormalize; static int pathspec_file_nul; +static int include_sparse; static const char *pathspec_from_file; +static int legacy_stash_p; /* support for the scripted `git stash` */ struct update_callback_data { int flags; int add_errors; }; -static void chmod_pathspec(struct pathspec *pathspec, char flip) +static int chmod_pathspec(struct pathspec *pathspec, char flip, int show_only) { - int i; + int i, ret = 0; for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; + int err; + + if (!include_sparse && + (ce_skip_worktree(ce) || + !path_in_sparse_checkout(ce->name, &the_index))) + continue; if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL)) continue; - if (chmod_cache_entry(ce, flip) < 0) - fprintf(stderr, "cannot chmod %cx '%s'\n", flip, ce->name); + if (!show_only) + err = chmod_cache_entry(ce, flip); + else + err = S_ISREG(ce->ce_mode) ? 0 : -1; + + if (err < 0) + ret = error(_("cannot chmod %cx '%s'"), flip, ce->name); } + + return ret; } static int fix_unmerged_status(struct diff_filepair *p, @@ -82,6 +97,10 @@ for (i = 0; i < q->nr; i++) { struct diff_filepair *p = q->queue[i]; const char *path = p->one->path; + + if (!include_sparse && !path_in_sparse_checkout(path, &the_index)) + continue; + switch (fix_unmerged_status(p, data)) { default: die(_("unexpected diff status %c"), p->status); @@ -135,6 +154,10 @@ for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; + if (!include_sparse && + (ce_skip_worktree(ce) || + !path_in_sparse_checkout(ce->name, &the_index))) + continue; if (ce_stage(ce)) continue; /* do not touch unmerged paths */ if (!S_ISREG(ce->ce_mode) && !S_ISLNK(ce->ce_mode)) @@ -163,63 +186,105 @@ *dst++ = entry; } dir->nr = dst - dir->entries; - add_pathspec_matches_against_index(pathspec, &the_index, seen); + add_pathspec_matches_against_index(pathspec, &the_index, seen, + PS_IGNORE_SKIP_WORKTREE); return seen; } -static void refresh(int verbose, const struct pathspec *pathspec) +static int refresh(int verbose, const struct pathspec *pathspec) { char *seen; - int i; + int i, ret = 0; + char *skip_worktree_seen = NULL; + struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP; + int flags = REFRESH_IGNORE_SKIP_WORKTREE | + (verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET); seen = xcalloc(pathspec->nr, 1); - refresh_index(&the_index, verbose ? REFRESH_IN_PORCELAIN : REFRESH_QUIET, - pathspec, seen, _("Unstaged changes after refreshing the index:")); + refresh_index(&the_index, flags, pathspec, seen, + _("Unstaged changes after refreshing the index:")); for (i = 0; i < pathspec->nr; i++) { - if (!seen[i]) - die(_("pathspec '%s' did not match any files"), - pathspec->items[i].match); + if (!seen[i]) { + const char *path = pathspec->items[i].original; + + if (matches_skip_worktree(pathspec, i, &skip_worktree_seen) || + !path_in_sparse_checkout(path, &the_index)) { + string_list_append(&only_match_skip_worktree, + pathspec->items[i].original); + } else { + die(_("pathspec '%s' did not match any files"), + pathspec->items[i].original); + } + } + } + + if (only_match_skip_worktree.nr) { + advise_on_updating_sparse_paths(&only_match_skip_worktree); + ret = 1; } + free(seen); + free(skip_worktree_seen); + string_list_clear(&only_match_skip_worktree, 0); + return ret; } int run_add_interactive(const char *revision, const char *patch_mode, const struct pathspec *pathspec) { int status, i; - struct argv_array argv = ARGV_ARRAY_INIT; + struct strvec argv = STRVEC_INIT; int use_builtin_add_i = git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1); - if (use_builtin_add_i < 0) - git_config_get_bool("add.interactive.usebuiltin", - &use_builtin_add_i); + 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 == 1) { + enum add_p_mode mode; + if (!patch_mode) return !!run_add_i(the_repository, pathspec); - if (strcmp(patch_mode, "--patch")) - die("'%s' not yet supported in the built-in add -p", - patch_mode); - return !!run_add_p(the_repository, pathspec); + + if (!strcmp(patch_mode, "--patch")) + mode = ADD_P_ADD; + else if (!strcmp(patch_mode, "--patch=stash")) + mode = ADD_P_STASH; + else if (!strcmp(patch_mode, "--patch=reset")) + mode = ADD_P_RESET; + else if (!strcmp(patch_mode, "--patch=checkout")) + mode = ADD_P_CHECKOUT; + else if (!strcmp(patch_mode, "--patch=worktree")) + mode = ADD_P_WORKTREE; + else + die("'%s' not supported", patch_mode); + + return !!run_add_p(the_repository, mode, revision, pathspec); } - argv_array_push(&argv, "add--interactive"); + strvec_push(&argv, "add--interactive"); if (patch_mode) - argv_array_push(&argv, patch_mode); + strvec_push(&argv, patch_mode); if (revision) - argv_array_push(&argv, revision); - argv_array_push(&argv, "--"); + strvec_push(&argv, revision); + strvec_push(&argv, "--"); for (i = 0; i < pathspec->nr; i++) /* pass original pathspec, to be re-parsed */ - argv_array_push(&argv, pathspec->items[i].original); + strvec_push(&argv, pathspec->items[i].original); - status = run_command_v_opt(argv.argv, RUN_GIT_CMD); - argv_array_clear(&argv); + status = run_command_v_opt(argv.v, RUN_GIT_CMD); + strvec_clear(&argv); return status; } -int interactive_add(int argc, const char **argv, const char *prefix, int patch) +int interactive_add(const char **argv, const char *prefix, int patch) { struct pathspec pathspec; @@ -258,9 +323,7 @@ rev.diffopt.output_format = DIFF_FORMAT_PATCH; rev.diffopt.use_color = 0; rev.diffopt.flags.ignore_dirty_submodules = 1; - out = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0666); - if (out < 0) - die(_("Could not open '%s' for writing."), file); + out = xopen(file, O_CREAT | O_WRONLY | O_TRUNC, 0666); rev.diffopt.file = xfdopen(out, "w"); rev.diffopt.close_file = 1; if (run_diff_files(&rev, 0)) @@ -316,17 +379,20 @@ OPT_BOOL(0, "renormalize", &add_renormalize, N_("renormalize EOL of tracked files (implies -u)")), OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that the path will be added later")), OPT_BOOL('A', "all", &addremove_explicit, N_("add changes from all tracked and untracked files")), - { OPTION_CALLBACK, 0, "ignore-removal", &addremove_explicit, + OPT_CALLBACK_F(0, "ignore-removal", &addremove_explicit, NULL /* takes no arguments */, N_("ignore paths removed in the working tree (same as --no-all)"), - PARSE_OPT_NOARG, ignore_removal_cb }, + PARSE_OPT_NOARG, ignore_removal_cb), OPT_BOOL( 0 , "refresh", &refresh_only, N_("don't add, only refresh the index")), OPT_BOOL( 0 , "ignore-errors", &ignore_add_errors, N_("just skip files which cannot be added because of errors")), OPT_BOOL( 0 , "ignore-missing", &ignore_missing, N_("check if - even missing - files are ignored in dry run")), + OPT_BOOL(0, "sparse", &include_sparse, N_("allow updating entries outside of the sparse-checkout cone")), OPT_STRING(0, "chmod", &chmod_arg, "(+|-)x", N_("override the executable bit of the listed files")), OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo, N_("warn when adding an embedded repository")), + OPT_HIDDEN_BOOL(0, "legacy-stash-p", &legacy_stash_p, + N_("backend for `git stash -p`")), OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END(), @@ -362,6 +428,7 @@ static void check_embedded_repo(const char *path) { struct strbuf name = STRBUF_INIT; + static int adviced_on_embedded_repo = 0; if (!warn_on_embedded_repo) return; @@ -373,10 +440,10 @@ strbuf_strip_suffix(&name, "/"); warning(_("adding embedded git repository: %s"), name.buf); - if (advice_add_embedded_repo) { + if (!adviced_on_embedded_repo && + advice_enabled(ADVICE_ADD_EMBEDDED_REPO)) { advise(embedded_advice, name.buf, name.buf); - /* there may be multiple entries; advise only once */ - advice_add_embedded_repo = 0; + adviced_on_embedded_repo = 1; } strbuf_release(&name); @@ -385,16 +452,26 @@ static int add_files(struct dir_struct *dir, int flags) { int i, exit_status = 0; + struct string_list matched_sparse_paths = STRING_LIST_INIT_NODUP; if (dir->ignored_nr) { fprintf(stderr, _(ignore_error)); for (i = 0; i < dir->ignored_nr; i++) fprintf(stderr, "%s\n", dir->ignored[i]->name); - fprintf(stderr, _("Use -f if you really want to add them.\n")); + if (advice_enabled(ADVICE_ADD_IGNORED_FILE)) + advise(_("Use -f if you really want to add them.\n" + "Turn this message off by running\n" + "\"git config advice.addIgnoredFile false\"")); exit_status = 1; } for (i = 0; i < dir->nr; i++) { + if (!include_sparse && + !path_in_sparse_checkout(dir->entries[i]->name, &the_index)) { + string_list_append(&matched_sparse_paths, + dir->entries[i]->name); + continue; + } if (add_file_to_index(&the_index, dir->entries[i]->name, flags)) { if (!ignore_add_errors) die(_("adding files failed")); @@ -403,6 +480,14 @@ check_embedded_repo(dir->entries[i]->name); } } + + if (matched_sparse_paths.nr) { + advise_on_updating_sparse_paths(&matched_sparse_paths); + exit_status = 1; + } + + string_list_clear(&matched_sparse_paths, 0); + return exit_status; } @@ -410,7 +495,7 @@ { int exit_status = 0; struct pathspec pathspec; - struct dir_struct dir; + struct dir_struct dir = DIR_INIT; int flags; int add_new_files; int require_pathspec; @@ -424,9 +509,22 @@ if (patch_interactive) add_interactive = 1; if (add_interactive) { + if (show_only) + die(_("--dry-run is incompatible with --interactive/--patch")); if (pathspec_from_file) die(_("--pathspec-from-file is incompatible with --interactive/--patch")); - exit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive)); + exit(interactive_add(argv + 1, prefix, patch_interactive)); + } + if (legacy_stash_p) { + struct pathspec pathspec; + + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_FULL | + PATHSPEC_SYMLINK_LEADING_PATH | + PATHSPEC_PREFIX_ORIGIN, + prefix, argv); + + return run_add_interactive(NULL, "--patch=stash", &pathspec); } if (edit_interactive) { @@ -455,6 +553,9 @@ add_new_files = !take_worktree_changes && !refresh_only && !add_renormalize; require_pathspec = !(take_worktree_changes || (0 < addremove_explicit)); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); /* @@ -480,7 +581,10 @@ if (require_pathspec && pathspec.nr == 0) { fprintf(stderr, _("Nothing specified, nothing added.\n")); - fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n")); + if (advice_enabled(ADVICE_ADD_EMPTY_PATHSPEC)) + advise( _("Maybe you wanted to say 'git add .'?\n" + "Turn this message off by running\n" + "\"git config advice.addEmptyPathspec false\"")); return 0; } @@ -505,7 +609,6 @@ int baselen; /* Set up the default git porcelain excludes */ - memset(&dir, 0, sizeof(dir)); if (!ignored_too) { dir.flags |= DIR_COLLECT_IGNORED; setup_standard_excludes(&dir); @@ -518,15 +621,18 @@ } if (refresh_only) { - refresh(verbose, &pathspec); + exit_status |= refresh(verbose, &pathspec); goto finish; } if (pathspec.nr) { int i; + char *skip_worktree_seen = NULL; + struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP; if (!seen) - seen = find_pathspecs_matching_against_index(&pathspec, &the_index); + seen = find_pathspecs_matching_against_index(&pathspec, + &the_index, PS_IGNORE_SKIP_WORKTREE); /* * file_exists() assumes exact match @@ -540,12 +646,25 @@ for (i = 0; i < pathspec.nr; i++) { const char *path = pathspec.items[i].match; + if (pathspec.items[i].magic & PATHSPEC_EXCLUDE) continue; - if (!seen[i] && path[0] && - ((pathspec.items[i].magic & - (PATHSPEC_GLOB | PATHSPEC_ICASE)) || - !file_exists(path))) { + if (seen[i]) + continue; + + if (!include_sparse && + matches_skip_worktree(&pathspec, i, &skip_worktree_seen)) { + string_list_append(&only_match_skip_worktree, + pathspec.items[i].original); + continue; + } + + /* Don't complain at 'git add .' on empty repo */ + if (!path[0]) + continue; + + if ((pathspec.items[i].magic & (PATHSPEC_GLOB | PATHSPEC_ICASE)) || + !file_exists(path)) { if (ignore_missing) { int dtype = DT_UNKNOWN; if (is_excluded(&dir, &the_index, path, &dtype)) @@ -556,7 +675,16 @@ pathspec.items[i].original); } } + + + if (only_match_skip_worktree.nr) { + advise_on_updating_sparse_paths(&only_match_skip_worktree); + exit_status = 1; + } + free(seen); + free(skip_worktree_seen); + string_list_clear(&only_match_skip_worktree, 0); } plug_bulk_checkin(); @@ -570,7 +698,7 @@ exit_status |= add_files(&dir, flags); if (chmod_arg && pathspec.nr) - chmod_pathspec(&pathspec, chmod_arg[0]); + exit_status |= chmod_pathspec(&pathspec, chmod_arg[0], show_only); unplug_bulk_checkin(); finish: @@ -578,7 +706,7 @@ COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("Unable to write new index file")); + dir_clear(&dir); UNLEAK(pathspec); - UNLEAK(dir); return exit_status; } diff -Nru git-2.25.1/builtin/am.c git-2.34.1/builtin/am.c --- git-2.25.1/builtin/am.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/am.c 2021-11-24 19:29:21.000000000 +0000 @@ -11,6 +11,7 @@ #include "parse-options.h" #include "dir.h" #include "run-command.h" +#include "hook.h" #include "quote.h" #include "tempfile.h" #include "lockfile.h" @@ -81,6 +82,11 @@ SIGNOFF_EXPLICIT /* --signoff was set on the command-line */ }; +enum show_patch_type { + SHOW_PATCH_RAW = 0, + SHOW_PATCH_DIFF = 1, +}; + struct am_state { /* state directory path */ char *dir; @@ -111,7 +117,8 @@ int keep; /* enum keep_type */ int message_id; int scissors; /* enum scissors_type */ - struct argv_array git_apply_opts; + int quoted_cr; /* enum quoted_cr_action */ + struct strvec git_apply_opts; const char *resolvemsg; int committer_date_is_author_date; int ignore_date; @@ -140,8 +147,9 @@ git_config_get_bool("am.messageid", &state->message_id); state->scissors = SCISSORS_UNSET; + state->quoted_cr = quoted_cr_unset; - argv_array_init(&state->git_apply_opts); + strvec_init(&state->git_apply_opts); if (!git_config_get_bool("commit.gpgsign", &gpgsign)) state->sign_commit = gpgsign ? "" : NULL; @@ -157,7 +165,17 @@ free(state->author_email); free(state->author_date); free(state->msg); - argv_array_clear(&state->git_apply_opts); + strvec_clear(&state->git_apply_opts); +} + +static int am_option_parse_quoted_cr(const struct option *opt, + const char *arg, int unset) +{ + BUG_ON_OPT_NEG(unset); + + if (mailinfo_parse_quoted_cr_action(arg, opt->value) != 0) + return error(_("bad action '%s' for '%s'"), arg, "--quoted-cr"); + return 0; } /** @@ -193,6 +211,7 @@ * If state->quiet is false, calls fprintf(fp, fmt, ...), and appends a newline * at the end. */ +__attribute__((format (printf, 3, 4))) static void say(const struct am_state *state, FILE *fp, const char *fmt, ...) { va_list ap; @@ -392,9 +411,15 @@ else state->scissors = SCISSORS_UNSET; + read_state_file(&sb, state, "quoted-cr", 1); + if (!*sb.buf) + state->quoted_cr = quoted_cr_unset; + else if (mailinfo_parse_quoted_cr_action(sb.buf, &state->quoted_cr) != 0) + die(_("could not parse %s"), am_path(state, "quoted-cr")); + read_state_file(&sb, state, "apply-opt", 1); - argv_array_clear(&state->git_apply_opts); - if (sq_dequote_to_argv_array(sb.buf, &state->git_apply_opts) < 0) + strvec_clear(&state->git_apply_opts); + if (sq_dequote_to_strvec(sb.buf, &state->git_apply_opts) < 0) die(_("could not parse %s"), am_path(state, "apply-opt")); state->rebasing = !!file_exists(am_path(state, "rebasing")); @@ -447,8 +472,8 @@ if (!hook) return 0; - argv_array_push(&cp.args, hook); - argv_array_push(&cp.args, "rebase"); + strvec_push(&cp.args, hook); + strvec_push(&cp.args, "rebase"); cp.in = xopen(am_path(state, "rewritten"), O_RDONLY); cp.stdout_to_stderr = 1; @@ -646,16 +671,16 @@ int ret; cp.git_cmd = 1; - argv_array_push(&cp.args, "mailsplit"); - argv_array_pushf(&cp.args, "-d%d", state->prec); - argv_array_pushf(&cp.args, "-o%s", state->dir); - argv_array_push(&cp.args, "-b"); + strvec_push(&cp.args, "mailsplit"); + strvec_pushf(&cp.args, "-d%d", state->prec); + strvec_pushf(&cp.args, "-o%s", state->dir); + strvec_push(&cp.args, "-b"); if (keep_cr) - argv_array_push(&cp.args, "--keep-cr"); + strvec_push(&cp.args, "--keep-cr"); if (mboxrd) - argv_array_push(&cp.args, "--mboxrd"); - argv_array_push(&cp.args, "--"); - argv_array_pushv(&cp.args, paths); + strvec_push(&cp.args, "--mboxrd"); + strvec_push(&cp.args, "--"); + strvec_pushv(&cp.args, paths); ret = capture_command(&cp, &last, 8); if (ret) @@ -782,7 +807,7 @@ const char *series_dir; char *series_dir_buf; FILE *fp; - struct argv_array patches = ARGV_ARRAY_INIT; + struct strvec patches = STRVEC_INIT; struct strbuf sb = STRBUF_INIT; int ret; @@ -800,16 +825,16 @@ if (*sb.buf == '#') continue; /* skip comment lines */ - argv_array_push(&patches, mkpath("%s/%s", series_dir, sb.buf)); + strvec_push(&patches, mkpath("%s/%s", series_dir, sb.buf)); } fclose(fp); strbuf_release(&sb); free(series_dir_buf); - ret = split_mail_conv(stgit_patch_to_mail, state, patches.argv, keep_cr); + ret = split_mail_conv(stgit_patch_to_mail, state, patches.v, keep_cr); - argv_array_clear(&patches); + strvec_clear(&patches); return ret; } @@ -997,7 +1022,25 @@ } write_state_text(state, "scissors", str); - sq_quote_argv(&sb, state->git_apply_opts.argv); + switch (state->quoted_cr) { + case quoted_cr_unset: + str = ""; + break; + case quoted_cr_nowarn: + str = "nowarn"; + break; + case quoted_cr_warn: + str = "warn"; + break; + case quoted_cr_strip: + str = "strip"; + break; + default: + BUG("invalid value for state->quoted_cr"); + } + write_state_text(state, "quoted-cr", str); + + sq_quote_argv(&sb, state->git_apply_opts.v); write_state_text(state, "apply-opt", sb.buf); if (state->rebasing) @@ -1157,6 +1200,18 @@ BUG("invalid value for state->scissors"); } + switch (state->quoted_cr) { + case quoted_cr_unset: + break; + case quoted_cr_nowarn: + case quoted_cr_warn: + case quoted_cr_strip: + mi.quoted_cr = state->quoted_cr; + break; + default: + BUG("invalid value for state->quoted_cr"); + } + mi.input = xfopen(mail, "r"); mi.output = xfopen(am_path(state, "info"), "w"); if (mailinfo(&mi, am_path(state, "msg"), am_path(state, "patch"))) @@ -1385,8 +1440,8 @@ */ static int run_apply(const struct am_state *state, const char *index_file) { - struct argv_array apply_paths = ARGV_ARRAY_INIT; - struct argv_array apply_opts = ARGV_ARRAY_INIT; + struct strvec apply_paths = STRVEC_INIT; + struct strvec apply_opts = STRVEC_INIT; struct apply_state apply_state; int res, opts_left; int force_apply = 0; @@ -1395,10 +1450,10 @@ if (init_apply_state(&apply_state, the_repository, NULL)) BUG("init_apply_state() failed"); - argv_array_push(&apply_opts, "apply"); - argv_array_pushv(&apply_opts, state->git_apply_opts.argv); + strvec_push(&apply_opts, "apply"); + strvec_pushv(&apply_opts, state->git_apply_opts.v); - opts_left = apply_parse_options(apply_opts.argc, apply_opts.argv, + opts_left = apply_parse_options(apply_opts.nr, apply_opts.v, &apply_state, &force_apply, &options, NULL); @@ -1421,12 +1476,12 @@ if (check_apply_state(&apply_state, force_apply)) BUG("check_apply_state() failed"); - argv_array_push(&apply_paths, am_path(state, "patch")); + strvec_push(&apply_paths, am_path(state, "patch")); - res = apply_all_patches(&apply_state, apply_paths.argc, apply_paths.argv, options); + res = apply_all_patches(&apply_state, apply_paths.nr, apply_paths.v, options); - argv_array_clear(&apply_paths); - argv_array_clear(&apply_opts); + strvec_clear(&apply_paths); + strvec_clear(&apply_opts); clear_apply_state(&apply_state); if (res) @@ -1449,10 +1504,10 @@ struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; - argv_array_push(&cp.args, "apply"); - argv_array_pushv(&cp.args, state->git_apply_opts.argv); - argv_array_pushf(&cp.args, "--build-fake-ancestor=%s", index_file); - argv_array_push(&cp.args, am_path(state, "patch")); + strvec_push(&cp.args, "apply"); + strvec_pushv(&cp.args, state->git_apply_opts.v); + strvec_pushf(&cp.args, "--build-fake-ancestor=%s", index_file); + strvec_push(&cp.args, am_path(state, "patch")); if (run_command(&cp)) return -1; @@ -1551,7 +1606,7 @@ struct object_id tree, parent, commit; const struct object_id *old_oid; struct commit_list *parents = NULL; - const char *reflog_msg, *author; + const char *reflog_msg, *author, *committer = NULL; struct strbuf sb = STRBUF_INIT; if (run_hook_le(NULL, "pre-applypatch", NULL)) @@ -1575,11 +1630,16 @@ IDENT_STRICT); if (state->committer_date_is_author_date) - setenv("GIT_COMMITTER_DATE", - state->ignore_date ? "" : state->author_date, 1); - - if (commit_tree(state->msg, state->msg_len, &tree, parents, &commit, - author, state->sign_commit)) + committer = fmt_ident(getenv("GIT_COMMITTER_NAME"), + getenv("GIT_COMMITTER_EMAIL"), + WANT_COMMITTER_IDENT, + state->ignore_date ? NULL + : state->author_date, + IDENT_STRICT); + + if (commit_tree_extended(state->msg, state->msg_len, &tree, parents, + &commit, author, committer, state->sign_commit, + NULL)) die(_("failed to write commit object")); reflog_msg = getenv("GIT_REFLOG_ACTION"); @@ -1671,7 +1731,7 @@ if (!pager) pager = "cat"; prepare_pager_args(&cp, pager); - argv_array_push(&cp.args, am_path(state, "patch")); + strvec_push(&cp.args, am_path(state, "patch")); run_command(&cp); } } @@ -1686,7 +1746,6 @@ */ static void am_run(struct am_state *state, int resume) { - const char *argv_gc_auto[] = {"gc", "--auto", NULL}; struct strbuf sb = STRBUF_INIT; unlink(am_path(state, "dirtyindex")); @@ -1762,8 +1821,8 @@ printf_ln(_("Patch failed at %s %.*s"), msgnum(state), linelen(state->msg), state->msg); - if (advice_amworkdir) - advise(_("Use 'git am --show-current-patch' to see the failed patch")); + if (advice_enabled(ADVICE_AM_WORK_DIR)) + advise(_("Use 'git am --show-current-patch=diff' to see the failed patch")); die_user_resolve(state); } @@ -1790,8 +1849,7 @@ */ if (!state->rebasing) { am_destroy(state); - close_object_store(the_repository->objects); - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + run_auto_maintenance(state->quiet); } } @@ -1860,7 +1918,8 @@ opts.dst_index = &the_index; opts.update = 1; opts.merge = 1; - opts.reset = reset; + opts.reset = reset ? UNPACK_RESET_PROTECT_UNTRACKED : 0; + opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */ opts.fn = twoway_merge; init_tree_desc(&t[0], head->buffer, head->size); init_tree_desc(&t[1], remote->buffer, remote->size); @@ -2048,7 +2107,8 @@ if (!has_orig_head) oidcpy(&orig_head, the_hash_algo->empty_tree); - clean_index(&curr_head, &orig_head); + if (clean_index(&curr_head, &orig_head)) + die(_("failed to clean index")); if (has_orig_head) update_ref("am --abort", "HEAD", &orig_head, @@ -2061,7 +2121,7 @@ am_destroy(state); } -static int show_patch(struct am_state *state) +static int show_patch(struct am_state *state, enum show_patch_type sub_mode) { struct strbuf sb = STRBUF_INIT; const char *patch_path; @@ -2078,7 +2138,17 @@ return ret; } - patch_path = am_path(state, msgnum(state)); + switch (sub_mode) { + case SHOW_PATCH_RAW: + patch_path = am_path(state, msgnum(state)); + break; + case SHOW_PATCH_DIFF: + patch_path = am_path(state, "patch"); + break; + default: + BUG("invalid mode for --show-current-patch"); + } + len = strbuf_read_file(&sb, patch_path, 0); if (len < 0) die_errno(_("failed to read '%s'"), patch_path); @@ -2118,7 +2188,7 @@ return 0; } -enum resume_mode { +enum resume_type { RESUME_FALSE = 0, RESUME_APPLY, RESUME_RESOLVED, @@ -2128,6 +2198,47 @@ RESUME_SHOW_PATCH }; +struct resume_mode { + enum resume_type mode; + enum show_patch_type sub_mode; +}; + +static int parse_opt_show_current_patch(const struct option *opt, const char *arg, int unset) +{ + int *opt_value = opt->value; + struct resume_mode *resume = container_of(opt_value, struct resume_mode, mode); + + /* + * Please update $__git_showcurrentpatch in git-completion.bash + * when you add new options + */ + const char *valid_modes[] = { + [SHOW_PATCH_DIFF] = "diff", + [SHOW_PATCH_RAW] = "raw" + }; + int new_value = SHOW_PATCH_RAW; + + BUG_ON_OPT_NEG(unset); + + if (arg) { + for (new_value = 0; new_value < ARRAY_SIZE(valid_modes); new_value++) { + if (!strcmp(arg, valid_modes[new_value])) + break; + } + if (new_value >= ARRAY_SIZE(valid_modes)) + return error(_("Invalid value for --show-current-patch: %s"), arg); + } + + if (resume->mode == RESUME_SHOW_PATCH && new_value != resume->sub_mode) + return error(_("--show-current-patch=%s is incompatible with " + "--show-current-patch=%s"), + arg, valid_modes[resume->sub_mode]); + + resume->mode = RESUME_SHOW_PATCH; + resume->sub_mode = new_value; + return 0; +} + static int git_am_config(const char *k, const char *v, void *cb) { int status; @@ -2145,7 +2256,7 @@ int binary = -1; int keep_cr = -1; int patch_format = PATCH_FORMAT_UNKNOWN; - enum resume_mode resume = RESUME_FALSE; + struct resume_mode resume = { .mode = RESUME_FALSE }; int in_progress; int ret = 0; @@ -2164,7 +2275,7 @@ N_("allow fall back on 3way merging if needed")), OPT__QUIET(&state.quiet, N_("be quiet")), OPT_SET_INT('s', "signoff", &state.signoff, - N_("add a Signed-off-by line to the commit message"), + N_("add a Signed-off-by trailer to the commit message"), SIGNOFF_EXPLICIT), OPT_BOOL('u', "utf8", &state.utf8, N_("recode into utf8 (default)")), @@ -2182,6 +2293,9 @@ 0, PARSE_OPT_NONEG), OPT_BOOL('c', "scissors", &state.scissors, N_("strip everything before a scissors line")), + OPT_CALLBACK_F(0, "quoted-cr", &state.quoted_cr, N_("action"), + N_("pass it through git-mailinfo"), + PARSE_OPT_NONEG, am_option_parse_quoted_cr), OPT_PASSTHRU_ARGV(0, "whitespace", &state.git_apply_opts, N_("action"), N_("pass it through git-apply"), 0), @@ -2214,24 +2328,26 @@ PARSE_OPT_NOARG), OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL, N_("override error message when patch failure occurs")), - OPT_CMDMODE(0, "continue", &resume, + OPT_CMDMODE(0, "continue", &resume.mode, N_("continue applying patches after resolving a conflict"), RESUME_RESOLVED), - OPT_CMDMODE('r', "resolved", &resume, + OPT_CMDMODE('r', "resolved", &resume.mode, N_("synonyms for --continue"), RESUME_RESOLVED), - OPT_CMDMODE(0, "skip", &resume, + OPT_CMDMODE(0, "skip", &resume.mode, N_("skip the current patch"), RESUME_SKIP), - OPT_CMDMODE(0, "abort", &resume, - N_("restore the original branch and abort the patching operation."), + OPT_CMDMODE(0, "abort", &resume.mode, + N_("restore the original branch and abort the patching operation"), RESUME_ABORT), - OPT_CMDMODE(0, "quit", &resume, - N_("abort the patching operation but keep HEAD where it is."), + OPT_CMDMODE(0, "quit", &resume.mode, + N_("abort the patching operation but keep HEAD where it is"), RESUME_QUIT), - OPT_CMDMODE(0, "show-current-patch", &resume, - N_("show the patch being applied."), - RESUME_SHOW_PATCH), + { OPTION_CALLBACK, 0, "show-current-patch", &resume.mode, + "(diff|raw)", + N_("show the patch being applied"), + PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, + parse_opt_show_current_patch, RESUME_SHOW_PATCH }, OPT_BOOL(0, "committer-date-is-author-date", &state.committer_date_is_author_date, N_("lie about committer date")), @@ -2281,17 +2397,17 @@ * intend to feed us a patch but wanted to continue * unattended. */ - if (argc || (resume == RESUME_FALSE && !isatty(0))) + if (argc || (resume.mode == RESUME_FALSE && !isatty(0))) die(_("previous rebase directory %s still exists but mbox given."), state.dir); - if (resume == RESUME_FALSE) - resume = RESUME_APPLY; + if (resume.mode == RESUME_FALSE) + resume.mode = RESUME_APPLY; if (state.signoff == SIGNOFF_EXPLICIT) am_append_signoff(&state); } else { - struct argv_array paths = ARGV_ARRAY_INIT; + struct strvec paths = STRVEC_INIT; int i; /* @@ -2300,7 +2416,7 @@ * stray directories. */ if (file_exists(state.dir) && !state.rebasing) { - if (resume == RESUME_ABORT || resume == RESUME_QUIT) { + if (resume.mode == RESUME_ABORT || resume.mode == RESUME_QUIT) { am_destroy(&state); am_state_release(&state); return 0; @@ -2311,25 +2427,25 @@ state.dir); } - if (resume) + if (resume.mode) die(_("Resolve operation not in progress, we are not resuming.")); for (i = 0; i < argc; i++) { if (is_absolute_path(argv[i]) || !prefix) - argv_array_push(&paths, argv[i]); + strvec_push(&paths, argv[i]); else - argv_array_push(&paths, mkpath("%s/%s", prefix, argv[i])); + strvec_push(&paths, mkpath("%s/%s", prefix, argv[i])); } - if (state.interactive && !paths.argc) + if (state.interactive && !paths.nr) die(_("interactive mode requires patches on the command line")); - am_setup(&state, patch_format, paths.argv, keep_cr); + am_setup(&state, patch_format, paths.v, keep_cr); - argv_array_clear(&paths); + strvec_clear(&paths); } - switch (resume) { + switch (resume.mode) { case RESUME_FALSE: am_run(&state, 0); break; @@ -2350,7 +2466,7 @@ am_destroy(&state); break; case RESUME_SHOW_PATCH: - ret = show_patch(&state); + ret = show_patch(&state, resume.sub_mode); break; default: BUG("invalid resume value"); diff -Nru git-2.25.1/builtin/annotate.c git-2.34.1/builtin/annotate.c --- git-2.25.1/builtin/annotate.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/annotate.c 2021-11-24 19:29:21.000000000 +0000 @@ -5,18 +5,18 @@ */ #include "git-compat-util.h" #include "builtin.h" -#include "argv-array.h" +#include "strvec.h" int cmd_annotate(int argc, const char **argv, const char *prefix) { - struct argv_array args = ARGV_ARRAY_INIT; + struct strvec args = STRVEC_INIT; int i; - argv_array_pushl(&args, "annotate", "-c", NULL); + strvec_pushl(&args, "annotate", "-c", NULL); for (i = 1; i < argc; i++) { - argv_array_push(&args, argv[i]); + strvec_push(&args, argv[i]); } - return cmd_blame(args.argc, args.argv, prefix); + return cmd_blame(args.nr, args.v, prefix); } diff -Nru git-2.25.1/builtin/archive.c git-2.34.1/builtin/archive.c --- git-2.25.1/builtin/archive.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/archive.c 2021-11-24 19:29:21.000000000 +0000 @@ -12,9 +12,7 @@ static void create_output_file(const char *output_file) { - int output_fd = open(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666); - if (output_fd < 0) - die_errno(_("could not create archive file '%s'"), output_file); + int output_fd = xopen(output_file, O_CREAT | O_WRONLY | O_TRUNC, 0666); if (output_fd != 1) { if (dup2(output_fd, 1) < 0) die_errno(_("could not redirect output")); diff -Nru git-2.25.1/builtin/bisect--helper.c git-2.34.1/builtin/bisect--helper.c --- git-2.25.1/builtin/bisect--helper.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/bisect--helper.c 2021-11-24 19:29:21.000000000 +0000 @@ -4,34 +4,42 @@ #include "bisect.h" #include "refs.h" #include "dir.h" -#include "argv-array.h" +#include "strvec.h" #include "run-command.h" #include "prompt.h" #include "quote.h" +#include "revision.h" static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS") static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV") static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK") static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START") -static GIT_PATH_FUNC(git_path_bisect_head, "BISECT_HEAD") static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG") static GIT_PATH_FUNC(git_path_head_name, "head-name") static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES") +static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT") +static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN") static const char * const git_bisect_helper_usage[] = { - N_("git bisect--helper --next-all [--no-checkout]"), - N_("git bisect--helper --write-terms "), - N_("git bisect--helper --bisect-clean-state"), N_("git bisect--helper --bisect-reset []"), - N_("git bisect--helper --bisect-write [--no-log] "), - N_("git bisect--helper --bisect-check-and-set-terms "), - N_("git bisect--helper --bisect-next-check []"), N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"), - N_("git bisect--helper --bisect-start [--term-{old,good}= --term-{new,bad}=]" - "[--no-checkout] [ [...]] [--] [...]"), + N_("git bisect--helper --bisect-start [--term-{new,bad}= --term-{old,good}=]" + " [--no-checkout] [--first-parent] [ [...]] [--] [...]"), + N_("git bisect--helper --bisect-next"), + N_("git bisect--helper --bisect-state (bad|new) []"), + N_("git bisect--helper --bisect-state (good|old) [...]"), + N_("git bisect--helper --bisect-replay "), + N_("git bisect--helper --bisect-skip [(|)...]"), + N_("git bisect--helper --bisect-visualize"), + N_("git bisect--helper --bisect-run ..."), NULL }; +struct add_bisect_ref_data { + struct rev_info *revs; + unsigned int object_flags; +}; + struct bisect_terms { char *term_good; char *term_bad; @@ -52,8 +60,10 @@ terms->term_bad = xstrdup(bad); } -static const char *vocab_bad = "bad|new"; -static const char *vocab_good = "good|old"; +static const char vocab_bad[] = "bad|new"; +static const char vocab_good[] = "good|old"; + +static int bisect_autostart(struct bisect_terms *terms); /* * Check whether the string `term` belongs to the set of strings @@ -74,6 +84,80 @@ return res; } +/* + * return code BISECT_INTERNAL_SUCCESS_MERGE_BASE + * and BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND are codes + * that indicate special success. + */ + +static int is_bisect_success(enum bisect_error res) +{ + return !res || + res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND || + res == BISECT_INTERNAL_SUCCESS_MERGE_BASE; +} + +static int write_in_file(const char *path, const char *mode, const char *format, va_list args) +{ + FILE *fp = NULL; + int res = 0; + + if (strcmp(mode, "w") && strcmp(mode, "a")) + BUG("write-in-file does not support '%s' mode", mode); + fp = fopen(path, mode); + if (!fp) + return error_errno(_("cannot open file '%s' in mode '%s'"), path, mode); + res = vfprintf(fp, format, args); + + if (res < 0) { + int saved_errno = errno; + fclose(fp); + errno = saved_errno; + return error_errno(_("could not write to file '%s'"), path); + } + + return fclose(fp); +} + +__attribute__((format (printf, 2, 3))) +static int write_to_file(const char *path, const char *format, ...) +{ + int res; + va_list args; + + va_start(args, format); + res = write_in_file(path, "w", format, args); + va_end(args); + + return res; +} + +__attribute__((format (printf, 2, 3))) +static int append_to_file(const char *path, const char *format, ...) +{ + int res; + va_list args; + + va_start(args, format); + res = write_in_file(path, "a", format, args); + va_end(args); + + return res; +} + +static int print_file_to_stdout(const char *path) +{ + int fd = open(path, O_RDONLY); + int ret = 0; + + if (fd < 0) + return error_errno(_("cannot open file '%s' for reading"), path); + if (copy_fd(fd, 1) < 0) + ret = error_errno(_("failed to read '%s'"), path); + close(fd); + return ret; +} + static int check_term_format(const char *term, const char *orig_term) { int res; @@ -104,7 +188,6 @@ static int write_terms(const char *bad, const char *good) { - FILE *fp = NULL; int res; if (!strcmp(bad, good)) @@ -113,39 +196,11 @@ if (check_term_format(bad, "bad") || check_term_format(good, "good")) return -1; - fp = fopen(git_path_bisect_terms(), "w"); - if (!fp) - return error_errno(_("could not open the file BISECT_TERMS")); - - res = fprintf(fp, "%s\n%s\n", bad, good); - res |= fclose(fp); - return (res < 0) ? -1 : 0; -} + res = write_to_file(git_path_bisect_terms(), "%s\n%s\n", bad, good); -static int is_expected_rev(const char *expected_hex) -{ - struct strbuf actual_hex = STRBUF_INIT; - int res = 0; - if (strbuf_read_file(&actual_hex, git_path_bisect_expected_rev(), 0) >= 40) { - strbuf_trim(&actual_hex); - res = !strcmp(actual_hex.buf, expected_hex); - } - strbuf_release(&actual_hex); return res; } -static void check_expected_revs(const char **revs, int rev_nr) -{ - int i; - - for (i = 0; i < rev_nr; i++) { - if (!is_expected_rev(revs[i])) { - unlink_or_warn(git_path_bisect_ancestors_ok()); - unlink_or_warn(git_path_bisect_expected_rev()); - } - } -} - static int bisect_reset(const char *commit) { struct strbuf branch = STRBUF_INIT; @@ -164,19 +219,19 @@ strbuf_addstr(&branch, commit); } - if (!file_exists(git_path_bisect_head())) { - struct argv_array argv = ARGV_ARRAY_INIT; + if (!ref_exists("BISECT_HEAD")) { + struct strvec argv = STRVEC_INIT; - argv_array_pushl(&argv, "checkout", branch.buf, "--", NULL); - if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { + strvec_pushl(&argv, "checkout", branch.buf, "--", NULL); + if (run_command_v_opt(argv.v, RUN_GIT_CMD)) { error(_("could not check out original" " HEAD '%s'. Try 'git bisect" " reset '."), branch.buf); strbuf_release(&branch); - argv_array_clear(&argv); + strvec_clear(&argv); return -1; } - argv_array_clear(&argv); + strvec_clear(&argv); } strbuf_release(&branch); @@ -206,31 +261,31 @@ struct object_id oid; struct commit *commit; FILE *fp = NULL; - int retval = 0; + int res = 0; if (!strcmp(state, terms->term_bad)) { strbuf_addf(&tag, "refs/bisect/%s", state); } else if (one_of(state, terms->term_good, "skip", NULL)) { strbuf_addf(&tag, "refs/bisect/%s-%s", state, rev); } else { - retval = error(_("Bad bisect_write argument: %s"), state); + res = error(_("Bad bisect_write argument: %s"), state); goto finish; } if (get_oid(rev, &oid)) { - retval = error(_("couldn't get the oid of the rev '%s'"), rev); + res = error(_("couldn't get the oid of the rev '%s'"), rev); goto finish; } if (update_ref(NULL, tag.buf, &oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR)) { - retval = -1; + res = -1; goto finish; } fp = fopen(git_path_bisect_log(), "a"); if (!fp) { - retval = error_errno(_("couldn't open the file '%s'"), git_path_bisect_log()); + res = error_errno(_("couldn't open the file '%s'"), git_path_bisect_log()); goto finish; } @@ -244,7 +299,7 @@ if (fp) fclose(fp); strbuf_release(&tag); - return retval; + return res; } static int check_and_set_terms(struct bisect_terms *terms, const char *cmd) @@ -291,26 +346,14 @@ "You then need to give me at least one %s and %s revision.\n" "You can use \"git bisect %s\" and \"git bisect %s\" for that."); -static int bisect_next_check(const struct bisect_terms *terms, - const char *current_term) +static int decide_next(const struct bisect_terms *terms, + const char *current_term, int missing_good, + int missing_bad) { - int missing_good = 1, missing_bad = 1, retval = 0; - const char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad); - const char *good_glob = xstrfmt("%s-*", terms->term_good); - - if (ref_exists(bad_ref)) - missing_bad = 0; - - for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/", - (void *) &missing_good); - if (!missing_good && !missing_bad) - goto finish; - - if (!current_term) { - retval = -1; - goto finish; - } + return 0; + if (!current_term) + return -1; if (missing_good && !missing_bad && !strcmp(current_term, terms->term_good)) { @@ -321,7 +364,7 @@ */ warning(_("bisecting only with a %s commit"), terms->term_bad); if (!isatty(0)) - goto finish; + return 0; /* * TRANSLATORS: Make sure to include [Y] and [n] in your * translation. The program will only accept English input @@ -329,21 +372,35 @@ */ yesno = git_prompt(_("Are you sure [Y/n]? "), PROMPT_ECHO); if (starts_with(yesno, "N") || starts_with(yesno, "n")) - retval = -1; - goto finish; - } - if (!is_empty_or_missing_file(git_path_bisect_start())) { - retval = error(_(need_bad_and_good_revision_warning), - vocab_bad, vocab_good, vocab_bad, vocab_good); - } else { - retval = error(_(need_bisect_start_warning), - vocab_good, vocab_bad, vocab_good, vocab_bad); + return -1; + return 0; } -finish: - free((void *) good_glob); - free((void *) bad_ref); - return retval; + if (!is_empty_or_missing_file(git_path_bisect_start())) + return error(_(need_bad_and_good_revision_warning), + vocab_bad, vocab_good, vocab_bad, vocab_good); + else + return error(_(need_bisect_start_warning), + vocab_good, vocab_bad, vocab_good, vocab_bad); +} + +static int bisect_next_check(const struct bisect_terms *terms, + const char *current_term) +{ + 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; + + for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/", + (void *) &missing_good); + + free(good_glob); + free(bad_ref); + + return decide_next(terms, current_term, missing_good, missing_bad); } static int get_terms(struct bisect_terms *terms) @@ -397,7 +454,7 @@ static int bisect_append_log_quoted(const char **argv) { - int retval = 0; + int res = 0; FILE *fp = fopen(git_path_bisect_log(), "a"); struct strbuf orig_args = STRBUF_INIT; @@ -405,25 +462,163 @@ return -1; if (fprintf(fp, "git bisect start") < 1) { - retval = -1; + res = -1; goto finish; } sq_quote_argv(&orig_args, argv); if (fprintf(fp, "%s\n", orig_args.buf) < 1) - retval = -1; + res = -1; finish: fclose(fp); strbuf_release(&orig_args); - return retval; + return res; +} + +static int add_bisect_ref(const char *refname, const struct object_id *oid, + int flags, void *cb) +{ + struct add_bisect_ref_data *data = cb; + + add_pending_oid(data->revs, refname, oid, data->object_flags); + + return 0; +} + +static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs) +{ + int res = 0; + struct add_bisect_ref_data cb = { revs }; + char *good = xstrfmt("%s-*", terms->term_good); + + /* + * We cannot use terms->term_bad directly in + * for_each_glob_ref_in() and we have to append a '*' to it, + * otherwise for_each_glob_ref_in() will append '/' and '*'. + */ + char *bad = xstrfmt("%s*", terms->term_bad); + + /* + * It is important to reset the flags used by revision walks + * as the previous call to bisect_next_all() in turn + * sets up a revision walk. + */ + reset_revision_walk(); + init_revisions(revs, NULL); + setup_revisions(0, NULL, revs, NULL); + for_each_glob_ref_in(add_bisect_ref, bad, "refs/bisect/", &cb); + cb.object_flags = UNINTERESTING; + for_each_glob_ref_in(add_bisect_ref, good, "refs/bisect/", &cb); + if (prepare_revision_walk(revs)) + res = error(_("revision walk setup failed\n")); + + free(good); + free(bad); + return res; +} + +static int bisect_skipped_commits(struct bisect_terms *terms) +{ + int res; + FILE *fp = NULL; + struct rev_info revs; + struct commit *commit; + struct pretty_print_context pp = {0}; + struct strbuf commit_name = STRBUF_INIT; + + res = prepare_revs(terms, &revs); + if (res) + return res; + + fp = fopen(git_path_bisect_log(), "a"); + if (!fp) + return error_errno(_("could not open '%s' for appending"), + git_path_bisect_log()); + + if (fprintf(fp, "# only skipped commits left to test\n") < 0) + return error_errno(_("failed to write to '%s'"), git_path_bisect_log()); + + while ((commit = get_revision(&revs)) != NULL) { + strbuf_reset(&commit_name); + format_commit_message(commit, "%s", + &commit_name, &pp); + fprintf(fp, "# possible first %s commit: [%s] %s\n", + terms->term_bad, oid_to_hex(&commit->object.oid), + commit_name.buf); + } + + /* + * Reset the flags used by revision walks in case + * there is another revision walk after this one. + */ + reset_revision_walk(); + + strbuf_release(&commit_name); + fclose(fp); + return 0; +} + +static int bisect_successful(struct bisect_terms *terms) +{ + struct object_id oid; + struct commit *commit; + struct pretty_print_context pp = {0}; + struct strbuf commit_name = STRBUF_INIT; + char *bad_ref = xstrfmt("refs/bisect/%s",terms->term_bad); + int res; + + read_ref(bad_ref, &oid); + commit = lookup_commit_reference_by_name(bad_ref); + format_commit_message(commit, "%s", &commit_name, &pp); + + res = append_to_file(git_path_bisect_log(), "# first %s commit: [%s] %s\n", + terms->term_bad, oid_to_hex(&commit->object.oid), + commit_name.buf); + + strbuf_release(&commit_name); + free(bad_ref); + return res; } -static int bisect_start(struct bisect_terms *terms, int no_checkout, - const char **argv, int argc) +static enum bisect_error bisect_next(struct bisect_terms *terms, const char *prefix) { + enum bisect_error res; + + if (bisect_autostart(terms)) + return BISECT_FAILED; + + if (bisect_next_check(terms, terms->term_good)) + return BISECT_FAILED; + + /* Perform all bisection computation */ + res = bisect_next_all(the_repository, prefix); + + if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) { + res = bisect_successful(terms); + return res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND; + } else if (res == BISECT_ONLY_SKIPPED_LEFT) { + res = bisect_skipped_commits(terms); + return res ? res : BISECT_ONLY_SKIPPED_LEFT; + } + return res; +} + +static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix) +{ + if (bisect_next_check(terms, NULL)) + return BISECT_OK; + + return bisect_next(terms, prefix); +} + +static enum bisect_error bisect_start(struct bisect_terms *terms, const char **argv, int argc) +{ + int no_checkout = 0; + int first_parent_only = 0; int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0; - int flags, pathspec_pos, retval = 0; + int flags, pathspec_pos; + enum bisect_error res = BISECT_OK; struct string_list revs = STRING_LIST_INIT_DUP; struct string_list states = STRING_LIST_INIT_DUP; struct strbuf start_head = STRBUF_INIT; @@ -451,11 +646,16 @@ break; } else if (!strcmp(arg, "--no-checkout")) { no_checkout = 1; + } else if (!strcmp(arg, "--first-parent")) { + first_parent_only = 1; } else if (!strcmp(arg, "--term-good") || !strcmp(arg, "--term-old")) { + i++; + if (argc <= i) + return error(_("'' is not a valid term")); must_write_terms = 1; free((void *) terms->term_good); - terms->term_good = xstrdup(argv[++i]); + terms->term_good = xstrdup(argv[i]); } else if (skip_prefix(arg, "--term-good=", &arg) || skip_prefix(arg, "--term-old=", &arg)) { must_write_terms = 1; @@ -463,25 +663,26 @@ terms->term_good = xstrdup(arg); } else if (!strcmp(arg, "--term-bad") || !strcmp(arg, "--term-new")) { + i++; + if (argc <= i) + return error(_("'' is not a valid term")); must_write_terms = 1; free((void *) terms->term_bad); - terms->term_bad = xstrdup(argv[++i]); + terms->term_bad = xstrdup(argv[i]); } else if (skip_prefix(arg, "--term-bad=", &arg) || skip_prefix(arg, "--term-new=", &arg)) { must_write_terms = 1; free((void *) terms->term_bad); terms->term_bad = xstrdup(arg); - } else if (starts_with(arg, "--") && - !one_of(arg, "--term-good", "--term-bad", NULL)) { + } else if (starts_with(arg, "--")) { return error(_("unrecognized option: '%s'"), arg); - } else { - char *commit_id = xstrfmt("%s^{commit}", arg); - if (get_oid(commit_id, &oid) && has_double_dash) - die(_("'%s' does not appear to be a valid " - "revision"), arg); - + } else if (!get_oidf(&oid, "%s^{commit}", arg)) { string_list_append(&revs, oid_to_hex(&oid)); - free(commit_id); + } else if (has_double_dash) { + die(_("'%s' does not appear to be a valid " + "revision"), arg); + } else { + break; } } pathspec_pos = i; @@ -519,12 +720,12 @@ strbuf_read_file(&start_head, git_path_bisect_start(), 0); strbuf_trim(&start_head); if (!no_checkout) { - struct argv_array argv = ARGV_ARRAY_INIT; + struct strvec argv = STRVEC_INIT; - argv_array_pushl(&argv, "checkout", start_head.buf, - "--", NULL); - if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { - retval = error(_("checking out '%s' failed." + strvec_pushl(&argv, "checkout", start_head.buf, + "--", NULL); + if (run_command_v_opt(argv.v, RUN_GIT_CMD)) { + res = error(_("checking out '%s' failed." " Try 'git bisect start " "'."), start_head.buf); @@ -556,28 +757,24 @@ * Get rid of any old bisect state. */ if (bisect_clean_state()) - return -1; - - /* - * In case of mistaken revs or checkout error, or signals received, - * "bisect_auto_next" below may exit or misbehave. - * We have to trap this to be able to clean up using - * "bisect_clean_state". - */ + return BISECT_FAILED; /* * Write new start state */ write_file(git_path_bisect_start(), "%s\n", start_head.buf); + if (first_parent_only) + write_file(git_path_bisect_first_parent(), "\n"); + if (no_checkout) { if (get_oid(start_head.buf, &oid) < 0) { - retval = error(_("invalid ref: '%s'"), start_head.buf); + res = error(_("invalid ref: '%s'"), start_head.buf); goto finish; } if (update_ref(NULL, "BISECT_HEAD", &oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR)) { - retval = -1; + res = BISECT_FAILED; goto finish; } } @@ -589,66 +786,430 @@ for (i = 0; i < states.nr; i++) if (bisect_write(states.items[i].string, revs.items[i].string, terms, 1)) { - retval = -1; + res = BISECT_FAILED; goto finish; } if (must_write_terms && write_terms(terms->term_bad, terms->term_good)) { - retval = -1; + res = BISECT_FAILED; goto finish; } - retval = bisect_append_log_quoted(argv); - if (retval) - retval = -1; + res = bisect_append_log_quoted(argv); + if (res) + res = BISECT_FAILED; finish: string_list_clear(&revs, 0); string_list_clear(&states, 0); strbuf_release(&start_head); strbuf_release(&bisect_names); - return retval; + if (res) + return res; + + res = bisect_auto_next(terms, NULL); + if (!is_bisect_success(res)) + bisect_clean_state(); + return res; +} + +static inline int file_is_not_empty(const char *path) +{ + return !is_empty_or_missing_file(path); +} + +static int bisect_autostart(struct bisect_terms *terms) +{ + int res; + const char *yesno; + + if (file_is_not_empty(git_path_bisect_start())) + return 0; + + fprintf_ln(stderr, _("You need to start by \"git bisect " + "start\"\n")); + + if (!isatty(STDIN_FILENO)) + return -1; + + /* + * TRANSLATORS: Make sure to include [Y] and [n] in your + * translation. The program will only accept English input + * at this point. + */ + yesno = git_prompt(_("Do you want me to do it for you " + "[Y/n]? "), PROMPT_ECHO); + res = tolower(*yesno) == 'n' ? + -1 : bisect_start(terms, empty_strvec, 0); + + return res; +} + +static enum bisect_error bisect_state(struct bisect_terms *terms, const char **argv, + int argc) +{ + const char *state; + int i, verify_expected = 1; + struct object_id oid, expected; + struct strbuf buf = STRBUF_INIT; + struct oid_array revs = OID_ARRAY_INIT; + + if (!argc) + return error(_("Please call `--bisect-state` with at least one argument")); + + if (bisect_autostart(terms)) + return BISECT_FAILED; + + state = argv[0]; + if (check_and_set_terms(terms, state) || + !one_of(state, terms->term_good, terms->term_bad, "skip", NULL)) + return BISECT_FAILED; + + argv++; + argc--; + if (argc > 1 && !strcmp(state, terms->term_bad)) + return error(_("'git bisect %s' can take only one argument."), terms->term_bad); + + if (argc == 0) { + const char *head = "BISECT_HEAD"; + enum get_oid_result res_head = get_oid(head, &oid); + + if (res_head == MISSING_OBJECT) { + head = "HEAD"; + res_head = get_oid(head, &oid); + } + + if (res_head) + error(_("Bad rev input: %s"), head); + oid_array_append(&revs, &oid); + } + + /* + * All input revs must be checked before executing bisect_write() + * to discard junk revs. + */ + + for (; argc; argc--, argv++) { + struct commit *commit; + + if (get_oid(*argv, &oid)){ + error(_("Bad rev input: %s"), *argv); + oid_array_clear(&revs); + return BISECT_FAILED; + } + + commit = lookup_commit_reference(the_repository, &oid); + if (!commit) + die(_("Bad rev input (not a commit): %s"), *argv); + + oid_array_append(&revs, &commit->object.oid); + } + + if (strbuf_read_file(&buf, git_path_bisect_expected_rev(), 0) < the_hash_algo->hexsz || + get_oid_hex(buf.buf, &expected) < 0) + verify_expected = 0; /* Ignore invalid file contents */ + strbuf_release(&buf); + + for (i = 0; i < revs.nr; i++) { + if (bisect_write(state, oid_to_hex(&revs.oid[i]), terms, 0)) { + oid_array_clear(&revs); + return BISECT_FAILED; + } + if (verify_expected && !oideq(&revs.oid[i], &expected)) { + unlink_or_warn(git_path_bisect_ancestors_ok()); + unlink_or_warn(git_path_bisect_expected_rev()); + verify_expected = 0; + } + } + + oid_array_clear(&revs); + return bisect_auto_next(terms, NULL); +} + +static enum bisect_error bisect_log(void) +{ + int fd, status; + const char* filename = git_path_bisect_log(); + + if (is_empty_or_missing_file(filename)) + return error(_("We are not bisecting.")); + + fd = open(filename, O_RDONLY); + if (fd < 0) + return BISECT_FAILED; + + status = copy_fd(fd, STDOUT_FILENO); + close(fd); + return status ? BISECT_FAILED : BISECT_OK; +} + +static int process_replay_line(struct bisect_terms *terms, struct strbuf *line) +{ + const char *p = line->buf + strspn(line->buf, " \t"); + char *word_end, *rev; + + if ((!skip_prefix(p, "git bisect", &p) && + !skip_prefix(p, "git-bisect", &p)) || !isspace(*p)) + return 0; + p += strspn(p, " \t"); + + word_end = (char *)p + strcspn(p, " \t"); + rev = word_end + strspn(word_end, " \t"); + *word_end = '\0'; /* NUL-terminate the word */ + + get_terms(terms); + if (check_and_set_terms(terms, p)) + return -1; + + if (!strcmp(p, "start")) { + struct strvec argv = STRVEC_INIT; + int res; + sq_dequote_to_strvec(rev, &argv); + res = bisect_start(terms, argv.v, argv.nr); + strvec_clear(&argv); + return res; + } + + if (one_of(p, terms->term_good, + terms->term_bad, "skip", NULL)) + return bisect_write(p, rev, terms, 0); + + if (!strcmp(p, "terms")) { + struct strvec argv = STRVEC_INIT; + int res; + sq_dequote_to_strvec(rev, &argv); + res = bisect_terms(terms, argv.nr == 1 ? argv.v[0] : NULL); + strvec_clear(&argv); + return res; + } + error(_("'%s'?? what are you talking about?"), p); + + return -1; +} + +static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *filename) +{ + FILE *fp = NULL; + enum bisect_error res = BISECT_OK; + struct strbuf line = STRBUF_INIT; + + if (is_empty_or_missing_file(filename)) + return error(_("cannot read file '%s' for replaying"), filename); + + if (bisect_reset(NULL)) + return BISECT_FAILED; + + fp = fopen(filename, "r"); + if (!fp) + return BISECT_FAILED; + + while ((strbuf_getline(&line, fp) != EOF) && !res) + res = process_replay_line(terms, &line); + + strbuf_release(&line); + fclose(fp); + + if (res) + return BISECT_FAILED; + + return bisect_auto_next(terms, NULL); +} + +static enum bisect_error bisect_skip(struct bisect_terms *terms, const char **argv, int argc) +{ + int i; + enum bisect_error res; + struct strvec argv_state = STRVEC_INIT; + + strvec_push(&argv_state, "skip"); + + for (i = 0; i < argc; i++) { + const char *dotdot = strstr(argv[i], ".."); + + if (dotdot) { + struct rev_info revs; + struct commit *commit; + + init_revisions(&revs, NULL); + setup_revisions(2, argv + i - 1, &revs, NULL); + + if (prepare_revision_walk(&revs)) + die(_("revision walk setup failed\n")); + while ((commit = get_revision(&revs)) != NULL) + strvec_push(&argv_state, + oid_to_hex(&commit->object.oid)); + + reset_revision_walk(); + } else { + strvec_push(&argv_state, argv[i]); + } + } + res = bisect_state(terms, argv_state.v, argv_state.nr); + + strvec_clear(&argv_state); + return res; +} + +static int bisect_visualize(struct bisect_terms *terms, const char **argv, int argc) +{ + struct strvec args = STRVEC_INIT; + int flags = RUN_COMMAND_NO_STDIN, res = 0; + struct strbuf sb = STRBUF_INIT; + + if (bisect_next_check(terms, NULL) != 0) + return BISECT_FAILED; + + if (!argc) { + if ((getenv("DISPLAY") || getenv("SESSIONNAME") || getenv("MSYSTEM") || + getenv("SECURITYSESSIONID")) && exists_in_PATH("gitk")) { + strvec_push(&args, "gitk"); + } else { + strvec_push(&args, "log"); + flags |= RUN_GIT_CMD; + } + } else { + if (argv[0][0] == '-') { + strvec_push(&args, "log"); + flags |= RUN_GIT_CMD; + } else if (strcmp(argv[0], "tig") && !starts_with(argv[0], "git")) + flags |= RUN_GIT_CMD; + + strvec_pushv(&args, argv); + } + + strvec_pushl(&args, "--bisect", "--", NULL); + + strbuf_read_file(&sb, git_path_bisect_names(), 0); + sq_dequote_to_strvec(sb.buf, &args); + strbuf_release(&sb); + + res = run_command_v_opt(args.v, flags); + strvec_clear(&args); + return res; +} + +static int bisect_run(struct bisect_terms *terms, const char **argv, int argc) +{ + int res = BISECT_OK; + struct strbuf command = STRBUF_INIT; + struct strvec args = STRVEC_INIT; + struct strvec run_args = STRVEC_INIT; + const char *new_state; + int temporary_stdout_fd, saved_stdout; + + if (bisect_next_check(terms, NULL)) + return BISECT_FAILED; + + if (argc) + sq_quote_argv(&command, argv); + else { + error(_("bisect run failed: no command provided.")); + return BISECT_FAILED; + } + + strvec_push(&run_args, command.buf); + + while (1) { + strvec_clear(&args); + + printf(_("running %s\n"), command.buf); + res = run_command_v_opt(run_args.v, RUN_USING_SHELL); + + if (res < 0 || 128 <= res) { + error(_("bisect run failed: exit code %d from" + " '%s' is < 0 or >= 128"), res, command.buf); + strbuf_release(&command); + return res; + } + + if (res == 125) + new_state = "skip"; + else if (!res) + new_state = terms->term_good; + else + new_state = terms->term_bad; + + temporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_WRONLY | O_TRUNC, 0666); + + if (temporary_stdout_fd < 0) + return error_errno(_("cannot open file '%s' for writing"), git_path_bisect_run()); + + fflush(stdout); + saved_stdout = dup(1); + dup2(temporary_stdout_fd, 1); + + res = bisect_state(terms, &new_state, 1); + + fflush(stdout); + dup2(saved_stdout, 1); + close(saved_stdout); + close(temporary_stdout_fd); + + print_file_to_stdout(git_path_bisect_run()); + + if (res == BISECT_ONLY_SKIPPED_LEFT) + error(_("bisect run cannot continue any more")); + else if (res == BISECT_INTERNAL_SUCCESS_MERGE_BASE) { + printf(_("bisect run success")); + res = BISECT_OK; + } else if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) { + printf(_("bisect found first bad commit")); + res = BISECT_OK; + } else if (res) { + error(_("bisect run failed: 'git bisect--helper --bisect-state" + " %s' exited with error code %d"), args.v[0], res); + } else { + continue; + } + + strbuf_release(&command); + strvec_clear(&args); + strvec_clear(&run_args); + return res; + } } int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { enum { - NEXT_ALL = 1, - WRITE_TERMS, - BISECT_CLEAN_STATE, - CHECK_EXPECTED_REVS, - BISECT_RESET, - BISECT_WRITE, - CHECK_AND_SET_TERMS, + BISECT_RESET = 1, BISECT_NEXT_CHECK, BISECT_TERMS, - BISECT_START + BISECT_START, + BISECT_AUTOSTART, + BISECT_NEXT, + BISECT_STATE, + BISECT_LOG, + BISECT_REPLAY, + BISECT_SKIP, + BISECT_VISUALIZE, + BISECT_RUN, } cmdmode = 0; - int no_checkout = 0, res = 0, nolog = 0; + int res = 0, nolog = 0; struct option options[] = { - OPT_CMDMODE(0, "next-all", &cmdmode, - N_("perform 'git bisect next'"), NEXT_ALL), - OPT_CMDMODE(0, "write-terms", &cmdmode, - N_("write the terms to .git/BISECT_TERMS"), WRITE_TERMS), - OPT_CMDMODE(0, "bisect-clean-state", &cmdmode, - N_("cleanup the bisection state"), BISECT_CLEAN_STATE), - OPT_CMDMODE(0, "check-expected-revs", &cmdmode, - N_("check for expected revs"), CHECK_EXPECTED_REVS), OPT_CMDMODE(0, "bisect-reset", &cmdmode, N_("reset the bisection state"), BISECT_RESET), - OPT_CMDMODE(0, "bisect-write", &cmdmode, - N_("write out the bisection state in BISECT_LOG"), BISECT_WRITE), - OPT_CMDMODE(0, "check-and-set-terms", &cmdmode, - N_("check and set terms in a bisection state"), CHECK_AND_SET_TERMS), OPT_CMDMODE(0, "bisect-next-check", &cmdmode, N_("check whether bad or good terms exist"), BISECT_NEXT_CHECK), OPT_CMDMODE(0, "bisect-terms", &cmdmode, N_("print out the bisect terms"), BISECT_TERMS), OPT_CMDMODE(0, "bisect-start", &cmdmode, N_("start the bisect session"), BISECT_START), - OPT_BOOL(0, "no-checkout", &no_checkout, - N_("update BISECT_HEAD instead of checking out the current commit")), + OPT_CMDMODE(0, "bisect-next", &cmdmode, + N_("find the next bisection commit"), BISECT_NEXT), + OPT_CMDMODE(0, "bisect-state", &cmdmode, + N_("mark the state of ref (or refs)"), BISECT_STATE), + OPT_CMDMODE(0, "bisect-log", &cmdmode, + N_("list the bisection steps so far"), BISECT_LOG), + OPT_CMDMODE(0, "bisect-replay", &cmdmode, + N_("replay the bisection process from the given file"), BISECT_REPLAY), + OPT_CMDMODE(0, "bisect-skip", &cmdmode, + N_("skip some commits for checkout"), BISECT_SKIP), + OPT_CMDMODE(0, "bisect-visualize", &cmdmode, + N_("visualize the bisection"), BISECT_VISUALIZE), + OPT_CMDMODE(0, "bisect-run", &cmdmode, + N_("use ... to automatically bisect."), BISECT_RUN), OPT_BOOL(0, "no-log", &nolog, N_("no log for BISECT_WRITE")), OPT_END() @@ -663,40 +1224,10 @@ usage_with_options(git_bisect_helper_usage, options); switch (cmdmode) { - case NEXT_ALL: - return bisect_next_all(the_repository, prefix, no_checkout); - case WRITE_TERMS: - if (argc != 2) - return error(_("--write-terms requires two arguments")); - return write_terms(argv[0], argv[1]); - case BISECT_CLEAN_STATE: - if (argc != 0) - return error(_("--bisect-clean-state requires no arguments")); - return bisect_clean_state(); - case CHECK_EXPECTED_REVS: - check_expected_revs(argv, argc); - return 0; case BISECT_RESET: if (argc > 1) return error(_("--bisect-reset requires either no argument or a commit")); - return !!bisect_reset(argc ? argv[0] : NULL); - case BISECT_WRITE: - if (argc != 4 && argc != 5) - return error(_("--bisect-write requires either 4 or 5 arguments")); - set_terms(&terms, argv[3], argv[2]); - res = bisect_write(argv[0], argv[1], &terms, nolog); - break; - case CHECK_AND_SET_TERMS: - if (argc != 3) - return error(_("--check-and-set-terms requires 3 arguments")); - set_terms(&terms, argv[2], argv[1]); - res = check_and_set_terms(&terms, argv[0]); - break; - case BISECT_NEXT_CHECK: - if (argc != 2 && argc != 3) - return error(_("--bisect-next-check requires 2 or 3 arguments")); - set_terms(&terms, argv[1], argv[0]); - res = bisect_next_check(&terms, argc == 3 ? argv[2] : NULL); + res = bisect_reset(argc ? argv[0] : NULL); break; case BISECT_TERMS: if (argc > 1) @@ -705,11 +1236,56 @@ break; case BISECT_START: set_terms(&terms, "bad", "good"); - res = bisect_start(&terms, no_checkout, argv, argc); + res = bisect_start(&terms, argv, argc); + break; + case BISECT_NEXT: + if (argc) + return error(_("--bisect-next requires 0 arguments")); + get_terms(&terms); + res = bisect_next(&terms, prefix); + break; + case BISECT_STATE: + set_terms(&terms, "bad", "good"); + get_terms(&terms); + res = bisect_state(&terms, argv, argc); + break; + case BISECT_LOG: + if (argc) + return error(_("--bisect-log requires 0 arguments")); + res = bisect_log(); + break; + case BISECT_REPLAY: + if (argc != 1) + return error(_("no logfile given")); + set_terms(&terms, "bad", "good"); + res = bisect_replay(&terms, argv[0]); + break; + case BISECT_SKIP: + set_terms(&terms, "bad", "good"); + get_terms(&terms); + res = bisect_skip(&terms, argv, argc); + break; + case BISECT_VISUALIZE: + get_terms(&terms); + res = bisect_visualize(&terms, argv, argc); + break; + case BISECT_RUN: + if (!argc) + return error(_("bisect run failed: no command provided.")); + get_terms(&terms); + res = bisect_run(&terms, argv, argc); break; default: - return error("BUG: unknown subcommand '%d'", cmdmode); + BUG("unknown subcommand %d", cmdmode); } free_terms(&terms); - return !!res; + + /* + * Handle early success + * From check_merge_bases > check_good_are_ancestors_of_bad > bisect_next_all + */ + if ((res == BISECT_INTERNAL_SUCCESS_MERGE_BASE) || (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND)) + res = BISECT_OK; + + return -res; } diff -Nru git-2.25.1/builtin/blame.c git-2.34.1/builtin/blame.c --- git-2.25.1/builtin/blame.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/blame.c 2021-11-24 19:29:21.000000000 +0000 @@ -27,6 +27,7 @@ #include "object-store.h" #include "blame.h" #include "refs.h" +#include "tag.h" static char blame_usage[] = N_("git blame [] [] [] [--] "); @@ -100,6 +101,16 @@ struct strbuf summary; }; +#define COMMIT_INFO_INIT { \ + .author = STRBUF_INIT, \ + .author_mail = STRBUF_INIT, \ + .author_tz = STRBUF_INIT, \ + .committer = STRBUF_INIT, \ + .committer_mail = STRBUF_INIT, \ + .committer_tz = STRBUF_INIT, \ + .summary = STRBUF_INIT, \ +} + /* * Parse author/committer line in the commit object buffer */ @@ -159,18 +170,6 @@ strbuf_add(name, namebuf, namelen); } -static void commit_info_init(struct commit_info *ci) -{ - - strbuf_init(&ci->author, 0); - strbuf_init(&ci->author_mail, 0); - strbuf_init(&ci->author_tz, 0); - strbuf_init(&ci->committer, 0); - strbuf_init(&ci->committer_mail, 0); - strbuf_init(&ci->committer_tz, 0); - strbuf_init(&ci->summary, 0); -} - static void commit_info_destroy(struct commit_info *ci) { @@ -191,8 +190,6 @@ const char *subject, *encoding; const char *message; - commit_info_init(ret); - encoding = get_log_output_encoding(); message = logmsg_reencode(commit, NULL, encoding); get_ac_line(message, "\nauthor ", @@ -245,7 +242,7 @@ */ static int emit_one_suspect_detail(struct blame_origin *suspect, int repeat) { - struct commit_info ci; + struct commit_info ci = COMMIT_INFO_INIT; if (!repeat && (suspect->commit->object.flags & METAINFO_SHOWN)) return 0; @@ -424,13 +421,11 @@ parse_color_fields("blue,12 month ago,white,1 month ago,red"); } -static void determine_line_heat(struct blame_entry *ent, const char **dest_color) +static void determine_line_heat(struct commit_info *ci, const char **dest_color) { int i = 0; - struct commit_info ci; - get_commit_info(ent->suspect->commit, &ci, 1); - while (i < colorfield_nr && ci.author_time > colorfield[i].hop) + while (i < colorfield_nr && ci->author_time > colorfield[i].hop) i++; *dest_color = colorfield[i].col; @@ -441,7 +436,7 @@ int cnt; const char *cp; struct blame_origin *suspect = ent->suspect; - struct commit_info ci; + struct commit_info ci = COMMIT_INFO_INIT; char hex[GIT_MAX_HEXSZ + 1]; int show_raw_time = !!(opt & OUTPUT_RAW_TIMESTAMP); const char *default_color = NULL, *color = NULL, *reset = NULL; @@ -452,7 +447,7 @@ cp = blame_nth_line(sb, ent->lno); if (opt & OUTPUT_SHOW_AGE_WITH_COLOR) { - determine_line_heat(ent, &default_color); + determine_line_heat(&ci, &default_color); color = default_color; reset = GIT_COLOR_RESET; } @@ -631,7 +626,7 @@ if (longest_file < num) longest_file = num; if (!(suspect->commit->object.flags & METAINFO_SHOWN)) { - struct commit_info ci; + struct commit_info ci = COMMIT_INFO_INIT; suspect->commit->object.flags |= METAINFO_SHOWN; get_commit_info(suspect->commit, &ci, 1); if (*option & OUTPUT_SHOW_EMAIL) @@ -803,6 +798,28 @@ return OBJ_NONE < oid_object_info(the_repository, &oid, NULL); } +static int peel_to_commit_oid(struct object_id *oid_ret, void *cbdata) +{ + struct repository *r = ((struct blame_scoreboard *)cbdata)->repo; + struct object_id oid; + + oidcpy(&oid, oid_ret); + while (1) { + struct object *obj; + int kind = oid_object_info(r, &oid, NULL); + if (kind == OBJ_COMMIT) { + oidcpy(oid_ret, &oid); + return 0; + } + if (kind != OBJ_TAG) + return -1; + obj = deref_tag(r, parse_object(r, &oid), NULL, 0); + if (!obj) + return -1; + oidcpy(&oid, &obj->oid); + } +} + static void build_ignorelist(struct blame_scoreboard *sb, struct string_list *ignore_revs_file_list, struct string_list *ignore_rev_list) @@ -815,10 +832,12 @@ if (!strcmp(i->string, "")) oidset_clear(&sb->ignore_list); else - oidset_parse_file(&sb->ignore_list, i->string); + oidset_parse_file_carefully(&sb->ignore_list, i->string, + peel_to_commit_oid, sb); } for_each_string_list_item(i, ignore_rev_list) { - if (get_oid_committish(i->string, &oid)) + if (get_oid_committish(i->string, &oid) || + peel_to_commit_oid(&oid, sb)) die(_("cannot find revision %s to ignore"), i->string); oidset_insert(&sb->ignore_list, &oid); } @@ -841,32 +860,33 @@ const char *revs_file = NULL; const char *contents_from = NULL; const struct option options[] = { - OPT_BOOL(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")), - OPT_BOOL('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")), - OPT_BOOL(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")), - OPT_BOOL(0, "show-stats", &show_stats, N_("Show work cost statistics")), - OPT_BOOL(0, "progress", &show_progress, N_("Force progress reporting")), - OPT_BIT(0, "score-debug", &output_option, N_("Show output score for blame entries"), OUTPUT_SHOW_SCORE), - OPT_BIT('f', "show-name", &output_option, N_("Show original filename (Default: auto)"), OUTPUT_SHOW_NAME), - OPT_BIT('n', "show-number", &output_option, N_("Show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER), - OPT_BIT('p', "porcelain", &output_option, N_("Show in a format designed for machine consumption"), OUTPUT_PORCELAIN), - OPT_BIT(0, "line-porcelain", &output_option, N_("Show porcelain format with per-line commit information"), OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN), - OPT_BIT('c', NULL, &output_option, N_("Use the same output mode as git-annotate (Default: off)"), OUTPUT_ANNOTATE_COMPAT), - OPT_BIT('t', NULL, &output_option, N_("Show raw timestamp (Default: off)"), OUTPUT_RAW_TIMESTAMP), - OPT_BIT('l', NULL, &output_option, N_("Show long commit SHA1 (Default: off)"), OUTPUT_LONG_OBJECT_NAME), - OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR), - OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL), - OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE), - OPT_STRING_LIST(0, "ignore-rev", &ignore_rev_list, N_("rev"), N_("Ignore when blaming")), - OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("Ignore revisions from ")), + OPT_BOOL(0, "incremental", &incremental, N_("show blame entries as we find them, incrementally")), + OPT_BOOL('b', NULL, &blank_boundary, N_("do not show object names of boundary commits (Default: off)")), + OPT_BOOL(0, "root", &show_root, N_("do not treat root commits as boundaries (Default: off)")), + OPT_BOOL(0, "show-stats", &show_stats, N_("show work cost statistics")), + OPT_BOOL(0, "progress", &show_progress, N_("force progress reporting")), + OPT_BIT(0, "score-debug", &output_option, N_("show output score for blame entries"), OUTPUT_SHOW_SCORE), + OPT_BIT('f', "show-name", &output_option, N_("show original filename (Default: auto)"), OUTPUT_SHOW_NAME), + OPT_BIT('n', "show-number", &output_option, N_("show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER), + OPT_BIT('p', "porcelain", &output_option, N_("show in a format designed for machine consumption"), OUTPUT_PORCELAIN), + OPT_BIT(0, "line-porcelain", &output_option, N_("show porcelain format with per-line commit information"), OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN), + OPT_BIT('c', NULL, &output_option, N_("use the same output mode as git-annotate (Default: off)"), OUTPUT_ANNOTATE_COMPAT), + OPT_BIT('t', NULL, &output_option, N_("show raw timestamp (Default: off)"), OUTPUT_RAW_TIMESTAMP), + OPT_BIT('l', NULL, &output_option, N_("show long commit SHA1 (Default: off)"), OUTPUT_LONG_OBJECT_NAME), + OPT_BIT('s', NULL, &output_option, N_("suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR), + OPT_BIT('e', "show-email", &output_option, N_("show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL), + OPT_BIT('w', NULL, &xdl_opts, N_("ignore whitespace differences"), XDF_IGNORE_WHITESPACE), + OPT_STRING_LIST(0, "ignore-rev", &ignore_rev_list, N_("rev"), N_("ignore when blaming")), + OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("ignore revisions from ")), OPT_BIT(0, "color-lines", &output_option, N_("color redundant metadata from previous line differently"), OUTPUT_COLOR_LINE), OPT_BIT(0, "color-by-age", &output_option, N_("color lines by age"), OUTPUT_SHOW_AGE_WITH_COLOR), - OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL), - OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from instead of calling git-rev-list")), - OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use 's contents as the final image")), - { OPTION_CALLBACK, 'C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback }, - { OPTION_CALLBACK, 'M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback }, - OPT_STRING_LIST('L', NULL, &range_list, N_("n,m"), N_("Process only line range n,m, counting from 1")), + OPT_BIT(0, "minimal", &xdl_opts, N_("spend extra cycles to find better match"), XDF_NEED_MINIMAL), + OPT_STRING('S', NULL, &revs_file, N_("file"), N_("use revisions from instead of calling git-rev-list")), + OPT_STRING(0, "contents", &contents_from, N_("file"), N_("use 's contents as the final image")), + OPT_CALLBACK_F('C', NULL, &opt, N_("score"), N_("find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback), + OPT_CALLBACK_F('M', NULL, &opt, N_("score"), N_("find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback), + OPT_STRING_LIST('L', NULL, &range_list, N_("range"), + N_("process only line range , or function :")), OPT__ABBREV(&abbrev), OPT_END() }; @@ -893,6 +913,9 @@ PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0); for (;;) { switch (parse_options_step(&ctx, options, blame_opt_usage)) { + case PARSE_OPT_NON_OPTION: + case PARSE_OPT_UNKNOWN: + break; case PARSE_OPT_HELP: case PARSE_OPT_ERROR: exit(129); @@ -1057,10 +1080,19 @@ sb.contents_from = contents_from; sb.reverse = reverse; sb.repo = the_repository; + sb.path = path; build_ignorelist(&sb, &ignore_revs_file_list, &ignore_rev_list); string_list_clear(&ignore_revs_file_list, 0); string_list_clear(&ignore_rev_list, 0); - setup_scoreboard(&sb, path, &o); + setup_scoreboard(&sb, &o); + + /* + * Changed-path Bloom filters are disabled when looking + * for copies. + */ + if (!(opt & PICKAXE_BLAME_COPY)) + setup_blame_bloom_data(&sb); + lno = sb.num_lines; if (lno && !range_list.nr) @@ -1078,7 +1110,7 @@ if ((!lno && (top || bottom)) || lno < bottom) die(Q_("file %s has only %lu line", "file %s has only %lu lines", - lno), path, lno); + lno), sb.path, lno); if (bottom < 1) bottom = 1; if (top < 1 || lno < top) @@ -1103,7 +1135,6 @@ string_list_clear(&range_list, 0); sb.ent = NULL; - sb.path = path; if (blame_move_score) sb.move_score = blame_move_score; @@ -1117,7 +1148,7 @@ sb.xdl_opts = xdl_opts; sb.no_whole_file_rename = no_whole_file_rename; - read_mailmap(&mailmap, NULL); + read_mailmap(&mailmap); sb.found_guilty_entry = &found_guilty_entry; sb.found_guilty_entry_data = π @@ -1164,5 +1195,7 @@ printf("num get patch: %d\n", sb.num_get_patch); printf("num commits: %d\n", sb.num_commits); } + + cleanup_scoreboard(&sb); return 0; } diff -Nru git-2.25.1/builtin/branch.c git-2.34.1/builtin/branch.c --- git-2.25.1/builtin/branch.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/branch.c 2021-11-24 19:29:21.000000000 +0000 @@ -26,7 +26,7 @@ #include "commit-reach.h" static const char * const builtin_branch_usage[] = { - N_("git branch [] [-r | -a] [--merged | --no-merged]"), + N_("git branch [] [-r | -a] [--merged] [--no-merged]"), N_("git branch [] [-l] [-f] []"), N_("git branch [] [-r] (-d | -D) ..."), N_("git branch [] (-m | -M) [] "), @@ -168,7 +168,7 @@ int kinds, int force) { struct commit *rev = lookup_commit_reference(the_repository, oid); - if (!rev) { + if (!force && !rev) { error(_("Couldn't look up commit object for '%s'"), refname); return -1; } @@ -202,6 +202,9 @@ int remote_branch = 0; struct strbuf bname = STRBUF_INIT; unsigned allowed_interpret; + struct string_list refs_to_delete = STRING_LIST_INIT_DUP; + struct string_list_item *item; + int branch_name_pos; switch (kinds) { case FILTER_REFS_REMOTES: @@ -219,6 +222,7 @@ default: die(_("cannot use -a with -d")); } + branch_name_pos = strcspn(fmt, "%"); if (!force) { head_rev = lookup_commit_reference(the_repository, &head_oid); @@ -265,30 +269,35 @@ goto next; } - if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : &oid, - REF_NO_DEREF)) { - error(remote_branch - ? _("Error deleting remote-tracking branch '%s'") - : _("Error deleting branch '%s'"), - bname.buf); - ret = 1; - goto next; - } - if (!quiet) { - printf(remote_branch - ? _("Deleted remote-tracking branch %s (was %s).\n") - : _("Deleted branch %s (was %s).\n"), - bname.buf, - (flags & REF_ISBROKEN) ? "broken" - : (flags & REF_ISSYMREF) ? target - : find_unique_abbrev(&oid, DEFAULT_ABBREV)); - } - delete_branch_config(bname.buf); + item = string_list_append(&refs_to_delete, name); + item->util = xstrdup((flags & REF_ISBROKEN) ? "broken" + : (flags & REF_ISSYMREF) ? target + : find_unique_abbrev(&oid, DEFAULT_ABBREV)); next: free(target); } + if (delete_refs(NULL, &refs_to_delete, REF_NO_DEREF)) + ret = 1; + + for_each_string_list_item(item, &refs_to_delete) { + char *describe_ref = item->util; + char *name = item->string; + if (!ref_exists(name)) { + char *refname = name + branch_name_pos; + if (!quiet) + printf(remote_branch + ? _("Deleted remote-tracking branch %s (was %s).\n") + : _("Deleted branch %s (was %s).\n"), + name + branch_name_pos, describe_ref); + + delete_branch_config(refname); + } + free(describe_ref); + } + string_list_clear(&refs_to_delete, 0); + free(name); strbuf_release(&bname); @@ -398,10 +407,13 @@ return strbuf_detach(&fmt, NULL); } -static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting, struct ref_format *format) +static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting, + struct ref_format *format, struct string_list *output) { int i; struct ref_array array; + struct strbuf out = STRBUF_INIT; + struct strbuf err = STRBUF_INIT; int maxwidth = 0; const char *remote_prefix = ""; char *to_free = NULL; @@ -416,7 +428,7 @@ memset(&array, 0, sizeof(array)); - filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN); + filter_refs(&array, filter, filter->kind); if (filter->verbose) maxwidth = calc_maxwidth(&array, strlen(remote_prefix)); @@ -431,22 +443,22 @@ ref_array_sort(sorting, &array); for (i = 0; i < array.nr; i++) { - struct strbuf out = STRBUF_INIT; - struct strbuf err = STRBUF_INIT; + strbuf_reset(&err); + strbuf_reset(&out); if (format_ref_array_item(array.items[i], format, &out, &err)) die("%s", err.buf); if (column_active(colopts)) { assert(!filter->verbose && "--column and --verbose are incompatible"); /* format to a string_list to let print_columns() do its job */ - string_list_append(&output, out.buf); + string_list_append(output, out.buf); } else { fwrite(out.buf, 1, out.len, stdout); putchar('\n'); } - strbuf_release(&err); - strbuf_release(&out); } + strbuf_release(&err); + strbuf_release(&out); ref_array_clear(&array); free(to_free); } @@ -468,7 +480,7 @@ static void reject_rebase_or_bisect_branch(const char *target) { - struct worktree **worktrees = get_worktrees(0); + struct worktree **worktrees = get_worktrees(); int i; for (i = 0; worktrees[i]; i++) { @@ -538,7 +550,9 @@ strbuf_addf(&logmsg, "Branch: renamed %s to %s", oldref.buf, newref.buf); - if (!copy && rename_ref(oldref.buf, newref.buf, logmsg.buf)) + if (!copy && + (!head || strcmp(oldname, head) || !is_null_oid(&head_oid)) && + rename_ref(oldref.buf, newref.buf, logmsg.buf)) die(_("Branch rename failed")); if (copy && copy_existing_ref(oldref.buf, newref.buf, logmsg.buf)) die(_("Branch copy failed")); @@ -653,10 +667,8 @@ OPT_NO_MERGED(&filter, N_("print only branches that are not merged")), OPT_COLUMN(0, "column", &colopts, N_("list branches in columns")), OPT_REF_SORT(sorting_tail), - { - OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), - N_("print only branches of the object"), 0, parse_opt_object_name - }, + OPT_CALLBACK(0, "points-at", &filter.points_at, N_("object"), + N_("print only branches of the object"), parse_opt_object_name), OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")), OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")), OPT_END(), @@ -690,12 +702,12 @@ !show_current && !unset_upstream && argc == 0) list = 1; - if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr || - filter.no_commit) + if (filter.with_commit || filter.no_commit || + filter.reachable_from || filter.unreachable_from || filter.points_at.nr) list = 1; if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current + - list + unset_upstream > 1) + list + edit_description + unset_upstream > 1) usage_with_options(builtin_branch_usage, options); if (filter.abbrev == -1) @@ -726,7 +738,7 @@ print_current_branch_name(); return 0; } else if (list) { - /* git branch --local also shows HEAD when it is detached */ + /* git branch --list also shows HEAD when it is detached */ if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached) filter.kind |= FILTER_REFS_DETACHED_HEAD; filter.name_patterns = argv; @@ -739,10 +751,13 @@ */ if (!sorting) sorting = ref_default_sorting(); - sorting->ignore_case = icase; - print_ref_list(&filter, sorting, &format); + ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase); + ref_sorting_set_sort_flags_all( + sorting, REF_SORTING_DETACHED_HEAD_FIRST, 1); + print_ref_list(&filter, sorting, &format, &output); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); + ref_sorting_release(sorting); return 0; } else if (edit_description) { const char *branch_name; @@ -831,10 +846,10 @@ die(_("Branch '%s' has no upstream information"), branch->name); strbuf_addf(&buf, "branch.%s.remote", branch->name); - git_config_set_multivar(buf.buf, NULL, NULL, 1); + git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE); strbuf_reset(&buf); strbuf_addf(&buf, "branch.%s.merge", branch->name); - git_config_set_multivar(buf.buf, NULL, NULL, 1); + git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE); strbuf_release(&buf); } else if (argc > 0 && argc <= 2) { if (filter.kind != FILTER_REFS_BRANCHES) diff -Nru git-2.25.1/builtin/bugreport.c git-2.34.1/builtin/bugreport.c --- git-2.25.1/builtin/bugreport.c 1970-01-01 00:00:00.000000000 +0000 +++ git-2.34.1/builtin/bugreport.c 2021-11-24 19:29:21.000000000 +0000 @@ -0,0 +1,166 @@ +#include "builtin.h" +#include "parse-options.h" +#include "strbuf.h" +#include "help.h" +#include "compat/compiler.h" +#include "hook.h" +#include "hook-list.h" + + +static void get_system_info(struct strbuf *sys_info) +{ + struct utsname uname_info; + char *shell = NULL; + + /* get git version from native cmd */ + strbuf_addstr(sys_info, _("git version:\n")); + get_version_info(sys_info, 1); + + /* system call for other version info */ + strbuf_addstr(sys_info, "uname: "); + if (uname(&uname_info)) + strbuf_addf(sys_info, _("uname() failed with error '%s' (%d)\n"), + strerror(errno), + errno); + else + strbuf_addf(sys_info, "%s %s %s %s\n", + uname_info.sysname, + uname_info.release, + uname_info.version, + uname_info.machine); + + strbuf_addstr(sys_info, _("compiler info: ")); + get_compiler_info(sys_info); + + strbuf_addstr(sys_info, _("libc info: ")); + get_libc_info(sys_info); + + shell = getenv("SHELL"); + strbuf_addf(sys_info, "$SHELL (typically, interactive shell): %s\n", + shell ? shell : ""); +} + +static void get_populated_hooks(struct strbuf *hook_info, int nongit) +{ + const char **p; + + if (nongit) { + strbuf_addstr(hook_info, + _("not run from a git repository - no hooks to show\n")); + return; + } + + for (p = hook_name_list; *p; p++) { + const char *hook = *p; + + if (hook_exists(hook)) + strbuf_addf(hook_info, "%s\n", hook); + } +} + +static const char * const bugreport_usage[] = { + N_("git bugreport [-o|--output-directory ] [-s|--suffix ]"), + NULL +}; + +static int get_bug_template(struct strbuf *template) +{ + const char template_text[] = N_( +"Thank you for filling out a Git bug report!\n" +"Please answer the following questions to help us understand your issue.\n" +"\n" +"What did you do before the bug happened? (Steps to reproduce your issue)\n" +"\n" +"What did you expect to happen? (Expected behavior)\n" +"\n" +"What happened instead? (Actual behavior)\n" +"\n" +"What's different between what you expected and what actually happened?\n" +"\n" +"Anything else you want to add:\n" +"\n" +"Please review the rest of the bug report below.\n" +"You can delete any lines you don't wish to share.\n"); + + strbuf_addstr(template, _(template_text)); + return 0; +} + +static void get_header(struct strbuf *buf, const char *title) +{ + strbuf_addf(buf, "\n\n[%s]\n", title); +} + +int cmd_bugreport(int argc, const char **argv, const char *prefix) +{ + struct strbuf buffer = STRBUF_INIT; + struct strbuf report_path = STRBUF_INIT; + int report = -1; + time_t now = time(NULL); + struct tm tm; + char *option_output = NULL; + char *option_suffix = "%Y-%m-%d-%H%M"; + const char *user_relative_path = NULL; + char *prefixed_filename; + + const struct option bugreport_options[] = { + OPT_STRING('o', "output-directory", &option_output, N_("path"), + N_("specify a destination for the bugreport file")), + OPT_STRING('s', "suffix", &option_suffix, N_("format"), + N_("specify a strftime format suffix for the filename")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, bugreport_options, + bugreport_usage, 0); + + /* Prepare the path to put the result */ + prefixed_filename = prefix_filename(prefix, + option_output ? option_output : ""); + strbuf_addstr(&report_path, prefixed_filename); + strbuf_complete(&report_path, '/'); + + strbuf_addstr(&report_path, "git-bugreport-"); + strbuf_addftime(&report_path, option_suffix, localtime_r(&now, &tm), 0, 0); + strbuf_addstr(&report_path, ".txt"); + + switch (safe_create_leading_directories(report_path.buf)) { + case SCLD_OK: + case SCLD_EXISTS: + break; + default: + die(_("could not create leading directories for '%s'"), + report_path.buf); + } + + /* Prepare the report contents */ + get_bug_template(&buffer); + + get_header(&buffer, _("System Info")); + get_system_info(&buffer); + + get_header(&buffer, _("Enabled Hooks")); + get_populated_hooks(&buffer, !startup_info->have_repository); + + /* fopen doesn't offer us an O_EXCL alternative, except with glibc. */ + report = xopen(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666); + + if (write_in_full(report, buffer.buf, buffer.len) < 0) + die_errno(_("unable to write to %s"), report_path.buf); + + close(report); + + /* + * We want to print the path relative to the user, but we still need the + * path relative to us to give to the editor. + */ + if (!(prefix && skip_prefix(report_path.buf, prefix, &user_relative_path))) + user_relative_path = report_path.buf; + fprintf(stderr, _("Created new report at '%s'.\n"), + user_relative_path); + + free(prefixed_filename); + UNLEAK(buffer); + UNLEAK(report_path); + return !!launch_editor(report_path.buf, NULL, NULL); +} diff -Nru git-2.25.1/builtin/bundle.c git-2.34.1/builtin/bundle.c --- git-2.25.1/builtin/bundle.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/bundle.c 2021-11-24 19:29:21.000000000 +0000 @@ -1,5 +1,5 @@ #include "builtin.h" -#include "argv-array.h" +#include "strvec.h" #include "parse-options.h" #include "cache.h" #include "bundle.h" @@ -39,14 +39,12 @@ NULL }; -static int verbose; - static int parse_options_cmd_bundle(int argc, const char **argv, const char* prefix, const char * const usagestr[], const struct option options[], - const char **bundle_file) { + char **bundle_file) { int newargc; newargc = parse_options(argc, argv, NULL, options, usagestr, PARSE_OPT_STOP_AT_NON_OPTION); @@ -59,8 +57,9 @@ static int cmd_bundle_create(int argc, const char **argv, const char *prefix) { int all_progress_implied = 0; int progress = isatty(STDERR_FILENO); - struct argv_array pack_opts; - + struct strvec pack_opts; + int version = -1; + int ret; struct option options[] = { OPT_SET_INT('q', "quiet", &progress, N_("do not show progress meter"), 0), @@ -71,101 +70,131 @@ OPT_BOOL(0, "all-progress-implied", &all_progress_implied, N_("similar to --all-progress when progress meter is shown")), + OPT_INTEGER(0, "version", &version, + N_("specify bundle format version")), OPT_END() }; - const char* bundle_file; + char *bundle_file; argc = parse_options_cmd_bundle(argc, argv, prefix, builtin_bundle_create_usage, options, &bundle_file); /* bundle internals use argv[1] as further parameters */ - argv_array_init(&pack_opts); + strvec_init(&pack_opts); if (progress == 0) - argv_array_push(&pack_opts, "--quiet"); + strvec_push(&pack_opts, "--quiet"); else if (progress == 1) - argv_array_push(&pack_opts, "--progress"); + strvec_push(&pack_opts, "--progress"); else if (progress == 2) - argv_array_push(&pack_opts, "--all-progress"); + strvec_push(&pack_opts, "--all-progress"); if (progress && all_progress_implied) - argv_array_push(&pack_opts, "--all-progress-implied"); + strvec_push(&pack_opts, "--all-progress-implied"); if (!startup_info->have_repository) die(_("Need a repository to create a bundle.")); - return !!create_bundle(the_repository, bundle_file, argc, argv, &pack_opts); + ret = !!create_bundle(the_repository, bundle_file, argc, argv, &pack_opts, version); + free(bundle_file); + return ret; } static int cmd_bundle_verify(int argc, const char **argv, const char *prefix) { - struct bundle_header header; + struct bundle_header header = BUNDLE_HEADER_INIT; int bundle_fd = -1; int quiet = 0; - + int ret; struct option options[] = { OPT_BOOL('q', "quiet", &quiet, N_("do not show bundle details")), OPT_END() }; - const char* bundle_file; + char *bundle_file; argc = parse_options_cmd_bundle(argc, argv, prefix, builtin_bundle_verify_usage, options, &bundle_file); /* bundle internals use argv[1] as further parameters */ - memset(&header, 0, sizeof(header)); - if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) - return 1; + if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) { + ret = 1; + goto cleanup; + } close(bundle_fd); - if (verify_bundle(the_repository, &header, !quiet)) - return 1; + if (verify_bundle(the_repository, &header, !quiet)) { + ret = 1; + goto cleanup; + } + fprintf(stderr, _("%s is okay\n"), bundle_file); - return 0; + ret = 0; +cleanup: + free(bundle_file); + bundle_header_release(&header); + return ret; } static int cmd_bundle_list_heads(int argc, const char **argv, const char *prefix) { - struct bundle_header header; + struct bundle_header header = BUNDLE_HEADER_INIT; int bundle_fd = -1; - + int ret; struct option options[] = { OPT_END() }; - const char* bundle_file; + char *bundle_file; argc = parse_options_cmd_bundle(argc, argv, prefix, builtin_bundle_list_heads_usage, options, &bundle_file); /* bundle internals use argv[1] as further parameters */ - memset(&header, 0, sizeof(header)); - if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) - return 1; + if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) { + ret = 1; + goto cleanup; + } close(bundle_fd); - return !!list_bundle_refs(&header, argc, argv); + ret = !!list_bundle_refs(&header, argc, argv); +cleanup: + free(bundle_file); + bundle_header_release(&header); + return ret; } static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix) { - struct bundle_header header; + struct bundle_header header = BUNDLE_HEADER_INIT; int bundle_fd = -1; + int ret; + int progress = isatty(2); struct option options[] = { + OPT_BOOL(0, "progress", &progress, + N_("show progress meter")), OPT_END() }; - const char* bundle_file; + char *bundle_file; + struct strvec extra_index_pack_args = STRVEC_INIT; argc = parse_options_cmd_bundle(argc, argv, prefix, builtin_bundle_unbundle_usage, options, &bundle_file); /* bundle internals use argv[1] as further parameters */ - memset(&header, 0, sizeof(header)); - if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) - return 1; + if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) { + ret = 1; + goto cleanup; + } if (!startup_info->have_repository) die(_("Need a repository to unbundle.")); - return !!unbundle(the_repository, &header, bundle_fd, 0) || + if (progress) + strvec_pushl(&extra_index_pack_args, "-v", "--progress-title", + _("Unbundling objects"), NULL); + ret = !!unbundle(the_repository, &header, bundle_fd, + &extra_index_pack_args) || list_bundle_refs(&header, argc, argv); + bundle_header_release(&header); +cleanup: + free(bundle_file); + return ret; } int cmd_bundle(int argc, const char **argv, const char *prefix) { struct option options[] = { - OPT__VERBOSE(&verbose, N_("be verbose; must be placed before a subcommand")), OPT_END() }; int result; diff -Nru git-2.25.1/builtin/cat-file.c git-2.34.1/builtin/cat-file.c --- git-2.25.1/builtin/cat-file.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/cat-file.c 2021-11-24 19:29:21.000000000 +0000 @@ -12,7 +12,7 @@ #include "userdiff.h" #include "streaming.h" #include "tree-walk.h" -#include "sha1-array.h" +#include "oid-array.h" #include "packfile.h" #include "object-store.h" #include "promisor-remote.h" @@ -42,7 +42,10 @@ oid_to_hex(oid), path); if ((type == OBJ_BLOB) && S_ISREG(mode)) { struct strbuf strbuf = STRBUF_INIT; - if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf)) { + struct checkout_metadata meta; + + init_checkout_metadata(&meta, NULL, NULL, oid); + if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf, &meta)) { free(*buf); *size = strbuf.len; *buf = strbuf_detach(&strbuf, NULL); @@ -262,7 +265,7 @@ strbuf_addstr(sb, data->rest); } else if (is_atom("deltabase", atom, len)) { if (data->mark_query) - data->info.delta_base_sha1 = data->delta_base_oid.hash; + data->info.delta_base_oid = &data->delta_base_oid; else strbuf_addstr(sb, oid_to_hex(&data->delta_base_oid)); @@ -352,18 +355,34 @@ } } +/* + * If "pack" is non-NULL, then "offset" is the byte offset within the pack from + * which the object may be accessed (though note that we may also rely on + * data->oid, too). If "pack" is NULL, then offset is ignored. + */ static void batch_object_write(const char *obj_name, struct strbuf *scratch, struct batch_options *opt, - struct expand_data *data) -{ - if (!data->skip_object_info && - oid_object_info_extended(the_repository, &data->oid, &data->info, - OBJECT_INFO_LOOKUP_REPLACE) < 0) { - printf("%s missing\n", - obj_name ? obj_name : oid_to_hex(&data->oid)); - fflush(stdout); - return; + struct expand_data *data, + struct packed_git *pack, + off_t offset) +{ + if (!data->skip_object_info) { + int ret; + + if (pack) + ret = packed_object_info(the_repository, pack, offset, + &data->info); + else + ret = oid_object_info_extended(the_repository, + &data->oid, &data->info, + OBJECT_INFO_LOOKUP_REPLACE); + if (ret < 0) { + printf("%s missing\n", + obj_name ? obj_name : oid_to_hex(&data->oid)); + fflush(stdout); + return; + } } strbuf_reset(scratch); @@ -425,7 +444,7 @@ return; } - batch_object_write(obj_name, scratch, opt, data); + batch_object_write(obj_name, scratch, opt, data, NULL, 0); } struct object_cb_data { @@ -439,7 +458,8 @@ { struct object_cb_data *data = vdata; oidcpy(&data->expand->oid, oid); - batch_object_write(NULL, data->scratch, data->opt, data->expand); + batch_object_write(NULL, data->scratch, data->opt, data->expand, + NULL, 0); return 0; } @@ -460,21 +480,26 @@ return 0; } -static int batch_unordered_object(const struct object_id *oid, void *vdata) +static int batch_unordered_object(const struct object_id *oid, + struct packed_git *pack, off_t offset, + void *vdata) { struct object_cb_data *data = vdata; if (oidset_insert(data->seen, oid)) return 0; - return batch_object_cb(oid, data); + oidcpy(&data->expand->oid, oid); + batch_object_write(NULL, data->scratch, data->opt, data->expand, + pack, offset); + return 0; } static int batch_unordered_loose(const struct object_id *oid, const char *path, void *data) { - return batch_unordered_object(oid, data); + return batch_unordered_object(oid, NULL, 0, data); } static int batch_unordered_packed(const struct object_id *oid, @@ -482,7 +507,9 @@ uint32_t pos, void *data) { - return batch_unordered_object(oid, data); + return batch_unordered_object(oid, pack, + nth_packed_object_offset(pack, pos), + data); } static int batch_objects(struct batch_options *opt) @@ -509,12 +536,6 @@ if (opt->cmdmode) data.split_on_whitespace = 1; - if (opt->all_objects) { - struct object_info empty = OBJECT_INFO_INIT; - if (!memcmp(&data.info, &empty, sizeof(empty))) - data.skip_object_info = 1; - } - /* * If we are printing out the object, then always fill in the type, * since we will want to decide whether or not to stream. @@ -524,10 +545,16 @@ if (opt->all_objects) { struct object_cb_data cb; + struct object_info empty = OBJECT_INFO_INIT; + + if (!memcmp(&data.info, &empty, sizeof(empty))) + data.skip_object_info = 1; if (has_promisor_remote()) warning("This repository uses promisor remotes. Some objects may not be loaded."); + read_replace_refs = 0; + cb.opt = opt; cb.expand = &data; cb.scratch = &output; @@ -593,7 +620,7 @@ static const char * const cat_file_usage[] = { N_("git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | | --textconv | --filters) [--path=] "), - N_("git cat-file (--batch | --batch-check) [--follow-symlinks] [--textconv | --filters]"), + N_("git cat-file (--batch[=] | --batch-check[=]) [--follow-symlinks] [--textconv | --filters]"), NULL }; @@ -647,14 +674,14 @@ OPT_BOOL(0, "allow-unknown-type", &unknown_type, N_("allow -s and -t to work with broken/corrupt objects")), OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")), - { OPTION_CALLBACK, 0, "batch", &batch, "format", + OPT_CALLBACK_F(0, "batch", &batch, "format", N_("show info and content of objects fed from the standard input"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, - batch_option_callback }, - { OPTION_CALLBACK, 0, "batch-check", &batch, "format", + batch_option_callback), + OPT_CALLBACK_F(0, "batch-check", &batch, "format", N_("show info about objects fed from the standard input"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, - batch_option_callback }, + batch_option_callback), OPT_BOOL(0, "follow-symlinks", &batch.follow_symlinks, N_("follow in-tree symlinks (used with --batch or --batch-check)")), OPT_BOOL(0, "batch-all-objects", &batch.all_objects, diff -Nru git-2.25.1/builtin/check-ignore.c git-2.34.1/builtin/check-ignore.c --- git-2.25.1/builtin/check-ignore.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/check-ignore.c 2021-11-24 19:29:21.000000000 +0000 @@ -100,7 +100,8 @@ * should not be ignored, in order to be consistent with * 'git status', 'git add' etc. */ - seen = find_pathspecs_matching_against_index(&pathspec, &the_index); + seen = find_pathspecs_matching_against_index(&pathspec, &the_index, + PS_HEED_SKIP_WORKTREE); for (i = 0; i < pathspec.nr; i++) { full_path = pathspec.items[i].match; pattern = NULL; @@ -108,6 +109,9 @@ int dtype = DT_UNKNOWN; pattern = last_matching_pattern(dir, &the_index, full_path, &dtype); + if (!verbose && pattern && + pattern->flags & PATTERN_FLAG_NEGATIVE) + pattern = NULL; } if (!quiet && (pattern || show_non_matching)) output_pattern(pathspec.items[i].original, pattern); @@ -115,6 +119,7 @@ num_ignored++; } free(seen); + clear_pathspec(&pathspec); return num_ignored; } @@ -148,7 +153,7 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix) { int num_ignored; - struct dir_struct dir; + struct dir_struct dir = DIR_INIT; git_config(git_default_config, NULL); @@ -177,7 +182,6 @@ if (!no_index && read_cache() < 0) die(_("index file corrupt")); - memset(&dir, 0, sizeof(dir)); setup_standard_excludes(&dir); if (stdin_paths) { @@ -187,7 +191,7 @@ maybe_flush_or_die(stdout, "ignore to stdout"); } - clear_directory(&dir); + dir_clear(&dir); return !num_ignored; } diff -Nru git-2.25.1/builtin/check-mailmap.c git-2.34.1/builtin/check-mailmap.c --- git-2.25.1/builtin/check-mailmap.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/check-mailmap.c 2021-11-24 19:29:21.000000000 +0000 @@ -47,7 +47,7 @@ if (argc == 0 && !use_stdin) die(_("no contacts specified")); - read_mailmap(&mailmap, NULL); + read_mailmap(&mailmap); for (i = 0; i < argc; ++i) check_mailmap(&mailmap, argv[i]); diff -Nru git-2.25.1/builtin/checkout.c git-2.34.1/builtin/checkout.c --- git-2.25.1/builtin/checkout.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/checkout.c 2021-11-24 19:29:21.000000000 +0000 @@ -26,6 +26,8 @@ #include "unpack-trees.h" #include "wt-status.h" #include "xdiff-interface.h" +#include "entry.h" +#include "parallel-checkout.h" static const char * const checkout_usage[] = { N_("git checkout [] "), @@ -88,12 +90,25 @@ struct tree *source_tree; }; +struct branch_info { + const char *name; /* The short name used */ + const char *path; /* The full name of a real branch */ + struct commit *commit; /* The named commit */ + char *refname; /* The full name of the ref being checked out. */ + struct object_id oid; /* The object ID of the commit being checked out. */ + /* + * if not null the branch is detached because it's already + * checked out in this checkout + */ + char *checkout; +}; + static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit, int changed) { return run_hook_le(NULL, "post-checkout", - oid_to_hex(old_commit ? &old_commit->object.oid : &null_oid), - oid_to_hex(new_commit ? &new_commit->object.oid : &null_oid), + oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()), + oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()), changed ? "1" : "0", NULL); /* "new_commit" can be NULL when checking out from the index before a commit exists. */ @@ -101,7 +116,7 @@ } static int update_some(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, int stage, void *context) + const char *pathname, unsigned mode, void *context) { int len; struct cache_entry *ce; @@ -142,8 +157,8 @@ static int read_tree_some(struct tree *tree, const struct pathspec *pathspec) { - read_tree_recursive(the_repository, tree, "", 0, 0, - pathspec, update_some, NULL); + read_tree(the_repository, tree, + pathspec, update_some, NULL); /* update the index with the given tree's info * for all args, expanding wildcards, and exit @@ -216,7 +231,8 @@ return error(_("path '%s' does not have their version"), ce->name); } -static int checkout_merged(int pos, const struct checkout *state, int *nr_checkouts) +static int checkout_merged(int pos, const struct checkout *state, + int *nr_checkouts, struct mem_pool *ce_mem_pool) { struct cache_entry *ce = active_cache[pos]; const char *path = ce->name; @@ -226,6 +242,8 @@ mmbuffer_t result_buf; struct object_id threeway[3]; unsigned mode = 0; + struct ll_merge_options ll_opts; + int renormalize = 0; memset(threeway, 0, sizeof(threeway)); while (pos < active_nr) { @@ -246,13 +264,12 @@ read_mmblob(&ours, &threeway[1]); read_mmblob(&theirs, &threeway[2]); - /* - * NEEDSWORK: re-create conflicts from merges with - * merge.renormalize set, too - */ + memset(&ll_opts, 0, sizeof(ll_opts)); + git_config_get_bool("merge.renormalize", &renormalize); + ll_opts.renormalize = renormalize; status = ll_merge(&result_buf, path, &ancestor, "base", &ours, "ours", &theirs, "theirs", - state->istate, NULL); + state->istate, &ll_opts); free(ancestor.ptr); free(ours.ptr); free(theirs.ptr); @@ -276,11 +293,10 @@ if (write_object_file(result_buf.ptr, result_buf.size, blob_type, &oid)) die(_("Unable to add merge result for '%s'"), path); free(result_buf.ptr); - ce = make_transient_cache_entry(mode, &oid, path, 2); + ce = make_transient_cache_entry(mode, &oid, path, 2, ce_mem_pool); if (!ce) die(_("make_cache_entry failed for path '%s'"), path); status = checkout_entry(ce, state, NULL, nr_checkouts); - discard_cache_entry(ce); return status; } @@ -308,7 +324,7 @@ * If it comes from the tree-ish, we already know it * matches the pathspec and could just stamp * CE_MATCHED to it from update_some(). But we still - * need ps_matched and read_tree_recursive (and + * need ps_matched and read_tree (and * eventually tree_entry_interesting) cannot fill * ps_matched yet. Once it can, we can avoid calling * match_pathspec() for _all_ entries when @@ -337,18 +353,31 @@ } } -static int checkout_worktree(const struct checkout_opts *opts) +static int checkout_worktree(const struct checkout_opts *opts, + const struct branch_info *info) { struct checkout state = CHECKOUT_INIT; int nr_checkouts = 0, nr_unmerged = 0; int errs = 0; int pos; + int pc_workers, pc_threshold; + struct mem_pool ce_mem_pool; state.force = 1; state.refresh_cache = 1; state.istate = &the_index; + mem_pool_init(&ce_mem_pool, 0); + get_parallel_checkout_configs(&pc_workers, &pc_threshold); + init_checkout_metadata(&state.meta, info->refname, + info->commit ? &info->commit->object.oid : &info->oid, + NULL); + enable_delayed_checkout(&state); + + if (pc_workers > 1) + init_parallel_checkout(); + for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; if (ce->ce_flags & CE_MATCHED) { @@ -364,13 +393,18 @@ &nr_checkouts, opts->overlay_mode); else if (opts->merge) errs |= checkout_merged(pos, &state, - &nr_unmerged); + &nr_unmerged, + &ce_mem_pool); pos = skip_same_name(ce, pos) - 1; } } + if (pc_workers > 1) + errs |= run_parallel_checkout(&state, pc_workers, pc_threshold, + NULL, NULL); + 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); + errs |= finish_delayed_checkout(&state, &nr_checkouts, opts->show_progress); if (opts->count_checkout_paths) { if (nr_unmerged) @@ -396,7 +430,7 @@ } static int checkout_paths(const struct checkout_opts *opts, - const char *revision) + const struct branch_info *new_branch_info) { int pos; static char *ps_matched; @@ -452,6 +486,21 @@ if (opts->patch_mode) { const char *patch_mode; + const char *rev = new_branch_info->name; + char rev_oid[GIT_MAX_HEXSZ + 1]; + + /* + * Since rev can be in the form of `...` (which is not + * recognized by diff-index), we will always replace the name + * with the hex of the commit (whether it's in `...` form or + * not) for the run_add_interactive() machinery to work + * properly. However, there is special logic for the HEAD case + * so we mustn't replace that. Also, when we were given a + * tree-object, new_branch_info->commit would be NULL, but we + * do not have to do any replacement, either. + */ + if (rev && new_branch_info->commit && strcmp(rev, "HEAD")) + rev = oid_to_hex_r(rev_oid, &new_branch_info->commit->object.oid); if (opts->checkout_index && opts->checkout_worktree) patch_mode = "--patch=checkout"; @@ -462,7 +511,7 @@ else BUG("either flag must have been set, worktree=%d, index=%d", opts->checkout_worktree, opts->checkout_index); - return run_add_interactive(revision, patch_mode, &opts->pathspec); + return run_add_interactive(rev, patch_mode, &opts->pathspec); } repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); @@ -523,7 +572,7 @@ /* Now we are committed to check them out */ if (opts->checkout_worktree) - errs |= checkout_worktree(opts); + errs |= checkout_worktree(opts, new_branch_info); else remove_marked_cache_entries(&the_index, 1); @@ -567,6 +616,7 @@ diff_setup_done(&rev.diffopt); add_pending_object(&rev, head, NULL); run_diff_index(&rev, 0); + object_array_clear(&rev.pending); } static void describe_detached_head(const char *msg, struct commit *commit) @@ -586,7 +636,8 @@ } static int reset_tree(struct tree *tree, const struct checkout_opts *o, - int worktree, int *writeout_error) + int worktree, int *writeout_error, + struct branch_info *info) { struct unpack_trees_options opts; struct tree_desc tree_desc; @@ -595,12 +646,17 @@ opts.head_idx = -1; opts.update = worktree; opts.skip_unmerged = !worktree; - opts.reset = 1; + opts.reset = o->force ? UNPACK_RESET_OVERWRITE_UNTRACKED : + UNPACK_RESET_PROTECT_UNTRACKED; + opts.preserve_ignored = (!o->force && !o->overwrite_ignore); opts.merge = 1; opts.fn = oneway_merge; opts.verbose_update = o->show_progress; opts.src_index = &the_index; opts.dst_index = &the_index; + init_checkout_metadata(&opts.meta, info->refname, + info->commit ? &info->commit->object.oid : null_oid(), + NULL); parse_tree(tree); init_tree_desc(&tree_desc, tree->buffer, tree->size); switch (unpack_trees(1, &tree_desc, &opts)) { @@ -620,21 +676,17 @@ } } -struct branch_info { - const char *name; /* The short name used */ - const char *path; /* The full name of a real branch */ - struct commit *commit; /* The named commit */ - /* - * if not null the branch is detached because it's already - * checked out in this checkout - */ - char *checkout; -}; - static void setup_branch_path(struct branch_info *branch) { struct strbuf buf = STRBUF_INIT; + /* + * If this is a ref, resolve it; otherwise, look up the OID for our + * expression. Failure here is okay. + */ + if (!dwim_ref(branch->name, strlen(branch->name), &branch->oid, &branch->refname, 0)) + repo_get_oid_committish(the_repository, branch->name, &branch->oid); + strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL); if (strcmp(buf.buf, branch->name)) branch->name = xstrdup(buf.buf); @@ -663,7 +715,7 @@ } else new_tree = get_commit_tree(new_branch_info->commit); if (opts->discard_changes) { - ret = reset_tree(new_tree, opts, 1, writeout_error); + ret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info); if (ret) return ret; } else { @@ -692,11 +744,11 @@ topts.quiet = opts->merge && old_branch_info->commit; topts.verbose_update = opts->show_progress; topts.fn = twoway_merge; - if (opts->overwrite_ignore) { - topts.dir = xcalloc(1, sizeof(*topts.dir)); - topts.dir->flags |= DIR_SHOW_IGNORED; - setup_standard_excludes(topts.dir); - } + init_checkout_metadata(&topts.meta, new_branch_info->refname, + new_branch_info->commit ? + &new_branch_info->commit->object.oid : + &new_branch_info->oid, NULL); + topts.preserve_ignored = !opts->overwrite_ignore; tree = parse_tree_indirect(old_branch_info->commit ? &old_branch_info->commit->object.oid : the_hash_algo->empty_tree); @@ -749,20 +801,13 @@ */ add_files_to_cache(NULL, NULL, 0); - /* - * NEEDSWORK: carrying over local changes - * when branches have different end-of-line - * normalization (or clean+smudge rules) is - * a pain; plumb in an option to set - * o.renormalize? - */ init_merge_options(&o, the_repository); o.verbosity = 0; work = write_in_core_index_as_tree(the_repository); ret = reset_tree(new_tree, opts, 1, - writeout_error); + writeout_error, new_branch_info); if (ret) return ret; o.ancestor = old_branch_info->name; @@ -782,7 +827,7 @@ exit(128); ret = reset_tree(new_tree, opts, 0, - writeout_error); + writeout_error, new_branch_info); strbuf_release(&o.obuf); strbuf_release(&old_commit_shortname); if (ret) @@ -790,9 +835,6 @@ } } - if (!active_cache_tree) - active_cache_tree = cache_tree(); - if (!cache_tree_fully_valid(active_cache_tree)) cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR); @@ -865,7 +907,7 @@ strbuf_addf(&msg, "checkout: moving from %s to %s", old_desc ? old_desc : "(invalid)", new_branch_info->name); else - strbuf_insert(&msg, 0, reflog_msg, strlen(reflog_msg)); + strbuf_insertstr(&msg, 0, reflog_msg); if (!strcmp(new_branch_info->name, "HEAD") && !new_branch_info->path && !opts->force_detach) { /* Nothing to do. */ @@ -874,7 +916,7 @@ REF_NO_DEREF, UPDATE_REFS_DIE_ON_ERR); if (!opts->quiet) { if (old_branch_info->path && - advice_detached_head && !opts->force_detach) + advice_enabled(ADVICE_DETACHED_HEAD) && !opts->force_detach) detach_advice(new_branch_info->name); describe_detached_head(_("HEAD is now at"), new_branch_info->commit); } @@ -967,7 +1009,7 @@ sb.buf); strbuf_release(&sb); - if (advice_detached_head) + if (advice_enabled(ADVICE_DETACHED_HEAD)) fprintf(stderr, Q_( /* The singular version */ @@ -1013,7 +1055,7 @@ describe_detached_head(_("Previous HEAD position was"), old_commit); /* Clean up objects used, as they will be reused. */ - clear_commit_marks_all(ALL_REV_FLAGS); + repo_clear_commit_marks(the_repository, ALL_REV_FLAGS); } static int switch_branches(const struct checkout_opts *opts, @@ -1077,11 +1119,16 @@ static int git_checkout_config(const char *var, const char *value, void *cb) { + struct checkout_opts *opts = cb; + if (!strcmp(var, "diff.ignoresubmodules")) { - struct checkout_opts *opts = cb; handle_ignore_submodules_arg(&opts->diff_options, value); return 0; } + if (!strcmp(var, "checkout.guess")) { + opts->dwim_new_local_branch = git_config_bool(var, value); + return 0; + } if (starts_with(var, "submodule.")) return git_default_submodule_config(var, value, NULL); @@ -1104,8 +1151,10 @@ if (!check_refname_format(new_branch_info->path, 0) && !read_ref(new_branch_info->path, &branch_rev)) oidcpy(rev, &branch_rev); - else + else { + free((char *)new_branch_info->path); new_branch_info->path = NULL; /* not an existing branch */ + } new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1); if (!new_branch_info->commit) { @@ -1117,12 +1166,43 @@ } } +static const char *parse_remote_branch(const char *arg, + struct object_id *rev, + int could_be_checkout_paths) +{ + int num_matches = 0; + const char *remote = unique_tracking_name(arg, rev, &num_matches); + + if (remote && could_be_checkout_paths) { + die(_("'%s' could be both a local file and a tracking branch.\n" + "Please use -- (and optionally --no-guess) to disambiguate"), + arg); + } + + if (!remote && num_matches > 1) { + if (advice_enabled(ADVICE_CHECKOUT_AMBIGUOUS_REMOTE_BRANCH_NAME)) { + advise(_("If you meant to check out a remote tracking branch on, e.g. 'origin',\n" + "you can do so by fully qualifying the name with the --track option:\n" + "\n" + " git checkout --track origin/\n" + "\n" + "If you'd like to always have checkouts of an ambiguous prefer\n" + "one remote, e.g. the 'origin' remote, consider setting\n" + "checkout.defaultRemote=origin in your config.")); + } + + die(_("'%s' matched multiple (%d) remote tracking branches"), + arg, num_matches); + } + + return remote; +} + static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, struct branch_info *new_branch_info, struct checkout_opts *opts, - struct object_id *rev, - int *dwim_remotes_matched) + struct object_id *rev) { const char **new_branch = &opts->new_branch; int argcount = 0; @@ -1227,13 +1307,9 @@ recover_with_dwim = 0; if (recover_with_dwim) { - const char *remote = unique_tracking_name(arg, rev, - dwim_remotes_matched); + const char *remote = parse_remote_branch(arg, rev, + could_be_checkout_paths); if (remote) { - if (could_be_checkout_paths) - die(_("'%s' could be both a local file and a tracking branch.\n" - "Please use -- (and optionally --no-guess) to disambiguate"), - arg); *new_branch = arg; arg = remote; /* DWIMmed to create local branch, case (3).(b) */ @@ -1300,7 +1376,7 @@ struct object_id oid; char *to_free; - if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free) == 1) { + if (dwim_ref(branch_info->name, strlen(branch_info->name), &oid, &to_free, 0) == 1) { const char *ref = to_free; if (skip_prefix(ref, "refs/tags/", &ref)) @@ -1435,9 +1511,9 @@ { struct option options[] = { OPT__QUIET(&opts->quiet, N_("suppress progress reporting")), - { OPTION_CALLBACK, 0, "recurse-submodules", NULL, + OPT_CALLBACK_F(0, "recurse-submodules", NULL, "checkout", "control recursive updating of submodules", - PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater }, + PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater), OPT_BOOL(0, "progress", &opts->show_progress, N_("force progress reporting")), OPT_BOOL('m', "merge", &opts->merge, N_("perform a 3-way merge with the new branch")), OPT_STRING(0, "conflict", &opts->conflict_style, N_("style"), @@ -1493,12 +1569,14 @@ return newopts; } +/* create-branch option (either b or c) */ +static char cb_option = 'b'; + static int checkout_main(int argc, const char **argv, const char *prefix, struct checkout_opts *opts, struct option *options, const char * const usagestr[]) { struct branch_info new_branch_info; - int dwim_remotes_matched = 0; int parseopt_flags = 0; memset(&new_branch_info, 0, sizeof(new_branch_info)); @@ -1508,6 +1586,9 @@ git_config(git_checkout_config, opts); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + opts->track = BRANCH_TRACK_UNSPECIFIED; if (!opts->accept_pathspec && !opts->accept_ref) @@ -1536,7 +1617,8 @@ } if ((!!opts->new_branch + !!opts->new_branch_force + !!opts->new_orphan_branch) > 1) - die(_("-b, -B and --orphan are mutually exclusive")); + die(_("-%c, -%c and --orphan are mutually exclusive"), + cb_option, toupper(cb_option)); if (opts->overlay_mode == 1 && opts->patch_mode) die(_("-p and --overlay are mutually exclusive")); @@ -1555,16 +1637,16 @@ if (opts->checkout_index < 0 || opts->checkout_worktree < 0) BUG("these flags should be non-negative by now"); /* - * convenient shortcut: "git restore --staged" equals - * "git restore --staged --source HEAD" + * convenient shortcut: "git restore --staged [--worktree]" equals + * "git restore --staged [--worktree] --source HEAD" */ - if (!opts->from_treeish && opts->checkout_index && !opts->checkout_worktree) + if (!opts->from_treeish && opts->checkout_index) opts->from_treeish = "HEAD"; /* * From here on, new_branch will contain the branch to be checked out, * and new_branch_force and new_orphan_branch will tell us which one of - * -b/-B/--orphan is being used. + * -b/-B/-c/-C/--orphan is being used. */ if (opts->new_branch_force) opts->new_branch = opts->new_branch_force; @@ -1572,7 +1654,7 @@ if (opts->new_orphan_branch) opts->new_branch = opts->new_orphan_branch; - /* --track without -b/-B/--orphan should DWIM */ + /* --track without -c/-C/-b/-B/--orphan should DWIM */ if (opts->track != BRANCH_TRACK_UNSPECIFIED && !opts->new_branch) { const char *argv0 = argv[0]; if (!argc || !strcmp(argv0, "--")) @@ -1581,7 +1663,7 @@ skip_prefix(argv0, "remotes/", &argv0); argv0 = strchr(argv0, '/'); if (!argv0 || !argv0[1]) - die(_("missing branch name; try -b")); + die(_("missing branch name; try -%c"), cb_option); opts->new_branch = argv0 + 1; } @@ -1606,8 +1688,7 @@ opts->track == BRANCH_TRACK_UNSPECIFIED && !opts->new_branch; int n = parse_branchname_arg(argc, argv, dwim_ok, - &new_branch_info, opts, &rev, - &dwim_remotes_matched); + &new_branch_info, opts, &rev); argv += n; argc -= n; } else if (!opts->accept_ref && opts->from_treeish) { @@ -1636,7 +1717,7 @@ * Try to give more helpful suggestion. * new_branch && argc > 1 will be caught later. */ - if (opts->new_branch && argc == 1) + if (opts->new_branch && argc == 1 && !new_branch_info.commit) die(_("'%s' is not a commit and a branch '%s' cannot be created from it"), argv[0], opts->new_branch); @@ -1662,6 +1743,8 @@ die(_("--pathspec-file-nul requires --pathspec-from-file")); } + opts->pathspec.recursive = 1; + if (opts->pathspec.nr) { if (1 < !!opts->writeout_stage + !!opts->force + !!opts->merge) die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\n" @@ -1684,28 +1767,10 @@ } UNLEAK(opts); - if (opts->patch_mode || opts->pathspec.nr) { - int ret = checkout_paths(opts, new_branch_info.name); - if (ret && dwim_remotes_matched > 1 && - advice_checkout_ambiguous_remote_branch_name) - advise(_("'%s' matched more than one remote tracking branch.\n" - "We found %d remotes with a reference that matched. So we fell back\n" - "on trying to resolve the argument as a path, but failed there too!\n" - "\n" - "If you meant to check out a remote tracking branch on, e.g. 'origin',\n" - "you can do so by fully qualifying the name with the --track option:\n" - "\n" - " git checkout --track origin/\n" - "\n" - "If you'd like to always have checkouts of an ambiguous prefer\n" - "one remote, e.g. the 'origin' remote, consider setting\n" - "checkout.defaultRemote=origin in your config."), - argv[0], - dwim_remotes_matched); - return ret; - } else { + if (opts->patch_mode || opts->pathspec.nr) + return checkout_paths(opts, &new_branch_info); + else return checkout_branch(opts, &new_branch_info); - } } int cmd_checkout(int argc, const char **argv, const char *prefix) @@ -1791,6 +1856,8 @@ options = add_common_options(&opts, options); options = add_common_switch_branch_options(&opts, options); + cb_option = 'c'; + ret = checkout_main(argc, argv, prefix, &opts, options, switch_branch_usage); FREE_AND_NULL(options); diff -Nru git-2.25.1/builtin/checkout-index.c git-2.34.1/builtin/checkout-index.c --- git-2.25.1/builtin/checkout-index.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/checkout-index.c 2021-11-24 19:29:21.000000000 +0000 @@ -11,6 +11,8 @@ #include "quote.h" #include "cache-tree.h" #include "parse-options.h" +#include "entry.h" +#include "parallel-checkout.h" #define CHECKOUT_ALL 4 static int nul_term_line; @@ -23,22 +25,35 @@ static void write_tempfile_record(const char *name, const char *prefix) { int i; + int have_tempname = 0; if (CHECKOUT_ALL == checkout_stage) { - for (i = 1; i < 4; i++) { - if (i > 1) - putchar(' '); - if (topath[i][0]) - fputs(topath[i], stdout); - else - putchar('.'); + for (i = 1; i < 4; i++) + if (topath[i][0]) { + have_tempname = 1; + break; + } + + if (have_tempname) { + for (i = 1; i < 4; i++) { + if (i > 1) + putchar(' '); + if (topath[i][0]) + fputs(topath[i], stdout); + else + putchar('.'); + } } - } else + } else if (topath[checkout_stage][0]) { + have_tempname = 1; fputs(topath[checkout_stage], stdout); + } - putchar('\t'); - write_name_quoted_relative(name, prefix, stdout, - nul_term_line ? '\0' : '\n'); + if (have_tempname) { + putchar('\t'); + write_name_quoted_relative(name, prefix, stdout, + nul_term_line ? '\0' : '\n'); + } for (i = 0; i < 4; i++) { topath[i][0] = 0; @@ -79,6 +94,14 @@ return errs > 0 ? -1 : 0; } + /* + * At this point we know we didn't try to check anything out. If it was + * because we did find an entry but it was stage 0, that's not an + * error. + */ + if (has_same_name && checkout_stage == CHECKOUT_ALL) + return 0; + if (!state.quiet) { fprintf(stderr, "git checkout-index: %s ", name); if (!has_same_name) @@ -93,11 +116,13 @@ return -1; } -static void checkout_all(const char *prefix, int prefix_length) +static int checkout_all(const char *prefix, int prefix_length) { int i, errs = 0; struct cache_entry *last_ce = NULL; + /* TODO: audit for interaction with sparse-index. */ + ensure_full_index(&the_index); for (i = 0; i < active_nr ; i++) { struct cache_entry *ce = active_cache[i]; if (ce_stage(ce) != checkout_stage @@ -120,11 +145,7 @@ } if (last_ce && to_tempfile) write_tempfile_record(last_ce->name, prefix); - if (errs) - /* we have already done our error reporting. - * exit with the same code as die(). - */ - exit(128); + return !!errs; } static const char * const builtin_checkout_index_usage[] = { @@ -159,6 +180,8 @@ int prefix_length; int force = 0, quiet = 0, not_new = 0; int index_opt = 0; + int err = 0; + int pc_workers, pc_threshold; struct option builtin_checkout_index_options[] = { OPT_BOOL('a', "all", &all, N_("check out all files in the index")), @@ -177,9 +200,9 @@ N_("write the content to temporary files")), OPT_STRING(0, "prefix", &state.base_dir, N_("string"), N_("when creating files, prepend ")), - { OPTION_CALLBACK, 0, "stage", NULL, "(1|2|3|all)", + OPT_CALLBACK_F(0, "stage", NULL, "(1|2|3|all)", N_("copy out the files from named stage"), - PARSE_OPT_NONEG, option_parse_stage }, + PARSE_OPT_NONEG, option_parse_stage), OPT_END() }; @@ -213,6 +236,10 @@ hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); } + get_parallel_checkout_configs(&pc_workers, &pc_threshold); + if (pc_workers > 1) + init_parallel_checkout(); + /* Check out named files first */ for (i = 0; i < argc; i++) { const char *arg = argv[i]; @@ -223,7 +250,7 @@ if (read_from_stdin) die("git checkout-index: don't mix '--stdin' and explicit filenames"); p = prefix_path(prefix, prefix_length, arg); - checkout_file(p, prefix); + err |= checkout_file(p, prefix); free(p); } @@ -245,7 +272,7 @@ strbuf_swap(&buf, &unquoted); } p = prefix_path(prefix, prefix_length, buf.buf); - checkout_file(p, prefix); + err |= checkout_file(p, prefix); free(p); } strbuf_release(&unquoted); @@ -253,7 +280,14 @@ } if (all) - checkout_all(prefix, prefix_length); + err |= checkout_all(prefix, prefix_length); + + if (pc_workers > 1) + err |= run_parallel_checkout(&state, pc_workers, pc_threshold, + NULL, NULL); + + if (err) + return 1; if (is_lock_file_locked(&lock_file) && write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) diff -Nru git-2.25.1/builtin/checkout--worker.c git-2.34.1/builtin/checkout--worker.c --- git-2.25.1/builtin/checkout--worker.c 1970-01-01 00:00:00.000000000 +0000 +++ git-2.34.1/builtin/checkout--worker.c 2021-11-24 19:29:21.000000000 +0000 @@ -0,0 +1,145 @@ +#include "builtin.h" +#include "config.h" +#include "entry.h" +#include "parallel-checkout.h" +#include "parse-options.h" +#include "pkt-line.h" + +static void packet_to_pc_item(const char *buffer, int len, + struct parallel_checkout_item *pc_item) +{ + const struct pc_item_fixed_portion *fixed_portion; + const char *variant; + char *encoding; + + if (len < sizeof(struct pc_item_fixed_portion)) + BUG("checkout worker received too short item (got %dB, exp %dB)", + len, (int)sizeof(struct pc_item_fixed_portion)); + + fixed_portion = (struct pc_item_fixed_portion *)buffer; + + if (len - sizeof(struct pc_item_fixed_portion) != + fixed_portion->name_len + fixed_portion->working_tree_encoding_len) + BUG("checkout worker received corrupted item"); + + variant = buffer + sizeof(struct pc_item_fixed_portion); + + /* + * Note: the main process uses zero length to communicate that the + * encoding is NULL. There is no use case that requires sending an + * actual empty string, since convert_attrs() never sets + * ca.working_tree_enconding to "". + */ + if (fixed_portion->working_tree_encoding_len) { + encoding = xmemdupz(variant, + fixed_portion->working_tree_encoding_len); + variant += fixed_portion->working_tree_encoding_len; + } else { + encoding = NULL; + } + + memset(pc_item, 0, sizeof(*pc_item)); + pc_item->ce = make_empty_transient_cache_entry(fixed_portion->name_len, NULL); + pc_item->ce->ce_namelen = fixed_portion->name_len; + pc_item->ce->ce_mode = fixed_portion->ce_mode; + memcpy(pc_item->ce->name, variant, pc_item->ce->ce_namelen); + oidcpy(&pc_item->ce->oid, &fixed_portion->oid); + + pc_item->id = fixed_portion->id; + pc_item->ca.crlf_action = fixed_portion->crlf_action; + pc_item->ca.ident = fixed_portion->ident; + pc_item->ca.working_tree_encoding = encoding; +} + +static void report_result(struct parallel_checkout_item *pc_item) +{ + struct pc_item_result res = { 0 }; + size_t size; + + res.id = pc_item->id; + res.status = pc_item->status; + + if (pc_item->status == PC_ITEM_WRITTEN) { + res.st = pc_item->st; + size = sizeof(res); + } else { + size = PC_ITEM_RESULT_BASE_SIZE; + } + + packet_write(1, (const char *)&res, size); +} + +/* Free the worker-side malloced data, but not pc_item itself. */ +static void release_pc_item_data(struct parallel_checkout_item *pc_item) +{ + free((char *)pc_item->ca.working_tree_encoding); + discard_cache_entry(pc_item->ce); +} + +static void worker_loop(struct checkout *state) +{ + struct parallel_checkout_item *items = NULL; + size_t i, nr = 0, alloc = 0; + + while (1) { + int len = packet_read(0, packet_buffer, sizeof(packet_buffer), + 0); + + if (len < 0) + BUG("packet_read() returned negative value"); + else if (!len) + break; + + ALLOC_GROW(items, nr + 1, alloc); + packet_to_pc_item(packet_buffer, len, &items[nr++]); + } + + for (i = 0; i < nr; i++) { + struct parallel_checkout_item *pc_item = &items[i]; + write_pc_item(pc_item, state); + report_result(pc_item); + release_pc_item_data(pc_item); + } + + packet_flush(1); + + free(items); +} + +static const char * const checkout_worker_usage[] = { + N_("git checkout--worker []"), + NULL +}; + +int cmd_checkout__worker(int argc, const char **argv, const char *prefix) +{ + struct checkout state = CHECKOUT_INIT; + struct option checkout_worker_options[] = { + OPT_STRING(0, "prefix", &state.base_dir, N_("string"), + N_("when creating files, prepend ")), + OPT_END() + }; + + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(checkout_worker_usage, + checkout_worker_options); + + git_config(git_default_config, NULL); + argc = parse_options(argc, argv, prefix, checkout_worker_options, + checkout_worker_usage, 0); + if (argc > 0) + usage_with_options(checkout_worker_usage, checkout_worker_options); + + if (state.base_dir) + state.base_dir_len = strlen(state.base_dir); + + /* + * Setting this on a worker won't actually update the index. We just + * need to tell the checkout machinery to lstat() the written entries, + * so that we can send this data back to the main process. + */ + state.refresh_cache = 1; + + worker_loop(&state); + return 0; +} diff -Nru git-2.25.1/builtin/clean.c git-2.34.1/builtin/clean.c --- git-2.25.1/builtin/clean.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/clean.c 2021-11-24 19:29:21.000000000 +0000 @@ -18,6 +18,7 @@ #include "color.h" #include "pathspec.h" #include "help.h" +#include "prompt.h" static int force = -1; /* unset */ static int interactive; @@ -161,7 +162,7 @@ if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && is_nonbare_repository_dir(path)) { if (!quiet) { - quote_path_relative(path->buf, prefix, "ed); + quote_path(path->buf, prefix, "ed, 0); printf(dry_run ? _(msg_would_skip_git_dir) : _(msg_skip_git_dir), quoted.buf); } @@ -176,7 +177,7 @@ res = dry_run ? 0 : rmdir(path->buf); if (res) { int saved_errno = errno; - quote_path_relative(path->buf, prefix, "ed); + quote_path(path->buf, prefix, "ed, 0); errno = saved_errno; warning_errno(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; @@ -188,10 +189,8 @@ strbuf_complete(path, '/'); len = path->len; - while ((e = readdir(dir)) != NULL) { + while ((e = readdir_skip_dot_and_dotdot(dir)) != NULL) { struct stat st; - if (is_dot_or_dotdot(e->d_name)) - continue; strbuf_setlen(path, len); strbuf_addstr(path, e->d_name); @@ -201,7 +200,7 @@ if (remove_dirs(path, prefix, force_flag, dry_run, quiet, &gone)) ret = 1; if (gone) { - quote_path_relative(path->buf, prefix, "ed); + quote_path(path->buf, prefix, "ed, 0); string_list_append(&dels, quoted.buf); } else *dir_gone = 0; @@ -209,11 +208,11 @@ } else { res = dry_run ? 0 : unlink(path->buf); if (!res) { - quote_path_relative(path->buf, prefix, "ed); + quote_path(path->buf, prefix, "ed, 0); string_list_append(&dels, quoted.buf); } else { int saved_errno = errno; - quote_path_relative(path->buf, prefix, "ed); + quote_path(path->buf, prefix, "ed, 0); errno = saved_errno; warning_errno(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; @@ -237,7 +236,7 @@ *dir_gone = 1; else { int saved_errno = errno; - quote_path_relative(path->buf, prefix, "ed); + quote_path(path->buf, prefix, "ed, 0); errno = saved_errno; warning_errno(_(msg_warn_remove_failed), quoted.buf); *dir_gone = 0; @@ -265,7 +264,7 @@ struct column_options copts; for_each_string_list_item(item, &del_list) { - qname = quote_path_relative(item->string, NULL, &buf); + qname = quote_path(item->string, NULL, &buf, 0); string_list_append(&list, qname); } @@ -420,7 +419,6 @@ return found; } - /* * Parse user input, and return choice(s) for menu (menu_stuff). * @@ -580,9 +578,7 @@ clean_get_color(CLEAN_COLOR_RESET)); } - if (strbuf_getline_lf(&choice, stdin) != EOF) { - strbuf_trim(&choice); - } else { + if (git_read_line_interactively(&choice) == EOF) { eof = 1; break; } @@ -625,7 +621,7 @@ nr += chosen[i]; } - result = xcalloc(st_add(nr, 1), sizeof(int)); + CALLOC_ARRAY(result, st_add(nr, 1)); for (i = 0; i < stuff->nr && j < nr; i++) { if (chosen[i]) result[j++] = i; @@ -645,7 +641,7 @@ static int filter_by_patterns_cmd(void) { - struct dir_struct dir; + struct dir_struct dir = DIR_INIT; struct strbuf confirm = STRBUF_INIT; struct strbuf **ignore_list; struct string_list_item *item; @@ -662,16 +658,13 @@ clean_print_color(CLEAN_COLOR_PROMPT); printf(_("Input ignore patterns>> ")); clean_print_color(CLEAN_COLOR_RESET); - if (strbuf_getline_lf(&confirm, stdin) != EOF) - strbuf_trim(&confirm); - else + if (git_read_line_interactively(&confirm) == EOF) putchar('\n'); /* quit filter_by_pattern mode if press ENTER or Ctrl-D */ if (!confirm.len) break; - memset(&dir, 0, sizeof(dir)); pl = add_pattern_list(&dir, EXC_CMDL, "manual exclude"); ignore_list = strbuf_split_max(&confirm, ' ', 0); @@ -702,7 +695,7 @@ } strbuf_list_free(ignore_list); - clear_directory(&dir); + dir_clear(&dir); } strbuf_release(&confirm); @@ -757,12 +750,10 @@ for_each_string_list_item(item, &del_list) { /* Ctrl-D should stop removing files */ if (!eof) { - qname = quote_path_relative(item->string, NULL, &buf); + qname = quote_path(item->string, NULL, &buf, 0); /* TRANSLATORS: Make sure to keep [y/N] as is */ printf(_("Remove %s [y/N]? "), qname); - if (strbuf_getline_lf(&confirm, stdin) != EOF) { - strbuf_trim(&confirm); - } else { + if (git_read_line_interactively(&confirm) == EOF) { putchar('\n'); eof = 1; } @@ -898,7 +889,7 @@ int ignored_only = 0, config_set = 0, errors = 0, gone = 1; int rm_flags = REMOVE_DIR_KEEP_NESTED_GIT; struct strbuf abs_path = STRBUF_INIT; - struct dir_struct dir; + struct dir_struct dir = DIR_INIT; struct pathspec pathspec; struct strbuf buf = STRBUF_INIT; struct string_list exclude_list = STRING_LIST_INIT_NODUP; @@ -912,8 +903,8 @@ OPT_BOOL('i', "interactive", &interactive, N_("interactive cleaning")), OPT_BOOL('d', NULL, &remove_directories, N_("remove whole directories")), - { OPTION_CALLBACK, 'e', "exclude", &exclude_list, N_("pattern"), - N_("add to ignore rules"), PARSE_OPT_NONEG, exclude_cb }, + OPT_CALLBACK_F('e', "exclude", &exclude_list, N_("pattern"), + N_("add to ignore rules"), PARSE_OPT_NONEG, exclude_cb), OPT_BOOL('x', NULL, &ignored, N_("remove ignored files, too")), OPT_BOOL('X', NULL, &ignored_only, N_("remove only ignored files")), @@ -929,13 +920,6 @@ argc = parse_options(argc, argv, prefix, options, builtin_clean_usage, 0); - memset(&dir, 0, sizeof(dir)); - if (ignored_only) - dir.flags |= DIR_SHOW_IGNORED; - - if (ignored && ignored_only) - die(_("-x and -X cannot be used together")); - if (!interactive && !dry_run && !force) { if (config_set) die(_("clean.requireForce set to true and neither -i, -n, nor -f given; " @@ -952,6 +936,13 @@ dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; + if (ignored && ignored_only) + die(_("-x and -X cannot be used together")); + if (!ignored) + setup_standard_excludes(&dir); + if (ignored_only) + dir.flags |= DIR_SHOW_IGNORED; + if (argc) { /* * Remaining args implies pathspecs specified, and we should @@ -960,15 +951,41 @@ remove_directories = 1; } - if (remove_directories) - dir.flags |= DIR_SHOW_IGNORED_TOO | DIR_KEEP_UNTRACKED_CONTENTS; + if (remove_directories && !ignored_only) { + /* + * We need to know about ignored files too: + * + * If (ignored), then we will delete ignored files as well. + * + * If (!ignored), then even though we not are doing + * anything with ignored files, we need to know about them + * so that we can avoid deleting a directory of untracked + * files that also contains an ignored file within it. + * + * For the (!ignored) case, since we only need to avoid + * deleting ignored files, we can set + * DIR_SHOW_IGNORED_TOO_MODE_MATCHING in order to avoid + * recursing into a directory which is itself ignored. + */ + dir.flags |= DIR_SHOW_IGNORED_TOO; + if (!ignored) + dir.flags |= DIR_SHOW_IGNORED_TOO_MODE_MATCHING; + + /* + * Let the fill_directory() machinery know that we aren't + * just recursing to collect the ignored files; we want all + * the untracked ones so that we can delete them. (Note: + * we could also set DIR_KEEP_UNTRACKED_CONTENTS when + * ignored_only is true, since DIR_KEEP_UNTRACKED_CONTENTS + * only has effect in combination with DIR_SHOW_IGNORED_TOO. It makes + * the code clearer to exclude it, though. + */ + dir.flags |= DIR_KEEP_UNTRACKED_CONTENTS; + } if (read_cache() < 0) die(_("index file corrupt")); - if (!ignored) - setup_standard_excludes(&dir); - pl = add_pattern_list(&dir, EXC_CMDL, "--exclude option"); for (i = 0; i < exclude_list.nr; i++) add_pattern(exclude_list.items[i].string, "", 0, pl, -(i+1)); @@ -982,35 +999,23 @@ for (i = 0; i < dir.nr; i++) { struct dir_entry *ent = dir.entries[i]; - int matches = 0; struct stat st; const char *rel; if (!cache_name_is_other(ent->name, ent->len)) continue; - if (pathspec.nr) - matches = dir_path_match(&the_index, ent, &pathspec, 0, NULL); - - if (pathspec.nr && !matches) - continue; - if (lstat(ent->name, &st)) die_errno("Cannot lstat '%s'", ent->name); - if (S_ISDIR(st.st_mode) && !remove_directories && - matches != MATCHED_EXACTLY) + if (S_ISDIR(st.st_mode) && !remove_directories) continue; rel = relative_path(ent->name, prefix, &buf); string_list_append(&del_list, rel); } - for (i = 0; i < dir.nr; i++) - free(dir.entries[i]); - - for (i = 0; i < dir.ignored_nr; i++) - free(dir.ignored[i]); + dir_clear(&dir); if (interactive && del_list.nr > 0) interactive_main_loop(); @@ -1036,19 +1041,19 @@ if (remove_dirs(&abs_path, prefix, rm_flags, dry_run, quiet, &gone)) errors++; if (gone && !quiet) { - qname = quote_path_relative(item->string, NULL, &buf); + qname = quote_path(item->string, NULL, &buf, 0); printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); } } else { res = dry_run ? 0 : unlink(abs_path.buf); if (res) { int saved_errno = errno; - qname = quote_path_relative(item->string, NULL, &buf); + qname = quote_path(item->string, NULL, &buf, 0); errno = saved_errno; warning_errno(_(msg_warn_remove_failed), qname); errors++; } else if (!quiet) { - qname = quote_path_relative(item->string, NULL, &buf); + qname = quote_path(item->string, NULL, &buf, 0); printf(dry_run ? _(msg_would_remove) : _(msg_remove), qname); } } diff -Nru git-2.25.1/builtin/clone.c git-2.34.1/builtin/clone.c --- git-2.25.1/builtin/clone.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/clone.c 2021-11-24 19:29:21.000000000 +0000 @@ -50,9 +50,12 @@ static int option_local = -1, option_no_hardlinks, option_shared; static int option_no_tags; static int option_shallow_submodules; +static int option_reject_shallow = -1; /* unspecified */ +static int config_reject_shallow = -1; /* unspecified */ static int deepen; static char *option_template, *option_depth, *option_since; static char *option_origin = NULL; +static char *remote_name = NULL; static char *option_branch = NULL; static struct string_list option_not = STRING_LIST_INIT_NODUP; static const char *real_git_dir; @@ -89,6 +92,8 @@ OPT__VERBOSITY(&option_verbosity), OPT_BOOL(0, "progress", &option_progress, N_("force progress reporting")), + OPT_BOOL(0, "reject-shallow", &option_reject_shallow, + N_("don't clone shallow repository")), OPT_BOOL('n', "no-checkout", &option_no_checkout, N_("don't create a checkout")), OPT_BOOL(0, "bare", &option_bare, N_("create a bare repository")), @@ -102,10 +107,10 @@ N_("don't use local hardlinks, always copy")), OPT_BOOL('s', "shared", &option_shared, N_("setup as shared repository")), - OPT_ALIAS(0, "recursive", "recurse-submodules"), { OPTION_CALLBACK, 0, "recurse-submodules", &option_recurse_submodules, N_("pathspec"), N_("initialize submodules in the clone"), PARSE_OPT_OPTARG, recurse_submodules_cb, (intptr_t)"." }, + OPT_ALIAS(0, "recursive", "recurse-submodules"), OPT_INTEGER('j', "jobs", &max_jobs, N_("number of submodules cloned in parallel")), OPT_STRING(0, "template", &option_template, N_("template-directory"), @@ -212,120 +217,6 @@ return canon; } -static char *guess_dir_name(const char *repo, int is_bundle, int is_bare) -{ - const char *end = repo + strlen(repo), *start, *ptr; - size_t len; - char *dir; - - /* - * Skip scheme. - */ - start = strstr(repo, "://"); - if (start == NULL) - start = repo; - else - start += 3; - - /* - * Skip authentication data. The stripping does happen - * greedily, such that we strip up to the last '@' inside - * the host part. - */ - for (ptr = start; ptr < end && !is_dir_sep(*ptr); ptr++) { - if (*ptr == '@') - start = ptr + 1; - } - - /* - * Strip trailing spaces, slashes and /.git - */ - while (start < end && (is_dir_sep(end[-1]) || isspace(end[-1]))) - end--; - if (end - start > 5 && is_dir_sep(end[-5]) && - !strncmp(end - 4, ".git", 4)) { - end -= 5; - while (start < end && is_dir_sep(end[-1])) - end--; - } - - /* - * Strip trailing port number if we've got only a - * hostname (that is, there is no dir separator but a - * colon). This check is required such that we do not - * strip URI's like '/foo/bar:2222.git', which should - * result in a dir '2222' being guessed due to backwards - * compatibility. - */ - if (memchr(start, '/', end - start) == NULL - && memchr(start, ':', end - start) != NULL) { - ptr = end; - while (start < ptr && isdigit(ptr[-1]) && ptr[-1] != ':') - ptr--; - if (start < ptr && ptr[-1] == ':') - end = ptr - 1; - } - - /* - * Find last component. To remain backwards compatible we - * also regard colons as path separators, such that - * cloning a repository 'foo:bar.git' would result in a - * directory 'bar' being guessed. - */ - ptr = end; - while (start < ptr && !is_dir_sep(ptr[-1]) && ptr[-1] != ':') - ptr--; - start = ptr; - - /* - * Strip .{bundle,git}. - */ - len = end - start; - strip_suffix_mem(start, &len, is_bundle ? ".bundle" : ".git"); - - if (!len || (len == 1 && *start == '/')) - die(_("No directory name could be guessed.\n" - "Please specify a directory on the command line")); - - if (is_bare) - dir = xstrfmt("%.*s.git", (int)len, start); - else - dir = xstrndup(start, len); - /* - * Replace sequences of 'control' characters and whitespace - * with one ascii space, remove leading and trailing spaces. - */ - if (*dir) { - char *out = dir; - int prev_space = 1 /* strip leading whitespace */; - for (end = dir; *end; ++end) { - char ch = *end; - if ((unsigned char)ch < '\x20') - ch = '\x20'; - if (isspace(ch)) { - if (prev_space) - continue; - prev_space = 1; - } else - prev_space = 0; - *out++ = ch; - } - *out = '\0'; - if (out > dir && prev_space) - out[-1] = '\0'; - } - return dir; -} - -static void strip_trailing_slashes(char *dir) -{ - char *end = dir + strlen(dir); - - while (dir < end - 1 && is_dir_sep(end[-1])) - end--; - *end = '\0'; -} - static int add_one_reference(struct string_list_item *item, void *cb_data) { struct strbuf err = STRBUF_INIT; @@ -420,6 +311,7 @@ struct dir_iterator *iter; int iter_status; unsigned int flags; + struct strbuf realpath = STRBUF_INIT; mkdir_if_missing(dest->buf, 0777); @@ -454,7 +346,8 @@ if (unlink(dest->buf) && errno != ENOENT) die_errno(_("failed to unlink '%s'"), dest->buf); if (!option_no_hardlinks) { - if (!link(real_path(src->buf), dest->buf)) + strbuf_realpath(&realpath, src->buf, 1); + if (!link(realpath.buf, dest->buf)) continue; if (option_local > 0) die_errno(_("failed to create link '%s'"), dest->buf); @@ -468,6 +361,8 @@ strbuf_setlen(src, src_len); die(_("failed to iterate over '%s'"), src->buf); } + + strbuf_release(&realpath); } static void clone_local(const char *src_repo, const char *dest_repo) @@ -639,14 +534,16 @@ continue; if (ends_with(ref->name, "^{}")) continue; - if (!has_object_file(&ref->old_oid)) + if (!has_object_file_with_flags(&ref->old_oid, + OBJECT_INFO_QUICK | + OBJECT_INFO_SKIP_FETCH_OBJECT)) continue; update_ref(msg, ref->name, &ref->old_oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR); } } -static int iterate_ref_map(void *cb_data, struct object_id *oid) +static const struct object_id *iterate_ref_map(void *cb_data) { struct ref **rm = cb_data; struct ref *ref = *rm; @@ -657,13 +554,11 @@ */ while (ref && !ref->peer_ref) ref = ref->next; - /* Returning -1 notes "end of list" to the caller. */ if (!ref) - return -1; + return NULL; - oidcpy(oid, &ref->old_oid); *rm = ref->next; - return 0; + return &ref->old_oid; } static void update_remote_refs(const struct ref *refs, @@ -672,8 +567,7 @@ const char *branch_top, const char *msg, struct transport *transport, - int check_connectivity, - int check_refs_only) + int check_connectivity) { const struct ref *rm = mapped_refs; @@ -682,7 +576,6 @@ opt.transport = transport; opt.progress = transport->progress; - opt.check_refs_only = !!check_refs_only; if (check_connected(iterate_ref_map, &rm, &opt)) die(_("remote did not send all necessary objects")); @@ -717,7 +610,7 @@ if (!option_bare) { update_ref(msg, "HEAD", &our->old_oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR); - install_branch_config(0, head, option_origin, our->name); + install_branch_config(0, head, remote_name, our->name); } } else if (our) { struct commit *c = lookup_commit_reference(the_repository, @@ -738,9 +631,9 @@ static int git_sparse_checkout_init(const char *repo) { - struct argv_array argv = ARGV_ARRAY_INIT; + struct strvec argv = STRVEC_INIT; int result = 0; - argv_array_pushl(&argv, "-C", repo, "sparse-checkout", "init", NULL); + strvec_pushl(&argv, "-C", repo, "sparse-checkout", "init", NULL); /* * We must apply the setting in the current process @@ -748,12 +641,12 @@ */ core_apply_sparse_checkout = 1; - if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { + if (run_command_v_opt(argv.v, RUN_GIT_CMD)) { error(_("failed to initialize sparse-checkout")); result = 1; } - argv_array_clear(&argv); + strvec_clear(&argv); return result; } @@ -777,13 +670,13 @@ return 0; } if (!strcmp(head, "HEAD")) { - if (advice_detached_head) + if (advice_enabled(ADVICE_DETACHED_HEAD)) detach_advice(oid_to_hex(&oid)); + FREE_AND_NULL(head); } else { if (!starts_with(head, "refs/heads/")) die(_("HEAD not found below refs/heads!")); } - free(head); /* We need to be in the new work tree for the checkout */ setup_work_tree(); @@ -794,10 +687,12 @@ opts.update = 1; opts.merge = 1; opts.clone = 1; + opts.preserve_ignored = 0; opts.fn = oneway_merge; opts.verbose_update = (option_verbosity >= 0); opts.src_index = &the_index; opts.dst_index = &the_index; + init_checkout_metadata(&opts.meta, head, &oid, NULL); tree = parse_tree_indirect(&oid); parse_tree(tree); @@ -805,42 +700,70 @@ if (unpack_trees(1, &t, &opts) < 0) die(_("unable to checkout working tree")); + free(head); + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); - err |= run_hook_le(NULL, "post-checkout", oid_to_hex(&null_oid), + err |= run_hook_le(NULL, "post-checkout", oid_to_hex(null_oid()), oid_to_hex(&oid), "1", NULL); if (!err && (option_recurse_submodules.nr > 0)) { - struct argv_array args = ARGV_ARRAY_INIT; - argv_array_pushl(&args, "submodule", "update", "--require-init", "--recursive", NULL); + struct strvec args = STRVEC_INIT; + strvec_pushl(&args, "submodule", "update", "--require-init", "--recursive", NULL); if (option_shallow_submodules == 1) - argv_array_push(&args, "--depth=1"); + strvec_push(&args, "--depth=1"); if (max_jobs != -1) - argv_array_pushf(&args, "--jobs=%d", max_jobs); + strvec_pushf(&args, "--jobs=%d", max_jobs); if (submodule_progress) - argv_array_push(&args, "--progress"); + strvec_push(&args, "--progress"); if (option_verbosity < 0) - argv_array_push(&args, "--quiet"); + strvec_push(&args, "--quiet"); if (option_remote_submodules) { - argv_array_push(&args, "--remote"); - argv_array_push(&args, "--no-fetch"); + strvec_push(&args, "--remote"); + strvec_push(&args, "--no-fetch"); } - err = run_command_v_opt(args.argv, RUN_GIT_CMD); - argv_array_clear(&args); + if (option_single_branch >= 0) + strvec_push(&args, option_single_branch ? + "--single-branch" : + "--no-single-branch"); + + err = run_command_v_opt(args.v, RUN_GIT_CMD); + strvec_clear(&args); } return err; } +static int git_clone_config(const char *k, const char *v, void *cb) +{ + if (!strcmp(k, "clone.defaultremotename")) { + free(remote_name); + remote_name = xstrdup(v); + } + if (!strcmp(k, "clone.rejectshallow")) + config_reject_shallow = git_config_bool(k, v); + + return git_default_config(k, v, cb); +} + static int write_one_config(const char *key, const char *value, void *data) { + /* + * give git_clone_config a chance to write config values back to the + * environment, since git_config_set_multivar_gently only deals with + * config-file writes + */ + int apply_failed = git_clone_config(key, value, data); + if (apply_failed) + return apply_failed; + return git_config_set_multivar_gently(key, value ? value : "true", CONFIG_REGEX_NONE, 0); @@ -893,12 +816,12 @@ } /* Configure the remote */ if (value.len) { - strbuf_addf(&key, "remote.%s.fetch", option_origin); + strbuf_addf(&key, "remote.%s.fetch", remote_name); git_config_set_multivar(key.buf, value.buf, "^$", 0); strbuf_reset(&key); if (option_mirror) { - strbuf_addf(&key, "remote.%s.mirror", option_origin); + strbuf_addf(&key, "remote.%s.mirror", remote_name); git_config_set(key.buf, "true"); strbuf_reset(&key); } @@ -932,16 +855,16 @@ int cmd_clone(int argc, const char **argv, const char *prefix) { int is_bundle = 0, is_local; + int reject_shallow = 0; const char *repo_name, *repo, *work_tree, *git_dir; - char *path, *dir; - int dest_exists; + char *path = NULL, *dir, *display_repo = NULL; + int dest_exists, real_dest_exists = 0; const struct ref *refs, *remote_head; - const struct ref *remote_head_points_at; + struct ref *remote_head_points_at = NULL; const struct ref *our_head_points_at; struct ref *mapped_refs; const struct ref *ref; struct strbuf key = STRBUF_INIT; - struct strbuf default_refspec = STRBUF_INIT; struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; struct transport *transport = NULL; const char *src_ref_prefix = "refs/heads/"; @@ -949,9 +872,13 @@ int err = 0, complete_refs_before_fetch = 1; int submodule_progress; - struct argv_array ref_prefixes = ARGV_ARRAY_INIT; + struct transport_ls_refs_options transport_ls_refs_options = + TRANSPORT_LS_REFS_OPTIONS_INIT; packet_trace_identity("clone"); + + git_config(git_clone_config, NULL); + argc = parse_options(argc, argv, prefix, builtin_clone_options, builtin_clone_usage, 0); @@ -980,18 +907,17 @@ option_no_checkout = 1; } - if (!option_origin) - option_origin = "origin"; - repo_name = argv[0]; path = get_repo_path(repo_name, &is_bundle); - if (path) + if (path) { + FREE_AND_NULL(path); repo = absolute_pathdup(repo_name); - else if (!strchr(repo_name, ':')) - die(_("repository '%s' does not exist"), repo_name); - else + } else if (strchr(repo_name, ':')) { repo = repo_name; + display_repo = transport_anonymize_url(repo); + } else + die(_("repository '%s' does not exist"), repo_name); /* no need to be strict, transport_set_option() will validate it again */ if (option_depth && atoi(option_depth) < 1) @@ -1000,15 +926,25 @@ if (argc == 2) dir = xstrdup(argv[1]); else - dir = guess_dir_name(repo_name, is_bundle, option_bare); - strip_trailing_slashes(dir); + dir = git_url_basename(repo_name, is_bundle, option_bare); + strip_dir_trailing_slashes(dir); dest_exists = path_exists(dir); if (dest_exists && !is_empty_dir(dir)) die(_("destination path '%s' already exists and is not " "an empty directory."), dir); - strbuf_addf(&reflog_msg, "clone: from %s", repo); + if (real_git_dir) { + real_dest_exists = path_exists(real_git_dir); + if (real_dest_exists && !is_empty_dir(real_git_dir)) + die(_("repository path '%s' already exists and is not " + "an empty directory."), real_git_dir); + } + + + strbuf_addf(&reflog_msg, "clone: from %s", + display_repo ? display_repo : repo); + free(display_repo); if (option_bare) work_tree = NULL; @@ -1042,7 +978,7 @@ } if (real_git_dir) { - if (path_exists(real_git_dir)) + if (real_dest_exists) junk_git_dir_flags |= REMOVE_DIR_KEEP_TOPLEVEL; junk_git_dir = real_git_dir; } else { @@ -1063,6 +999,7 @@ if (option_recurse_submodules.nr > 0) { struct string_list_item *item; struct strbuf sb = STRBUF_INIT; + int val; /* remove duplicates */ string_list_sort(&option_recurse_submodules); @@ -1079,6 +1016,10 @@ strbuf_detach(&sb, NULL)); } + if (!git_config_get_bool("submodule.stickyRecursiveClone", &val) && + val) + string_list_append(&option_config, "submodule.recurse=true"); + if (option_required_reference.nr && option_optional_reference.nr) die(_("clone --recursive is not compatible with " @@ -1096,14 +1037,47 @@ } } - init_db(git_dir, real_git_dir, option_template, INIT_DB_QUIET); + init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, NULL, + INIT_DB_QUIET); - if (real_git_dir) + if (real_git_dir) { + free((char *)git_dir); git_dir = real_git_dir; + } + /* + * additional config can be injected with -c, make sure it's included + * after init_db, which clears the entire config environment. + */ write_config(&option_config); - git_config(git_default_config, NULL); + /* + * re-read config after init_db and write_config to pick up any config + * injected by --template and --config, respectively. + */ + git_config(git_clone_config, NULL); + + /* + * If option_reject_shallow is specified from CLI option, + * ignore config_reject_shallow from git_clone_config. + */ + if (config_reject_shallow != -1) + reject_shallow = config_reject_shallow; + if (option_reject_shallow != -1) + reject_shallow = option_reject_shallow; + + /* + * 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) + remote_name = xstrdup(option_origin); + + if (remote_name == NULL) + remote_name = xstrdup("origin"); + + if (!valid_remote_name(remote_name)) + die(_("'%s' is not a valid remote name"), remote_name); if (option_bare) { if (option_mirror) @@ -1112,15 +1086,15 @@ git_config_set("core.bare", "true"); } else { - strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin); + strbuf_addf(&branch_top, "refs/remotes/%s/", remote_name); } - strbuf_addf(&key, "remote.%s.url", option_origin); + strbuf_addf(&key, "remote.%s.url", remote_name); git_config_set(key.buf, repo); strbuf_reset(&key); if (option_no_tags) { - strbuf_addf(&key, "remote.%s.tagOpt", option_origin); + strbuf_addf(&key, "remote.%s.tagOpt", remote_name); git_config_set(key.buf, "--no-tags"); strbuf_reset(&key); } @@ -1128,14 +1102,13 @@ if (option_required_reference.nr || option_optional_reference.nr) setup_reference(); - if (option_sparse_checkout && git_sparse_checkout_init(repo)) + if (option_sparse_checkout && git_sparse_checkout_init(dir)) return 1; - remote = remote_get(option_origin); + remote = remote_get(remote_name); - strbuf_addf(&default_refspec, "+%s*:%s*", src_ref_prefix, - branch_top.buf); - refspec_append(&remote->fetch, default_refspec.buf); + refspec_appendf(&remote->fetch, "+%s*:%s*", src_ref_prefix, + branch_top.buf); transport = transport_get(remote, remote->url[0]); transport_set_verbosity(transport, option_verbosity, option_progress); @@ -1153,6 +1126,8 @@ if (filter_options.choice) warning(_("--filter is ignored in local clones; use file:// instead.")); if (!access(mkpath("%s/shallow", path), F_OK)) { + if (reject_shallow) + die(_("source repository is shallow, reject to clone.")); if (option_local > 0) warning(_("source repository is shallow, ignoring --local")); is_local = 0; @@ -1164,6 +1139,8 @@ transport_set_option(transport, TRANS_OPT_KEEP, "yes"); + if (reject_shallow) + transport_set_option(transport, TRANS_OPT_REJECT_SHALLOW, "1"); if (option_depth) transport_set_option(transport, TRANS_OPT_DEPTH, option_depth); @@ -1195,16 +1172,28 @@ transport->smart_options->check_self_contained_and_connected = 1; - argv_array_push(&ref_prefixes, "HEAD"); - refspec_ref_prefixes(&remote->fetch, &ref_prefixes); + strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD"); + refspec_ref_prefixes(&remote->fetch, + &transport_ls_refs_options.ref_prefixes); if (option_branch) - expand_ref_prefix(&ref_prefixes, option_branch); + expand_ref_prefix(&transport_ls_refs_options.ref_prefixes, + option_branch); if (!option_no_tags) - argv_array_push(&ref_prefixes, "refs/tags/"); + strvec_push(&transport_ls_refs_options.ref_prefixes, + "refs/tags/"); - refs = transport_get_remote_refs(transport, &ref_prefixes); + refs = transport_get_remote_refs(transport, &transport_ls_refs_options); if (refs) { + int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); + + /* + * Now that we know what algorithm the remote side is using, + * let's set ours to the same thing. + */ + initialize_repository_version(hash_algo, 1); + repo_set_hash_algo(the_repository, hash_algo); + mapped_refs = wanted_peer_refs(refs, &remote->fetch); /* * transport_get_remote_refs() may return refs with null sha-1 @@ -1222,8 +1211,10 @@ break; } - if (!is_local && !complete_refs_before_fetch) - transport_fetch_refs(transport, mapped_refs); + if (!is_local && !complete_refs_before_fetch) { + if (transport_fetch_refs(transport, mapped_refs)) + die(_("remote transport reported error")); + } remote_head = find_ref_by_name(refs, "HEAD"); remote_head_points_at = @@ -1235,15 +1226,18 @@ if (!our_head_points_at) die(_("Remote branch %s not found in upstream %s"), - option_branch, option_origin); + option_branch, remote_name); } else our_head_points_at = remote_head_points_at; } else { + const char *branch; + char *ref; + if (option_branch) die(_("Remote branch %s not found in upstream %s"), - option_branch, option_origin); + option_branch, remote_name); warning(_("You appear to have cloned an empty repository.")); mapped_refs = NULL; @@ -1251,25 +1245,40 @@ remote_head_points_at = NULL; remote_head = NULL; 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; + transport_ls_refs_options.unborn_head_target = NULL; + create_symref("HEAD", ref, reflog_msg.buf); + } else { + branch = git_default_branch_name(0); + ref = xstrfmt("refs/heads/%s", branch); + } + if (!option_bare) - install_branch_config(0, "master", option_origin, - "refs/heads/master"); + install_branch_config(0, branch, remote_name, ref); + + free(ref); } write_refspec_config(src_ref_prefix, our_head_points_at, remote_head_points_at, &branch_top); if (filter_options.choice) - partial_clone_register(option_origin, &filter_options); + partial_clone_register(remote_name, &filter_options); if (is_local) clone_local(path, git_dir); - else if (refs && complete_refs_before_fetch) - transport_fetch_refs(transport, mapped_refs); + else if (refs && complete_refs_before_fetch) { + if (transport_fetch_refs(transport, mapped_refs)) + die(_("remote transport reported error")); + } update_remote_refs(refs, mapped_refs, remote_head_points_at, branch_top.buf, reflog_msg.buf, transport, - !is_local, filter_options.choice); + !is_local); update_head(our_head_points_at, remote_head, reflog_msg.buf); @@ -1292,12 +1301,18 @@ junk_mode = JUNK_LEAVE_REPO; err = checkout(submodule_progress); + free(remote_name); strbuf_release(&reflog_msg); strbuf_release(&branch_top); strbuf_release(&key); - strbuf_release(&default_refspec); + free_refs(mapped_refs); + free_refs(remote_head_points_at); + free(dir); + free(path); + UNLEAK(repo); junk_mode = JUNK_LEAVE_ALL; - argv_array_clear(&ref_prefixes); + strvec_clear(&transport_ls_refs_options.ref_prefixes); + free(transport_ls_refs_options.unborn_head_target); return err; } diff -Nru git-2.25.1/builtin/column.c git-2.34.1/builtin/column.c --- git-2.25.1/builtin/column.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/column.c 2021-11-24 19:29:21.000000000 +0000 @@ -27,10 +27,10 @@ OPT_STRING(0, "command", &real_command, N_("name"), N_("lookup config vars")), OPT_COLUMN(0, "mode", &colopts, N_("layout to use")), OPT_INTEGER(0, "raw-mode", &colopts, N_("layout to use")), - OPT_INTEGER(0, "width", &copts.width, N_("Maximum width")), - OPT_STRING(0, "indent", &copts.indent, N_("string"), N_("Padding space on left border")), - OPT_INTEGER(0, "nl", &copts.nl, N_("Padding space on right border")), - OPT_INTEGER(0, "padding", &copts.padding, N_("Padding space between columns")), + OPT_INTEGER(0, "width", &copts.width, N_("maximum width")), + OPT_STRING(0, "indent", &copts.indent, N_("string"), N_("padding space on left border")), + OPT_STRING(0, "nl", &copts.nl, N_("string"), N_("padding space on right border")), + OPT_INTEGER(0, "padding", &copts.padding, N_("padding space between columns")), OPT_END() }; diff -Nru git-2.25.1/builtin/commit.c git-2.34.1/builtin/commit.c --- git-2.25.1/builtin/commit.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/commit.c 2021-11-24 19:29:21.000000000 +0000 @@ -19,6 +19,7 @@ #include "revision.h" #include "wt-status.h" #include "run-command.h" +#include "hook.h" #include "refs.h" #include "log-tree.h" #include "strbuf.h" @@ -59,6 +60,9 @@ " git commit --allow-empty\n" "\n"); +static const char empty_rebase_pick_advice[] = +N_("Otherwise, please use 'git rebase --skip'\n"); + static const char empty_cherry_pick_advice_single[] = N_("Otherwise, please use 'git cherry-pick --skip'\n"); @@ -102,7 +106,8 @@ */ static const char *author_message, *author_message_buffer; static char *edit_message, *use_message; -static char *fixup_message, *squash_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 int edit_flag = -1; /* unspecified */ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; @@ -110,6 +115,7 @@ static int no_post_rewrite, allow_empty_message, pathspec_file_nul; static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg; static char *sign_commit, *pathspec_from_file; +static struct strvec trailer_args = STRVEC_INIT; /* * The default commit message cleanup mode will remove the lines @@ -122,13 +128,20 @@ static const char *cleanup_arg; static enum commit_whence whence; -static int sequencer_in_use; static int use_editor = 1, include_status = 1; static int have_option_m; static struct strbuf message = STRBUF_INIT; static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED; +static int opt_pass_trailer(const struct option *opt, const char *arg, int unset) +{ + BUG_ON_OPT_NEG(unset); + + strvec_pushl(&trailer_args, "--trailer", arg, NULL); + return 0; +} + static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset) { enum wt_status_format *value = (enum wt_status_format *)opt->value; @@ -179,12 +192,7 @@ { if (file_exists(git_path_merge_head(the_repository))) whence = FROM_MERGE; - else if (file_exists(git_path_cherry_pick_head(the_repository))) { - whence = FROM_CHERRY_PICK; - if (file_exists(git_path_seq_dir())) - sequencer_in_use = 1; - } - else + else if (!sequencer_determine_whence(the_repository, &whence)) whence = FROM_COMMIT; if (s) s->whence = whence; @@ -196,7 +204,7 @@ init_diff_ui_defaults(); git_config(fn, s); determine_whence(s); - s->hints = advice_status_hints; /* must come after git_config() */ + s->hints = advice_enabled(ADVICE_STATUS_HINTS); /* must come after git_config() */ } static void rollback_index_files(void) @@ -254,6 +262,8 @@ free(max_prefix); } + /* TODO: audit for interaction with sparse-index. */ + ensure_full_index(&the_index); for (i = 0; i < active_nr; i++) { const struct cache_entry *ce = active_cache[i]; struct string_list_item *item; @@ -329,7 +339,7 @@ die_resolve_conflict("commit"); } -static const char *prepare_index(int argc, const char **argv, const char *prefix, +static const char *prepare_index(const char **argv, const char *prefix, const struct commit *current_head, int is_status) { struct string_list partial = STRING_LIST_INIT_DUP; @@ -360,14 +370,15 @@ die(_("--pathspec-file-nul requires --pathspec-from-file")); } - if (!pathspec.nr && (also || (only && !amend && !allow_empty))) + if (!pathspec.nr && (also || (only && !allow_empty && + (!amend || (fixup_message && strcmp(fixup_prefix, "amend")))))) die(_("No paths with --include/--only does not make sense.")); if (read_cache_preload(&pathspec) < 0) die(_("index file corrupt")); if (interactive) { - char *old_index_env = NULL; + char *old_index_env = NULL, *old_repo_index_file; hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); refresh_cache_or_die(refresh_flags); @@ -375,12 +386,16 @@ if (write_locked_index(&the_index, &index_lock, 0)) die(_("unable to create temporary index")); + old_repo_index_file = the_repository->index_file; + the_repository->index_file = + (char *)get_lock_file_path(&index_lock); old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT)); - setenv(INDEX_ENVIRONMENT, get_lock_file_path(&index_lock), 1); + setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1); - if (interactive_add(argc, argv, prefix, patch_interactive) != 0) + if (interactive_add(argv, prefix, patch_interactive) != 0) die(_("interactive add failed")); + the_repository->index_file = old_repo_index_file; if (old_index_env && *old_index_env) setenv(INDEX_ENVIRONMENT, old_index_env, 1); else @@ -473,8 +488,10 @@ if (whence != FROM_COMMIT) { if (whence == FROM_MERGE) die(_("cannot do a partial commit during a merge.")); - else if (whence == FROM_CHERRY_PICK) + else if (is_from_cherry_pick(whence)) die(_("cannot do a partial commit during a cherry-pick.")); + else if (is_from_rebase(whence)) + die(_("cannot do a partial commit during a rebase.")); } if (list_paths(&partial, !current_head ? NULL : "HEAD", &pathspec)) @@ -678,6 +695,22 @@ comment_line_char = *p; } +static void prepare_amend_commit(struct commit *commit, struct strbuf *sb, + struct pretty_print_context *ctx) +{ + const char *buffer, *subject, *fmt; + + buffer = get_commit_buffer(commit, NULL); + find_commit_subject(buffer, &subject); + /* + * If we amend the 'amend!' commit then we don't want to + * duplicate the subject line. + */ + fmt = starts_with(subject, "amend!") ? "%b" : "%B"; + format_commit_message(commit, fmt, sb, ctx); + unuse_commit_buffer(commit, buffer); +} + static int prepare_to_commit(const char *index_file, const char *prefix, struct commit *current_head, struct wt_status *s, @@ -742,15 +775,33 @@ } else if (fixup_message) { struct pretty_print_context ctx = {0}; struct commit *commit; - commit = lookup_commit_reference_by_name(fixup_message); + char *fmt; + commit = lookup_commit_reference_by_name(fixup_commit); if (!commit) - die(_("could not lookup commit %s"), fixup_message); + die(_("could not lookup commit %s"), fixup_commit); ctx.output_encoding = get_commit_output_encoding(); - format_commit_message(commit, "fixup! %s\n\n", - &sb, &ctx); - if (have_option_m) - strbuf_addbuf(&sb, &message); + fmt = xstrfmt("%s! %%s\n\n", fixup_prefix); + format_commit_message(commit, fmt, &sb, &ctx); + free(fmt); hook_arg1 = "message"; + + /* + * Only `-m` commit message option is checked here, as + * it supports `--fixup` to append the commit message. + * + * The other commit message options `-c`/`-C`/`-F` are + * incompatible with all the forms of `--fixup` and + * have already errored out while parsing the `git commit` + * options. + */ + if (have_option_m && !strcmp(fixup_prefix, "fixup")) + strbuf_addbuf(&sb, &message); + + if (!strcmp(fixup_prefix, "amend")) { + if (have_option_m) + die(_("cannot combine -m with --fixup:%s"), fixup_message); + prepare_amend_commit(commit, &sb, &ctx); + } } else if (!stat(git_path_merge_msg(the_repository), &statbuf)) { size_t merge_msg_start; @@ -791,7 +842,7 @@ */ else if (whence == FROM_MERGE) hook_arg1 = "merge"; - else if (whence == FROM_CHERRY_PICK) { + else if (is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) { hook_arg1 = "commit"; hook_arg2 = "CHERRY_PICK_HEAD"; } @@ -839,44 +890,49 @@ int ident_shown = 0; int saved_color_setting; struct ident_split ci, ai; - + const char *hint_cleanup_all = allow_empty_message ? + _("Please enter the commit message for your changes." + " Lines starting\nwith '%c' will be ignored.\n") : + _("Please enter the commit message for your changes." + " Lines starting\nwith '%c' will be ignored, and an empty" + " message aborts the commit.\n"); + const char *hint_cleanup_space = allow_empty_message ? + _("Please enter the commit message for your changes." + " Lines starting\n" + "with '%c' will be kept; you may remove them" + " yourself if you want to.\n") : + _("Please enter the commit message for your changes." + " Lines starting\n" + "with '%c' will be kept; you may remove them" + " yourself if you want to.\n" + "An empty message aborts the commit.\n"); if (whence != FROM_COMMIT) { if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS && !merge_contains_scissors) wt_status_add_cut_line(s->fp); - status_printf_ln(s, GIT_COLOR_NORMAL, - whence == FROM_MERGE - ? _("\n" - "It looks like you may be committing a merge.\n" - "If this is not correct, please remove the file\n" - " %s\n" - "and try again.\n") - : _("\n" - "It looks like you may be committing a cherry-pick.\n" - "If this is not correct, please remove the file\n" - " %s\n" - "and try again.\n"), + status_printf_ln( + s, GIT_COLOR_NORMAL, whence == FROM_MERGE ? - git_path_merge_head(the_repository) : - git_path_cherry_pick_head(the_repository)); + _("\n" + "It looks like you may be committing a merge.\n" + "If this is not correct, please run\n" + " git update-ref -d MERGE_HEAD\n" + "and try again.\n") : + _("\n" + "It looks like you may be committing a cherry-pick.\n" + "If this is not correct, please run\n" + " git update-ref -d CHERRY_PICK_HEAD\n" + "and try again.\n")); } fprintf(s->fp, "\n"); if (cleanup_mode == COMMIT_MSG_CLEANUP_ALL) - status_printf(s, GIT_COLOR_NORMAL, - _("Please enter the commit message for your changes." - " Lines starting\nwith '%c' will be ignored, and an empty" - " message aborts the commit.\n"), comment_line_char); + status_printf(s, GIT_COLOR_NORMAL, hint_cleanup_all, comment_line_char); else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) { if (whence == FROM_COMMIT && !merge_contains_scissors) wt_status_add_cut_line(s->fp); } else /* COMMIT_MSG_CLEANUP_SPACE, that is. */ - status_printf(s, GIT_COLOR_NORMAL, - _("Please enter the commit message for your changes." - " Lines starting\n" - "with '%c' will be kept; you may remove them" - " yourself if you want to.\n" - "An empty message aborts the commit.\n"), comment_line_char); + status_printf(s, GIT_COLOR_NORMAL, hint_cleanup_space, comment_line_char); /* * These should never fail because they come from our own @@ -930,6 +986,8 @@ if (get_oid(parent, &oid)) { int i, ita_nr = 0; + /* TODO: audit for interaction with sparse-index. */ + ensure_full_index(&the_index); for (i = 0; i < active_nr; i++) if (ce_intent_to_add(active_cache[i])) ita_nr++; @@ -957,6 +1015,18 @@ fclose(s->fp); + if (trailer_args.nr) { + struct child_process run_trailer = CHILD_PROCESS_INIT; + + strvec_pushl(&run_trailer.args, "interpret-trailers", + "--in-place", git_path_commit_editmsg(), NULL); + strvec_pushv(&run_trailer.args, trailer_args.v); + run_trailer.git_cmd = 1; + if (run_command(&run_trailer)) + die(_("unable to pass trailers to --trailers")); + strvec_clear(&trailer_args); + } + /* * Reject an attempt to record a non-merge empty commit without * explicit --allow-empty. In the cherry-pick case, it may be @@ -964,22 +1034,25 @@ */ if (!committable && whence != FROM_MERGE && !allow_empty && !(amend && is_a_merge(current_head))) { - s->hints = advice_status_hints; + s->hints = advice_enabled(ADVICE_STATUS_HINTS); s->display_comment_prefix = old_display_comment_prefix; run_status(stdout, index_file, prefix, 0, s); if (amend) fputs(_(empty_amend_advice), stderr); - else if (whence == FROM_CHERRY_PICK) { + else if (is_from_cherry_pick(whence) || + whence == FROM_REBASE_PICK) { fputs(_(empty_cherry_pick_advice), stderr); - if (!sequencer_in_use) + if (whence == FROM_CHERRY_PICK_SINGLE) fputs(_(empty_cherry_pick_advice_single), stderr); - else + else if (whence == FROM_CHERRY_PICK_MULTI) fputs(_(empty_cherry_pick_advice_multi), stderr); + else + fputs(_(empty_rebase_pick_advice), stderr); } return 0; } - if (!no_verify && find_hook("pre-commit")) { + if (!no_verify && hook_exists("pre-commit")) { /* * Re-read the index as pre-commit hook could have updated it, * and write it out as a tree. We must do this before we invoke @@ -999,15 +1072,15 @@ return 0; if (use_editor) { - struct argv_array env = ARGV_ARRAY_INIT; + struct strvec env = STRVEC_INIT; - argv_array_pushf(&env, "GIT_INDEX_FILE=%s", index_file); - if (launch_editor(git_path_commit_editmsg(), NULL, env.argv)) { + strvec_pushf(&env, "GIT_INDEX_FILE=%s", index_file); + if (launch_editor(git_path_commit_editmsg(), NULL, env.v)) { fprintf(stderr, _("Please supply the message using either -m or -F option.\n")); exit(1); } - argv_array_clear(&env); + strvec_clear(&env); } if (!no_verify && @@ -1035,7 +1108,7 @@ av[++ac] = NULL; setup_revisions(ac, av, &revs, NULL); revs.mailmap = &mailmap; - read_mailmap(revs.mailmap, NULL); + read_mailmap(revs.mailmap); if (prepare_revision_walk(&revs)) die(_("revision walk setup failed")); @@ -1148,6 +1221,19 @@ s->ahead_behind_flags = AHEAD_BEHIND_FULL; } +static void check_fixup_reword_options(int argc, const char *argv[]) { + if (whence != FROM_COMMIT) { + if (whence == FROM_MERGE) + die(_("You are in the middle of a merge -- cannot reword.")); + else if (is_from_cherry_pick(whence)) + die(_("You are in the middle of a cherry-pick -- cannot reword.")); + } + if (argc) + die(_("cannot combine reword option of --fixup with path '%s'"), *argv); + if (patch_interactive || interactive || all || also || only) + die(_("reword option of --fixup is mutually exclusive with --patch/--interactive/--all/--include/--only")); +} + static int parse_and_validate_options(int argc, const char *argv[], const struct option *options, const char * const usage[], @@ -1166,10 +1252,8 @@ if (force_author && renew_authorship) die(_("Using both --reset-author and --author does not make sense")); - if (logfile || have_option_m || use_message || fixup_message) + if (logfile || have_option_m || use_message) use_editor = 0; - if (0 <= edit_flag) - use_editor = edit_flag; /* Sanity check options */ if (amend && !current_head) @@ -1177,8 +1261,10 @@ if (amend && whence != FROM_COMMIT) { if (whence == FROM_MERGE) die(_("You are in the middle of a merge -- cannot amend.")); - else if (whence == FROM_CHERRY_PICK) + else if (is_from_cherry_pick(whence)) die(_("You are in the middle of a cherry-pick -- cannot amend.")); + else if (whence == FROM_REBASE_PICK) + die(_("You are in the middle of a rebase -- cannot amend.")); } if (fixup_message && squash_message) die(_("Options --squash and --fixup cannot be used together")); @@ -1200,7 +1286,8 @@ use_message = edit_message; if (amend && !use_message && !fixup_message) use_message = "HEAD"; - if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship) + if (!use_message && !is_from_cherry_pick(whence) && + !is_from_rebase(whence) && renew_authorship) die(_("--reset-author can be used only with -C, -c or --amend.")); if (use_message) { use_message_buffer = read_commit_message(use_message); @@ -1209,7 +1296,8 @@ author_message_buffer = use_message_buffer; } } - if (whence == FROM_CHERRY_PICK && !renew_authorship) { + if ((is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) && + !renew_authorship) { author_message = "CHERRY_PICK_HEAD"; author_message_buffer = read_commit_message(author_message); } @@ -1219,6 +1307,45 @@ if (also + only + all + interactive > 1) die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); + + if (fixup_message) { + /* + * We limit --fixup's suboptions to only alpha characters. + * If the first character after a run of alpha is colon, + * then the part before the colon may be a known suboption + * name like `amend` or `reword`, or a misspelt suboption + * name. In either case, we treat it as + * --fixup=:. + * + * Otherwise, we are dealing with --fixup=. + */ + char *p = fixup_message; + while (isalpha(*p)) + p++; + if (p > fixup_message && *p == ':') { + *p = '\0'; + fixup_commit = p + 1; + if (!strcmp("amend", fixup_message) || + !strcmp("reword", fixup_message)) { + fixup_prefix = "amend"; + allow_empty = 1; + if (*fixup_message == 'r') { + check_fixup_reword_options(argc, argv); + only = 1; + } + } else { + die(_("unknown option: --fixup=%s:%s"), fixup_message, fixup_commit); + } + } else { + fixup_commit = fixup_message; + fixup_prefix = "fixup"; + use_editor = 0; + } + } + + if (0 <= edit_flag) + use_editor = edit_flag; + cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor); handle_untracked_files_arg(s); @@ -1233,13 +1360,13 @@ return argc; } -static int dry_run_commit(int argc, const char **argv, const char *prefix, +static int dry_run_commit(const char **argv, const char *prefix, const struct commit *current_head, struct wt_status *s) { int committable; const char *index_file; - index_file = prepare_index(argc, argv, prefix, current_head, 1); + index_file = prepare_index(argv, prefix, current_head, 1); committable = run_status(stdout, index_file, prefix, 0, s); rollback_index_files(); @@ -1362,9 +1489,9 @@ N_("show stash information")), OPT_BOOL(0, "ahead-behind", &s.ahead_behind_flags, N_("compute full ahead/behind values")), - { OPTION_CALLBACK, 0, "porcelain", &status_format, + OPT_CALLBACK_F(0, "porcelain", &status_format, N_("version"), N_("machine-readable output"), - PARSE_OPT_OPTARG, opt_parse_porcelain }, + PARSE_OPT_OPTARG, opt_parse_porcelain), OPT_SET_INT(0, "long", &status_format, N_("show status in long format (default)"), STATUS_FORMAT_LONG), @@ -1383,15 +1510,18 @@ PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, OPT_COLUMN(0, "column", &s.colopts, N_("list untracked files in columns")), OPT_BOOL(0, "no-renames", &no_renames, N_("do not detect renames")), - { OPTION_CALLBACK, 'M', "find-renames", &rename_score_arg, + OPT_CALLBACK_F('M', "find-renames", &rename_score_arg, N_("n"), N_("detect renames, optionally set similarity index"), - PARSE_OPT_OPTARG | PARSE_OPT_NONEG, opt_parse_rename_score }, + PARSE_OPT_OPTARG | PARSE_OPT_NONEG, opt_parse_rename_score), OPT_END(), }; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_status_usage, builtin_status_options); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + status_init_config(&s, git_status_config); argc = parse_options(argc, argv, prefix, builtin_status_options, @@ -1484,7 +1614,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix) { - const char *argv_gc_auto[] = {"gc", "--auto", NULL}; static struct wt_status s; static struct option builtin_commit_options[] = { OPT__QUIET(&quiet, N_("suppress summary after successful commit")), @@ -1497,10 +1626,15 @@ OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m), OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")), OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")), - OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")), + /* + * TRANSLATORS: Leave "[(amend|reword):]" as-is, + * and only translate . + */ + OPT_STRING(0, "fixup", &fixup_message, N_("[(amend|reword):]commit"), N_("use autosquash formatted message to fixup or amend/reword specified commit")), OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")), OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")), - OPT_BOOL('s', "signoff", &signoff, N_("add Signed-off-by:")), + OPT_CALLBACK_F(0, "trailer", NULL, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer), + OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")), OPT_FILENAME('t', "template", &template_file, N_("use specified template file")), OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")), OPT_CLEANUP(&cleanup_arg), @@ -1557,6 +1691,9 @@ if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_commit_usage, builtin_commit_options); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + status_init_config(&s, git_commit_config); s.commit_template = 1; status_format = STATUS_FORMAT_NONE; /* Ignore status.short */ @@ -1577,8 +1714,8 @@ verbose = (config_commit_verbose < 0) ? 0 : config_commit_verbose; if (dry_run) - return dry_run_commit(argc, argv, prefix, current_head, &s); - index_file = prepare_index(argc, argv, prefix, current_head, 0); + return dry_run_commit(argv, prefix, current_head, &s); + index_file = prepare_index(argv, prefix, current_head, 0); /* Set up everything for writing the commit object. This includes running hooks, writing the trees, and interacting with the user. */ @@ -1627,8 +1764,10 @@ reduce_heads_replace(&parents); } else { if (!reflog_msg) - reflog_msg = (whence == FROM_CHERRY_PICK) + reflog_msg = is_from_cherry_pick(whence) ? "commit (cherry-pick)" + : is_from_rebase(whence) + ? "commit (rebase)" : "commit"; commit_list_insert(current_head, &parents); } @@ -1654,8 +1793,21 @@ exit(1); } + if (fixup_message && starts_with(sb.buf, "amend! ") && + !allow_empty_message) { + struct strbuf body = STRBUF_INIT; + size_t len = commit_subject_length(sb.buf); + strbuf_addstr(&body, sb.buf + len); + if (message_is_empty(&body, cleanup_mode)) { + rollback_index_files(); + fprintf(stderr, _("Aborting commit due to empty commit message body.\n")); + exit(1); + } + strbuf_release(&body); + } + if (amend) { - const char *exclude_gpgsig[2] = { "gpgsig", NULL }; + const char *exclude_gpgsig[3] = { "gpgsig", "gpgsig-sha256", NULL }; extra = read_commit_extra_headers(current_head, exclude_gpgsig); } else { struct commit_extra_header **tail = &extra; @@ -1663,8 +1815,8 @@ } if (commit_tree_extended(sb.buf, sb.len, &active_cache_tree->oid, - parents, &oid, author_ident.buf, sign_commit, - extra)) { + parents, &oid, author_ident.buf, NULL, + sign_commit, extra)) { rollback_index_files(); die(_("failed to write commit object")); } @@ -1688,12 +1840,10 @@ "new_index file. Check that disk is not full and quota is\n" "not exceeded, and then \"git restore --staged :/\" to recover.")); - if (git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) && - write_commit_graph_reachable(get_object_directory(), 0, NULL)) - return 1; + git_test_write_commit_graph_or_die(); repo_rerere(the_repository, 0); - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + run_auto_maintenance(quiet); run_commit_hook(use_editor, get_index_file(), "post-commit", NULL); if (amend && !no_post_rewrite) { commit_post_rewrite(the_repository, current_head, &oid); @@ -1709,6 +1859,8 @@ &oid, flags); } + apply_autostash(git_path_merge_autostash(the_repository)); + UNLEAK(err); UNLEAK(sb); return 0; diff -Nru git-2.25.1/builtin/commit-graph.c git-2.34.1/builtin/commit-graph.c --- git-2.25.1/builtin/commit-graph.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/commit-graph.c 2021-11-24 19:29:21.000000000 +0000 @@ -6,21 +6,32 @@ #include "repository.h" #include "commit-graph.h" #include "object-store.h" +#include "progress.h" +#include "tag.h" -static char const * const builtin_commit_graph_usage[] = { - N_("git commit-graph verify [--object-dir ] [--shallow] [--[no-]progress]"), - N_("git commit-graph write [--object-dir ] [--append|--split] [--reachable|--stdin-packs|--stdin-commits] [--[no-]progress] "), +#define BUILTIN_COMMIT_GRAPH_VERIFY_USAGE \ + N_("git commit-graph verify [--object-dir ] [--shallow] [--[no-]progress]") + +#define BUILTIN_COMMIT_GRAPH_WRITE_USAGE \ + N_("git commit-graph write [--object-dir ] [--append] " \ + "[--split[=]] [--reachable|--stdin-packs|--stdin-commits] " \ + "[--changed-paths] [--[no-]max-new-filters ] [--[no-]progress] " \ + "") + +static const char * builtin_commit_graph_verify_usage[] = { + BUILTIN_COMMIT_GRAPH_VERIFY_USAGE, NULL }; -static const char * const builtin_commit_graph_verify_usage[] = { - N_("git commit-graph verify [--object-dir ] [--shallow] [--[no-]progress]"), +static const char * builtin_commit_graph_write_usage[] = { + BUILTIN_COMMIT_GRAPH_WRITE_USAGE, NULL }; -static const char * const builtin_commit_graph_write_usage[] = { - N_("git commit-graph write [--object-dir ] [--append|--split] [--reachable|--stdin-packs|--stdin-commits] [--[no-]progress] "), - NULL +static char const * const builtin_commit_graph_usage[] = { + BUILTIN_COMMIT_GRAPH_VERIFY_USAGE, + BUILTIN_COMMIT_GRAPH_WRITE_USAGE, + NULL, }; static struct opts_commit_graph { @@ -32,11 +43,25 @@ int split; int shallow; int progress; + int enable_changed_paths; } opts; +static struct option common_opts[] = { + OPT_STRING(0, "object-dir", &opts.obj_dir, + N_("dir"), + N_("the object directory to store the graph")), + OPT_END() +}; + +static struct option *add_common_options(struct option *to) +{ + return parse_options_concat(common_opts, to); +} + static int graph_verify(int argc, const char **argv) { struct commit_graph *graph = NULL; + struct object_directory *odb = NULL; char *graph_name; int open_ok; int fd; @@ -44,21 +69,22 @@ int flags = 0; static struct option builtin_commit_graph_verify_options[] = { - OPT_STRING(0, "object-dir", &opts.obj_dir, - N_("dir"), - N_("The object directory to store the graph")), OPT_BOOL(0, "shallow", &opts.shallow, N_("if the commit-graph is split, only verify the tip file")), - OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")), + OPT_BOOL(0, "progress", &opts.progress, + N_("force progress reporting")), OPT_END(), }; + struct option *options = add_common_options(builtin_commit_graph_verify_options); trace2_cmd_mode("verify"); opts.progress = isatty(2); argc = parse_options(argc, argv, NULL, - builtin_commit_graph_verify_options, + options, builtin_commit_graph_verify_usage, 0); + if (argc) + usage_with_options(builtin_commit_graph_verify_usage, options); if (!opts.obj_dir) opts.obj_dir = get_object_directory(); @@ -67,17 +93,19 @@ if (opts.progress) flags |= COMMIT_GRAPH_WRITE_PROGRESS; - graph_name = get_commit_graph_filename(opts.obj_dir); + odb = find_odb(the_repository, opts.obj_dir); + graph_name = get_commit_graph_filename(odb); open_ok = open_commit_graph(graph_name, &fd, &st); if (!open_ok && errno != ENOENT) die_errno(_("Could not open commit-graph '%s'"), graph_name); FREE_AND_NULL(graph_name); + FREE_AND_NULL(options); if (open_ok) - graph = load_commit_graph_one_fd_st(fd, &st); - else - graph = read_commit_graph_one(the_repository, opts.obj_dir); + graph = load_commit_graph_one_fd_st(the_repository, fd, &st, odb); + else + graph = read_commit_graph_one(the_repository, odb); /* Return failure if open_ok predicted success */ if (!graph) @@ -88,20 +116,91 @@ } extern int read_replace_refs; -static struct split_commit_graph_opts split_opts; +static struct commit_graph_opts write_opts; + +static int write_option_parse_split(const struct option *opt, const char *arg, + int unset) +{ + enum commit_graph_split_flags *flags = opt->value; + + BUG_ON_OPT_NEG(unset); + + opts.split = 1; + if (!arg) + return 0; + + if (!strcmp(arg, "no-merge")) + *flags = COMMIT_GRAPH_SPLIT_MERGE_PROHIBITED; + else if (!strcmp(arg, "replace")) + *flags = COMMIT_GRAPH_SPLIT_REPLACE; + else + die(_("unrecognized --split argument, %s"), arg); + + return 0; +} + +static int read_one_commit(struct oidset *commits, struct progress *progress, + const char *hash) +{ + struct object *result; + struct object_id oid; + const char *end; + + if (parse_oid_hex(hash, &oid, &end)) + return error(_("unexpected non-hex object ID: %s"), hash); + + result = deref_tag(the_repository, parse_object(the_repository, &oid), + NULL, 0); + if (!result) + return error(_("invalid object: %s"), hash); + else if (object_as_type(result, OBJ_COMMIT, 1)) + oidset_insert(commits, &result->oid); + + display_progress(progress, oidset_size(commits)); + + return 0; +} + +static int write_option_max_new_filters(const struct option *opt, + const char *arg, + int unset) +{ + int *to = opt->value; + if (unset) + *to = -1; + else { + const char *s; + *to = strtol(arg, (char **)&s, 10); + if (*s) + return error(_("option `%s' expects a numerical value"), + "max-new-filters"); + } + return 0; +} + +static int git_commit_graph_write_config(const char *var, const char *value, + void *cb) +{ + if (!strcmp(var, "commitgraph.maxnewfilters")) + write_opts.max_new_filters = git_config_int(var, value); + /* + * No need to fall-back to 'git_default_config', since this was already + * called in 'cmd_commit_graph()'. + */ + return 0; +} static int graph_write(int argc, const char **argv) { - struct string_list *pack_indexes = NULL; - struct string_list *commit_hex = NULL; - struct string_list lines; + struct string_list pack_indexes = STRING_LIST_INIT_NODUP; + struct strbuf buf = STRBUF_INIT; + struct oidset commits = OIDSET_INIT; + struct object_directory *odb = NULL; int result = 0; enum commit_graph_write_flags flags = 0; + struct progress *progress = NULL; static struct option builtin_commit_graph_write_options[] = { - OPT_STRING(0, "object-dir", &opts.obj_dir, - N_("dir"), - N_("The object directory to store the graph")), OPT_BOOL(0, "reachable", &opts.reachable, N_("start walk at all refs")), OPT_BOOL(0, "stdin-packs", &opts.stdin_packs, @@ -110,28 +209,43 @@ N_("start walk at commits listed by stdin")), OPT_BOOL(0, "append", &opts.append, N_("include all commits already in the commit-graph file")), - OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")), - OPT_BOOL(0, "split", &opts.split, - N_("allow writing an incremental commit-graph file")), - OPT_INTEGER(0, "max-commits", &split_opts.max_commits, + OPT_BOOL(0, "changed-paths", &opts.enable_changed_paths, + N_("enable computation for changed paths")), + OPT_CALLBACK_F(0, "split", &write_opts.split_flags, NULL, + N_("allow writing an incremental commit-graph file"), + PARSE_OPT_OPTARG | PARSE_OPT_NONEG, + write_option_parse_split), + OPT_INTEGER(0, "max-commits", &write_opts.max_commits, N_("maximum number of commits in a non-base split commit-graph")), - OPT_INTEGER(0, "size-multiple", &split_opts.size_multiple, + OPT_INTEGER(0, "size-multiple", &write_opts.size_multiple, N_("maximum ratio between two levels of a split commit-graph")), - OPT_EXPIRY_DATE(0, "expire-time", &split_opts.expire_time, - N_("maximum number of commits in a non-base split commit-graph")), + OPT_EXPIRY_DATE(0, "expire-time", &write_opts.expire_time, + N_("only expire files older than a given date-time")), + OPT_CALLBACK_F(0, "max-new-filters", &write_opts.max_new_filters, + NULL, N_("maximum number of changed-path Bloom filters to compute"), + 0, write_option_max_new_filters), + OPT_BOOL(0, "progress", &opts.progress, + N_("force progress reporting")), OPT_END(), }; + struct option *options = add_common_options(builtin_commit_graph_write_options); opts.progress = isatty(2); - split_opts.size_multiple = 2; - split_opts.max_commits = 0; - split_opts.expire_time = 0; + opts.enable_changed_paths = -1; + write_opts.size_multiple = 2; + write_opts.max_commits = 0; + write_opts.expire_time = 0; + write_opts.max_new_filters = -1; trace2_cmd_mode("write"); + git_config(git_commit_graph_write_config, &opts); + argc = parse_options(argc, argv, NULL, - builtin_commit_graph_write_options, + options, builtin_commit_graph_write_usage, 0); + if (argc) + usage_with_options(builtin_commit_graph_write_usage, options); if (opts.reachable + opts.stdin_packs + opts.stdin_commits > 1) die(_("use at most one of --reachable, --stdin-commits, or --stdin-packs")); @@ -143,71 +257,76 @@ flags |= COMMIT_GRAPH_WRITE_SPLIT; if (opts.progress) flags |= COMMIT_GRAPH_WRITE_PROGRESS; + if (!opts.enable_changed_paths) + flags |= COMMIT_GRAPH_NO_WRITE_BLOOM_FILTERS; + if (opts.enable_changed_paths == 1 || + git_env_bool(GIT_TEST_COMMIT_GRAPH_CHANGED_PATHS, 0)) + flags |= COMMIT_GRAPH_WRITE_BLOOM_FILTERS; - read_replace_refs = 0; + odb = find_odb(the_repository, opts.obj_dir); if (opts.reachable) { - if (write_commit_graph_reachable(opts.obj_dir, flags, &split_opts)) + if (write_commit_graph_reachable(odb, flags, &write_opts)) return 1; return 0; } - string_list_init(&lines, 0); - if (opts.stdin_packs || opts.stdin_commits) { - struct strbuf buf = STRBUF_INIT; - + if (opts.stdin_packs) { while (strbuf_getline(&buf, stdin) != EOF) - string_list_append(&lines, strbuf_detach(&buf, NULL)); - - if (opts.stdin_packs) - pack_indexes = &lines; - if (opts.stdin_commits) { - commit_hex = &lines; - flags |= COMMIT_GRAPH_WRITE_CHECK_OIDS; + string_list_append(&pack_indexes, + strbuf_detach(&buf, NULL)); + } else if (opts.stdin_commits) { + oidset_init(&commits, 0); + if (opts.progress) + progress = start_delayed_progress( + _("Collecting commits from input"), 0); + + while (strbuf_getline(&buf, stdin) != EOF) { + if (read_one_commit(&commits, progress, buf.buf)) { + result = 1; + goto cleanup; + } } - UNLEAK(buf); + stop_progress(&progress); } - if (write_commit_graph(opts.obj_dir, - pack_indexes, - commit_hex, + if (write_commit_graph(odb, + opts.stdin_packs ? &pack_indexes : NULL, + opts.stdin_commits ? &commits : NULL, flags, - &split_opts)) + &write_opts)) result = 1; - UNLEAK(lines); +cleanup: + FREE_AND_NULL(options); + string_list_clear(&pack_indexes, 0); + strbuf_release(&buf); return result; } int cmd_commit_graph(int argc, const char **argv, const char *prefix) { - static struct option builtin_commit_graph_options[] = { - OPT_STRING(0, "object-dir", &opts.obj_dir, - N_("dir"), - N_("The object directory to store the graph")), - OPT_END(), - }; - - if (argc == 2 && !strcmp(argv[1], "-h")) - usage_with_options(builtin_commit_graph_usage, - builtin_commit_graph_options); + struct option *builtin_commit_graph_options = common_opts; git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, builtin_commit_graph_options, builtin_commit_graph_usage, PARSE_OPT_STOP_AT_NON_OPTION); + if (!argc) + goto usage; + read_replace_refs = 0; save_commit_buffer = 0; - if (argc > 0) { - if (!strcmp(argv[0], "verify")) - return graph_verify(argc, argv); - if (!strcmp(argv[0], "write")) - return graph_write(argc, argv); - } + if (!strcmp(argv[0], "verify")) + return graph_verify(argc, argv); + else if (argc && !strcmp(argv[0], "write")) + return graph_write(argc, argv); + error(_("unrecognized subcommand: %s"), argv[0]); +usage: usage_with_options(builtin_commit_graph_usage, builtin_commit_graph_options); } diff -Nru git-2.25.1/builtin/commit-tree.c git-2.34.1/builtin/commit-tree.c --- git-2.25.1/builtin/commit-tree.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/commit-tree.c 2021-11-24 19:29:21.000000000 +0000 @@ -88,9 +88,7 @@ if (!strcmp(arg, "-")) fd = 0; else { - fd = open(arg, O_RDONLY); - if (fd < 0) - die_errno(_("git commit-tree: failed to open '%s'"), arg); + fd = xopen(arg, O_RDONLY); } if (strbuf_read(buf, fd, 0) < 0) die_errno(_("git commit-tree: failed to read '%s'"), arg); @@ -108,15 +106,15 @@ struct object_id commit_oid; struct option options[] = { - { OPTION_CALLBACK, 'p', NULL, &parents, N_("parent"), + OPT_CALLBACK_F('p', NULL, &parents, N_("parent"), N_("id of a parent commit object"), PARSE_OPT_NONEG, - parse_parent_arg_callback }, - { OPTION_CALLBACK, 'm', NULL, &buffer, N_("message"), + parse_parent_arg_callback), + OPT_CALLBACK_F('m', NULL, &buffer, N_("message"), N_("commit message"), PARSE_OPT_NONEG, - parse_message_arg_callback }, - { OPTION_CALLBACK, 'F', NULL, &buffer, N_("file"), + parse_message_arg_callback), + OPT_CALLBACK_F('F', NULL, &buffer, N_("file"), N_("read commit log message from file"), PARSE_OPT_NONEG, - parse_file_arg_callback }, + parse_file_arg_callback), { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, OPT_END() diff -Nru git-2.25.1/builtin/config.c git-2.34.1/builtin/config.c --- git-2.25.1/builtin/config.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/config.c 2021-11-24 19:29:21.000000000 +0000 @@ -14,6 +14,7 @@ static char *key; static regex_t *key_regexp; +static const char *value_pattern; static regex_t *regexp; static int show_keys; static int omit_values; @@ -29,10 +30,12 @@ static struct git_config_source given_config_source; static int actions, type; static char *default_value; -static int end_null; +static int end_nul; static int respect_includes_opt = -1; static struct config_options config_options; static int show_origin; +static int show_scope; +static int fixed_value; #define ACTION_GET (1<<0) #define ACTION_GET_ALL (1<<1) @@ -64,6 +67,7 @@ #define TYPE_PATH 4 #define TYPE_EXPIRY_DATE 5 #define TYPE_COLOR 6 +#define TYPE_BOOL_OR_STR 7 #define OPT_CALLBACK_VALUE(s, l, v, h, i) \ { OPTION_CALLBACK, (s), (l), (v), NULL, (h), PARSE_OPT_NOARG | \ @@ -93,6 +97,8 @@ new_type = TYPE_INT; else if (!strcmp(arg, "bool-or-int")) new_type = TYPE_BOOL_OR_INT; + else if (!strcmp(arg, "bool-or-str")) + new_type = TYPE_BOOL_OR_STR; else if (!strcmp(arg, "path")) new_type = TYPE_PATH; else if (!strcmp(arg, "expiry-date")) @@ -129,17 +135,18 @@ OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")), OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object")), OPT_GROUP(N_("Action")), - OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET), - OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-regex]"), ACTION_GET_ALL), - OPT_BIT(0, "get-regexp", &actions, N_("get values for regexp: name-regex [value-regex]"), ACTION_GET_REGEXP), + OPT_BIT(0, "get", &actions, N_("get value: name [value-pattern]"), ACTION_GET), + OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-pattern]"), ACTION_GET_ALL), + OPT_BIT(0, "get-regexp", &actions, N_("get values for regexp: name-regex [value-pattern]"), ACTION_GET_REGEXP), OPT_BIT(0, "get-urlmatch", &actions, N_("get value specific for the URL: section[.var] URL"), ACTION_GET_URLMATCH), - OPT_BIT(0, "replace-all", &actions, N_("replace all matching variables: name value [value_regex]"), ACTION_REPLACE_ALL), + OPT_BIT(0, "replace-all", &actions, N_("replace all matching variables: name value [value-pattern]"), ACTION_REPLACE_ALL), OPT_BIT(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD), - OPT_BIT(0, "unset", &actions, N_("remove a variable: name [value-regex]"), ACTION_UNSET), - OPT_BIT(0, "unset-all", &actions, N_("remove all matches: name [value-regex]"), ACTION_UNSET_ALL), + OPT_BIT(0, "unset", &actions, N_("remove a variable: name [value-pattern]"), ACTION_UNSET), + OPT_BIT(0, "unset-all", &actions, N_("remove all matches: name [value-pattern]"), ACTION_UNSET_ALL), OPT_BIT(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION), OPT_BIT(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION), OPT_BIT('l', "list", &actions, N_("list all"), ACTION_LIST), + OPT_BOOL(0, "fixed-value", &fixed_value, N_("use string equality when comparing values to 'value-pattern'")), OPT_BIT('e', "edit", &actions, N_("open an editor"), ACTION_EDIT), OPT_BIT(0, "get-color", &actions, N_("find the color configured: slot [default]"), ACTION_GET_COLOR), OPT_BIT(0, "get-colorbool", &actions, N_("find the color setting: slot [stdout-is-tty]"), ACTION_GET_COLORBOOL), @@ -148,13 +155,15 @@ OPT_CALLBACK_VALUE(0, "bool", &type, N_("value is \"true\" or \"false\""), TYPE_BOOL), OPT_CALLBACK_VALUE(0, "int", &type, N_("value is decimal number"), TYPE_INT), OPT_CALLBACK_VALUE(0, "bool-or-int", &type, N_("value is --bool or --int"), TYPE_BOOL_OR_INT), + OPT_CALLBACK_VALUE(0, "bool-or-str", &type, N_("value is --bool or string"), TYPE_BOOL_OR_STR), OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH), OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE), OPT_GROUP(N_("Other")), - OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")), + OPT_BOOL('z', "null", &end_nul, N_("terminate values with NUL byte")), OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")), OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")), OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")), + OPT_BOOL(0, "show-scope", &show_scope, N_("show scope of config (worktree, local, global, system, command)")), OPT_STRING(0, "default", &default_value, N_("value"), N_("with --get, use default value when missing entry")), OPT_END(), }; @@ -178,22 +187,34 @@ static void show_config_origin(struct strbuf *buf) { - const char term = end_null ? '\0' : '\t'; + const char term = end_nul ? '\0' : '\t'; strbuf_addstr(buf, current_config_origin_type()); strbuf_addch(buf, ':'); - if (end_null) + if (end_nul) strbuf_addstr(buf, current_config_name()); else quote_c_style(current_config_name(), buf, NULL, 0); strbuf_addch(buf, term); } +static void show_config_scope(struct strbuf *buf) +{ + const char term = end_nul ? '\0' : '\t'; + const char *scope = config_scope_name(current_config_scope()); + + strbuf_addstr(buf, N_(scope)); + strbuf_addch(buf, term); +} + static int show_all_config(const char *key_, const char *value_, void *cb) { - if (show_origin) { + if (show_origin || show_scope) { struct strbuf buf = STRBUF_INIT; - show_config_origin(&buf); + if (show_scope) + show_config_scope(&buf); + if (show_origin) + show_config_origin(&buf); /* Use fwrite as "buf" can contain \0's if "end_null" is set. */ fwrite(buf.buf, 1, buf.len, stdout); strbuf_release(&buf); @@ -213,6 +234,8 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value_) { + if (show_scope) + show_config_scope(buf); if (show_origin) show_config_origin(buf); if (show_keys) @@ -234,6 +257,12 @@ strbuf_addstr(buf, v ? "true" : "false"); else strbuf_addf(buf, "%d", v); + } else if (type == TYPE_BOOL_OR_STR) { + int v = git_parse_maybe_bool(value_); + if (v < 0) + strbuf_addstr(buf, value_); + else + strbuf_addstr(buf, v ? "true" : "false"); } else if (type == TYPE_PATH) { const char *v; if (git_config_pathname(&v, key_, value_) < 0) @@ -270,6 +299,8 @@ return 0; if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0)) return 0; + if (fixed_value && strcmp(value_pattern, (value_?value_:""))) + return 0; if (regexp != NULL && (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0))) return 0; @@ -280,7 +311,7 @@ return format_config(&values->items[values->nr++], key_, value_); } -static int get_value(const char *key_, const char *regex_) +static int get_value(const char *key_, const char *regex_, unsigned flags) { int ret = CONFIG_GENERIC_ERROR; struct strbuf_list values = {NULL}; @@ -317,7 +348,9 @@ } } - if (regex_) { + if (regex_ && (flags & CONFIG_FLAGS_FIXED_VALUE)) + value_pattern = regex_; + else if (regex_) { if (regex_[0] == '!') { do_not_match = 1; regex_++; @@ -395,6 +428,13 @@ else return xstrdup(v ? "true" : "false"); } + if (type == TYPE_BOOL_OR_STR) { + int v = git_parse_maybe_bool(value); + if (v < 0) + return xstrdup(value); + else + return xstrdup(v ? "true" : "false"); + } if (type == TYPE_COLOR) { char v[COLOR_MAXLEN]; if (git_config_color(v, key, value)) @@ -535,7 +575,7 @@ int ret; char *section_tail; struct string_list_item *item; - struct urlmatch_config config = { STRING_LIST_INIT_DUP }; + struct urlmatch_config config = URLMATCH_CONFIG_INIT; struct string_list values = STRING_LIST_INIT_DUP; config.collect_fn = urlmatch_collect_fn; @@ -598,6 +638,7 @@ { int nongit = !startup_info->have_repository; char *value; + int flags = 0; given_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT)); @@ -612,22 +653,27 @@ usage_builtin_config(); } - if (use_local_config && nongit) - die(_("--local can only be used inside a git repository")); + if (nongit) { + if (use_local_config) + die(_("--local can only be used inside a git repository")); + if (given_config_source.blob) + die(_("--blob can only be used inside a git repository")); + if (use_worktree_config) + die(_("--worktree can only be used inside a git repository")); - if (given_config_source.blob && nongit) - die(_("--blob can only be used inside a git repository")); + } if (given_config_source.file && !strcmp(given_config_source.file, "-")) { given_config_source.file = NULL; given_config_source.use_stdin = 1; + given_config_source.scope = CONFIG_SCOPE_COMMAND; } if (use_global_config) { - char *user_config = expand_user_path("~/.gitconfig", 0); - char *xdg_config = xdg_config_home("config"); + char *user_config, *xdg_config; + git_global_config(&user_config, &xdg_config); if (!user_config) /* * It is unknown if HOME/.gitconfig exists, so @@ -637,6 +683,8 @@ */ die(_("$HOME not set")); + given_config_source.scope = CONFIG_SCOPE_GLOBAL; + if (access_or_warn(user_config, R_OK, 0) && xdg_config && !access_or_warn(xdg_config, R_OK, 0)) { given_config_source.file = xdg_config; @@ -646,12 +694,14 @@ free(xdg_config); } } - else if (use_system_config) - given_config_source.file = git_etc_gitconfig(); - else if (use_local_config) + else if (use_system_config) { + given_config_source.file = git_system_config(); + given_config_source.scope = CONFIG_SCOPE_SYSTEM; + } else if (use_local_config) { given_config_source.file = git_pathdup("config"); - else if (use_worktree_config) { - struct worktree **worktrees = get_worktrees(0); + given_config_source.scope = CONFIG_SCOPE_LOCAL; + } else if (use_worktree_config) { + struct worktree **worktrees = get_worktrees(); if (repository_format_worktree_config) given_config_source.file = git_pathdup("config.worktree"); else if (worktrees[0] && worktrees[1]) @@ -662,13 +712,18 @@ "section in \"git help worktree\" for details")); else given_config_source.file = git_pathdup("config"); + given_config_source.scope = CONFIG_SCOPE_LOCAL; free_worktrees(worktrees); } else if (given_config_source.file) { if (!is_absolute_path(given_config_source.file) && prefix) given_config_source.file = prefix_filename(prefix, given_config_source.file); + given_config_source.scope = CONFIG_SCOPE_COMMAND; + } else if (given_config_source.blob) { + given_config_source.scope = CONFIG_SCOPE_COMMAND; } + if (respect_includes_opt == -1) config_options.respect_includes = !given_config_source.file; else @@ -678,7 +733,7 @@ config_options.git_dir = get_git_dir(); } - if (end_null) { + if (end_nul) { term = '\0'; delim = '\n'; key_delim = '\n'; @@ -719,6 +774,42 @@ usage_builtin_config(); } + /* check usage of --fixed-value */ + if (fixed_value) { + int allowed_usage = 0; + + switch (actions) { + /* git config --get */ + case ACTION_GET: + /* git config --get-all */ + case ACTION_GET_ALL: + /* git config --get-regexp */ + case ACTION_GET_REGEXP: + /* git config --unset */ + case ACTION_UNSET: + /* git config --unset-all */ + case ACTION_UNSET_ALL: + allowed_usage = argc > 1 && !!argv[1]; + break; + + /* git config */ + case ACTION_SET_ALL: + /* git config --replace-all */ + case ACTION_REPLACE_ALL: + allowed_usage = argc > 2 && !!argv[2]; + break; + + /* other options don't allow --fixed-value */ + } + + if (!allowed_usage) { + error(_("--fixed-value only applies with 'value-pattern'")); + usage_builtin_config(); + } + + flags |= CONFIG_FLAGS_FIXED_VALUE; + } + if (actions & PAGING_ACTIONS) setup_auto_pager("config", 1); @@ -780,7 +871,8 @@ value = normalize_value(argv[0], argv[1]); UNLEAK(value); return git_config_set_multivar_in_file_gently(given_config_source.file, - argv[0], value, argv[2], 0); + argv[0], value, argv[2], + flags); } else if (actions == ACTION_ADD) { check_write(); @@ -789,7 +881,8 @@ UNLEAK(value); return git_config_set_multivar_in_file_gently(given_config_source.file, argv[0], value, - CONFIG_REGEX_NONE, 0); + CONFIG_REGEX_NONE, + flags); } else if (actions == ACTION_REPLACE_ALL) { check_write(); @@ -797,23 +890,24 @@ value = normalize_value(argv[0], argv[1]); UNLEAK(value); return git_config_set_multivar_in_file_gently(given_config_source.file, - argv[0], value, argv[2], 1); + argv[0], value, argv[2], + flags | CONFIG_FLAGS_MULTI_REPLACE); } else if (actions == ACTION_GET) { check_argc(argc, 1, 2); - return get_value(argv[0], argv[1]); + return get_value(argv[0], argv[1], flags); } else if (actions == ACTION_GET_ALL) { do_all = 1; check_argc(argc, 1, 2); - return get_value(argv[0], argv[1]); + return get_value(argv[0], argv[1], flags); } else if (actions == ACTION_GET_REGEXP) { show_keys = 1; use_key_regexp = 1; do_all = 1; check_argc(argc, 1, 2); - return get_value(argv[0], argv[1]); + return get_value(argv[0], argv[1], flags); } else if (actions == ACTION_GET_URLMATCH) { check_argc(argc, 2, 2); @@ -824,7 +918,8 @@ check_argc(argc, 1, 2); if (argc == 2) return git_config_set_multivar_in_file_gently(given_config_source.file, - argv[0], NULL, argv[1], 0); + argv[0], NULL, argv[1], + flags); else return git_config_set_in_file_gently(given_config_source.file, argv[0], NULL); @@ -833,7 +928,8 @@ check_write(); check_argc(argc, 1, 2); return git_config_set_multivar_in_file_gently(given_config_source.file, - argv[0], NULL, argv[1], 1); + argv[0], NULL, argv[1], + flags | CONFIG_FLAGS_MULTI_REPLACE); } else if (actions == ACTION_RENAME_SECTION) { int ret; diff -Nru git-2.25.1/builtin/credential.c git-2.34.1/builtin/credential.c --- git-2.25.1/builtin/credential.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/credential.c 2021-11-24 19:29:21.000000000 +0000 @@ -1,6 +1,7 @@ #include "git-compat-util.h" #include "credential.h" #include "builtin.h" +#include "config.h" static const char usage_msg[] = "git credential [fill|approve|reject]"; @@ -10,6 +11,8 @@ const char *op; struct credential c = CREDENTIAL_INIT; + git_config(git_default_config, NULL); + if (argc != 2 || !strcmp(argv[1], "-h")) usage(usage_msg); op = argv[1]; diff -Nru git-2.25.1/builtin/credential-cache.c git-2.34.1/builtin/credential-cache.c --- git-2.25.1/builtin/credential-cache.c 1970-01-01 00:00:00.000000000 +0000 +++ git-2.34.1/builtin/credential-cache.c 2021-11-24 19:29:21.000000000 +0000 @@ -0,0 +1,183 @@ +#include "builtin.h" +#include "parse-options.h" + +#ifndef NO_UNIX_SOCKETS + +#include "credential.h" +#include "string-list.h" +#include "unix-socket.h" +#include "run-command.h" + +#define FLAG_SPAWN 0x1 +#define FLAG_RELAY 0x2 + +#ifdef GIT_WINDOWS_NATIVE + +static int connection_closed(int error) +{ + return (error == EINVAL); +} + +static int connection_fatally_broken(int error) +{ + return (error != ENOENT) && (error != ENETDOWN); +} + +#else + +static int connection_closed(int error) +{ + return (error == ECONNRESET); +} + +static int connection_fatally_broken(int error) +{ + return (error != ENOENT) && (error != ECONNREFUSED); +} + +#endif + +static int send_request(const char *socket, const struct strbuf *out) +{ + int got_data = 0; + int fd = unix_stream_connect(socket, 0); + + if (fd < 0) + return -1; + + if (write_in_full(fd, out->buf, out->len) < 0) + die_errno("unable to write to cache daemon"); + shutdown(fd, SHUT_WR); + + while (1) { + char in[1024]; + int r; + + r = read_in_full(fd, in, sizeof(in)); + if (r == 0 || (r < 0 && connection_closed(errno))) + break; + if (r < 0) + die_errno("read error from cache daemon"); + write_or_die(1, in, r); + got_data = 1; + } + close(fd); + return got_data; +} + +static void spawn_daemon(const char *socket) +{ + struct child_process daemon = CHILD_PROCESS_INIT; + char buf[128]; + int r; + + strvec_pushl(&daemon.args, + "credential-cache--daemon", socket, + NULL); + daemon.git_cmd = 1; + daemon.no_stdin = 1; + daemon.out = -1; + + if (start_command(&daemon)) + die_errno("unable to start cache daemon"); + r = read_in_full(daemon.out, buf, sizeof(buf)); + if (r < 0) + die_errno("unable to read result code from cache daemon"); + if (r != 3 || memcmp(buf, "ok\n", 3)) + die("cache daemon did not start: %.*s", r, buf); + close(daemon.out); +} + +static void do_cache(const char *socket, const char *action, int timeout, + int flags) +{ + struct strbuf buf = STRBUF_INIT; + + strbuf_addf(&buf, "action=%s\n", action); + strbuf_addf(&buf, "timeout=%d\n", timeout); + if (flags & FLAG_RELAY) { + if (strbuf_read(&buf, 0, 0) < 0) + die_errno("unable to relay credential"); + } + + if (send_request(socket, &buf) < 0) { + if (connection_fatally_broken(errno)) + die_errno("unable to connect to cache daemon"); + if (flags & FLAG_SPAWN) { + spawn_daemon(socket); + if (send_request(socket, &buf) < 0) + die_errno("unable to connect to cache daemon"); + } + } + strbuf_release(&buf); +} + +static char *get_socket_path(void) +{ + struct stat sb; + char *old_dir, *socket; + old_dir = interpolate_path("~/.git-credential-cache", 0); + if (old_dir && !stat(old_dir, &sb) && S_ISDIR(sb.st_mode)) + socket = xstrfmt("%s/socket", old_dir); + else + socket = xdg_cache_home("credential/socket"); + free(old_dir); + return socket; +} + +int cmd_credential_cache(int argc, const char **argv, const char *prefix) +{ + char *socket_path = NULL; + int timeout = 900; + const char *op; + const char * const usage[] = { + "git credential-cache [] ", + NULL + }; + struct option options[] = { + OPT_INTEGER(0, "timeout", &timeout, + "number of seconds to cache credentials"), + OPT_STRING(0, "socket", &socket_path, "path", + "path of cache-daemon socket"), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, usage, 0); + if (!argc) + usage_with_options(usage, options); + op = argv[0]; + + if (!socket_path) + socket_path = get_socket_path(); + if (!socket_path) + die("unable to find a suitable socket path; use --socket"); + + if (!strcmp(op, "exit")) + do_cache(socket_path, op, timeout, 0); + else if (!strcmp(op, "get") || !strcmp(op, "erase")) + do_cache(socket_path, op, timeout, FLAG_RELAY); + else if (!strcmp(op, "store")) + do_cache(socket_path, op, timeout, FLAG_RELAY|FLAG_SPAWN); + else + ; /* ignore unknown operation */ + + return 0; +} + +#else + +int cmd_credential_cache(int argc, const char **argv, const char *prefix) +{ + const char * const usage[] = { + "git credential-cache [options] ", + "", + "credential-cache is disabled in this build of Git", + NULL + }; + struct option options[] = { OPT_END() }; + + argc = parse_options(argc, argv, prefix, options, usage, 0); + die(_("credential-cache unavailable; no unix socket support")); +} + +#endif /* NO_UNIX_SOCKETS */ diff -Nru git-2.25.1/builtin/credential-cache--daemon.c git-2.34.1/builtin/credential-cache--daemon.c --- git-2.25.1/builtin/credential-cache--daemon.c 1970-01-01 00:00:00.000000000 +0000 +++ git-2.34.1/builtin/credential-cache--daemon.c 2021-11-24 19:29:21.000000000 +0000 @@ -0,0 +1,319 @@ +#include "builtin.h" +#include "parse-options.h" + +#ifndef NO_UNIX_SOCKETS + +#include "config.h" +#include "tempfile.h" +#include "credential.h" +#include "unix-socket.h" + +struct credential_cache_entry { + struct credential item; + timestamp_t expiration; +}; +static struct credential_cache_entry *entries; +static int entries_nr; +static int entries_alloc; + +static void cache_credential(struct credential *c, int timeout) +{ + struct credential_cache_entry *e; + + ALLOC_GROW(entries, entries_nr + 1, entries_alloc); + e = &entries[entries_nr++]; + + /* take ownership of pointers */ + memcpy(&e->item, c, sizeof(*c)); + memset(c, 0, sizeof(*c)); + e->expiration = time(NULL) + timeout; +} + +static struct credential_cache_entry *lookup_credential(const struct credential *c) +{ + int i; + for (i = 0; i < entries_nr; i++) { + struct credential *e = &entries[i].item; + if (credential_match(c, e)) + return &entries[i]; + } + return NULL; +} + +static void remove_credential(const struct credential *c) +{ + struct credential_cache_entry *e; + + e = lookup_credential(c); + if (e) + e->expiration = 0; +} + +static timestamp_t check_expirations(void) +{ + static timestamp_t wait_for_entry_until; + int i = 0; + timestamp_t now = time(NULL); + timestamp_t next = TIME_MAX; + + /* + * Initially give the client 30 seconds to actually contact us + * and store a credential before we decide there's no point in + * keeping the daemon around. + */ + if (!wait_for_entry_until) + wait_for_entry_until = now + 30; + + while (i < entries_nr) { + if (entries[i].expiration <= now) { + entries_nr--; + credential_clear(&entries[i].item); + if (i != entries_nr) + memcpy(&entries[i], &entries[entries_nr], sizeof(*entries)); + /* + * Stick around 30 seconds in case a new credential + * shows up (e.g., because we just removed a failed + * one, and we will soon get the correct one). + */ + wait_for_entry_until = now + 30; + } + else { + if (entries[i].expiration < next) + next = entries[i].expiration; + i++; + } + } + + if (!entries_nr) { + if (wait_for_entry_until <= now) + return 0; + next = wait_for_entry_until; + } + + return next - now; +} + +static int read_request(FILE *fh, struct credential *c, + struct strbuf *action, int *timeout) +{ + static struct strbuf item = STRBUF_INIT; + const char *p; + + strbuf_getline_lf(&item, fh); + if (!skip_prefix(item.buf, "action=", &p)) + return error("client sent bogus action line: %s", item.buf); + strbuf_addstr(action, p); + + strbuf_getline_lf(&item, fh); + if (!skip_prefix(item.buf, "timeout=", &p)) + return error("client sent bogus timeout line: %s", item.buf); + *timeout = atoi(p); + + if (credential_read(c, fh) < 0) + return -1; + return 0; +} + +static void serve_one_client(FILE *in, FILE *out) +{ + struct credential c = CREDENTIAL_INIT; + struct strbuf action = STRBUF_INIT; + int timeout = -1; + + if (read_request(in, &c, &action, &timeout) < 0) + /* ignore error */ ; + else if (!strcmp(action.buf, "get")) { + struct credential_cache_entry *e = lookup_credential(&c); + if (e) { + fprintf(out, "username=%s\n", e->item.username); + fprintf(out, "password=%s\n", e->item.password); + } + } + else if (!strcmp(action.buf, "exit")) { + /* + * It's important that we clean up our socket first, and then + * signal the client only once we have finished the cleanup. + * Calling exit() directly does this, because we clean up in + * our atexit() handler, and then signal the client when our + * process actually ends, which closes the socket and gives + * them EOF. + */ + exit(0); + } + else if (!strcmp(action.buf, "erase")) + remove_credential(&c); + else if (!strcmp(action.buf, "store")) { + if (timeout < 0) + warning("cache client didn't specify a timeout"); + else if (!c.username || !c.password) + warning("cache client gave us a partial credential"); + else { + remove_credential(&c); + cache_credential(&c, timeout); + } + } + else + warning("cache client sent unknown action: %s", action.buf); + + credential_clear(&c); + strbuf_release(&action); +} + +static int serve_cache_loop(int fd) +{ + struct pollfd pfd; + timestamp_t wakeup; + + wakeup = check_expirations(); + if (!wakeup) + return 0; + + pfd.fd = fd; + pfd.events = POLLIN; + if (poll(&pfd, 1, 1000 * wakeup) < 0) { + if (errno != EINTR) + die_errno("poll failed"); + return 1; + } + + if (pfd.revents & POLLIN) { + int client, client2; + FILE *in, *out; + + client = accept(fd, NULL, NULL); + if (client < 0) { + warning_errno("accept failed"); + return 1; + } + client2 = dup(client); + if (client2 < 0) { + warning_errno("dup failed"); + close(client); + return 1; + } + + in = xfdopen(client, "r"); + out = xfdopen(client2, "w"); + serve_one_client(in, out); + fclose(in); + fclose(out); + } + return 1; +} + +static void serve_cache(const char *socket_path, int debug) +{ + struct unix_stream_listen_opts opts = UNIX_STREAM_LISTEN_OPTS_INIT; + int fd; + + fd = unix_stream_listen(socket_path, &opts); + if (fd < 0) + die_errno("unable to bind to '%s'", socket_path); + + printf("ok\n"); + fclose(stdout); + if (!debug) { + if (!freopen("/dev/null", "w", stderr)) + die_errno("unable to point stderr to /dev/null"); + } + + while (serve_cache_loop(fd)) + ; /* nothing */ + + close(fd); +} + +static const char permissions_advice[] = N_( +"The permissions on your socket directory are too loose; other\n" +"users may be able to read your cached credentials. Consider running:\n" +"\n" +" chmod 0700 %s"); +static void init_socket_directory(const char *path) +{ + struct stat st; + char *path_copy = xstrdup(path); + char *dir = dirname(path_copy); + + if (!stat(dir, &st)) { + if (st.st_mode & 077) + die(_(permissions_advice), dir); + } else { + /* + * We must be sure to create the directory with the correct mode, + * not just chmod it after the fact; otherwise, there is a race + * condition in which somebody can chdir to it, sleep, then try to open + * our protected socket. + */ + if (safe_create_leading_directories_const(dir) < 0) + die_errno("unable to create directories for '%s'", dir); + if (mkdir(dir, 0700) < 0) + die_errno("unable to mkdir '%s'", dir); + } + + if (chdir(dir)) + /* + * We don't actually care what our cwd is; we chdir here just to + * be a friendly daemon and avoid tying up our original cwd. + * If this fails, it's OK to just continue without that benefit. + */ + ; + + free(path_copy); +} + +int cmd_credential_cache_daemon(int argc, const char **argv, const char *prefix) +{ + struct tempfile *socket_file; + const char *socket_path; + int ignore_sighup = 0; + static const char *usage[] = { + "git-credential-cache--daemon [opts] ", + NULL + }; + int debug = 0; + const struct option options[] = { + OPT_BOOL(0, "debug", &debug, + N_("print debugging messages to stderr")), + OPT_END() + }; + + git_config_get_bool("credentialcache.ignoresighup", &ignore_sighup); + + argc = parse_options(argc, argv, prefix, options, usage, 0); + socket_path = argv[0]; + + if (!socket_path) + usage_with_options(usage, options); + + if (!is_absolute_path(socket_path)) + die("socket directory must be an absolute path"); + + init_socket_directory(socket_path); + socket_file = register_tempfile(socket_path); + + if (ignore_sighup) + signal(SIGHUP, SIG_IGN); + + serve_cache(socket_path, debug); + delete_tempfile(&socket_file); + + return 0; +} + +#else + +int cmd_credential_cache_daemon(int argc, const char **argv, const char *prefix) +{ + const char * const usage[] = { + "git credential-cache--daemon [options] ", + "", + "credential-cache--daemon is disabled in this build of Git", + NULL + }; + struct option options[] = { OPT_END() }; + + argc = parse_options(argc, argv, prefix, options, usage, 0); + die(_("credential-cache--daemon unavailable; no unix socket support")); +} + +#endif /* NO_UNIX_SOCKET */ diff -Nru git-2.25.1/builtin/credential-store.c git-2.34.1/builtin/credential-store.c --- git-2.25.1/builtin/credential-store.c 1970-01-01 00:00:00.000000000 +0000 +++ git-2.34.1/builtin/credential-store.c 2021-11-24 19:29:21.000000000 +0000 @@ -0,0 +1,199 @@ +#include "builtin.h" +#include "config.h" +#include "lockfile.h" +#include "credential.h" +#include "string-list.h" +#include "parse-options.h" + +static struct lock_file credential_lock; + +static int parse_credential_file(const char *fn, + struct credential *c, + void (*match_cb)(struct credential *), + void (*other_cb)(struct strbuf *)) +{ + FILE *fh; + struct strbuf line = STRBUF_INIT; + struct credential entry = CREDENTIAL_INIT; + int found_credential = 0; + + fh = fopen(fn, "r"); + if (!fh) { + if (errno != ENOENT && errno != EACCES) + die_errno("unable to open %s", fn); + return found_credential; + } + + while (strbuf_getline_lf(&line, fh) != EOF) { + if (!credential_from_url_gently(&entry, line.buf, 1) && + entry.username && entry.password && + credential_match(c, &entry)) { + found_credential = 1; + if (match_cb) { + match_cb(&entry); + break; + } + } + else if (other_cb) + other_cb(&line); + } + + credential_clear(&entry); + strbuf_release(&line); + fclose(fh); + return found_credential; +} + +static void print_entry(struct credential *c) +{ + printf("username=%s\n", c->username); + printf("password=%s\n", c->password); +} + +static void print_line(struct strbuf *buf) +{ + strbuf_addch(buf, '\n'); + write_or_die(get_lock_file_fd(&credential_lock), buf->buf, buf->len); +} + +static void rewrite_credential_file(const char *fn, struct credential *c, + struct strbuf *extra) +{ + int timeout_ms = 1000; + + git_config_get_int("credentialstore.locktimeoutms", &timeout_ms); + if (hold_lock_file_for_update_timeout(&credential_lock, fn, 0, timeout_ms) < 0) + die_errno(_("unable to get credential storage lock in %d ms"), timeout_ms); + if (extra) + print_line(extra); + parse_credential_file(fn, c, NULL, print_line); + if (commit_lock_file(&credential_lock) < 0) + die_errno("unable to write credential store"); +} + +static void store_credential_file(const char *fn, struct credential *c) +{ + struct strbuf buf = STRBUF_INIT; + + strbuf_addf(&buf, "%s://", c->protocol); + strbuf_addstr_urlencode(&buf, c->username, is_rfc3986_unreserved); + strbuf_addch(&buf, ':'); + strbuf_addstr_urlencode(&buf, c->password, is_rfc3986_unreserved); + strbuf_addch(&buf, '@'); + if (c->host) + strbuf_addstr_urlencode(&buf, c->host, is_rfc3986_unreserved); + if (c->path) { + strbuf_addch(&buf, '/'); + strbuf_addstr_urlencode(&buf, c->path, + is_rfc3986_reserved_or_unreserved); + } + + rewrite_credential_file(fn, c, &buf); + strbuf_release(&buf); +} + +static void store_credential(const struct string_list *fns, struct credential *c) +{ + struct string_list_item *fn; + + /* + * Sanity check that what we are storing is actually sensible. + * In particular, we can't make a URL without a protocol field. + * Without either a host or pathname (depending on the scheme), + * we have no primary key. And without a username and password, + * we are not actually storing a credential. + */ + if (!c->protocol || !(c->host || c->path) || !c->username || !c->password) + return; + + for_each_string_list_item(fn, fns) + if (!access(fn->string, F_OK)) { + store_credential_file(fn->string, c); + return; + } + /* + * Write credential to the filename specified by fns->items[0], thus + * creating it + */ + if (fns->nr) + store_credential_file(fns->items[0].string, c); +} + +static void remove_credential(const struct string_list *fns, struct credential *c) +{ + struct string_list_item *fn; + + /* + * Sanity check that we actually have something to match + * against. The input we get is a restrictive pattern, + * so technically a blank credential means "erase everything". + * But it is too easy to accidentally send this, since it is equivalent + * to empty input. So explicitly disallow it, and require that the + * pattern have some actual content to match. + */ + if (!c->protocol && !c->host && !c->path && !c->username) + return; + for_each_string_list_item(fn, fns) + if (!access(fn->string, F_OK)) + rewrite_credential_file(fn->string, c, NULL); +} + +static void lookup_credential(const struct string_list *fns, struct credential *c) +{ + struct string_list_item *fn; + + for_each_string_list_item(fn, fns) + if (parse_credential_file(fn->string, c, print_entry, NULL)) + return; /* Found credential */ +} + +int cmd_credential_store(int argc, const char **argv, const char *prefix) +{ + const char * const usage[] = { + "git credential-store [] ", + NULL + }; + const char *op; + struct credential c = CREDENTIAL_INIT; + struct string_list fns = STRING_LIST_INIT_DUP; + char *file = NULL; + struct option options[] = { + OPT_STRING(0, "file", &file, "path", + "fetch and store credentials in "), + OPT_END() + }; + + umask(077); + + argc = parse_options(argc, (const char **)argv, prefix, options, usage, 0); + if (argc != 1) + usage_with_options(usage, options); + op = argv[0]; + + if (file) { + string_list_append(&fns, file); + } else { + if ((file = interpolate_path("~/.git-credentials", 0))) + string_list_append_nodup(&fns, file); + file = xdg_config_home("credentials"); + if (file) + string_list_append_nodup(&fns, file); + } + if (!fns.nr) + die("unable to set up default path; use --file"); + + if (credential_read(&c, stdin) < 0) + die("unable to read credential"); + + if (!strcmp(op, "get")) + lookup_credential(&fns, &c); + else if (!strcmp(op, "erase")) + remove_credential(&fns, &c); + else if (!strcmp(op, "store")) + store_credential(&fns, &c); + else + ; /* Ignore unknown operation. */ + + string_list_clear(&fns, 0); + return 0; +} diff -Nru git-2.25.1/builtin/describe.c git-2.34.1/builtin/describe.c --- git-2.25.1/builtin/describe.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/describe.c 2021-11-24 19:29:21.000000000 +0000 @@ -12,7 +12,7 @@ #include "revision.h" #include "diff.h" #include "hashmap.h" -#include "argv-array.h" +#include "strvec.h" #include "run-command.h" #include "object-store.h" #include "list-objects.h" @@ -54,6 +54,7 @@ struct tag *tag; unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */ unsigned name_checked:1; + unsigned misnamed:1; struct object_id oid; char *path; }; @@ -132,6 +133,7 @@ e->tag = tag; e->prio = prio; e->name_checked = 0; + e->misnamed = 0; oidcpy(&e->oid, oid); free(e->path); e->path = xstrdup(path); @@ -192,7 +194,7 @@ } /* Is it annotated? */ - if (!peel_ref(path, &peeled)) { + if (!peel_iterated_oid(oid, &peeled)) { is_annotated = !oideq(oid, &peeled); } else { oidcpy(&peeled, oid); @@ -275,10 +277,11 @@ die(_("annotated tag %s not available"), n->path); } if (n->tag && !n->name_checked) { - if (!n->tag->tag) - die(_("annotated tag %s has no embedded name"), n->path); - if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) - warning(_("tag '%s' is really '%s' here"), n->tag->tag, n->path); + if (strcmp(n->tag->tag, all ? n->path + 5 : n->path)) { + warning(_("tag '%s' is externally known as '%s'"), + n->path, n->tag->tag); + n->misnamed = 1; + } n->name_checked = 1; } @@ -314,7 +317,7 @@ * Exact match to an existing ref. */ append_name(n, dst); - if (longformat) + if (n->misnamed || longformat) append_suffix(0, n->tag ? get_tagged_oid(n->tag) : oid, dst); if (suffix) strbuf_addstr(dst, suffix); @@ -376,11 +379,25 @@ if (!(c->object.flags & t->flag_within)) t->depth++; } + /* Stop if last remaining path already covered by best candidate(s) */ if (annotated_cnt && !list) { - if (debug) - fprintf(stderr, _("finished search at %s\n"), - oid_to_hex(&c->object.oid)); - break; + int best_depth = INT_MAX; + unsigned best_within = 0; + for (cur_match = 0; cur_match < match_cnt; cur_match++) { + struct possible_tag *t = &all_matches[cur_match]; + if (t->depth < best_depth) { + best_depth = t->depth; + best_within = t->flag_within; + } else if (t->depth == best_depth) { + best_within |= t->flag_within; + } + } + if ((c->object.flags & best_within) == best_within) { + if (debug) + fprintf(stderr, _("finished search at %s\n"), + oid_to_hex(&c->object.oid)); + break; + } } while (parents) { struct commit *p = parents->item; @@ -449,7 +466,7 @@ } append_name(all_matches[0].name, dst); - if (abbrev) + if (all_matches[0].name->misnamed || abbrev) append_suffix(all_matches[0].depth, &cmit->object.oid, dst); if (suffix) strbuf_addstr(dst, suffix); @@ -484,15 +501,15 @@ static void describe_blob(struct object_id oid, struct strbuf *dst) { struct rev_info revs; - struct argv_array args = ARGV_ARRAY_INIT; - struct process_commit_data pcd = { null_oid, oid, dst, &revs}; + struct strvec args = STRVEC_INIT; + struct process_commit_data pcd = { *null_oid(), oid, dst, &revs}; - argv_array_pushl(&args, "internal: The first arg is not parsed", - "--objects", "--in-commit-order", "--reverse", "HEAD", - NULL); + strvec_pushl(&args, "internal: The first arg is not parsed", + "--objects", "--in-commit-order", "--reverse", "HEAD", + NULL); repo_init_revisions(the_repository, &revs, NULL); - if (setup_revisions(args.argc, args.argv, &revs, NULL) > 1) + if (setup_revisions(args.nr, args.v, &revs, NULL) > 1) BUG("setup_revisions could not handle all args?"); if (prepare_revision_walk(&revs)) @@ -577,26 +594,26 @@ if (contains) { struct string_list_item *item; - struct argv_array args; + struct strvec args; - argv_array_init(&args); - argv_array_pushl(&args, "name-rev", - "--peel-tag", "--name-only", "--no-undefined", - NULL); + strvec_init(&args); + strvec_pushl(&args, "name-rev", + "--peel-tag", "--name-only", "--no-undefined", + NULL); if (always) - argv_array_push(&args, "--always"); + strvec_push(&args, "--always"); if (!all) { - argv_array_push(&args, "--tags"); + strvec_push(&args, "--tags"); for_each_string_list_item(item, &patterns) - argv_array_pushf(&args, "--refs=refs/tags/%s", item->string); + strvec_pushf(&args, "--refs=refs/tags/%s", item->string); for_each_string_list_item(item, &exclude_patterns) - argv_array_pushf(&args, "--exclude=refs/tags/%s", item->string); + strvec_pushf(&args, "--exclude=refs/tags/%s", item->string); } if (argc) - argv_array_pushv(&args, argv); + strvec_pushv(&args, argv); else - argv_array_push(&args, "HEAD"); - return cmd_name_rev(args.argc, args.argv, prefix); + strvec_push(&args, "HEAD"); + return cmd_name_rev(args.nr, args.v, prefix); } hashmap_init(&names, commit_name_neq, NULL, 0); @@ -607,7 +624,7 @@ if (argc == 0) { if (broken) { struct child_process cp = CHILD_PROCESS_INIT; - argv_array_pushv(&cp.args, diff_index_args); + strvec_pushv(&cp.args, diff_index_args); cp.git_cmd = 1; cp.no_stdin = 1; cp.no_stdout = 1; @@ -629,7 +646,7 @@ } else if (dirty) { struct lock_file index_lock = LOCK_INIT; struct rev_info revs; - struct argv_array args = ARGV_ARRAY_INIT; + struct strvec args = STRVEC_INIT; int fd, result; setup_work_tree(); @@ -641,8 +658,8 @@ repo_update_index_if_able(the_repository, &index_lock); repo_init_revisions(the_repository, &revs, prefix); - argv_array_pushv(&args, diff_index_args); - if (setup_revisions(args.argc, args.argv, &revs, NULL) != 1) + strvec_pushv(&args, diff_index_args); + if (setup_revisions(args.nr, args.v, &revs, NULL) != 1) BUG("malformed internal diff-index command line"); result = run_diff_index(&revs, 0); diff -Nru git-2.25.1/builtin/diff.c git-2.34.1/builtin/diff.c --- git-2.25.1/builtin/diff.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/diff.c 2021-11-24 19:29:21.000000000 +0000 @@ -6,24 +6,32 @@ #define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" #include "config.h" +#include "ewah/ewok.h" #include "lockfile.h" #include "color.h" #include "commit.h" #include "blob.h" #include "tag.h" #include "diff.h" +#include "diff-merges.h" #include "diffcore.h" #include "revision.h" #include "log-tree.h" #include "builtin.h" #include "submodule.h" -#include "sha1-array.h" +#include "oid-array.h" #define DIFF_NO_INDEX_EXPLICIT 1 #define DIFF_NO_INDEX_IMPLICIT 2 static const char builtin_diff_usage[] = -"git diff [] [ []] [--] [...]"; +"git diff [] [] [--] [...]\n" +" or: git diff [] --cached [--merge-base] [] [--] [...]\n" +" or: git diff [] [--merge-base] [...] [--] [...]\n" +" or: git diff [] ...] [--] [...]\n" +" or: git diff [] ]\n" +" or: git diff [] --no-index [--] ]\n" +COMMON_DIFF_OPTIONS_HELP; static const char *blob_path(struct object_array_entry *entry) { @@ -90,7 +98,7 @@ stuff_change(&revs->diffopt, blob[0]->mode, canon_mode(st.st_mode), - &blob[0]->item->oid, &null_oid, + &blob[0]->item->oid, null_oid(), 1, 0, blob[0]->path ? blob[0]->path : path, path); @@ -127,11 +135,13 @@ static int builtin_diff_index(struct rev_info *revs, int argc, const char **argv) { - int cached = 0; + unsigned int option = 0; while (1 < argc) { const char *arg = argv[1]; if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged")) - cached = 1; + option |= DIFF_INDEX_CACHED; + else if (!strcmp(arg, "--merge-base")) + option |= DIFF_INDEX_MERGE_BASE; else usage(builtin_diff_usage); argv++; argc--; @@ -144,7 +154,7 @@ revs->max_count != -1 || revs->min_age != -1 || revs->max_age != -1) usage(builtin_diff_usage); - if (!cached) { + if (!(option & DIFF_INDEX_CACHED)) { setup_work_tree(); if (read_cache_preload(&revs->diffopt.pathspec) < 0) { perror("read_cache_preload"); @@ -154,7 +164,7 @@ perror("read_cache"); return -1; } - return run_diff_index(revs, cached); + return run_diff_index(revs, option); } static int builtin_diff_tree(struct rev_info *revs, @@ -163,19 +173,34 @@ struct object_array_entry *ent1) { const struct object_id *(oid[2]); - int swap = 0; + struct object_id mb_oid; + int merge_base = 0; - if (argc > 1) - usage(builtin_diff_usage); + while (1 < argc) { + const char *arg = argv[1]; + if (!strcmp(arg, "--merge-base")) + merge_base = 1; + else + usage(builtin_diff_usage); + argv++; argc--; + } - /* - * We saw two trees, ent0 and ent1. If ent1 is uninteresting, - * swap them. - */ - if (ent1->item->flags & UNINTERESTING) - swap = 1; - oid[swap] = &ent0->item->oid; - oid[1 - swap] = &ent1->item->oid; + if (merge_base) { + diff_get_merge_base(revs, &mb_oid); + oid[0] = &mb_oid; + oid[1] = &revs->pending.objects[1].item->oid; + } else { + int swap = 0; + + /* + * We saw two trees, ent0 and ent1. If ent1 is uninteresting, + * swap them. + */ + if (ent1->item->flags & UNINTERESTING) + swap = 1; + oid[swap] = &ent0->item->oid; + oid[1 - swap] = &ent1->item->oid; + } diff_tree_oid(oid[0], oid[1], "", &revs->diffopt); log_tree_diff_flush(revs); return 0; @@ -192,12 +217,11 @@ if (argc > 1) usage(builtin_diff_usage); - if (!revs->dense_combined_merges && !revs->combine_merges) - revs->dense_combined_merges = revs->combine_merges = 1; + diff_merges_set_dense_combined_if_unset(revs); + for (i = 1; i < ents; i++) oid_array_append(&parents, &ent[i].item->oid); - diff_tree_combined(&ent[0].item->oid, &parents, - revs->dense_combined_merges, revs); + diff_tree_combined(&ent[0].item->oid, &parents, revs); oid_array_clear(&parents); return 0; } @@ -242,9 +266,9 @@ * dense one, --cc can be explicitly asked for, or just rely * on the default). */ - if (revs->max_count == -1 && !revs->combine_merges && + if (revs->max_count == -1 && (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) - revs->combine_merges = revs->dense_combined_merges = 1; + diff_merges_set_dense_combined_if_unset(revs); setup_work_tree(); if (read_cache_preload(&revs->diffopt.pathspec) < 0) { @@ -254,6 +278,108 @@ return run_diff_files(revs, options); } +struct symdiff { + struct bitmap *skip; + int warn; + const char *base, *left, *right; +}; + +/* + * Check for symmetric-difference arguments, and if present, arrange + * everything we need to know to handle them correctly. As a bonus, + * weed out all bogus range-based revision specifications, e.g., + * "git diff A..B C..D" or "git diff A..B C" get rejected. + * + * For an actual symmetric diff, *symdiff is set this way: + * + * - its skip is non-NULL and marks *all* rev->pending.objects[i] + * indices that the caller should ignore (extra merge bases, of + * which there might be many, and A in A...B). Note that the + * chosen merge base and right side are NOT marked. + * - warn is set if there are multiple merge bases. + * - base, left, and right point to the names to use in a + * warning about multiple merge bases. + * + * If there is no symmetric diff argument, sym->skip is NULL and + * sym->warn is cleared. The remaining fields are not set. + */ +static void symdiff_prepare(struct rev_info *rev, struct symdiff *sym) +{ + int i, is_symdiff = 0, basecount = 0, othercount = 0; + int lpos = -1, rpos = -1, basepos = -1; + struct bitmap *map = NULL; + + /* + * Use the whence fields to find merge bases and left and + * right parts of symmetric difference, so that we do not + * depend on the order that revisions are parsed. If there + * are any revs that aren't from these sources, we have a + * "git diff C A...B" or "git diff A...B C" case. Or we + * could even get "git diff A...B C...E", for instance. + * + * If we don't have just one merge base, we pick one + * at random. + * + * NB: REV_CMD_LEFT, REV_CMD_RIGHT are also used for A..B, + * so we must check for SYMMETRIC_LEFT too. The two arrays + * rev->pending.objects and rev->cmdline.rev are parallel. + */ + for (i = 0; i < rev->cmdline.nr; i++) { + struct object *obj = rev->pending.objects[i].item; + switch (rev->cmdline.rev[i].whence) { + case REV_CMD_MERGE_BASE: + if (basepos < 0) + basepos = i; + basecount++; + break; /* do mark all bases */ + case REV_CMD_LEFT: + if (lpos >= 0) + usage(builtin_diff_usage); + lpos = i; + if (obj->flags & SYMMETRIC_LEFT) { + is_symdiff = 1; + break; /* do mark A */ + } + continue; + case REV_CMD_RIGHT: + if (rpos >= 0) + usage(builtin_diff_usage); + rpos = i; + continue; /* don't mark B */ + case REV_CMD_PARENTS_ONLY: + case REV_CMD_REF: + case REV_CMD_REV: + othercount++; + continue; + } + if (map == NULL) + map = bitmap_new(); + bitmap_set(map, i); + } + + /* + * Forbid any additional revs for both A...B and A..B. + */ + if (lpos >= 0 && othercount > 0) + usage(builtin_diff_usage); + + if (!is_symdiff) { + bitmap_free(map); + sym->warn = 0; + sym->skip = NULL; + return; + } + + sym->left = rev->pending.objects[lpos].name; + sym->right = rev->pending.objects[rpos].name; + if (basecount == 0) + die(_("%s...%s: no merge base"), sym->left, sym->right); + sym->base = rev->pending.objects[basepos].name; + bitmap_unset(map, basepos); /* unmark the base we want */ + sym->warn = basecount > 1; + sym->skip = map; +} + int cmd_diff(int argc, const char **argv, const char *prefix) { int i; @@ -263,19 +389,29 @@ struct object_array_entry *blob[2]; int nongit = 0, no_index = 0; int result = 0; + struct symdiff sdiff; /* * We could get N tree-ish in the rev.pending_objects list. - * Also there could be M blobs there, and P pathspecs. + * Also there could be M blobs there, and P pathspecs. --cached may + * also be present. * * N=0, M=0: - * cache vs files (diff-files) + * cache vs files (diff-files) + * + * N=0, M=0, --cached: + * HEAD vs cache (diff-index --cached) + * * N=0, M=2: * compare two random blobs. P must be zero. + * * N=0, M=1, P=1: - * compare a blob with a working tree file. + * compare a blob with a working tree file. * * N=1, M=0: + * tree vs files (diff-index) + * + * N=1, M=0, --cached: * tree vs cache (diff-index --cached) * * N=2, M=0: @@ -317,7 +453,7 @@ init_diff_ui_defaults(); git_config(git_diff_ui_config, NULL); - precompose_argv(argc, argv); + prefix = precompose_argv_prefix(argc, argv, prefix); repo_init_revisions(the_repository, &rev, prefix); @@ -355,6 +491,7 @@ } rev.diffopt.flags.recursive = 1; + rev.diffopt.rotate_to_strict = 1; setup_diff_pager(&rev.diffopt); @@ -382,6 +519,7 @@ } } + symdiff_prepare(&rev, &sdiff); for (i = 0; i < rev.pending.nr; i++) { struct object_array_entry *entry = &rev.pending.objects[i]; struct object *obj = entry->item; @@ -396,6 +534,8 @@ obj = &get_commit_tree(((struct commit *)obj))->object; if (obj->type == OBJ_TREE) { + if (sdiff.skip && bitmap_get(sdiff.skip, i)) + continue; obj->flags |= flags; add_object_array(obj, name, &ent); } else if (obj->type == OBJ_BLOB) { @@ -437,21 +577,12 @@ usage(builtin_diff_usage); else if (ent.nr == 1) result = builtin_diff_index(&rev, argc, argv); - else if (ent.nr == 2) + else if (ent.nr == 2) { + if (sdiff.warn) + warning(_("%s...%s: multiple merge bases, using %s"), + sdiff.left, sdiff.right, sdiff.base); result = builtin_diff_tree(&rev, argc, argv, &ent.objects[0], &ent.objects[1]); - else if (ent.objects[0].item->flags & UNINTERESTING) { - /* - * diff A...B where there is at least one merge base - * between A and B. We have ent.objects[0] == - * merge-base, ent.objects[ents-2] == A, and - * ent.objects[ents-1] == B. Show diff between the - * base and B. Note that we pick one merge base at - * random if there are more than one. - */ - result = builtin_diff_tree(&rev, argc, argv, - &ent.objects[0], - &ent.objects[ent.nr-1]); } else result = builtin_diff_combined(&rev, argc, argv, ent.objects, ent.nr); diff -Nru git-2.25.1/builtin/diff-files.c git-2.34.1/builtin/diff-files.c --- git-2.25.1/builtin/diff-files.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/diff-files.c 2021-11-24 19:29:21.000000000 +0000 @@ -7,6 +7,7 @@ #include "cache.h" #include "config.h" #include "diff.h" +#include "diff-merges.h" #include "commit.h" #include "revision.h" #include "builtin.h" @@ -28,7 +29,14 @@ git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ repo_init_revisions(the_repository, &rev, prefix); rev.abbrev = 0; - precompose_argv(argc, argv); + + /* + * Consider "intent-to-add" files as new by default, unless + * explicitly specified in the command line or anywhere else. + */ + rev.diffopt.ita_invisible_in_index = 1; + + prefix = precompose_argv_prefix(argc, argv, prefix); argc = setup_revisions(argc, argv, &rev, NULL); while (1 < argc && argv[1][0] == '-') { @@ -46,6 +54,7 @@ } if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; + rev.diffopt.rotate_to_strict = 1; /* * Make sure there are NO revision (i.e. pending object) parameter, @@ -62,9 +71,9 @@ * was not asked to. "diff-files -c -p" should not densify * (the user should ask with "diff-files --cc" explicitly). */ - if (rev.max_count == -1 && !rev.combine_merges && + if (rev.max_count == -1 && (rev.diffopt.output_format & DIFF_FORMAT_PATCH)) - rev.combine_merges = rev.dense_combined_merges = 1; + diff_merges_set_dense_combined_if_unset(&rev); if (read_cache_preload(&rev.diffopt.pathspec) < 0) { perror("read_cache_preload"); diff -Nru git-2.25.1/builtin/diff-index.c git-2.34.1/builtin/diff-index.c --- git-2.25.1/builtin/diff-index.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/diff-index.c 2021-11-24 19:29:21.000000000 +0000 @@ -2,6 +2,7 @@ #include "cache.h" #include "config.h" #include "diff.h" +#include "diff-merges.h" #include "commit.h" #include "revision.h" #include "builtin.h" @@ -15,7 +16,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) { struct rev_info rev; - int cached = 0; + unsigned int option = 0; int i; int result; @@ -25,20 +26,32 @@ git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ repo_init_revisions(the_repository, &rev, prefix); rev.abbrev = 0; - precompose_argv(argc, argv); + prefix = precompose_argv_prefix(argc, argv, prefix); + + /* + * We need (some of) diff for merges options (e.g., --cc), and we need + * to avoid conflict with our own meaning of "-m". + */ + diff_merges_suppress_m_parsing(); argc = setup_revisions(argc, argv, &rev, NULL); for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!strcmp(arg, "--cached")) - cached = 1; + option |= DIFF_INDEX_CACHED; + else if (!strcmp(arg, "--merge-base")) + option |= DIFF_INDEX_MERGE_BASE; + else if (!strcmp(arg, "-m")) + rev.match_missing = 1; else usage(diff_cache_usage); } if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; + rev.diffopt.rotate_to_strict = 1; + /* * Make sure there is one revision (i.e. pending object), * and there is no revision filtering parameters. @@ -46,7 +59,7 @@ if (rev.pending.nr != 1 || rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1) usage(diff_cache_usage); - if (!cached) { + if (!(option & DIFF_INDEX_CACHED)) { setup_work_tree(); if (read_cache_preload(&rev.diffopt.pathspec) < 0) { perror("read_cache_preload"); @@ -56,7 +69,7 @@ perror("read_cache"); return -1; } - result = run_diff_index(&rev, cached); + result = run_diff_index(&rev, option); UNLEAK(rev); return diff_result_code(&rev.diffopt, result); } diff -Nru git-2.25.1/builtin/difftool.c git-2.34.1/builtin/difftool.c --- git-2.25.1/builtin/difftool.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/difftool.c 2021-11-24 19:29:21.000000000 +0000 @@ -18,11 +18,12 @@ #include "run-command.h" #include "exec-cmd.h" #include "parse-options.h" -#include "argv-array.h" +#include "strvec.h" #include "strbuf.h" #include "lockfile.h" #include "object-store.h" #include "dir.h" +#include "entry.h" static int trust_exit_code; @@ -210,10 +211,10 @@ strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path); env[0] = index_env.buf; - argv_array_pushl(&update_index.args, - "--git-dir", git_dir, "--work-tree", workdir, - "update-index", "--really-refresh", "-q", - "--unmerged", NULL); + strvec_pushl(&update_index.args, + "--git-dir", git_dir, "--work-tree", workdir, + "update-index", "--really-refresh", "-q", + "--unmerged", NULL); update_index.no_stdin = 1; update_index.no_stdout = 1; update_index.no_stderr = 1; @@ -225,9 +226,9 @@ /* Ignore any errors of update-index */ run_command(&update_index); - argv_array_pushl(&diff_files.args, - "--git-dir", git_dir, "--work-tree", workdir, - "diff-files", "--name-only", "-z", NULL); + strvec_pushl(&diff_files.args, + "--git-dir", git_dir, "--work-tree", workdir, + "diff-files", "--name-only", "-z", NULL); diff_files.no_stdin = 1; diff_files.git_cmd = 1; diff_files.use_shell = 0; @@ -251,16 +252,6 @@ strbuf_release(&buf); } -static NORETURN void exit_cleanup(const char *tmpdir, int exit_code) -{ - struct strbuf buf = STRBUF_INIT; - strbuf_addstr(&buf, tmpdir); - remove_dir_recursively(&buf, 0); - if (exit_code) - warning(_("failed: %d"), exit_code); - exit(exit_code); -} - static int ensure_leading_directories(char *path) { switch (safe_create_leading_directories(path)) { @@ -322,33 +313,60 @@ struct cache_entry *ce; int ret; - ce = make_transient_cache_entry(mode, oid, path, 0); + ce = make_transient_cache_entry(mode, oid, path, 0, NULL); ret = checkout_entry(ce, state, NULL, NULL); discard_cache_entry(ce); return ret; } +static void write_file_in_directory(struct strbuf *dir, size_t dir_len, + const char *path, const char *content) +{ + add_path(dir, dir_len, path); + ensure_leading_directories(dir->buf); + unlink(dir->buf); + write_file(dir->buf, "%s", content); +} + +/* Write the file contents for the left and right sides of the difftool + * dir-diff representation for submodules and symlinks. Symlinks and submodules + * are written as regular text files so that external diff tools can diff them + * as text files, resulting in behavior that is analogous to to what "git diff" + * displays for symlink and submodule diffs. + */ +static void write_standin_files(struct pair_entry *entry, + struct strbuf *ldir, size_t ldir_len, + struct strbuf *rdir, size_t rdir_len) +{ + if (*entry->left) + write_file_in_directory(ldir, ldir_len, entry->path, entry->left); + if (*entry->right) + write_file_in_directory(rdir, rdir_len, entry->path, entry->right); +} + static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, - int argc, const char **argv) + struct child_process *child) { - char tmpdir[PATH_MAX]; struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT; struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT; struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT; struct strbuf wtdir = STRBUF_INIT; - char *lbase_dir, *rbase_dir; + struct strbuf tmpdir = STRBUF_INIT; + char *lbase_dir = NULL, *rbase_dir = NULL; size_t ldir_len, rdir_len, wtdir_len; const char *workdir, *tmp; int ret = 0, i; - FILE *fp; - struct hashmap working_tree_dups, submodules, symlinks2; + FILE *fp = NULL; + struct hashmap working_tree_dups = HASHMAP_INIT(working_tree_entry_cmp, + NULL); + struct hashmap submodules = HASHMAP_INIT(pair_cmp, NULL); + struct hashmap symlinks2 = HASHMAP_INIT(pair_cmp, NULL); struct hashmap_iter iter; struct pair_entry *entry; struct index_state wtindex; struct checkout lstate, rstate; - int rc, flags = RUN_GIT_CMD, err = 0; - struct child_process child = CHILD_PROCESS_INIT; + int flags = RUN_GIT_CMD, err = 0; const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL }; struct hashmap wt_modified, tmp_modified; int indices_loaded = 0; @@ -357,11 +375,15 @@ /* Setup temp directories */ tmp = getenv("TMPDIR"); - xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); - if (!mkdtemp(tmpdir)) - return error("could not create '%s'", tmpdir); - strbuf_addf(&ldir, "%s/left/", tmpdir); - strbuf_addf(&rdir, "%s/right/", tmpdir); + strbuf_add_absolute_path(&tmpdir, tmp ? tmp : "/tmp"); + strbuf_trim_trailing_dir_sep(&tmpdir); + strbuf_addstr(&tmpdir, "/git-difftool.XXXXXX"); + if (!mkdtemp(tmpdir.buf)) { + ret = error("could not create '%s'", tmpdir.buf); + goto finish; + } + strbuf_addf(&ldir, "%s/left/", tmpdir.buf); + strbuf_addf(&rdir, "%s/right/", tmpdir.buf); strbuf_addstr(&wtdir, workdir); if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1])) strbuf_addch(&wtdir, '/'); @@ -383,23 +405,15 @@ rdir_len = rdir.len; wtdir_len = wtdir.len; - hashmap_init(&working_tree_dups, working_tree_entry_cmp, NULL, 0); - hashmap_init(&submodules, pair_cmp, NULL, 0); - hashmap_init(&symlinks2, pair_cmp, NULL, 0); - - child.no_stdin = 1; - child.git_cmd = 1; - child.use_shell = 0; - child.clean_on_exit = 1; - child.dir = prefix; - child.out = -1; - argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z", - NULL); - for (i = 0; i < argc; i++) - argv_array_push(&child.args, argv[i]); - if (start_command(&child)) + child->no_stdin = 1; + child->git_cmd = 1; + child->use_shell = 0; + child->clean_on_exit = 1; + child->dir = prefix; + child->out = -1; + if (start_command(child)) die("could not obtain raw diff"); - fp = xfdopen(child.out, "r"); + fp = xfdopen(child->out, "r"); /* Build index info for left and right sides of the diff */ i = 0; @@ -410,9 +424,9 @@ const char *src_path, *dst_path; if (starts_with(info.buf, "::")) - die(N_("combined diff formats('-c' and '--cc') are " + die(N_("combined diff formats ('-c' and '--cc') are " "not supported in\n" - "directory diff mode('-d' and '--dir-diff').")); + "directory diff mode ('-d' and '--dir-diff').")); if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid, &status)) @@ -525,7 +539,7 @@ fclose(fp); fp = NULL; - if (finish_command(&child)) { + if (finish_command(child)) { ret = error("error occurred running diff --raw"); goto finish; } @@ -540,38 +554,19 @@ */ hashmap_for_each_entry(&submodules, &iter, entry, entry /* member name */) { - if (*entry->left) { - add_path(&ldir, ldir_len, entry->path); - ensure_leading_directories(ldir.buf); - write_file(ldir.buf, "%s", entry->left); - } - if (*entry->right) { - add_path(&rdir, rdir_len, entry->path); - ensure_leading_directories(rdir.buf); - write_file(rdir.buf, "%s", entry->right); - } + write_standin_files(entry, &ldir, ldir_len, &rdir, rdir_len); } /* - * Symbolic links require special treatment.The standard "git diff" + * Symbolic links require special treatment. The standard "git diff" * shows only the link itself, not the contents of the link target. * This loop replicates that behavior. */ hashmap_for_each_entry(&symlinks2, &iter, entry, entry /* member name */) { - if (*entry->left) { - add_path(&ldir, ldir_len, entry->path); - ensure_leading_directories(ldir.buf); - write_file(ldir.buf, "%s", entry->left); - } - if (*entry->right) { - add_path(&rdir, rdir_len, entry->path); - ensure_leading_directories(rdir.buf); - write_file(rdir.buf, "%s", entry->right); - } - } - strbuf_release(&buf); + write_standin_files(entry, &ldir, ldir_len, &rdir, rdir_len); + } strbuf_setlen(&ldir, ldir_len); helper_argv[1] = ldir.buf; @@ -583,7 +578,10 @@ flags = 0; } else setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1); - rc = run_command_v_opt(helper_argv, flags); + ret = run_command_v_opt(helper_argv, flags); + + /* TODO: audit for interaction with sparse-index. */ + ensure_full_index(&wtindex); /* * If the diff includes working copy files and those @@ -614,7 +612,7 @@ if (!indices_loaded) { struct lock_file lock = LOCK_INIT; strbuf_reset(&buf); - strbuf_addf(&buf, "%s/wtindex", tmpdir); + strbuf_addf(&buf, "%s/wtindex", tmpdir.buf); if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 || write_locked_index(&wtindex, &lock, COMMIT_LOCK)) { ret = error("could not write %s", buf.buf); @@ -644,11 +642,14 @@ } if (err) { - warning(_("temporary files exist in '%s'."), tmpdir); + warning(_("temporary files exist in '%s'."), tmpdir.buf); warning(_("you may want to cleanup or recover these.")); - exit(1); - } else - exit_cleanup(tmpdir, rc); + ret = 1; + } else { + remove_dir_recursively(&tmpdir, 0); + if (ret) + warning(_("failed: %d"), ret); + } finish: if (fp) @@ -660,31 +661,29 @@ strbuf_release(&rdir); strbuf_release(&wtdir); strbuf_release(&buf); + strbuf_release(&tmpdir); - return ret; + return (ret < 0) ? 1 : ret; } static int run_file_diff(int prompt, const char *prefix, - int argc, const char **argv) + struct child_process *child) { - struct argv_array args = ARGV_ARRAY_INIT; const char *env[] = { "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL, NULL }; - int ret = 0, i; if (prompt > 0) env[2] = "GIT_DIFFTOOL_PROMPT=true"; else if (!prompt) env[2] = "GIT_DIFFTOOL_NO_PROMPT=true"; + child->git_cmd = 1; + child->dir = prefix; + strvec_pushv(&child->env_array, env); - argv_array_push(&args, "diff"); - for (i = 0; i < argc; i++) - argv_array_push(&args, argv[i]); - ret = run_command_v_opt_cd_env(args.argv, RUN_GIT_CMD, prefix, env); - exit(ret); + return run_command(child); } int cmd_difftool(int argc, const char **argv, const char *prefix) @@ -711,12 +710,13 @@ "`--tool`")), OPT_BOOL(0, "trust-exit-code", &trust_exit_code, N_("make 'git-difftool' exit when an invoked diff " - "tool returns a non - zero exit code")), + "tool returns a non-zero exit code")), OPT_STRING('x', "extcmd", &extcmd, N_("command"), N_("specify a custom command for viewing diffs")), - OPT_ARGUMENT("no-index", &no_index, N_("passed to `diff`")), + OPT_BOOL(0, "no-index", &no_index, N_("passed to `diff`")), OPT_END() }; + struct child_process child = CHILD_PROCESS_INIT; git_config(difftool_config, NULL); symlinks = has_symlinks; @@ -766,7 +766,14 @@ * will invoke a separate instance of 'git-difftool--helper' for * each file that changed. */ + strvec_push(&child.args, "diff"); + if (no_index) + strvec_push(&child.args, "--no-index"); + if (dir_diff) + strvec_pushl(&child.args, "--raw", "--no-abbrev", "-z", NULL); + strvec_pushv(&child.args, argv); + if (dir_diff) - return run_dir_diff(extcmd, symlinks, prefix, argc, argv); - return run_file_diff(prompt, prefix, argc, argv); + return run_dir_diff(extcmd, symlinks, prefix, &child); + return run_file_diff(prompt, prefix, &child); } diff -Nru git-2.25.1/builtin/diff-tree.c git-2.34.1/builtin/diff-tree.c --- git-2.25.1/builtin/diff-tree.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/diff-tree.c 2021-11-24 19:29:21.000000000 +0000 @@ -109,7 +109,9 @@ struct object *tree1, *tree2; static struct rev_info *opt = &log_tree_opt; struct setup_revision_opt s_r_opt; + struct userformat_want w; int read_stdin = 0; + int merge_base = 0; if (argc == 2 && !strcmp(argv[1], "-h")) usage(diff_tree_usage); @@ -124,9 +126,17 @@ memset(&s_r_opt, 0, sizeof(s_r_opt)); s_r_opt.tweak = diff_tree_tweak_rev; - precompose_argv(argc, argv); + prefix = precompose_argv_prefix(argc, argv, prefix); argc = setup_revisions(argc, argv, opt, &s_r_opt); + memset(&w, 0, sizeof(w)); + userformat_find_requirements(NULL, &w); + + if (!opt->show_notes_given && w.notes) + opt->show_notes = 1; + if (opt->show_notes) + load_display_notes(&opt->notes_opt); + while (--argc > 0) { const char *arg = *++argv; @@ -134,9 +144,20 @@ read_stdin = 1; continue; } + if (!strcmp(arg, "--merge-base")) { + merge_base = 1; + continue; + } usage(diff_tree_usage); } + if (read_stdin && merge_base) + die(_("--stdin and --merge-base are mutually exclusive")); + if (merge_base && opt->pending.nr != 2) + die(_("--merge-base only works with two commits")); + + opt->diffopt.rotate_to_strict = 1; + /* * NOTE! We expect "a..b" to expand to "^a b" but it is * perfectly valid for revision range parser to yield "b ^a", @@ -156,7 +177,12 @@ case 2: tree1 = opt->pending.objects[0].item; tree2 = opt->pending.objects[1].item; - if (tree2->flags & UNINTERESTING) { + if (merge_base) { + struct object_id oid; + + diff_get_merge_base(opt, &oid); + tree1 = lookup_object(the_repository, &oid); + } else if (tree2->flags & UNINTERESTING) { SWAP(tree2, tree1); } diff_tree_oid(&tree1->oid, &tree2->oid, "", &opt->diffopt); @@ -168,6 +194,7 @@ int saved_nrl = 0; int saved_dcctc = 0; + opt->diffopt.rotate_to_strict = 0; if (opt->diffopt.detect_rename) { if (!the_index.cache) repo_read_index(the_repository); diff -Nru git-2.25.1/builtin/env--helper.c git-2.34.1/builtin/env--helper.c --- git-2.25.1/builtin/env--helper.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/env--helper.c 2021-11-24 19:29:21.000000000 +0000 @@ -7,18 +7,22 @@ NULL }; -static enum { +enum cmdmode { ENV_HELPER_TYPE_BOOL = 1, ENV_HELPER_TYPE_ULONG -} cmdmode = 0; +}; static int option_parse_type(const struct option *opt, const char *arg, int unset) { + enum cmdmode *cmdmode = opt->value; + + BUG_ON_OPT_NEG(unset); + if (!strcmp(arg, "bool")) - cmdmode = ENV_HELPER_TYPE_BOOL; + *cmdmode = ENV_HELPER_TYPE_BOOL; else if (!strcmp(arg, "ulong")) - cmdmode = ENV_HELPER_TYPE_ULONG; + *cmdmode = ENV_HELPER_TYPE_ULONG; else die(_("unrecognized --type argument, %s"), arg); @@ -33,6 +37,7 @@ int ret; int ret_int, default_int; unsigned long ret_ulong, default_ulong; + enum cmdmode cmdmode = 0; struct option opts[] = { OPT_CALLBACK_F(0, "type", &cmdmode, N_("type"), N_("value is given this type"), PARSE_OPT_NONEG, diff -Nru git-2.25.1/builtin/fast-export.c git-2.34.1/builtin/fast-export.c --- git-2.25.1/builtin/fast-export.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/fast-export.c 2021-11-24 19:29:21.000000000 +0000 @@ -45,6 +45,7 @@ static struct string_list tag_refs = STRING_LIST_INIT_NODUP; static struct refspec refspecs = REFSPEC_INIT_FETCH; static int anonymize; +static struct hashmap anonymized_seeds; static struct revision_sources revision_sources; static int parse_opt_signed_tag_mode(const struct option *opt, @@ -120,24 +121,33 @@ struct anonymized_entry { struct hashmap_entry hash; + const char *anon; + const char orig[FLEX_ARRAY]; +}; + +struct anonymized_entry_key { + struct hashmap_entry hash; const char *orig; size_t orig_len; - const char *anon; - size_t anon_len; }; static int anonymized_entry_cmp(const void *unused_cmp_data, const struct hashmap_entry *eptr, const struct hashmap_entry *entry_or_key, - const void *unused_keydata) + const void *keydata) { const struct anonymized_entry *a, *b; a = container_of(eptr, const struct anonymized_entry, hash); - b = container_of(entry_or_key, const struct anonymized_entry, hash); + if (keydata) { + const struct anonymized_entry_key *key = keydata; + int equal = !strncmp(a->orig, key->orig, key->orig_len) && + !a->orig[key->orig_len]; + return !equal; + } - return a->orig_len != b->orig_len || - memcmp(a->orig, b->orig, a->orig_len); + b = container_of(entry_or_key, const struct anonymized_entry, hash); + return strcmp(a->orig, b->orig); } /* @@ -145,31 +155,39 @@ * the same anonymized string with another. The actual generation * is farmed out to the generate function. */ -static const void *anonymize_mem(struct hashmap *map, - void *(*generate)(const void *, size_t *), - const void *orig, size_t *len) +static const char *anonymize_str(struct hashmap *map, + char *(*generate)(void *), + const char *orig, size_t len, + void *data) { - struct anonymized_entry key, *ret; + struct anonymized_entry_key key; + struct anonymized_entry *ret; if (!map->cmpfn) hashmap_init(map, anonymized_entry_cmp, NULL, 0); - hashmap_entry_init(&key.hash, memhash(orig, *len)); + hashmap_entry_init(&key.hash, memhash(orig, len)); key.orig = orig; - key.orig_len = *len; - ret = hashmap_get_entry(map, &key, hash, NULL); + key.orig_len = len; + /* First check if it's a token the user configured manually... */ + if (anonymized_seeds.cmpfn) + ret = hashmap_get_entry(&anonymized_seeds, &key, hash, &key); + else + ret = NULL; + + /* ...otherwise check if we've already seen it in this context... */ + if (!ret) + ret = hashmap_get_entry(map, &key, hash, &key); + + /* ...and finally generate a new mapping if necessary */ if (!ret) { - ret = xmalloc(sizeof(*ret)); + FLEX_ALLOC_MEM(ret, orig, orig, len); hashmap_entry_init(&ret->hash, key.hash.hash); - ret->orig = xstrdup(orig); - ret->orig_len = *len; - ret->anon = generate(orig, len); - ret->anon_len = *len; + ret->anon = generate(data); hashmap_put(map, &ret->hash); } - *len = ret->anon_len; return ret->anon; } @@ -181,13 +199,13 @@ */ static void anonymize_path(struct strbuf *out, const char *path, struct hashmap *map, - void *(*generate)(const void *, size_t *)) + char *(*generate)(void *)) { while (*path) { const char *end_of_component = strchrnul(path, '/'); size_t len = end_of_component - path; - const char *c = anonymize_mem(map, generate, path, &len); - strbuf_add(out, c, len); + const char *c = anonymize_str(map, generate, path, len, NULL); + strbuf_addstr(out, c); path = end_of_component; if (*path) strbuf_addch(out, *path++); @@ -293,7 +311,8 @@ buf = read_object_file(oid, &type, &size); if (!buf) die("could not read blob %s", oid_to_hex(oid)); - if (check_object_signature(oid, buf, size, type_name(type)) < 0) + if (check_object_signature(the_repository, oid, buf, size, + type_name(type), NULL) < 0) die("oid mismatch in blob %s", oid_to_hex(oid)); object = parse_object_buffer(the_repository, oid, type, size, buf, &eaten); @@ -360,12 +379,12 @@ printf("%s", path); } -static void *anonymize_path_component(const void *path, size_t *len) +static char *anonymize_path_component(void *data) { static int counter; struct strbuf out = STRBUF_INIT; strbuf_addf(&out, "path%d", counter++); - return strbuf_detach(&out, len); + return strbuf_detach(&out, NULL); } static void print_path(const char *path) @@ -382,20 +401,23 @@ } } -static void *generate_fake_oid(const void *old, size_t *len) +static char *generate_fake_oid(void *data) { static uint32_t counter = 1; /* avoid null oid */ const unsigned hashsz = the_hash_algo->rawsz; - unsigned char *out = xcalloc(hashsz, 1); - put_be32(out + hashsz - 4, counter++); - return out; + struct object_id oid; + char *hex = xmallocz(GIT_MAX_HEXSZ); + + oidclr(&oid); + put_be32(oid.hash + hashsz - 4, counter++); + return oid_to_hex_r(hex, &oid); } -static const struct object_id *anonymize_oid(const struct object_id *oid) +static const char *anonymize_oid(const char *oid_hex) { static struct hashmap objs; - size_t len = the_hash_algo->rawsz; - return anonymize_mem(&objs, generate_fake_oid, oid, &len); + size_t len = strlen(oid_hex); + return anonymize_str(&objs, generate_fake_oid, oid_hex, len, NULL); } static void show_filemodify(struct diff_queue_struct *q, @@ -454,9 +476,9 @@ */ if (no_data || S_ISGITLINK(spec->mode)) printf("M %06o %s ", spec->mode, - oid_to_hex(anonymize ? - anonymize_oid(&spec->oid) : - &spec->oid)); + anonymize ? + anonymize_oid(oid_to_hex(&spec->oid)) : + oid_to_hex(&spec->oid)); else { struct object *object = lookup_object(the_repository, &spec->oid); @@ -492,12 +514,12 @@ return bol; } -static void *anonymize_ref_component(const void *old, size_t *len) +static char *anonymize_ref_component(void *data) { static int counter; struct strbuf out = STRBUF_INIT; strbuf_addf(&out, "ref%d", counter++); - return strbuf_detach(&out, len); + return strbuf_detach(&out, NULL); } static const char *anonymize_refname(const char *refname) @@ -516,13 +538,6 @@ static struct strbuf anon = STRBUF_INIT; int i; - /* - * We also leave "master" as a special case, since it does not reveal - * anything interesting. - */ - if (!strcmp(refname, "refs/heads/master")) - return refname; - strbuf_reset(&anon); for (i = 0; i < ARRAY_SIZE(prefixes); i++) { if (skip_prefix(refname, prefixes[i], &refname)) { @@ -545,14 +560,13 @@ return xstrfmt("subject %d\n\nbody\n", counter++); } -static struct hashmap idents; -static void *anonymize_ident(const void *old, size_t *len) +static char *anonymize_ident(void *data) { static int counter; struct strbuf out = STRBUF_INIT; strbuf_addf(&out, "User %d ", counter, counter); counter++; - return strbuf_detach(&out, len); + return strbuf_detach(&out, NULL); } /* @@ -562,6 +576,7 @@ */ static void anonymize_ident_line(const char **beg, const char **end) { + static struct hashmap idents; static struct strbuf buffers[] = { STRBUF_INIT, STRBUF_INIT }; static unsigned which_buffer; @@ -587,9 +602,9 @@ size_t len; len = split.mail_end - split.name_begin; - ident = anonymize_mem(&idents, anonymize_ident, - split.name_begin, &len); - strbuf_add(out, ident, len); + ident = anonymize_str(&idents, anonymize_ident, + split.name_begin, len, NULL); + strbuf_addstr(out, ident); strbuf_addch(out, ' '); strbuf_add(out, split.date_begin, split.tz_end - split.date_begin); } else { @@ -711,9 +726,10 @@ if (mark) printf(":%d\n", mark); else - printf("%s\n", oid_to_hex(anonymize ? - anonymize_oid(&obj->oid) : - &obj->oid)); + printf("%s\n", + anonymize ? + anonymize_oid(oid_to_hex(&obj->oid)) : + oid_to_hex(&obj->oid)); i++; } @@ -728,12 +744,12 @@ show_progress(); } -static void *anonymize_tag(const void *old, size_t *len) +static char *anonymize_tag(void *data) { static int counter; struct strbuf out = STRBUF_INIT; strbuf_addf(&out, "tag message %d", counter++); - return strbuf_detach(&out, len); + return strbuf_detach(&out, NULL); } static void handle_tail(struct object_array *commits, struct rev_info *revs, @@ -803,8 +819,9 @@ name = anonymize_refname(name); if (message) { static struct hashmap tags; - message = anonymize_mem(&tags, anonymize_tag, - message, &message_size); + message = anonymize_str(&tags, anonymize_tag, + message, message_size, NULL); + message_size = strlen(message); } } @@ -854,7 +871,7 @@ p = rewrite_commit((struct commit *)tagged); if (!p) { printf("reset %s\nfrom %s\n\n", - name, oid_to_hex(&null_oid)); + name, oid_to_hex(null_oid())); free(buf); return; } @@ -868,10 +885,9 @@ if (tagged->type == OBJ_TAG) { printf("reset %s\nfrom %s\n\n", - name, oid_to_hex(&null_oid)); + name, oid_to_hex(null_oid())); } - if (starts_with(name, "refs/tags/")) - name += 10; + skip_prefix(name, "refs/tags/", &name); printf("tag %s\n", name); if (mark_tags) { mark_next_object(&tag->object); @@ -908,7 +924,6 @@ if (!tag) die("Tag %s points nowhere?", e->name); return (struct commit *)tag; - break; } default: return NULL; @@ -928,7 +943,7 @@ if (e->flags & UNINTERESTING) continue; - if (dwim_ref(e->name, strlen(e->name), &oid, &full_name) != 1) + if (dwim_ref(e->name, strlen(e->name), &oid, &full_name, 0) != 1) continue; if (refspecs.nr) { @@ -1002,7 +1017,7 @@ * it. */ printf("reset %s\nfrom %s\n\n", - name, oid_to_hex(&null_oid)); + name, oid_to_hex(null_oid())); continue; } @@ -1011,7 +1026,7 @@ /* * Getting here means we have a commit which * was excluded by a negative refspec (e.g. - * fast-export ^master master). If we are + * fast-export ^HEAD HEAD). If we are * referencing excluded commits, set the ref * to the exact commit. Otherwise, the user * wants the branch exported but every commit @@ -1021,7 +1036,7 @@ if (!reference_excluded_commits) { /* delete the ref */ printf("reset %s\nfrom %s\n\n", - name, oid_to_hex(&null_oid)); + name, oid_to_hex(null_oid())); continue; } /* set ref to commit using oid, not mark */ @@ -1132,10 +1147,41 @@ continue; printf("reset %s\nfrom %s\n\n", - refspec->dst, oid_to_hex(&null_oid)); + refspec->dst, oid_to_hex(null_oid())); } } +static char *anonymize_seed(void *data) +{ + return xstrdup(data); +} + +static int parse_opt_anonymize_map(const struct option *opt, + const char *arg, int unset) +{ + struct hashmap *map = opt->value; + const char *delim, *value; + size_t keylen; + + BUG_ON_OPT_NEG(unset); + + delim = strchr(arg, ':'); + if (delim) { + keylen = delim - arg; + value = delim + 1; + } else { + keylen = strlen(arg); + value = arg; + } + + if (!keylen || !*value) + return error(_("--anonymize-map token cannot be empty")); + + anonymize_str(map, anonymize_seed, arg, keylen, (void *)value); + + return 0; +} + int cmd_fast_export(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -1160,29 +1206,32 @@ N_("select handling of commit messages in an alternate encoding"), parse_opt_reencode_mode), OPT_STRING(0, "export-marks", &export_filename, N_("file"), - N_("Dump marks to this file")), + N_("dump marks to this file")), OPT_STRING(0, "import-marks", &import_filename, N_("file"), - N_("Import marks from this file")), + N_("import marks from this file")), OPT_STRING(0, "import-marks-if-exists", &import_filename_if_exists, N_("file"), - N_("Import marks from this file if it exists")), + N_("import marks from this file if it exists")), OPT_BOOL(0, "fake-missing-tagger", &fake_missing_tagger, - N_("Fake a tagger when tags lack one")), + N_("fake a tagger when tags lack one")), OPT_BOOL(0, "full-tree", &full_tree, - N_("Output full tree for each commit")), + N_("output full tree for each commit")), OPT_BOOL(0, "use-done-feature", &use_done_feature, - N_("Use the done feature to terminate the stream")), - OPT_BOOL(0, "no-data", &no_data, N_("Skip output of blob data")), + N_("use the done feature to terminate the stream")), + OPT_BOOL(0, "no-data", &no_data, N_("skip output of blob data")), OPT_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"), - N_("Apply refspec to exported refs")), + N_("apply refspec to exported refs")), OPT_BOOL(0, "anonymize", &anonymize, N_("anonymize output")), + OPT_CALLBACK_F(0, "anonymize-map", &anonymized_seeds, N_("from:to"), + N_("convert to in anonymized output"), + PARSE_OPT_NONEG, parse_opt_anonymize_map), OPT_BOOL(0, "reference-excluded-parents", - &reference_excluded_commits, N_("Reference parents which are not in fast-export stream by object id")), + &reference_excluded_commits, N_("reference parents which are not in fast-export stream by object id")), OPT_BOOL(0, "show-original-ids", &show_original_ids, - N_("Show original object ids of blobs/commits")), + N_("show original object ids of blobs/commits")), OPT_BOOL(0, "mark-tags", &mark_tags, - N_("Label tags with mark ids")), + N_("label tags with mark ids")), OPT_END() }; @@ -1204,6 +1253,9 @@ if (argc > 1) usage_with_options (fast_export_usage, options); + if (anonymized_seeds.cmpfn && !anonymize) + die(_("--anonymize-map without --anonymize does not make sense")); + if (refspecs_list.nr) { int i; diff -Nru git-2.25.1/builtin/fast-import.c git-2.34.1/builtin/fast-import.c --- git-2.25.1/builtin/fast-import.c 1970-01-01 00:00:00.000000000 +0000 +++ git-2.34.1/builtin/fast-import.c 2021-11-24 19:29:21.000000000 +0000 @@ -0,0 +1,3636 @@ +#include "builtin.h" +#include "cache.h" +#include "repository.h" +#include "config.h" +#include "lockfile.h" +#include "object.h" +#include "blob.h" +#include "tree.h" +#include "commit.h" +#include "delta.h" +#include "pack.h" +#include "refs.h" +#include "csum-file.h" +#include "quote.h" +#include "dir.h" +#include "run-command.h" +#include "packfile.h" +#include "object-store.h" +#include "mem-pool.h" +#include "commit-reach.h" +#include "khash.h" + +#define PACK_ID_BITS 16 +#define MAX_PACK_ID ((1<rawsz * 3) + +struct object_entry { + struct pack_idx_entry idx; + struct hashmap_entry ent; + uint32_t type : TYPE_BITS, + pack_id : PACK_ID_BITS, + depth : DEPTH_BITS; +}; + +static int object_entry_hashcmp(const void *map_data, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, + const void *keydata) +{ + const struct object_id *oid = keydata; + const struct object_entry *e1, *e2; + + e1 = container_of(eptr, const struct object_entry, ent); + if (oid) + return oidcmp(&e1->idx.oid, oid); + + e2 = container_of(entry_or_key, const struct object_entry, ent); + return oidcmp(&e1->idx.oid, &e2->idx.oid); +} + +struct object_entry_pool { + struct object_entry_pool *next_pool; + struct object_entry *next_free; + struct object_entry *end; + struct object_entry entries[FLEX_ARRAY]; /* more */ +}; + +struct mark_set { + union { + struct object_id *oids[1024]; + struct object_entry *marked[1024]; + struct mark_set *sets[1024]; + } data; + unsigned int shift; +}; + +struct last_object { + struct strbuf data; + off_t offset; + unsigned int depth; + unsigned no_swap : 1; +}; + +struct atom_str { + struct atom_str *next_atom; + unsigned short str_len; + char str_dat[FLEX_ARRAY]; /* more */ +}; + +struct tree_content; +struct tree_entry { + struct tree_content *tree; + struct atom_str *name; + struct tree_entry_ms { + uint16_t mode; + struct object_id oid; + } versions[2]; +}; + +struct tree_content { + unsigned int entry_capacity; /* must match avail_tree_content */ + unsigned int entry_count; + unsigned int delta_depth; + struct tree_entry *entries[FLEX_ARRAY]; /* more */ +}; + +struct avail_tree_content { + unsigned int entry_capacity; /* must match tree_content */ + struct avail_tree_content *next_avail; +}; + +struct branch { + struct branch *table_next_branch; + struct branch *active_next_branch; + const char *name; + struct tree_entry branch_tree; + uintmax_t last_commit; + uintmax_t num_notes; + unsigned active : 1; + unsigned delete : 1; + unsigned pack_id : PACK_ID_BITS; + struct object_id oid; +}; + +struct tag { + struct tag *next_tag; + const char *name; + unsigned int pack_id; + struct object_id oid; +}; + +struct hash_list { + struct hash_list *next; + struct object_id oid; +}; + +typedef enum { + WHENSPEC_RAW = 1, + WHENSPEC_RAW_PERMISSIVE, + WHENSPEC_RFC2822, + WHENSPEC_NOW +} whenspec_type; + +struct recent_command { + struct recent_command *prev; + struct recent_command *next; + char *buf; +}; + +typedef void (*mark_set_inserter_t)(struct mark_set **s, struct object_id *oid, uintmax_t mark); +typedef void (*each_mark_fn_t)(uintmax_t mark, void *obj, void *cbp); + +/* Configured limits on output */ +static unsigned long max_depth = 50; +static off_t max_packsize; +static int unpack_limit = 100; +static int force_update; + +/* Stats and misc. counters */ +static uintmax_t alloc_count; +static uintmax_t marks_set_count; +static uintmax_t object_count_by_type[1 << TYPE_BITS]; +static uintmax_t duplicate_count_by_type[1 << TYPE_BITS]; +static uintmax_t delta_count_by_type[1 << TYPE_BITS]; +static uintmax_t delta_count_attempts_by_type[1 << TYPE_BITS]; +static unsigned long object_count; +static unsigned long branch_count; +static unsigned long branch_load_count; +static int failure; +static FILE *pack_edges; +static unsigned int show_stats = 1; +static int global_argc; +static const char **global_argv; + +/* Memory pools */ +static struct mem_pool fi_mem_pool = {NULL, 2*1024*1024 - + sizeof(struct mp_block), 0 }; + +/* Atom management */ +static unsigned int atom_table_sz = 4451; +static unsigned int atom_cnt; +static struct atom_str **atom_table; + +/* The .pack file being generated */ +static struct pack_idx_option pack_idx_opts; +static unsigned int pack_id; +static struct hashfile *pack_file; +static struct packed_git *pack_data; +static struct packed_git **all_packs; +static off_t pack_size; + +/* Table of objects we've written. */ +static unsigned int object_entry_alloc = 5000; +static struct object_entry_pool *blocks; +static struct hashmap object_table; +static struct mark_set *marks; +static const char *export_marks_file; +static const char *import_marks_file; +static int import_marks_file_from_stream; +static int import_marks_file_ignore_missing; +static int import_marks_file_done; +static int relative_marks_paths; + +/* Our last blob */ +static struct last_object last_blob = { STRBUF_INIT, 0, 0, 0 }; + +/* Tree management */ +static unsigned int tree_entry_alloc = 1000; +static void *avail_tree_entry; +static unsigned int avail_tree_table_sz = 100; +static struct avail_tree_content **avail_tree_table; +static size_t tree_entry_allocd; +static struct strbuf old_tree = STRBUF_INIT; +static struct strbuf new_tree = STRBUF_INIT; + +/* Branch data */ +static unsigned long max_active_branches = 5; +static unsigned long cur_active_branches; +static unsigned long branch_table_sz = 1039; +static struct branch **branch_table; +static struct branch *active_branches; + +/* Tag data */ +static struct tag *first_tag; +static struct tag *last_tag; + +/* Input stream parsing */ +static whenspec_type whenspec = WHENSPEC_RAW; +static struct strbuf command_buf = STRBUF_INIT; +static int unread_command_buf; +static struct recent_command cmd_hist = {&cmd_hist, &cmd_hist, NULL}; +static struct recent_command *cmd_tail = &cmd_hist; +static struct recent_command *rc_free; +static unsigned int cmd_save = 100; +static uintmax_t next_mark; +static struct strbuf new_data = STRBUF_INIT; +static int seen_data_command; +static int require_explicit_termination; +static int allow_unsafe_features; + +/* Signal handling */ +static volatile sig_atomic_t checkpoint_requested; + +/* Submodule marks */ +static struct string_list sub_marks_from = STRING_LIST_INIT_DUP; +static struct string_list sub_marks_to = STRING_LIST_INIT_DUP; +static kh_oid_map_t *sub_oid_map; + +/* Where to write output of cat-blob commands */ +static int cat_blob_fd = STDOUT_FILENO; + +static void parse_argv(void); +static void parse_get_mark(const char *p); +static void parse_cat_blob(const char *p); +static void parse_ls(const char *p, struct branch *b); + +static void for_each_mark(struct mark_set *m, uintmax_t base, each_mark_fn_t callback, void *p) +{ + uintmax_t k; + if (m->shift) { + for (k = 0; k < 1024; k++) { + if (m->data.sets[k]) + for_each_mark(m->data.sets[k], base + (k << m->shift), callback, p); + } + } else { + for (k = 0; k < 1024; k++) { + if (m->data.marked[k]) + callback(base + k, m->data.marked[k], p); + } + } +} + +static void dump_marks_fn(uintmax_t mark, void *object, void *cbp) { + struct object_entry *e = object; + FILE *f = cbp; + + fprintf(f, ":%" PRIuMAX " %s\n", mark, oid_to_hex(&e->idx.oid)); +} + +static void write_branch_report(FILE *rpt, struct branch *b) +{ + fprintf(rpt, "%s:\n", b->name); + + fprintf(rpt, " status :"); + if (b->active) + fputs(" active", rpt); + if (b->branch_tree.tree) + fputs(" loaded", rpt); + if (is_null_oid(&b->branch_tree.versions[1].oid)) + fputs(" dirty", rpt); + fputc('\n', rpt); + + fprintf(rpt, " tip commit : %s\n", oid_to_hex(&b->oid)); + fprintf(rpt, " old tree : %s\n", + oid_to_hex(&b->branch_tree.versions[0].oid)); + fprintf(rpt, " cur tree : %s\n", + oid_to_hex(&b->branch_tree.versions[1].oid)); + fprintf(rpt, " commit clock: %" PRIuMAX "\n", b->last_commit); + + fputs(" last pack : ", rpt); + if (b->pack_id < MAX_PACK_ID) + fprintf(rpt, "%u", b->pack_id); + fputc('\n', rpt); + + fputc('\n', rpt); +} + +static void write_crash_report(const char *err) +{ + char *loc = git_pathdup("fast_import_crash_%"PRIuMAX, (uintmax_t) getpid()); + FILE *rpt = fopen(loc, "w"); + struct branch *b; + unsigned long lu; + struct recent_command *rc; + + if (!rpt) { + error_errno("can't write crash report %s", loc); + free(loc); + return; + } + + fprintf(stderr, "fast-import: dumping crash report to %s\n", loc); + + fprintf(rpt, "fast-import crash report:\n"); + fprintf(rpt, " fast-import process: %"PRIuMAX"\n", (uintmax_t) getpid()); + fprintf(rpt, " parent process : %"PRIuMAX"\n", (uintmax_t) getppid()); + fprintf(rpt, " at %s\n", show_date(time(NULL), 0, DATE_MODE(ISO8601))); + fputc('\n', rpt); + + fputs("fatal: ", rpt); + fputs(err, rpt); + fputc('\n', rpt); + + fputc('\n', rpt); + fputs("Most Recent Commands Before Crash\n", rpt); + fputs("---------------------------------\n", rpt); + for (rc = cmd_hist.next; rc != &cmd_hist; rc = rc->next) { + if (rc->next == &cmd_hist) + fputs("* ", rpt); + else + fputs(" ", rpt); + fputs(rc->buf, rpt); + fputc('\n', rpt); + } + + fputc('\n', rpt); + fputs("Active Branch LRU\n", rpt); + fputs("-----------------\n", rpt); + fprintf(rpt, " active_branches = %lu cur, %lu max\n", + cur_active_branches, + max_active_branches); + fputc('\n', rpt); + fputs(" pos clock name\n", rpt); + fputs(" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n", rpt); + for (b = active_branches, lu = 0; b; b = b->active_next_branch) + fprintf(rpt, " %2lu) %6" PRIuMAX" %s\n", + ++lu, b->last_commit, b->name); + + fputc('\n', rpt); + fputs("Inactive Branches\n", rpt); + fputs("-----------------\n", rpt); + for (lu = 0; lu < branch_table_sz; lu++) { + for (b = branch_table[lu]; b; b = b->table_next_branch) + write_branch_report(rpt, b); + } + + if (first_tag) { + struct tag *tg; + fputc('\n', rpt); + fputs("Annotated Tags\n", rpt); + fputs("--------------\n", rpt); + for (tg = first_tag; tg; tg = tg->next_tag) { + fputs(oid_to_hex(&tg->oid), rpt); + fputc(' ', rpt); + fputs(tg->name, rpt); + fputc('\n', rpt); + } + } + + fputc('\n', rpt); + fputs("Marks\n", rpt); + fputs("-----\n", rpt); + if (export_marks_file) + fprintf(rpt, " exported to %s\n", export_marks_file); + else + for_each_mark(marks, 0, dump_marks_fn, rpt); + + fputc('\n', rpt); + fputs("-------------------\n", rpt); + fputs("END OF CRASH REPORT\n", rpt); + fclose(rpt); + free(loc); +} + +static void end_packfile(void); +static void unkeep_all_packs(void); +static void dump_marks(void); + +static NORETURN void die_nicely(const char *err, va_list params) +{ + static int zombie; + char message[2 * PATH_MAX]; + + vsnprintf(message, sizeof(message), err, params); + fputs("fatal: ", stderr); + fputs(message, stderr); + fputc('\n', stderr); + + if (!zombie) { + zombie = 1; + write_crash_report(message); + end_packfile(); + unkeep_all_packs(); + dump_marks(); + } + exit(128); +} + +#ifndef SIGUSR1 /* Windows, for example */ + +static void set_checkpoint_signal(void) +{ +} + +#else + +static void checkpoint_signal(int signo) +{ + checkpoint_requested = 1; +} + +static void set_checkpoint_signal(void) +{ + struct sigaction sa; + + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = checkpoint_signal; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sigaction(SIGUSR1, &sa, NULL); +} + +#endif + +static void alloc_objects(unsigned int cnt) +{ + struct object_entry_pool *b; + + b = xmalloc(sizeof(struct object_entry_pool) + + cnt * sizeof(struct object_entry)); + b->next_pool = blocks; + b->next_free = b->entries; + b->end = b->entries + cnt; + blocks = b; + alloc_count += cnt; +} + +static struct object_entry *new_object(struct object_id *oid) +{ + struct object_entry *e; + + if (blocks->next_free == blocks->end) + alloc_objects(object_entry_alloc); + + e = blocks->next_free++; + oidcpy(&e->idx.oid, oid); + return e; +} + +static struct object_entry *find_object(struct object_id *oid) +{ + return hashmap_get_entry_from_hash(&object_table, oidhash(oid), oid, + struct object_entry, ent); +} + +static struct object_entry *insert_object(struct object_id *oid) +{ + struct object_entry *e; + unsigned int hash = oidhash(oid); + + e = hashmap_get_entry_from_hash(&object_table, hash, oid, + struct object_entry, ent); + if (!e) { + e = new_object(oid); + e->idx.offset = 0; + hashmap_entry_init(&e->ent, hash); + hashmap_add(&object_table, &e->ent); + } + + return e; +} + +static void invalidate_pack_id(unsigned int id) +{ + unsigned long lu; + struct tag *t; + struct hashmap_iter iter; + struct object_entry *e; + + hashmap_for_each_entry(&object_table, &iter, e, ent) { + if (e->pack_id == id) + e->pack_id = MAX_PACK_ID; + } + + for (lu = 0; lu < branch_table_sz; lu++) { + struct branch *b; + + for (b = branch_table[lu]; b; b = b->table_next_branch) + if (b->pack_id == id) + b->pack_id = MAX_PACK_ID; + } + + for (t = first_tag; t; t = t->next_tag) + if (t->pack_id == id) + t->pack_id = MAX_PACK_ID; +} + +static unsigned int hc_str(const char *s, size_t len) +{ + unsigned int r = 0; + while (len-- > 0) + r = r * 31 + *s++; + return r; +} + +static void insert_mark(struct mark_set **top, uintmax_t idnum, struct object_entry *oe) +{ + struct mark_set *s = *top; + + while ((idnum >> s->shift) >= 1024) { + s = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set)); + s->shift = (*top)->shift + 10; + s->data.sets[0] = *top; + *top = s; + } + while (s->shift) { + uintmax_t i = idnum >> s->shift; + idnum -= i << s->shift; + if (!s->data.sets[i]) { + s->data.sets[i] = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set)); + s->data.sets[i]->shift = s->shift - 10; + } + s = s->data.sets[i]; + } + if (!s->data.marked[idnum]) + marks_set_count++; + s->data.marked[idnum] = oe; +} + +static void *find_mark(struct mark_set *s, uintmax_t idnum) +{ + uintmax_t orig_idnum = idnum; + struct object_entry *oe = NULL; + if ((idnum >> s->shift) < 1024) { + while (s && s->shift) { + uintmax_t i = idnum >> s->shift; + idnum -= i << s->shift; + s = s->data.sets[i]; + } + if (s) + oe = s->data.marked[idnum]; + } + if (!oe) + die("mark :%" PRIuMAX " not declared", orig_idnum); + return oe; +} + +static struct atom_str *to_atom(const char *s, unsigned short len) +{ + unsigned int hc = hc_str(s, len) % atom_table_sz; + struct atom_str *c; + + for (c = atom_table[hc]; c; c = c->next_atom) + if (c->str_len == len && !strncmp(s, c->str_dat, len)) + return c; + + c = mem_pool_alloc(&fi_mem_pool, sizeof(struct atom_str) + len + 1); + c->str_len = len; + memcpy(c->str_dat, s, len); + c->str_dat[len] = 0; + c->next_atom = atom_table[hc]; + atom_table[hc] = c; + atom_cnt++; + return c; +} + +static struct branch *lookup_branch(const char *name) +{ + unsigned int hc = hc_str(name, strlen(name)) % branch_table_sz; + struct branch *b; + + for (b = branch_table[hc]; b; b = b->table_next_branch) + if (!strcmp(name, b->name)) + return b; + return NULL; +} + +static struct branch *new_branch(const char *name) +{ + unsigned int hc = hc_str(name, strlen(name)) % branch_table_sz; + struct branch *b = lookup_branch(name); + + if (b) + die("Invalid attempt to create duplicate branch: %s", name); + if (check_refname_format(name, REFNAME_ALLOW_ONELEVEL)) + die("Branch name doesn't conform to GIT standards: %s", name); + + b = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct branch)); + b->name = mem_pool_strdup(&fi_mem_pool, name); + b->table_next_branch = branch_table[hc]; + b->branch_tree.versions[0].mode = S_IFDIR; + b->branch_tree.versions[1].mode = S_IFDIR; + b->num_notes = 0; + b->active = 0; + b->pack_id = MAX_PACK_ID; + branch_table[hc] = b; + branch_count++; + return b; +} + +static unsigned int hc_entries(unsigned int cnt) +{ + cnt = cnt & 7 ? (cnt / 8) + 1 : cnt / 8; + return cnt < avail_tree_table_sz ? cnt : avail_tree_table_sz - 1; +} + +static struct tree_content *new_tree_content(unsigned int cnt) +{ + struct avail_tree_content *f, *l = NULL; + struct tree_content *t; + unsigned int hc = hc_entries(cnt); + + for (f = avail_tree_table[hc]; f; l = f, f = f->next_avail) + if (f->entry_capacity >= cnt) + break; + + if (f) { + if (l) + l->next_avail = f->next_avail; + else + avail_tree_table[hc] = f->next_avail; + } else { + cnt = cnt & 7 ? ((cnt / 8) + 1) * 8 : cnt; + f = mem_pool_alloc(&fi_mem_pool, sizeof(*t) + sizeof(t->entries[0]) * cnt); + f->entry_capacity = cnt; + } + + t = (struct tree_content*)f; + t->entry_count = 0; + t->delta_depth = 0; + return t; +} + +static void release_tree_entry(struct tree_entry *e); +static void release_tree_content(struct tree_content *t) +{ + struct avail_tree_content *f = (struct avail_tree_content*)t; + unsigned int hc = hc_entries(f->entry_capacity); + f->next_avail = avail_tree_table[hc]; + avail_tree_table[hc] = f; +} + +static void release_tree_content_recursive(struct tree_content *t) +{ + unsigned int i; + for (i = 0; i < t->entry_count; i++) + release_tree_entry(t->entries[i]); + release_tree_content(t); +} + +static struct tree_content *grow_tree_content( + struct tree_content *t, + int amt) +{ + struct tree_content *r = new_tree_content(t->entry_count + amt); + r->entry_count = t->entry_count; + r->delta_depth = t->delta_depth; + COPY_ARRAY(r->entries, t->entries, t->entry_count); + release_tree_content(t); + return r; +} + +static struct tree_entry *new_tree_entry(void) +{ + struct tree_entry *e; + + if (!avail_tree_entry) { + unsigned int n = tree_entry_alloc; + tree_entry_allocd += n * sizeof(struct tree_entry); + ALLOC_ARRAY(e, n); + avail_tree_entry = e; + while (n-- > 1) { + *((void**)e) = e + 1; + e++; + } + *((void**)e) = NULL; + } + + e = avail_tree_entry; + avail_tree_entry = *((void**)e); + return e; +} + +static void release_tree_entry(struct tree_entry *e) +{ + if (e->tree) + release_tree_content_recursive(e->tree); + *((void**)e) = avail_tree_entry; + avail_tree_entry = e; +} + +static struct tree_content *dup_tree_content(struct tree_content *s) +{ + struct tree_content *d; + struct tree_entry *a, *b; + unsigned int i; + + if (!s) + return NULL; + d = new_tree_content(s->entry_count); + for (i = 0; i < s->entry_count; i++) { + a = s->entries[i]; + b = new_tree_entry(); + memcpy(b, a, sizeof(*a)); + if (a->tree && is_null_oid(&b->versions[1].oid)) + b->tree = dup_tree_content(a->tree); + else + b->tree = NULL; + d->entries[i] = b; + } + d->entry_count = s->entry_count; + d->delta_depth = s->delta_depth; + + return d; +} + +static void start_packfile(void) +{ + struct strbuf tmp_file = STRBUF_INIT; + struct packed_git *p; + int pack_fd; + + pack_fd = odb_mkstemp(&tmp_file, "pack/tmp_pack_XXXXXX"); + FLEX_ALLOC_STR(p, pack_name, tmp_file.buf); + strbuf_release(&tmp_file); + + p->pack_fd = pack_fd; + p->do_not_close = 1; + pack_file = hashfd(pack_fd, p->pack_name); + + pack_data = p; + pack_size = write_pack_header(pack_file, 0); + object_count = 0; + + REALLOC_ARRAY(all_packs, pack_id + 1); + all_packs[pack_id] = p; +} + +static const char *create_index(void) +{ + const char *tmpfile; + struct pack_idx_entry **idx, **c, **last; + struct object_entry *e; + struct object_entry_pool *o; + + /* Build the table of object IDs. */ + ALLOC_ARRAY(idx, object_count); + c = idx; + for (o = blocks; o; o = o->next_pool) + for (e = o->next_free; e-- != o->entries;) + if (pack_id == e->pack_id) + *c++ = &e->idx; + last = idx + object_count; + if (c != last) + die("internal consistency error creating the index"); + + tmpfile = write_idx_file(NULL, idx, object_count, &pack_idx_opts, + pack_data->hash); + free(idx); + return tmpfile; +} + +static char *keep_pack(const char *curr_index_name) +{ + static const char *keep_msg = "fast-import"; + struct strbuf name = STRBUF_INIT; + int keep_fd; + + odb_pack_name(&name, pack_data->hash, "keep"); + keep_fd = odb_pack_keep(name.buf); + if (keep_fd < 0) + die_errno("cannot create keep file"); + write_or_die(keep_fd, keep_msg, strlen(keep_msg)); + if (close(keep_fd)) + die_errno("failed to write keep file"); + + odb_pack_name(&name, pack_data->hash, "pack"); + if (finalize_object_file(pack_data->pack_name, name.buf)) + die("cannot store pack file"); + + odb_pack_name(&name, pack_data->hash, "idx"); + if (finalize_object_file(curr_index_name, name.buf)) + die("cannot store index file"); + free((void *)curr_index_name); + return strbuf_detach(&name, NULL); +} + +static void unkeep_all_packs(void) +{ + struct strbuf name = STRBUF_INIT; + int k; + + for (k = 0; k < pack_id; k++) { + struct packed_git *p = all_packs[k]; + odb_pack_name(&name, p->hash, "keep"); + unlink_or_warn(name.buf); + } + strbuf_release(&name); +} + +static int loosen_small_pack(const struct packed_git *p) +{ + struct child_process unpack = CHILD_PROCESS_INIT; + + if (lseek(p->pack_fd, 0, SEEK_SET) < 0) + die_errno("Failed seeking to start of '%s'", p->pack_name); + + unpack.in = p->pack_fd; + unpack.git_cmd = 1; + unpack.stdout_to_stderr = 1; + strvec_push(&unpack.args, "unpack-objects"); + if (!show_stats) + strvec_push(&unpack.args, "-q"); + + return run_command(&unpack); +} + +static void end_packfile(void) +{ + static int running; + + if (running || !pack_data) + return; + + running = 1; + clear_delta_base_cache(); + if (object_count) { + struct packed_git *new_p; + struct object_id cur_pack_oid; + char *idx_name; + int i; + struct branch *b; + struct tag *t; + + close_pack_windows(pack_data); + finalize_hashfile(pack_file, cur_pack_oid.hash, 0); + fixup_pack_header_footer(pack_data->pack_fd, pack_data->hash, + pack_data->pack_name, object_count, + cur_pack_oid.hash, pack_size); + + if (object_count <= unpack_limit) { + if (!loosen_small_pack(pack_data)) { + invalidate_pack_id(pack_id); + goto discard_pack; + } + } + + close(pack_data->pack_fd); + idx_name = keep_pack(create_index()); + + /* Register the packfile with core git's machinery. */ + new_p = add_packed_git(idx_name, strlen(idx_name), 1); + if (!new_p) + die("core git rejected index %s", idx_name); + all_packs[pack_id] = new_p; + install_packed_git(the_repository, new_p); + free(idx_name); + + /* Print the boundary */ + if (pack_edges) { + fprintf(pack_edges, "%s:", new_p->pack_name); + for (i = 0; i < branch_table_sz; i++) { + for (b = branch_table[i]; b; b = b->table_next_branch) { + if (b->pack_id == pack_id) + fprintf(pack_edges, " %s", + oid_to_hex(&b->oid)); + } + } + for (t = first_tag; t; t = t->next_tag) { + if (t->pack_id == pack_id) + fprintf(pack_edges, " %s", + oid_to_hex(&t->oid)); + } + fputc('\n', pack_edges); + fflush(pack_edges); + } + + pack_id++; + } + else { +discard_pack: + close(pack_data->pack_fd); + unlink_or_warn(pack_data->pack_name); + } + FREE_AND_NULL(pack_data); + running = 0; + + /* We can't carry a delta across packfiles. */ + strbuf_release(&last_blob.data); + last_blob.offset = 0; + last_blob.depth = 0; +} + +static void cycle_packfile(void) +{ + end_packfile(); + start_packfile(); +} + +static int store_object( + enum object_type type, + struct strbuf *dat, + struct last_object *last, + struct object_id *oidout, + uintmax_t mark) +{ + void *out, *delta; + struct object_entry *e; + unsigned char hdr[96]; + struct object_id oid; + unsigned long hdrlen, deltalen; + git_hash_ctx c; + git_zstream s; + + hdrlen = xsnprintf((char *)hdr, sizeof(hdr), "%s %lu", + type_name(type), (unsigned long)dat->len) + 1; + the_hash_algo->init_fn(&c); + the_hash_algo->update_fn(&c, hdr, hdrlen); + the_hash_algo->update_fn(&c, dat->buf, dat->len); + the_hash_algo->final_oid_fn(&oid, &c); + if (oidout) + oidcpy(oidout, &oid); + + e = insert_object(&oid); + if (mark) + insert_mark(&marks, mark, e); + if (e->idx.offset) { + duplicate_count_by_type[type]++; + return 1; + } else if (find_sha1_pack(oid.hash, + get_all_packs(the_repository))) { + e->type = type; + e->pack_id = MAX_PACK_ID; + e->idx.offset = 1; /* just not zero! */ + duplicate_count_by_type[type]++; + return 1; + } + + if (last && last->data.len && last->data.buf && last->depth < max_depth + && dat->len > the_hash_algo->rawsz) { + + delta_count_attempts_by_type[type]++; + delta = diff_delta(last->data.buf, last->data.len, + dat->buf, dat->len, + &deltalen, dat->len - the_hash_algo->rawsz); + } else + delta = NULL; + + git_deflate_init(&s, pack_compression_level); + if (delta) { + s.next_in = delta; + s.avail_in = deltalen; + } else { + s.next_in = (void *)dat->buf; + s.avail_in = dat->len; + } + s.avail_out = git_deflate_bound(&s, s.avail_in); + s.next_out = out = xmalloc(s.avail_out); + while (git_deflate(&s, Z_FINISH) == Z_OK) + ; /* nothing */ + git_deflate_end(&s); + + /* Determine if we should auto-checkpoint. */ + if ((max_packsize + && (pack_size + PACK_SIZE_THRESHOLD + s.total_out) > max_packsize) + || (pack_size + PACK_SIZE_THRESHOLD + s.total_out) < pack_size) { + + /* This new object needs to *not* have the current pack_id. */ + e->pack_id = pack_id + 1; + cycle_packfile(); + + /* We cannot carry a delta into the new pack. */ + if (delta) { + FREE_AND_NULL(delta); + + git_deflate_init(&s, pack_compression_level); + s.next_in = (void *)dat->buf; + s.avail_in = dat->len; + s.avail_out = git_deflate_bound(&s, s.avail_in); + s.next_out = out = xrealloc(out, s.avail_out); + while (git_deflate(&s, Z_FINISH) == Z_OK) + ; /* nothing */ + git_deflate_end(&s); + } + } + + e->type = type; + e->pack_id = pack_id; + e->idx.offset = pack_size; + object_count++; + object_count_by_type[type]++; + + crc32_begin(pack_file); + + if (delta) { + off_t ofs = e->idx.offset - last->offset; + unsigned pos = sizeof(hdr) - 1; + + delta_count_by_type[type]++; + e->depth = last->depth + 1; + + hdrlen = encode_in_pack_object_header(hdr, sizeof(hdr), + OBJ_OFS_DELTA, deltalen); + hashwrite(pack_file, hdr, hdrlen); + pack_size += hdrlen; + + hdr[pos] = ofs & 127; + while (ofs >>= 7) + hdr[--pos] = 128 | (--ofs & 127); + hashwrite(pack_file, hdr + pos, sizeof(hdr) - pos); + pack_size += sizeof(hdr) - pos; + } else { + e->depth = 0; + hdrlen = encode_in_pack_object_header(hdr, sizeof(hdr), + type, dat->len); + hashwrite(pack_file, hdr, hdrlen); + pack_size += hdrlen; + } + + hashwrite(pack_file, out, s.total_out); + pack_size += s.total_out; + + e->idx.crc32 = crc32_end(pack_file); + + free(out); + free(delta); + if (last) { + if (last->no_swap) { + last->data = *dat; + } else { + strbuf_swap(&last->data, dat); + } + last->offset = e->idx.offset; + last->depth = e->depth; + } + return 0; +} + +static void truncate_pack(struct hashfile_checkpoint *checkpoint) +{ + if (hashfile_truncate(pack_file, checkpoint)) + die_errno("cannot truncate pack to skip duplicate"); + pack_size = checkpoint->offset; +} + +static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark) +{ + size_t in_sz = 64 * 1024, out_sz = 64 * 1024; + unsigned char *in_buf = xmalloc(in_sz); + unsigned char *out_buf = xmalloc(out_sz); + struct object_entry *e; + struct object_id oid; + unsigned long hdrlen; + off_t offset; + git_hash_ctx c; + git_zstream s; + struct hashfile_checkpoint checkpoint; + int status = Z_OK; + + /* Determine if we should auto-checkpoint. */ + if ((max_packsize + && (pack_size + PACK_SIZE_THRESHOLD + len) > max_packsize) + || (pack_size + PACK_SIZE_THRESHOLD + len) < pack_size) + cycle_packfile(); + + hashfile_checkpoint(pack_file, &checkpoint); + offset = checkpoint.offset; + + hdrlen = xsnprintf((char *)out_buf, out_sz, "blob %" PRIuMAX, len) + 1; + + the_hash_algo->init_fn(&c); + the_hash_algo->update_fn(&c, out_buf, hdrlen); + + crc32_begin(pack_file); + + git_deflate_init(&s, pack_compression_level); + + hdrlen = encode_in_pack_object_header(out_buf, out_sz, OBJ_BLOB, len); + + s.next_out = out_buf + hdrlen; + s.avail_out = out_sz - hdrlen; + + while (status != Z_STREAM_END) { + if (0 < len && !s.avail_in) { + size_t cnt = in_sz < len ? in_sz : (size_t)len; + size_t n = fread(in_buf, 1, cnt, stdin); + if (!n && feof(stdin)) + die("EOF in data (%" PRIuMAX " bytes remaining)", len); + + the_hash_algo->update_fn(&c, in_buf, n); + s.next_in = in_buf; + s.avail_in = n; + len -= n; + } + + status = git_deflate(&s, len ? 0 : Z_FINISH); + + if (!s.avail_out || status == Z_STREAM_END) { + size_t n = s.next_out - out_buf; + hashwrite(pack_file, out_buf, n); + pack_size += n; + s.next_out = out_buf; + s.avail_out = out_sz; + } + + switch (status) { + case Z_OK: + case Z_BUF_ERROR: + case Z_STREAM_END: + continue; + default: + die("unexpected deflate failure: %d", status); + } + } + git_deflate_end(&s); + the_hash_algo->final_oid_fn(&oid, &c); + + if (oidout) + oidcpy(oidout, &oid); + + e = insert_object(&oid); + + if (mark) + insert_mark(&marks, mark, e); + + if (e->idx.offset) { + duplicate_count_by_type[OBJ_BLOB]++; + truncate_pack(&checkpoint); + + } else if (find_sha1_pack(oid.hash, + get_all_packs(the_repository))) { + e->type = OBJ_BLOB; + e->pack_id = MAX_PACK_ID; + e->idx.offset = 1; /* just not zero! */ + duplicate_count_by_type[OBJ_BLOB]++; + truncate_pack(&checkpoint); + + } else { + e->depth = 0; + e->type = OBJ_BLOB; + e->pack_id = pack_id; + e->idx.offset = offset; + e->idx.crc32 = crc32_end(pack_file); + object_count++; + object_count_by_type[OBJ_BLOB]++; + } + + free(in_buf); + free(out_buf); +} + +/* All calls must be guarded by find_object() or find_mark() to + * ensure the 'struct object_entry' passed was written by this + * process instance. We unpack the entry by the offset, avoiding + * the need for the corresponding .idx file. This unpacking rule + * works because we only use OBJ_REF_DELTA within the packfiles + * created by fast-import. + * + * oe must not be NULL. Such an oe usually comes from giving + * an unknown SHA-1 to find_object() or an undefined mark to + * find_mark(). Callers must test for this condition and use + * the standard read_sha1_file() when it happens. + * + * oe->pack_id must not be MAX_PACK_ID. Such an oe is usually from + * find_mark(), where the mark was reloaded from an existing marks + * file and is referencing an object that this fast-import process + * instance did not write out to a packfile. Callers must test for + * this condition and use read_sha1_file() instead. + */ +static void *gfi_unpack_entry( + struct object_entry *oe, + unsigned long *sizep) +{ + enum object_type type; + struct packed_git *p = all_packs[oe->pack_id]; + if (p == pack_data && p->pack_size < (pack_size + the_hash_algo->rawsz)) { + /* The object is stored in the packfile we are writing to + * and we have modified it since the last time we scanned + * back to read a previously written object. If an old + * window covered [p->pack_size, p->pack_size + rawsz) its + * data is stale and is not valid. Closing all windows + * and updating the packfile length ensures we can read + * the newly written data. + */ + close_pack_windows(p); + hashflush(pack_file); + + /* We have to offer rawsz bytes additional on the end of + * the packfile as the core unpacker code assumes the + * footer is present at the file end and must promise + * at least rawsz bytes within any window it maps. But + * we don't actually create the footer here. + */ + p->pack_size = pack_size + the_hash_algo->rawsz; + } + return unpack_entry(the_repository, p, oe->idx.offset, &type, sizep); +} + +static const char *get_mode(const char *str, uint16_t *modep) +{ + unsigned char c; + uint16_t mode = 0; + + while ((c = *str++) != ' ') { + if (c < '0' || c > '7') + return NULL; + mode = (mode << 3) + (c - '0'); + } + *modep = mode; + return str; +} + +static void load_tree(struct tree_entry *root) +{ + struct object_id *oid = &root->versions[1].oid; + struct object_entry *myoe; + struct tree_content *t; + unsigned long size; + char *buf; + const char *c; + + root->tree = t = new_tree_content(8); + if (is_null_oid(oid)) + return; + + myoe = find_object(oid); + if (myoe && myoe->pack_id != MAX_PACK_ID) { + if (myoe->type != OBJ_TREE) + die("Not a tree: %s", oid_to_hex(oid)); + t->delta_depth = myoe->depth; + buf = gfi_unpack_entry(myoe, &size); + if (!buf) + die("Can't load tree %s", oid_to_hex(oid)); + } else { + enum object_type type; + buf = read_object_file(oid, &type, &size); + if (!buf || type != OBJ_TREE) + die("Can't load tree %s", oid_to_hex(oid)); + } + + c = buf; + while (c != (buf + size)) { + struct tree_entry *e = new_tree_entry(); + + if (t->entry_count == t->entry_capacity) + root->tree = t = grow_tree_content(t, t->entry_count); + t->entries[t->entry_count++] = e; + + e->tree = NULL; + c = get_mode(c, &e->versions[1].mode); + if (!c) + die("Corrupt mode in %s", oid_to_hex(oid)); + e->versions[0].mode = e->versions[1].mode; + e->name = to_atom(c, strlen(c)); + c += e->name->str_len + 1; + oidread(&e->versions[0].oid, (unsigned char *)c); + oidread(&e->versions[1].oid, (unsigned char *)c); + c += the_hash_algo->rawsz; + } + free(buf); +} + +static int tecmp0 (const void *_a, const void *_b) +{ + struct tree_entry *a = *((struct tree_entry**)_a); + struct tree_entry *b = *((struct tree_entry**)_b); + return base_name_compare( + a->name->str_dat, a->name->str_len, a->versions[0].mode, + b->name->str_dat, b->name->str_len, b->versions[0].mode); +} + +static int tecmp1 (const void *_a, const void *_b) +{ + struct tree_entry *a = *((struct tree_entry**)_a); + struct tree_entry *b = *((struct tree_entry**)_b); + return base_name_compare( + a->name->str_dat, a->name->str_len, a->versions[1].mode, + b->name->str_dat, b->name->str_len, b->versions[1].mode); +} + +static void mktree(struct tree_content *t, int v, struct strbuf *b) +{ + size_t maxlen = 0; + unsigned int i; + + if (!v) + QSORT(t->entries, t->entry_count, tecmp0); + else + QSORT(t->entries, t->entry_count, tecmp1); + + for (i = 0; i < t->entry_count; i++) { + if (t->entries[i]->versions[v].mode) + maxlen += t->entries[i]->name->str_len + 34; + } + + strbuf_reset(b); + strbuf_grow(b, maxlen); + for (i = 0; i < t->entry_count; i++) { + struct tree_entry *e = t->entries[i]; + if (!e->versions[v].mode) + continue; + strbuf_addf(b, "%o %s%c", + (unsigned int)(e->versions[v].mode & ~NO_DELTA), + e->name->str_dat, '\0'); + strbuf_add(b, e->versions[v].oid.hash, the_hash_algo->rawsz); + } +} + +static void store_tree(struct tree_entry *root) +{ + struct tree_content *t; + unsigned int i, j, del; + struct last_object lo = { STRBUF_INIT, 0, 0, /* no_swap */ 1 }; + struct object_entry *le = NULL; + + if (!is_null_oid(&root->versions[1].oid)) + return; + + if (!root->tree) + load_tree(root); + t = root->tree; + + for (i = 0; i < t->entry_count; i++) { + if (t->entries[i]->tree) + store_tree(t->entries[i]); + } + + if (!(root->versions[0].mode & NO_DELTA)) + le = find_object(&root->versions[0].oid); + if (S_ISDIR(root->versions[0].mode) && le && le->pack_id == pack_id) { + mktree(t, 0, &old_tree); + lo.data = old_tree; + lo.offset = le->idx.offset; + lo.depth = t->delta_depth; + } + + mktree(t, 1, &new_tree); + store_object(OBJ_TREE, &new_tree, &lo, &root->versions[1].oid, 0); + + t->delta_depth = lo.depth; + for (i = 0, j = 0, del = 0; i < t->entry_count; i++) { + struct tree_entry *e = t->entries[i]; + if (e->versions[1].mode) { + e->versions[0].mode = e->versions[1].mode; + oidcpy(&e->versions[0].oid, &e->versions[1].oid); + t->entries[j++] = e; + } else { + release_tree_entry(e); + del++; + } + } + t->entry_count -= del; +} + +static void tree_content_replace( + struct tree_entry *root, + const struct object_id *oid, + const uint16_t mode, + struct tree_content *newtree) +{ + if (!S_ISDIR(mode)) + die("Root cannot be a non-directory"); + oidclr(&root->versions[0].oid); + oidcpy(&root->versions[1].oid, oid); + if (root->tree) + release_tree_content_recursive(root->tree); + root->tree = newtree; +} + +static int tree_content_set( + struct tree_entry *root, + const char *p, + const struct object_id *oid, + const uint16_t mode, + struct tree_content *subtree) +{ + struct tree_content *t; + const char *slash1; + unsigned int i, n; + struct tree_entry *e; + + slash1 = strchrnul(p, '/'); + n = slash1 - p; + if (!n) + die("Empty path component found in input"); + if (!*slash1 && !S_ISDIR(mode) && subtree) + die("Non-directories cannot have subtrees"); + + if (!root->tree) + load_tree(root); + t = root->tree; + for (i = 0; i < t->entry_count; i++) { + e = t->entries[i]; + if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) { + if (!*slash1) { + if (!S_ISDIR(mode) + && e->versions[1].mode == mode + && oideq(&e->versions[1].oid, oid)) + return 0; + e->versions[1].mode = mode; + oidcpy(&e->versions[1].oid, oid); + if (e->tree) + release_tree_content_recursive(e->tree); + e->tree = subtree; + + /* + * We need to leave e->versions[0].sha1 alone + * to avoid modifying the preimage tree used + * when writing out the parent directory. + * But after replacing the subdir with a + * completely different one, it's not a good + * delta base any more, and besides, we've + * thrown away the tree entries needed to + * make a delta against it. + * + * So let's just explicitly disable deltas + * for the subtree. + */ + if (S_ISDIR(e->versions[0].mode)) + e->versions[0].mode |= NO_DELTA; + + oidclr(&root->versions[1].oid); + return 1; + } + if (!S_ISDIR(e->versions[1].mode)) { + e->tree = new_tree_content(8); + e->versions[1].mode = S_IFDIR; + } + if (!e->tree) + load_tree(e); + if (tree_content_set(e, slash1 + 1, oid, mode, subtree)) { + oidclr(&root->versions[1].oid); + return 1; + } + return 0; + } + } + + if (t->entry_count == t->entry_capacity) + root->tree = t = grow_tree_content(t, t->entry_count); + e = new_tree_entry(); + e->name = to_atom(p, n); + e->versions[0].mode = 0; + oidclr(&e->versions[0].oid); + t->entries[t->entry_count++] = e; + if (*slash1) { + e->tree = new_tree_content(8); + e->versions[1].mode = S_IFDIR; + tree_content_set(e, slash1 + 1, oid, mode, subtree); + } else { + e->tree = subtree; + e->versions[1].mode = mode; + oidcpy(&e->versions[1].oid, oid); + } + oidclr(&root->versions[1].oid); + return 1; +} + +static int tree_content_remove( + struct tree_entry *root, + const char *p, + struct tree_entry *backup_leaf, + int allow_root) +{ + struct tree_content *t; + const char *slash1; + unsigned int i, n; + struct tree_entry *e; + + slash1 = strchrnul(p, '/'); + n = slash1 - p; + + if (!root->tree) + load_tree(root); + + if (!*p && allow_root) { + e = root; + goto del_entry; + } + + t = root->tree; + for (i = 0; i < t->entry_count; i++) { + e = t->entries[i]; + if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) { + if (*slash1 && !S_ISDIR(e->versions[1].mode)) + /* + * If p names a file in some subdirectory, and a + * file or symlink matching the name of the + * parent directory of p exists, then p cannot + * exist and need not be deleted. + */ + return 1; + if (!*slash1 || !S_ISDIR(e->versions[1].mode)) + goto del_entry; + if (!e->tree) + load_tree(e); + if (tree_content_remove(e, slash1 + 1, backup_leaf, 0)) { + for (n = 0; n < e->tree->entry_count; n++) { + if (e->tree->entries[n]->versions[1].mode) { + oidclr(&root->versions[1].oid); + return 1; + } + } + backup_leaf = NULL; + goto del_entry; + } + return 0; + } + } + return 0; + +del_entry: + if (backup_leaf) + memcpy(backup_leaf, e, sizeof(*backup_leaf)); + else if (e->tree) + release_tree_content_recursive(e->tree); + e->tree = NULL; + e->versions[1].mode = 0; + oidclr(&e->versions[1].oid); + oidclr(&root->versions[1].oid); + return 1; +} + +static int tree_content_get( + struct tree_entry *root, + const char *p, + struct tree_entry *leaf, + int allow_root) +{ + struct tree_content *t; + const char *slash1; + unsigned int i, n; + struct tree_entry *e; + + slash1 = strchrnul(p, '/'); + n = slash1 - p; + if (!n && !allow_root) + die("Empty path component found in input"); + + if (!root->tree) + load_tree(root); + + if (!n) { + e = root; + goto found_entry; + } + + t = root->tree; + for (i = 0; i < t->entry_count; i++) { + e = t->entries[i]; + if (e->name->str_len == n && !fspathncmp(p, e->name->str_dat, n)) { + if (!*slash1) + goto found_entry; + if (!S_ISDIR(e->versions[1].mode)) + return 0; + if (!e->tree) + load_tree(e); + return tree_content_get(e, slash1 + 1, leaf, 0); + } + } + return 0; + +found_entry: + memcpy(leaf, e, sizeof(*leaf)); + if (e->tree && is_null_oid(&e->versions[1].oid)) + leaf->tree = dup_tree_content(e->tree); + else + leaf->tree = NULL; + return 1; +} + +static int update_branch(struct branch *b) +{ + static const char *msg = "fast-import"; + struct ref_transaction *transaction; + struct object_id old_oid; + struct strbuf err = STRBUF_INIT; + + if (is_null_oid(&b->oid)) { + if (b->delete) + delete_ref(NULL, b->name, NULL, 0); + return 0; + } + if (read_ref(b->name, &old_oid)) + oidclr(&old_oid); + if (!force_update && !is_null_oid(&old_oid)) { + struct commit *old_cmit, *new_cmit; + + old_cmit = lookup_commit_reference_gently(the_repository, + &old_oid, 0); + new_cmit = lookup_commit_reference_gently(the_repository, + &b->oid, 0); + if (!old_cmit || !new_cmit) + return error("Branch %s is missing commits.", b->name); + + if (!in_merge_bases(old_cmit, new_cmit)) { + warning("Not updating %s" + " (new tip %s does not contain %s)", + b->name, oid_to_hex(&b->oid), + oid_to_hex(&old_oid)); + return -1; + } + } + transaction = ref_transaction_begin(&err); + if (!transaction || + ref_transaction_update(transaction, b->name, &b->oid, &old_oid, + 0, msg, &err) || + ref_transaction_commit(transaction, &err)) { + ref_transaction_free(transaction); + error("%s", err.buf); + strbuf_release(&err); + return -1; + } + ref_transaction_free(transaction); + strbuf_release(&err); + return 0; +} + +static void dump_branches(void) +{ + unsigned int i; + struct branch *b; + + for (i = 0; i < branch_table_sz; i++) { + for (b = branch_table[i]; b; b = b->table_next_branch) + failure |= update_branch(b); + } +} + +static void dump_tags(void) +{ + static const char *msg = "fast-import"; + struct tag *t; + struct strbuf ref_name = STRBUF_INIT; + struct strbuf err = STRBUF_INIT; + struct ref_transaction *transaction; + + transaction = ref_transaction_begin(&err); + if (!transaction) { + failure |= error("%s", err.buf); + goto cleanup; + } + for (t = first_tag; t; t = t->next_tag) { + strbuf_reset(&ref_name); + strbuf_addf(&ref_name, "refs/tags/%s", t->name); + + if (ref_transaction_update(transaction, ref_name.buf, + &t->oid, NULL, 0, msg, &err)) { + failure |= error("%s", err.buf); + goto cleanup; + } + } + if (ref_transaction_commit(transaction, &err)) + failure |= error("%s", err.buf); + + cleanup: + ref_transaction_free(transaction); + strbuf_release(&ref_name); + strbuf_release(&err); +} + +static void dump_marks(void) +{ + struct lock_file mark_lock = LOCK_INIT; + FILE *f; + + if (!export_marks_file || (import_marks_file && !import_marks_file_done)) + return; + + if (safe_create_leading_directories_const(export_marks_file)) { + failure |= error_errno("unable to create leading directories of %s", + export_marks_file); + return; + } + + if (hold_lock_file_for_update(&mark_lock, export_marks_file, 0) < 0) { + failure |= error_errno("Unable to write marks file %s", + export_marks_file); + return; + } + + f = fdopen_lock_file(&mark_lock, "w"); + if (!f) { + int saved_errno = errno; + rollback_lock_file(&mark_lock); + failure |= error("Unable to write marks file %s: %s", + export_marks_file, strerror(saved_errno)); + return; + } + + for_each_mark(marks, 0, dump_marks_fn, f); + if (commit_lock_file(&mark_lock)) { + failure |= error_errno("Unable to write file %s", + export_marks_file); + return; + } +} + +static void insert_object_entry(struct mark_set **s, struct object_id *oid, uintmax_t mark) +{ + struct object_entry *e; + e = find_object(oid); + if (!e) { + enum object_type type = oid_object_info(the_repository, + oid, NULL); + if (type < 0) + die("object not found: %s", oid_to_hex(oid)); + e = insert_object(oid); + e->type = type; + e->pack_id = MAX_PACK_ID; + e->idx.offset = 1; /* just not zero! */ + } + insert_mark(s, mark, e); +} + +static void insert_oid_entry(struct mark_set **s, struct object_id *oid, uintmax_t mark) +{ + insert_mark(s, mark, xmemdupz(oid, sizeof(*oid))); +} + +static void read_mark_file(struct mark_set **s, FILE *f, mark_set_inserter_t inserter) +{ + char line[512]; + while (fgets(line, sizeof(line), f)) { + uintmax_t mark; + char *end; + struct object_id oid; + + /* Ensure SHA-1 objects are padded with zeros. */ + memset(oid.hash, 0, sizeof(oid.hash)); + + end = strchr(line, '\n'); + if (line[0] != ':' || !end) + die("corrupt mark line: %s", line); + *end = 0; + mark = strtoumax(line + 1, &end, 10); + if (!mark || end == line + 1 + || *end != ' ' + || get_oid_hex_any(end + 1, &oid) == GIT_HASH_UNKNOWN) + die("corrupt mark line: %s", line); + inserter(s, &oid, mark); + } +} + +static void read_marks(void) +{ + FILE *f = fopen(import_marks_file, "r"); + if (f) + ; + else if (import_marks_file_ignore_missing && errno == ENOENT) + goto done; /* Marks file does not exist */ + else + die_errno("cannot read '%s'", import_marks_file); + read_mark_file(&marks, f, insert_object_entry); + fclose(f); +done: + import_marks_file_done = 1; +} + + +static int read_next_command(void) +{ + static int stdin_eof = 0; + + if (stdin_eof) { + unread_command_buf = 0; + return EOF; + } + + for (;;) { + if (unread_command_buf) { + unread_command_buf = 0; + } else { + struct recent_command *rc; + + stdin_eof = strbuf_getline_lf(&command_buf, stdin); + if (stdin_eof) + return EOF; + + if (!seen_data_command + && !starts_with(command_buf.buf, "feature ") + && !starts_with(command_buf.buf, "option ")) { + parse_argv(); + } + + rc = rc_free; + if (rc) + rc_free = rc->next; + else { + rc = cmd_hist.next; + cmd_hist.next = rc->next; + cmd_hist.next->prev = &cmd_hist; + free(rc->buf); + } + + rc->buf = xstrdup(command_buf.buf); + rc->prev = cmd_tail; + rc->next = cmd_hist.prev; + rc->prev->next = rc; + cmd_tail = rc; + } + if (command_buf.buf[0] == '#') + continue; + return 0; + } +} + +static void skip_optional_lf(void) +{ + int term_char = fgetc(stdin); + if (term_char != '\n' && term_char != EOF) + ungetc(term_char, stdin); +} + +static void parse_mark(void) +{ + const char *v; + if (skip_prefix(command_buf.buf, "mark :", &v)) { + next_mark = strtoumax(v, NULL, 10); + read_next_command(); + } + else + next_mark = 0; +} + +static void parse_original_identifier(void) +{ + const char *v; + if (skip_prefix(command_buf.buf, "original-oid ", &v)) + read_next_command(); +} + +static int parse_data(struct strbuf *sb, uintmax_t limit, uintmax_t *len_res) +{ + const char *data; + strbuf_reset(sb); + + if (!skip_prefix(command_buf.buf, "data ", &data)) + die("Expected 'data n' command, found: %s", command_buf.buf); + + if (skip_prefix(data, "<<", &data)) { + char *term = xstrdup(data); + size_t term_len = command_buf.len - (data - command_buf.buf); + + for (;;) { + if (strbuf_getline_lf(&command_buf, stdin) == EOF) + die("EOF in data (terminator '%s' not found)", term); + if (term_len == command_buf.len + && !strcmp(term, command_buf.buf)) + break; + strbuf_addbuf(sb, &command_buf); + strbuf_addch(sb, '\n'); + } + free(term); + } + else { + uintmax_t len = strtoumax(data, NULL, 10); + size_t n = 0, length = (size_t)len; + + if (limit && limit < len) { + *len_res = len; + return 0; + } + if (length < len) + die("data is too large to use in this context"); + + while (n < length) { + size_t s = strbuf_fread(sb, length - n, stdin); + if (!s && feof(stdin)) + die("EOF in data (%lu bytes remaining)", + (unsigned long)(length - n)); + n += s; + } + } + + skip_optional_lf(); + return 1; +} + +static int validate_raw_date(const char *src, struct strbuf *result, int strict) +{ + const char *orig_src = src; + char *endp; + unsigned long num; + + errno = 0; + + num = strtoul(src, &endp, 10); + /* + * NEEDSWORK: perhaps check for reasonable values? For example, we + * could error on values representing times more than a + * day in the future. + */ + if (errno || endp == src || *endp != ' ') + return -1; + + src = endp + 1; + if (*src != '-' && *src != '+') + return -1; + + num = strtoul(src + 1, &endp, 10); + /* + * NEEDSWORK: check for brokenness other than num > 1400, such as + * (num % 100) >= 60, or ((num % 100) % 15) != 0 ? + */ + if (errno || endp == src + 1 || *endp || /* did not parse */ + (strict && (1400 < num)) /* parsed a broken timezone */ + ) + return -1; + + strbuf_addstr(result, orig_src); + return 0; +} + +static char *parse_ident(const char *buf) +{ + const char *ltgt; + size_t name_len; + struct strbuf ident = STRBUF_INIT; + + /* ensure there is a space delimiter even if there is no name */ + if (*buf == '<') + --buf; + + ltgt = buf + strcspn(buf, "<>"); + if (*ltgt != '<') + die("Missing < in ident string: %s", buf); + if (ltgt != buf && ltgt[-1] != ' ') + die("Missing space before < in ident string: %s", buf); + ltgt = ltgt + 1 + strcspn(ltgt + 1, "<>"); + if (*ltgt != '>') + die("Missing > in ident string: %s", buf); + ltgt++; + if (*ltgt != ' ') + die("Missing space after > in ident string: %s", buf); + ltgt++; + name_len = ltgt - buf; + strbuf_add(&ident, buf, name_len); + + switch (whenspec) { + case WHENSPEC_RAW: + if (validate_raw_date(ltgt, &ident, 1) < 0) + die("Invalid raw date \"%s\" in ident: %s", ltgt, buf); + break; + case WHENSPEC_RAW_PERMISSIVE: + if (validate_raw_date(ltgt, &ident, 0) < 0) + die("Invalid raw date \"%s\" in ident: %s", ltgt, buf); + break; + case WHENSPEC_RFC2822: + if (parse_date(ltgt, &ident) < 0) + die("Invalid rfc2822 date \"%s\" in ident: %s", ltgt, buf); + break; + case WHENSPEC_NOW: + if (strcmp("now", ltgt)) + die("Date in ident must be 'now': %s", buf); + datestamp(&ident); + break; + } + + return strbuf_detach(&ident, NULL); +} + +static void parse_and_store_blob( + struct last_object *last, + struct object_id *oidout, + uintmax_t mark) +{ + static struct strbuf buf = STRBUF_INIT; + uintmax_t len; + + if (parse_data(&buf, big_file_threshold, &len)) + store_object(OBJ_BLOB, &buf, last, oidout, mark); + else { + if (last) { + strbuf_release(&last->data); + last->offset = 0; + last->depth = 0; + } + stream_blob(len, oidout, mark); + skip_optional_lf(); + } +} + +static void parse_new_blob(void) +{ + read_next_command(); + parse_mark(); + parse_original_identifier(); + parse_and_store_blob(&last_blob, NULL, next_mark); +} + +static void unload_one_branch(void) +{ + while (cur_active_branches + && cur_active_branches >= max_active_branches) { + uintmax_t min_commit = ULONG_MAX; + struct branch *e, *l = NULL, *p = NULL; + + for (e = active_branches; e; e = e->active_next_branch) { + if (e->last_commit < min_commit) { + p = l; + min_commit = e->last_commit; + } + l = e; + } + + if (p) { + e = p->active_next_branch; + p->active_next_branch = e->active_next_branch; + } else { + e = active_branches; + active_branches = e->active_next_branch; + } + e->active = 0; + e->active_next_branch = NULL; + if (e->branch_tree.tree) { + release_tree_content_recursive(e->branch_tree.tree); + e->branch_tree.tree = NULL; + } + cur_active_branches--; + } +} + +static void load_branch(struct branch *b) +{ + load_tree(&b->branch_tree); + if (!b->active) { + b->active = 1; + b->active_next_branch = active_branches; + active_branches = b; + cur_active_branches++; + branch_load_count++; + } +} + +static unsigned char convert_num_notes_to_fanout(uintmax_t num_notes) +{ + unsigned char fanout = 0; + while ((num_notes >>= 8)) + fanout++; + return fanout; +} + +static void construct_path_with_fanout(const char *hex_sha1, + unsigned char fanout, char *path) +{ + unsigned int i = 0, j = 0; + if (fanout >= the_hash_algo->rawsz) + die("Too large fanout (%u)", fanout); + while (fanout) { + path[i++] = hex_sha1[j++]; + path[i++] = hex_sha1[j++]; + path[i++] = '/'; + fanout--; + } + memcpy(path + i, hex_sha1 + j, the_hash_algo->hexsz - j); + path[i + the_hash_algo->hexsz - j] = '\0'; +} + +static uintmax_t do_change_note_fanout( + struct tree_entry *orig_root, struct tree_entry *root, + char *hex_oid, unsigned int hex_oid_len, + char *fullpath, unsigned int fullpath_len, + unsigned char fanout) +{ + struct tree_content *t; + struct tree_entry *e, leaf; + unsigned int i, tmp_hex_oid_len, tmp_fullpath_len; + uintmax_t num_notes = 0; + struct object_id oid; + /* hex oid + '/' between each pair of hex digits + NUL */ + char realpath[GIT_MAX_HEXSZ + ((GIT_MAX_HEXSZ / 2) - 1) + 1]; + const unsigned hexsz = the_hash_algo->hexsz; + + if (!root->tree) + load_tree(root); + t = root->tree; + + for (i = 0; t && i < t->entry_count; i++) { + e = t->entries[i]; + tmp_hex_oid_len = hex_oid_len + e->name->str_len; + tmp_fullpath_len = fullpath_len; + + /* + * We're interested in EITHER existing note entries (entries + * with exactly 40 hex chars in path, not including directory + * separators), OR directory entries that may contain note + * entries (with < 40 hex chars in path). + * Also, each path component in a note entry must be a multiple + * of 2 chars. + */ + if (!e->versions[1].mode || + tmp_hex_oid_len > hexsz || + e->name->str_len % 2) + continue; + + /* This _may_ be a note entry, or a subdir containing notes */ + memcpy(hex_oid + hex_oid_len, e->name->str_dat, + e->name->str_len); + if (tmp_fullpath_len) + fullpath[tmp_fullpath_len++] = '/'; + memcpy(fullpath + tmp_fullpath_len, e->name->str_dat, + e->name->str_len); + tmp_fullpath_len += e->name->str_len; + fullpath[tmp_fullpath_len] = '\0'; + + if (tmp_hex_oid_len == hexsz && !get_oid_hex(hex_oid, &oid)) { + /* This is a note entry */ + if (fanout == 0xff) { + /* Counting mode, no rename */ + num_notes++; + continue; + } + construct_path_with_fanout(hex_oid, fanout, realpath); + if (!strcmp(fullpath, realpath)) { + /* Note entry is in correct location */ + num_notes++; + continue; + } + + /* Rename fullpath to realpath */ + if (!tree_content_remove(orig_root, fullpath, &leaf, 0)) + die("Failed to remove path %s", fullpath); + tree_content_set(orig_root, realpath, + &leaf.versions[1].oid, + leaf.versions[1].mode, + leaf.tree); + } else if (S_ISDIR(e->versions[1].mode)) { + /* This is a subdir that may contain note entries */ + num_notes += do_change_note_fanout(orig_root, e, + hex_oid, tmp_hex_oid_len, + fullpath, tmp_fullpath_len, fanout); + } + + /* The above may have reallocated the current tree_content */ + t = root->tree; + } + return num_notes; +} + +static uintmax_t change_note_fanout(struct tree_entry *root, + unsigned char fanout) +{ + /* + * The size of path is due to one slash between every two hex digits, + * plus the terminating NUL. Note that there is no slash at the end, so + * the number of slashes is one less than half the number of hex + * characters. + */ + char hex_oid[GIT_MAX_HEXSZ], path[GIT_MAX_HEXSZ + (GIT_MAX_HEXSZ / 2) - 1 + 1]; + return do_change_note_fanout(root, root, hex_oid, 0, path, 0, fanout); +} + +static int parse_mapped_oid_hex(const char *hex, struct object_id *oid, const char **end) +{ + int algo; + khiter_t it; + + /* Make SHA-1 object IDs have all-zero padding. */ + memset(oid->hash, 0, sizeof(oid->hash)); + + algo = parse_oid_hex_any(hex, oid, end); + if (algo == GIT_HASH_UNKNOWN) + return -1; + + it = kh_get_oid_map(sub_oid_map, *oid); + /* No such object? */ + if (it == kh_end(sub_oid_map)) { + /* If we're using the same algorithm, pass it through. */ + if (hash_algos[algo].format_id == the_hash_algo->format_id) + return 0; + return -1; + } + oidcpy(oid, kh_value(sub_oid_map, it)); + return 0; +} + +/* + * Given a pointer into a string, parse a mark reference: + * + * idnum ::= ':' bigint; + * + * Return the first character after the value in *endptr. + * + * Complain if the following character is not what is expected, + * either a space or end of the string. + */ +static uintmax_t parse_mark_ref(const char *p, char **endptr) +{ + uintmax_t mark; + + assert(*p == ':'); + p++; + mark = strtoumax(p, endptr, 10); + if (*endptr == p) + die("No value after ':' in mark: %s", command_buf.buf); + return mark; +} + +/* + * Parse the mark reference, and complain if this is not the end of + * the string. + */ +static uintmax_t parse_mark_ref_eol(const char *p) +{ + char *end; + uintmax_t mark; + + mark = parse_mark_ref(p, &end); + if (*end != '\0') + die("Garbage after mark: %s", command_buf.buf); + return mark; +} + +/* + * Parse the mark reference, demanding a trailing space. Return a + * pointer to the space. + */ +static uintmax_t parse_mark_ref_space(const char **p) +{ + uintmax_t mark; + char *end; + + mark = parse_mark_ref(*p, &end); + if (*end++ != ' ') + die("Missing space after mark: %s", command_buf.buf); + *p = end; + return mark; +} + +static void file_change_m(const char *p, struct branch *b) +{ + static struct strbuf uq = STRBUF_INIT; + const char *endp; + struct object_entry *oe; + struct object_id oid; + uint16_t mode, inline_data = 0; + + p = get_mode(p, &mode); + if (!p) + die("Corrupt mode: %s", command_buf.buf); + switch (mode) { + case 0644: + case 0755: + mode |= S_IFREG; + case S_IFREG | 0644: + case S_IFREG | 0755: + case S_IFLNK: + case S_IFDIR: + case S_IFGITLINK: + /* ok */ + break; + default: + die("Corrupt mode: %s", command_buf.buf); + } + + if (*p == ':') { + oe = find_mark(marks, parse_mark_ref_space(&p)); + oidcpy(&oid, &oe->idx.oid); + } else if (skip_prefix(p, "inline ", &p)) { + inline_data = 1; + oe = NULL; /* not used with inline_data, but makes gcc happy */ + } else { + if (parse_mapped_oid_hex(p, &oid, &p)) + die("Invalid dataref: %s", command_buf.buf); + oe = find_object(&oid); + if (*p++ != ' ') + die("Missing space after SHA1: %s", command_buf.buf); + } + + strbuf_reset(&uq); + if (!unquote_c_style(&uq, p, &endp)) { + if (*endp) + die("Garbage after path in: %s", command_buf.buf); + p = uq.buf; + } + + /* Git does not track empty, non-toplevel directories. */ + if (S_ISDIR(mode) && is_empty_tree_oid(&oid) && *p) { + tree_content_remove(&b->branch_tree, p, NULL, 0); + return; + } + + if (S_ISGITLINK(mode)) { + if (inline_data) + die("Git links cannot be specified 'inline': %s", + command_buf.buf); + else if (oe) { + if (oe->type != OBJ_COMMIT) + die("Not a commit (actually a %s): %s", + type_name(oe->type), command_buf.buf); + } + /* + * Accept the sha1 without checking; it expected to be in + * another repository. + */ + } else if (inline_data) { + if (S_ISDIR(mode)) + die("Directories cannot be specified 'inline': %s", + command_buf.buf); + if (p != uq.buf) { + strbuf_addstr(&uq, p); + p = uq.buf; + } + while (read_next_command() != EOF) { + const char *v; + if (skip_prefix(command_buf.buf, "cat-blob ", &v)) + parse_cat_blob(v); + else { + parse_and_store_blob(&last_blob, &oid, 0); + break; + } + } + } else { + enum object_type expected = S_ISDIR(mode) ? + OBJ_TREE: OBJ_BLOB; + enum object_type type = oe ? oe->type : + oid_object_info(the_repository, &oid, + NULL); + if (type < 0) + die("%s not found: %s", + S_ISDIR(mode) ? "Tree" : "Blob", + command_buf.buf); + if (type != expected) + die("Not a %s (actually a %s): %s", + type_name(expected), type_name(type), + command_buf.buf); + } + + if (!*p) { + tree_content_replace(&b->branch_tree, &oid, mode, NULL); + return; + } + tree_content_set(&b->branch_tree, p, &oid, mode, NULL); +} + +static void file_change_d(const char *p, struct branch *b) +{ + static struct strbuf uq = STRBUF_INIT; + const char *endp; + + strbuf_reset(&uq); + if (!unquote_c_style(&uq, p, &endp)) { + if (*endp) + die("Garbage after path in: %s", command_buf.buf); + p = uq.buf; + } + tree_content_remove(&b->branch_tree, p, NULL, 1); +} + +static void file_change_cr(const char *s, struct branch *b, int rename) +{ + const char *d; + static struct strbuf s_uq = STRBUF_INIT; + static struct strbuf d_uq = STRBUF_INIT; + const char *endp; + struct tree_entry leaf; + + strbuf_reset(&s_uq); + if (!unquote_c_style(&s_uq, s, &endp)) { + if (*endp != ' ') + die("Missing space after source: %s", command_buf.buf); + } else { + endp = strchr(s, ' '); + if (!endp) + die("Missing space after source: %s", command_buf.buf); + strbuf_add(&s_uq, s, endp - s); + } + s = s_uq.buf; + + endp++; + if (!*endp) + die("Missing dest: %s", command_buf.buf); + + d = endp; + strbuf_reset(&d_uq); + if (!unquote_c_style(&d_uq, d, &endp)) { + if (*endp) + die("Garbage after dest in: %s", command_buf.buf); + d = d_uq.buf; + } + + memset(&leaf, 0, sizeof(leaf)); + if (rename) + tree_content_remove(&b->branch_tree, s, &leaf, 1); + else + tree_content_get(&b->branch_tree, s, &leaf, 1); + if (!leaf.versions[1].mode) + die("Path %s not in branch", s); + if (!*d) { /* C "path/to/subdir" "" */ + tree_content_replace(&b->branch_tree, + &leaf.versions[1].oid, + leaf.versions[1].mode, + leaf.tree); + return; + } + tree_content_set(&b->branch_tree, d, + &leaf.versions[1].oid, + leaf.versions[1].mode, + leaf.tree); +} + +static void note_change_n(const char *p, struct branch *b, unsigned char *old_fanout) +{ + static struct strbuf uq = STRBUF_INIT; + struct object_entry *oe; + struct branch *s; + struct object_id oid, commit_oid; + char path[GIT_MAX_RAWSZ * 3]; + uint16_t inline_data = 0; + unsigned char new_fanout; + + /* + * When loading a branch, we don't traverse its tree to count the real + * number of notes (too expensive to do this for all non-note refs). + * This means that recently loaded notes refs might incorrectly have + * b->num_notes == 0, and consequently, old_fanout might be wrong. + * + * Fix this by traversing the tree and counting the number of notes + * when b->num_notes == 0. If the notes tree is truly empty, the + * calculation should not take long. + */ + if (b->num_notes == 0 && *old_fanout == 0) { + /* Invoke change_note_fanout() in "counting mode". */ + b->num_notes = change_note_fanout(&b->branch_tree, 0xff); + *old_fanout = convert_num_notes_to_fanout(b->num_notes); + } + + /* Now parse the notemodify command. */ + /* or 'inline' */ + if (*p == ':') { + oe = find_mark(marks, parse_mark_ref_space(&p)); + oidcpy(&oid, &oe->idx.oid); + } else if (skip_prefix(p, "inline ", &p)) { + inline_data = 1; + oe = NULL; /* not used with inline_data, but makes gcc happy */ + } else { + if (parse_mapped_oid_hex(p, &oid, &p)) + die("Invalid dataref: %s", command_buf.buf); + oe = find_object(&oid); + if (*p++ != ' ') + die("Missing space after SHA1: %s", command_buf.buf); + } + + /* */ + s = lookup_branch(p); + if (s) { + if (is_null_oid(&s->oid)) + die("Can't add a note on empty branch."); + oidcpy(&commit_oid, &s->oid); + } else if (*p == ':') { + uintmax_t commit_mark = parse_mark_ref_eol(p); + struct object_entry *commit_oe = find_mark(marks, commit_mark); + if (commit_oe->type != OBJ_COMMIT) + die("Mark :%" PRIuMAX " not a commit", commit_mark); + oidcpy(&commit_oid, &commit_oe->idx.oid); + } else if (!get_oid(p, &commit_oid)) { + unsigned long size; + char *buf = read_object_with_reference(the_repository, + &commit_oid, + commit_type, &size, + &commit_oid); + if (!buf || size < the_hash_algo->hexsz + 6) + die("Not a valid commit: %s", p); + free(buf); + } else + die("Invalid ref name or SHA1 expression: %s", p); + + if (inline_data) { + if (p != uq.buf) { + strbuf_addstr(&uq, p); + p = uq.buf; + } + read_next_command(); + parse_and_store_blob(&last_blob, &oid, 0); + } else if (oe) { + if (oe->type != OBJ_BLOB) + die("Not a blob (actually a %s): %s", + type_name(oe->type), command_buf.buf); + } else if (!is_null_oid(&oid)) { + enum object_type type = oid_object_info(the_repository, &oid, + NULL); + if (type < 0) + die("Blob not found: %s", command_buf.buf); + if (type != OBJ_BLOB) + die("Not a blob (actually a %s): %s", + type_name(type), command_buf.buf); + } + + construct_path_with_fanout(oid_to_hex(&commit_oid), *old_fanout, path); + if (tree_content_remove(&b->branch_tree, path, NULL, 0)) + b->num_notes--; + + if (is_null_oid(&oid)) + return; /* nothing to insert */ + + b->num_notes++; + new_fanout = convert_num_notes_to_fanout(b->num_notes); + construct_path_with_fanout(oid_to_hex(&commit_oid), new_fanout, path); + tree_content_set(&b->branch_tree, path, &oid, S_IFREG | 0644, NULL); +} + +static void file_change_deleteall(struct branch *b) +{ + release_tree_content_recursive(b->branch_tree.tree); + oidclr(&b->branch_tree.versions[0].oid); + oidclr(&b->branch_tree.versions[1].oid); + load_tree(&b->branch_tree); + b->num_notes = 0; +} + +static void parse_from_commit(struct branch *b, char *buf, unsigned long size) +{ + if (!buf || size < the_hash_algo->hexsz + 6) + die("Not a valid commit: %s", oid_to_hex(&b->oid)); + if (memcmp("tree ", buf, 5) + || get_oid_hex(buf + 5, &b->branch_tree.versions[1].oid)) + die("The commit %s is corrupt", oid_to_hex(&b->oid)); + oidcpy(&b->branch_tree.versions[0].oid, + &b->branch_tree.versions[1].oid); +} + +static void parse_from_existing(struct branch *b) +{ + if (is_null_oid(&b->oid)) { + oidclr(&b->branch_tree.versions[0].oid); + oidclr(&b->branch_tree.versions[1].oid); + } else { + unsigned long size; + char *buf; + + buf = read_object_with_reference(the_repository, + &b->oid, commit_type, &size, + &b->oid); + parse_from_commit(b, buf, size); + free(buf); + } +} + +static int parse_objectish(struct branch *b, const char *objectish) +{ + struct branch *s; + struct object_id oid; + + oidcpy(&oid, &b->branch_tree.versions[1].oid); + + s = lookup_branch(objectish); + if (b == s) + die("Can't create a branch from itself: %s", b->name); + else if (s) { + struct object_id *t = &s->branch_tree.versions[1].oid; + oidcpy(&b->oid, &s->oid); + oidcpy(&b->branch_tree.versions[0].oid, t); + oidcpy(&b->branch_tree.versions[1].oid, t); + } else if (*objectish == ':') { + uintmax_t idnum = parse_mark_ref_eol(objectish); + struct object_entry *oe = find_mark(marks, idnum); + if (oe->type != OBJ_COMMIT) + die("Mark :%" PRIuMAX " not a commit", idnum); + if (!oideq(&b->oid, &oe->idx.oid)) { + oidcpy(&b->oid, &oe->idx.oid); + if (oe->pack_id != MAX_PACK_ID) { + unsigned long size; + char *buf = gfi_unpack_entry(oe, &size); + parse_from_commit(b, buf, size); + free(buf); + } else + parse_from_existing(b); + } + } else if (!get_oid(objectish, &b->oid)) { + parse_from_existing(b); + if (is_null_oid(&b->oid)) + b->delete = 1; + } + else + die("Invalid ref name or SHA1 expression: %s", objectish); + + if (b->branch_tree.tree && !oideq(&oid, &b->branch_tree.versions[1].oid)) { + release_tree_content_recursive(b->branch_tree.tree); + b->branch_tree.tree = NULL; + } + + read_next_command(); + return 1; +} + +static int parse_from(struct branch *b) +{ + const char *from; + + if (!skip_prefix(command_buf.buf, "from ", &from)) + return 0; + + return parse_objectish(b, from); +} + +static int parse_objectish_with_prefix(struct branch *b, const char *prefix) +{ + const char *base; + + if (!skip_prefix(command_buf.buf, prefix, &base)) + return 0; + + return parse_objectish(b, base); +} + +static struct hash_list *parse_merge(unsigned int *count) +{ + struct hash_list *list = NULL, **tail = &list, *n; + const char *from; + struct branch *s; + + *count = 0; + while (skip_prefix(command_buf.buf, "merge ", &from)) { + n = xmalloc(sizeof(*n)); + s = lookup_branch(from); + if (s) + oidcpy(&n->oid, &s->oid); + else if (*from == ':') { + uintmax_t idnum = parse_mark_ref_eol(from); + struct object_entry *oe = find_mark(marks, idnum); + if (oe->type != OBJ_COMMIT) + die("Mark :%" PRIuMAX " not a commit", idnum); + oidcpy(&n->oid, &oe->idx.oid); + } else if (!get_oid(from, &n->oid)) { + unsigned long size; + char *buf = read_object_with_reference(the_repository, + &n->oid, + commit_type, + &size, &n->oid); + if (!buf || size < the_hash_algo->hexsz + 6) + die("Not a valid commit: %s", from); + free(buf); + } else + die("Invalid ref name or SHA1 expression: %s", from); + + n->next = NULL; + *tail = n; + tail = &n->next; + + (*count)++; + read_next_command(); + } + return list; +} + +static void parse_new_commit(const char *arg) +{ + static struct strbuf msg = STRBUF_INIT; + struct branch *b; + char *author = NULL; + char *committer = NULL; + char *encoding = NULL; + struct hash_list *merge_list = NULL; + unsigned int merge_count; + unsigned char prev_fanout, new_fanout; + const char *v; + + b = lookup_branch(arg); + if (!b) + b = new_branch(arg); + + read_next_command(); + parse_mark(); + parse_original_identifier(); + if (skip_prefix(command_buf.buf, "author ", &v)) { + author = parse_ident(v); + read_next_command(); + } + if (skip_prefix(command_buf.buf, "committer ", &v)) { + committer = parse_ident(v); + read_next_command(); + } + if (!committer) + die("Expected committer but didn't get one"); + if (skip_prefix(command_buf.buf, "encoding ", &v)) { + encoding = xstrdup(v); + read_next_command(); + } + parse_data(&msg, 0, NULL); + read_next_command(); + parse_from(b); + merge_list = parse_merge(&merge_count); + + /* ensure the branch is active/loaded */ + if (!b->branch_tree.tree || !max_active_branches) { + unload_one_branch(); + load_branch(b); + } + + prev_fanout = convert_num_notes_to_fanout(b->num_notes); + + /* file_change* */ + while (command_buf.len > 0) { + if (skip_prefix(command_buf.buf, "M ", &v)) + file_change_m(v, b); + else if (skip_prefix(command_buf.buf, "D ", &v)) + file_change_d(v, b); + else if (skip_prefix(command_buf.buf, "R ", &v)) + file_change_cr(v, b, 1); + else if (skip_prefix(command_buf.buf, "C ", &v)) + file_change_cr(v, b, 0); + else if (skip_prefix(command_buf.buf, "N ", &v)) + note_change_n(v, b, &prev_fanout); + else if (!strcmp("deleteall", command_buf.buf)) + file_change_deleteall(b); + else if (skip_prefix(command_buf.buf, "ls ", &v)) + parse_ls(v, b); + else if (skip_prefix(command_buf.buf, "cat-blob ", &v)) + parse_cat_blob(v); + else { + unread_command_buf = 1; + break; + } + if (read_next_command() == EOF) + break; + } + + new_fanout = convert_num_notes_to_fanout(b->num_notes); + if (new_fanout != prev_fanout) + b->num_notes = change_note_fanout(&b->branch_tree, new_fanout); + + /* build the tree and the commit */ + store_tree(&b->branch_tree); + oidcpy(&b->branch_tree.versions[0].oid, + &b->branch_tree.versions[1].oid); + + strbuf_reset(&new_data); + strbuf_addf(&new_data, "tree %s\n", + oid_to_hex(&b->branch_tree.versions[1].oid)); + if (!is_null_oid(&b->oid)) + strbuf_addf(&new_data, "parent %s\n", + oid_to_hex(&b->oid)); + while (merge_list) { + struct hash_list *next = merge_list->next; + strbuf_addf(&new_data, "parent %s\n", + oid_to_hex(&merge_list->oid)); + free(merge_list); + merge_list = next; + } + strbuf_addf(&new_data, + "author %s\n" + "committer %s\n", + author ? author : committer, committer); + if (encoding) + strbuf_addf(&new_data, + "encoding %s\n", + encoding); + strbuf_addch(&new_data, '\n'); + strbuf_addbuf(&new_data, &msg); + free(author); + free(committer); + free(encoding); + + if (!store_object(OBJ_COMMIT, &new_data, NULL, &b->oid, next_mark)) + b->pack_id = pack_id; + b->last_commit = object_count_by_type[OBJ_COMMIT]; +} + +static void parse_new_tag(const char *arg) +{ + static struct strbuf msg = STRBUF_INIT; + const char *from; + char *tagger; + struct branch *s; + struct tag *t; + uintmax_t from_mark = 0; + struct object_id oid; + enum object_type type; + const char *v; + + t = mem_pool_alloc(&fi_mem_pool, sizeof(struct tag)); + memset(t, 0, sizeof(struct tag)); + t->name = mem_pool_strdup(&fi_mem_pool, arg); + if (last_tag) + last_tag->next_tag = t; + else + first_tag = t; + last_tag = t; + read_next_command(); + parse_mark(); + + /* from ... */ + if (!skip_prefix(command_buf.buf, "from ", &from)) + die("Expected from command, got %s", command_buf.buf); + s = lookup_branch(from); + if (s) { + if (is_null_oid(&s->oid)) + die("Can't tag an empty branch."); + oidcpy(&oid, &s->oid); + type = OBJ_COMMIT; + } else if (*from == ':') { + struct object_entry *oe; + from_mark = parse_mark_ref_eol(from); + oe = find_mark(marks, from_mark); + type = oe->type; + oidcpy(&oid, &oe->idx.oid); + } else if (!get_oid(from, &oid)) { + struct object_entry *oe = find_object(&oid); + if (!oe) { + type = oid_object_info(the_repository, &oid, NULL); + if (type < 0) + die("Not a valid object: %s", from); + } else + type = oe->type; + } else + die("Invalid ref name or SHA1 expression: %s", from); + read_next_command(); + + /* original-oid ... */ + parse_original_identifier(); + + /* tagger ... */ + if (skip_prefix(command_buf.buf, "tagger ", &v)) { + tagger = parse_ident(v); + read_next_command(); + } else + tagger = NULL; + + /* tag payload/message */ + parse_data(&msg, 0, NULL); + + /* build the tag object */ + strbuf_reset(&new_data); + + strbuf_addf(&new_data, + "object %s\n" + "type %s\n" + "tag %s\n", + oid_to_hex(&oid), type_name(type), t->name); + if (tagger) + strbuf_addf(&new_data, + "tagger %s\n", tagger); + strbuf_addch(&new_data, '\n'); + strbuf_addbuf(&new_data, &msg); + free(tagger); + + if (store_object(OBJ_TAG, &new_data, NULL, &t->oid, next_mark)) + t->pack_id = MAX_PACK_ID; + else + t->pack_id = pack_id; +} + +static void parse_reset_branch(const char *arg) +{ + struct branch *b; + const char *tag_name; + + b = lookup_branch(arg); + if (b) { + oidclr(&b->oid); + oidclr(&b->branch_tree.versions[0].oid); + oidclr(&b->branch_tree.versions[1].oid); + if (b->branch_tree.tree) { + release_tree_content_recursive(b->branch_tree.tree); + b->branch_tree.tree = NULL; + } + } + else + b = new_branch(arg); + read_next_command(); + parse_from(b); + if (b->delete && skip_prefix(b->name, "refs/tags/", &tag_name)) { + /* + * Elsewhere, we call dump_branches() before dump_tags(), + * and dump_branches() will handle ref deletions first, so + * in order to make sure the deletion actually takes effect, + * we need to remove the tag from our list of tags to update. + * + * NEEDSWORK: replace list of tags with hashmap for faster + * deletion? + */ + struct tag *t, *prev = NULL; + for (t = first_tag; t; t = t->next_tag) { + if (!strcmp(t->name, tag_name)) + break; + prev = t; + } + if (t) { + if (prev) + prev->next_tag = t->next_tag; + else + first_tag = t->next_tag; + if (!t->next_tag) + last_tag = prev; + /* There is no mem_pool_free(t) function to call. */ + } + } + if (command_buf.len > 0) + unread_command_buf = 1; +} + +static void cat_blob_write(const char *buf, unsigned long size) +{ + if (write_in_full(cat_blob_fd, buf, size) < 0) + die_errno("Write to frontend failed"); +} + +static void cat_blob(struct object_entry *oe, struct object_id *oid) +{ + struct strbuf line = STRBUF_INIT; + unsigned long size; + enum object_type type = 0; + char *buf; + + if (!oe || oe->pack_id == MAX_PACK_ID) { + buf = read_object_file(oid, &type, &size); + } else { + type = oe->type; + buf = gfi_unpack_entry(oe, &size); + } + + /* + * Output based on batch_one_object() from cat-file.c. + */ + if (type <= 0) { + strbuf_reset(&line); + strbuf_addf(&line, "%s missing\n", oid_to_hex(oid)); + cat_blob_write(line.buf, line.len); + strbuf_release(&line); + free(buf); + return; + } + if (!buf) + die("Can't read object %s", oid_to_hex(oid)); + if (type != OBJ_BLOB) + die("Object %s is a %s but a blob was expected.", + oid_to_hex(oid), type_name(type)); + strbuf_reset(&line); + strbuf_addf(&line, "%s %s %"PRIuMAX"\n", oid_to_hex(oid), + type_name(type), (uintmax_t)size); + cat_blob_write(line.buf, line.len); + strbuf_release(&line); + cat_blob_write(buf, size); + cat_blob_write("\n", 1); + if (oe && oe->pack_id == pack_id) { + last_blob.offset = oe->idx.offset; + strbuf_attach(&last_blob.data, buf, size, size); + last_blob.depth = oe->depth; + } else + free(buf); +} + +static void parse_get_mark(const char *p) +{ + struct object_entry *oe; + char output[GIT_MAX_HEXSZ + 2]; + + /* get-mark SP LF */ + if (*p != ':') + die("Not a mark: %s", p); + + oe = find_mark(marks, parse_mark_ref_eol(p)); + if (!oe) + die("Unknown mark: %s", command_buf.buf); + + xsnprintf(output, sizeof(output), "%s\n", oid_to_hex(&oe->idx.oid)); + cat_blob_write(output, the_hash_algo->hexsz + 1); +} + +static void parse_cat_blob(const char *p) +{ + struct object_entry *oe; + struct object_id oid; + + /* cat-blob SP LF */ + if (*p == ':') { + oe = find_mark(marks, parse_mark_ref_eol(p)); + if (!oe) + die("Unknown mark: %s", command_buf.buf); + oidcpy(&oid, &oe->idx.oid); + } else { + if (parse_mapped_oid_hex(p, &oid, &p)) + die("Invalid dataref: %s", command_buf.buf); + if (*p) + die("Garbage after SHA1: %s", command_buf.buf); + oe = find_object(&oid); + } + + cat_blob(oe, &oid); +} + +static struct object_entry *dereference(struct object_entry *oe, + struct object_id *oid) +{ + unsigned long size; + char *buf = NULL; + const unsigned hexsz = the_hash_algo->hexsz; + + if (!oe) { + enum object_type type = oid_object_info(the_repository, oid, + NULL); + if (type < 0) + die("object not found: %s", oid_to_hex(oid)); + /* cache it! */ + oe = insert_object(oid); + oe->type = type; + oe->pack_id = MAX_PACK_ID; + oe->idx.offset = 1; + } + switch (oe->type) { + case OBJ_TREE: /* easy case. */ + return oe; + case OBJ_COMMIT: + case OBJ_TAG: + break; + default: + die("Not a tree-ish: %s", command_buf.buf); + } + + if (oe->pack_id != MAX_PACK_ID) { /* in a pack being written */ + buf = gfi_unpack_entry(oe, &size); + } else { + enum object_type unused; + buf = read_object_file(oid, &unused, &size); + } + if (!buf) + die("Can't load object %s", oid_to_hex(oid)); + + /* Peel one layer. */ + switch (oe->type) { + case OBJ_TAG: + if (size < hexsz + strlen("object ") || + get_oid_hex(buf + strlen("object "), oid)) + die("Invalid SHA1 in tag: %s", command_buf.buf); + break; + case OBJ_COMMIT: + if (size < hexsz + strlen("tree ") || + get_oid_hex(buf + strlen("tree "), oid)) + die("Invalid SHA1 in commit: %s", command_buf.buf); + } + + free(buf); + return find_object(oid); +} + +static void insert_mapped_mark(uintmax_t mark, void *object, void *cbp) +{ + struct object_id *fromoid = object; + struct object_id *tooid = find_mark(cbp, mark); + int ret; + khiter_t it; + + it = kh_put_oid_map(sub_oid_map, *fromoid, &ret); + /* We've already seen this object. */ + if (ret == 0) + return; + kh_value(sub_oid_map, it) = tooid; +} + +static void build_mark_map_one(struct mark_set *from, struct mark_set *to) +{ + for_each_mark(from, 0, insert_mapped_mark, to); +} + +static void build_mark_map(struct string_list *from, struct string_list *to) +{ + struct string_list_item *fromp, *top; + + sub_oid_map = kh_init_oid_map(); + + for_each_string_list_item(fromp, from) { + top = string_list_lookup(to, fromp->string); + if (!fromp->util) { + die(_("Missing from marks for submodule '%s'"), fromp->string); + } else if (!top || !top->util) { + die(_("Missing to marks for submodule '%s'"), fromp->string); + } + build_mark_map_one(fromp->util, top->util); + } +} + +static struct object_entry *parse_treeish_dataref(const char **p) +{ + struct object_id oid; + struct object_entry *e; + + if (**p == ':') { /* */ + e = find_mark(marks, parse_mark_ref_space(p)); + if (!e) + die("Unknown mark: %s", command_buf.buf); + oidcpy(&oid, &e->idx.oid); + } else { /* */ + if (parse_mapped_oid_hex(*p, &oid, p)) + die("Invalid dataref: %s", command_buf.buf); + e = find_object(&oid); + if (*(*p)++ != ' ') + die("Missing space after tree-ish: %s", command_buf.buf); + } + + while (!e || e->type != OBJ_TREE) + e = dereference(e, &oid); + return e; +} + +static void print_ls(int mode, const unsigned char *hash, const char *path) +{ + static struct strbuf line = STRBUF_INIT; + + /* See show_tree(). */ + const char *type = + S_ISGITLINK(mode) ? commit_type : + S_ISDIR(mode) ? tree_type : + blob_type; + + if (!mode) { + /* missing SP path LF */ + strbuf_reset(&line); + strbuf_addstr(&line, "missing "); + quote_c_style(path, &line, NULL, 0); + strbuf_addch(&line, '\n'); + } else { + /* mode SP type SP object_name TAB path LF */ + strbuf_reset(&line); + strbuf_addf(&line, "%06o %s %s\t", + mode & ~NO_DELTA, type, hash_to_hex(hash)); + quote_c_style(path, &line, NULL, 0); + strbuf_addch(&line, '\n'); + } + cat_blob_write(line.buf, line.len); +} + +static void parse_ls(const char *p, struct branch *b) +{ + struct tree_entry *root = NULL; + struct tree_entry leaf = {NULL}; + + /* ls SP ( SP)? */ + if (*p == '"') { + if (!b) + die("Not in a commit: %s", command_buf.buf); + root = &b->branch_tree; + } else { + struct object_entry *e = parse_treeish_dataref(&p); + root = new_tree_entry(); + oidcpy(&root->versions[1].oid, &e->idx.oid); + if (!is_null_oid(&root->versions[1].oid)) + root->versions[1].mode = S_IFDIR; + load_tree(root); + } + if (*p == '"') { + static struct strbuf uq = STRBUF_INIT; + const char *endp; + strbuf_reset(&uq); + if (unquote_c_style(&uq, p, &endp)) + die("Invalid path: %s", command_buf.buf); + if (*endp) + die("Garbage after path in: %s", command_buf.buf); + p = uq.buf; + } + tree_content_get(root, p, &leaf, 1); + /* + * A directory in preparation would have a sha1 of zero + * until it is saved. Save, for simplicity. + */ + if (S_ISDIR(leaf.versions[1].mode)) + store_tree(&leaf); + + print_ls(leaf.versions[1].mode, leaf.versions[1].oid.hash, p); + if (leaf.tree) + release_tree_content_recursive(leaf.tree); + if (!b || root != &b->branch_tree) + release_tree_entry(root); +} + +static void checkpoint(void) +{ + checkpoint_requested = 0; + if (object_count) { + cycle_packfile(); + } + dump_branches(); + dump_tags(); + dump_marks(); +} + +static void parse_checkpoint(void) +{ + checkpoint_requested = 1; + skip_optional_lf(); +} + +static void parse_progress(void) +{ + fwrite(command_buf.buf, 1, command_buf.len, stdout); + fputc('\n', stdout); + fflush(stdout); + skip_optional_lf(); +} + +static void parse_alias(void) +{ + struct object_entry *e; + struct branch b; + + skip_optional_lf(); + read_next_command(); + + /* mark ... */ + parse_mark(); + if (!next_mark) + die(_("Expected 'mark' command, got %s"), command_buf.buf); + + /* to ... */ + memset(&b, 0, sizeof(b)); + if (!parse_objectish_with_prefix(&b, "to ")) + die(_("Expected 'to' command, got %s"), command_buf.buf); + e = find_object(&b.oid); + assert(e); + insert_mark(&marks, next_mark, e); +} + +static char* make_fast_import_path(const char *path) +{ + if (!relative_marks_paths || is_absolute_path(path)) + return xstrdup(path); + return git_pathdup("info/fast-import/%s", path); +} + +static void option_import_marks(const char *marks, + int from_stream, int ignore_missing) +{ + if (import_marks_file) { + if (from_stream) + die("Only one import-marks command allowed per stream"); + + /* read previous mark file */ + if(!import_marks_file_from_stream) + read_marks(); + } + + import_marks_file = make_fast_import_path(marks); + import_marks_file_from_stream = from_stream; + import_marks_file_ignore_missing = ignore_missing; +} + +static void option_date_format(const char *fmt) +{ + if (!strcmp(fmt, "raw")) + whenspec = WHENSPEC_RAW; + else if (!strcmp(fmt, "raw-permissive")) + whenspec = WHENSPEC_RAW_PERMISSIVE; + else if (!strcmp(fmt, "rfc2822")) + whenspec = WHENSPEC_RFC2822; + else if (!strcmp(fmt, "now")) + whenspec = WHENSPEC_NOW; + else + die("unknown --date-format argument %s", fmt); +} + +static unsigned long ulong_arg(const char *option, const char *arg) +{ + char *endptr; + unsigned long rv = strtoul(arg, &endptr, 0); + if (strchr(arg, '-') || endptr == arg || *endptr) + die("%s: argument must be a non-negative integer", option); + return rv; +} + +static void option_depth(const char *depth) +{ + max_depth = ulong_arg("--depth", depth); + if (max_depth > MAX_DEPTH) + die("--depth cannot exceed %u", MAX_DEPTH); +} + +static void option_active_branches(const char *branches) +{ + max_active_branches = ulong_arg("--active-branches", branches); +} + +static void option_export_marks(const char *marks) +{ + export_marks_file = make_fast_import_path(marks); +} + +static void option_cat_blob_fd(const char *fd) +{ + unsigned long n = ulong_arg("--cat-blob-fd", fd); + if (n > (unsigned long) INT_MAX) + die("--cat-blob-fd cannot exceed %d", INT_MAX); + cat_blob_fd = (int) n; +} + +static void option_export_pack_edges(const char *edges) +{ + if (pack_edges) + fclose(pack_edges); + pack_edges = xfopen(edges, "a"); +} + +static void option_rewrite_submodules(const char *arg, struct string_list *list) +{ + struct mark_set *ms; + FILE *fp; + char *s = xstrdup(arg); + char *f = strchr(s, ':'); + if (!f) + die(_("Expected format name:filename for submodule rewrite option")); + *f = '\0'; + f++; + CALLOC_ARRAY(ms, 1); + + fp = fopen(f, "r"); + if (!fp) + die_errno("cannot read '%s'", f); + read_mark_file(&ms, fp, insert_oid_entry); + fclose(fp); + + string_list_insert(list, s)->util = ms; +} + +static int parse_one_option(const char *option) +{ + if (skip_prefix(option, "max-pack-size=", &option)) { + unsigned long v; + if (!git_parse_ulong(option, &v)) + return 0; + if (v < 8192) { + warning("max-pack-size is now in bytes, assuming --max-pack-size=%lum", v); + v *= 1024 * 1024; + } else if (v < 1024 * 1024) { + warning("minimum max-pack-size is 1 MiB"); + v = 1024 * 1024; + } + max_packsize = v; + } else if (skip_prefix(option, "big-file-threshold=", &option)) { + unsigned long v; + if (!git_parse_ulong(option, &v)) + return 0; + big_file_threshold = v; + } else if (skip_prefix(option, "depth=", &option)) { + option_depth(option); + } else if (skip_prefix(option, "active-branches=", &option)) { + option_active_branches(option); + } else if (skip_prefix(option, "export-pack-edges=", &option)) { + option_export_pack_edges(option); + } else if (!strcmp(option, "quiet")) { + show_stats = 0; + } else if (!strcmp(option, "stats")) { + show_stats = 1; + } else if (!strcmp(option, "allow-unsafe-features")) { + ; /* already handled during early option parsing */ + } else { + return 0; + } + + return 1; +} + +static void check_unsafe_feature(const char *feature, int from_stream) +{ + if (from_stream && !allow_unsafe_features) + die(_("feature '%s' forbidden in input without --allow-unsafe-features"), + feature); +} + +static int parse_one_feature(const char *feature, int from_stream) +{ + const char *arg; + + if (skip_prefix(feature, "date-format=", &arg)) { + option_date_format(arg); + } else if (skip_prefix(feature, "import-marks=", &arg)) { + check_unsafe_feature("import-marks", from_stream); + option_import_marks(arg, from_stream, 0); + } else if (skip_prefix(feature, "import-marks-if-exists=", &arg)) { + check_unsafe_feature("import-marks-if-exists", from_stream); + option_import_marks(arg, from_stream, 1); + } else if (skip_prefix(feature, "export-marks=", &arg)) { + check_unsafe_feature(feature, from_stream); + option_export_marks(arg); + } else if (!strcmp(feature, "alias")) { + ; /* Don't die - this feature is supported */ + } else if (skip_prefix(feature, "rewrite-submodules-to=", &arg)) { + option_rewrite_submodules(arg, &sub_marks_to); + } else if (skip_prefix(feature, "rewrite-submodules-from=", &arg)) { + option_rewrite_submodules(arg, &sub_marks_from); + } else if (!strcmp(feature, "get-mark")) { + ; /* Don't die - this feature is supported */ + } else if (!strcmp(feature, "cat-blob")) { + ; /* Don't die - this feature is supported */ + } else if (!strcmp(feature, "relative-marks")) { + relative_marks_paths = 1; + } else if (!strcmp(feature, "no-relative-marks")) { + relative_marks_paths = 0; + } else if (!strcmp(feature, "done")) { + require_explicit_termination = 1; + } else if (!strcmp(feature, "force")) { + force_update = 1; + } else if (!strcmp(feature, "notes") || !strcmp(feature, "ls")) { + ; /* do nothing; we have the feature */ + } else { + return 0; + } + + return 1; +} + +static void parse_feature(const char *feature) +{ + if (seen_data_command) + die("Got feature command '%s' after data command", feature); + + if (parse_one_feature(feature, 1)) + return; + + die("This version of fast-import does not support feature %s.", feature); +} + +static void parse_option(const char *option) +{ + if (seen_data_command) + die("Got option command '%s' after data command", option); + + if (parse_one_option(option)) + return; + + die("This version of fast-import does not support option: %s", option); +} + +static void git_pack_config(void) +{ + int indexversion_value; + int limit; + unsigned long packsizelimit_value; + + if (!git_config_get_ulong("pack.depth", &max_depth)) { + if (max_depth > MAX_DEPTH) + max_depth = MAX_DEPTH; + } + if (!git_config_get_int("pack.indexversion", &indexversion_value)) { + 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); + } + if (!git_config_get_ulong("pack.packsizelimit", &packsizelimit_value)) + max_packsize = packsizelimit_value; + + if (!git_config_get_int("fastimport.unpacklimit", &limit)) + unpack_limit = limit; + else if (!git_config_get_int("transfer.unpacklimit", &limit)) + unpack_limit = limit; + + git_config(git_default_config, NULL); +} + +static const char fast_import_usage[] = +"git fast-import [--date-format=] [--max-pack-size=] [--big-file-threshold=] [--depth=] [--active-branches=] [--export-marks=]"; + +static void parse_argv(void) +{ + unsigned int i; + + for (i = 1; i < global_argc; i++) { + const char *a = global_argv[i]; + + if (*a != '-' || !strcmp(a, "--")) + break; + + if (!skip_prefix(a, "--", &a)) + die("unknown option %s", a); + + if (parse_one_option(a)) + continue; + + if (parse_one_feature(a, 0)) + continue; + + if (skip_prefix(a, "cat-blob-fd=", &a)) { + option_cat_blob_fd(a); + continue; + } + + die("unknown option --%s", a); + } + if (i != global_argc) + usage(fast_import_usage); + + seen_data_command = 1; + if (import_marks_file) + read_marks(); + build_mark_map(&sub_marks_from, &sub_marks_to); +} + +int cmd_fast_import(int argc, const char **argv, const char *prefix) +{ + unsigned int i; + + if (argc == 2 && !strcmp(argv[1], "-h")) + usage(fast_import_usage); + + reset_pack_idx_option(&pack_idx_opts); + git_pack_config(); + + alloc_objects(object_entry_alloc); + strbuf_init(&command_buf, 0); + CALLOC_ARRAY(atom_table, atom_table_sz); + CALLOC_ARRAY(branch_table, branch_table_sz); + CALLOC_ARRAY(avail_tree_table, avail_tree_table_sz); + marks = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set)); + + hashmap_init(&object_table, object_entry_hashcmp, NULL, 0); + + /* + * We don't parse most options until after we've seen the set of + * "feature" lines at the start of the stream (which allows the command + * line to override stream data). But we must do an early parse of any + * command-line options that impact how we interpret the feature lines. + */ + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (*arg != '-' || !strcmp(arg, "--")) + break; + if (!strcmp(arg, "--allow-unsafe-features")) + allow_unsafe_features = 1; + } + + global_argc = argc; + global_argv = argv; + + rc_free = mem_pool_alloc(&fi_mem_pool, cmd_save * sizeof(*rc_free)); + for (i = 0; i < (cmd_save - 1); i++) + rc_free[i].next = &rc_free[i + 1]; + rc_free[cmd_save - 1].next = NULL; + + start_packfile(); + set_die_routine(die_nicely); + set_checkpoint_signal(); + while (read_next_command() != EOF) { + const char *v; + if (!strcmp("blob", command_buf.buf)) + parse_new_blob(); + else if (skip_prefix(command_buf.buf, "commit ", &v)) + parse_new_commit(v); + else if (skip_prefix(command_buf.buf, "tag ", &v)) + parse_new_tag(v); + else if (skip_prefix(command_buf.buf, "reset ", &v)) + parse_reset_branch(v); + else if (skip_prefix(command_buf.buf, "ls ", &v)) + parse_ls(v, NULL); + else if (skip_prefix(command_buf.buf, "cat-blob ", &v)) + parse_cat_blob(v); + else if (skip_prefix(command_buf.buf, "get-mark ", &v)) + parse_get_mark(v); + else if (!strcmp("checkpoint", command_buf.buf)) + parse_checkpoint(); + else if (!strcmp("done", command_buf.buf)) + break; + else if (!strcmp("alias", command_buf.buf)) + parse_alias(); + else if (starts_with(command_buf.buf, "progress ")) + parse_progress(); + else if (skip_prefix(command_buf.buf, "feature ", &v)) + parse_feature(v); + else if (skip_prefix(command_buf.buf, "option git ", &v)) + parse_option(v); + else if (starts_with(command_buf.buf, "option ")) + /* ignore non-git options*/; + else + die("Unsupported command: %s", command_buf.buf); + + if (checkpoint_requested) + checkpoint(); + } + + /* argv hasn't been parsed yet, do so */ + if (!seen_data_command) + parse_argv(); + + if (require_explicit_termination && feof(stdin)) + die("stream ends early"); + + end_packfile(); + + dump_branches(); + dump_tags(); + unkeep_all_packs(); + dump_marks(); + + if (pack_edges) + fclose(pack_edges); + + if (show_stats) { + uintmax_t total_count = 0, duplicate_count = 0; + for (i = 0; i < ARRAY_SIZE(object_count_by_type); i++) + total_count += object_count_by_type[i]; + for (i = 0; i < ARRAY_SIZE(duplicate_count_by_type); i++) + duplicate_count += duplicate_count_by_type[i]; + + fprintf(stderr, "%s statistics:\n", argv[0]); + fprintf(stderr, "---------------------------------------------------------------------\n"); + fprintf(stderr, "Alloc'd objects: %10" PRIuMAX "\n", alloc_count); + fprintf(stderr, "Total objects: %10" PRIuMAX " (%10" PRIuMAX " duplicates )\n", total_count, duplicate_count); + fprintf(stderr, " blobs : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_BLOB], duplicate_count_by_type[OBJ_BLOB], delta_count_by_type[OBJ_BLOB], delta_count_attempts_by_type[OBJ_BLOB]); + fprintf(stderr, " trees : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_TREE], duplicate_count_by_type[OBJ_TREE], delta_count_by_type[OBJ_TREE], delta_count_attempts_by_type[OBJ_TREE]); + fprintf(stderr, " commits: %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_COMMIT], duplicate_count_by_type[OBJ_COMMIT], delta_count_by_type[OBJ_COMMIT], delta_count_attempts_by_type[OBJ_COMMIT]); + fprintf(stderr, " tags : %10" PRIuMAX " (%10" PRIuMAX " duplicates %10" PRIuMAX " deltas of %10" PRIuMAX" attempts)\n", object_count_by_type[OBJ_TAG], duplicate_count_by_type[OBJ_TAG], delta_count_by_type[OBJ_TAG], delta_count_attempts_by_type[OBJ_TAG]); + fprintf(stderr, "Total branches: %10lu (%10lu loads )\n", branch_count, branch_load_count); + fprintf(stderr, " marks: %10" PRIuMAX " (%10" PRIuMAX " unique )\n", (((uintmax_t)1) << marks->shift) * 1024, marks_set_count); + fprintf(stderr, " atoms: %10u\n", atom_cnt); + fprintf(stderr, "Memory total: %10" PRIuMAX " KiB\n", (tree_entry_allocd + fi_mem_pool.pool_alloc + alloc_count*sizeof(struct object_entry))/1024); + fprintf(stderr, " pools: %10lu KiB\n", (unsigned long)((tree_entry_allocd + fi_mem_pool.pool_alloc) /1024)); + fprintf(stderr, " objects: %10" PRIuMAX " KiB\n", (alloc_count*sizeof(struct object_entry))/1024); + fprintf(stderr, "---------------------------------------------------------------------\n"); + pack_report(); + fprintf(stderr, "---------------------------------------------------------------------\n"); + fprintf(stderr, "\n"); + } + + return failure ? 1 : 0; +} diff -Nru git-2.25.1/builtin/fetch.c git-2.34.1/builtin/fetch.c --- git-2.25.1/builtin/fetch.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/fetch.c 2021-11-24 19:29:21.000000000 +0000 @@ -19,7 +19,7 @@ #include "submodule-config.h" #include "submodule.h" #include "connected.h" -#include "argv-array.h" +#include "strvec.h" #include "utf8.h" #include "packfile.h" #include "list-objects-filter-options.h" @@ -27,6 +27,7 @@ #include "branch.h" #include "promisor-remote.h" #include "commit-graph.h" +#include "shallow.h" #define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000) @@ -47,6 +48,7 @@ static int fetch_prune_config = -1; /* unspecified */ static int fetch_show_forced_updates = 1; static uint64_t forced_updates_ms = 0; +static int prefetch = 0; static int prune = -1; /* unspecified */ #define PRUNE_BY_DEFAULT 0 /* do we prune by default? */ @@ -55,12 +57,14 @@ #define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */ static int all, append, dry_run, force, keep, multiple, update_head_ok; +static int write_fetch_head = 1; static int verbosity, deepen_relative, set_upstream; static int progress = -1; static int enable_auto_gc = 1; static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen; static int max_jobs = -1, submodule_fetch_jobs_config = -1; static int fetch_parallel_config = 1; +static int atomic_fetch; static enum transport_family family; static const char *depth; static const char *deepen_since; @@ -78,6 +82,8 @@ static struct string_list server_options = STRING_LIST_INIT_DUP; static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP; static int fetch_write_commit_graph = -1; +static int stdin_refspecs = 0; +static int negotiate_only; static int git_fetch_config(const char *k, const char *v, void *cb) { @@ -141,6 +147,8 @@ N_("set upstream for git pull/fetch")), OPT_BOOL('a', "append", &append, N_("append to .git/FETCH_HEAD instead of overwriting")), + OPT_BOOL(0, "atomic", &atomic_fetch, + N_("use atomic transaction to update references")), OPT_STRING(0, "upload-pack", &upload_pack, N_("path"), N_("path to upload pack on remote end")), OPT__FORCE(&force, N_("force overwrite of local reference"), 0), @@ -152,15 +160,19 @@ N_("do not fetch all tags (--no-tags)"), TAGS_UNSET), OPT_INTEGER('j', "jobs", &max_jobs, N_("number of submodules fetched in parallel")), + OPT_BOOL(0, "prefetch", &prefetch, + N_("modify the refspec to place all refs within refs/prefetch/")), OPT_BOOL('p', "prune", &prune, N_("prune remote-tracking branches no longer on remote")), OPT_BOOL('P', "prune-tags", &prune_tags, N_("prune local tags no longer on remote and clobber changed tags")), - { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, N_("on-demand"), + OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules, N_("on-demand"), N_("control recursive fetching of submodules"), - PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules }, + PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules), OPT_BOOL(0, "dry-run", &dry_run, N_("dry run")), + OPT_BOOL(0, "write-fetch-head", &write_fetch_head, + N_("write fetched references to the FETCH_HEAD file")), OPT_BOOL('k', "keep", &keep, N_("keep downloaded pack")), OPT_BOOL('u', "update-head-ok", &update_head_ok, N_("allow updating of HEAD ref")), @@ -178,15 +190,15 @@ 1, PARSE_OPT_NONEG), { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, N_("dir"), N_("prepend this to submodule path output"), PARSE_OPT_HIDDEN }, - { OPTION_CALLBACK, 0, "recurse-submodules-default", + OPT_CALLBACK_F(0, "recurse-submodules-default", &recurse_submodules_default, N_("on-demand"), N_("default for recursive fetching of submodules " "(lower priority than config files)"), - PARSE_OPT_HIDDEN, option_fetch_parse_recurse_submodules }, + PARSE_OPT_HIDDEN, option_fetch_parse_recurse_submodules), OPT_BOOL(0, "update-shallow", &update_shallow, N_("accept refs that update .git/shallow")), - { OPTION_CALLBACK, 0, "refmap", NULL, N_("refmap"), - N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg }, + OPT_CALLBACK_F(0, "refmap", NULL, N_("refmap"), + N_("specify fetch refmap"), PARSE_OPT_NONEG, parse_refmap_arg), OPT_STRING_LIST('o', "server-option", &server_options, N_("server-specific"), N_("option to transmit")), OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"), TRANSPORT_FAMILY_IPV4), @@ -194,13 +206,19 @@ TRANSPORT_FAMILY_IPV6), OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"), N_("report that we have only objects reachable from this object")), + OPT_BOOL(0, "negotiate-only", &negotiate_only, + N_("do not fetch a packfile; instead, print ancestors of negotiation tips")), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), + OPT_BOOL(0, "auto-maintenance", &enable_auto_gc, + N_("run 'maintenance --auto' after fetching")), OPT_BOOL(0, "auto-gc", &enable_auto_gc, - N_("run 'gc --auto' after fetching")), + N_("run 'maintenance --auto' after fetching")), OPT_BOOL(0, "show-forced-updates", &fetch_show_forced_updates, N_("check for forced-updates on all updated branches")), OPT_BOOL(0, "write-commit-graph", &fetch_write_commit_graph, N_("write the commit-graph after fetching")), + OPT_BOOL(0, "stdin", &stdin_refspecs, + N_("accept refspecs from stdin")), OPT_END() }; @@ -335,6 +353,7 @@ struct string_list_item *remote_ref_item; const struct ref *ref; struct refname_hash_entry *item = NULL; + const int quick_flags = OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT; refname_hash_init(&existing_refs); refname_hash_init(&remote_refs); @@ -353,10 +372,9 @@ */ if (ends_with(ref->name, "^{}")) { if (item && - !has_object_file_with_flags(&ref->old_oid, - OBJECT_INFO_QUICK) && + !has_object_file_with_flags(&ref->old_oid, quick_flags) && !oidset_contains(&fetch_oids, &ref->old_oid) && - !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) && + !has_object_file_with_flags(&item->oid, quick_flags) && !oidset_contains(&fetch_oids, &item->oid)) clear_item(item); item = NULL; @@ -370,7 +388,7 @@ * fetch. */ if (item && - !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) && + !has_object_file_with_flags(&item->oid, quick_flags) && !oidset_contains(&fetch_oids, &item->oid)) clear_item(item); @@ -384,14 +402,14 @@ item = refname_hash_add(&remote_refs, ref->name, &ref->old_oid); string_list_insert(&remote_refs_list, ref->name); } - hashmap_free_entries(&existing_refs, struct refname_hash_entry, ent); + hashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent); /* * We may have a final lightweight tag that needs to be * checked to see if it needs fetching. */ if (item && - !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) && + !has_object_file_with_flags(&item->oid, quick_flags) && !oidset_contains(&fetch_oids, &item->oid)) clear_item(item); @@ -419,11 +437,61 @@ **tail = rm; *tail = &rm->next; } - hashmap_free_entries(&remote_refs, struct refname_hash_entry, ent); + hashmap_clear_and_free(&remote_refs, struct refname_hash_entry, ent); string_list_clear(&remote_refs_list, 0); oidset_clear(&fetch_oids); } +static void filter_prefetch_refspec(struct refspec *rs) +{ + int i; + + if (!prefetch) + return; + + for (i = 0; i < rs->nr; i++) { + struct strbuf new_dst = STRBUF_INIT; + char *old_dst; + const char *sub = NULL; + + if (rs->items[i].negative) + continue; + if (!rs->items[i].dst || + (rs->items[i].src && + !strncmp(rs->items[i].src, "refs/tags/", 10))) { + int j; + + free(rs->items[i].src); + free(rs->items[i].dst); + + for (j = i + 1; j < rs->nr; j++) { + rs->items[j - 1] = rs->items[j]; + rs->raw[j - 1] = rs->raw[j]; + } + rs->nr--; + i--; + continue; + } + + old_dst = rs->items[i].dst; + strbuf_addstr(&new_dst, "refs/prefetch/"); + + /* + * If old_dst starts with "refs/", then place + * sub after that prefix. Otherwise, start at + * the beginning of the string. + */ + if (!skip_prefix(old_dst, "refs/", &sub)) + sub = old_dst; + strbuf_addstr(&new_dst, sub); + + rs->items[i].dst = strbuf_detach(&new_dst, NULL); + rs->items[i].force = 1; + + free(old_dst); + } +} + static struct ref *get_ref_map(struct remote *remote, const struct ref *remote_refs, struct refspec *rs, @@ -438,6 +506,11 @@ struct ref *orefs = NULL, **oref_tail = &orefs; struct hashmap existing_refs; + int existing_refs_populated = 0; + + filter_prefetch_refspec(rs); + if (remote) + filter_prefetch_refspec(&remote->fetch); if (rs->nr) { struct refspec *fetch_refspec; @@ -507,7 +580,7 @@ if (has_merge && !strcmp(branch->remote_name, remote->name)) add_merge_config(&ref_map, remote_refs, branch, &tail); - } else { + } else if (!prefetch) { ref_map = get_remote_ref(remote_refs, "HEAD"); if (!ref_map) die(_("Couldn't find remote ref HEAD")); @@ -529,10 +602,17 @@ tail = &rm->next; } - ref_map = ref_remove_duplicates(ref_map); + /* + * apply negative refspecs first, before we remove duplicates. This is + * necessary as negative refspecs might remove an otherwise conflicting + * duplicate. + */ + if (rs->nr) + ref_map = apply_negative_refspecs(ref_map, rs); + else + ref_map = apply_negative_refspecs(ref_map, &remote->fetch); - refname_hash_init(&existing_refs); - for_each_ref(add_one_refname, &existing_refs); + ref_map = ref_remove_duplicates(ref_map); for (rm = ref_map; rm; rm = rm->next) { if (rm->peer_ref) { @@ -540,6 +620,12 @@ struct refname_hash_entry *peer_item; unsigned int hash = strhash(refname); + if (!existing_refs_populated) { + refname_hash_init(&existing_refs); + for_each_ref(add_one_refname, &existing_refs); + existing_refs_populated = 1; + } + peer_item = hashmap_get_entry_from_hash(&existing_refs, hash, refname, struct refname_hash_entry, ent); @@ -549,7 +635,8 @@ } } } - hashmap_free_entries(&existing_refs, struct refname_hash_entry, ent); + if (existing_refs_populated) + hashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent); return ref_map; } @@ -559,13 +646,14 @@ static int s_update_ref(const char *action, struct ref *ref, + struct ref_transaction *transaction, int check_old) { char *msg; char *rla = getenv("GIT_REFLOG_ACTION"); - struct ref_transaction *transaction; + struct ref_transaction *our_transaction = NULL; struct strbuf err = STRBUF_INIT; - int ret, df_conflict = 0; + int ret; if (dry_run) return 0; @@ -573,31 +661,47 @@ rla = default_rla.buf; msg = xstrfmt("%s: %s", rla, action); - transaction = ref_transaction_begin(&err); - if (!transaction || - ref_transaction_update(transaction, ref->name, - &ref->new_oid, - check_old ? &ref->old_oid : NULL, - 0, msg, &err)) - goto fail; + /* + * If no transaction was passed to us, we manage the transaction + * ourselves. Otherwise, we trust the caller to handle the transaction + * lifecycle. + */ + if (!transaction) { + transaction = our_transaction = ref_transaction_begin(&err); + if (!transaction) { + ret = STORE_REF_ERROR_OTHER; + goto out; + } + } - ret = ref_transaction_commit(transaction, &err); + ret = ref_transaction_update(transaction, ref->name, &ref->new_oid, + check_old ? &ref->old_oid : NULL, + 0, msg, &err); if (ret) { - df_conflict = (ret == TRANSACTION_NAME_CONFLICT); - goto fail; + ret = STORE_REF_ERROR_OTHER; + goto out; } - ref_transaction_free(transaction); - strbuf_release(&err); - free(msg); - return 0; -fail: - ref_transaction_free(transaction); - error("%s", err.buf); + if (our_transaction) { + switch (ref_transaction_commit(our_transaction, &err)) { + case 0: + break; + case TRANSACTION_NAME_CONFLICT: + ret = STORE_REF_ERROR_DF_CONFLICT; + goto out; + default: + ret = STORE_REF_ERROR_OTHER; + goto out; + } + } + +out: + ref_transaction_free(our_transaction); + if (ret) + error("%s", err.buf); strbuf_release(&err); free(msg); - return df_conflict ? STORE_REF_ERROR_DF_CONFLICT - : STORE_REF_ERROR_OTHER; + return ret; } static int refcol_width = 10; @@ -608,7 +712,7 @@ int max, rlen, llen, len; /* uptodate lines are only shown on high verbosity level */ - if (!verbosity && oideq(&ref->peer_ref->old_oid, &ref->old_oid)) + if (verbosity <= 0 && oideq(&ref->peer_ref->old_oid, &ref->old_oid)) return; max = term_columns(); @@ -644,7 +748,10 @@ struct ref *rm; const char *format = "full"; - git_config_get_string_const("fetch.output", &format); + if (verbosity < 0) + return; + + git_config_get_string_tmp("fetch.output", &format); if (!strcasecmp(format, "full")) compact_format = 0; else if (!strcasecmp(format, "compact")) @@ -723,7 +830,12 @@ const char *remote, const char *local, int summary_width) { - int width = (summary_width + strlen(summary) - gettext_width(summary)); + int width; + + if (verbosity < 0) + return; + + width = (summary_width + strlen(summary) - gettext_width(summary)); strbuf_addf(display, "%c %-*s ", code, width, summary); if (!compact_format) @@ -735,19 +847,18 @@ } static int update_local_ref(struct ref *ref, + struct ref_transaction *transaction, const char *remote, const struct ref *remote_ref, struct strbuf *display, int summary_width) { struct commit *current = NULL, *updated; - enum object_type type; struct branch *current_branch = branch_get(NULL); const char *pretty_ref = prettify_refname(ref->name); int fast_forward = 0; - type = oid_object_info(the_repository, &ref->new_oid, NULL); - if (type < 0) + if (!repo_has_object_file(the_repository, &ref->new_oid)) die(_("object %s not found"), oid_to_hex(&ref->new_oid)); if (oideq(&ref->old_oid, &ref->new_oid)) { @@ -775,7 +886,7 @@ starts_with(ref->name, "refs/tags/")) { if (force || ref->force) { int r; - r = s_update_ref("updating tag", ref, 0); + r = s_update_ref("updating tag", ref, transaction, 0); format_display(display, r ? '!' : 't', _("[tag update]"), r ? _("unable to update local ref") : NULL, remote, pretty_ref, summary_width); @@ -812,7 +923,7 @@ what = _("[new ref]"); } - r = s_update_ref(msg, ref, 0); + r = s_update_ref(msg, ref, transaction, 0); format_display(display, r ? '!' : '*', what, r ? _("unable to update local ref") : NULL, remote, pretty_ref, summary_width); @@ -834,7 +945,7 @@ strbuf_add_unique_abbrev(&quickref, ¤t->object.oid, DEFAULT_ABBREV); strbuf_addstr(&quickref, ".."); strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV); - r = s_update_ref("fast-forward", ref, 1); + r = s_update_ref("fast-forward", ref, transaction, 1); format_display(display, r ? '!' : ' ', quickref.buf, r ? _("unable to update local ref") : NULL, remote, pretty_ref, summary_width); @@ -846,7 +957,7 @@ strbuf_add_unique_abbrev(&quickref, ¤t->object.oid, DEFAULT_ABBREV); strbuf_addstr(&quickref, "..."); strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV); - r = s_update_ref("forced-update", ref, 1); + r = s_update_ref("forced-update", ref, transaction, 1); format_display(display, r ? '!' : '+', quickref.buf, r ? _("unable to update local ref") : _("forced update"), remote, pretty_ref, summary_width); @@ -859,7 +970,7 @@ } } -static int iterate_ref_map(void *cb_data, struct object_id *oid) +static const struct object_id *iterate_ref_map(void *cb_data) { struct ref **rm = cb_data; struct ref *ref = *rm; @@ -867,12 +978,94 @@ while (ref && ref->status == REF_STATUS_REJECT_SHALLOW) ref = ref->next; if (!ref) - return -1; /* end of the list */ + return NULL; *rm = ref->next; - oidcpy(oid, &ref->old_oid); + return &ref->old_oid; +} + +struct fetch_head { + FILE *fp; + struct strbuf buf; +}; + +static int open_fetch_head(struct fetch_head *fetch_head) +{ + const char *filename = git_path_fetch_head(the_repository); + + if (write_fetch_head) { + fetch_head->fp = fopen(filename, "a"); + if (!fetch_head->fp) + return error_errno(_("cannot open %s"), filename); + strbuf_init(&fetch_head->buf, 0); + } else { + fetch_head->fp = NULL; + } + return 0; } +static void append_fetch_head(struct fetch_head *fetch_head, + const struct object_id *old_oid, + enum fetch_head_status fetch_head_status, + const char *note, + const char *url, size_t url_len) +{ + char old_oid_hex[GIT_MAX_HEXSZ + 1]; + const char *merge_status_marker; + size_t i; + + if (!fetch_head->fp) + return; + + switch (fetch_head_status) { + case FETCH_HEAD_NOT_FOR_MERGE: + merge_status_marker = "not-for-merge"; + break; + case FETCH_HEAD_MERGE: + merge_status_marker = ""; + break; + default: + /* do not write anything to FETCH_HEAD */ + return; + } + + strbuf_addf(&fetch_head->buf, "%s\t%s\t%s", + oid_to_hex_r(old_oid_hex, old_oid), merge_status_marker, note); + for (i = 0; i < url_len; ++i) + if ('\n' == url[i]) + strbuf_addstr(&fetch_head->buf, "\\n"); + else + strbuf_addch(&fetch_head->buf, url[i]); + strbuf_addch(&fetch_head->buf, '\n'); + + /* + * When using an atomic fetch, we do not want to update FETCH_HEAD if + * any of the reference updates fails. We thus have to write all + * updates to a buffer first and only commit it as soon as all + * references have been successfully updated. + */ + if (!atomic_fetch) { + strbuf_write(&fetch_head->buf, fetch_head->fp); + strbuf_reset(&fetch_head->buf); + } +} + +static void commit_fetch_head(struct fetch_head *fetch_head) +{ + if (!fetch_head->fp || !atomic_fetch) + return; + strbuf_write(&fetch_head->buf, fetch_head->fp); +} + +static void close_fetch_head(struct fetch_head *fetch_head) +{ + if (!fetch_head->fp) + return; + + fclose(fetch_head->fp); + strbuf_release(&fetch_head->buf); +} + static const char warn_show_forced_updates[] = N_("Fetch normally indicates which branches had a forced update,\n" "but that check has been disabled. To re-enable, use '--show-forced-updates'\n" @@ -885,20 +1078,19 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, int connectivity_checked, struct ref *ref_map) { - FILE *fp; - struct commit *commit; + struct fetch_head fetch_head; int url_len, i, rc = 0; - struct strbuf note = STRBUF_INIT; + struct strbuf note = STRBUF_INIT, err = STRBUF_INIT; + struct ref_transaction *transaction = NULL; const char *what, *kind; struct ref *rm; char *url; - const char *filename = dry_run ? "/dev/null" : git_path_fetch_head(the_repository); int want_status; int summary_width = transport_summary_width(ref_map); - fp = fopen(filename, "a"); - if (!fp) - return error_errno(_("cannot open %s"), filename); + rc = open_fetch_head(&fetch_head); + if (rc) + return -1; if (raw_url) url = transport_anonymize_url(raw_url); @@ -906,13 +1098,23 @@ url = xstrdup("foreign"); if (!connectivity_checked) { + struct check_connected_options opt = CHECK_CONNECTED_INIT; + rm = ref_map; - if (check_connected(iterate_ref_map, &rm, NULL)) { + if (check_connected(iterate_ref_map, &rm, &opt)) { rc = error(_("%s did not send all necessary objects\n"), url); goto abort; } } + if (atomic_fetch) { + transaction = ref_transaction_begin(&err); + if (!transaction) { + error("%s", err.buf); + goto abort; + } + } + prepare_format_display(ref_map); /* @@ -924,21 +1126,33 @@ want_status <= FETCH_HEAD_IGNORE; want_status++) { for (rm = ref_map; rm; rm = rm->next) { + struct commit *commit = NULL; struct ref *ref = NULL; - const char *merge_status_marker = ""; if (rm->status == REF_STATUS_REJECT_SHALLOW) { if (want_status == FETCH_HEAD_MERGE) - warning(_("reject %s because shallow roots are not allowed to be updated"), + warning(_("rejected %s because shallow roots are not allowed to be updated"), rm->peer_ref ? rm->peer_ref->name : rm->name); continue; } - commit = lookup_commit_reference_gently(the_repository, - &rm->old_oid, - 1); - if (!commit) - rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE; + /* + * References in "refs/tags/" are often going to point + * to annotated tags, which are not part of the + * commit-graph. We thus only try to look up refs in + * the graph which are not in that namespace to not + * regress performance in repositories with many + * annotated tags. + */ + if (!starts_with(rm->name, "refs/tags/")) + commit = lookup_commit_in_graph(the_repository, &rm->old_oid); + if (!commit) { + commit = lookup_commit_reference_gently(the_repository, + &rm->old_oid, + 1); + if (!commit) + rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE; + } if (rm->fetch_head_status != want_status) continue; @@ -950,8 +1164,10 @@ ref->force = rm->peer_ref->force; } - if (recurse_submodules != RECURSE_SUBMODULES_OFF) + if (recurse_submodules != RECURSE_SUBMODULES_OFF && + (!rm->peer_ref || !oideq(&ref->old_oid, &ref->new_oid))) { check_for_new_submodule_commits(&rm->old_oid); + } if (!strcmp(rm->name, "HEAD")) { kind = ""; @@ -981,55 +1197,55 @@ strbuf_addf(¬e, "%s ", kind); strbuf_addf(¬e, "'%s' of ", what); } - switch (rm->fetch_head_status) { - case FETCH_HEAD_NOT_FOR_MERGE: - merge_status_marker = "not-for-merge"; - /* fall-through */ - case FETCH_HEAD_MERGE: - fprintf(fp, "%s\t%s\t%s", - oid_to_hex(&rm->old_oid), - merge_status_marker, - note.buf); - for (i = 0; i < url_len; ++i) - if ('\n' == url[i]) - fputs("\\n", fp); - else - fputc(url[i], fp); - fputc('\n', fp); - break; - default: - /* do not write anything to FETCH_HEAD */ - break; - } + + append_fetch_head(&fetch_head, &rm->old_oid, + rm->fetch_head_status, + note.buf, url, url_len); strbuf_reset(¬e); if (ref) { - rc |= update_local_ref(ref, what, rm, ¬e, - summary_width); + rc |= update_local_ref(ref, transaction, what, + rm, ¬e, summary_width); free(ref); - } else + } else if (write_fetch_head || dry_run) { + /* + * Display fetches written to FETCH_HEAD (or + * would be written to FETCH_HEAD, if --dry-run + * is set). + */ format_display(¬e, '*', *kind ? kind : "branch", NULL, *what ? what : "HEAD", "FETCH_HEAD", summary_width); + } if (note.len) { - if (verbosity >= 0 && !shown_url) { + if (!shown_url) { fprintf(stderr, _("From %.*s\n"), url_len, url); shown_url = 1; } - if (verbosity >= 0) - fprintf(stderr, " %s\n", note.buf); + fprintf(stderr, " %s\n", note.buf); } } } + if (!rc && transaction) { + rc = ref_transaction_commit(transaction, &err); + if (rc) { + error("%s", err.buf); + goto abort; + } + } + + if (!rc) + commit_fetch_head(&fetch_head); + if (rc & STORE_REF_ERROR_DF_CONFLICT) error(_("some local refs could not be updated; try running\n" " 'git remote prune %s' to remove any old, conflicting " "branches"), remote_name); - if (advice_fetch_show_forced_updates) { + if (advice_enabled(ADVICE_FETCH_SHOW_FORCED_UPDATES)) { if (!fetch_show_forced_updates) { warning(_(warn_show_forced_updates)); } else if (forced_updates_ms > FORCED_UPDATES_DELAY_WARNING_IN_MS) { @@ -1040,8 +1256,10 @@ abort: strbuf_release(¬e); + strbuf_release(&err); + ref_transaction_free(transaction); free(url); - fclose(fp); + close_fetch_head(&fetch_head); return rc; } @@ -1080,37 +1298,35 @@ return check_connected(iterate_ref_map, &rm, &opt); } -static int fetch_refs(struct transport *transport, struct ref *ref_map) +static int fetch_and_consume_refs(struct transport *transport, struct ref *ref_map) { - int ret = check_exist_and_connected(ref_map); + int connectivity_checked = 1; + int ret; + + /* + * We don't need to perform a fetch in case we can already satisfy all + * refs. + */ + ret = check_exist_and_connected(ref_map); if (ret) { trace2_region_enter("fetch", "fetch_refs", the_repository); ret = transport_fetch_refs(transport, ref_map); trace2_region_leave("fetch", "fetch_refs", the_repository); + if (ret) + goto out; + connectivity_checked = transport->smart_options ? + transport->smart_options->connectivity_checked : 0; } - if (!ret) - /* - * Keep the new pack's ".keep" file around to allow the caller - * time to update refs to reference the new objects. - */ - return 0; - transport_unlock_pack(transport); - return ret; -} -/* Update local refs based on the ref values fetched from a remote */ -static int consume_refs(struct transport *transport, struct ref *ref_map) -{ - int connectivity_checked = transport->smart_options - ? transport->smart_options->connectivity_checked : 0; - int ret; trace2_region_enter("fetch", "consume_refs", the_repository); ret = store_updated_refs(transport->url, transport->remote->name, connectivity_checked, ref_map); - transport_unlock_pack(transport); trace2_region_leave("fetch", "consume_refs", the_repository); + +out: + transport_unlock_pack(transport); return ret; } @@ -1226,7 +1442,9 @@ if (!has_glob_specials(s)) { struct object_id oid; if (get_oid(s, &oid)) - die("%s is not a valid object", s); + die(_("%s is not a valid object"), s); + if (!has_object(the_repository, &oid, 0)) + die(_("the object %s does not exist"), s); oid_array_append(oids, &oid); continue; } @@ -1297,8 +1515,7 @@ transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); transport_set_option(transport, TRANS_OPT_DEPTH, "0"); transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL); - if (!fetch_refs(transport, ref_map)) - consume_refs(transport, ref_map); + fetch_and_consume_refs(transport, ref_map); if (gsecondary) { transport_disconnect(gsecondary); @@ -1313,7 +1530,8 @@ int autotags = (transport->remote->fetch_tags == 1); int retcode = 0; const struct ref *remote_refs; - struct argv_array ref_prefixes = ARGV_ARRAY_INIT; + struct transport_ls_refs_options transport_ls_refs_options = + TRANSPORT_LS_REFS_OPTIONS_INIT; int must_list_refs = 1; if (tags == TAGS_DEFAULT) { @@ -1324,7 +1542,7 @@ } /* if not appending, truncate FETCH_HEAD */ - if (!append && !dry_run) { + if (!append && write_fetch_head) { retcode = truncate_fetch_head(); if (retcode) goto cleanup; @@ -1333,7 +1551,7 @@ if (rs->nr) { int i; - refspec_ref_prefixes(rs, &ref_prefixes); + refspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes); /* * We can avoid listing refs if all of them are exact @@ -1347,22 +1565,25 @@ } } } else if (transport->remote && transport->remote->fetch.nr) - refspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes); + refspec_ref_prefixes(&transport->remote->fetch, + &transport_ls_refs_options.ref_prefixes); if (tags == TAGS_SET || tags == TAGS_DEFAULT) { must_list_refs = 1; - if (ref_prefixes.argc) - argv_array_push(&ref_prefixes, "refs/tags/"); + if (transport_ls_refs_options.ref_prefixes.nr) + strvec_push(&transport_ls_refs_options.ref_prefixes, + "refs/tags/"); } if (must_list_refs) { trace2_region_enter("fetch", "remote_refs", the_repository); - remote_refs = transport_get_remote_refs(transport, &ref_prefixes); + remote_refs = transport_get_remote_refs(transport, + &transport_ls_refs_options); trace2_region_leave("fetch", "remote_refs", the_repository); } else remote_refs = NULL; - argv_array_clear(&ref_prefixes); + strvec_clear(&transport_ls_refs_options.ref_prefixes); ref_map = get_ref_map(transport->remote, remote_refs, rs, tags, &autotags); @@ -1385,7 +1606,7 @@ transport->url); } } - if (fetch_refs(transport, ref_map) || consume_refs(transport, ref_map)) { + if (fetch_and_consume_refs(transport, ref_map)) { free_refs(ref_map); retcode = 1; goto cleanup; @@ -1500,35 +1721,38 @@ return 1; } -static void add_options_to_argv(struct argv_array *argv) +static void add_options_to_argv(struct strvec *argv) { if (dry_run) - argv_array_push(argv, "--dry-run"); + strvec_push(argv, "--dry-run"); if (prune != -1) - argv_array_push(argv, prune ? "--prune" : "--no-prune"); + strvec_push(argv, prune ? "--prune" : "--no-prune"); if (prune_tags != -1) - argv_array_push(argv, prune_tags ? "--prune-tags" : "--no-prune-tags"); + strvec_push(argv, prune_tags ? "--prune-tags" : "--no-prune-tags"); if (update_head_ok) - argv_array_push(argv, "--update-head-ok"); + strvec_push(argv, "--update-head-ok"); if (force) - argv_array_push(argv, "--force"); + strvec_push(argv, "--force"); if (keep) - argv_array_push(argv, "--keep"); + strvec_push(argv, "--keep"); if (recurse_submodules == RECURSE_SUBMODULES_ON) - argv_array_push(argv, "--recurse-submodules"); + strvec_push(argv, "--recurse-submodules"); else if (recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) - argv_array_push(argv, "--recurse-submodules=on-demand"); + strvec_push(argv, "--recurse-submodules=on-demand"); if (tags == TAGS_SET) - argv_array_push(argv, "--tags"); + strvec_push(argv, "--tags"); else if (tags == TAGS_UNSET) - argv_array_push(argv, "--no-tags"); + strvec_push(argv, "--no-tags"); if (verbosity >= 2) - argv_array_push(argv, "-v"); + strvec_push(argv, "-v"); if (verbosity >= 1) - argv_array_push(argv, "-v"); + strvec_push(argv, "-v"); else if (verbosity < 0) - argv_array_push(argv, "-q"); - + strvec_push(argv, "-q"); + if (family == TRANSPORT_FAMILY_IPV4) + strvec_push(argv, "--ipv4"); + else if (family == TRANSPORT_FAMILY_IPV6) + strvec_push(argv, "--ipv6"); } /* Fetch multiple remotes in parallel */ @@ -1551,8 +1775,8 @@ remote = state->remotes->items[state->next++].string; *task_cb = remote; - argv_array_pushv(&cp->args, state->argv); - argv_array_push(&cp->args, remote); + strvec_pushv(&cp->args, state->argv); + strvec_push(&cp->args, remote); cp->git_cmd = 1; if (verbosity >= 0) @@ -1589,22 +1813,22 @@ static int fetch_multiple(struct string_list *list, int max_children) { int i, result = 0; - struct argv_array argv = ARGV_ARRAY_INIT; + struct strvec argv = STRVEC_INIT; - if (!append && !dry_run) { + if (!append && write_fetch_head) { int errcode = truncate_fetch_head(); if (errcode) return errcode; } - argv_array_pushl(&argv, "fetch", "--append", "--no-auto-gc", - "--no-write-commit-graph", NULL); + strvec_pushl(&argv, "fetch", "--append", "--no-auto-gc", + "--no-write-commit-graph", NULL); add_options_to_argv(&argv); if (max_children != 1 && list->nr != 1) { - struct parallel_fetch_state state = { argv.argv, list, 0, 0 }; + struct parallel_fetch_state state = { argv.v, list, 0, 0 }; - argv_array_push(&argv, "--end-of-options"); + strvec_push(&argv, "--end-of-options"); result = run_processes_parallel_tr2(max_children, &fetch_next_remote, &fetch_failed_to_start, @@ -1617,17 +1841,17 @@ } else for (i = 0; i < list->nr; i++) { const char *name = list->items[i].string; - argv_array_push(&argv, name); + strvec_push(&argv, name); if (verbosity >= 0) printf(_("Fetching %s\n"), name); - if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { + if (run_command_v_opt(argv.v, RUN_GIT_CMD)) { error(_("Could not fetch %s"), name); result = 1; } - argv_array_pop(&argv); + strvec_pop(&argv); } - argv_array_clear(&argv); + strvec_clear(&argv); return !!result; } @@ -1655,7 +1879,7 @@ * If this is a partial-fetch request, we enable partial on * this repo if not already enabled and remember the given * filter-spec as the default for subsequent fetches to this - * remote. + * remote if there is currently no default filter-spec. */ if (filter_options.choice) { partial_clone_register(remote->name, &filter_options); @@ -1672,7 +1896,8 @@ return; } -static int fetch_one(struct remote *remote, int argc, const char **argv, int prune_tags_ok) +static int fetch_one(struct remote *remote, int argc, const char **argv, + int prune_tags_ok, int use_stdin_refspecs) { struct refspec rs = REFSPEC_INIT_FETCH; int i; @@ -1715,20 +1940,24 @@ for (i = 0; i < argc; i++) { if (!strcmp(argv[i], "tag")) { - char *tag; i++; if (i >= argc) die(_("You need to specify a tag name.")); - tag = xstrfmt("refs/tags/%s:refs/tags/%s", - argv[i], argv[i]); - refspec_append(&rs, tag); - free(tag); + refspec_appendf(&rs, "refs/tags/%s:refs/tags/%s", + argv[i], argv[i]); } else { refspec_append(&rs, argv[i]); } } + if (use_stdin_refspecs) { + struct strbuf line = STRBUF_INIT; + while (strbuf_getline_lf(&line, stdin) != EOF) + refspec_append(&rs, line.buf); + strbuf_release(&line); + } + if (server_options.nr) gtransport->server_options = &server_options; @@ -1750,21 +1979,34 @@ struct remote *remote = NULL; int result = 0; int prune_tags_ok = 1; - struct argv_array argv_gc_auto = ARGV_ARRAY_INIT; packet_trace_identity("fetch"); /* Record the command line for the reflog */ strbuf_addstr(&default_rla, "fetch"); - for (i = 1; i < argc; i++) - strbuf_addf(&default_rla, " %s", argv[i]); + for (i = 1; i < argc; i++) { + /* This handles non-URLs gracefully */ + char *anon = transport_anonymize_url(argv[i]); + + strbuf_addf(&default_rla, " %s", anon); + free(anon); + } - fetch_config_from_gitmodules(&submodule_fetch_jobs_config, - &recurse_submodules); git_config(git_fetch_config, NULL); argc = parse_options(argc, argv, prefix, builtin_fetch_options, builtin_fetch_usage, 0); + if (recurse_submodules != RECURSE_SUBMODULES_OFF) { + int *sfjc = submodule_fetch_jobs_config == -1 + ? &submodule_fetch_jobs_config : NULL; + int *rs = recurse_submodules == RECURSE_SUBMODULES_DEFAULT + ? &recurse_submodules : NULL; + + fetch_config_from_gitmodules(sfjc, rs); + } + + if (negotiate_only && !negotiation_tip.nr) + die(_("--negotiate-only needs one or more --negotiate-tip=*")); if (deepen_relative) { if (deepen_relative < 0) @@ -1788,8 +2030,9 @@ if (depth || deepen_since || deepen_not.nr) deepen = 1; - if (filter_options.choice && !has_promisor_remote()) - die("--filter can only be used when extensions.partialClone is set"); + /* FETCH_HEAD never gets updated in --dry-run mode */ + if (dry_run) + write_fetch_head = 0; if (all) { if (argc == 1) @@ -1821,10 +2064,32 @@ } } - if (remote) { + if (negotiate_only) { + struct oidset acked_commits = OIDSET_INIT; + struct oidset_iter iter; + const struct object_id *oid; + + if (!remote) + die(_("must supply remote when using --negotiate-only")); + gtransport = prepare_transport(remote, 1); + if (gtransport->smart_options) { + gtransport->smart_options->acked_commits = &acked_commits; + } else { + warning(_("Protocol does not support --negotiate-only, exiting.")); + return 1; + } + if (server_options.nr) + gtransport->server_options = &server_options; + result = transport_fetch_refs(gtransport, NULL); + + oidset_iter_init(&acked_commits, &iter); + while ((oid = oidset_iter_next(&iter))) + printf("%s\n", oid_to_hex(oid)); + oidset_clear(&acked_commits); + } else if (remote) { if (filter_options.choice || has_promisor_remote()) fetch_one_setup_partial(remote); - result = fetch_one(remote, argc, argv, prune_tags_ok); + result = fetch_one(remote, argc, argv, prune_tags_ok, stdin_refspecs); } else { int max_children = max_jobs; @@ -1832,6 +2097,14 @@ die(_("--filter can only be used with the remote " "configured in extensions.partialclone")); + if (atomic_fetch) + die(_("--atomic can only be used when fetching " + "from one remote")); + + if (stdin_refspecs) + die(_("--stdin can only be used when fetching " + "from one remote")); + if (max_children < 0) max_children = fetch_parallel_config; @@ -1840,7 +2113,7 @@ } if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) { - struct argv_array options = ARGV_ARRAY_INIT; + struct strvec options = STRVEC_INIT; int max_children = max_jobs; if (max_children < 0) @@ -1856,7 +2129,7 @@ recurse_submodules_default, verbosity < 0, max_children); - argv_array_clear(&options); + strvec_clear(&options); } string_list_clear(&list, 0); @@ -1870,20 +2143,13 @@ if (progress) commit_graph_flags |= COMMIT_GRAPH_WRITE_PROGRESS; - write_commit_graph_reachable(get_object_directory(), + write_commit_graph_reachable(the_repository->objects->odb, commit_graph_flags, NULL); } - close_object_store(the_repository->objects); - - if (enable_auto_gc) { - argv_array_pushl(&argv_gc_auto, "gc", "--auto", NULL); - if (verbosity < 0) - argv_array_push(&argv_gc_auto, "--quiet"); - run_command_v_opt(argv_gc_auto.argv, RUN_GIT_CMD); - argv_array_clear(&argv_gc_auto); - } + if (enable_auto_gc) + run_auto_maintenance(verbosity < 0); return result; } diff -Nru git-2.25.1/builtin/fetch-pack.c git-2.34.1/builtin/fetch-pack.c --- git-2.25.1/builtin/fetch-pack.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/fetch-pack.c 2021-11-24 19:29:21.000000000 +0000 @@ -3,7 +3,7 @@ #include "fetch-pack.h" #include "remote.h" #include "connect.h" -#include "sha1-array.h" +#include "oid-array.h" #include "protocol.h" static const char fetch_pack_usage[] = @@ -48,8 +48,8 @@ struct ref **sought = NULL; int nr_sought = 0, alloc_sought = 0; int fd[2]; - char *pack_lockfile = NULL; - char **pack_lockfile_ptr = NULL; + struct string_list pack_lockfiles = STRING_LIST_INIT_DUP; + struct string_list *pack_lockfiles_ptr = NULL; struct child_process *conn; struct fetch_pack_args args; struct oid_array shallow = OID_ARRAY_INIT; @@ -134,7 +134,7 @@ } if (!strcmp("--lock-pack", arg)) { args.lock_pack = 1; - pack_lockfile_ptr = &pack_lockfile; + pack_lockfiles_ptr = &pack_lockfiles; continue; } if (!strcmp("--check-self-contained-and-connected", arg)) { @@ -153,10 +153,6 @@ args.from_promisor = 1; continue; } - if (!strcmp("--no-dependents", arg)) { - args.no_dependents = 1; - continue; - } if (skip_prefix(arg, ("--" CL_ARG__FILTER "="), &arg)) { parse_list_objects_filter(&args.filter_options, arg); continue; @@ -224,7 +220,8 @@ version = discover_version(&reader); switch (version) { case protocol_v2: - get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL); + get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, + args.stateless_rpc); break; case protocol_v1: case protocol_v0: @@ -235,10 +232,15 @@ } ref = fetch_pack(&args, fd, ref, sought, nr_sought, - &shallow, pack_lockfile_ptr, version); - if (pack_lockfile) { - printf("lock %s\n", pack_lockfile); + &shallow, pack_lockfiles_ptr, version); + if (pack_lockfiles.nr) { + int i; + + printf("lock %s\n", pack_lockfiles.items[0].string); fflush(stdout); + for (i = 1; i < pack_lockfiles.nr; i++) + warning(_("Lockfile created but not reported: %s"), + pack_lockfiles.items[i].string); } if (args.check_self_contained_and_connected && args.self_contained_and_connected) { diff -Nru git-2.25.1/builtin/fmt-merge-msg.c git-2.34.1/builtin/fmt-merge-msg.c --- git-2.25.1/builtin/fmt-merge-msg.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/fmt-merge-msg.c 2021-11-24 19:29:21.000000000 +0000 @@ -1,669 +1,13 @@ #include "builtin.h" -#include "cache.h" #include "config.h" -#include "refs.h" -#include "object-store.h" -#include "commit.h" -#include "diff.h" -#include "revision.h" -#include "tag.h" -#include "string-list.h" -#include "branch.h" #include "fmt-merge-msg.h" -#include "gpg-interface.h" -#include "repository.h" -#include "commit-reach.h" +#include "parse-options.h" static const char * const fmt_merge_msg_usage[] = { N_("git fmt-merge-msg [-m ] [--log[=] | --no-log] [--file ]"), NULL }; -static int use_branch_desc; - -int fmt_merge_msg_config(const char *key, const char *value, void *cb) -{ - if (!strcmp(key, "merge.log") || !strcmp(key, "merge.summary")) { - int is_bool; - merge_log_config = git_config_bool_or_int(key, value, &is_bool); - if (!is_bool && merge_log_config < 0) - return error("%s: negative length %s", key, value); - if (is_bool && merge_log_config) - merge_log_config = DEFAULT_MERGE_LOG_LEN; - } else if (!strcmp(key, "merge.branchdesc")) { - use_branch_desc = git_config_bool(key, value); - } else { - return git_default_config(key, value, cb); - } - return 0; -} - -/* merge data per repository where the merged tips came from */ -struct src_data { - struct string_list branch, tag, r_branch, generic; - int head_status; -}; - -struct origin_data { - struct object_id oid; - unsigned is_local_branch:1; -}; - -static void init_src_data(struct src_data *data) -{ - data->branch.strdup_strings = 1; - data->tag.strdup_strings = 1; - data->r_branch.strdup_strings = 1; - data->generic.strdup_strings = 1; -} - -static struct string_list srcs = STRING_LIST_INIT_DUP; -static struct string_list origins = STRING_LIST_INIT_DUP; - -struct merge_parents { - int alloc, nr; - struct merge_parent { - struct object_id given; - struct object_id commit; - unsigned char used; - } *item; -}; - -/* - * I know, I know, this is inefficient, but you won't be pulling and merging - * hundreds of heads at a time anyway. - */ -static struct merge_parent *find_merge_parent(struct merge_parents *table, - struct object_id *given, - struct object_id *commit) -{ - int i; - for (i = 0; i < table->nr; i++) { - if (given && !oideq(&table->item[i].given, given)) - continue; - if (commit && !oideq(&table->item[i].commit, commit)) - continue; - return &table->item[i]; - } - return NULL; -} - -static void add_merge_parent(struct merge_parents *table, - struct object_id *given, - struct object_id *commit) -{ - if (table->nr && find_merge_parent(table, given, commit)) - return; - ALLOC_GROW(table->item, table->nr + 1, table->alloc); - oidcpy(&table->item[table->nr].given, given); - oidcpy(&table->item[table->nr].commit, commit); - table->item[table->nr].used = 0; - table->nr++; -} - -static int handle_line(char *line, struct merge_parents *merge_parents) -{ - int i, len = strlen(line); - struct origin_data *origin_data; - char *src; - const char *origin, *tag_name; - struct src_data *src_data; - struct string_list_item *item; - int pulling_head = 0; - struct object_id oid; - const unsigned hexsz = the_hash_algo->hexsz; - - if (len < hexsz + 3 || line[hexsz] != '\t') - return 1; - - if (starts_with(line + hexsz + 1, "not-for-merge")) - return 0; - - if (line[hexsz + 1] != '\t') - return 2; - - i = get_oid_hex(line, &oid); - if (i) - return 3; - - if (!find_merge_parent(merge_parents, &oid, NULL)) - return 0; /* subsumed by other parents */ - - origin_data = xcalloc(1, sizeof(struct origin_data)); - oidcpy(&origin_data->oid, &oid); - - if (line[len - 1] == '\n') - line[len - 1] = 0; - line += hexsz + 2; - - /* - * At this point, line points at the beginning of comment e.g. - * "branch 'frotz' of git://that/repository.git". - * Find the repository name and point it with src. - */ - src = strstr(line, " of "); - if (src) { - *src = 0; - src += 4; - pulling_head = 0; - } else { - src = line; - pulling_head = 1; - } - - item = unsorted_string_list_lookup(&srcs, src); - if (!item) { - item = string_list_append(&srcs, src); - item->util = xcalloc(1, sizeof(struct src_data)); - init_src_data(item->util); - } - src_data = item->util; - - if (pulling_head) { - origin = src; - src_data->head_status |= 1; - } else if (skip_prefix(line, "branch ", &origin)) { - origin_data->is_local_branch = 1; - string_list_append(&src_data->branch, origin); - src_data->head_status |= 2; - } else if (skip_prefix(line, "tag ", &tag_name)) { - origin = line; - string_list_append(&src_data->tag, tag_name); - src_data->head_status |= 2; - } else if (skip_prefix(line, "remote-tracking branch ", &origin)) { - string_list_append(&src_data->r_branch, origin); - src_data->head_status |= 2; - } else { - origin = src; - string_list_append(&src_data->generic, line); - src_data->head_status |= 2; - } - - if (!strcmp(".", src) || !strcmp(src, origin)) { - int len = strlen(origin); - if (origin[0] == '\'' && origin[len - 1] == '\'') - origin = xmemdupz(origin + 1, len - 2); - } else - origin = xstrfmt("%s of %s", origin, src); - if (strcmp(".", src)) - origin_data->is_local_branch = 0; - string_list_append(&origins, origin)->util = origin_data; - return 0; -} - -static void print_joined(const char *singular, const char *plural, - struct string_list *list, struct strbuf *out) -{ - if (list->nr == 0) - return; - if (list->nr == 1) { - strbuf_addf(out, "%s%s", singular, list->items[0].string); - } else { - int i; - strbuf_addstr(out, plural); - for (i = 0; i < list->nr - 1; i++) - strbuf_addf(out, "%s%s", i > 0 ? ", " : "", - list->items[i].string); - strbuf_addf(out, " and %s", list->items[list->nr - 1].string); - } -} - -static void add_branch_desc(struct strbuf *out, const char *name) -{ - struct strbuf desc = STRBUF_INIT; - - if (!read_branch_desc(&desc, name)) { - const char *bp = desc.buf; - while (*bp) { - const char *ep = strchrnul(bp, '\n'); - if (*ep) - ep++; - strbuf_addf(out, " : %.*s", (int)(ep - bp), bp); - bp = ep; - } - strbuf_complete_line(out); - } - strbuf_release(&desc); -} - -#define util_as_integral(elem) ((intptr_t)((elem)->util)) - -static void record_person_from_buf(int which, struct string_list *people, - const char *buffer) -{ - char *name_buf, *name, *name_end; - struct string_list_item *elem; - const char *field; - - field = (which == 'a') ? "\nauthor " : "\ncommitter "; - name = strstr(buffer, field); - if (!name) - return; - name += strlen(field); - name_end = strchrnul(name, '<'); - if (*name_end) - name_end--; - while (isspace(*name_end) && name <= name_end) - name_end--; - if (name_end < name) - return; - name_buf = xmemdupz(name, name_end - name + 1); - - elem = string_list_lookup(people, name_buf); - if (!elem) { - elem = string_list_insert(people, name_buf); - elem->util = (void *)0; - } - elem->util = (void*)(util_as_integral(elem) + 1); - free(name_buf); -} - - -static void record_person(int which, struct string_list *people, - struct commit *commit) -{ - const char *buffer = get_commit_buffer(commit, NULL); - record_person_from_buf(which, people, buffer); - unuse_commit_buffer(commit, buffer); -} - -static int cmp_string_list_util_as_integral(const void *a_, const void *b_) -{ - const struct string_list_item *a = a_, *b = b_; - return util_as_integral(b) - util_as_integral(a); -} - -static void add_people_count(struct strbuf *out, struct string_list *people) -{ - if (people->nr == 1) - strbuf_addstr(out, people->items[0].string); - else if (people->nr == 2) - strbuf_addf(out, "%s (%d) and %s (%d)", - people->items[0].string, - (int)util_as_integral(&people->items[0]), - people->items[1].string, - (int)util_as_integral(&people->items[1])); - else if (people->nr) - strbuf_addf(out, "%s (%d) and others", - people->items[0].string, - (int)util_as_integral(&people->items[0])); -} - -static void credit_people(struct strbuf *out, - struct string_list *them, - int kind) -{ - const char *label; - const char *me; - - if (kind == 'a') { - label = "By"; - me = git_author_info(IDENT_NO_DATE); - } else { - label = "Via"; - me = git_committer_info(IDENT_NO_DATE); - } - - if (!them->nr || - (them->nr == 1 && - me && - skip_prefix(me, them->items->string, &me) && - starts_with(me, " <"))) - return; - strbuf_addf(out, "\n%c %s ", comment_line_char, label); - add_people_count(out, them); -} - -static void add_people_info(struct strbuf *out, - struct string_list *authors, - struct string_list *committers) -{ - QSORT(authors->items, authors->nr, - cmp_string_list_util_as_integral); - QSORT(committers->items, committers->nr, - cmp_string_list_util_as_integral); - - credit_people(out, authors, 'a'); - credit_people(out, committers, 'c'); -} - -static void shortlog(const char *name, - struct origin_data *origin_data, - struct commit *head, - struct rev_info *rev, - struct fmt_merge_msg_opts *opts, - struct strbuf *out) -{ - int i, count = 0; - struct commit *commit; - struct object *branch; - struct string_list subjects = STRING_LIST_INIT_DUP; - struct string_list authors = STRING_LIST_INIT_DUP; - struct string_list committers = STRING_LIST_INIT_DUP; - int flags = UNINTERESTING | TREESAME | SEEN | SHOWN | ADDED; - struct strbuf sb = STRBUF_INIT; - const struct object_id *oid = &origin_data->oid; - int limit = opts->shortlog_len; - - branch = deref_tag(the_repository, parse_object(the_repository, oid), - oid_to_hex(oid), - the_hash_algo->hexsz); - if (!branch || branch->type != OBJ_COMMIT) - return; - - setup_revisions(0, NULL, rev, NULL); - add_pending_object(rev, branch, name); - add_pending_object(rev, &head->object, "^HEAD"); - head->object.flags |= UNINTERESTING; - if (prepare_revision_walk(rev)) - die("revision walk setup failed"); - while ((commit = get_revision(rev)) != NULL) { - struct pretty_print_context ctx = {0}; - - if (commit->parents && commit->parents->next) { - /* do not list a merge but count committer */ - if (opts->credit_people) - record_person('c', &committers, commit); - continue; - } - if (!count && opts->credit_people) - /* the 'tip' committer */ - record_person('c', &committers, commit); - if (opts->credit_people) - record_person('a', &authors, commit); - count++; - if (subjects.nr > limit) - continue; - - format_commit_message(commit, "%s", &sb, &ctx); - strbuf_ltrim(&sb); - - if (!sb.len) - string_list_append(&subjects, - oid_to_hex(&commit->object.oid)); - else - string_list_append_nodup(&subjects, - strbuf_detach(&sb, NULL)); - } - - if (opts->credit_people) - add_people_info(out, &authors, &committers); - if (count > limit) - strbuf_addf(out, "\n* %s: (%d commits)\n", name, count); - else - strbuf_addf(out, "\n* %s:\n", name); - - if (origin_data->is_local_branch && use_branch_desc) - add_branch_desc(out, name); - - for (i = 0; i < subjects.nr; i++) - if (i >= limit) - strbuf_addstr(out, " ...\n"); - else - strbuf_addf(out, " %s\n", subjects.items[i].string); - - clear_commit_marks((struct commit *)branch, flags); - clear_commit_marks(head, flags); - free_commit_list(rev->commits); - rev->commits = NULL; - rev->pending.nr = 0; - - string_list_clear(&authors, 0); - string_list_clear(&committers, 0); - string_list_clear(&subjects, 0); -} - -static void fmt_merge_msg_title(struct strbuf *out, - const char *current_branch) -{ - int i = 0; - char *sep = ""; - - strbuf_addstr(out, "Merge "); - for (i = 0; i < srcs.nr; i++) { - struct src_data *src_data = srcs.items[i].util; - const char *subsep = ""; - - strbuf_addstr(out, sep); - sep = "; "; - - if (src_data->head_status == 1) { - strbuf_addstr(out, srcs.items[i].string); - continue; - } - if (src_data->head_status == 3) { - subsep = ", "; - strbuf_addstr(out, "HEAD"); - } - if (src_data->branch.nr) { - strbuf_addstr(out, subsep); - subsep = ", "; - print_joined("branch ", "branches ", &src_data->branch, - out); - } - if (src_data->r_branch.nr) { - strbuf_addstr(out, subsep); - subsep = ", "; - print_joined("remote-tracking branch ", "remote-tracking branches ", - &src_data->r_branch, out); - } - if (src_data->tag.nr) { - strbuf_addstr(out, subsep); - subsep = ", "; - print_joined("tag ", "tags ", &src_data->tag, out); - } - if (src_data->generic.nr) { - strbuf_addstr(out, subsep); - print_joined("commit ", "commits ", &src_data->generic, - out); - } - if (strcmp(".", srcs.items[i].string)) - strbuf_addf(out, " of %s", srcs.items[i].string); - } - - if (!strcmp("master", current_branch)) - strbuf_addch(out, '\n'); - else - strbuf_addf(out, " into %s\n", current_branch); -} - -static void fmt_tag_signature(struct strbuf *tagbuf, - struct strbuf *sig, - const char *buf, - unsigned long len) -{ - const char *tag_body = strstr(buf, "\n\n"); - if (tag_body) { - tag_body += 2; - strbuf_add(tagbuf, tag_body, buf + len - tag_body); - } - strbuf_complete_line(tagbuf); - if (sig->len) { - strbuf_addch(tagbuf, '\n'); - strbuf_add_commented_lines(tagbuf, sig->buf, sig->len); - } -} - -static void fmt_merge_msg_sigs(struct strbuf *out) -{ - int i, tag_number = 0, first_tag = 0; - struct strbuf tagbuf = STRBUF_INIT; - - for (i = 0; i < origins.nr; i++) { - struct object_id *oid = origins.items[i].util; - enum object_type type; - unsigned long size, len; - char *buf = read_object_file(oid, &type, &size); - struct signature_check sigc = { 0 }; - struct strbuf sig = STRBUF_INIT; - - if (!buf || type != OBJ_TAG) - goto next; - len = parse_signature(buf, size); - - if (size == len) - ; /* merely annotated */ - else if (!check_signature(buf, len, buf + len, size - len, - &sigc)) { - strbuf_addstr(&sig, sigc.gpg_output); - signature_check_clear(&sigc); - } else - strbuf_addstr(&sig, "gpg verification failed.\n"); - - if (!tag_number++) { - fmt_tag_signature(&tagbuf, &sig, buf, len); - first_tag = i; - } else { - if (tag_number == 2) { - struct strbuf tagline = STRBUF_INIT; - strbuf_addch(&tagline, '\n'); - strbuf_add_commented_lines(&tagline, - origins.items[first_tag].string, - strlen(origins.items[first_tag].string)); - strbuf_insert(&tagbuf, 0, tagline.buf, - tagline.len); - strbuf_release(&tagline); - } - strbuf_addch(&tagbuf, '\n'); - strbuf_add_commented_lines(&tagbuf, - origins.items[i].string, - strlen(origins.items[i].string)); - fmt_tag_signature(&tagbuf, &sig, buf, len); - } - strbuf_release(&sig); - next: - free(buf); - } - if (tagbuf.len) { - strbuf_addch(out, '\n'); - strbuf_addbuf(out, &tagbuf); - } - strbuf_release(&tagbuf); -} - -static void find_merge_parents(struct merge_parents *result, - struct strbuf *in, struct object_id *head) -{ - struct commit_list *parents; - struct commit *head_commit; - int pos = 0, i, j; - - parents = NULL; - while (pos < in->len) { - int len; - char *p = in->buf + pos; - char *newline = strchr(p, '\n'); - const char *q; - struct object_id oid; - struct commit *parent; - struct object *obj; - - len = newline ? newline - p : strlen(p); - pos += len + !!newline; - - if (parse_oid_hex(p, &oid, &q) || - q[0] != '\t' || - q[1] != '\t') - continue; /* skip not-for-merge */ - /* - * Do not use get_merge_parent() here; we do not have - * "name" here and we do not want to contaminate its - * util field yet. - */ - obj = parse_object(the_repository, &oid); - parent = (struct commit *)peel_to_type(NULL, 0, obj, OBJ_COMMIT); - if (!parent) - continue; - commit_list_insert(parent, &parents); - add_merge_parent(result, &obj->oid, &parent->object.oid); - } - head_commit = lookup_commit(the_repository, head); - if (head_commit) - commit_list_insert(head_commit, &parents); - reduce_heads_replace(&parents); - - while (parents) { - struct commit *cmit = pop_commit(&parents); - for (i = 0; i < result->nr; i++) - if (oideq(&result->item[i].commit, &cmit->object.oid)) - result->item[i].used = 1; - } - - for (i = j = 0; i < result->nr; i++) { - if (result->item[i].used) { - if (i != j) - result->item[j] = result->item[i]; - j++; - } - } - result->nr = j; -} - -int fmt_merge_msg(struct strbuf *in, struct strbuf *out, - struct fmt_merge_msg_opts *opts) -{ - int i = 0, pos = 0; - struct object_id head_oid; - const char *current_branch; - void *current_branch_to_free; - struct merge_parents merge_parents; - - memset(&merge_parents, 0, sizeof(merge_parents)); - - /* get current branch */ - current_branch = current_branch_to_free = - resolve_refdup("HEAD", RESOLVE_REF_READING, &head_oid, NULL); - if (!current_branch) - die("No current branch"); - if (starts_with(current_branch, "refs/heads/")) - current_branch += 11; - - find_merge_parents(&merge_parents, in, &head_oid); - - /* get a line */ - while (pos < in->len) { - int len; - char *newline, *p = in->buf + pos; - - newline = strchr(p, '\n'); - len = newline ? newline - p : strlen(p); - pos += len + !!newline; - i++; - p[len] = 0; - if (handle_line(p, &merge_parents)) - die("error in line %d: %.*s", i, len, p); - } - - if (opts->add_title && srcs.nr) - fmt_merge_msg_title(out, current_branch); - - if (origins.nr) - fmt_merge_msg_sigs(out); - - if (opts->shortlog_len) { - struct commit *head; - struct rev_info rev; - - head = lookup_commit_or_die(&head_oid, "HEAD"); - repo_init_revisions(the_repository, &rev, NULL); - rev.commit_format = CMIT_FMT_ONELINE; - rev.ignore_merges = 1; - rev.limited = 1; - - strbuf_complete_line(out); - - for (i = 0; i < origins.nr; i++) - shortlog(origins.items[i].string, - origins.items[i].util, - head, &rev, opts, out); - } - - strbuf_complete_line(out); - free(current_branch_to_free); - free(merge_parents.item); - return 0; -} - int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) { const char *inpath = NULL; diff -Nru git-2.25.1/builtin/for-each-ref.c git-2.34.1/builtin/for-each-ref.c --- git-2.25.1/builtin/for-each-ref.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/for-each-ref.c 2021-11-24 19:29:21.000000000 +0000 @@ -9,7 +9,7 @@ static char const * const for_each_ref_usage[] = { N_("git for-each-ref [] []"), N_("git for-each-ref [--points-at ]"), - N_("git for-each-ref [(--merged | --no-merged) []]"), + N_("git for-each-ref [--merged []] [--no-merged []]"), N_("git for-each-ref [--contains []] [--no-contains []]"), NULL }; @@ -22,6 +22,8 @@ struct ref_array array; struct ref_filter filter; struct ref_format format = REF_FORMAT_INIT; + struct strbuf output = STRBUF_INIT; + struct strbuf err = STRBUF_INIT; struct option opts[] = { OPT_BIT('s', "shell", &format.quote_style, @@ -70,18 +72,30 @@ if (!sorting) sorting = ref_default_sorting(); - sorting->ignore_case = icase; + ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase); filter.ignore_case = icase; filter.name_patterns = argv; filter.match_as_path = 1; - filter_refs(&array, &filter, FILTER_REFS_ALL | FILTER_REFS_INCLUDE_BROKEN); + filter_refs(&array, &filter, FILTER_REFS_ALL); ref_array_sort(sorting, &array); if (!maxcount || array.nr < maxcount) maxcount = array.nr; - for (i = 0; i < maxcount; i++) - show_ref_array_item(array.items[i], &format); + for (i = 0; i < maxcount; i++) { + strbuf_reset(&err); + strbuf_reset(&output); + if (format_ref_array_item(array.items[i], &format, &output, &err)) + die("%s", err.buf); + fwrite(output.buf, 1, output.len, stdout); + putchar('\n'); + } + + strbuf_release(&err); + strbuf_release(&output); ref_array_clear(&array); + free_commit_list(filter.with_commit); + free_commit_list(filter.no_commit); + ref_sorting_release(sorting); return 0; } diff -Nru git-2.25.1/builtin/for-each-repo.c git-2.34.1/builtin/for-each-repo.c --- git-2.25.1/builtin/for-each-repo.c 1970-01-01 00:00:00.000000000 +0000 +++ git-2.34.1/builtin/for-each-repo.c 2021-11-24 19:29:21.000000000 +0000 @@ -0,0 +1,59 @@ +#include "cache.h" +#include "config.h" +#include "builtin.h" +#include "parse-options.h" +#include "run-command.h" +#include "string-list.h" + +static const char * const for_each_repo_usage[] = { + N_("git for-each-repo --config= "), + NULL +}; + +static int run_command_on_repo(const char *path, int argc, const char ** argv) +{ + int i; + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = 1; + strvec_pushl(&child.args, "-C", path, NULL); + + for (i = 0; i < argc; i++) + strvec_push(&child.args, argv[i]); + + return run_command(&child); +} + +int cmd_for_each_repo(int argc, const char **argv, const char *prefix) +{ + static const char *config_key = NULL; + int i, result = 0; + const struct string_list *values; + + const struct option options[] = { + OPT_STRING(0, "config", &config_key, N_("config"), + N_("config key storing a list of repository paths")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, for_each_repo_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (!config_key) + die(_("missing --config=")); + + values = repo_config_get_value_multi(the_repository, + config_key); + + /* + * Do nothing on an empty list, which is equivalent to the case + * where the config variable does not exist at all. + */ + if (!values) + return 0; + + for (i = 0; !result && i < values->nr; i++) + result = run_command_on_repo(values->items[i].string, argc, argv); + + return result; +} diff -Nru git-2.25.1/builtin/fsck.c git-2.34.1/builtin/fsck.c --- git-2.25.1/builtin/fsck.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/fsck.c 2021-11-24 19:29:21.000000000 +0000 @@ -49,6 +49,7 @@ #define ERROR_PACK 04 #define ERROR_REFS 010 #define ERROR_COMMIT_GRAPH 020 +#define ERROR_MULTI_PACK_INDEX 040 static const char *describe_object(const struct object_id *oid) { @@ -70,29 +71,6 @@ return ret; } -static int fsck_config(const char *var, const char *value, void *cb) -{ - if (strcmp(var, "fsck.skiplist") == 0) { - const char *path; - struct strbuf sb = STRBUF_INIT; - - if (git_config_pathname(&path, var, value)) - return 1; - strbuf_addf(&sb, "skiplist=%s", path); - free((char *)path); - fsck_set_msg_types(&fsck_obj_options, sb.buf); - strbuf_release(&sb); - return 0; - } - - if (skip_prefix(var, "fsck.", &var)) { - fsck_set_msg_type(&fsck_obj_options, var, value); - return 0; - } - - return git_default_config(var, value, cb); -} - static int objerror(struct object *obj, const char *err) { errors_found |= ERROR_OBJECT; @@ -106,7 +84,9 @@ static int fsck_error_func(struct fsck_options *o, const struct object_id *oid, enum object_type object_type, - int msg_type, const char *message) + enum fsck_msg_type msg_type, + enum fsck_msg_id msg_id, + const char *message) { switch (msg_type) { case FSCK_WARN: @@ -129,7 +109,8 @@ static struct object_array pending; -static int mark_object(struct object *obj, int type, void *data, struct fsck_options *options) +static int mark_object(struct object *obj, enum object_type type, + void *data, struct fsck_options *options) { struct object *parent = data; @@ -167,7 +148,7 @@ return 0; if (!(obj->flags & HAS_OBJ)) { - if (parent && !has_object_file(&obj->oid)) { + if (parent && !has_object(the_repository, &obj->oid, 1)) { printf_ln(_("broken link from %7s %s\n" " to %7s %s"), printable_type(&parent->oid, parent->type), @@ -214,7 +195,8 @@ return !!result; } -static int mark_used(struct object *obj, int type, void *data, struct fsck_options *options) +static int mark_used(struct object *obj, enum object_type object_type, + void *data, struct fsck_options *options) { if (!obj) return 1; @@ -240,7 +222,7 @@ enum object_type type = oid_object_info(the_repository, &obj->oid, NULL); if (type > 0) - object_as_type(the_repository, obj, type, 0); + object_as_type(obj, type, 0); } options.walk = mark_used; @@ -576,7 +558,7 @@ for_each_rawref(fsck_handle_ref, NULL); - worktrees = get_worktrees(0); + worktrees = get_worktrees(); for (p = worktrees; *p; p++) { struct worktree *wt = *p; struct strbuf ref = STRBUF_INIT; @@ -611,18 +593,44 @@ } } +struct for_each_loose_cb +{ + struct progress *progress; + struct strbuf obj_type; +}; + static int fsck_loose(const struct object_id *oid, const char *path, void *data) { + struct for_each_loose_cb *cb_data = data; struct object *obj; - enum object_type type; + enum object_type type = OBJ_NONE; unsigned long size; - void *contents; + void *contents = NULL; int eaten; + struct object_info oi = OBJECT_INFO_INIT; + struct object_id real_oid = *null_oid(); + int err = 0; - if (read_loose_object(path, oid, &type, &size, &contents) < 0) { + strbuf_reset(&cb_data->obj_type); + oi.type_name = &cb_data->obj_type; + oi.sizep = &size; + oi.typep = &type; + + if (read_loose_object(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); + else + err = error(_("%s: object corrupt or missing: %s"), + oid_to_hex(oid), path); + } + if (type != OBJ_NONE && type < 0) + err = error(_("%s: object is of unknown type '%s': %s"), + oid_to_hex(&real_oid), cb_data->obj_type.buf, + path); + if (err < 0) { errors_found |= ERROR_OBJECT; - error(_("%s: object corrupt or missing: %s"), - oid_to_hex(oid), path); + free(contents); return 0; /* keep checking other objects */ } @@ -658,8 +666,10 @@ return 0; } -static int fsck_subdir(unsigned int nr, const char *path, void *progress) +static int fsck_subdir(unsigned int nr, const char *path, void *data) { + struct for_each_loose_cb *cb_data = data; + struct progress *progress = cb_data->progress; display_progress(progress, nr + 1); return 0; } @@ -667,6 +677,10 @@ static void fsck_object_dir(const char *path) { struct progress *progress = NULL; + struct for_each_loose_cb cb_data = { + .obj_type = STRBUF_INIT, + .progress = progress, + }; if (verbose) fprintf_ln(stderr, _("Checking object directory")); @@ -675,9 +689,10 @@ progress = start_progress(_("Checking object directories"), 256); for_each_loose_file_in_objdir(path, fsck_loose, fsck_cruft, fsck_subdir, - progress); + &cb_data); display_progress(progress, 256); stop_progress(&progress); + strbuf_release(&cb_data.obj_type); } static int fsck_head_link(const char *head_ref_name, @@ -744,7 +759,7 @@ static void mark_object_for_connectivity(const struct object_id *oid) { - struct object *obj = lookup_unknown_object(oid); + struct object *obj = lookup_unknown_object(the_repository, oid); obj->flags |= HAS_OBJ; } @@ -820,7 +835,8 @@ if (name_objects) fsck_enable_object_names(&fsck_walk_options); - git_config(fsck_config, NULL); + git_config(git_fsck_config, &fsck_obj_options); + prepare_repo_settings(the_repository); if (connectivity_only) { for_each_loose_object(mark_loose_for_connectivity, NULL, 0); @@ -900,6 +916,8 @@ verify_index_checksum = 1; verify_ce_order = 1; read_cache(); + /* TODO: audit for interaction with sparse-index. */ + ensure_full_index(&the_index); for (i = 0; i < active_nr; i++) { unsigned int mode; struct blob *blob; @@ -924,7 +942,7 @@ check_connectivity(); - if (!git_config_get_bool("core.commitgraph", &i) && i) { + if (the_repository->settings.core_commit_graph) { struct child_process commit_graph_verify = CHILD_PROCESS_INIT; const char *verify_argv[] = { "commit-graph", "verify", NULL, NULL, NULL }; @@ -940,7 +958,7 @@ } } - if (!git_config_get_bool("core.multipackindex", &i) && i) { + if (the_repository->settings.core_multi_pack_index) { struct child_process midx_verify = CHILD_PROCESS_INIT; const char *midx_argv[] = { "multi-pack-index", "verify", NULL, NULL, NULL }; @@ -952,7 +970,7 @@ midx_argv[2] = "--object-dir"; midx_argv[3] = odb->path; if (run_command(&midx_verify)) - errors_found |= ERROR_COMMIT_GRAPH; + errors_found |= ERROR_MULTI_PACK_INDEX; } } diff -Nru git-2.25.1/builtin/gc.c git-2.34.1/builtin/gc.c --- git-2.25.1/builtin/gc.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/gc.c 2021-11-24 19:29:21.000000000 +0000 @@ -18,7 +18,7 @@ #include "parse-options.h" #include "run-command.h" #include "sigchain.h" -#include "argv-array.h" +#include "strvec.h" #include "commit.h" #include "commit-graph.h" #include "packfile.h" @@ -28,6 +28,10 @@ #include "blob.h" #include "tree.h" #include "promisor-remote.h" +#include "refs.h" +#include "remote.h" +#include "object-store.h" +#include "exec-cmd.h" #define FAILED_RUN "failed to run %s" @@ -50,12 +54,11 @@ static unsigned long big_pack_threshold; static unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE; -static struct argv_array pack_refs_cmd = ARGV_ARRAY_INIT; -static struct argv_array reflog = ARGV_ARRAY_INIT; -static struct argv_array repack = ARGV_ARRAY_INIT; -static struct argv_array prune = ARGV_ARRAY_INIT; -static struct argv_array prune_worktrees = ARGV_ARRAY_INIT; -static struct argv_array rerere = ARGV_ARRAY_INIT; +static struct strvec reflog = STRVEC_INIT; +static struct strvec repack = STRVEC_INIT; +static struct strvec prune = STRVEC_INIT; +static struct strvec prune_worktrees = STRVEC_INIT; +static struct strvec rerere = STRVEC_INIT; static struct tempfile *pidfile; static struct lock_file log_lock; @@ -88,7 +91,7 @@ */ int saved_errno = errno; fprintf(stderr, _("Failed to fstat %s: %s"), - get_tempfile_path(log_lock.tempfile), + get_lock_file_path(&log_lock), strerror(saved_errno)); fflush(stderr); commit_lock_file(&log_lock); @@ -159,6 +162,15 @@ git_config(git_default_config, NULL); } +struct maintenance_run_opts; +static int maintenance_task_pack_refs(MAYBE_UNUSED struct maintenance_run_opts *opts) +{ + struct strvec pack_refs_cmd = STRVEC_INIT; + strvec_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); + + return run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD); +} + static int too_many_loose_objects(void) { /* @@ -297,7 +309,7 @@ /* and then obj_hash[], underestimated in fact */ heap += sizeof(struct object *) * nr_objects; /* revindex is used also */ - heap += sizeof(struct revindex_entry) * nr_objects; + heap += (sizeof(off_t) + sizeof(uint32_t)) * nr_objects; /* * read_sha1_file() (either at delta calculation phase, or * writing phase) also fills up the delta base cache @@ -311,18 +323,18 @@ static int keep_one_pack(struct string_list_item *item, void *data) { - argv_array_pushf(&repack, "--keep-pack=%s", basename(item->string)); + strvec_pushf(&repack, "--keep-pack=%s", basename(item->string)); return 0; } static void add_repack_all_option(struct string_list *keep_pack) { if (prune_expire && !strcmp(prune_expire, "now")) - argv_array_push(&repack, "-a"); + strvec_push(&repack, "-a"); else { - argv_array_push(&repack, "-A"); + strvec_push(&repack, "-A"); if (prune_expire) - argv_array_pushf(&repack, "--unpack-unreachable=%s", prune_expire); + strvec_pushf(&repack, "--unpack-unreachable=%s", prune_expire); } if (keep_pack) @@ -331,7 +343,7 @@ static void add_repack_incremental_option(void) { - argv_array_push(&repack, "--no-write-bitmap-index"); + strvec_push(&repack, "--no-write-bitmap-index"); } static int need_to_gc(void) @@ -490,7 +502,7 @@ */ warning(_("The last gc run reported the following. " "Please correct the root cause\n" - "and remove %s.\n" + "and remove %s\n" "Automatic cleanup will not be performed " "until the file is removed.\n\n" "%s"), @@ -514,11 +526,11 @@ if (done++) return; - if (pack_refs && run_command_v_opt(pack_refs_cmd.argv, RUN_GIT_CMD)) - die(FAILED_RUN, pack_refs_cmd.argv[0]); + if (pack_refs && maintenance_task_pack_refs(NULL)) + die(FAILED_RUN, "pack-refs"); - if (prune_reflogs && run_command_v_opt(reflog.argv, RUN_GIT_CMD)) - die(FAILED_RUN, reflog.argv[0]); + if (prune_reflogs && run_command_v_opt(reflog.v, RUN_GIT_CMD)) + die(FAILED_RUN, reflog.v[0]); } int cmd_gc(int argc, const char **argv, const char *prefix) @@ -530,7 +542,7 @@ const char *name; pid_t pid; int daemonized = 0; - int keep_base_pack = -1; + int keep_largest_pack = -1; timestamp_t dummy; struct option builtin_gc_options[] = { @@ -544,7 +556,7 @@ OPT_BOOL_F(0, "force", &force, N_("force running gc even if there may be another gc running"), PARSE_OPT_NOCOMPLETE), - OPT_BOOL(0, "keep-largest-pack", &keep_base_pack, + OPT_BOOL(0, "keep-largest-pack", &keep_largest_pack, N_("repack all other packs except the largest pack")), OPT_END() }; @@ -552,12 +564,11 @@ if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_gc_usage, builtin_gc_options); - argv_array_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); - argv_array_pushl(&reflog, "reflog", "expire", "--all", NULL); - argv_array_pushl(&repack, "repack", "-d", "-l", NULL); - argv_array_pushl(&prune, "prune", "--expire", NULL); - argv_array_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL); - argv_array_pushl(&rerere, "rerere", "gc", NULL); + strvec_pushl(&reflog, "reflog", "expire", "--all", NULL); + strvec_pushl(&repack, "repack", "-d", "-l", NULL); + strvec_pushl(&prune, "prune", "--expire", NULL); + strvec_pushl(&prune_worktrees, "worktree", "prune", "--expire", NULL); + strvec_pushl(&rerere, "rerere", "gc", NULL); /* default expiry time, overwritten in gc_config */ gc_config(); @@ -576,14 +587,14 @@ die(_("failed to parse prune expiry value %s"), prune_expire); if (aggressive) { - argv_array_push(&repack, "-f"); + strvec_push(&repack, "-f"); if (aggressive_depth > 0) - argv_array_pushf(&repack, "--depth=%d", aggressive_depth); + strvec_pushf(&repack, "--depth=%d", aggressive_depth); if (aggressive_window > 0) - argv_array_pushf(&repack, "--window=%d", aggressive_window); + strvec_pushf(&repack, "--window=%d", aggressive_window); } if (quiet) - argv_array_push(&repack, "-q"); + strvec_push(&repack, "-q"); if (auto_gc) { /* @@ -621,8 +632,8 @@ } else { struct string_list keep_pack = STRING_LIST_INIT_NODUP; - if (keep_base_pack != -1) { - if (keep_base_pack) + if (keep_largest_pack != -1) { + if (keep_largest_pack) find_base_packs(&keep_pack, 0); } else if (big_pack_threshold) { find_base_packs(&keep_pack, big_pack_threshold); @@ -652,30 +663,30 @@ gc_before_repack(); if (!repository_format_precious_objects) { - close_object_store(the_repository->objects); - if (run_command_v_opt(repack.argv, RUN_GIT_CMD)) - die(FAILED_RUN, repack.argv[0]); + if (run_command_v_opt(repack.v, + RUN_GIT_CMD | RUN_CLOSE_OBJECT_STORE)) + die(FAILED_RUN, repack.v[0]); if (prune_expire) { - argv_array_push(&prune, prune_expire); + strvec_push(&prune, prune_expire); if (quiet) - argv_array_push(&prune, "--no-progress"); + strvec_push(&prune, "--no-progress"); if (has_promisor_remote()) - argv_array_push(&prune, - "--exclude-promisor-objects"); - if (run_command_v_opt(prune.argv, RUN_GIT_CMD)) - die(FAILED_RUN, prune.argv[0]); + strvec_push(&prune, + "--exclude-promisor-objects"); + if (run_command_v_opt(prune.v, RUN_GIT_CMD)) + die(FAILED_RUN, prune.v[0]); } } if (prune_worktrees_expire) { - argv_array_push(&prune_worktrees, prune_worktrees_expire); - if (run_command_v_opt(prune_worktrees.argv, RUN_GIT_CMD)) - die(FAILED_RUN, prune_worktrees.argv[0]); + strvec_push(&prune_worktrees, prune_worktrees_expire); + if (run_command_v_opt(prune_worktrees.v, RUN_GIT_CMD)) + die(FAILED_RUN, prune_worktrees.v[0]); } - if (run_command_v_opt(rerere.argv, RUN_GIT_CMD)) - die(FAILED_RUN, rerere.argv[0]); + if (run_command_v_opt(rerere.v, RUN_GIT_CMD)) + die(FAILED_RUN, rerere.v[0]); report_garbage = report_pack_garbage; reprepare_packed_git(the_repository); @@ -686,7 +697,7 @@ prepare_repo_settings(the_repository); if (the_repository->settings.gc_write_commit_graph == 1) - write_commit_graph_reachable(get_object_directory(), + write_commit_graph_reachable(the_repository->objects->odb, !quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0, NULL); @@ -699,3 +710,1813 @@ return 0; } + +static const char *const builtin_maintenance_run_usage[] = { + N_("git maintenance run [--auto] [--[no-]quiet] [--task=] [--schedule]"), + NULL +}; + +enum schedule_priority { + SCHEDULE_NONE = 0, + SCHEDULE_WEEKLY = 1, + SCHEDULE_DAILY = 2, + SCHEDULE_HOURLY = 3, +}; + +static enum schedule_priority parse_schedule(const char *value) +{ + if (!value) + return SCHEDULE_NONE; + if (!strcasecmp(value, "hourly")) + return SCHEDULE_HOURLY; + if (!strcasecmp(value, "daily")) + return SCHEDULE_DAILY; + if (!strcasecmp(value, "weekly")) + return SCHEDULE_WEEKLY; + return SCHEDULE_NONE; +} + +static int maintenance_opt_schedule(const struct option *opt, const char *arg, + int unset) +{ + enum schedule_priority *priority = opt->value; + + if (unset) + die(_("--no-schedule is not allowed")); + + *priority = parse_schedule(arg); + + if (!*priority) + die(_("unrecognized --schedule argument '%s'"), arg); + + return 0; +} + +struct maintenance_run_opts { + int auto_flag; + int quiet; + enum schedule_priority schedule; +}; + +/* Remember to update object flag allocation in object.h */ +#define SEEN (1u<<0) + +struct cg_auto_data { + int num_not_in_graph; + int limit; +}; + +static int dfs_on_ref(const char *refname, + const struct object_id *oid, int flags, + void *cb_data) +{ + struct cg_auto_data *data = (struct cg_auto_data *)cb_data; + int result = 0; + struct object_id peeled; + struct commit_list *stack = NULL; + struct commit *commit; + + if (!peel_iterated_oid(oid, &peeled)) + oid = &peeled; + if (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT) + return 0; + + commit = lookup_commit(the_repository, oid); + if (!commit) + return 0; + if (parse_commit(commit) || + commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH) + return 0; + + data->num_not_in_graph++; + + if (data->num_not_in_graph >= data->limit) + return 1; + + commit_list_append(commit, &stack); + + while (!result && stack) { + struct commit_list *parent; + + commit = pop_commit(&stack); + + for (parent = commit->parents; parent; parent = parent->next) { + if (parse_commit(parent->item) || + commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH || + parent->item->object.flags & SEEN) + continue; + + parent->item->object.flags |= SEEN; + data->num_not_in_graph++; + + if (data->num_not_in_graph >= data->limit) { + result = 1; + break; + } + + commit_list_append(parent->item, &stack); + } + } + + free_commit_list(stack); + return result; +} + +static int should_write_commit_graph(void) +{ + int result; + struct cg_auto_data data; + + data.num_not_in_graph = 0; + data.limit = 100; + git_config_get_int("maintenance.commit-graph.auto", + &data.limit); + + if (!data.limit) + return 0; + if (data.limit < 0) + return 1; + + result = for_each_ref(dfs_on_ref, &data); + + repo_clear_commit_marks(the_repository, SEEN); + + return result; +} + +static int run_write_commit_graph(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = child.close_object_store = 1; + strvec_pushl(&child.args, "commit-graph", "write", + "--split", "--reachable", NULL); + + if (opts->quiet) + strvec_push(&child.args, "--no-progress"); + + return !!run_command(&child); +} + +static int maintenance_task_commit_graph(struct maintenance_run_opts *opts) +{ + prepare_repo_settings(the_repository); + if (!the_repository->settings.core_commit_graph) + return 0; + + if (run_write_commit_graph(opts)) { + error(_("failed to write commit-graph")); + return 1; + } + + return 0; +} + +static int fetch_remote(struct remote *remote, void *cbdata) +{ + struct maintenance_run_opts *opts = cbdata; + struct child_process child = CHILD_PROCESS_INIT; + + if (remote->skip_default_update) + return 0; + + child.git_cmd = 1; + strvec_pushl(&child.args, "fetch", remote->name, + "--prefetch", "--prune", "--no-tags", + "--no-write-fetch-head", "--recurse-submodules=no", + NULL); + + if (opts->quiet) + strvec_push(&child.args, "--quiet"); + + return !!run_command(&child); +} + +static int maintenance_task_prefetch(struct maintenance_run_opts *opts) +{ + git_config_set_multivar_gently("log.excludedecoration", + "refs/prefetch/", + "refs/prefetch/", + CONFIG_FLAGS_FIXED_VALUE | + CONFIG_FLAGS_MULTI_REPLACE); + + if (for_each_remote(fetch_remote, opts)) { + error(_("failed to prefetch remotes")); + return 1; + } + + return 0; +} + +static int maintenance_task_gc(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = child.close_object_store = 1; + strvec_push(&child.args, "gc"); + + if (opts->auto_flag) + strvec_push(&child.args, "--auto"); + if (opts->quiet) + strvec_push(&child.args, "--quiet"); + else + strvec_push(&child.args, "--no-quiet"); + + return run_command(&child); +} + +static int prune_packed(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = 1; + strvec_push(&child.args, "prune-packed"); + + if (opts->quiet) + strvec_push(&child.args, "--quiet"); + + return !!run_command(&child); +} + +struct write_loose_object_data { + FILE *in; + int count; + int batch_size; +}; + +static int loose_object_auto_limit = 100; + +static int loose_object_count(const struct object_id *oid, + const char *path, + void *data) +{ + int *count = (int*)data; + if (++(*count) >= loose_object_auto_limit) + return 1; + return 0; +} + +static int loose_object_auto_condition(void) +{ + int count = 0; + + git_config_get_int("maintenance.loose-objects.auto", + &loose_object_auto_limit); + + if (!loose_object_auto_limit) + return 0; + if (loose_object_auto_limit < 0) + return 1; + + return for_each_loose_file_in_objdir(the_repository->objects->odb->path, + loose_object_count, + NULL, NULL, &count); +} + +static int bail_on_loose(const struct object_id *oid, + const char *path, + void *data) +{ + return 1; +} + +static int write_loose_object_to_stdin(const struct object_id *oid, + const char *path, + void *data) +{ + struct write_loose_object_data *d = (struct write_loose_object_data *)data; + + fprintf(d->in, "%s\n", oid_to_hex(oid)); + + return ++(d->count) > d->batch_size; +} + +static int pack_loose(struct maintenance_run_opts *opts) +{ + struct repository *r = the_repository; + int result = 0; + struct write_loose_object_data data; + struct child_process pack_proc = CHILD_PROCESS_INIT; + + /* + * Do not start pack-objects process + * if there are no loose objects. + */ + if (!for_each_loose_file_in_objdir(r->objects->odb->path, + bail_on_loose, + NULL, NULL, NULL)) + return 0; + + pack_proc.git_cmd = 1; + + strvec_push(&pack_proc.args, "pack-objects"); + if (opts->quiet) + strvec_push(&pack_proc.args, "--quiet"); + strvec_pushf(&pack_proc.args, "%s/pack/loose", r->objects->odb->path); + + pack_proc.in = -1; + + if (start_command(&pack_proc)) { + error(_("failed to start 'git pack-objects' process")); + return 1; + } + + data.in = xfdopen(pack_proc.in, "w"); + data.count = 0; + data.batch_size = 50000; + + for_each_loose_file_in_objdir(r->objects->odb->path, + write_loose_object_to_stdin, + NULL, + NULL, + &data); + + fclose(data.in); + + if (finish_command(&pack_proc)) { + error(_("failed to finish 'git pack-objects' process")); + result = 1; + } + + return result; +} + +static int maintenance_task_loose_objects(struct maintenance_run_opts *opts) +{ + return prune_packed(opts) || pack_loose(opts); +} + +static int incremental_repack_auto_condition(void) +{ + struct packed_git *p; + int incremental_repack_auto_limit = 10; + int count = 0; + + prepare_repo_settings(the_repository); + if (!the_repository->settings.core_multi_pack_index) + return 0; + + git_config_get_int("maintenance.incremental-repack.auto", + &incremental_repack_auto_limit); + + if (!incremental_repack_auto_limit) + return 0; + if (incremental_repack_auto_limit < 0) + return 1; + + for (p = get_packed_git(the_repository); + count < incremental_repack_auto_limit && p; + p = p->next) { + if (!p->multi_pack_index) + count++; + } + + return count >= incremental_repack_auto_limit; +} + +static int multi_pack_index_write(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = 1; + strvec_pushl(&child.args, "multi-pack-index", "write", NULL); + + if (opts->quiet) + strvec_push(&child.args, "--no-progress"); + + if (run_command(&child)) + return error(_("failed to write multi-pack-index")); + + return 0; +} + +static int multi_pack_index_expire(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = child.close_object_store = 1; + strvec_pushl(&child.args, "multi-pack-index", "expire", NULL); + + if (opts->quiet) + strvec_push(&child.args, "--no-progress"); + + if (run_command(&child)) + return error(_("'git multi-pack-index expire' failed")); + + return 0; +} + +#define TWO_GIGABYTES (INT32_MAX) + +static off_t get_auto_pack_size(void) +{ + /* + * The "auto" value is special: we optimize for + * one large pack-file (i.e. from a clone) and + * expect the rest to be small and they can be + * repacked quickly. + * + * The strategy we select here is to select a + * size that is one more than the second largest + * pack-file. This ensures that we will repack + * at least two packs if there are three or more + * packs. + */ + off_t max_size = 0; + off_t second_largest_size = 0; + off_t result_size; + struct packed_git *p; + struct repository *r = the_repository; + + reprepare_packed_git(r); + for (p = get_all_packs(r); p; p = p->next) { + if (p->pack_size > max_size) { + second_largest_size = max_size; + max_size = p->pack_size; + } else if (p->pack_size > second_largest_size) + second_largest_size = p->pack_size; + } + + result_size = second_largest_size + 1; + + /* But limit ourselves to a batch size of 2g */ + if (result_size > TWO_GIGABYTES) + result_size = TWO_GIGABYTES; + + return result_size; +} + +static int multi_pack_index_repack(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = child.close_object_store = 1; + strvec_pushl(&child.args, "multi-pack-index", "repack", NULL); + + if (opts->quiet) + strvec_push(&child.args, "--no-progress"); + + strvec_pushf(&child.args, "--batch-size=%"PRIuMAX, + (uintmax_t)get_auto_pack_size()); + + if (run_command(&child)) + return error(_("'git multi-pack-index repack' failed")); + + return 0; +} + +static int maintenance_task_incremental_repack(struct maintenance_run_opts *opts) +{ + prepare_repo_settings(the_repository); + if (!the_repository->settings.core_multi_pack_index) { + warning(_("skipping incremental-repack task because core.multiPackIndex is disabled")); + return 0; + } + + if (multi_pack_index_write(opts)) + return 1; + if (multi_pack_index_expire(opts)) + return 1; + if (multi_pack_index_repack(opts)) + return 1; + return 0; +} + +typedef int maintenance_task_fn(struct maintenance_run_opts *opts); + +/* + * An auto condition function returns 1 if the task should run + * and 0 if the task should NOT run. See needs_to_gc() for an + * example. + */ +typedef int maintenance_auto_fn(void); + +struct maintenance_task { + const char *name; + maintenance_task_fn *fn; + maintenance_auto_fn *auto_condition; + unsigned enabled:1; + + enum schedule_priority schedule; + + /* -1 if not selected. */ + int selected_order; +}; + +enum maintenance_task_label { + TASK_PREFETCH, + TASK_LOOSE_OBJECTS, + TASK_INCREMENTAL_REPACK, + TASK_GC, + TASK_COMMIT_GRAPH, + TASK_PACK_REFS, + + /* Leave as final value */ + TASK__COUNT +}; + +static struct maintenance_task tasks[] = { + [TASK_PREFETCH] = { + "prefetch", + maintenance_task_prefetch, + }, + [TASK_LOOSE_OBJECTS] = { + "loose-objects", + maintenance_task_loose_objects, + loose_object_auto_condition, + }, + [TASK_INCREMENTAL_REPACK] = { + "incremental-repack", + maintenance_task_incremental_repack, + incremental_repack_auto_condition, + }, + [TASK_GC] = { + "gc", + maintenance_task_gc, + need_to_gc, + 1, + }, + [TASK_COMMIT_GRAPH] = { + "commit-graph", + maintenance_task_commit_graph, + should_write_commit_graph, + }, + [TASK_PACK_REFS] = { + "pack-refs", + maintenance_task_pack_refs, + NULL, + }, +}; + +static int compare_tasks_by_selection(const void *a_, const void *b_) +{ + const struct maintenance_task *a = a_; + const struct maintenance_task *b = b_; + + return b->selected_order - a->selected_order; +} + +static int maintenance_run_tasks(struct maintenance_run_opts *opts) +{ + int i, found_selected = 0; + int result = 0; + struct lock_file lk; + struct repository *r = the_repository; + char *lock_path = xstrfmt("%s/maintenance", r->objects->odb->path); + + if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) { + /* + * Another maintenance command is running. + * + * If --auto was provided, then it is likely due to a + * recursive process stack. Do not report an error in + * that case. + */ + if (!opts->auto_flag && !opts->quiet) + warning(_("lock file '%s' exists, skipping maintenance"), + lock_path); + free(lock_path); + return 0; + } + free(lock_path); + + for (i = 0; !found_selected && i < TASK__COUNT; i++) + found_selected = tasks[i].selected_order >= 0; + + if (found_selected) + QSORT(tasks, TASK__COUNT, compare_tasks_by_selection); + + for (i = 0; i < TASK__COUNT; i++) { + if (found_selected && tasks[i].selected_order < 0) + continue; + + if (!found_selected && !tasks[i].enabled) + continue; + + if (opts->auto_flag && + (!tasks[i].auto_condition || + !tasks[i].auto_condition())) + continue; + + if (opts->schedule && tasks[i].schedule < opts->schedule) + continue; + + trace2_region_enter("maintenance", tasks[i].name, r); + if (tasks[i].fn(opts)) { + error(_("task '%s' failed"), tasks[i].name); + result = 1; + } + trace2_region_leave("maintenance", tasks[i].name, r); + } + + rollback_lock_file(&lk); + return result; +} + +static void initialize_maintenance_strategy(void) +{ + char *config_str; + + if (git_config_get_string("maintenance.strategy", &config_str)) + return; + + if (!strcasecmp(config_str, "incremental")) { + tasks[TASK_GC].schedule = SCHEDULE_NONE; + tasks[TASK_COMMIT_GRAPH].enabled = 1; + tasks[TASK_COMMIT_GRAPH].schedule = SCHEDULE_HOURLY; + tasks[TASK_PREFETCH].enabled = 1; + tasks[TASK_PREFETCH].schedule = SCHEDULE_HOURLY; + tasks[TASK_INCREMENTAL_REPACK].enabled = 1; + tasks[TASK_INCREMENTAL_REPACK].schedule = SCHEDULE_DAILY; + tasks[TASK_LOOSE_OBJECTS].enabled = 1; + tasks[TASK_LOOSE_OBJECTS].schedule = SCHEDULE_DAILY; + tasks[TASK_PACK_REFS].enabled = 1; + tasks[TASK_PACK_REFS].schedule = SCHEDULE_WEEKLY; + } +} + +static void initialize_task_config(int schedule) +{ + int i; + struct strbuf config_name = STRBUF_INIT; + gc_config(); + + if (schedule) + initialize_maintenance_strategy(); + + for (i = 0; i < TASK__COUNT; i++) { + int config_value; + char *config_str; + + strbuf_reset(&config_name); + strbuf_addf(&config_name, "maintenance.%s.enabled", + tasks[i].name); + + if (!git_config_get_bool(config_name.buf, &config_value)) + tasks[i].enabled = config_value; + + strbuf_reset(&config_name); + strbuf_addf(&config_name, "maintenance.%s.schedule", + tasks[i].name); + + if (!git_config_get_string(config_name.buf, &config_str)) { + tasks[i].schedule = parse_schedule(config_str); + free(config_str); + } + } + + strbuf_release(&config_name); +} + +static int task_option_parse(const struct option *opt, + const char *arg, int unset) +{ + int i, num_selected = 0; + struct maintenance_task *task = NULL; + + BUG_ON_OPT_NEG(unset); + + for (i = 0; i < TASK__COUNT; i++) { + if (tasks[i].selected_order >= 0) + num_selected++; + if (!strcasecmp(tasks[i].name, arg)) { + task = &tasks[i]; + } + } + + if (!task) { + error(_("'%s' is not a valid task"), arg); + return 1; + } + + if (task->selected_order >= 0) { + error(_("task '%s' cannot be selected multiple times"), arg); + return 1; + } + + task->selected_order = num_selected + 1; + + return 0; +} + +static int maintenance_run(int argc, const char **argv, const char *prefix) +{ + int i; + struct maintenance_run_opts opts; + struct option builtin_maintenance_run_options[] = { + OPT_BOOL(0, "auto", &opts.auto_flag, + N_("run tasks based on the state of the repository")), + OPT_CALLBACK(0, "schedule", &opts.schedule, N_("frequency"), + N_("run tasks based on frequency"), + maintenance_opt_schedule), + OPT_BOOL(0, "quiet", &opts.quiet, + N_("do not report progress or other information over stderr")), + OPT_CALLBACK_F(0, "task", NULL, N_("task"), + N_("run a specific task"), + PARSE_OPT_NONEG, task_option_parse), + OPT_END() + }; + memset(&opts, 0, sizeof(opts)); + + opts.quiet = !isatty(2); + + for (i = 0; i < TASK__COUNT; i++) + tasks[i].selected_order = -1; + + argc = parse_options(argc, argv, prefix, + builtin_maintenance_run_options, + builtin_maintenance_run_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (opts.auto_flag && opts.schedule) + die(_("use at most one of --auto and --schedule=")); + + initialize_task_config(opts.schedule); + + if (argc != 0) + usage_with_options(builtin_maintenance_run_usage, + builtin_maintenance_run_options); + return maintenance_run_tasks(&opts); +} + +static char *get_maintpath(void) +{ + struct strbuf sb = STRBUF_INIT; + const char *p = the_repository->worktree ? + the_repository->worktree : the_repository->gitdir; + + strbuf_realpath(&sb, p, 1); + return strbuf_detach(&sb, NULL); +} + +static int maintenance_register(void) +{ + int rc; + char *config_value; + struct child_process config_set = CHILD_PROCESS_INIT; + struct child_process config_get = CHILD_PROCESS_INIT; + char *maintpath = get_maintpath(); + + /* Disable foreground maintenance */ + git_config_set("maintenance.auto", "false"); + + /* Set maintenance strategy, if unset */ + if (!git_config_get_string("maintenance.strategy", &config_value)) + free(config_value); + else + git_config_set("maintenance.strategy", "incremental"); + + config_get.git_cmd = 1; + strvec_pushl(&config_get.args, "config", "--global", "--get", + "--fixed-value", "maintenance.repo", maintpath, NULL); + config_get.out = -1; + + if (start_command(&config_get)) { + rc = error(_("failed to run 'git config'")); + goto done; + } + + /* We already have this value in our config! */ + if (!finish_command(&config_get)) { + rc = 0; + goto done; + } + + config_set.git_cmd = 1; + strvec_pushl(&config_set.args, "config", "--add", "--global", "maintenance.repo", + maintpath, NULL); + + rc = run_command(&config_set); + +done: + free(maintpath); + return rc; +} + +static int maintenance_unregister(void) +{ + int rc; + struct child_process config_unset = CHILD_PROCESS_INIT; + char *maintpath = get_maintpath(); + + config_unset.git_cmd = 1; + strvec_pushl(&config_unset.args, "config", "--global", "--unset", + "--fixed-value", "maintenance.repo", maintpath, NULL); + + rc = run_command(&config_unset); + free(maintpath); + return rc; +} + +static const char *get_frequency(enum schedule_priority schedule) +{ + switch (schedule) { + case SCHEDULE_HOURLY: + return "hourly"; + case SCHEDULE_DAILY: + return "daily"; + case SCHEDULE_WEEKLY: + return "weekly"; + default: + BUG("invalid schedule %d", schedule); + } +} + +/* + * get_schedule_cmd` reads the GIT_TEST_MAINT_SCHEDULER environment variable + * to mock the schedulers that `git maintenance start` rely on. + * + * For test purpose, GIT_TEST_MAINT_SCHEDULER can be set to a comma-separated + * list of colon-separated key/value pairs where each pair contains a scheduler + * and its corresponding mock. + * + * * If $GIT_TEST_MAINT_SCHEDULER is not set, return false and leave the + * arguments unmodified. + * + * * If $GIT_TEST_MAINT_SCHEDULER is set, return true. + * In this case, the *cmd value is read as input. + * + * * if the input value *cmd is the key of one of the comma-separated list + * item, then *is_available is set to true and *cmd is modified and becomes + * the mock command. + * + * * if the input value *cmd isn’t the key of any of the comma-separated list + * item, then *is_available is set to false. + * + * Ex.: + * GIT_TEST_MAINT_SCHEDULER not set + * +-------+-------------------------------------------------+ + * | Input | Output | + * | *cmd | return code | *cmd | *is_available | + * +-------+-------------+-------------------+---------------+ + * | "foo" | false | "foo" (unchanged) | (unchanged) | + * +-------+-------------+-------------------+---------------+ + * + * GIT_TEST_MAINT_SCHEDULER set to “foo:./mock_foo.sh,bar:./mock_bar.sh” + * +-------+-------------------------------------------------+ + * | Input | Output | + * | *cmd | return code | *cmd | *is_available | + * +-------+-------------+-------------------+---------------+ + * | "foo" | true | "./mock.foo.sh" | true | + * | "qux" | true | "qux" (unchanged) | false | + * +-------+-------------+-------------------+---------------+ + */ +static int get_schedule_cmd(const char **cmd, int *is_available) +{ + char *testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER")); + struct string_list_item *item; + struct string_list list = STRING_LIST_INIT_NODUP; + + if (!testing) + return 0; + + if (is_available) + *is_available = 0; + + string_list_split_in_place(&list, testing, ',', -1); + for_each_string_list_item(item, &list) { + struct string_list pair = STRING_LIST_INIT_NODUP; + + if (string_list_split_in_place(&pair, item->string, ':', 2) != 2) + continue; + + if (!strcmp(*cmd, pair.items[0].string)) { + *cmd = pair.items[1].string; + if (is_available) + *is_available = 1; + string_list_clear(&list, 0); + UNLEAK(testing); + return 1; + } + } + + string_list_clear(&list, 0); + free(testing); + return 1; +} + +static int is_launchctl_available(void) +{ + const char *cmd = "launchctl"; + int is_available; + if (get_schedule_cmd(&cmd, &is_available)) + return is_available; + +#ifdef __APPLE__ + return 1; +#else + return 0; +#endif +} + +static char *launchctl_service_name(const char *frequency) +{ + struct strbuf label = STRBUF_INIT; + strbuf_addf(&label, "org.git-scm.git.%s", frequency); + return strbuf_detach(&label, NULL); +} + +static char *launchctl_service_filename(const char *name) +{ + char *expanded; + struct strbuf filename = STRBUF_INIT; + strbuf_addf(&filename, "~/Library/LaunchAgents/%s.plist", name); + + expanded = interpolate_path(filename.buf, 1); + if (!expanded) + die(_("failed to expand path '%s'"), filename.buf); + + strbuf_release(&filename); + return expanded; +} + +static char *launchctl_get_uid(void) +{ + return xstrfmt("gui/%d", getuid()); +} + +static int launchctl_boot_plist(int enable, const char *filename) +{ + const char *cmd = "launchctl"; + int result; + struct child_process child = CHILD_PROCESS_INIT; + char *uid = launchctl_get_uid(); + + get_schedule_cmd(&cmd, NULL); + strvec_split(&child.args, cmd); + strvec_pushl(&child.args, enable ? "bootstrap" : "bootout", uid, + filename, NULL); + + child.no_stderr = 1; + child.no_stdout = 1; + + if (start_command(&child)) + die(_("failed to start launchctl")); + + result = finish_command(&child); + + free(uid); + return result; +} + +static int launchctl_remove_plist(enum schedule_priority schedule) +{ + const char *frequency = get_frequency(schedule); + char *name = launchctl_service_name(frequency); + char *filename = launchctl_service_filename(name); + int result = launchctl_boot_plist(0, filename); + unlink(filename); + free(filename); + free(name); + return result; +} + +static int launchctl_remove_plists(void) +{ + return launchctl_remove_plist(SCHEDULE_HOURLY) || + launchctl_remove_plist(SCHEDULE_DAILY) || + launchctl_remove_plist(SCHEDULE_WEEKLY); +} + +static int launchctl_list_contains_plist(const char *name, const char *cmd) +{ + struct child_process child = CHILD_PROCESS_INIT; + + strvec_split(&child.args, cmd); + strvec_pushl(&child.args, "list", name, NULL); + + child.no_stderr = 1; + child.no_stdout = 1; + + if (start_command(&child)) + die(_("failed to start launchctl")); + + /* Returns failure if 'name' doesn't exist. */ + return !finish_command(&child); +} + +static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule) +{ + int i, fd; + const char *preamble, *repeat; + const char *frequency = get_frequency(schedule); + char *name = launchctl_service_name(frequency); + char *filename = launchctl_service_filename(name); + struct lock_file lk = LOCK_INIT; + static unsigned long lock_file_timeout_ms = ULONG_MAX; + struct strbuf plist = STRBUF_INIT, plist2 = STRBUF_INIT; + struct stat st; + const char *cmd = "launchctl"; + + get_schedule_cmd(&cmd, NULL); + preamble = "\n" + "\n" + "" + "\n" + "Label%s\n" + "ProgramArguments\n" + "\n" + "%s/git\n" + "--exec-path=%s\n" + "for-each-repo\n" + "--config=maintenance.repo\n" + "maintenance\n" + "run\n" + "--schedule=%s\n" + "\n" + "StartCalendarInterval\n" + "\n"; + strbuf_addf(&plist, preamble, name, exec_path, exec_path, frequency); + + switch (schedule) { + case SCHEDULE_HOURLY: + repeat = "\n" + "Hour%d\n" + "Minute0\n" + "\n"; + for (i = 1; i <= 23; i++) + strbuf_addf(&plist, repeat, i); + break; + + case SCHEDULE_DAILY: + repeat = "\n" + "Day%d\n" + "Hour0\n" + "Minute0\n" + "\n"; + for (i = 1; i <= 6; i++) + strbuf_addf(&plist, repeat, i); + break; + + case SCHEDULE_WEEKLY: + strbuf_addstr(&plist, + "\n" + "Day0\n" + "Hour0\n" + "Minute0\n" + "\n"); + break; + + default: + /* unreachable */ + break; + } + strbuf_addstr(&plist, "\n\n\n"); + + if (safe_create_leading_directories(filename)) + die(_("failed to create directories for '%s'"), filename); + + if ((long)lock_file_timeout_ms < 0 && + git_config_get_ulong("gc.launchctlplistlocktimeoutms", + &lock_file_timeout_ms)) + lock_file_timeout_ms = 150; + + fd = hold_lock_file_for_update_timeout(&lk, filename, LOCK_DIE_ON_ERROR, + lock_file_timeout_ms); + + /* + * Does this file already exist? With the intended contents? Is it + * registered already? Then it does not need to be re-registered. + */ + if (!stat(filename, &st) && st.st_size == plist.len && + strbuf_read_file(&plist2, filename, plist.len) == plist.len && + !strbuf_cmp(&plist, &plist2) && + launchctl_list_contains_plist(name, cmd)) + rollback_lock_file(&lk); + else { + if (write_in_full(fd, plist.buf, plist.len) < 0 || + commit_lock_file(&lk)) + die_errno(_("could not write '%s'"), filename); + + /* bootout might fail if not already running, so ignore */ + launchctl_boot_plist(0, filename); + if (launchctl_boot_plist(1, filename)) + die(_("failed to bootstrap service %s"), filename); + } + + free(filename); + free(name); + strbuf_release(&plist); + strbuf_release(&plist2); + return 0; +} + +static int launchctl_add_plists(void) +{ + const char *exec_path = git_exec_path(); + + return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY) || + launchctl_schedule_plist(exec_path, SCHEDULE_DAILY) || + launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY); +} + +static int launchctl_update_schedule(int run_maintenance, int fd) +{ + if (run_maintenance) + return launchctl_add_plists(); + else + return launchctl_remove_plists(); +} + +static int is_schtasks_available(void) +{ + const char *cmd = "schtasks"; + int is_available; + if (get_schedule_cmd(&cmd, &is_available)) + return is_available; + +#ifdef GIT_WINDOWS_NATIVE + return 1; +#else + return 0; +#endif +} + +static char *schtasks_task_name(const char *frequency) +{ + struct strbuf label = STRBUF_INIT; + strbuf_addf(&label, "Git Maintenance (%s)", frequency); + return strbuf_detach(&label, NULL); +} + +static int schtasks_remove_task(enum schedule_priority schedule) +{ + const char *cmd = "schtasks"; + int result; + struct strvec args = STRVEC_INIT; + const char *frequency = get_frequency(schedule); + char *name = schtasks_task_name(frequency); + + get_schedule_cmd(&cmd, NULL); + strvec_split(&args, cmd); + strvec_pushl(&args, "/delete", "/tn", name, "/f", NULL); + + result = run_command_v_opt(args.v, 0); + + strvec_clear(&args); + free(name); + return result; +} + +static int schtasks_remove_tasks(void) +{ + return schtasks_remove_task(SCHEDULE_HOURLY) || + schtasks_remove_task(SCHEDULE_DAILY) || + schtasks_remove_task(SCHEDULE_WEEKLY); +} + +static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule) +{ + const char *cmd = "schtasks"; + int result; + struct child_process child = CHILD_PROCESS_INIT; + const char *xml; + struct tempfile *tfile; + const char *frequency = get_frequency(schedule); + char *name = schtasks_task_name(frequency); + struct strbuf tfilename = STRBUF_INIT; + + get_schedule_cmd(&cmd, NULL); + + strbuf_addf(&tfilename, "%s/schedule_%s_XXXXXX", + get_git_common_dir(), frequency); + tfile = xmks_tempfile(tfilename.buf); + strbuf_release(&tfilename); + + if (!fdopen_tempfile(tfile, "w")) + die(_("failed to create temp xml file")); + + xml = "\n" + "\n" + "\n" + "\n"; + fputs(xml, tfile->fp); + + switch (schedule) { + case SCHEDULE_HOURLY: + fprintf(tfile->fp, + "2020-01-01T01:00:00\n" + "true\n" + "\n" + "1\n" + "\n" + "\n" + "PT1H\n" + "PT23H\n" + "false\n" + "\n"); + break; + + case SCHEDULE_DAILY: + fprintf(tfile->fp, + "2020-01-01T00:00:00\n" + "true\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "\n" + "1\n" + "\n"); + break; + + case SCHEDULE_WEEKLY: + fprintf(tfile->fp, + "2020-01-01T00:00:00\n" + "true\n" + "\n" + "\n" + "\n" + "\n" + "1\n" + "\n"); + break; + + default: + break; + } + + xml = "\n" + "\n" + "\n" + "\n" + "InteractiveToken\n" + "LeastPrivilege\n" + "\n" + "\n" + "\n" + "IgnoreNew\n" + "true\n" + "true\n" + "true\n" + "false\n" + "PT72H\n" + "7\n" + "\n" + "\n" + "\n" + "\"%s\\git.exe\"\n" + "--exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%s\n" + "\n" + "\n" + "\n"; + fprintf(tfile->fp, xml, exec_path, exec_path, frequency); + strvec_split(&child.args, cmd); + strvec_pushl(&child.args, "/create", "/tn", name, "/f", "/xml", + get_tempfile_path(tfile), NULL); + close_tempfile_gently(tfile); + + child.no_stdout = 1; + child.no_stderr = 1; + + if (start_command(&child)) + die(_("failed to start schtasks")); + result = finish_command(&child); + + delete_tempfile(&tfile); + free(name); + return result; +} + +static int schtasks_schedule_tasks(void) +{ + const char *exec_path = git_exec_path(); + + return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY) || + schtasks_schedule_task(exec_path, SCHEDULE_DAILY) || + schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY); +} + +static int schtasks_update_schedule(int run_maintenance, int fd) +{ + if (run_maintenance) + return schtasks_schedule_tasks(); + else + return schtasks_remove_tasks(); +} + +MAYBE_UNUSED +static int check_crontab_process(const char *cmd) +{ + struct child_process child = CHILD_PROCESS_INIT; + + strvec_split(&child.args, cmd); + strvec_push(&child.args, "-l"); + child.no_stdin = 1; + child.no_stdout = 1; + child.no_stderr = 1; + child.silent_exec_failure = 1; + + if (start_command(&child)) + return 0; + /* Ignore exit code, as an empty crontab will return error. */ + finish_command(&child); + return 1; +} + +static int is_crontab_available(void) +{ + const char *cmd = "crontab"; + int is_available; + + if (get_schedule_cmd(&cmd, &is_available)) + return is_available; + +#ifdef __APPLE__ + /* + * macOS has cron, but it requires special permissions and will + * create a UI alert when attempting to run this command. + */ + return 0; +#else + return check_crontab_process(cmd); +#endif +} + +#define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE" +#define END_LINE "# END GIT MAINTENANCE SCHEDULE" + +static int crontab_update_schedule(int run_maintenance, int fd) +{ + const char *cmd = "crontab"; + int result = 0; + int in_old_region = 0; + struct child_process crontab_list = CHILD_PROCESS_INIT; + struct child_process crontab_edit = CHILD_PROCESS_INIT; + FILE *cron_list, *cron_in; + struct strbuf line = STRBUF_INIT; + + get_schedule_cmd(&cmd, NULL); + strvec_split(&crontab_list.args, cmd); + strvec_push(&crontab_list.args, "-l"); + crontab_list.in = -1; + crontab_list.out = dup(fd); + crontab_list.git_cmd = 0; + + if (start_command(&crontab_list)) + return error(_("failed to run 'crontab -l'; your system might not support 'cron'")); + + /* Ignore exit code, as an empty crontab will return error. */ + finish_command(&crontab_list); + + /* + * Read from the .lock file, filtering out the old + * schedule while appending the new schedule. + */ + cron_list = fdopen(fd, "r"); + rewind(cron_list); + + strvec_split(&crontab_edit.args, cmd); + crontab_edit.in = -1; + crontab_edit.git_cmd = 0; + + if (start_command(&crontab_edit)) + return error(_("failed to run 'crontab'; your system might not support 'cron'")); + + cron_in = fdopen(crontab_edit.in, "w"); + if (!cron_in) { + result = error(_("failed to open stdin of 'crontab'")); + goto done_editing; + } + + while (!strbuf_getline_lf(&line, cron_list)) { + if (!in_old_region && !strcmp(line.buf, BEGIN_LINE)) + in_old_region = 1; + else if (in_old_region && !strcmp(line.buf, END_LINE)) + in_old_region = 0; + else if (!in_old_region) + fprintf(cron_in, "%s\n", line.buf); + } + strbuf_release(&line); + + if (run_maintenance) { + struct strbuf line_format = STRBUF_INIT; + const char *exec_path = git_exec_path(); + + fprintf(cron_in, "%s\n", BEGIN_LINE); + fprintf(cron_in, + "# The following schedule was created by Git\n"); + fprintf(cron_in, "# Any edits made in this region might be\n"); + fprintf(cron_in, + "# replaced in the future by a Git command.\n\n"); + + strbuf_addf(&line_format, + "%%s %%s * * %%s \"%s/git\" --exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%%s\n", + exec_path, exec_path); + fprintf(cron_in, line_format.buf, "0", "1-23", "*", "hourly"); + fprintf(cron_in, line_format.buf, "0", "0", "1-6", "daily"); + fprintf(cron_in, line_format.buf, "0", "0", "0", "weekly"); + strbuf_release(&line_format); + + fprintf(cron_in, "\n%s\n", END_LINE); + } + + fflush(cron_in); + fclose(cron_in); + close(crontab_edit.in); + +done_editing: + if (finish_command(&crontab_edit)) + result = error(_("'crontab' died")); + else + fclose(cron_list); + return result; +} + +static int real_is_systemd_timer_available(void) +{ + struct child_process child = CHILD_PROCESS_INIT; + + strvec_pushl(&child.args, "systemctl", "--user", "list-timers", NULL); + child.no_stdin = 1; + child.no_stdout = 1; + child.no_stderr = 1; + child.silent_exec_failure = 1; + + if (start_command(&child)) + return 0; + if (finish_command(&child)) + return 0; + return 1; +} + +static int is_systemd_timer_available(void) +{ + const char *cmd = "systemctl"; + int is_available; + + if (get_schedule_cmd(&cmd, &is_available)) + return is_available; + + return real_is_systemd_timer_available(); +} + +static char *xdg_config_home_systemd(const char *filename) +{ + return xdg_config_home_for("systemd/user", filename); +} + +static int systemd_timer_enable_unit(int enable, + enum schedule_priority schedule) +{ + const char *cmd = "systemctl"; + struct child_process child = CHILD_PROCESS_INIT; + const char *frequency = get_frequency(schedule); + + /* + * Disabling the systemd unit while it is already disabled makes + * systemctl print an error. + * Let's ignore it since it means we already are in the expected state: + * the unit is disabled. + * + * On the other hand, enabling a systemd unit which is already enabled + * produces no error. + */ + if (!enable) + child.no_stderr = 1; + + get_schedule_cmd(&cmd, NULL); + strvec_split(&child.args, cmd); + strvec_pushl(&child.args, "--user", enable ? "enable" : "disable", + "--now", NULL); + strvec_pushf(&child.args, "git-maintenance@%s.timer", frequency); + + if (start_command(&child)) + return error(_("failed to start systemctl")); + if (finish_command(&child)) + /* + * Disabling an already disabled systemd unit makes + * systemctl fail. + * Let's ignore this failure. + * + * Enabling an enabled systemd unit doesn't fail. + */ + if (enable) + return error(_("failed to run systemctl")); + return 0; +} + +static int systemd_timer_delete_unit_templates(void) +{ + int ret = 0; + char *filename = xdg_config_home_systemd("git-maintenance@.timer"); + if (unlink(filename) && !is_missing_file_error(errno)) + ret = error_errno(_("failed to delete '%s'"), filename); + FREE_AND_NULL(filename); + + filename = xdg_config_home_systemd("git-maintenance@.service"); + if (unlink(filename) && !is_missing_file_error(errno)) + ret = error_errno(_("failed to delete '%s'"), filename); + + free(filename); + return ret; +} + +static int systemd_timer_delete_units(void) +{ + return systemd_timer_enable_unit(0, SCHEDULE_HOURLY) || + systemd_timer_enable_unit(0, SCHEDULE_DAILY) || + systemd_timer_enable_unit(0, SCHEDULE_WEEKLY) || + systemd_timer_delete_unit_templates(); +} + +static int systemd_timer_write_unit_templates(const char *exec_path) +{ + char *filename; + FILE *file; + const char *unit; + + filename = xdg_config_home_systemd("git-maintenance@.timer"); + if (safe_create_leading_directories(filename)) { + error(_("failed to create directories for '%s'"), filename); + goto error; + } + file = fopen_or_warn(filename, "w"); + if (file == NULL) + goto error; + + unit = "# This file was created and is maintained by Git.\n" + "# Any edits made in this file might be replaced in the future\n" + "# by a Git command.\n" + "\n" + "[Unit]\n" + "Description=Optimize Git repositories data\n" + "\n" + "[Timer]\n" + "OnCalendar=%i\n" + "Persistent=true\n" + "\n" + "[Install]\n" + "WantedBy=timers.target\n"; + if (fputs(unit, file) == EOF) { + error(_("failed to write to '%s'"), filename); + fclose(file); + goto error; + } + if (fclose(file) == EOF) { + error_errno(_("failed to flush '%s'"), filename); + goto error; + } + free(filename); + + filename = xdg_config_home_systemd("git-maintenance@.service"); + file = fopen_or_warn(filename, "w"); + if (file == NULL) + goto error; + + unit = "# This file was created and is maintained by Git.\n" + "# Any edits made in this file might be replaced in the future\n" + "# by a Git command.\n" + "\n" + "[Unit]\n" + "Description=Optimize Git repositories data\n" + "\n" + "[Service]\n" + "Type=oneshot\n" + "ExecStart=\"%s/git\" --exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%%i\n" + "LockPersonality=yes\n" + "MemoryDenyWriteExecute=yes\n" + "NoNewPrivileges=yes\n" + "RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6\n" + "RestrictNamespaces=yes\n" + "RestrictRealtime=yes\n" + "RestrictSUIDSGID=yes\n" + "SystemCallArchitectures=native\n" + "SystemCallFilter=@system-service\n"; + if (fprintf(file, unit, exec_path, exec_path) < 0) { + error(_("failed to write to '%s'"), filename); + fclose(file); + goto error; + } + if (fclose(file) == EOF) { + error_errno(_("failed to flush '%s'"), filename); + goto error; + } + free(filename); + return 0; + +error: + free(filename); + systemd_timer_delete_unit_templates(); + return -1; +} + +static int systemd_timer_setup_units(void) +{ + const char *exec_path = git_exec_path(); + + int ret = systemd_timer_write_unit_templates(exec_path) || + systemd_timer_enable_unit(1, SCHEDULE_HOURLY) || + systemd_timer_enable_unit(1, SCHEDULE_DAILY) || + systemd_timer_enable_unit(1, SCHEDULE_WEEKLY); + if (ret) + systemd_timer_delete_units(); + return ret; +} + +static int systemd_timer_update_schedule(int run_maintenance, int fd) +{ + if (run_maintenance) + return systemd_timer_setup_units(); + else + return systemd_timer_delete_units(); +} + +enum scheduler { + SCHEDULER_INVALID = -1, + SCHEDULER_AUTO, + SCHEDULER_CRON, + SCHEDULER_SYSTEMD, + SCHEDULER_LAUNCHCTL, + SCHEDULER_SCHTASKS, +}; + +static const struct { + const char *name; + int (*is_available)(void); + int (*update_schedule)(int run_maintenance, int fd); +} scheduler_fn[] = { + [SCHEDULER_CRON] = { + .name = "crontab", + .is_available = is_crontab_available, + .update_schedule = crontab_update_schedule, + }, + [SCHEDULER_SYSTEMD] = { + .name = "systemctl", + .is_available = is_systemd_timer_available, + .update_schedule = systemd_timer_update_schedule, + }, + [SCHEDULER_LAUNCHCTL] = { + .name = "launchctl", + .is_available = is_launchctl_available, + .update_schedule = launchctl_update_schedule, + }, + [SCHEDULER_SCHTASKS] = { + .name = "schtasks", + .is_available = is_schtasks_available, + .update_schedule = schtasks_update_schedule, + }, +}; + +static enum scheduler parse_scheduler(const char *value) +{ + if (!value) + return SCHEDULER_INVALID; + else if (!strcasecmp(value, "auto")) + return SCHEDULER_AUTO; + else if (!strcasecmp(value, "cron") || !strcasecmp(value, "crontab")) + return SCHEDULER_CRON; + else if (!strcasecmp(value, "systemd") || + !strcasecmp(value, "systemd-timer")) + return SCHEDULER_SYSTEMD; + else if (!strcasecmp(value, "launchctl")) + return SCHEDULER_LAUNCHCTL; + else if (!strcasecmp(value, "schtasks")) + return SCHEDULER_SCHTASKS; + else + return SCHEDULER_INVALID; +} + +static int maintenance_opt_scheduler(const struct option *opt, const char *arg, + int unset) +{ + enum scheduler *scheduler = opt->value; + + BUG_ON_OPT_NEG(unset); + + *scheduler = parse_scheduler(arg); + if (*scheduler == SCHEDULER_INVALID) + return error(_("unrecognized --scheduler argument '%s'"), arg); + return 0; +} + +struct maintenance_start_opts { + enum scheduler scheduler; +}; + +static enum scheduler resolve_scheduler(enum scheduler scheduler) +{ + if (scheduler != SCHEDULER_AUTO) + return scheduler; + +#if defined(__APPLE__) + return SCHEDULER_LAUNCHCTL; + +#elif defined(GIT_WINDOWS_NATIVE) + return SCHEDULER_SCHTASKS; + +#elif defined(__linux__) + if (is_systemd_timer_available()) + return SCHEDULER_SYSTEMD; + else if (is_crontab_available()) + return SCHEDULER_CRON; + else + die(_("neither systemd timers nor crontab are available")); + +#else + return SCHEDULER_CRON; +#endif +} + +static void validate_scheduler(enum scheduler scheduler) +{ + if (scheduler == SCHEDULER_INVALID) + BUG("invalid scheduler"); + if (scheduler == SCHEDULER_AUTO) + BUG("resolve_scheduler should have been called before"); + + if (!scheduler_fn[scheduler].is_available()) + die(_("%s scheduler is not available"), + scheduler_fn[scheduler].name); +} + +static int update_background_schedule(const struct maintenance_start_opts *opts, + int enable) +{ + unsigned int i; + int result = 0; + struct lock_file lk; + char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path); + + if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) { + free(lock_path); + return error(_("another process is scheduling background maintenance")); + } + + for (i = 1; i < ARRAY_SIZE(scheduler_fn); i++) { + if (enable && opts->scheduler == i) + continue; + if (!scheduler_fn[i].is_available()) + continue; + scheduler_fn[i].update_schedule(0, get_lock_file_fd(&lk)); + } + + if (enable) + result = scheduler_fn[opts->scheduler].update_schedule( + 1, get_lock_file_fd(&lk)); + + rollback_lock_file(&lk); + + free(lock_path); + return result; +} + +static const char *const builtin_maintenance_start_usage[] = { + N_("git maintenance start [--scheduler=]"), + NULL +}; + +static int maintenance_start(int argc, const char **argv, const char *prefix) +{ + struct maintenance_start_opts opts = { 0 }; + struct option options[] = { + OPT_CALLBACK_F( + 0, "scheduler", &opts.scheduler, N_("scheduler"), + N_("scheduler to trigger git maintenance run"), + PARSE_OPT_NONEG, maintenance_opt_scheduler), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, + builtin_maintenance_start_usage, 0); + if (argc) + usage_with_options(builtin_maintenance_start_usage, options); + + opts.scheduler = resolve_scheduler(opts.scheduler); + validate_scheduler(opts.scheduler); + + if (maintenance_register()) + warning(_("failed to add repo to global config")); + return update_background_schedule(&opts, 1); +} + +static int maintenance_stop(void) +{ + return update_background_schedule(NULL, 0); +} + +static const char builtin_maintenance_usage[] = N_("git maintenance []"); + +int cmd_maintenance(int argc, const char **argv, const char *prefix) +{ + if (argc < 2 || + (argc == 2 && !strcmp(argv[1], "-h"))) + usage(builtin_maintenance_usage); + + if (!strcmp(argv[1], "run")) + return maintenance_run(argc - 1, argv + 1, prefix); + if (!strcmp(argv[1], "start")) + return maintenance_start(argc - 1, argv + 1, prefix); + if (!strcmp(argv[1], "stop")) + return maintenance_stop(); + if (!strcmp(argv[1], "register")) + return maintenance_register(); + if (!strcmp(argv[1], "unregister")) + return maintenance_unregister(); + + die(_("invalid subcommand: %s"), argv[1]); +} diff -Nru git-2.25.1/builtin/grep.c git-2.34.1/builtin/grep.c --- git-2.25.1/builtin/grep.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/grep.c 2021-11-24 19:29:21.000000000 +0000 @@ -24,6 +24,7 @@ #include "submodule.h" #include "submodule-config.h" #include "object-store.h" +#include "packfile.h" static char const * const grep_usage[] = { N_("git grep [] [-e] [...] [[--] ...]"), @@ -32,7 +33,6 @@ static int recurse_submodules; -#define GREP_NUM_THREADS_DEFAULT 8 static int num_threads; static pthread_t *threads; @@ -65,6 +65,9 @@ /* Has all work items been added? */ static int all_work_added; +static struct repository **repos_to_free; +static size_t repos_to_free_nr, repos_to_free_alloc; + /* This lock protects all the variables above. */ static pthread_mutex_t grep_mutex; @@ -91,8 +94,11 @@ static int skip_first_line; -static void add_work(struct grep_opt *opt, const struct grep_source *gs) +static void add_work(struct grep_opt *opt, struct grep_source *gs) { + if (opt->binary != GREP_BINARY_TEXT) + grep_source_load_driver(gs, opt->repo->index); + grep_lock(); while ((todo_end+1) % ARRAY_SIZE(todo) == todo_done) { @@ -100,9 +106,6 @@ } todo[todo_end].source = *gs; - if (opt->binary != GREP_BINARY_TEXT) - grep_source_load_driver(&todo[todo_end].source, - opt->repo->index); todo[todo_end].done = 0; strbuf_reset(&todo[todo_end].out); todo_end = (todo_end + 1) % ARRAY_SIZE(todo); @@ -168,6 +171,19 @@ grep_unlock(); } +static void free_repos(void) +{ + int i; + + for (i = 0; i < repos_to_free_nr; i++) { + repo_clear(repos_to_free[i]); + free(repos_to_free[i]); + } + FREE_AND_NULL(repos_to_free); + repos_to_free_nr = 0; + repos_to_free_alloc = 0; +} + static void *run(void *arg) { int hit = 0; @@ -183,8 +199,8 @@ grep_source_clear_data(&w->source); work_done(w); } - free_grep_patterns(arg); - free(arg); + free_grep_patterns(opt); + free(opt); return (void*) (intptr_t) hit; } @@ -200,24 +216,22 @@ int i; pthread_mutex_init(&grep_mutex, NULL); - pthread_mutex_init(&grep_read_mutex, NULL); pthread_mutex_init(&grep_attr_mutex, NULL); pthread_cond_init(&cond_add, NULL); pthread_cond_init(&cond_write, NULL); pthread_cond_init(&cond_result, NULL); grep_use_locks = 1; + enable_obj_read_lock(); for (i = 0; i < ARRAY_SIZE(todo); i++) { strbuf_init(&todo[i].out, 0); } - threads = xcalloc(num_threads, sizeof(*threads)); + CALLOC_ARRAY(threads, num_threads); for (i = 0; i < num_threads; i++) { int err; struct grep_opt *o = grep_opt_dup(opt); o->output = strbuf_out; - if (i) - o->debug = 0; compile_grep_patterns(o); err = pthread_create(&threads[i], NULL, run, o); @@ -257,12 +271,12 @@ free(threads); pthread_mutex_destroy(&grep_mutex); - pthread_mutex_destroy(&grep_read_mutex); pthread_mutex_destroy(&grep_attr_mutex); pthread_cond_destroy(&cond_add); pthread_cond_destroy(&cond_write); pthread_cond_destroy(&cond_result); grep_use_locks = 0; + disable_obj_read_lock(); return hit; } @@ -295,14 +309,36 @@ return st; } -static void *lock_and_read_oid_file(const struct object_id *oid, enum object_type *type, unsigned long *size) +static void grep_source_name(struct grep_opt *opt, const char *filename, + int tree_name_len, struct strbuf *out) { - void *data; + strbuf_reset(out); + + if (opt->null_following_name) { + if (opt->relative && opt->prefix_length) { + struct strbuf rel_buf = STRBUF_INIT; + const char *rel_name = + relative_path(filename + tree_name_len, + opt->prefix, &rel_buf); + + if (tree_name_len) + strbuf_add(out, filename, tree_name_len); + + strbuf_addstr(out, rel_name); + strbuf_release(&rel_buf); + } else { + strbuf_addstr(out, filename); + } + return; + } - grep_read_lock(); - data = read_object_file(oid, type, size); - grep_read_unlock(); - return data; + if (opt->relative && opt->prefix_length) + quote_path(filename + tree_name_len, opt->prefix, out, 0); + else + quote_c_style(filename + tree_name_len, out, NULL, 0); + + if (tree_name_len) + strbuf_insert(out, 0, filename, tree_name_len); } static int grep_oid(struct grep_opt *opt, const struct object_id *oid, @@ -312,14 +348,8 @@ struct strbuf pathbuf = STRBUF_INIT; struct grep_source gs; - if (opt->relative && opt->prefix_length) { - quote_path_relative(filename + tree_name_len, opt->prefix, &pathbuf); - strbuf_insert(&pathbuf, 0, filename, tree_name_len); - } else { - strbuf_addstr(&pathbuf, filename); - } - - grep_source_init(&gs, GREP_SOURCE_OID, pathbuf.buf, path, oid); + grep_source_name(opt, filename, tree_name_len, &pathbuf); + grep_source_init_oid(&gs, pathbuf.buf, path, oid, opt->repo); strbuf_release(&pathbuf); if (num_threads > 1) { @@ -344,12 +374,8 @@ struct strbuf buf = STRBUF_INIT; struct grep_source gs; - if (opt->relative && opt->prefix_length) - quote_path_relative(filename, opt->prefix, &buf); - else - strbuf_addstr(&buf, filename); - - grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename, filename); + grep_source_name(opt, filename, 0, &buf); + grep_source_init_file(&gs, buf.buf, filename); strbuf_release(&buf); if (num_threads > 1) { @@ -375,7 +401,7 @@ if (len == 1 && *(const char *)data == '\0') return; - string_list_append(path_list, xstrndup(data, len)); + string_list_append_nodup(path_list, xstrndup(data, len)); } static void run_pager(struct grep_opt *opt, const char *prefix) @@ -385,7 +411,7 @@ int i, status; for (i = 0; i < path_list->nr; i++) - argv_array_push(&child.args, path_list->items[i].string); + strvec_push(&child.args, path_list->items[i].string); child.dir = prefix; child.use_shell = 1; @@ -405,80 +431,74 @@ const struct object_id *oid, const char *filename, const char *path, int cached) { - struct repository subrepo; + struct repository *subrepo; struct repository *superproject = opt->repo; - const struct submodule *sub = submodule_from_path(superproject, - &null_oid, path); struct grep_opt subopt; - int hit; - - /* - * NEEDSWORK: submodules functions need to be protected because they - * access the object store via config_from_gitmodules(): the latter - * uses get_oid() which, for now, relies on the global the_repository - * object. - */ - grep_read_lock(); + int hit = 0; - if (!is_submodule_active(superproject, path)) { - grep_read_unlock(); + if (!is_submodule_active(superproject, path)) return 0; - } - if (repo_submodule_init(&subrepo, superproject, sub)) { - grep_read_unlock(); + subrepo = xmalloc(sizeof(*subrepo)); + if (repo_submodule_init(subrepo, superproject, path, null_oid())) { + free(subrepo); return 0; } + ALLOC_GROW(repos_to_free, repos_to_free_nr + 1, repos_to_free_alloc); + repos_to_free[repos_to_free_nr++] = subrepo; - repo_read_gitmodules(&subrepo); + /* + * NEEDSWORK: repo_read_gitmodules() might call + * add_to_alternates_memory() via config_from_gitmodules(). This + * operation causes a race condition with concurrent object readings + * performed by the worker threads. That's why we need obj_read_lock() + * here. It should be removed once it's no longer necessary to add the + * subrepo's odbs to the in-memory alternates list. + */ + obj_read_lock(); + repo_read_gitmodules(subrepo, 0); /* - * NEEDSWORK: This adds the submodule's object directory to the list of - * alternates for the single in-memory object store. This has some bad - * consequences for memory (processed objects will never be freed) and - * performance (this increases the number of pack files git has to pay - * attention to, to the sum of the number of pack files in all the - * repositories processed so far). This can be removed once the object - * store is no longer global and instead is a member of the repository - * object. + * All code paths tested by test code no longer need submodule ODBs to + * be added as alternates, but add it to the list just in case. + * Submodule ODBs added through add_submodule_odb_by_path() will be + * lazily registered as alternates when needed (and except in an + * unexpected code interaction, it won't be needed). */ - add_to_alternates_memory(subrepo.objects->odb->path); - grep_read_unlock(); + add_submodule_odb_by_path(subrepo->objects->odb->path); + obj_read_unlock(); memcpy(&subopt, opt, sizeof(subopt)); - subopt.repo = &subrepo; + subopt.repo = subrepo; if (oid) { - struct object *object; + enum object_type object_type; struct tree_desc tree; void *data; unsigned long size; struct strbuf base = STRBUF_INIT; - object = parse_object_or_die(oid, oid_to_hex(oid)); - - grep_read_lock(); - data = read_object_with_reference(&subrepo, - &object->oid, tree_type, + obj_read_lock(); + object_type = oid_object_info(subrepo, oid, NULL); + obj_read_unlock(); + data = read_object_with_reference(subrepo, + oid, tree_type, &size, NULL); - grep_read_unlock(); - if (!data) - die(_("unable to read tree (%s)"), oid_to_hex(&object->oid)); + die(_("unable to read tree (%s)"), oid_to_hex(oid)); strbuf_addstr(&base, filename); strbuf_addch(&base, '/'); init_tree_desc(&tree, data, size); hit = grep_tree(&subopt, pathspec, &tree, &base, base.len, - object->type == OBJ_COMMIT); + object_type == OBJ_COMMIT); strbuf_release(&base); free(data); } else { hit = grep_cache(&subopt, pathspec, cached); } - repo_clear(&subrepo); return hit; } @@ -498,8 +518,14 @@ if (repo_read_index(repo) < 0) die(_("index file corrupt")); + /* TODO: audit for interaction with sparse-index. */ + ensure_full_index(repo->index); for (nr = 0; nr < repo->index->cache_nr; nr++) { const struct cache_entry *ce = repo->index->cache[nr]; + + if (!cached && ce_skip_worktree(ce)) + continue; + strbuf_setlen(&name, name_base_len); strbuf_addstr(&name, ce->name); @@ -512,8 +538,7 @@ * cache entry are identical, even if worktree file has * been modified, so use cache version instead */ - if (cached || (ce->ce_flags & CE_VALID) || - ce_skip_worktree(ce)) { + if (cached || (ce->ce_flags & CE_VALID)) { if (ce_stage(ce) || ce_intent_to_add(ce)) continue; hit |= grep_oid(opt, &ce->oid, name.buf, @@ -587,7 +612,7 @@ void *data; unsigned long size; - data = lock_and_read_oid_file(&entry.oid, &type, &size); + data = read_object_file(&entry.oid, &type, &size); if (!data) die(_("unable to read tree (%s)"), oid_to_hex(&entry.oid)); @@ -625,12 +650,9 @@ struct strbuf base; int hit, len; - grep_read_lock(); data = read_object_with_reference(opt->repo, &obj->oid, tree_type, &size, NULL); - grep_read_unlock(); - if (!data) die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid)); @@ -659,13 +681,29 @@ for (i = 0; i < nr; i++) { struct object *real_obj; + + obj_read_lock(); real_obj = deref_tag(opt->repo, list->objects[i].item, NULL, 0); + obj_read_unlock(); + + if (!real_obj) { + char hex[GIT_MAX_HEXSZ + 1]; + const char *name = list->objects[i].name; + + if (!name) { + oid_to_hex_r(hex, &list->objects[i].item->oid); + name = hex; + } + die(_("invalid object '%s' given."), name); + } /* load the gitmodules file for this rev */ if (recurse_submodules) { submodule_free(opt->repo); + obj_read_lock(); gitmodules_config_oid(&real_obj->oid); + obj_read_unlock(); } if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].path)) { @@ -680,10 +718,9 @@ static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec, int exc_std, int use_index) { - struct dir_struct dir; + struct dir_struct dir = DIR_INIT; int i, hit = 0; - memset(&dir, 0, sizeof(dir)); if (!use_index) dir.flags |= DIR_NO_GITLINKS; if (exc_std) @@ -691,12 +728,11 @@ fill_directory(&dir, opt->repo->index, pathspec); for (i = 0; i < dir.nr; i++) { - if (!dir_path_match(opt->repo->index, dir.entries[i], pathspec, 0, NULL)) - continue; hit |= grep_file(opt, dir.entries[i]->name); if (hit && opt->status_only) break; } + dir_clear(&dir); return hit; } @@ -803,7 +839,7 @@ struct grep_opt opt; struct object_array list = OBJECT_ARRAY_INIT; struct pathspec pathspec; - struct string_list path_list = STRING_LIST_INIT_NODUP; + struct string_list path_list = STRING_LIST_INIT_DUP; int i; int dummy; int use_index = 1; @@ -898,27 +934,24 @@ OPT_GROUP(""), OPT_CALLBACK('f', NULL, &opt, N_("file"), N_("read patterns from file"), file_callback), - { OPTION_CALLBACK, 'e', NULL, &opt, N_("pattern"), - N_("match "), PARSE_OPT_NONEG, pattern_callback }, - { OPTION_CALLBACK, 0, "and", &opt, NULL, - N_("combine patterns specified with -e"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback }, + OPT_CALLBACK_F('e', NULL, &opt, N_("pattern"), + N_("match "), PARSE_OPT_NONEG, pattern_callback), + OPT_CALLBACK_F(0, "and", &opt, NULL, + N_("combine patterns specified with -e"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, and_callback), OPT_BOOL(0, "or", &dummy, ""), - { OPTION_CALLBACK, 0, "not", &opt, NULL, "", - PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback }, - { OPTION_CALLBACK, '(', NULL, &opt, NULL, "", - PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, - open_callback }, - { OPTION_CALLBACK, ')', NULL, &opt, NULL, "", - PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, - close_callback }, + OPT_CALLBACK_F(0, "not", &opt, NULL, "", + PARSE_OPT_NOARG | PARSE_OPT_NONEG, not_callback), + OPT_CALLBACK_F('(', NULL, &opt, NULL, "", + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, + open_callback), + OPT_CALLBACK_F(')', NULL, &opt, NULL, "", + PARSE_OPT_NOARG | PARSE_OPT_NONEG | PARSE_OPT_NODASH, + close_callback), OPT__QUIET(&opt.status_only, N_("indicate hit with exit status without output")), OPT_BOOL(0, "all-match", &opt.all_match, N_("show only matches from files that match all patterns")), - OPT_SET_INT_F(0, "debug", &opt.debug, - N_("show parse tree for grep expression"), - 1, PARSE_OPT_HIDDEN), OPT_GROUP(""), { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager, N_("pager"), N_("show matching files in the pager"), @@ -930,7 +963,6 @@ OPT_END() }; - init_grep_defaults(the_repository); git_config(grep_cmd_config, NULL); grep_init(&opt, the_repository, prefix); @@ -1065,7 +1097,10 @@ pathspec.recursive = 1; pathspec.recurse_submodules = !!recurse_submodules; - if (list.nr || cached || show_in_pager) { + if (recurse_submodules && untracked) + die(_("--untracked not supported with --recurse-submodules")); + + if (show_in_pager) { if (num_threads > 1) warning(_("invalid option combination, ignoring --threads")); num_threads = 1; @@ -1075,7 +1110,7 @@ } else if (num_threads < 0) die(_("invalid number of threads specified (%d)"), num_threads); else if (num_threads == 0) - num_threads = HAVE_THREADS ? GREP_NUM_THREADS_DEFAULT : 1; + num_threads = HAVE_THREADS ? online_cpus() : 1; if (num_threads > 1) { if (!HAVE_THREADS) @@ -1084,6 +1119,17 @@ && (opt.pre_context || opt.post_context || opt.file_break || opt.funcbody)) skip_first_line = 1; + + /* + * Pre-read gitmodules (if not read already) and force eager + * initialization of packed_git to prevent racy lazy + * reading/initialization once worker threads are started. + */ + if (recurse_submodules) + repo_read_gitmodules(the_repository, 1); + if (startup_info->have_repository) + (void)get_packed_git(the_repository); + start_threads(&opt); } else { /* @@ -1113,20 +1159,20 @@ strbuf_addf(&buf, "+/%s%s", strcmp("less", pager) ? "" : "*", opt.pattern_list->pattern); - string_list_append(&path_list, - strbuf_detach(&buf, NULL)); + string_list_append_nodup(&path_list, + strbuf_detach(&buf, NULL)); } } - if (recurse_submodules && untracked) - die(_("--untracked not supported with --recurse-submodules")); - if (!show_in_pager && !opt.status_only) setup_pager(); if (!use_index && (untracked || cached)) die(_("--cached or --untracked cannot be used with --no-index")); + if (untracked && cached) + die(_("--untracked cannot be used with --cached")); + if (!use_index || untracked) { int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude; hit = grep_directory(&opt, &pathspec, use_exclude, use_index); @@ -1149,7 +1195,9 @@ if (hit && show_in_pager) run_pager(&opt, prefix); clear_pathspec(&pathspec); + string_list_clear(&path_list, 0); free_grep_patterns(&opt); - grep_destroy(); + object_array_clear(&list); + free_repos(); return !hit; } diff -Nru git-2.25.1/builtin/hash-object.c git-2.34.1/builtin/hash-object.c --- git-2.25.1/builtin/hash-object.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/hash-object.c 2021-11-24 19:29:21.000000000 +0000 @@ -53,9 +53,7 @@ unsigned flags, int literally) { int fd; - fd = open(path, O_RDONLY); - if (fd < 0) - die_errno("Cannot open '%s'", path); + fd = xopen(path, O_RDONLY); hash_fd(fd, type, vpath, flags, literally); } @@ -117,7 +115,7 @@ prefix = setup_git_directory_gently(&nongit); if (vpath && prefix) - vpath = xstrdup(prefix_filename(prefix, vpath)); + vpath = prefix_filename(prefix, vpath); git_config(git_default_config, NULL); diff -Nru git-2.25.1/builtin/help.c git-2.34.1/builtin/help.c --- git-2.25.1/builtin/help.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/help.c 2021-11-24 19:29:21.000000000 +0000 @@ -7,7 +7,7 @@ #include "exec-cmd.h" #include "parse-options.h" #include "run-command.h" -#include "column.h" +#include "config-list.h" #include "help.h" #include "alias.h" @@ -33,35 +33,154 @@ HELP_FORMAT_WEB }; -static const char *html_path; +enum show_config_type { + SHOW_CONFIG_HUMAN, + SHOW_CONFIG_VARS, + SHOW_CONFIG_SECTIONS, +}; + +static enum help_action { + HELP_ACTION_ALL = 1, + HELP_ACTION_GUIDES, + HELP_ACTION_CONFIG, + HELP_ACTION_CONFIG_FOR_COMPLETION, + HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, +} cmd_mode; -static int show_all = 0; -static int show_guides = 0; -static int show_config; +static const char *html_path; static int verbose = 1; -static unsigned int colopts; static enum help_format help_format = HELP_FORMAT_NONE; static int exclude_guides; static struct option builtin_help_options[] = { - OPT_BOOL('a', "all", &show_all, N_("print all available commands")), + OPT_CMDMODE('a', "all", &cmd_mode, N_("print all available commands"), + HELP_ACTION_ALL), OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")), - OPT_BOOL('g', "guides", &show_guides, N_("print list of useful guides")), - OPT_BOOL('c', "config", &show_config, N_("print all configuration variable names")), - OPT_SET_INT_F(0, "config-for-completion", &show_config, "", 2, PARSE_OPT_HIDDEN), OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN), OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"), HELP_FORMAT_WEB), OPT_SET_INT('i', "info", &help_format, N_("show info page"), HELP_FORMAT_INFO), OPT__VERBOSE(&verbose, N_("print command description")), + + OPT_CMDMODE('g', "guides", &cmd_mode, N_("print list of useful guides"), + HELP_ACTION_GUIDES), + OPT_CMDMODE('c', "config", &cmd_mode, N_("print all configuration variable names"), + HELP_ACTION_CONFIG), + OPT_CMDMODE_F(0, "config-for-completion", &cmd_mode, "", + HELP_ACTION_CONFIG_FOR_COMPLETION, PARSE_OPT_HIDDEN), + OPT_CMDMODE_F(0, "config-sections-for-completion", &cmd_mode, "", + HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION, PARSE_OPT_HIDDEN), + OPT_END(), }; static const char * const builtin_help_usage[] = { - N_("git help [--all] [--guides] [--man | --web | --info] []"), + N_("git help [-a|--all] [--[no-]verbose]]\n" + " [[-i|--info] [-m|--man] [-w|--web]] []"), + N_("git help [-g|--guides]"), + N_("git help [-c|--config]"), NULL }; +struct slot_expansion { + const char *prefix; + const char *placeholder; + void (*fn)(struct string_list *list, const char *prefix); + int found; +}; + +static void list_config_help(enum show_config_type type) +{ + struct slot_expansion slot_expansions[] = { + { "advice", "*", list_config_advices }, + { "color.branch", "", list_config_color_branch_slots }, + { "color.decorate", "", list_config_color_decorate_slots }, + { "color.diff", "", list_config_color_diff_slots }, + { "color.grep", "", list_config_color_grep_slots }, + { "color.interactive", "", list_config_color_interactive_slots }, + { "color.remote", "", list_config_color_sideband_slots }, + { "color.status", "", list_config_color_status_slots }, + { "fsck", "", list_config_fsck_msg_ids }, + { "receive.fsck", "", list_config_fsck_msg_ids }, + { NULL, NULL, NULL } + }; + const char **p; + struct slot_expansion *e; + struct string_list keys = STRING_LIST_INIT_DUP; + struct string_list keys_uniq = STRING_LIST_INIT_DUP; + struct string_list_item *item; + int i; + + for (p = config_name_list; *p; p++) { + const char *var = *p; + struct strbuf sb = STRBUF_INIT; + + for (e = slot_expansions; e->prefix; e++) { + + strbuf_reset(&sb); + strbuf_addf(&sb, "%s.%s", e->prefix, e->placeholder); + if (!strcasecmp(var, sb.buf)) { + e->fn(&keys, e->prefix); + e->found++; + break; + } + } + strbuf_release(&sb); + if (!e->prefix) + string_list_append(&keys, var); + } + + for (e = slot_expansions; e->prefix; e++) + if (!e->found) + BUG("slot_expansion %s.%s is not used", + e->prefix, e->placeholder); + + string_list_sort(&keys); + for (i = 0; i < keys.nr; i++) { + const char *var = keys.items[i].string; + const char *wildcard, *tag, *cut; + const char *dot = NULL; + struct strbuf sb = STRBUF_INIT; + + switch (type) { + case SHOW_CONFIG_HUMAN: + puts(var); + continue; + case SHOW_CONFIG_SECTIONS: + dot = strchr(var, '.'); + break; + case SHOW_CONFIG_VARS: + break; + } + wildcard = strchr(var, '*'); + tag = strchr(var, '<'); + + if (!dot && !wildcard && !tag) { + string_list_append(&keys_uniq, var); + continue; + } + + if (dot) + cut = dot; + else if (wildcard && !tag) + cut = wildcard; + else if (!wildcard && tag) + cut = tag; + else + cut = wildcard < tag ? wildcard : tag; + + strbuf_add(&sb, var, cut - var); + string_list_append(&keys_uniq, sb.buf); + strbuf_release(&sb); + + } + string_list_clear(&keys, 0); + string_list_remove_duplicates(&keys_uniq, 0); + for_each_string_list_item(item, &keys_uniq) + puts(item->string); + string_list_clear(&keys_uniq, 0); +} + static enum help_format parse_help_format(const char *format) { if (!strcmp(format, "man")) @@ -242,7 +361,7 @@ static int add_man_viewer_info(const char *var, const char *value) { const char *name, *subkey; - int namelen; + size_t namelen; if (parse_config_key(var, "man", &name, &namelen, &subkey) < 0 || !name) return 0; @@ -263,8 +382,6 @@ static int git_help_config(const char *var, const char *value, void *cb) { - if (starts_with(var, "column.")) - return git_column_config(var, value, "help", &colopts); if (!strcmp(var, "help.format")) { if (!value) return config_error_nonbool(var); @@ -350,10 +467,9 @@ warning(_("'%s': unknown man viewer."), name); } -static void show_man_page(const char *git_cmd) +static void show_man_page(const char *page) { struct man_viewer_list *viewer; - const char *page = cmd_to_page(git_cmd); const char *fallback = getenv("GIT_MAN_VIEWER"); setup_man_path(); @@ -367,9 +483,8 @@ die(_("no man viewer handled the request")); } -static void show_info_page(const char *git_cmd) +static void show_info_page(const char *page) { - const char *page = cmd_to_page(git_cmd); setenv("INFOPATH", system_path(GIT_INFO_PATH), 1); execlp("info", "info", "gitman", page, (char *)NULL); die(_("no info viewer handled the request")); @@ -383,11 +498,14 @@ if (!html_path) html_path = to_free = system_path(GIT_HTML_PATH); - /* Check that we have a git documentation directory. */ + /* + * Check that the page we're looking for exists. + */ if (!strstr(html_path, "://")) { - if (stat(mkpath("%s/git.html", html_path), &st) + if (stat(mkpath("%s/%s.html", html_path, page), &st) || !S_ISREG(st.st_mode)) - die("'%s': not a documentation directory.", html_path); + die("'%s/%s.html': documentation file not found.", + html_path, page); } strbuf_init(page_path, 0); @@ -400,9 +518,8 @@ execl_git_cmd("web--browse", "-c", "help.browser", path, (char *)NULL); } -static void show_html_page(const char *git_cmd) +static void show_html_page(const char *page) { - const char *page = cmd_to_page(git_cmd); struct strbuf page_path; /* it leaks but we exec bellow */ get_html_page_path(&page_path, page); @@ -458,17 +575,25 @@ return cmd; } +static void no_extra_argc(int argc) +{ + if (argc) + usage_msg_opt(_("this option doesn't take any other arguments"), + builtin_help_usage, builtin_help_options); +} + int cmd_help(int argc, const char **argv, const char *prefix) { int nongit; enum help_format parsed_help_format; + const char *page; argc = parse_options(argc, argv, prefix, builtin_help_options, builtin_help_usage, 0); parsed_help_format = help_format; - if (show_all) { - git_config(git_help_config, NULL); + switch (cmd_mode) { + case HELP_ACTION_ALL: if (verbose) { setup_pager(); list_all_cmds_help(); @@ -476,33 +601,30 @@ } printf(_("usage: %s%s"), _(git_usage_string), "\n\n"); load_command_list("git-", &main_cmds, &other_cmds); - list_commands(colopts, &main_cmds, &other_cmds); - } - - if (show_config) { - int for_human = show_config == 1; - - if (!for_human) { - list_config_help(for_human); - return 0; - } + list_commands(&main_cmds, &other_cmds); + printf("%s\n", _(git_more_info_string)); + break; + case HELP_ACTION_GUIDES: + no_extra_argc(argc); + list_guides_help(); + printf("%s\n", _(git_more_info_string)); + return 0; + case HELP_ACTION_CONFIG_FOR_COMPLETION: + no_extra_argc(argc); + list_config_help(SHOW_CONFIG_VARS); + return 0; + case HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION: + no_extra_argc(argc); + list_config_help(SHOW_CONFIG_SECTIONS); + return 0; + case HELP_ACTION_CONFIG: + no_extra_argc(argc); setup_pager(); - list_config_help(for_human); + list_config_help(SHOW_CONFIG_HUMAN); printf("\n%s\n", _("'git help config' for more information")); return 0; } - if (show_guides) - list_common_guides_help(); - - if (show_all || show_guides) { - printf("%s\n", _(git_more_info_string)); - /* - * We're done. Ignore any remaining args - */ - return 0; - } - if (!argv[0]) { printf(_("usage: %s%s"), _(git_usage_string), "\n\n"); list_common_cmds_help(); @@ -520,16 +642,17 @@ argv[0] = check_git_cmd(argv[0]); + page = cmd_to_page(argv[0]); switch (help_format) { case HELP_FORMAT_NONE: case HELP_FORMAT_MAN: - show_man_page(argv[0]); + show_man_page(page); break; case HELP_FORMAT_INFO: - show_info_page(argv[0]); + show_info_page(page); break; case HELP_FORMAT_WEB: - show_html_page(argv[0]); + show_html_page(page); break; } diff -Nru git-2.25.1/builtin/index-pack.c git-2.34.1/builtin/index-pack.c --- git-2.25.1/builtin/index-pack.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/index-pack.c 2021-11-24 19:29:21.000000000 +0000 @@ -17,7 +17,7 @@ #include "promisor-remote.h" static const char index_pack_usage[] = -"git index-pack [-v] [-o ] [--keep | --keep=] [--verify] [--strict] ( | --stdin [--fix-thin] [])"; +"git index-pack [-v] [-o ] [--keep | --keep=] [--[no-]rev-index] [--verify] [--strict] ( | --stdin [--fix-thin] [])"; struct object_entry { struct pack_idx_entry idx; @@ -33,19 +33,61 @@ }; struct base_data { + /* Initialized by make_base(). */ struct base_data *base; - struct base_data *child; struct object_entry *obj; - void *data; - unsigned long size; int ref_first, ref_last; int ofs_first, ofs_last; + /* + * Threads should increment retain_data if they are about to call + * patch_delta() using this struct's data as a base, and decrement this + * when they are done. While retain_data is nonzero, this struct's data + * will not be freed even if the delta base cache limit is exceeded. + */ + int retain_data; + /* + * The number of direct children that have not been fully processed + * (entered work_head, entered done_head, left done_head). When this + * number reaches zero, this struct base_data can be freed. + */ + int children_remaining; + + /* Not initialized by make_base(). */ + struct list_head list; + void *data; + unsigned long size; }; +/* + * Stack of struct base_data that have unprocessed children. + * threaded_second_pass() uses this as a source of work (the other being the + * objects array). + * + * Guarded by work_mutex. + */ +static LIST_HEAD(work_head); + +/* + * Stack of struct base_data that have children, all of whom have been + * processed or are being processed, and at least one child is being processed. + * These struct base_data must be kept around until the last child is + * processed. + * + * Guarded by work_mutex. + */ +static LIST_HEAD(done_head); + +/* + * All threads share one delta base cache. + * + * base_cache_used is guarded by work_mutex, and base_cache_limit is read-only + * in a thread. + */ +static size_t base_cache_used; +static size_t base_cache_limit; + struct thread_local { pthread_t thread; - struct base_data *base_cache; - size_t base_cache_used; int pack_fd; }; @@ -78,8 +120,9 @@ static int from_stdin; static int strict; static int do_fsck_object; -static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; +static struct fsck_options fsck_options = FSCK_OPTIONS_MISSING_GITMODULES; static int verbose; +static const char *progress_title; static int show_resolving_progress; static int show_stat; static int check_self_contained_and_connected; @@ -117,10 +160,6 @@ #define deepest_delta_lock() lock_mutex(&deepest_delta_mutex) #define deepest_delta_unlock() unlock_mutex(&deepest_delta_mutex) -static pthread_mutex_t type_cas_mutex; -#define type_cas_lock() lock_mutex(&type_cas_mutex) -#define type_cas_unlock() unlock_mutex(&type_cas_mutex) - static pthread_key_t key; static inline void lock_mutex(pthread_mutex_t *mutex) @@ -144,15 +183,12 @@ init_recursive_mutex(&read_mutex); pthread_mutex_init(&counter_mutex, NULL); pthread_mutex_init(&work_mutex, NULL); - pthread_mutex_init(&type_cas_mutex, NULL); if (show_stat) pthread_mutex_init(&deepest_delta_mutex, NULL); pthread_key_create(&key, NULL); - thread_data = xcalloc(nr_threads, sizeof(*thread_data)); + CALLOC_ARRAY(thread_data, nr_threads); for (i = 0; i < nr_threads; i++) { - thread_data[i].pack_fd = open(curr_pack, O_RDONLY); - if (thread_data[i].pack_fd == -1) - die_errno(_("unable to open %s"), curr_pack); + thread_data[i].pack_fd = xopen(curr_pack, O_RDONLY); } threads_active = 1; @@ -167,7 +203,6 @@ pthread_mutex_destroy(&read_mutex); pthread_mutex_destroy(&counter_mutex); pthread_mutex_destroy(&work_mutex); - pthread_mutex_destroy(&type_cas_mutex); if (show_stat) pthread_mutex_destroy(&deepest_delta_mutex); for (i = 0; i < nr_threads; i++) @@ -176,7 +211,8 @@ free(thread_data); } -static int mark_link(struct object *obj, int type, void *data, struct fsck_options *options) +static int mark_link(struct object *obj, enum object_type type, + void *data, struct fsck_options *options) { if (!obj) return -1; @@ -301,15 +337,11 @@ "pack/tmp_pack_XXXXXX"); pack_name = strbuf_detach(&tmp_file, NULL); } else { - output_fd = open(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600); - if (output_fd < 0) - die_errno(_("unable to create '%s'"), pack_name); + output_fd = xopen(pack_name, O_CREAT|O_EXCL|O_RDWR, 0600); } nothread_data.pack_fd = output_fd; } else { - input_fd = open(pack_name, O_RDONLY); - if (input_fd < 0) - die_errno(_("cannot open packfile '%s'"), pack_name); + input_fd = xopen(pack_name, O_RDONLY); output_fd = -1; nothread_data.pack_fd = input_fd; } @@ -332,9 +364,7 @@ use(sizeof(struct pack_header)); } -static NORETURN void bad_object(off_t offset, const char *format, - ...) __attribute__((format (printf, 2, 3))); - +__attribute__((format (printf, 2, 3))) static NORETURN void bad_object(off_t offset, const char *format, ...) { va_list params; @@ -364,56 +394,42 @@ pthread_setspecific(key, data); } -static struct base_data *alloc_base_data(void) -{ - struct base_data *base = xcalloc(1, sizeof(struct base_data)); - base->ref_last = -1; - base->ofs_last = -1; - return base; -} - static void free_base_data(struct base_data *c) { if (c->data) { FREE_AND_NULL(c->data); - get_thread_data()->base_cache_used -= c->size; + base_cache_used -= c->size; } } static void prune_base_data(struct base_data *retain) { - struct base_data *b; - struct thread_local *data = get_thread_data(); - for (b = data->base_cache; - data->base_cache_used > delta_base_cache_limit && b; - b = b->child) { - if (b->data && b != retain) - free_base_data(b); - } -} + struct list_head *pos; -static void link_base_data(struct base_data *base, struct base_data *c) -{ - if (base) - base->child = c; - else - get_thread_data()->base_cache = c; + if (base_cache_used <= base_cache_limit) + return; - c->base = base; - c->child = NULL; - if (c->data) - get_thread_data()->base_cache_used += c->size; - prune_base_data(c); -} + list_for_each_prev(pos, &done_head) { + struct base_data *b = list_entry(pos, struct base_data, list); + if (b->retain_data || b == retain) + continue; + if (b->data) { + free_base_data(b); + if (base_cache_used <= base_cache_limit) + return; + } + } -static void unlink_base_data(struct base_data *c) -{ - struct base_data *base = c->base; - if (base) - base->child = NULL; - else - get_thread_data()->base_cache = NULL; - free_base_data(c); + list_for_each_prev(pos, &work_head) { + struct base_data *b = list_entry(pos, struct base_data, list); + if (b->retain_data || b == retain) + continue; + if (b->data) { + free_base_data(b); + if (base_cache_used <= base_cache_limit) + return; + } + } } static int is_delta_type(enum object_type type) @@ -466,7 +482,7 @@ bad_object(offset, _("inflate returned %d"), status); git_inflate_end(&stream); if (oid) - the_hash_algo->final_fn(oid->hash, &c); + the_hash_algo->final_oid_fn(oid, &c); return buf == fixed_buf ? NULL : buf; } @@ -501,7 +517,7 @@ switch (obj->type) { case OBJ_REF_DELTA: - hashcpy(ref_oid->hash, fill(the_hash_algo->rawsz)); + oidread(ref_oid, fill(the_hash_algo->rawsz)); use(the_hash_algo->rawsz); break; case OBJ_OFS_DELTA: @@ -614,7 +630,7 @@ 0; } -static int find_ofs_delta(const off_t offset, enum object_type type) +static int find_ofs_delta(const off_t offset) { int first = 0, last = nr_ofs_deltas; @@ -624,7 +640,8 @@ int cmp; cmp = compare_ofs_delta_bases(offset, delta->offset, - type, objects[delta->obj_no].type); + OBJ_OFS_DELTA, + objects[delta->obj_no].type); if (!cmp) return next; if (cmp < 0) { @@ -637,10 +654,9 @@ } static void find_ofs_delta_children(off_t offset, - int *first_index, int *last_index, - enum object_type type) + int *first_index, int *last_index) { - int first = find_ofs_delta(offset, type); + int first = find_ofs_delta(offset); int last = first; int end = nr_ofs_deltas - 1; @@ -668,7 +684,7 @@ return oidcmp(oid1, oid2); } -static int find_ref_delta(const struct object_id *oid, enum object_type type) +static int find_ref_delta(const struct object_id *oid) { int first = 0, last = nr_ref_deltas; @@ -678,7 +694,8 @@ int cmp; cmp = compare_ref_delta_bases(oid, &delta->oid, - type, objects[delta->obj_no].type); + OBJ_REF_DELTA, + objects[delta->obj_no].type); if (!cmp) return next; if (cmp < 0) { @@ -691,10 +708,9 @@ } static void find_ref_delta_children(const struct object_id *oid, - int *first_index, int *last_index, - enum object_type type) + int *first_index, int *last_index) { - int first = find_ref_delta(oid, type); + int first = find_ref_delta(oid); int last = first; int end = nr_ref_deltas - 1; @@ -757,7 +773,8 @@ memset(&data, 0, sizeof(data)); data.entry = entry; - data.st = open_istream(&entry->idx.oid, &type, &size, NULL); + data.st = open_istream(the_repository, &entry->idx.oid, &type, &size, + NULL); if (!data.st) return -1; if (size != entry->size || type != entry->type) @@ -865,26 +882,15 @@ } /* - * This function is part of find_unresolved_deltas(). There are two - * walkers going in the opposite ways. - * - * The first one in find_unresolved_deltas() traverses down from - * parent node to children, deflating nodes along the way. However, - * memory for deflated nodes is limited by delta_base_cache_limit, so - * at some point parent node's deflated content may be freed. - * - * The second walker is this function, which goes from current node up - * to top parent if necessary to deflate the node. In normal - * situation, its parent node would be already deflated, so it just - * needs to apply delta. + * Ensure that this node has been reconstructed and return its contents. * - * In the worst case scenario, parent node is no longer deflated because - * we're running out of delta_base_cache_limit; we need to re-deflate - * parents, possibly up to the top base. - * - * All deflated objects here are subject to be freed if we exceed - * delta_base_cache_limit, just like in find_unresolved_deltas(), we - * just need to make sure the last node is not freed. + * In the typical and best case, this node would already be reconstructed + * (through the invocation to resolve_delta() in threaded_second_pass()) and it + * would not be pruned. However, if pruning of this node was necessary due to + * reaching delta_base_cache_limit, this function will find the closest + * ancestor with reconstructed data that has not been pruned (or if there is + * none, the ultimate base object), and reconstruct each node in the delta + * chain in order to generate the reconstructed data for this node. */ static void *get_base_data(struct base_data *c) { @@ -901,7 +907,7 @@ if (!delta_nr) { c->data = get_data_from_pack(obj); c->size = obj->size; - get_thread_data()->base_cache_used += c->size; + base_cache_used += c->size; prune_base_data(c); } for (; delta_nr > 0; delta_nr--) { @@ -917,7 +923,7 @@ free(raw); if (!c->data) bad_object(obj->idx.offset, _("failed to apply delta")); - get_thread_data()->base_cache_used += c->size; + base_cache_used += c->size; prune_base_data(c); } free(delta); @@ -925,10 +931,27 @@ return c->data; } -static void resolve_delta(struct object_entry *delta_obj, - struct base_data *base, struct base_data *result) +static struct base_data *make_base(struct object_entry *obj, + struct base_data *parent) { - void *base_data, *delta_data; + struct base_data *base = xcalloc(1, sizeof(struct base_data)); + base->base = parent; + base->obj = obj; + find_ref_delta_children(&obj->idx.oid, + &base->ref_first, &base->ref_last); + find_ofs_delta_children(obj->idx.offset, + &base->ofs_first, &base->ofs_last); + base->children_remaining = base->ref_last - base->ref_first + + base->ofs_last - base->ofs_first + 2; + return base; +} + +static struct base_data *resolve_delta(struct object_entry *delta_obj, + struct base_data *base) +{ + void *delta_data, *result_data; + struct base_data *result; + unsigned long result_size; if (show_stat) { int i = delta_obj - objects; @@ -941,113 +964,26 @@ obj_stat[i].base_object_no = j; } delta_data = get_data_from_pack(delta_obj); - base_data = get_base_data(base); - result->obj = delta_obj; - result->data = patch_delta(base_data, base->size, - delta_data, delta_obj->size, &result->size); + assert(base->data); + result_data = patch_delta(base->data, base->size, + delta_data, delta_obj->size, &result_size); free(delta_data); - if (!result->data) + if (!result_data) bad_object(delta_obj->idx.offset, _("failed to apply delta")); - hash_object_file(result->data, result->size, + hash_object_file(the_hash_algo, result_data, result_size, type_name(delta_obj->real_type), &delta_obj->idx.oid); - sha1_object(result->data, NULL, result->size, delta_obj->real_type, + sha1_object(result_data, NULL, result_size, delta_obj->real_type, &delta_obj->idx.oid); + + result = make_base(delta_obj, base); + result->data = result_data; + result->size = result_size; + counter_lock(); nr_resolved_deltas++; counter_unlock(); -} -/* - * Standard boolean compare-and-swap: atomically check whether "*type" is - * "want"; if so, swap in "set" and return true. Otherwise, leave it untouched - * and return false. - */ -static int compare_and_swap_type(signed char *type, - enum object_type want, - enum object_type set) -{ - enum object_type old; - - type_cas_lock(); - old = *type; - if (old == want) - *type = set; - type_cas_unlock(); - - return old == want; -} - -static struct base_data *find_unresolved_deltas_1(struct base_data *base, - struct base_data *prev_base) -{ - if (base->ref_last == -1 && base->ofs_last == -1) { - find_ref_delta_children(&base->obj->idx.oid, - &base->ref_first, &base->ref_last, - OBJ_REF_DELTA); - - find_ofs_delta_children(base->obj->idx.offset, - &base->ofs_first, &base->ofs_last, - OBJ_OFS_DELTA); - - if (base->ref_last == -1 && base->ofs_last == -1) { - free(base->data); - return NULL; - } - - link_base_data(prev_base, base); - } - - if (base->ref_first <= base->ref_last) { - struct object_entry *child = objects + ref_deltas[base->ref_first].obj_no; - struct base_data *result = alloc_base_data(); - - if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA, - base->obj->real_type)) - BUG("child->real_type != OBJ_REF_DELTA"); - - resolve_delta(child, base, result); - if (base->ref_first == base->ref_last && base->ofs_last == -1) - free_base_data(base); - - base->ref_first++; - return result; - } - - if (base->ofs_first <= base->ofs_last) { - struct object_entry *child = objects + ofs_deltas[base->ofs_first].obj_no; - struct base_data *result = alloc_base_data(); - - assert(child->real_type == OBJ_OFS_DELTA); - child->real_type = base->obj->real_type; - resolve_delta(child, base, result); - if (base->ofs_first == base->ofs_last) - free_base_data(base); - - base->ofs_first++; - return result; - } - - unlink_base_data(base); - return NULL; -} - -static void find_unresolved_deltas(struct base_data *base) -{ - struct base_data *new_base, *prev_base = NULL; - for (;;) { - new_base = find_unresolved_deltas_1(base, prev_base); - - if (new_base) { - prev_base = base; - base = new_base; - } else { - free(base); - base = prev_base; - if (!base) - return; - prev_base = base->base; - } - } + return result; } static int compare_ofs_delta_entry(const void *a, const void *b) @@ -1068,34 +1004,135 @@ return oidcmp(&delta_a->oid, &delta_b->oid); } -static void resolve_base(struct object_entry *obj) -{ - struct base_data *base_obj = alloc_base_data(); - base_obj->obj = obj; - base_obj->data = NULL; - find_unresolved_deltas(base_obj); -} - static void *threaded_second_pass(void *data) { - set_thread_data(data); + if (data) + set_thread_data(data); for (;;) { - int i; + struct base_data *parent = NULL; + struct object_entry *child_obj; + struct base_data *child; + counter_lock(); display_progress(progress, nr_resolved_deltas); counter_unlock(); + work_lock(); - while (nr_dispatched < nr_objects && - is_delta_type(objects[nr_dispatched].type)) - nr_dispatched++; - if (nr_dispatched >= nr_objects) { - work_unlock(); - break; + if (list_empty(&work_head)) { + /* + * Take an object from the object array. + */ + while (nr_dispatched < nr_objects && + is_delta_type(objects[nr_dispatched].type)) + nr_dispatched++; + if (nr_dispatched >= nr_objects) { + work_unlock(); + break; + } + child_obj = &objects[nr_dispatched++]; + } else { + /* + * Peek at the top of the stack, and take a child from + * it. + */ + parent = list_first_entry(&work_head, struct base_data, + list); + + if (parent->ref_first <= parent->ref_last) { + int offset = ref_deltas[parent->ref_first++].obj_no; + child_obj = objects + offset; + if (child_obj->real_type != OBJ_REF_DELTA) + die("REF_DELTA at offset %"PRIuMAX" already resolved (duplicate base %s?)", + (uintmax_t) child_obj->idx.offset, + oid_to_hex(&parent->obj->idx.oid)); + child_obj->real_type = parent->obj->real_type; + } else { + child_obj = objects + + ofs_deltas[parent->ofs_first++].obj_no; + assert(child_obj->real_type == OBJ_OFS_DELTA); + child_obj->real_type = parent->obj->real_type; + } + + if (parent->ref_first > parent->ref_last && + parent->ofs_first > parent->ofs_last) { + /* + * This parent has run out of children, so move + * it to done_head. + */ + list_del(&parent->list); + list_add(&parent->list, &done_head); + } + + /* + * Ensure that the parent has data, since we will need + * it later. + * + * NEEDSWORK: If parent data needs to be reloaded, this + * prolongs the time that the current thread spends in + * the mutex. A mitigating factor is that parent data + * needs to be reloaded only if the delta base cache + * limit is exceeded, so in the typical case, this does + * not happen. + */ + get_base_data(parent); + parent->retain_data++; } - i = nr_dispatched++; work_unlock(); - resolve_base(&objects[i]); + if (parent) { + child = resolve_delta(child_obj, parent); + if (!child->children_remaining) + FREE_AND_NULL(child->data); + } else { + child = make_base(child_obj, NULL); + if (child->children_remaining) { + /* + * Since this child has its own delta children, + * we will need this data in the future. + * Inflate now so that future iterations will + * have access to this object's data while + * outside the work mutex. + */ + child->data = get_data_from_pack(child_obj); + child->size = child_obj->size; + } + } + + work_lock(); + if (parent) + parent->retain_data--; + if (child->data) { + /* + * This child has its own children, so add it to + * work_head. + */ + list_add(&child->list, &work_head); + base_cache_used += child->size; + prune_base_data(NULL); + } else { + /* + * This child does not have its own children. It may be + * the last descendant of its ancestors; free those + * that we can. + */ + struct base_data *p = parent; + + while (p) { + struct base_data *next_p; + + p->children_remaining--; + if (p->children_remaining) + break; + + next_p = p->base; + free_base_data(p); + list_del(&p->list); + free(p); + + p = next_p; + } + } + work_unlock(); } return NULL; } @@ -1115,6 +1152,7 @@ if (verbose) progress = start_progress( + progress_title ? progress_title : from_stdin ? _("Receiving objects") : _("Indexing objects"), nr_objects); for (i = 0; i < nr_objects; i++) { @@ -1196,6 +1234,7 @@ nr_ref_deltas + nr_ofs_deltas); nr_dispatched = 0; + base_cache_limit = delta_base_cache_limit * nr_threads; if (nr_threads > 1 || getenv("GIT_FORCE_THREADS")) { init_thread(); for (i = 0; i < nr_threads; i++) { @@ -1210,15 +1249,7 @@ cleanup_thread(); return; } - - for (i = 0; i < nr_objects; i++) { - struct object_entry *obj = &objects[i]; - - if (is_delta_type(obj->type)) - continue; - resolve_base(obj); - display_progress(progress, nr_resolved_deltas); - } + threaded_second_pass(¬hread_data); } /* @@ -1321,7 +1352,7 @@ obj[1].idx.offset += write_compressed(f, buf, size); obj[0].idx.crc32 = crc32_end(f); hashflush(f); - hashcpy(obj->idx.oid.hash, sha1); + oidread(&obj->idx.oid, sha1); return obj; } @@ -1365,44 +1396,50 @@ continue; oid_array_append(&to_fetch, &d->oid); } - if (to_fetch.nr) - promisor_remote_get_direct(the_repository, - to_fetch.oid, to_fetch.nr); + promisor_remote_get_direct(the_repository, + to_fetch.oid, to_fetch.nr); oid_array_clear(&to_fetch); } for (i = 0; i < nr_ref_deltas; i++) { struct ref_delta_entry *d = sorted_by_pos[i]; enum object_type type; - struct base_data *base_obj = alloc_base_data(); + void *data; + unsigned long size; if (objects[d->obj_no].real_type != OBJ_REF_DELTA) continue; - base_obj->data = read_object_file(&d->oid, &type, - &base_obj->size); - if (!base_obj->data) + data = read_object_file(&d->oid, &type, &size); + if (!data) continue; - if (check_object_signature(&d->oid, base_obj->data, - base_obj->size, type_name(type))) + if (check_object_signature(the_repository, &d->oid, + data, size, + type_name(type), NULL)) die(_("local object %s is corrupt"), oid_to_hex(&d->oid)); - base_obj->obj = append_obj_to_pack(f, d->oid.hash, - base_obj->data, base_obj->size, type); - find_unresolved_deltas(base_obj); + + /* + * Add this as an object to the objects array and call + * threaded_second_pass() (which will pick up the added + * object). + */ + append_obj_to_pack(f, d->oid.hash, data, size, type); + threaded_second_pass(NULL); + display_progress(progress, nr_resolved_deltas); } free(sorted_by_pos); } -static const char *derive_filename(const char *pack_name, const char *suffix, - struct strbuf *buf) +static const char *derive_filename(const char *pack_name, const char *strip, + const char *suffix, struct strbuf *buf) { size_t len; - if (!strip_suffix(pack_name, ".pack", &len)) - die(_("packfile name '%s' does not end with '.pack'"), - pack_name); + if (!strip_suffix(pack_name, strip, &len) || !len || + pack_name[len - 1] != '.') + die(_("packfile name '%s' does not end with '.%s'"), + pack_name, strip); strbuf_add(buf, pack_name, len); - strbuf_addch(buf, '.'); strbuf_addstr(buf, suffix); return buf->buf; } @@ -1417,7 +1454,7 @@ int msg_len = strlen(msg); if (pack_name) - filename = derive_filename(pack_name, suffix, &name_buf); + filename = derive_filename(pack_name, "pack", suffix, &name_buf); else filename = odb_pack_name(&name_buf, hash, suffix); @@ -1440,14 +1477,32 @@ strbuf_release(&name_buf); } +static void rename_tmp_packfile(const char **final_name, + const char *curr_name, + struct strbuf *name, unsigned char *hash, + const char *ext, int make_read_only_if_same) +{ + if (*final_name != curr_name) { + if (!*final_name) + *final_name = odb_pack_name(name, hash, ext); + if (finalize_object_file(curr_name, *final_name)) + die(_("unable to rename temporary '*.%s' file to '%s'"), + ext, *final_name); + } else if (make_read_only_if_same) { + chmod(*final_name, 0444); + } +} + static void final(const char *final_pack_name, const char *curr_pack_name, const char *final_index_name, const char *curr_index_name, + const char *final_rev_index_name, const char *curr_rev_index_name, const char *keep_msg, const char *promisor_msg, unsigned char *hash) { const char *report = "pack"; struct strbuf pack_name = STRBUF_INIT; struct strbuf index_name = STRBUF_INIT; + struct strbuf rev_index_name = STRBUF_INIT; int err; if (!from_stdin) { @@ -1466,21 +1521,13 @@ write_special_file("promisor", promisor_msg, final_pack_name, hash, NULL); - if (final_pack_name != curr_pack_name) { - if (!final_pack_name) - final_pack_name = odb_pack_name(&pack_name, hash, "pack"); - if (finalize_object_file(curr_pack_name, final_pack_name)) - die(_("cannot store pack file")); - } else if (from_stdin) - chmod(final_pack_name, 0444); - - if (final_index_name != curr_index_name) { - if (!final_index_name) - final_index_name = odb_pack_name(&index_name, hash, "idx"); - if (finalize_object_file(curr_index_name, final_index_name)) - die(_("cannot store index file")); - } else - chmod(final_index_name, 0444); + rename_tmp_packfile(&final_pack_name, curr_pack_name, &pack_name, + hash, "pack", from_stdin); + if (curr_rev_index_name) + rename_tmp_packfile(&final_rev_index_name, curr_rev_index_name, + &rev_index_name, hash, "rev", 1); + rename_tmp_packfile(&final_index_name, curr_index_name, &index_name, + hash, "idx", 1); if (do_fsck_object) { struct packed_git *p; @@ -1511,6 +1558,7 @@ } } + strbuf_release(&rev_index_name); strbuf_release(&index_name); strbuf_release(&pack_name); } @@ -1536,6 +1584,12 @@ } return 0; } + if (!strcmp(k, "pack.writereverseindex")) { + if (git_config_bool(k, v)) + opts->flags |= WRITE_REV; + else + opts->flags &= ~WRITE_REV; + } return git_default_config(k, v, cb); } @@ -1552,14 +1606,10 @@ { const uint32_t *idx1, *idx2; uint32_t i; - const uint32_t hashwords = the_hash_algo->rawsz / sizeof(uint32_t); /* The address of the 4-byte offset table */ - idx1 = (((const uint32_t *)p->index_data) - + 2 /* 8-byte header */ - + 256 /* fan out */ - + hashwords * p->num_objects /* object ID table */ - + p->num_objects /* CRC32 table */ + idx1 = (((const uint32_t *)((const uint8_t *)p->index_data + p->crc_offset)) + + (size_t)p->num_objects /* CRC32 table */ ); /* The address of the 8-byte offset table */ @@ -1603,7 +1653,7 @@ /* * Get rid of the idx file as we do not need it anymore. * NEEDSWORK: extract this bit from free_pack_by_name() in - * sha1-file.c, perhaps? It shouldn't matter very much as we + * object-file.c, perhaps? It shouldn't matter very much as we * know we haven't installed this pack (hence we never have * read anything from it). */ @@ -1617,7 +1667,7 @@ unsigned long *chain_histogram = NULL; if (deepest_delta) - chain_histogram = xcalloc(deepest_delta, sizeof(unsigned long)); + CALLOC_ARRAY(chain_histogram, deepest_delta); for (i = 0; i < nr_objects; i++) { struct object_entry *obj = &objects[i]; @@ -1657,17 +1707,20 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) { - int i, fix_thin_pack = 0, verify = 0, stat_only = 0; + int i, fix_thin_pack = 0, verify = 0, stat_only = 0, rev_index; const char *curr_index; - const char *index_name = NULL, *pack_name = NULL; + const char *curr_rev_index = NULL; + const char *index_name = NULL, *pack_name = NULL, *rev_index_name = NULL; const char *keep_msg = NULL; const char *promisor_msg = NULL; struct strbuf index_name_buf = STRBUF_INIT; + struct strbuf rev_index_name_buf = STRBUF_INIT; struct pack_idx_entry **idx_objects; struct pack_idx_option opts; unsigned char pack_hash[GIT_MAX_RAWSZ]; unsigned foreign_nr = 1; /* zero is a "good" value, assume bad */ int report_end_of_input = 0; + int hash_algo = 0; /* * index-pack never needs to fetch missing objects except when @@ -1688,6 +1741,11 @@ if (prefix && chdir(prefix)) die(_("Cannot come back to cwd")); + if (git_env_bool(GIT_TEST_WRITE_REV_INDEX, 0)) + rev_index = 1; + else + rev_index = !!(opts.flags & (WRITE_REV_VERIFY | WRITE_REV)); + for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -1742,6 +1800,10 @@ input_len = sizeof(*hdr); } else if (!strcmp(arg, "-v")) { verbose = 1; + } else if (!strcmp(arg, "--progress-title")) { + if (progress_title || (i+1) >= argc) + usage(index_pack_usage); + progress_title = argv[++i]; } else if (!strcmp(arg, "--show-resolving-progress")) { show_resolving_progress = 1; } else if (!strcmp(arg, "--report-end-of-input")) { @@ -1761,6 +1823,15 @@ die(_("bad %s"), arg); } else if (skip_prefix(arg, "--max-input-size=", &arg)) { max_input_size = strtoumax(arg, NULL, 10); + } else if (skip_prefix(arg, "--object-format=", &arg)) { + hash_algo = hash_algo_by_name(arg); + if (hash_algo == GIT_HASH_UNKNOWN) + die(_("unknown hash algorithm '%s'"), arg); + repo_set_hash_algo(the_repository, hash_algo); + } else if (!strcmp(arg, "--rev-index")) { + rev_index = 1; + } else if (!strcmp(arg, "--no-rev-index")) { + rev_index = 0; } else usage(index_pack_usage); continue; @@ -1777,8 +1848,19 @@ die(_("--fix-thin cannot be used without --stdin")); if (from_stdin && !startup_info->have_repository) die(_("--stdin requires a git repository")); + if (from_stdin && hash_algo) + die(_("--object-format cannot be used with --stdin")); if (!index_name && pack_name) - index_name = derive_filename(pack_name, "idx", &index_name_buf); + index_name = derive_filename(pack_name, "pack", "idx", &index_name_buf); + + opts.flags &= ~(WRITE_REV | WRITE_REV_VERIFY); + if (rev_index) { + opts.flags |= verify ? WRITE_REV_VERIFY : WRITE_REV; + if (index_name) + rev_index_name = derive_filename(index_name, + "idx", "rev", + &rev_index_name_buf); + } if (verify) { if (!index_name) @@ -1791,17 +1873,30 @@ if (HAVE_THREADS && !nr_threads) { nr_threads = online_cpus(); - /* An experiment showed that more threads does not mean faster */ - if (nr_threads > 3) - nr_threads = 3; + /* + * Experiments show that going above 20 threads doesn't help, + * no matter how many cores you have. Below that, we tend to + * max at half the number of online_cpus(), presumably because + * half of those are hyperthreads rather than full cores. We'll + * never reduce the level below "3", though, to match a + * historical value that nobody complained about. + */ + if (nr_threads < 4) + ; /* too few cores to consider capping */ + else if (nr_threads < 6) + nr_threads = 3; /* historic cap */ + else if (nr_threads < 40) + nr_threads /= 2; + else + nr_threads = 20; /* hard cap */ } curr_pack = open_pack_file(pack_name); parse_pack_header(); - objects = xcalloc(st_add(nr_objects, 1), sizeof(struct object_entry)); + CALLOC_ARRAY(objects, st_add(nr_objects, 1)); if (show_stat) - obj_stat = xcalloc(st_add(nr_objects, 1), sizeof(struct object_stat)); - ofs_deltas = xcalloc(nr_objects, sizeof(struct ofs_delta_entry)); + CALLOC_ARRAY(obj_stat, st_add(nr_objects, 1)); + CALLOC_ARRAY(ofs_deltas, nr_objects); parse_pack_objects(pack_hash); if (report_end_of_input) write_in_full(2, "\0", 1); @@ -1819,11 +1914,16 @@ for (i = 0; i < nr_objects; i++) idx_objects[i] = &objects[i].idx; curr_index = write_idx_file(index_name, idx_objects, nr_objects, &opts, pack_hash); + if (rev_index) + curr_rev_index = write_rev_file(rev_index_name, idx_objects, + nr_objects, pack_hash, + opts.flags); free(idx_objects); if (!verify) final(pack_name, curr_pack, index_name, curr_index, + rev_index_name, curr_rev_index, keep_msg, promisor_msg, pack_hash); else @@ -1834,10 +1934,13 @@ free(objects); strbuf_release(&index_name_buf); + strbuf_release(&rev_index_name_buf); if (pack_name == NULL) free((void *) curr_pack); if (index_name == NULL) free((void *) curr_index); + if (rev_index_name == NULL) + free((void *) curr_rev_index); /* * Let the caller know this pack is not self contained diff -Nru git-2.25.1/builtin/init-db.c git-2.34.1/builtin/init-db.c --- git-2.25.1/builtin/init-db.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/init-db.c 2021-11-24 19:29:21.000000000 +0000 @@ -9,6 +9,7 @@ #include "builtin.h" #include "exec-cmd.h" #include "parse-options.h" +#include "worktree.h" #ifndef DEFAULT_GIT_TEMPLATE_DIR #define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates" @@ -20,9 +21,10 @@ #define TEST_FILEMODE 1 #endif +#define GIT_DEFAULT_HASH_ENVIRONMENT "GIT_DEFAULT_HASH" + static int init_is_bare_repository = 0; static int init_shared_repository = -1; -static const char *init_db_template_dir; static void copy_templates_1(struct strbuf *path, struct strbuf *template_path, DIR *dir) @@ -91,7 +93,7 @@ } } -static void copy_templates(const char *template_dir) +static void copy_templates(const char *template_dir, const char *init_template_dir) { struct strbuf path = STRBUF_INIT; struct strbuf template_path = STRBUF_INIT; @@ -104,7 +106,7 @@ if (!template_dir) template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT); if (!template_dir) - template_dir = init_db_template_dir; + template_dir = init_template_dir; if (!template_dir) template_dir = to_free = system_path(DEFAULT_GIT_TEMPLATE_DIR); if (!template_dir[0]) { @@ -151,17 +153,6 @@ clear_repository_format(&template_format); } -static int git_init_db_config(const char *k, const char *v, void *cb) -{ - if (!strcmp(k, "init.templatedir")) - return git_config_pathname(&init_db_template_dir, k, v); - - if (starts_with(k, "core.")) - return platform_core_config(k, v, cb); - - return 0; -} - /* * If the git_dir is not directly inside the working tree, then git will not * find it by default, and we need to set the worktree explicitly. @@ -176,21 +167,41 @@ return 1; } +void initialize_repository_version(int hash_algo, int reinit) +{ + char repo_version_string[10]; + int repo_version = GIT_REPO_VERSION; + + if (hash_algo != GIT_HASH_SHA1) + repo_version = GIT_REPO_VERSION_READ; + + /* This forces creation of new config file */ + xsnprintf(repo_version_string, sizeof(repo_version_string), + "%d", repo_version); + git_config_set("core.repositoryformatversion", repo_version_string); + + if (hash_algo != GIT_HASH_SHA1) + git_config_set("extensions.objectformat", + hash_algos[hash_algo].name); + else if (reinit) + git_config_set_gently("extensions.objectformat", NULL); +} + static int create_default_files(const char *template_path, - const char *original_git_dir) + const char *original_git_dir, + const char *initial_branch, + const struct repository_format *fmt, + int quiet) { struct stat st1; struct strbuf buf = STRBUF_INIT; char *path; - char repo_version_string[10]; char junk[2]; int reinit; int filemode; struct strbuf err = STRBUF_INIT; - - /* Just look for `init.templatedir` */ - init_db_template_dir = NULL; /* re-set in case it was set before */ - git_config(git_init_db_config, NULL); + const char *init_template_dir = NULL; + const char *work_tree = get_git_work_tree(); /* * First copy the templates -- we might have the default @@ -201,7 +212,9 @@ * values (since we've just potentially changed what's available on * disk). */ - copy_templates(template_path); + git_config_get_pathname("init.templatedir", &init_template_dir); + copy_templates(template_path, init_template_dir); + free((char *)init_template_dir); git_config_clear(); reset_shared_repository(); git_config(git_default_config, NULL); @@ -210,7 +223,7 @@ * We must make sure command-line options continue to override any * values we might have just re-read from the config. */ - is_bare_repository_cfg = init_is_bare_repository; + is_bare_repository_cfg = init_is_bare_repository || !work_tree; if (init_shared_repository != -1) set_shared_repository(init_shared_repository); @@ -233,21 +246,29 @@ die("failed to set up refs db: %s", err.buf); /* - * Create the default symlink from ".git/HEAD" to the "master" - * branch, if it does not exist yet. + * Point the HEAD symref to the initial branch with if HEAD does + * not yet exist. */ path = git_path_buf(&buf, "HEAD"); reinit = (!access(path, R_OK) || readlink(path, junk, sizeof(junk)-1) != -1); if (!reinit) { - if (create_symref("HEAD", "refs/heads/master", NULL) < 0) + char *ref; + + if (!initial_branch) + initial_branch = git_default_branch_name(quiet); + + ref = xstrfmt("refs/heads/%s", initial_branch); + if (check_refname_format(ref, 0) < 0) + die(_("invalid initial branch name: '%s'"), + initial_branch); + + if (create_symref("HEAD", ref, NULL) < 0) exit(1); + free(ref); } - /* This forces creation of new config file */ - xsnprintf(repo_version_string, sizeof(repo_version_string), - "%d", GIT_REPO_VERSION); - git_config_set("core.repositoryformatversion", repo_version_string); + initialize_repository_version(fmt->hash_algo, 0); /* Check filemode trustability */ path = git_path_buf(&buf, "config"); @@ -266,7 +287,6 @@ if (is_bare_repository()) git_config_set("core.bare", "true"); else { - const char *work_tree = get_git_work_tree(); git_config_set("core.bare", "false"); /* allow template config file to override the default */ if (log_all_ref_updates == LOG_REFS_UNSET) @@ -335,17 +355,40 @@ if (rename(src, git_dir)) die_errno(_("unable to move %s to %s"), src, git_dir); + repair_worktrees(NULL, NULL); } write_file(git_link, "gitdir: %s", git_dir); } +static void validate_hash_algorithm(struct repository_format *repo_fmt, int hash) +{ + const char *env = getenv(GIT_DEFAULT_HASH_ENVIRONMENT); + /* + * If we already have an initialized repo, don't allow the user to + * specify a different algorithm, as that could cause corruption. + * Otherwise, if the user has specified one on the command line, use it. + */ + if (repo_fmt->version >= 0 && hash != GIT_HASH_UNKNOWN && hash != repo_fmt->hash_algo) + die(_("attempt to reinitialize repository with different hash")); + else if (hash != GIT_HASH_UNKNOWN) + repo_fmt->hash_algo = hash; + else if (env) { + int env_algo = hash_algo_by_name(env); + if (env_algo == GIT_HASH_UNKNOWN) + die(_("unknown hash algorithm '%s'"), env); + repo_fmt->hash_algo = env_algo; + } +} + int init_db(const char *git_dir, const char *real_git_dir, - const char *template_dir, unsigned int flags) + const char *template_dir, int hash, const char *initial_branch, + unsigned int flags) { int reinit; int exist_ok = flags & INIT_DB_EXIST_OK; char *original_git_dir = real_pathdup(git_dir, 1); + struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; if (real_git_dir) { struct stat st; @@ -356,18 +399,18 @@ if (!exist_ok && !stat(real_git_dir, &st)) die(_("%s already exists"), real_git_dir); - set_git_dir(real_path(real_git_dir)); + set_git_dir(real_git_dir, 1); git_dir = get_git_dir(); separate_git_dir(git_dir, original_git_dir); } else { - set_git_dir(real_path(git_dir)); + set_git_dir(git_dir, 1); git_dir = get_git_dir(); } startup_info->have_repository = 1; - /* Just look for `core.hidedotfiles` */ - git_config(git_init_db_config, NULL); + /* Ensure `core.hidedotfiles` is processed */ + git_config(platform_core_config, NULL); safe_create_dir(git_dir, 0); @@ -378,9 +421,16 @@ * config file, so this will not fail. What we are catching * is an attempt to reinitialize new repository with an old tool. */ - check_repository_format(); + check_repository_format(&repo_fmt); + + validate_hash_algorithm(&repo_fmt, hash); - reinit = create_default_files(template_dir, original_git_dir); + reinit = create_default_files(template_dir, original_git_dir, + initial_branch, &repo_fmt, + flags & INIT_DB_QUIET); + if (reinit && initial_branch) + warning(_("re-init: ignored --initial-branch=%s"), + initial_branch); create_object_directory(); @@ -482,6 +532,9 @@ const char *work_tree; const char *template_dir = NULL; unsigned int flags = 0; + const char *object_format = NULL; + const char *initial_branch = NULL; + int hash_algo = GIT_HASH_UNKNOWN; const struct option init_db_options[] = { OPT_STRING(0, "template", &template_dir, N_("template-directory"), N_("directory from which templates will be used")), @@ -494,16 +547,25 @@ OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET), OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"), N_("separate git dir from working tree")), + OPT_STRING('b', "initial-branch", &initial_branch, N_("name"), + N_("override the name of the initial branch")), + OPT_STRING(0, "object-format", &object_format, N_("hash"), + N_("specify the hash algorithm to use")), OPT_END() }; argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0); + if (real_git_dir && is_bare_repository_cfg == 1) + die(_("--separate-git-dir and --bare are mutually exclusive")); + if (real_git_dir && !is_absolute_path(real_git_dir)) real_git_dir = real_pathdup(real_git_dir, 1); - if (template_dir && *template_dir && !is_absolute_path(template_dir)) + if (template_dir && *template_dir && !is_absolute_path(template_dir)) { template_dir = absolute_pathdup(template_dir); + UNLEAK(template_dir); + } if (argc == 1) { int mkdir_tried = 0; @@ -546,6 +608,12 @@ free(cwd); } + if (object_format) { + hash_algo = hash_algo_by_name(object_format); + if (hash_algo == GIT_HASH_UNKNOWN) + die(_("unknown hash algorithm '%s'"), object_format); + } + if (init_shared_repository != -1) set_shared_repository(init_shared_repository); @@ -567,6 +635,30 @@ if (!git_dir) git_dir = DEFAULT_GIT_DIR_ENVIRONMENT; + /* + * When --separate-git-dir is used inside a linked worktree, take + * care to ensure that the common .git/ directory is relocated, not + * the worktree-specific .git/worktrees// directory. + */ + if (real_git_dir) { + int err; + const char *p; + struct strbuf sb = STRBUF_INIT; + + p = read_gitfile_gently(git_dir, &err); + if (p && get_common_dir(&sb, p)) { + struct strbuf mainwt = STRBUF_INIT; + + strbuf_addbuf(&mainwt, &sb); + strbuf_strip_suffix(&mainwt, "/.git"); + if (chdir(mainwt.buf) < 0) + die_errno(_("cannot chdir to %s"), mainwt.buf); + strbuf_release(&mainwt); + git_dir = strbuf_detach(&sb, NULL); + } + strbuf_release(&sb); + } + if (is_bare_repository_cfg < 0) is_bare_repository_cfg = guess_repository_type(git_dir); @@ -588,6 +680,8 @@ get_git_work_tree()); } else { + if (real_git_dir) + die(_("--separate-git-dir incompatible with bare repository")); if (work_tree) set_git_work_tree(work_tree); } @@ -597,5 +691,6 @@ UNLEAK(work_tree); flags |= INIT_DB_EXIST_OK; - return init_db(git_dir, real_git_dir, template_dir, flags); + return init_db(git_dir, real_git_dir, template_dir, hash_algo, + initial_branch, flags); } diff -Nru git-2.25.1/builtin/interpret-trailers.c git-2.34.1/builtin/interpret-trailers.c --- git-2.25.1/builtin/interpret-trailers.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/interpret-trailers.c 2021-11-24 19:29:21.000000000 +0000 @@ -105,8 +105,8 @@ OPT_BOOL(0, "only-trailers", &opts.only_trailers, N_("output only the trailers")), OPT_BOOL(0, "only-input", &opts.only_input, N_("do not apply config rules")), OPT_BOOL(0, "unfold", &opts.unfold, N_("join whitespace-continued values")), - { OPTION_CALLBACK, 0, "parse", &opts, NULL, N_("set parsing options"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse }, + OPT_CALLBACK_F(0, "parse", &opts, NULL, N_("set parsing options"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_parse), OPT_BOOL(0, "no-divider", &opts.no_divider, N_("do not treat --- specially")), OPT_CALLBACK(0, "trailer", &trailers, N_("trailer"), N_("trailer(s) to add"), option_parse_trailer), diff -Nru git-2.25.1/builtin/log.c git-2.34.1/builtin/log.c --- git-2.25.1/builtin/log.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/log.c 2021-11-24 19:29:21.000000000 +0000 @@ -12,6 +12,7 @@ #include "color.h" #include "commit.h" #include "diff.h" +#include "diff-merges.h" #include "revision.h" #include "log-tree.h" #include "builtin.h" @@ -33,11 +34,11 @@ #include "commit-slab.h" #include "repository.h" #include "commit-reach.h" -#include "interdiff.h" #include "range-diff.h" #define MAIL_DEFAULT_WRAP 72 #define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100 +#define FORMAT_PATCH_NAME_MAX_DEFAULT 64 /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -46,10 +47,12 @@ static int default_show_root = 1; static int default_follow; static int default_show_signature; +static int default_encode_email_headers = 1; static int decoration_style; static int decoration_given; static int use_mailmap_config = 1; static const char *fmt_patch_subject_prefix = "PATCH"; +static int fmt_patch_name_max = FORMAT_PATCH_NAME_MAX_DEFAULT; static const char *fmt_pretty; static const char * const builtin_log_usage[] = { @@ -131,7 +134,6 @@ static void init_log_defaults(void) { - init_grep_defaults(the_repository); init_diff_ui_defaults(); decoration_style = auto_decoration_style(); @@ -150,7 +152,9 @@ rev->abbrev_commit = default_abbrev_commit; rev->show_root_diff = default_show_root; rev->subject_prefix = fmt_patch_subject_prefix; + rev->patch_name_max = fmt_patch_name_max; rev->show_signature = default_show_signature; + rev->encode_email_headers = default_encode_email_headers; rev->diffopt.flags.allow_textconv = 1; if (default_date_mode) @@ -164,23 +168,26 @@ int quiet = 0, source = 0, mailmap; static struct line_opt_callback_data line_cb = {NULL, NULL, STRING_LIST_INIT_DUP}; static struct string_list decorate_refs_exclude = STRING_LIST_INIT_NODUP; + static struct string_list decorate_refs_exclude_config = STRING_LIST_INIT_NODUP; static struct string_list decorate_refs_include = STRING_LIST_INIT_NODUP; struct decoration_filter decoration_filter = {&decorate_refs_include, - &decorate_refs_exclude}; + &decorate_refs_exclude, + &decorate_refs_exclude_config}; static struct revision_sources revision_sources; const struct option builtin_log_options[] = { OPT__QUIET(&quiet, N_("suppress diff output")), OPT_BOOL(0, "source", &source, N_("show source")), - OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")), + OPT_BOOL(0, "use-mailmap", &mailmap, N_("use mail map file")), + OPT_ALIAS(0, "mailmap", "use-mailmap"), OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include, N_("pattern"), N_("only decorate refs that match ")), OPT_STRING_LIST(0, "decorate-refs-exclude", &decorate_refs_exclude, N_("pattern"), N_("do not decorate refs that match ")), - { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), - PARSE_OPT_OPTARG, decorate_callback}, - OPT_CALLBACK('L', NULL, &line_cb, "n,m:file", - N_("Process line range n,m in file, counting from 1"), + OPT_CALLBACK_F(0, "decorate", NULL, NULL, N_("decorate options"), + PARSE_OPT_OPTARG, decorate_callback), + OPT_CALLBACK('L', NULL, &line_cb, "range:file", + N_("trace the evolution of line range , or function : in "), log_line_range_callback), OPT_END() }; @@ -202,6 +209,9 @@ if (argc > 1) die(_("unrecognized argument: %s"), argv[1]); + if (rev->line_level_traverse && rev->prune_data.nr) + die(_("-L: cannot be used with pathspec")); + memset(&w, 0, sizeof(w)); userformat_find_requirements(NULL, &w); @@ -221,7 +231,7 @@ if (mailmap) { rev->mailmap = xcalloc(1, sizeof(struct string_list)); - read_mailmap(rev->mailmap, NULL); + read_mailmap(rev->mailmap); } if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) { @@ -235,8 +245,23 @@ rev->abbrev_commit = 0; } + if (rev->commit_format == CMIT_FMT_USERFORMAT && !w.decorate) + decoration_style = 0; + if (decoration_style) { + const struct string_list *config_exclude = + repo_config_get_value_multi(the_repository, + "log.excludeDecoration"); + + if (config_exclude) { + struct string_list_item *item; + for_each_string_list_item(item, config_exclude) + string_list_append(&decorate_refs_exclude_config, + item->string); + } + rev->show_decorations = 1; + load_ref_decorations(&decoration_filter, decoration_style); } @@ -285,10 +310,11 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list) { - int i = revs->early_output, close_file = revs->diffopt.close_file; + int i = revs->early_output; int show_header = 1; + int no_free = revs->diffopt.no_free; - revs->diffopt.close_file = 0; + revs->diffopt.no_free = 0; sort_in_topological_order(&list, revs->sort_order); while (list && i) { struct commit *commit = list->item; @@ -305,8 +331,8 @@ case commit_ignore: break; case commit_error: - if (close_file) - fclose(revs->diffopt.file); + revs->diffopt.no_free = no_free; + diff_free(&revs->diffopt); return; } list = list->next; @@ -314,8 +340,8 @@ /* Did we already get enough commits for the early output? */ if (!i) { - if (close_file) - fclose(revs->diffopt.file); + revs->diffopt.no_free = 0; + diff_free(&revs->diffopt); return; } @@ -379,7 +405,7 @@ { struct commit *commit; int saved_nrl = 0; - int saved_dcctc = 0, close_file = rev->diffopt.close_file; + int saved_dcctc = 0; if (rev->early_output) setup_early_output(); @@ -395,7 +421,7 @@ * and HAS_CHANGES being accumulated in rev->diffopt, so be careful to * retain that state information if replacing rev->diffopt in this loop */ - rev->diffopt.close_file = 0; + rev->diffopt.no_free = 1; while ((commit = get_revision(rev)) != NULL) { if (!log_tree_commit(rev, commit) && rev->max_count >= 0) /* @@ -420,8 +446,8 @@ } rev->diffopt.degraded_cc_to_c = saved_dcctc; rev->diffopt.needed_rename_limit = saved_nrl; - if (close_file) - fclose(rev->diffopt.file); + rev->diffopt.no_free = 0; + diff_free(&rev->diffopt); if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF && rev->diffopt.flags.check_failed) { @@ -438,6 +464,14 @@ return git_config_string(&fmt_pretty, var, value); if (!strcmp(var, "format.subjectprefix")) return git_config_string(&fmt_patch_subject_prefix, var, value); + if (!strcmp(var, "format.filenamemaxlength")) { + fmt_patch_name_max = git_config_int(var, value); + return 0; + } + if (!strcmp(var, "format.encodeemailheaders")) { + default_encode_email_headers = git_config_bool(var, value); + return 0; + } if (!strcmp(var, "log.abbrevcommit")) { default_abbrev_commit = git_config_bool(var, value); return 0; @@ -450,6 +484,8 @@ decoration_style = 0; /* maybe warn? */ return 0; } + if (!strcmp(var, "log.diffmerges")) + return diff_merges_config(value); if (!strcmp(var, "log.showroot")) { default_show_root = git_config_bool(var, value); return 0; @@ -568,7 +604,7 @@ static int show_tree_object(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, int stage, void *context) + const char *pathname, unsigned mode, void *context) { FILE *file = context; fprintf(file, "%s%s\n", pathname, S_ISDIR(mode) ? "/" : ""); @@ -578,15 +614,10 @@ static void show_setup_revisions_tweak(struct rev_info *rev, struct setup_revision_opt *opt) { - if (rev->ignore_merges) { - /* There was no "-m" on the command line */ - rev->ignore_merges = 0; - if (!rev->first_parent_only && !rev->combine_merges) { - /* No "--first-parent", "-c", or "--cc" */ - rev->combine_merges = 1; - rev->dense_combined_merges = 1; - } - } + if (rev->first_parent_only) + diff_merges_default_to_first_parent(rev); + else + diff_merges_default_to_dense_combined(rev); if (!rev->diffopt.output_format) rev->diffopt.output_format = DIFF_FORMAT_PATCH; } @@ -606,7 +637,7 @@ repo_init_revisions(the_repository, &rev, prefix); rev.diff = 1; rev.always_show_header = 1; - rev.no_walk = REVISION_WALK_NO_WALK_SORTED; + rev.no_walk = 1; rev.diffopt.stat_width = -1; /* Scale to real terminal size */ memset(&opt, 0, sizeof(opt)); @@ -655,9 +686,9 @@ diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), name, diff_get_color_opt(&rev.diffopt, DIFF_RESET)); - read_tree_recursive(the_repository, (struct tree *)o, "", - 0, 0, &match_all, show_tree_object, - rev.diffopt.file); + read_tree(the_repository, (struct tree *)o, + &match_all, show_tree_object, + rev.diffopt.file); rev.shown_one = 1; break; case OBJ_COMMIT: @@ -707,13 +738,8 @@ rev->prune_data.nr == 1) rev->diffopt.flags.follow_renames = 1; - /* Turn --cc/-c into -p --cc/-c when -p was not given */ - if (!rev->diffopt.output_format && rev->combine_merges) - rev->diffopt.output_format = DIFF_FORMAT_PATCH; - - /* Turn -m on when --cc/-c was given */ - if (rev->combine_merges) - rev->ignore_merges = 0; + if (rev->first_parent_only) + diff_merges_default_to_first_parent(rev); } int cmd_log(int argc, const char **argv, const char *prefix) @@ -786,9 +812,15 @@ COVER_FROM_AUTO }; +enum auto_base_setting { + AUTO_BASE_NEVER, + AUTO_BASE_ALWAYS, + AUTO_BASE_WHEN_ABLE +}; + static enum thread_level thread; static int do_signoff; -static int base_auto; +static enum auto_base_setting auto_base; static char *from; static const char *signature = git_version_string; static const char *signature_file; @@ -887,7 +919,11 @@ if (!strcmp(var, "format.outputdirectory")) return git_config_string(&config_output_directory, var, value); if (!strcmp(var, "format.useautobase")) { - base_auto = git_config_bool(var, value); + if (value && !strcasecmp(value, "whenAble")) { + auto_base = AUTO_BASE_WHEN_ABLE; + return 0; + } + auto_base = git_config_bool(var, value) ? AUTO_BASE_ALWAYS : AUTO_BASE_NEVER; return 0; } if (!strcmp(var, "format.from")) { @@ -926,15 +962,9 @@ struct rev_info *rev, int quiet) { struct strbuf filename = STRBUF_INIT; - int suffix_len = strlen(rev->patch_suffix) + 1; if (output_directory) { strbuf_addstr(&filename, output_directory); - if (filename.len >= - PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) { - strbuf_release(&filename); - return error(_("name of output directory is too long")); - } strbuf_complete(&filename, '/'); } @@ -1041,7 +1071,7 @@ return NULL; ref = rev->cmdline.rev[positive].name; tip_oid = &rev->cmdline.rev[positive].item->oid; - if (dwim_ref(ref, strlen(ref), &branch_oid, &full_ref) && + if (dwim_ref(ref, strlen(ref), &branch_oid, &full_ref, 0) && skip_prefix(full_ref, "refs/heads/", &v) && oideq(tip_oid, &branch_oid)) branch = xstrdup(v); @@ -1107,24 +1137,24 @@ static int get_notes_refs(struct string_list_item *item, void *arg) { - argv_array_pushf(arg, "--notes=%s", item->string); + strvec_pushf(arg, "--notes=%s", item->string); return 0; } -static void get_notes_args(struct argv_array *arg, struct rev_info *rev) +static void get_notes_args(struct strvec *arg, struct rev_info *rev) { if (!rev->show_notes) { - argv_array_push(arg, "--no-notes"); + strvec_push(arg, "--no-notes"); } else if (rev->notes_opt.use_default_notes > 0 || (rev->notes_opt.use_default_notes == -1 && !rev->notes_opt.extra_notes_refs.nr)) { - argv_array_push(arg, "--notes"); + strvec_push(arg, "--notes"); } else { for_each_string_list(&rev->notes_opt.extra_notes_refs, get_notes_refs, arg); } } -static void make_cover_letter(struct rev_info *rev, int use_stdout, +static void make_cover_letter(struct rev_info *rev, int use_separate_file, struct commit *origin, int nr, struct commit **list, const char *branch_name, @@ -1144,7 +1174,7 @@ committer = git_committer_info(0); - if (!use_stdout && + if (use_separate_file && open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet)) die(_("failed to create cover-letter file")); @@ -1176,6 +1206,7 @@ log.in1 = 2; log.in2 = 4; log.file = rev->diffopt.file; + log.groups = SHORTLOG_GROUP_AUTHOR; for (i = 0; i < nr; i++) shortlog_add_commit(&log, list[i]); @@ -1187,7 +1218,8 @@ if (rev->idiff_oid1) { fprintf_ln(rev->diffopt.file, "%s", rev->idiff_title); - show_interdiff(rev, 0); + show_interdiff(rev->idiff_oid1, rev->idiff_oid2, 0, + &rev->diffopt); } if (rev->rdiff1) { @@ -1196,16 +1228,22 @@ * can be added later if deemed desirable. */ struct diff_options opts; - struct argv_array other_arg = ARGV_ARRAY_INIT; + struct strvec other_arg = STRVEC_INIT; + struct range_diff_options range_diff_opts = { + .creation_factor = rev->creation_factor, + .dual_color = 1, + .diffopt = &opts, + .other_arg = &other_arg + }; + diff_setup(&opts); opts.file = rev->diffopt.file; opts.use_color = rev->diffopt.use_color; diff_setup_done(&opts); fprintf_ln(rev->diffopt.file, "%s", rev->rdiff_title); get_notes_args(&other_arg, rev); - show_range_diff(rev->rdiff1, rev->rdiff2, - rev->creation_factor, 1, &opts, &other_arg); - argv_array_clear(&other_arg); + show_range_diff(rev->rdiff1, rev->rdiff2, &range_diff_opts); + strvec_clear(&other_arg); } } @@ -1404,6 +1442,23 @@ return 0; } +static int base_callback(const struct option *opt, const char *arg, int unset) +{ + const char **base_commit = opt->value; + + if (unset) { + auto_base = AUTO_BASE_NEVER; + *base_commit = NULL; + } else if (!strcmp(arg, "auto")) { + auto_base = AUTO_BASE_ALWAYS; + *base_commit = NULL; + } else { + auto_base = AUTO_BASE_NEVER; + *base_commit = arg; + } + return 0; +} + struct base_tree_info { struct object_id base_commit; int nr_patch_id, alloc_patch_id; @@ -1416,13 +1471,36 @@ { struct commit *base = NULL; struct commit **rev; - int i = 0, rev_nr = 0; + int i = 0, rev_nr = 0, auto_select, die_on_failure; + + switch (auto_base) { + case AUTO_BASE_NEVER: + if (base_commit) { + auto_select = 0; + die_on_failure = 1; + } else { + /* no base information is requested */ + return NULL; + } + break; + case AUTO_BASE_ALWAYS: + case AUTO_BASE_WHEN_ABLE: + if (base_commit) { + BUG("requested automatic base selection but a commit was provided"); + } else { + auto_select = 1; + die_on_failure = auto_base == AUTO_BASE_ALWAYS; + } + break; + default: + BUG("unexpected automatic base selection method"); + } - if (base_commit && strcmp(base_commit, "auto")) { + if (!auto_select) { base = lookup_commit_reference_by_name(base_commit); if (!base) die(_("unknown commit %s"), base_commit); - } else if ((base_commit && !strcmp(base_commit, "auto"))) { + } else { struct branch *curr_branch = branch_get(NULL); const char *upstream = branch_get_upstream(curr_branch, NULL); if (upstream) { @@ -1430,19 +1508,32 @@ struct commit *commit; struct object_id oid; - if (get_oid(upstream, &oid)) - die(_("failed to resolve '%s' as a valid ref"), upstream); + if (get_oid(upstream, &oid)) { + if (die_on_failure) + die(_("failed to resolve '%s' as a valid ref"), upstream); + else + return NULL; + } commit = lookup_commit_or_die(&oid, "upstream base"); base_list = get_merge_bases_many(commit, total, list); /* There should be one and only one merge base. */ - if (!base_list || base_list->next) - die(_("could not find exact merge base")); + if (!base_list || base_list->next) { + if (die_on_failure) { + die(_("could not find exact merge base")); + } else { + free_commit_list(base_list); + return NULL; + } + } base = base_list->item; free_commit_list(base_list); } else { - die(_("failed to get upstream, if you want to record base commit automatically,\n" - "please use git branch --set-upstream-to to track a remote branch.\n" - "Or you could specify base commit by --base= manually")); + if (die_on_failure) + die(_("failed to get upstream, if you want to record base commit automatically,\n" + "please use git branch --set-upstream-to to track a remote branch.\n" + "Or you could specify base commit by --base= manually")); + else + return NULL; } } @@ -1459,8 +1550,14 @@ for (i = 0; i < rev_nr / 2; i++) { struct commit_list *merge_base; merge_base = get_merge_bases(rev[2 * i], rev[2 * i + 1]); - if (!merge_base || merge_base->next) - die(_("failed to find exact merge base")); + if (!merge_base || merge_base->next) { + if (die_on_failure) { + die(_("failed to find exact merge base")); + } else { + free(rev); + return NULL; + } + } rev[i] = merge_base->item; } @@ -1470,12 +1567,24 @@ rev_nr = DIV_ROUND_UP(rev_nr, 2); } - if (!in_merge_bases(base, rev[0])) - die(_("base commit should be the ancestor of revision list")); + if (!in_merge_bases(base, rev[0])) { + if (die_on_failure) { + die(_("base commit should be the ancestor of revision list")); + } else { + free(rev); + return NULL; + } + } for (i = 0; i < total; i++) { - if (base == list[i]) - die(_("base commit shouldn't be in revision list")); + if (base == list[i]) { + if (die_on_failure) { + die(_("base commit shouldn't be in revision list")); + } else { + free(rev); + return NULL; + } + } } free(rev); @@ -1558,13 +1667,19 @@ oidclr(&bases->base_commit); } -static const char *diff_title(struct strbuf *sb, int reroll_count, - const char *generic, const char *rerolled) -{ - if (reroll_count <= 0) +static const char *diff_title(struct strbuf *sb, + const char *reroll_count, + const char *generic, + const char *rerolled) +{ + int v; + + /* RFC may be v0, so allow -v1 to diff against v0 */ + if (reroll_count && !strtol_i(reroll_count, 10, &v) && + v >= 1) + strbuf_addf(sb, rerolled, v - 1); + else strbuf_addstr(sb, generic); - else /* RFC may be v0, so allow -v1 to diff against v0 */ - strbuf_addf(sb, rerolled, reroll_count - 1); return sb->buf; } @@ -1575,16 +1690,20 @@ struct commit *head) { const char *head_oid = oid_to_hex(&head->object.oid); + int prev_is_range = is_range_diff_range(prev); - if (!strstr(prev, "..")) { + if (prev_is_range) + strbuf_addstr(r1, prev); + else strbuf_addf(r1, "%s..%s", head_oid, prev); + + if (origin) + strbuf_addf(r2, "%s..%s", oid_to_hex(&origin->object.oid), head_oid); + else if (prev_is_range) + die(_("failed to infer range-diff origin of current series")); + else { + warning(_("using '%s' as range-diff origin of current series"), prev); strbuf_addf(r2, "%s..%s", prev, head_oid); - } else if (!origin) { - die(_("failed to infer range-diff ranges")); - } else { - strbuf_addstr(r1, prev); - strbuf_addf(r2, "%s..%s", - oid_to_hex(&origin->object.oid), head_oid); } } @@ -1609,11 +1728,12 @@ struct strbuf buf = STRBUF_INIT; int use_patch_format = 0; int quiet = 0; - int reroll_count = -1; + const char *reroll_count = NULL; char *cover_from_description_arg = NULL; char *branch_name = NULL; char *base_commit = NULL; struct base_tree_info bases; + struct commit *base; int show_progress = 0; struct progress *progress = NULL; struct oid_array idiff_prev = OID_ARRAY_INIT; @@ -1625,13 +1745,13 @@ int creation_factor = -1; const struct option builtin_format_patch_options[] = { - { OPTION_CALLBACK, 'n', "numbered", &numbered, NULL, + OPT_CALLBACK_F('n', "numbered", &numbered, NULL, N_("use [PATCH n/m] even with a single patch"), - PARSE_OPT_NOARG, numbered_callback }, - { OPTION_CALLBACK, 'N', "no-numbered", &numbered, NULL, + PARSE_OPT_NOARG, numbered_callback), + OPT_CALLBACK_F('N', "no-numbered", &numbered, NULL, N_("use [PATCH] even with multiple patches"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, no_numbered_callback }, - OPT_BOOL('s', "signoff", &do_signoff, N_("add Signed-off-by:")), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, no_numbered_callback), + OPT_BOOL('s', "signoff", &do_signoff, N_("add a Signed-off-by trailer")), OPT_BOOL(0, "stdout", &use_stdout, N_("print patches to standard out")), OPT_BOOL(0, "cover-letter", &cover_letter, @@ -1642,23 +1762,25 @@ N_("use instead of '.patch'")), OPT_INTEGER(0, "start-number", &start_number, N_("start numbering patches at instead of 1")), - OPT_INTEGER('v', "reroll-count", &reroll_count, + OPT_STRING('v', "reroll-count", &reroll_count, N_("reroll-count"), N_("mark the series as Nth re-roll")), - { OPTION_CALLBACK, 0, "rfc", &rev, NULL, - N_("Use [RFC PATCH] instead of [PATCH]"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback }, + OPT_INTEGER(0, "filename-max-length", &fmt_patch_name_max, + N_("max length of output filename")), + OPT_CALLBACK_F(0, "rfc", &rev, NULL, + N_("use [RFC PATCH] instead of [PATCH]"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback), OPT_STRING(0, "cover-from-description", &cover_from_description_arg, N_("cover-from-description-mode"), N_("generate parts of a cover letter based on a branch's description")), - { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"), - N_("Use [] instead of [PATCH]"), - PARSE_OPT_NONEG, subject_prefix_callback }, - { OPTION_CALLBACK, 'o', "output-directory", &output_directory, + OPT_CALLBACK_F(0, "subject-prefix", &rev, N_("prefix"), + N_("use [] instead of [PATCH]"), + PARSE_OPT_NONEG, subject_prefix_callback), + OPT_CALLBACK_F('o', "output-directory", &output_directory, N_("dir"), N_("store resulting files in "), - PARSE_OPT_NONEG, output_directory_callback }, - { OPTION_CALLBACK, 'k', "keep-subject", &rev, NULL, + PARSE_OPT_NONEG, output_directory_callback), + OPT_CALLBACK_F('k', "keep-subject", &rev, NULL, N_("don't strip/add [PATCH]"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback }, + PARSE_OPT_NOARG | PARSE_OPT_NONEG, keep_callback), OPT_BOOL(0, "no-binary", &no_binary_diff, N_("don't output binary diffs")), OPT_BOOL(0, "zero-commit", &zero_commit, @@ -1669,31 +1791,30 @@ N_("show patch format instead of default (patch + stat)"), 1, PARSE_OPT_NONEG), OPT_GROUP(N_("Messaging")), - { OPTION_CALLBACK, 0, "add-header", NULL, N_("header"), - N_("add email header"), 0, header_callback }, - { OPTION_CALLBACK, 0, "to", NULL, N_("email"), N_("add To: header"), - 0, to_callback }, - { OPTION_CALLBACK, 0, "cc", NULL, N_("email"), N_("add Cc: header"), - 0, cc_callback }, - { OPTION_CALLBACK, 0, "from", &from, N_("ident"), + OPT_CALLBACK(0, "add-header", NULL, N_("header"), + N_("add email header"), header_callback), + OPT_CALLBACK(0, "to", NULL, N_("email"), N_("add To: header"), to_callback), + OPT_CALLBACK(0, "cc", NULL, N_("email"), N_("add Cc: header"), cc_callback), + OPT_CALLBACK_F(0, "from", &from, N_("ident"), N_("set From address to (or committer ident if absent)"), - PARSE_OPT_OPTARG, from_callback }, + PARSE_OPT_OPTARG, from_callback), OPT_STRING(0, "in-reply-to", &in_reply_to, N_("message-id"), N_("make first mail a reply to ")), - { OPTION_CALLBACK, 0, "attach", &rev, N_("boundary"), + OPT_CALLBACK_F(0, "attach", &rev, N_("boundary"), N_("attach the patch"), PARSE_OPT_OPTARG, - attach_callback }, - { OPTION_CALLBACK, 0, "inline", &rev, N_("boundary"), + attach_callback), + OPT_CALLBACK_F(0, "inline", &rev, N_("boundary"), N_("inline the patch"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, - inline_callback }, - { OPTION_CALLBACK, 0, "thread", &thread, N_("style"), + inline_callback), + OPT_CALLBACK_F(0, "thread", &thread, N_("style"), N_("enable message threading, styles: shallow, deep"), - PARSE_OPT_OPTARG, thread_callback }, + PARSE_OPT_OPTARG, thread_callback), OPT_STRING(0, "signature", &signature, N_("signature"), N_("add a signature")), - OPT_STRING(0, "base", &base_commit, N_("base-commit"), - N_("add prerequisite tree info to the patch series")), + OPT_CALLBACK_F(0, "base", &base_commit, N_("base-commit"), + N_("add prerequisite tree info to the patch series"), + 0, base_callback), OPT_FILENAME(0, "signature-file", &signature_file, N_("add a signature from a file")), OPT__QUIET(&quiet, N_("don't print the patch filenames")), @@ -1719,6 +1840,7 @@ rev.show_notes = show_notes; memcpy(&rev.notes_opt, ¬es_opt, sizeof(notes_opt)); rev.commit_format = CMIT_FMT_EMAIL; + rev.encode_email_headers = default_encode_email_headers; rev.expand_tabs_in_log_default = 0; rev.verbose_header = 1; rev.diff = 1; @@ -1729,9 +1851,6 @@ s_r_opt.def = "HEAD"; s_r_opt.revarg_opt = REVARG_COMMITTISH; - if (base_auto) - base_commit = "auto"; - if (default_attach) { rev.mime_boundary = default_attach; rev.no_inline = 1; @@ -1747,12 +1866,17 @@ PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); + /* Make sure "0000-$sub.patch" gives non-negative length for $sub */ + if (fmt_patch_name_max <= strlen("0000-") + strlen(fmt_patch_suffix)) + fmt_patch_name_max = strlen("0000-") + strlen(fmt_patch_suffix); + if (cover_from_description_arg) cover_from_description_mode = parse_cover_from_description(cover_from_description_arg); - if (0 < reroll_count) { + if (reroll_count) { struct strbuf sprefix = STRBUF_INIT; - strbuf_addf(&sprefix, "%s v%d", + + strbuf_addf(&sprefix, "%s v%s", rev.subject_prefix, reroll_count); rev.reroll_count = reroll_count; rev.subject_prefix = strbuf_detach(&sprefix, NULL); @@ -1831,6 +1955,7 @@ rev.diffopt.output_format |= DIFF_FORMAT_PATCH; rev.zero_commit = zero_commit; + rev.patch_name_max = fmt_patch_name_max; if (!rev.diffopt.flags.text && !no_binary_diff) rev.diffopt.flags.binary = 1; @@ -1838,20 +1963,26 @@ if (rev.show_notes) load_display_notes(&rev.notes_opt); - if (!output_directory && !use_stdout) - output_directory = config_output_directory; + if (use_stdout + rev.diffopt.close_file + !!output_directory > 1) + die(_("--stdout, --output, and --output-directory are mutually exclusive")); - if (!use_stdout) - output_directory = set_outdir(prefix, output_directory); - else + if (use_stdout) { setup_pager(); - - if (output_directory) { + } else if (rev.diffopt.close_file) { + /* + * The diff code parsed --output; it has already opened the + * file, but we must instruct it not to close after each diff. + */ + rev.diffopt.no_free = 1; + } else { int saved; + + if (!output_directory) + output_directory = config_output_directory; + output_directory = set_outdir(prefix, output_directory); + if (rev.diffopt.use_color != GIT_COLOR_ALWAYS) rev.diffopt.use_color = GIT_COLOR_NEVER; - if (use_stdout) - die(_("standard output, or directory, which one?")); /* * We consider as 'outside of gitdir', therefore avoid * applying adjust_shared_perm in s-c-l-d. @@ -1995,8 +2126,8 @@ } memset(&bases, 0, sizeof(bases)); - if (base_commit) { - struct commit *base = get_base_commit(base_commit, list, nr); + base = get_base_commit(base_commit, list, nr); + if (base) { reset_revision_walk(); clear_object_flags(UNINTERESTING); prepare_bases(&bases, base, list, nr); @@ -2013,7 +2144,7 @@ if (cover_letter) { if (thread) gen_message_id(&rev, "cover"); - make_cover_letter(&rev, use_stdout, + make_cover_letter(&rev, !!output_directory, origin, nr, list, branch_name, quiet); print_bases(&bases, rev.diffopt.file); print_signature(rev.diffopt.file); @@ -2068,7 +2199,7 @@ gen_message_id(&rev, oid_to_hex(&commit->object.oid)); } - if (!use_stdout && + if (output_directory && open_next_file(rev.numbered_files ? NULL : commit, NULL, &rev, quiet)) die(_("failed to create output files")); shown = log_tree_commit(&rev, commit); @@ -2081,7 +2212,7 @@ * the log; when using one file per patch, we do * not want the extra blank line. */ - if (!use_stdout) + if (output_directory) rev.shown_one = 0; if (shown) { print_bases(&bases, rev.diffopt.file); @@ -2092,7 +2223,7 @@ else print_signature(rev.diffopt.file); } - if (!use_stdout) + if (output_directory) fclose(rev.diffopt.file); } stop_progress(&progress); diff -Nru git-2.25.1/builtin/ls-files.c git-2.34.1/builtin/ls-files.c --- git-2.25.1/builtin/ls-files.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/ls-files.c 2021-11-24 19:29:21.000000000 +0000 @@ -12,6 +12,7 @@ #include "dir.h" #include "builtin.h" #include "tree.h" +#include "cache-tree.h" #include "parse-options.h" #include "resolve-undo.h" #include "string-list.h" @@ -35,6 +36,7 @@ static int debug_mode; static int show_eol; static int recurse_submodules; +static int skipping_duplicates; static const char *prefix; static int max_prefix_len; @@ -55,7 +57,7 @@ static const char *tag_skip_worktree = ""; static const char *tag_resolve_undo = ""; -static void write_eolinfo(const struct index_state *istate, +static void write_eolinfo(struct index_state *istate, const struct cache_entry *ce, const char *path) { if (show_eol) { @@ -120,7 +122,7 @@ } } -static void show_dir_entry(const struct index_state *istate, +static void show_dir_entry(struct index_state *istate, const char *tag, struct dir_entry *ent) { int len = max_prefix_len; @@ -128,15 +130,16 @@ if (len > ent->len) die("git ls-files: internal error - directory entry not superset of prefix"); - if (!dir_path_match(istate, ent, &pathspec, len, ps_matched)) - return; + /* If ps_matches is non-NULL, figure out which pathspec(s) match. */ + if (ps_matched) + dir_path_match(istate, ent, &pathspec, len, ps_matched); fputs(tag, stdout); write_eolinfo(istate, NULL, ent->name); write_name(ent->name); } -static void show_other_files(const struct index_state *istate, +static void show_other_files(struct index_state *istate, const struct dir_struct *dir) { int i; @@ -149,7 +152,7 @@ } } -static void show_killed_files(const struct index_state *istate, +static void show_killed_files(struct index_state *istate, const struct dir_struct *dir) { int i; @@ -206,10 +209,8 @@ struct dir_struct *dir, const char *path) { struct repository subrepo; - const struct submodule *sub = submodule_from_path(superproject, - &null_oid, path); - if (repo_submodule_init(&subrepo, superproject, sub)) + if (repo_submodule_init(&subrepo, superproject, path, null_oid())) return; if (repo_read_index(&subrepo) < 0) @@ -251,7 +252,7 @@ } } -static void show_ru_info(const struct index_state *istate) +static void show_ru_info(struct index_state *istate) { struct string_list_item *item; @@ -311,45 +312,61 @@ if (show_killed) show_killed_files(repo->index, dir); } - if (show_cached || show_stage) { - for (i = 0; i < repo->index->cache_nr; i++) { - const struct cache_entry *ce = repo->index->cache[i]; - construct_fullname(&fullname, repo, ce); + if (!(show_cached || show_stage || show_deleted || show_modified)) + return; + /* TODO: audit for interaction with sparse-index. */ + 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 ((dir->flags & DIR_SHOW_IGNORED) && - !ce_excluded(dir, repo->index, fullname.buf, ce)) - continue; - if (show_unmerged && !ce_stage(ce)) - continue; - if (ce->ce_flags & CE_UPDATE) - continue; + construct_fullname(&fullname, repo, ce); + + if ((dir->flags & DIR_SHOW_IGNORED) && + !ce_excluded(dir, repo->index, fullname.buf, ce)) + continue; + if (ce->ce_flags & CE_UPDATE) + continue; + if ((show_cached || show_stage) && + (!show_unmerged || ce_stage(ce))) { show_ce(repo, dir, ce, fullname.buf, ce_stage(ce) ? tag_unmerged : (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached)); + if (skipping_duplicates) + goto skip_to_next_name; } - } - if (show_deleted || show_modified) { - for (i = 0; i < repo->index->cache_nr; i++) { - const struct cache_entry *ce = repo->index->cache[i]; - struct stat st; - int err; - construct_fullname(&fullname, repo, ce); + if (!(show_deleted || show_modified)) + continue; + if (ce_skip_worktree(ce)) + continue; + stat_err = lstat(fullname.buf, &st); + if (stat_err && (errno != ENOENT && errno != ENOTDIR)) + error_errno("cannot lstat '%s'", fullname.buf); + if (stat_err && show_deleted) { + show_ce(repo, dir, ce, fullname.buf, tag_removed); + if (skipping_duplicates) + goto skip_to_next_name; + } + if (show_modified && + (stat_err || ie_modified(repo->index, ce, &st, 0))) { + show_ce(repo, dir, ce, fullname.buf, tag_modified); + if (skipping_duplicates) + goto skip_to_next_name; + } + continue; - if ((dir->flags & DIR_SHOW_IGNORED) && - !ce_excluded(dir, repo->index, fullname.buf, ce)) - continue; - if (ce->ce_flags & CE_UPDATE) - continue; - if (ce_skip_worktree(ce)) - continue; - err = lstat(fullname.buf, &st); - if (show_deleted && err) - show_ce(repo, dir, ce, fullname.buf, tag_removed); - if (show_modified && ie_modified(repo->index, ce, &st, 0)) - show_ce(repo, dir, ce, fullname.buf, tag_modified); +skip_to_next_name: + { + int j; + struct cache_entry **cache = repo->index->cache; + for (j = i + 1; j < repo->index->cache_nr; j++) + if (strcmp(ce->name, cache[j]->name)) + break; + i = j - 1; /* compensate for the for loop */ } } @@ -404,6 +421,53 @@ return common_prefix_len; } +static int read_one_entry_opt(struct index_state *istate, + const struct object_id *oid, + struct strbuf *base, + const char *pathname, + unsigned mode, int opt) +{ + int len; + struct cache_entry *ce; + + if (S_ISDIR(mode)) + return READ_TREE_RECURSIVE; + + len = strlen(pathname); + ce = make_empty_cache_entry(istate, base->len + len); + + ce->ce_mode = create_ce_mode(mode); + ce->ce_flags = create_ce_flags(1); + ce->ce_namelen = base->len + len; + memcpy(ce->name, base->buf, base->len); + memcpy(ce->name + base->len, pathname, len+1); + oidcpy(&ce->oid, oid); + return add_index_entry(istate, ce, opt); +} + +static int read_one_entry(const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode, + void *context) +{ + struct index_state *istate = context; + return read_one_entry_opt(istate, oid, base, pathname, + mode, + ADD_CACHE_OK_TO_ADD|ADD_CACHE_SKIP_DFCHECK); +} + +/* + * This is used when the caller knows there is no existing entries at + * the stage that will conflict with the entry being added. + */ +static int read_one_entry_quick(const struct object_id *oid, struct strbuf *base, + const char *pathname, unsigned mode, + void *context) +{ + struct index_state *istate = context; + return read_one_entry_opt(istate, oid, base, pathname, + mode, ADD_CACHE_JUST_APPEND); +} + /* * Read the tree specified with --with-tree option * (typically, HEAD) into stage #1 and then @@ -420,6 +484,8 @@ struct pathspec pathspec; struct cache_entry *last_stage0 = NULL; int i; + read_tree_fn_t fn = NULL; + int err; if (get_oid(tree_name, &oid)) die("tree-ish %s not found.", tree_name); @@ -428,6 +494,8 @@ die("bad tree-ish %s", tree_name); /* Hoist the unmerged entries up to stage #3 to make room */ + /* TODO: audit for interaction with sparse-index. */ + ensure_full_index(istate); for (i = 0; i < istate->cache_nr; i++) { struct cache_entry *ce = istate->cache[i]; if (!ce_stage(ce)) @@ -442,9 +510,32 @@ PATHSPEC_PREFER_CWD, prefix, matchbuf); } else memset(&pathspec, 0, sizeof(pathspec)); - if (read_tree(the_repository, tree, 1, &pathspec, istate)) + + /* + * See if we have cache entry at the stage. If so, + * do it the original slow way, otherwise, append and then + * sort at the end. + */ + for (i = 0; !fn && i < istate->cache_nr; i++) { + const struct cache_entry *ce = istate->cache[i]; + if (ce_stage(ce) == 1) + fn = read_one_entry; + } + + if (!fn) + fn = read_one_entry_quick; + err = read_tree(the_repository, tree, &pathspec, fn, istate); + if (err) die("unable to read tree entries %s", tree_name); + /* + * Sort the cache entry -- we need to nuke the cache tree, though. + */ + if (fn == read_one_entry_quick) { + cache_tree_free(&istate->cache_tree); + QSORT(istate->cache, istate->cache_nr, cmp_cache_name_compare); + } + for (i = 0; i < istate->cache_nr; i++) { struct cache_entry *ce = istate->cache[i]; switch (ce_stage(ce)) { @@ -514,14 +605,14 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) { int require_work_tree = 0, show_tag = 0, i; - const char *max_prefix; - struct dir_struct dir; + char *max_prefix; + struct dir_struct dir = DIR_INIT; struct pattern_list *pl; struct string_list exclude_list = STRING_LIST_INIT_NODUP; struct option builtin_ls_files_options[] = { /* Think twice before adding "--nul" synonym to this */ OPT_SET_INT('z', NULL, &line_terminator, - N_("paths are separated with NUL character"), '\0'), + N_("separate paths with the NUL character"), '\0'), OPT_BOOL('t', NULL, &show_tag, N_("identify the file status with tags")), OPT_BOOL('v', NULL, &show_valid_bit, @@ -554,18 +645,18 @@ N_("show unmerged files in the output")), OPT_BOOL(0, "resolve-undo", &show_resolve_undo, N_("show resolve-undo information")), - { OPTION_CALLBACK, 'x', "exclude", &exclude_list, N_("pattern"), + OPT_CALLBACK_F('x', "exclude", &exclude_list, N_("pattern"), N_("skip files matching pattern"), - PARSE_OPT_NONEG, option_parse_exclude }, - { OPTION_CALLBACK, 'X', "exclude-from", &dir, N_("file"), - N_("exclude patterns are read from "), - PARSE_OPT_NONEG, option_parse_exclude_from }, + PARSE_OPT_NONEG, option_parse_exclude), + OPT_CALLBACK_F('X', "exclude-from", &dir, N_("file"), + N_("read exclude patterns from "), + PARSE_OPT_NONEG, option_parse_exclude_from), OPT_STRING(0, "exclude-per-directory", &dir.exclude_per_dir, N_("file"), N_("read additional per-directory exclude patterns in ")), - { OPTION_CALLBACK, 0, "exclude-standard", &dir, NULL, + OPT_CALLBACK_F(0, "exclude-standard", &dir, NULL, N_("add the standard git exclusions"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, - option_parse_exclude_standard }, + option_parse_exclude_standard), OPT_SET_INT_F(0, "full-name", &prefix_len, N_("make the output relative to the project top directory"), 0, PARSE_OPT_NONEG), @@ -577,13 +668,15 @@ N_("pretend that paths removed since are still present")), OPT__ABBREV(&abbrev), OPT_BOOL(0, "debug", &debug_mode, N_("show debugging data")), + OPT_BOOL(0, "deduplicate", &skipping_duplicates, + N_("suppress duplicate entries")), OPT_END() }; + int ret = 0; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(ls_files_usage, builtin_ls_files_options); - memset(&dir, 0, sizeof(dir)); prefix = cmd_prefix; if (prefix) prefix_len = strlen(prefix); @@ -616,6 +709,8 @@ * you also show the stage information. */ show_stage = 1; + if (show_tag || show_stage) + skipping_duplicates = 0; if (dir.exclude_per_dir) exc_given = 1; @@ -655,6 +750,9 @@ if (pathspec.nr && error_unmatch) ps_matched = xcalloc(pathspec.nr, 1); + if ((dir.flags & DIR_SHOW_IGNORED) && !show_others && !show_cached) + die("ls-files -i must be used with either -o or -c"); + if ((dir.flags & DIR_SHOW_IGNORED) && !exc_given) die("ls-files --ignored needs some exclude pattern"); @@ -678,15 +776,13 @@ if (show_resolve_undo) show_ru_info(the_repository->index); - if (ps_matched) { - int bad; - bad = report_path_error(ps_matched, &pathspec); - if (bad) - fprintf(stderr, "Did you forget to 'git add'?\n"); - - return bad ? 1 : 0; + if (ps_matched && report_path_error(ps_matched, &pathspec)) { + fprintf(stderr, "Did you forget to 'git add'?\n"); + ret = 1; } - UNLEAK(dir); - return 0; + string_list_clear(&exclude_list, 0); + dir_clear(&dir); + free(max_prefix); + return ret; } diff -Nru git-2.25.1/builtin/ls-remote.c git-2.34.1/builtin/ls-remote.c --- git-2.25.1/builtin/ls-remote.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/ls-remote.c 2021-11-24 19:29:21.000000000 +0000 @@ -7,8 +7,8 @@ static const char * const ls_remote_usage[] = { N_("git ls-remote [--heads] [--tags] [--refs] [--upload-pack=]\n" - " [-q | --quiet] [--exit-code] [--get-url]\n" - " [--symref] [ [...]]"), + " [-q | --quiet] [--exit-code] [--get-url]\n" + " [--symref] [ [...]]"), NULL }; @@ -45,7 +45,8 @@ int show_symref_target = 0; const char *uploadpack = NULL; const char **pattern = NULL; - struct argv_array ref_prefixes = ARGV_ARRAY_INIT; + struct transport_ls_refs_options transport_options = + TRANSPORT_LS_REFS_OPTIONS_INIT; int i; struct string_list server_options = STRING_LIST_INIT_DUP; @@ -83,18 +84,22 @@ PARSE_OPT_STOP_AT_NON_OPTION); dest = argv[0]; + packet_trace_identity("ls-remote"); + + UNLEAK(sorting); + if (argc > 1) { int i; - pattern = xcalloc(argc, sizeof(const char *)); + CALLOC_ARRAY(pattern, argc); for (i = 1; i < argc; i++) { pattern[i - 1] = xstrfmt("*/%s", argv[i]); } } if (flags & REF_TAGS) - argv_array_push(&ref_prefixes, "refs/tags/"); + strvec_push(&transport_options.ref_prefixes, "refs/tags/"); if (flags & REF_HEADS) - argv_array_push(&ref_prefixes, "refs/heads/"); + strvec_push(&transport_options.ref_prefixes, "refs/heads/"); remote = remote_get(dest); if (!remote) { @@ -107,7 +112,6 @@ if (get_url) { printf("%s\n", *remote->url); - UNLEAK(sorting); return 0; } @@ -117,10 +121,10 @@ if (server_options.nr) transport->server_options = &server_options; - ref = transport_get_remote_refs(transport, &ref_prefixes); - if (transport_disconnect(transport)) { - UNLEAK(sorting); - return 1; + ref = transport_get_remote_refs(transport, &transport_options); + if (ref) { + int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); + repo_set_hash_algo(the_repository, hash_algo); } if (!dest && !quiet) @@ -146,7 +150,8 @@ status = 0; /* we found something */ } - UNLEAK(sorting); ref_array_clear(&ref_array); + if (transport_disconnect(transport)) + return 1; return status; } diff -Nru git-2.25.1/builtin/ls-tree.c git-2.34.1/builtin/ls-tree.c --- git-2.25.1/builtin/ls-tree.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/ls-tree.c 2021-11-24 19:29:21.000000000 +0000 @@ -62,7 +62,7 @@ } static int show_tree(const struct object_id *oid, struct strbuf *base, - const char *pathname, unsigned mode, int stage, void *context) + const char *pathname, unsigned mode, void *context) { int retval = 0; int baselen; @@ -185,6 +185,6 @@ tree = parse_tree_indirect(&oid); if (!tree) die("not a tree object"); - return !!read_tree_recursive(the_repository, tree, "", 0, 0, - &pathspec, show_tree, NULL); + return !!read_tree(the_repository, tree, + &pathspec, show_tree, NULL); } diff -Nru git-2.25.1/builtin/mailinfo.c git-2.34.1/builtin/mailinfo.c --- git-2.25.1/builtin/mailinfo.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/mailinfo.c 2021-11-24 19:29:21.000000000 +0000 @@ -7,54 +7,103 @@ #include "utf8.h" #include "strbuf.h" #include "mailinfo.h" +#include "parse-options.h" -static const char mailinfo_usage[] = - "git mailinfo [-k | -b] [-m | --message-id] [-u | --encoding= | -n] [--scissors | --no-scissors] < mail >info"; +static const char * const mailinfo_usage[] = { + /* TRANSLATORS: keep <> in "<" mail ">" info. */ + N_("git mailinfo [] < mail >info"), + NULL, +}; + +struct metainfo_charset +{ + enum { + CHARSET_DEFAULT, + CHARSET_NO_REENCODE, + CHARSET_EXPLICIT, + } policy; + const char *charset; +}; + +static int parse_opt_explicit_encoding(const struct option *opt, + const char *arg, int unset) +{ + struct metainfo_charset *meta_charset = opt->value; + + BUG_ON_OPT_NEG(unset); + + meta_charset->policy = CHARSET_EXPLICIT; + meta_charset->charset = arg; + + return 0; +} + +static int parse_opt_quoted_cr(const struct option *opt, const char *arg, int unset) +{ + BUG_ON_OPT_NEG(unset); + + if (mailinfo_parse_quoted_cr_action(arg, opt->value) != 0) + return error(_("bad action '%s' for '%s'"), arg, "--quoted-cr"); + return 0; +} int cmd_mailinfo(int argc, const char **argv, const char *prefix) { - const char *def_charset; + struct metainfo_charset meta_charset; struct mailinfo mi; int status; char *msgfile, *patchfile; + struct option options[] = { + OPT_BOOL('k', NULL, &mi.keep_subject, N_("keep subject")), + OPT_BOOL('b', NULL, &mi.keep_non_patch_brackets_in_subject, + N_("keep non patch brackets in subject")), + OPT_BOOL('m', "message-id", &mi.add_message_id, + N_("copy Message-ID to the end of commit message")), + OPT_SET_INT_F('u', NULL, &meta_charset.policy, + N_("re-code metadata to i18n.commitEncoding"), + CHARSET_DEFAULT, PARSE_OPT_NONEG), + OPT_SET_INT_F('n', NULL, &meta_charset.policy, + N_("disable charset re-coding of metadata"), + CHARSET_NO_REENCODE, PARSE_OPT_NONEG), + OPT_CALLBACK_F(0, "encoding", &meta_charset, N_("encoding"), + N_("re-code metadata to this encoding"), + PARSE_OPT_NONEG, parse_opt_explicit_encoding), + OPT_BOOL(0, "scissors", &mi.use_scissors, N_("use scissors")), + OPT_CALLBACK_F(0, "quoted-cr", &mi.quoted_cr, N_(""), + N_("action when quoted CR is found"), + PARSE_OPT_NONEG, parse_opt_quoted_cr), + OPT_HIDDEN_BOOL(0, "inbody-headers", &mi.use_inbody_headers, + N_("use headers in message's body")), + OPT_END() + }; + setup_mailinfo(&mi); + meta_charset.policy = CHARSET_DEFAULT; - def_charset = get_commit_output_encoding(); - mi.metainfo_charset = def_charset; + argc = parse_options(argc, argv, prefix, options, mailinfo_usage, 0); - while (1 < argc && argv[1][0] == '-') { - if (!strcmp(argv[1], "-k")) - mi.keep_subject = 1; - else if (!strcmp(argv[1], "-b")) - mi.keep_non_patch_brackets_in_subject = 1; - else if (!strcmp(argv[1], "-m") || !strcmp(argv[1], "--message-id")) - mi.add_message_id = 1; - else if (!strcmp(argv[1], "-u")) - mi.metainfo_charset = def_charset; - else if (!strcmp(argv[1], "-n")) - mi.metainfo_charset = NULL; - else if (starts_with(argv[1], "--encoding=")) - mi.metainfo_charset = argv[1] + 11; - else if (!strcmp(argv[1], "--scissors")) - mi.use_scissors = 1; - else if (!strcmp(argv[1], "--no-scissors")) - mi.use_scissors = 0; - else if (!strcmp(argv[1], "--no-inbody-headers")) - mi.use_inbody_headers = 0; - else - usage(mailinfo_usage); - argc--; argv++; - } + if (argc != 2) + usage_with_options(mailinfo_usage, options); - if (argc != 3) - usage(mailinfo_usage); + switch (meta_charset.policy) { + case CHARSET_DEFAULT: + mi.metainfo_charset = get_commit_output_encoding(); + break; + case CHARSET_NO_REENCODE: + mi.metainfo_charset = NULL; + break; + case CHARSET_EXPLICIT: + break; + default: + BUG("invalid meta_charset.policy"); + } mi.input = stdin; mi.output = stdout; - msgfile = prefix_filename(prefix, argv[1]); - patchfile = prefix_filename(prefix, argv[2]); + msgfile = prefix_filename(prefix, argv[0]); + patchfile = prefix_filename(prefix, argv[1]); status = !!mailinfo(&mi, msgfile, patchfile); clear_mailinfo(&mi); diff -Nru git-2.25.1/builtin/mailsplit.c git-2.34.1/builtin/mailsplit.c --- git-2.25.1/builtin/mailsplit.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/mailsplit.c 2021-11-24 19:29:21.000000000 +0000 @@ -75,9 +75,7 @@ fprintf(stderr, "corrupt mailbox\n"); exit(1); } - fd = open(name, O_WRONLY | O_CREAT | O_EXCL, 0666); - if (fd < 0) - die_errno("cannot open output file '%s'", name); + fd = xopen(name, O_WRONLY | O_CREAT | O_EXCL, 0666); output = xfdopen(fd, "w"); /* Copy it out, while searching for a line that begins with diff -Nru git-2.25.1/builtin/merge-base.c git-2.34.1/builtin/merge-base.c --- git-2.25.1/builtin/merge-base.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/merge-base.c 2021-11-24 19:29:21.000000000 +0000 @@ -114,26 +114,16 @@ static int handle_fork_point(int argc, const char **argv) { struct object_id oid; - char *refname; struct commit *derived, *fork_point; const char *commitname; - switch (dwim_ref(argv[0], strlen(argv[0]), &oid, &refname)) { - case 0: - die("No such ref: '%s'", argv[0]); - case 1: - break; /* good */ - default: - die("Ambiguous refname: '%s'", argv[0]); - } - commitname = (argc == 2) ? argv[1] : "HEAD"; if (get_oid(commitname, &oid)) die("Not a valid object name: '%s'", commitname); derived = lookup_commit_reference(the_repository, &oid); - fork_point = get_fork_point(refname, derived); + fork_point = get_fork_point(argv[0], derived); if (!fork_point) return 1; diff -Nru git-2.25.1/builtin/merge.c git-2.34.1/builtin/merge.c --- git-2.25.1/builtin/merge.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/merge.c 2021-11-24 19:29:21.000000000 +0000 @@ -13,7 +13,9 @@ #include "builtin.h" #include "lockfile.h" #include "run-command.h" +#include "hook.h" #include "diff.h" +#include "diff-merges.h" #include "refs.h" #include "refspec.h" #include "commit.h" @@ -28,6 +30,7 @@ #include "rerere.h" #include "help.h" #include "merge-recursive.h" +#include "merge-ort-wrappers.h" #include "resolve-undo.h" #include "remote.h" #include "fmt-merge-msg.h" @@ -40,6 +43,7 @@ #include "branch.h" #include "commit-reach.h" #include "wt-status.h" +#include "commit-graph.h" #define DEFAULT_TWOHEAD (1<<0) #define DEFAULT_OCTOPUS (1<<1) @@ -53,8 +57,8 @@ static const char * const builtin_merge_usage[] = { N_("git merge [] [...]"), - N_("git merge --abort"), - N_("git merge --continue"), + "git merge --abort", + "git merge --continue", NULL }; @@ -62,6 +66,7 @@ static int option_commit = -1; static int option_edit = -1; static int allow_trivial = 1, have_message, verify_signatures; +static int check_trust_level = 1; static int overwrite_ignore = 1; static struct strbuf merge_msg = STRBUF_INIT; static struct strategy **use_strategies; @@ -70,7 +75,6 @@ static size_t xopts_nr, xopts_alloc; static const char *branch; static char *branch_mergeoptions; -static int option_renormalize; static int verbosity; static int allow_rerere_auto; static int abort_current_merge; @@ -81,11 +85,13 @@ static int default_to_upstream = 1; static int signoff; static const char *sign_commit; +static int autostash; static int no_verify; static struct strategy all_strategy[] = { - { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, + { "recursive", NO_TRIVIAL }, { "octopus", DEFAULT_OCTOPUS }, + { "ort", DEFAULT_TWOHEAD | NO_TRIVIAL }, { "resolve", 0 }, { "ours", NO_FAST_FORWARD | NO_TRIVIAL }, { "subtree", NO_FAST_FORWARD | NO_TRIVIAL }, @@ -157,10 +163,17 @@ struct strategy *ret; static struct cmdnames main_cmds, other_cmds; static int loaded; + char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM"); if (!name) return NULL; + if (default_strategy && + !strcmp(default_strategy, "ort") && + !strcmp(name, "recursive")) { + name = "ort"; + } + for (i = 0; i < ARRAY_SIZE(all_strategy); i++) if (!strcmp(name, all_strategy[i].name)) return &all_strategy[i]; @@ -198,7 +211,7 @@ exit(1); } - ret = xcalloc(1, sizeof(struct strategy)); + CALLOC_ARRAY(ret, 1); ret->name = xstrdup(name); ret->attr = NO_TRIVIAL; return ret; @@ -240,9 +253,9 @@ } static struct option builtin_merge_options[] = { - { OPTION_CALLBACK, 'n', NULL, NULL, NULL, + OPT_CALLBACK_F('n', NULL, NULL, NULL, N_("do not show a diffstat at the end of the merge"), - PARSE_OPT_NOARG, option_parse_n }, + PARSE_OPT_NOARG, option_parse_n), 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)")), @@ -285,8 +298,9 @@ OPT_SET_INT(0, "progress", &show_progress, N_("force progress reporting"), 1), { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_AUTOSTASH(&autostash), OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")), - OPT_BOOL(0, "signoff", &signoff, N_("add Signed-off-by:")), + OPT_BOOL(0, "signoff", &signoff, N_("add a Signed-off-by trailer")), OPT_BOOL(0, "no-verify", &no_verify, N_("bypass pre-merge-commit and commit-msg hooks")), OPT_END() }; @@ -380,10 +394,14 @@ } /* This is called when no merge was necessary. */ -static void finish_up_to_date(const char *msg) +static void finish_up_to_date(void) { - if (verbosity >= 0) - printf("%s%s\n", squash ? _(" (nothing to squash)") : "", msg); + if (verbosity >= 0) { + if (squash) + puts(_("Already up to date. (nothing to squash)")); + else + puts(_("Already up to date.")); + } remove_merge_branch_state(the_repository); } @@ -397,7 +415,7 @@ printf(_("Squash commit -- not updating HEAD\n")); repo_init_revisions(the_repository, &rev, NULL); - rev.ignore_merges = 1; + diff_merges_suppress(&rev); rev.commit_format = CMIT_FMT_MEDIUM; commit->object.flags |= UNINTERESTING; @@ -446,15 +464,13 @@ if (verbosity >= 0 && !merge_msg.len) printf(_("No merge message -- not updating HEAD\n")); else { - const char *argv_gc_auto[] = { "gc", "--auto", NULL }; update_ref(reflog_message.buf, "HEAD", new_head, head, 0, UPDATE_REFS_DIE_ON_ERR); /* * We ignore errors in 'gc --auto', since the * user should see them. */ - close_object_store(the_repository->objects); - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); + run_auto_maintenance(verbosity < 0); } } if (new_head && show_diffstat) { @@ -474,6 +490,7 @@ /* Run a post-merge hook */ run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL); + apply_autostash(git_path_merge_autostash(the_repository)); strbuf_release(&reflog_message); } @@ -486,7 +503,7 @@ struct strbuf bname = STRBUF_INIT; struct merge_remote_desc *desc; const char *ptr; - char *found_ref; + char *found_ref = NULL; int len, early; strbuf_branchname(&bname, remote, 0); @@ -497,7 +514,7 @@ if (!remote_head) die(_("'%s' does not point to a commit"), remote); - if (dwim_ref(remote, strlen(remote), &branch_head, &found_ref) > 0) { + if (dwim_ref(remote, strlen(remote), &branch_head, &found_ref, 0) > 0) { if (starts_with(found_ref, "refs/heads/")) { strbuf_addf(msg, "%s\t\tbranch '%s' of .\n", oid_to_hex(&branch_head), remote); @@ -569,6 +586,7 @@ strbuf_addf(msg, "%s\t\tcommit '%s'\n", oid_to_hex(&remote_head->object.oid), remote); cleanup: + free(found_ref); strbuf_release(&buf); strbuf_release(&bname); } @@ -596,10 +614,12 @@ static int git_merge_config(const char *k, const char *v, void *cb) { int status; + const char *str; - if (branch && starts_with(k, "branch.") && - starts_with(k + 7, branch) && - !strcmp(k + 7 + strlen(branch), ".mergeoptions")) { + if (branch && + skip_prefix(k, "branch.", &str) && + skip_prefix(str, branch, &str) && + !strcmp(str, ".mergeoptions")) { free(branch_mergeoptions); branch_mergeoptions = xstrdup(v); return 0; @@ -615,8 +635,6 @@ return git_config_string(&pull_octopus, k, v); else if (!strcmp(k, "commit.cleanup")) return git_config_string(&cleanup_arg, k, v); - else if (!strcmp(k, "merge.renormalize")) - option_renormalize = git_config_bool(k, v); else if (!strcmp(k, "merge.ff")) { int boolval = git_parse_maybe_bool(v); if (0 <= boolval) { @@ -631,6 +649,11 @@ } else if (!strcmp(k, "commit.gpgsign")) { sign_commit = git_config_bool(k, v) ? "" : NULL; return 0; + } else if (!strcmp(k, "gpg.mintrustlevel")) { + check_trust_level = 0; + } else if (!strcmp(k, "merge.autostash")) { + autostash = git_config_bool(k, v); + return 0; } status = fmt_merge_msg_config(k, v, cb); @@ -658,6 +681,7 @@ opts.verbose_update = 1; opts.trivial_merges_only = 1; opts.merge = 1; + opts.preserve_ignored = 0; /* FIXME: !overwrite_ignore */ trees[nr_trees] = parse_tree_indirect(common); if (!trees[nr_trees++]) return -1; @@ -693,7 +717,8 @@ if (refresh_and_write_cache(REFRESH_QUIET, SKIP_IF_UNCHANGED, 0) < 0) return error(_("Unable to write index.")); - if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) { + if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree") || + !strcmp(strategy, "ort")) { struct lock_file lock = LOCK_INIT; int clean, x; struct commit *result; @@ -710,13 +735,12 @@ if (!strcmp(strategy, "subtree")) o.subtree_shift = ""; - o.renormalize = option_renormalize; o.show_rename_progress = show_progress == -1 ? isatty(2) : show_progress; for (x = 0; x < xopts_nr; x++) if (parse_merge_opt(&o, xopts[x])) - die(_("Unknown option for merge-recursive: -X%s"), xopts[x]); + die(_("unknown strategy option: -X%s"), xopts[x]); o.branch1 = head_arg; o.branch2 = merge_remote_util(remoteheads->item)->name; @@ -725,8 +749,12 @@ commit_list_insert(j->item, &reversed); hold_locked_index(&lock, LOCK_DIE_ON_ERROR); - clean = merge_recursive(&o, head, - remoteheads->item, reversed, &result); + if (!strcmp(strategy, "ort")) + clean = merge_ort_recursive(&o, head, remoteheads->item, + reversed, &result); + else + clean = merge_recursive(&o, head, remoteheads->item, + reversed, &result); if (clean < 0) exit(128); if (write_locked_index(&the_index, &lock, @@ -822,7 +850,7 @@ * and write it out as a tree. We must do this before we invoke * the editor and after we invoke run_status above. */ - if (find_hook("pre-merge-commit")) + if (hook_exists("pre-merge-commit")) discard_cache(); read_cache_from(index_file); strbuf_addbuf(&msg, &merge_msg); @@ -835,9 +863,11 @@ strbuf_commented_addf(&msg, "\n"); } strbuf_commented_addf(&msg, _(merge_editor_comment)); - strbuf_commented_addf(&msg, _(cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS ? - scissors_editor_comment : - no_scissors_editor_comment), comment_line_char); + if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) + strbuf_commented_addf(&msg, _(scissors_editor_comment)); + else + strbuf_commented_addf(&msg, + _(no_scissors_editor_comment), comment_line_char); } if (signoff) append_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0); @@ -1109,9 +1139,7 @@ merge_names = &fetch_head_file; filename = git_path_fetch_head(the_repository); - fd = open(filename, O_RDONLY); - if (fd < 0) - die_errno(_("could not open '%s' for reading"), filename); + fd = xopen(filename, O_RDONLY); if (strbuf_read(merge_names, fd, 0) < 0) die_errno(_("could not read '%s'"), filename); @@ -1249,6 +1277,9 @@ if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_merge_usage, builtin_merge_options); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + /* * Check if we are _not_ on a detached HEAD, i.e. if there is a * current branch. @@ -1257,6 +1288,12 @@ if (branch) skip_prefix(branch, "refs/heads/", &branch); + if (!pull_twohead) { + char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM"); + if (default_strategy && !strcmp(default_strategy, "ort")) + pull_twohead = "ort"; + } + init_diff_ui_defaults(); git_config(git_merge_config, NULL); @@ -1278,6 +1315,7 @@ if (abort_current_merge) { int nargc = 2; const char *nargv[] = {"reset", "--merge", NULL}; + struct strbuf stash_oid = STRBUF_INIT; if (orig_argc != 2) usage_msg_opt(_("--abort expects no arguments"), @@ -1286,8 +1324,17 @@ if (!file_exists(git_path_merge_head(the_repository))) die(_("There is no merge to abort (MERGE_HEAD missing).")); + if (read_oneliner(&stash_oid, git_path_merge_autostash(the_repository), + READ_ONELINER_SKIP_IF_EMPTY)) + unlink(git_path_merge_autostash(the_repository)); + /* Invoke 'git reset --merge' */ ret = cmd_reset(nargc, nargv, prefix); + + if (stash_oid.len) + apply_autostash_oid(stash_oid.buf); + + strbuf_release(&stash_oid); goto done; } @@ -1325,14 +1372,14 @@ * There is no unmerged entry, don't advise 'git * add/rm ', just 'git commit'. */ - if (advice_resolve_conflict) + if (advice_enabled(ADVICE_RESOLVE_CONFLICT)) die(_("You have not concluded your merge (MERGE_HEAD exists).\n" "Please, commit your changes before you merge.")); else die(_("You have not concluded your merge (MERGE_HEAD exists).")); } - if (file_exists(git_path_cherry_pick_head(the_repository))) { - if (advice_resolve_conflict) + if (ref_exists("CHERRY_PICK_HEAD")) { + if (advice_enabled(ADVICE_RESOLVE_CONFLICT)) die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n" "Please, commit your changes before you merge.")); else @@ -1397,7 +1444,8 @@ die(_("Can merge only exactly one commit into empty head")); if (verify_signatures) - verify_merge_signature(remoteheads->item, verbosity); + verify_merge_signature(remoteheads->item, verbosity, + check_trust_level); remote_head_oid = &remoteheads->item->object.oid; read_empty(remote_head_oid, 0); @@ -1420,7 +1468,8 @@ if (verify_signatures) { for (p = remoteheads; p; p = p->next) { - verify_merge_signature(p->item, verbosity); + verify_merge_signature(p->item, verbosity, + check_trust_level); } } @@ -1440,6 +1489,12 @@ fast_forward = FF_NO; } + if (!use_strategies && !pull_twohead && + remoteheads && !remoteheads->next) { + char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM"); + if (default_strategy) + append_strategy(get_strategy(default_strategy)); + } if (!use_strategies) { if (!remoteheads) ; /* already up-to-date */ @@ -1482,7 +1537,7 @@ * If head can reach all the merge then we are up to date. * but first the most common case of merging one remote. */ - finish_up_to_date(_("Already up to date.")); + finish_up_to_date(); goto done; } else if (fast_forward != FF_NO && !remoteheads->next && !common->next && @@ -1508,16 +1563,22 @@ goto done; } + if (autostash) + create_autostash(the_repository, + git_path_merge_autostash(the_repository), + "merge"); if (checkout_fast_forward(the_repository, &head_commit->object.oid, &commit->object.oid, overwrite_ignore)) { + apply_autostash(git_path_merge_autostash(the_repository)); ret = 1; goto done; } finish(head_commit, remoteheads, &commit->object.oid, msg.buf); remove_merge_branch_state(the_repository); + strbuf_release(&msg); goto done; } else if (!remoteheads->next && common->next) ; @@ -1566,13 +1627,18 @@ } } if (up_to_date) { - finish_up_to_date(_("Already up to date. Yeeah!")); + finish_up_to_date(); goto done; } } if (fast_forward == FF_ONLY) - die(_("Not possible to fast-forward, aborting.")); + die_ff_impossible(); + + if (autostash) + create_autostash(the_repository, + git_path_merge_autostash(the_repository), + "merge"); /* We are going to make a new commit. */ git_committer_info(IDENT_STRICT); @@ -1624,7 +1690,7 @@ } merge_was_ok = 1; } - cnt = evaluate_result(); + cnt = (use_strategies_nr > 1) ? evaluate_result() : 0; if (best_cnt <= 0 || cnt <= best_cnt) { best_strategy = use_strategies[i]->name; best_cnt = cnt; @@ -1655,6 +1721,7 @@ else fprintf(stderr, _("Merge with strategy %s failed.\n"), use_strategies[0]->name); + apply_autostash(git_path_merge_autostash(the_repository)); ret = 2; goto done; } else if (best_strategy == wt_strategy) @@ -1662,15 +1729,17 @@ else { printf(_("Rewinding the tree to pristine...\n")); restore_state(&head_commit->object.oid, &stash); - printf(_("Using the %s to prepare resolving by hand.\n"), + printf(_("Using the %s strategy to prepare resolving by hand.\n"), best_strategy); try_merge_strategy(best_strategy, common, remoteheads, head_commit); } - if (squash) + if (squash) { finish(head_commit, remoteheads, NULL, NULL); - else + + git_test_write_commit_graph_or_die(); + } else write_merge_state(remoteheads); if (merge_was_ok) @@ -1680,6 +1749,7 @@ ret = suggest_conflicts(); done: + strbuf_release(&buf); free(branch_to_free); return ret; } diff -Nru git-2.25.1/builtin/merge-index.c git-2.34.1/builtin/merge-index.c --- git-2.25.1/builtin/merge-index.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/merge-index.c 2021-11-24 19:29:21.000000000 +0000 @@ -58,6 +58,8 @@ static void merge_all(void) { int i; + /* TODO: audit for interaction with sparse-index. */ + ensure_full_index(&the_index); for (i = 0; i < active_nr; i++) { const struct cache_entry *ce = active_cache[i]; if (!ce_stage(ce)) @@ -80,6 +82,9 @@ read_cache(); + /* TODO: audit for interaction with sparse-index. */ + ensure_full_index(&the_index); + i = 1; if (!strcmp(argv[i], "-o")) { one_shot = 1; diff -Nru git-2.25.1/builtin/merge-ours.c git-2.34.1/builtin/merge-ours.c --- git-2.25.1/builtin/merge-ours.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/merge-ours.c 2021-11-24 19:29:21.000000000 +0000 @@ -28,6 +28,6 @@ if (read_cache() < 0) die_errno("read_cache failed"); if (index_differs_from(the_repository, "HEAD", NULL, 0)) - exit(2); - exit(0); + return 2; + return 0; } diff -Nru git-2.25.1/builtin/merge-tree.c git-2.34.1/builtin/merge-tree.c --- git-2.25.1/builtin/merge-tree.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/merge-tree.c 2021-11-24 19:29:21.000000000 +0000 @@ -107,14 +107,12 @@ mmfile_t src, dst; xpparam_t xpp; xdemitconf_t xecfg; - xdemitcb_t ecb; + xdemitcb_t ecb = { .out_line = show_outf }; + memset(&xpp, 0, sizeof(xpp)); xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; - ecb.out_hunk = NULL; - ecb.out_line = show_outf; - ecb.priv = NULL; src.ptr = origin(entry, &size); if (!src.ptr) diff -Nru git-2.25.1/builtin/mktag.c git-2.34.1/builtin/mktag.c --- git-2.25.1/builtin/mktag.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/mktag.c 2021-11-24 19:29:21.000000000 +0000 @@ -1,176 +1,109 @@ #include "builtin.h" +#include "parse-options.h" #include "tag.h" #include "replace-object.h" #include "object-store.h" +#include "fsck.h" +#include "config.h" -/* - * A signature file has a very simple fixed format: four lines - * of "object " + "type " + "tag " + - * "tagger ", followed by a blank line, a free-form tag - * message and a signature block that git itself doesn't care about, - * but that can be verified with gpg or similar. - * - * The first four lines are guaranteed to be at least 83 bytes: - * "object \n" is 48 bytes, "type tag\n" at 9 bytes is the - * shortest possible type-line, "tag .\n" at 6 bytes is the shortest - * single-character-tag line, and "tagger . <> 0 +0000\n" at 20 bytes is - * the shortest possible tagger-line. - */ - -/* - * We refuse to tag something we can't verify. Just because. - */ -static int verify_object(const struct object_id *oid, const char *expected_type) +static char const * const builtin_mktag_usage[] = { + N_("git mktag"), + NULL +}; +static int option_strict = 1; + +static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; + +static int mktag_fsck_error_func(struct fsck_options *o, + const struct object_id *oid, + enum object_type object_type, + enum fsck_msg_type msg_type, + enum fsck_msg_id msg_id, + const char *message) { - int ret = -1; - enum object_type type; - unsigned long size; - void *buffer = read_object_file(oid, &type, &size); - const struct object_id *repl = lookup_replace_object(the_repository, oid); - - if (buffer) { - if (type == type_from_string(expected_type)) - ret = check_object_signature(repl, buffer, size, expected_type); - free(buffer); + switch (msg_type) { + case FSCK_WARN: + if (!option_strict) { + fprintf_ln(stderr, _("warning: tag input does not pass fsck: %s"), message); + return 0; + + } + /* fallthrough */ + case FSCK_ERROR: + /* + * We treat both warnings and errors as errors, things + * like missing "tagger" lines are "only" warnings + * under fsck, we've always considered them an error. + */ + fprintf_ln(stderr, _("error: tag input does not pass fsck: %s"), message); + return 1; + default: + BUG(_("%d (FSCK_IGNORE?) should never trigger this callback"), + msg_type); } - return ret; } -static int verify_tag(char *buffer, unsigned long size) +static int verify_object_in_tag(struct object_id *tagged_oid, int *tagged_type) { - int typelen; - char type[20]; - struct object_id oid; - const char *object, *type_line, *tag_line, *tagger_line, *lb, *rb, *p; - size_t len; - - if (size < 84) - return error("wanna fool me ? you obviously got the size wrong !"); - - buffer[size] = 0; - - /* Verify object line */ - object = buffer; - if (memcmp(object, "object ", 7)) - return error("char%d: does not start with \"object \"", 0); - - if (parse_oid_hex(object + 7, &oid, &p)) - return error("char%d: could not get SHA1 hash", 7); - - /* Verify type line */ - type_line = p + 1; - if (memcmp(type_line - 1, "\ntype ", 6)) - return error("char%d: could not find \"\\ntype \"", 47); - - /* Verify tag-line */ - tag_line = strchr(type_line, '\n'); - if (!tag_line) - return error("char%"PRIuMAX": could not find next \"\\n\"", - (uintmax_t) (type_line - buffer)); - tag_line++; - if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n') - return error("char%"PRIuMAX": no \"tag \" found", - (uintmax_t) (tag_line - buffer)); - - /* Get the actual type */ - typelen = tag_line - type_line - strlen("type \n"); - if (typelen >= sizeof(type)) - return error("char%"PRIuMAX": type too long", - (uintmax_t) (type_line+5 - buffer)); - - memcpy(type, type_line+5, typelen); - type[typelen] = 0; - - /* Verify that the object matches */ - if (verify_object(&oid, type)) - return error("char%d: could not verify object %s", 7, oid_to_hex(&oid)); - - /* Verify the tag-name: we don't allow control characters or spaces in it */ - tag_line += 4; - for (;;) { - unsigned char c = *tag_line++; - if (c == '\n') - break; - if (c > ' ') - continue; - return error("char%"PRIuMAX": could not verify tag name", - (uintmax_t) (tag_line - buffer)); - } - - /* Verify the tagger line */ - tagger_line = tag_line; + int ret; + enum object_type type; + unsigned long size; + void *buffer; + const struct object_id *repl; - if (memcmp(tagger_line, "tagger ", 7)) - return error("char%"PRIuMAX": could not find \"tagger \"", - (uintmax_t) (tagger_line - buffer)); - - /* - * Check for correct form for name and email - * i.e. " <" followed by "> " on _this_ line - * No angle brackets within the name or email address fields. - * No spaces within the email address field. - */ - tagger_line += 7; - if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) || - strpbrk(tagger_line, "<>\n") != lb+1 || - strpbrk(lb+2, "><\n ") != rb) - return error("char%"PRIuMAX": malformed tagger field", - (uintmax_t) (tagger_line - buffer)); - - /* Check for author name, at least one character, space is acceptable */ - if (lb == tagger_line) - return error("char%"PRIuMAX": missing tagger name", - (uintmax_t) (tagger_line - buffer)); - - /* timestamp, 1 or more digits followed by space */ - tagger_line = rb + 2; - if (!(len = strspn(tagger_line, "0123456789"))) - return error("char%"PRIuMAX": missing tag timestamp", - (uintmax_t) (tagger_line - buffer)); - tagger_line += len; - if (*tagger_line != ' ') - return error("char%"PRIuMAX": malformed tag timestamp", - (uintmax_t) (tagger_line - buffer)); - tagger_line++; - - /* timezone, 5 digits [+-]hhmm, max. 1400 */ - if (!((tagger_line[0] == '+' || tagger_line[0] == '-') && - strspn(tagger_line+1, "0123456789") == 4 && - tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400)) - return error("char%"PRIuMAX": malformed tag timezone", - (uintmax_t) (tagger_line - buffer)); - tagger_line += 6; - - /* Verify the blank line separating the header from the body */ - if (*tagger_line != '\n') - return error("char%"PRIuMAX": trailing garbage in tag header", - (uintmax_t) (tagger_line - buffer)); + buffer = read_object_file(tagged_oid, &type, &size); + if (!buffer) + die(_("could not read tagged object '%s'"), + oid_to_hex(tagged_oid)); + if (type != *tagged_type) + die(_("object '%s' tagged as '%s', but is a '%s' type"), + oid_to_hex(tagged_oid), + type_name(*tagged_type), type_name(type)); + + repl = lookup_replace_object(the_repository, tagged_oid); + ret = check_object_signature(the_repository, repl, + buffer, size, type_name(*tagged_type), + NULL); + free(buffer); - /* The actual stuff afterwards we don't care about.. */ - return 0; + return ret; } int cmd_mktag(int argc, const char **argv, const char *prefix) { + static struct option builtin_mktag_options[] = { + OPT_BOOL(0, "strict", &option_strict, + N_("enable more strict checking")), + OPT_END(), + }; struct strbuf buf = STRBUF_INIT; + struct object_id tagged_oid; + int tagged_type; struct object_id result; - if (argc != 1) - usage("git mktag"); - - if (strbuf_read(&buf, 0, 4096) < 0) { - die_errno("could not read from stdin"); - } + argc = parse_options(argc, argv, NULL, + builtin_mktag_options, + builtin_mktag_usage, 0); + + if (strbuf_read(&buf, 0, 0) < 0) + die_errno(_("could not read from stdin")); + + fsck_options.error_func = mktag_fsck_error_func; + fsck_set_msg_type_from_ids(&fsck_options, FSCK_MSG_EXTRA_HEADER_ENTRY, + FSCK_WARN); + /* config might set fsck.extraHeaderEntry=* again */ + git_config(git_fsck_config, &fsck_options); + if (fsck_tag_standalone(NULL, buf.buf, buf.len, &fsck_options, + &tagged_oid, &tagged_type)) + die(_("tag on stdin did not pass our strict fsck check")); - /* Verify it for some basic sanity: it needs to start with - "object \ntype\ntagger " */ - if (verify_tag(buf.buf, buf.len) < 0) - die("invalid tag signature file"); + if (verify_object_in_tag(&tagged_oid, &tagged_type)) + die(_("tag on stdin did not refer to a valid object")); if (write_object_file(buf.buf, buf.len, tag_type, &result) < 0) - die("unable to write tag file"); + die(_("unable to write tag file")); strbuf_release(&buf); - printf("%s\n", oid_to_hex(&result)); + puts(oid_to_hex(&result)); return 0; } diff -Nru git-2.25.1/builtin/mktree.c git-2.34.1/builtin/mktree.c --- git-2.25.1/builtin/mktree.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/mktree.c 2021-11-24 19:29:21.000000000 +0000 @@ -189,5 +189,5 @@ used=0; /* reset tree entry buffer for re-use in batch mode */ } strbuf_release(&sb); - exit(0); + return 0; } diff -Nru git-2.25.1/builtin/multi-pack-index.c git-2.34.1/builtin/multi-pack-index.c --- git-2.25.1/builtin/multi-pack-index.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/multi-pack-index.c 2021-11-24 19:29:21.000000000 +0000 @@ -4,67 +4,256 @@ #include "parse-options.h" #include "midx.h" #include "trace2.h" +#include "object-store.h" +#define BUILTIN_MIDX_WRITE_USAGE \ + N_("git multi-pack-index [] write [--preferred-pack=]" \ + "[--refs-snapshot=]") + +#define BUILTIN_MIDX_VERIFY_USAGE \ + N_("git multi-pack-index [] verify") + +#define BUILTIN_MIDX_EXPIRE_USAGE \ + N_("git multi-pack-index [] expire") + +#define BUILTIN_MIDX_REPACK_USAGE \ + N_("git multi-pack-index [] repack [--batch-size=]") + +static char const * const builtin_multi_pack_index_write_usage[] = { + BUILTIN_MIDX_WRITE_USAGE, + NULL +}; +static char const * const builtin_multi_pack_index_verify_usage[] = { + BUILTIN_MIDX_VERIFY_USAGE, + NULL +}; +static char const * const builtin_multi_pack_index_expire_usage[] = { + BUILTIN_MIDX_EXPIRE_USAGE, + NULL +}; +static char const * const builtin_multi_pack_index_repack_usage[] = { + BUILTIN_MIDX_REPACK_USAGE, + NULL +}; static char const * const builtin_multi_pack_index_usage[] = { - N_("git multi-pack-index [] (write|verify|expire|repack --batch-size=)"), + BUILTIN_MIDX_WRITE_USAGE, + BUILTIN_MIDX_VERIFY_USAGE, + BUILTIN_MIDX_EXPIRE_USAGE, + BUILTIN_MIDX_REPACK_USAGE, NULL }; static struct opts_multi_pack_index { const char *object_dir; + const char *preferred_pack; + const char *refs_snapshot; unsigned long batch_size; - int progress; + unsigned flags; + int stdin_packs; } opts; -int cmd_multi_pack_index(int argc, const char **argv, - const char *prefix) +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_END(), +}; + +static struct option *add_common_options(struct option *prev) +{ + return parse_options_concat(common_opts, prev); +} + +static int git_multi_pack_index_write_config(const char *var, const char *value, + void *cb) { - unsigned flags = 0; + if (!strcmp(var, "pack.writebitmaphashcache")) { + if (git_config_bool(var, value)) + opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE; + else + opts.flags &= ~MIDX_WRITE_BITMAP_HASH_CACHE; + } + + /* + * We should never make a fall-back call to 'git_default_config', since + * this was already called in 'cmd_multi_pack_index()'. + */ + return 0; +} + +static void read_packs_from_stdin(struct string_list *to) +{ + struct strbuf buf = STRBUF_INIT; + while (strbuf_getline(&buf, stdin) != EOF) + string_list_append(to, buf.buf); + string_list_sort(to); + + strbuf_release(&buf); +} + +static int cmd_multi_pack_index_write(int argc, const char **argv) +{ + struct option *options; + static struct option builtin_multi_pack_index_write_options[] = { + OPT_STRING(0, "preferred-pack", &opts.preferred_pack, + N_("preferred-pack"), + N_("pack for reuse when computing a multi-pack bitmap")), + OPT_BIT(0, "bitmap", &opts.flags, N_("write multi-pack bitmap"), + MIDX_WRITE_BITMAP | MIDX_WRITE_REV_INDEX), + OPT_BIT(0, "progress", &opts.flags, + N_("force progress reporting"), MIDX_PROGRESS), + OPT_BOOL(0, "stdin-packs", &opts.stdin_packs, + N_("write multi-pack index containing only given indexes")), + OPT_FILENAME(0, "refs-snapshot", &opts.refs_snapshot, + N_("refs snapshot for selecting bitmap commits")), + OPT_END(), + }; + + opts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE; + + git_config(git_multi_pack_index_write_config, NULL); + + options = add_common_options(builtin_multi_pack_index_write_options); + + trace2_cmd_mode(argv[0]); + + if (isatty(2)) + opts.flags |= MIDX_PROGRESS; + argc = parse_options(argc, argv, NULL, + options, builtin_multi_pack_index_write_usage, + PARSE_OPT_KEEP_UNKNOWN); + if (argc) + usage_with_options(builtin_multi_pack_index_write_usage, + options); + + FREE_AND_NULL(options); + + if (opts.stdin_packs) { + struct string_list packs = STRING_LIST_INIT_DUP; + int ret; + + read_packs_from_stdin(&packs); + + ret = write_midx_file_only(opts.object_dir, &packs, + opts.preferred_pack, + opts.refs_snapshot, opts.flags); + + string_list_clear(&packs, 0); + + return ret; - static struct option builtin_multi_pack_index_options[] = { - OPT_FILENAME(0, "object-dir", &opts.object_dir, - N_("object directory containing set of packfile and pack-index pairs")), - OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")), + } + return write_midx_file(opts.object_dir, opts.preferred_pack, + opts.refs_snapshot, opts.flags); +} + +static int cmd_multi_pack_index_verify(int argc, const char **argv) +{ + struct option *options; + static struct option builtin_multi_pack_index_verify_options[] = { + OPT_BIT(0, "progress", &opts.flags, + N_("force progress reporting"), MIDX_PROGRESS), + OPT_END(), + }; + options = add_common_options(builtin_multi_pack_index_verify_options); + + trace2_cmd_mode(argv[0]); + + if (isatty(2)) + opts.flags |= MIDX_PROGRESS; + argc = parse_options(argc, argv, NULL, + options, builtin_multi_pack_index_verify_usage, + PARSE_OPT_KEEP_UNKNOWN); + if (argc) + usage_with_options(builtin_multi_pack_index_verify_usage, + options); + + return verify_midx_file(the_repository, opts.object_dir, opts.flags); +} + +static int cmd_multi_pack_index_expire(int argc, const char **argv) +{ + struct option *options; + static struct option builtin_multi_pack_index_expire_options[] = { + OPT_BIT(0, "progress", &opts.flags, + N_("force progress reporting"), MIDX_PROGRESS), + OPT_END(), + }; + options = add_common_options(builtin_multi_pack_index_expire_options); + + trace2_cmd_mode(argv[0]); + + if (isatty(2)) + opts.flags |= MIDX_PROGRESS; + argc = parse_options(argc, argv, NULL, + options, builtin_multi_pack_index_expire_usage, + PARSE_OPT_KEEP_UNKNOWN); + if (argc) + usage_with_options(builtin_multi_pack_index_expire_usage, + options); + + return expire_midx_packs(the_repository, opts.object_dir, opts.flags); +} + +static int cmd_multi_pack_index_repack(int argc, const char **argv) +{ + struct option *options; + static struct option builtin_multi_pack_index_repack_options[] = { OPT_MAGNITUDE(0, "batch-size", &opts.batch_size, N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")), + OPT_BIT(0, "progress", &opts.flags, + N_("force progress reporting"), MIDX_PROGRESS), OPT_END(), }; + options = add_common_options(builtin_multi_pack_index_repack_options); + + trace2_cmd_mode(argv[0]); + + if (isatty(2)) + opts.flags |= MIDX_PROGRESS; + argc = parse_options(argc, argv, NULL, + options, + builtin_multi_pack_index_repack_usage, + PARSE_OPT_KEEP_UNKNOWN); + if (argc) + usage_with_options(builtin_multi_pack_index_repack_usage, + options); + + FREE_AND_NULL(options); + + return midx_repack(the_repository, opts.object_dir, + (size_t)opts.batch_size, opts.flags); +} + +int cmd_multi_pack_index(int argc, const char **argv, + const char *prefix) +{ + struct option *builtin_multi_pack_index_options = common_opts; + git_config(git_default_config, NULL); - opts.progress = isatty(2); argc = parse_options(argc, argv, prefix, builtin_multi_pack_index_options, - builtin_multi_pack_index_usage, 0); + builtin_multi_pack_index_usage, + PARSE_OPT_STOP_AT_NON_OPTION); if (!opts.object_dir) opts.object_dir = get_object_directory(); - if (opts.progress) - flags |= MIDX_PROGRESS; - if (argc == 0) - usage_with_options(builtin_multi_pack_index_usage, - builtin_multi_pack_index_options); - - if (argc > 1) { - die(_("too many arguments")); - return 1; - } - - trace2_cmd_mode(argv[0]); + if (!argc) + goto usage; if (!strcmp(argv[0], "repack")) - return midx_repack(the_repository, opts.object_dir, - (size_t)opts.batch_size, flags); - if (opts.batch_size) - die(_("--batch-size option is only for 'repack' subcommand")); - - if (!strcmp(argv[0], "write")) - return write_midx_file(opts.object_dir, flags); - if (!strcmp(argv[0], "verify")) - return verify_midx_file(the_repository, opts.object_dir, flags); - if (!strcmp(argv[0], "expire")) - return expire_midx_packs(the_repository, opts.object_dir, flags); - - die(_("unrecognized subcommand: %s"), argv[0]); + return cmd_multi_pack_index_repack(argc, argv); + else if (!strcmp(argv[0], "write")) + return cmd_multi_pack_index_write(argc, argv); + else if (!strcmp(argv[0], "verify")) + return cmd_multi_pack_index_verify(argc, argv); + else if (!strcmp(argv[0], "expire")) + return cmd_multi_pack_index_expire(argc, argv); + + error(_("unrecognized subcommand: %s"), argv[0]); +usage: + usage_with_options(builtin_multi_pack_index_usage, + builtin_multi_pack_index_options); } diff -Nru git-2.25.1/builtin/mv.c git-2.34.1/builtin/mv.c --- git-2.25.1/builtin/mv.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/mv.c 2021-11-24 19:29:21.000000000 +0000 @@ -118,20 +118,23 @@ int cmd_mv(int argc, const char **argv, const char *prefix) { int i, flags, gitmodules_modified = 0; - int verbose = 0, show_only = 0, force = 0, ignore_errors = 0; + int verbose = 0, show_only = 0, force = 0, ignore_errors = 0, ignore_sparse = 0; struct option builtin_mv_options[] = { OPT__VERBOSE(&verbose, N_("be verbose")), OPT__DRY_RUN(&show_only, N_("dry run")), OPT__FORCE(&force, N_("force move/rename even if target exists"), PARSE_OPT_NOCOMPLETE), OPT_BOOL('k', NULL, &ignore_errors, N_("skip move/rename errors")), + OPT_BOOL(0, "sparse", &ignore_sparse, N_("allow updating entries outside of the sparse-checkout cone")), OPT_END(), }; const char **source, **destination, **dest_path, **submodule_gitfile; - enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX } *modes; + enum update_mode { BOTH = 0, WORKING_DIRECTORY, INDEX, SPARSE } *modes; struct stat st; struct string_list src_for_dst = STRING_LIST_INIT_NODUP; struct lock_file lock_file = LOCK_INIT; + struct cache_entry *ce; + struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP; git_config(git_default_config, NULL); @@ -175,14 +178,17 @@ const char *src = source[i], *dst = destination[i]; int length, src_is_dir; const char *bad = NULL; + int skip_sparse = 0; if (show_only) printf(_("Checking rename of '%s' to '%s'\n"), src, dst); length = strlen(src); - if (lstat(src, &st) < 0) - bad = _("bad source"); - else if (!strncmp(src, dst, length) && + if (lstat(src, &st) < 0) { + /* only error if existence is expected. */ + if (modes[i] != SPARSE) + bad = _("bad source"); + } else if (!strncmp(src, dst, length) && (dst[length] == 0 || dst[length] == '/')) { bad = _("can not move directory into itself"); } else if ((src_is_dir = S_ISDIR(st.st_mode)) @@ -211,18 +217,21 @@ dst_len = strlen(dst); for (j = 0; j < last - first; j++) { - const char *path = active_cache[first + j]->name; + const struct cache_entry *ce = active_cache[first + j]; + const char *path = ce->name; source[argc + j] = path; destination[argc + j] = prefix_path(dst, dst_len, path + length + 1); - modes[argc + j] = INDEX; + modes[argc + j] = ce_skip_worktree(ce) ? SPARSE : INDEX; submodule_gitfile[argc + j] = NULL; } argc += last - first; } - } else if (cache_name_pos(src, length) < 0) + } else if (!(ce = cache_file_exists(src, length, 0))) { bad = _("not under version control"); - else if (lstat(dst, &st) == 0 && + } else if (ce_stage(ce)) { + bad = _("conflicted"); + } else if (lstat(dst, &st) == 0 && (!ignore_case || strcasecmp(src, dst))) { bad = _("destination exists"); if (force) { @@ -241,14 +250,36 @@ bad = _("multiple sources for the same target"); else if (is_dir_sep(dst[strlen(dst) - 1])) bad = _("destination directory does not exist"); - else + else { + /* + * We check if the paths are in the sparse-checkout + * definition as a very final check, since that + * allows us to point the user to the --sparse + * option as a way to have a successful run. + */ + if (!ignore_sparse && + !path_in_sparse_checkout(src, &the_index)) { + string_list_append(&only_match_skip_worktree, src); + skip_sparse = 1; + } + if (!ignore_sparse && + !path_in_sparse_checkout(dst, &the_index)) { + string_list_append(&only_match_skip_worktree, dst); + skip_sparse = 1; + } + + if (skip_sparse) + goto remove_entry; + string_list_insert(&src_for_dst, dst); + } if (!bad) continue; if (!ignore_errors) die(_("%s, source=%s, destination=%s"), bad, src, dst); +remove_entry: if (--argc > 0) { int n = argc - i; memmove(source + i, source + i + 1, @@ -263,6 +294,12 @@ } } + if (only_match_skip_worktree.nr) { + advise_on_updating_sparse_paths(&only_match_skip_worktree); + if (!ignore_errors) + return 1; + } + for (i = 0; i < argc; i++) { const char *src = source[i], *dst = destination[i]; enum update_mode mode = modes[i]; @@ -271,7 +308,7 @@ printf(_("Renaming %s to %s\n"), src, dst); if (show_only) continue; - if (mode != INDEX && rename(src, dst) < 0) { + if (mode != INDEX && mode != SPARSE && rename(src, dst) < 0) { if (ignore_errors) continue; die_errno(_("renaming '%s' failed"), src); @@ -300,5 +337,10 @@ COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("Unable to write new index file")); + string_list_clear(&src_for_dst, 0); + UNLEAK(source); + UNLEAK(dest_path); + free(submodule_gitfile); + free(modes); return 0; } diff -Nru git-2.25.1/builtin/name-rev.c git-2.34.1/builtin/name-rev.c --- git-2.25.1/builtin/name-rev.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/name-rev.c 2021-11-24 19:29:21.000000000 +0000 @@ -7,7 +7,7 @@ #include "refs.h" #include "parse-options.h" #include "prio-queue.h" -#include "sha1-lookup.h" +#include "hash-lookup.h" #include "commit-slab.h" /* @@ -16,15 +16,15 @@ */ #define CUTOFF_DATE_SLOP 86400 -typedef struct rev_name { - const char *tip_name; +struct rev_name { + char *tip_name; timestamp_t taggerdate; int generation; int distance; int from_tag; -} rev_name; +}; -define_commit_slab(commit_rev_name, struct rev_name *); +define_commit_slab(commit_rev_name, struct rev_name); static timestamp_t cutoff = TIME_MAX; static struct commit_rev_name rev_names; @@ -32,16 +32,16 @@ /* How many generations are maximally preferred over _one_ merge traversal? */ #define MERGE_TRAVERSAL_WEIGHT 65535 -static struct rev_name *get_commit_rev_name(struct commit *commit) +static int is_valid_rev_name(const struct rev_name *name) { - struct rev_name **slot = commit_rev_name_peek(&rev_names, commit); - - return slot ? *slot : NULL; + return name && (name->generation || name->tip_name); } -static void set_commit_rev_name(struct commit *commit, struct rev_name *name) +static struct rev_name *get_commit_rev_name(const struct commit *commit) { - *commit_rev_name_at(&rev_names, commit) = name; + struct rev_name *name = commit_rev_name_peek(&rev_names, commit); + + return is_valid_rev_name(name) ? name : NULL; } static int is_better_name(struct rev_name *name, @@ -81,28 +81,54 @@ } static struct rev_name *create_or_update_name(struct commit *commit, - const char *tip_name, timestamp_t taggerdate, int generation, int distance, int from_tag) { - struct rev_name *name = get_commit_rev_name(commit); - - if (name == NULL) { - name = xmalloc(sizeof(*name)); - set_commit_rev_name(commit, name); - goto copy_data; - } else if (is_better_name(name, taggerdate, distance, from_tag)) { -copy_data: - name->tip_name = tip_name; - name->taggerdate = taggerdate; - name->generation = generation; - name->distance = distance; - name->from_tag = from_tag; + struct rev_name *name = commit_rev_name_at(&rev_names, commit); - return name; - } else - return NULL; + if (is_valid_rev_name(name)) { + if (!is_better_name(name, taggerdate, distance, from_tag)) + return NULL; + + /* + * This string might still be shared with ancestors + * (generation > 0). We can release it here regardless, + * because the new name that has just won will be better + * for them as well, so name_rev() will replace these + * stale pointers when it processes the parents. + */ + if (!name->generation) + free(name->tip_name); + } + + name->taggerdate = taggerdate; + name->generation = generation; + name->distance = distance; + name->from_tag = from_tag; + + return name; +} + +static char *get_parent_name(const struct rev_name *name, int parent_number) +{ + struct strbuf sb = STRBUF_INIT; + size_t len; + + strip_suffix(name->tip_name, "^0", &len); + if (name->generation > 0) { + strbuf_grow(&sb, len + + 1 + decimal_width(name->generation) + + 1 + decimal_width(parent_number)); + strbuf_addf(&sb, "%.*s~%d^%d", (int)len, name->tip_name, + name->generation, parent_number); + } else { + strbuf_grow(&sb, len + + 1 + decimal_width(parent_number)); + strbuf_addf(&sb, "%.*s^%d", (int)len, name->tip_name, + parent_number); + } + return strbuf_detach(&sb, NULL); } static void name_rev(struct commit *start_commit, @@ -113,20 +139,20 @@ struct commit *commit; struct commit **parents_to_queue = NULL; size_t parents_to_queue_nr, parents_to_queue_alloc = 0; - char *to_free = NULL; + struct rev_name *start_name; parse_commit(start_commit); if (start_commit->date < cutoff) return; - if (deref) - tip_name = to_free = xstrfmt("%s^0", tip_name); - - if (!create_or_update_name(start_commit, tip_name, taggerdate, 0, 0, - from_tag)) { - free(to_free); + start_name = create_or_update_name(start_commit, taggerdate, 0, 0, + from_tag); + if (!start_name) return; - } + if (deref) + start_name->tip_name = xstrfmt("%s^0", tip_name); + else + start_name->tip_name = xstrdup(tip_name); memset(&queue, 0, sizeof(queue)); /* Use the prio_queue as LIFO */ prio_queue_put(&queue, start_commit); @@ -142,7 +168,7 @@ parents; parents = parents->next, parent_number++) { struct commit *parent = parents->item; - const char *new_name; + struct rev_name *parent_name; int generation, distance; parse_commit(parent); @@ -150,30 +176,23 @@ continue; if (parent_number > 1) { - size_t len; - - strip_suffix(name->tip_name, "^0", &len); - if (name->generation > 0) - new_name = xstrfmt("%.*s~%d^%d", - (int)len, - name->tip_name, - name->generation, - parent_number); - else - new_name = xstrfmt("%.*s^%d", (int)len, - name->tip_name, - parent_number); generation = 0; distance = name->distance + MERGE_TRAVERSAL_WEIGHT; } else { - new_name = name->tip_name; generation = name->generation + 1; distance = name->distance + 1; } - if (create_or_update_name(parent, new_name, taggerdate, - generation, distance, - from_tag)) { + parent_name = create_or_update_name(parent, taggerdate, + generation, + distance, from_tag); + if (parent_name) { + if (parent_number > 1) + parent_name->tip_name = + get_parent_name(name, + parent_number); + else + parent_name->tip_name = name->tip_name; ALLOC_GROW(parents_to_queue, parents_to_queue_nr + 1, parents_to_queue_alloc); @@ -228,6 +247,10 @@ struct tip_table_entry { struct object_id oid; const char *refname; + struct commit *commit; + timestamp_t taggerdate; + unsigned int from_tag:1; + unsigned int deref:1; } *table; int nr; int alloc; @@ -235,13 +258,18 @@ } tip_table; static void add_to_tip_table(const struct object_id *oid, const char *refname, - int shorten_unambiguous) + int shorten_unambiguous, struct commit *commit, + timestamp_t taggerdate, int from_tag, int deref) { refname = name_ref_abbrev(refname, shorten_unambiguous); ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc); oidcpy(&tip_table.table[tip_table.nr].oid, oid); tip_table.table[tip_table.nr].refname = xstrdup(refname); + tip_table.table[tip_table.nr].commit = commit; + tip_table.table[tip_table.nr].taggerdate = taggerdate; + tip_table.table[tip_table.nr].from_tag = from_tag; + tip_table.table[tip_table.nr].deref = deref; tip_table.nr++; tip_table.sorted = 0; } @@ -252,12 +280,30 @@ return oidcmp(&a->oid, &b->oid); } +static int cmp_by_tag_and_age(const void *a_, const void *b_) +{ + const struct tip_table_entry *a = a_, *b = b_; + int cmp; + + /* Prefer tags. */ + cmp = b->from_tag - a->from_tag; + if (cmp) + return cmp; + + /* Older is better. */ + if (a->taggerdate < b->taggerdate) + return -1; + return a->taggerdate != b->taggerdate; +} + static int name_ref(const char *path, const struct object_id *oid, int flags, void *cb_data) { struct object *o = parse_object(the_repository, oid); struct name_ref_data *data = cb_data; int can_abbreviate_output = data->tags_only && data->name_only; int deref = 0; + int from_tag = 0; + struct commit *commit = NULL; timestamp_t taggerdate = TIME_MAX; if (data->tags_only && !starts_with(path, "refs/tags/")) @@ -306,8 +352,6 @@ return 0; } - add_to_tip_table(oid, path, can_abbreviate_output); - while (o && o->type == OBJ_TAG) { struct tag *t = (struct tag *) o; if (!t->tagged) @@ -317,21 +361,39 @@ taggerdate = t->date; } if (o && o->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *)o; - int from_tag = starts_with(path, "refs/tags/"); - + commit = (struct commit *)o; + from_tag = starts_with(path, "refs/tags/"); if (taggerdate == TIME_MAX) taggerdate = commit->date; - path = name_ref_abbrev(path, can_abbreviate_output); - name_rev(commit, xstrdup(path), taggerdate, from_tag, deref); } + + add_to_tip_table(oid, path, can_abbreviate_output, commit, taggerdate, + from_tag, deref); return 0; } -static const unsigned char *nth_tip_table_ent(size_t ix, void *table_) +static void name_tips(void) +{ + int i; + + /* + * Try to set better names first, so that worse ones spread + * less. + */ + QSORT(tip_table.table, tip_table.nr, cmp_by_tag_and_age); + for (i = 0; i < tip_table.nr; i++) { + struct tip_table_entry *e = &tip_table.table[i]; + if (e->commit) { + name_rev(e->commit, e->refname, e->taggerdate, + e->from_tag, e->deref); + } + } +} + +static const struct object_id *nth_tip_table_ent(size_t ix, const void *table_) { - struct tip_table_entry *table = table_; - return table[ix].oid.hash; + const struct tip_table_entry *table = table_; + return &table[ix].oid; } static const char *get_exact_ref_match(const struct object *o) @@ -346,8 +408,8 @@ tip_table.sorted = 1; } - found = sha1_pos(o->oid.hash, tip_table.table, tip_table.nr, - nth_tip_table_ent); + found = oid_pos(&o->oid, tip_table.table, tip_table.nr, + nth_tip_table_ent); if (0 <= found) return tip_table.table[found].refname; return NULL; @@ -357,11 +419,11 @@ static const char *get_rev_name(const struct object *o, struct strbuf *buf) { struct rev_name *n; - struct commit *c; + const struct commit *c; if (o->type != OBJ_COMMIT) return get_exact_ref_match(o); - c = (struct commit *) o; + c = (const struct commit *) o; n = get_commit_rev_name(c); if (!n) return NULL; @@ -459,7 +521,7 @@ int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; struct name_ref_data data = { 0, 0, STRING_LIST_INIT_NODUP, STRING_LIST_INIT_NODUP }; struct option opts[] = { - OPT_BOOL(0, "name-only", &data.name_only, N_("print only names (no SHA-1)")), + OPT_BOOL(0, "name-only", &data.name_only, N_("print only ref-based names (no object names)")), OPT_BOOL(0, "tags", &data.tags_only, N_("only use tags to name the commits")), OPT_STRING_LIST(0, "refs", &data.ref_filters, N_("pattern"), N_("only use refs matching ")), @@ -540,6 +602,7 @@ cutoff = TIME_MIN; } for_each_ref(name_ref, &data); + name_tips(); if (transform_stdin) { char buffer[2048]; diff -Nru git-2.25.1/builtin/notes.c git-2.34.1/builtin/notes.c --- git-2.25.1/builtin/notes.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/notes.c 2021-11-24 19:29:21.000000000 +0000 @@ -172,9 +172,7 @@ /* write the template message before editing: */ d->edit_path = git_pathdup("NOTES_EDITMSG"); - fd = open(d->edit_path, O_CREAT | O_TRUNC | O_WRONLY, 0600); - if (fd < 0) - die_errno(_("could not create file '%s'"), d->edit_path); + fd = xopen(d->edit_path, O_CREAT | O_TRUNC | O_WRONLY, 0600); if (d->given) write_or_die(fd, d->buf.buf, d->buf.len); @@ -373,7 +371,7 @@ git_notes_list_usage, 0); if (1 < argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_list_usage, options); } @@ -406,18 +404,18 @@ const struct object_id *note; struct note_data d = { 0, 0, NULL, STRBUF_INIT }; struct option options[] = { - { OPTION_CALLBACK, 'm', "message", &d, N_("message"), + OPT_CALLBACK_F('m', "message", &d, N_("message"), N_("note contents as a string"), PARSE_OPT_NONEG, - parse_msg_arg}, - { OPTION_CALLBACK, 'F', "file", &d, N_("file"), + parse_msg_arg), + OPT_CALLBACK_F('F', "file", &d, N_("file"), N_("note contents in a file"), PARSE_OPT_NONEG, - parse_file_arg}, - { OPTION_CALLBACK, 'c', "reedit-message", &d, N_("object"), + parse_file_arg), + OPT_CALLBACK_F('c', "reedit-message", &d, N_("object"), N_("reuse and edit specified note object"), PARSE_OPT_NONEG, - parse_reedit_arg}, - { OPTION_CALLBACK, 'C', "reuse-message", &d, N_("object"), + parse_reedit_arg), + OPT_CALLBACK_F('C', "reuse-message", &d, N_("object"), N_("reuse specified note object"), PARSE_OPT_NONEG, - parse_reuse_arg}, + parse_reuse_arg), OPT_BOOL(0, "allow-empty", &allow_empty, N_("allow storing empty note")), OPT__FORCE(&force, N_("replace existing notes"), PARSE_OPT_NOCOMPLETE), @@ -428,7 +426,7 @@ PARSE_OPT_KEEP_ARGV0); if (2 < argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_add_usage, options); } @@ -506,7 +504,7 @@ if (from_stdin || rewrite_cmd) { if (argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_copy_usage, options); } else { return notes_copy_from_stdin(force, rewrite_cmd); @@ -514,11 +512,11 @@ } if (argc < 1) { - error(_("too few parameters")); + error(_("too few arguments")); usage_with_options(git_notes_copy_usage, options); } if (2 < argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_copy_usage, options); } @@ -572,18 +570,18 @@ const char * const *usage; struct note_data d = { 0, 0, NULL, STRBUF_INIT }; struct option options[] = { - { OPTION_CALLBACK, 'm', "message", &d, N_("message"), + OPT_CALLBACK_F('m', "message", &d, N_("message"), N_("note contents as a string"), PARSE_OPT_NONEG, - parse_msg_arg}, - { OPTION_CALLBACK, 'F', "file", &d, N_("file"), + parse_msg_arg), + OPT_CALLBACK_F('F', "file", &d, N_("file"), N_("note contents in a file"), PARSE_OPT_NONEG, - parse_file_arg}, - { OPTION_CALLBACK, 'c', "reedit-message", &d, N_("object"), + parse_file_arg), + OPT_CALLBACK_F('c', "reedit-message", &d, N_("object"), N_("reuse and edit specified note object"), PARSE_OPT_NONEG, - parse_reedit_arg}, - { OPTION_CALLBACK, 'C', "reuse-message", &d, N_("object"), + parse_reedit_arg), + OPT_CALLBACK_F('C', "reuse-message", &d, N_("object"), N_("reuse specified note object"), PARSE_OPT_NONEG, - parse_reuse_arg}, + parse_reuse_arg), OPT_BOOL(0, "allow-empty", &allow_empty, N_("allow storing empty note")), OPT_END() @@ -595,7 +593,7 @@ PARSE_OPT_KEEP_ARGV0); if (2 < argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(usage, options); } @@ -622,7 +620,7 @@ strbuf_grow(&d.buf, size + 1); if (d.buf.len && prev_buf && size) - strbuf_insert(&d.buf, 0, "\n", 1); + strbuf_insertstr(&d.buf, 0, "\n"); if (prev_buf && size) strbuf_insert(&d.buf, 0, prev_buf, size); free(prev_buf); @@ -662,7 +660,7 @@ 0); if (1 < argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_show_usage, options); } @@ -730,7 +728,7 @@ else oidclr(&parent_oid); - t = xcalloc(1, sizeof(struct notes_tree)); + CALLOC_ARRAY(t, 1); init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0); o->local_ref = local_ref_to_free = @@ -745,7 +743,7 @@ memset(&pretty_ctx, 0, sizeof(pretty_ctx)); format_commit_message(partial, "%s", &msg, &pretty_ctx); strbuf_trim(&msg); - strbuf_insert(&msg, 0, "notes: ", 7); + strbuf_insertstr(&msg, 0, "notes: "); update_ref(msg.buf, o->local_ref, &oid, is_null_oid(&parent_oid) ? NULL : &parent_oid, 0, UPDATE_REFS_DIE_ON_ERR); @@ -812,7 +810,7 @@ error(_("must specify a notes ref to merge")); usage_with_options(git_notes_merge_usage, options); } else if (!do_merge && argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_merge_usage, options); } @@ -960,7 +958,7 @@ 0); if (argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_prune_usage, options); } @@ -982,7 +980,7 @@ git_notes_get_ref_usage, 0); if (argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_get_ref_usage, options); } diff -Nru git-2.25.1/builtin/pack-objects.c git-2.34.1/builtin/pack-objects.c --- git-2.25.1/builtin/pack-objects.c 2020-02-17 04:47:35.000000000 +0000 +++ git-2.34.1/builtin/pack-objects.c 2021-11-24 19:29:21.000000000 +0000 @@ -26,14 +26,144 @@ #include "pack-bitmap.h" #include "delta-islands.h" #include "reachable.h" -#include "sha1-array.h" -#include "argv-array.h" +#include "oid-array.h" +#include "strvec.h" #include "list.h" #include "packfile.h" #include "object-store.h" #include "dir.h" #include "midx.h" #include "trace2.h" +#include "shallow.h" +#include "promisor-remote.h" + +/* + * Objects we are going to pack are collected in the `to_pack` structure. + * It contains an array (dynamically expanded) of the object data, and a map + * that can resolve SHA1s to their position in the array. + */ +static struct packing_data to_pack; + +static inline struct object_entry *oe_delta( + const struct packing_data *pack, + const struct object_entry *e) +{ + if (!e->delta_idx) + return NULL; + if (e->ext_base) + return &pack->ext_bases[e->delta_idx - 1]; + else + return &pack->objects[e->delta_idx - 1]; +} + +static inline unsigned long oe_delta_size(struct packing_data *pack, + const struct object_entry *e) +{ + if (e->delta_size_valid) + return e->delta_size_; + + /* + * pack->delta_size[] can't be NULL because oe_set_delta_size() + * must have been called when a new delta is saved with + * oe_set_delta(). + * If oe_delta() returns NULL (i.e. default state, which means + * delta_size_valid is also false), then the caller must never + * call oe_delta_size(). + */ + return pack->delta_size[e - pack->objects]; +} + +unsigned long oe_get_size_slow(struct packing_data *pack, + const struct object_entry *e); + +static inline unsigned long oe_size(struct packing_data *pack, + const struct object_entry *e) +{ + if (e->size_valid) + return e->size_; + + return oe_get_size_slow(pack, e); +} + +static inline void oe_set_delta(struct packing_data *pack, + struct object_entry *e, + struct object_entry *delta) +{ + if (delta) + e->delta_idx = (delta - pack->objects) + 1; + else + e->delta_idx = 0; +} + +static inline struct object_entry *oe_delta_sibling( + const struct packing_data *pack, + const struct object_entry *e) +{ + if (e->delta_sibling_idx) + return &pack->objects[e->delta_sibling_idx - 1]; + return NULL; +} + +static inline struct object_entry *oe_delta_child( + const struct packing_data *pack, + const struct object_entry *e) +{ + if (e->delta_child_idx) + return &pack->objects[e->delta_child_idx - 1]; + return NULL; +} + +static inline void oe_set_delta_child(struct packing_data *pack, + struct object_entry *e, + struct object_entry *delta) +{ + if (delta) + e->delta_child_idx = (delta - pack->objects) + 1; + else + e->delta_child_idx = 0; +} + +static inline void oe_set_delta_sibling(struct packing_data *pack, + struct object_entry *e, + struct object_entry *delta) +{ + if (delta) + e->delta_sibling_idx = (delta - pack->objects) + 1; + else + e->delta_sibling_idx = 0; +} + +static inline void oe_set_size(struct packing_data *pack, + struct object_entry *e, + unsigned long size) +{ + if (size < pack->oe_size_limit) { + e->size_ = size; + e->size_valid = 1; + } else { + e->size_valid = 0; + if (oe_get_size_slow(pack, e) != size) + BUG("'size' is supposed to be the object size!"); + } +} + +static inline void oe_set_delta_size(struct packing_data *pack, + struct object_entry *e, + unsigned long size) +{ + if (size < pack->oe_delta_size_limit) { + e->delta_size_ = size; + e->delta_size_valid = 1; + } else { + packing_data_lock(pack); + if (!pack->delta_size) + ALLOC_ARRAY(pack->delta_size, pack->nr_alloc); + packing_data_unlock(pack); + + pack->delta_size[e - pack->objects] = size; + e->delta_size_valid = 0; + } +} #define IN_PACK(obj) oe_in_pack(&to_pack, obj) #define SIZE(obj) oe_size(&to_pack, obj) @@ -54,13 +184,6 @@ NULL }; -/* - * Objects we are going to pack are collected in the `to_pack` structure. - * It contains an array (dynamically expanded) of the object data, and a map - * that can resolve SHA1s to their position in the array. - */ -static struct packing_data to_pack; - static struct pack_idx_entry **written_list; static uint32_t nr_result, nr_written, nr_seen; static struct bitmap_index *bitmap_git; @@ -92,10 +215,11 @@ static struct packed_git *reuse_packfile; static uint32_t reuse_packfile_objects; -static off_t reuse_packfile_offset; +static struct bitmap *reuse_packfile_bitmap; static int use_bitmap_index_default = 1; static int use_bitmap_index = -1; +static int allow_pack_reuse = 1; static enum { WRITE_BITMAP_FALSE = 0, WRITE_BITMAP_QUIET, @@ -115,6 +239,8 @@ static struct list_objects_filter_options filter_options; +static struct string_list uri_protocols = STRING_LIST_INIT_NODUP; + enum missing_action { MA_ERROR = 0, /* fail if any missing objects are encountered */ MA_ALLOW_ANY, /* silently allow ALL missing objects */ @@ -123,6 +249,15 @@ static enum missing_action arg_missing_action; static show_object_fn fn_show_object; +struct configured_exclusion { + struct oidmap_entry e; + char *pack_hash_hex; + char *uri; +}; +static struct oidmap configured_exclusions; + +static struct oidset excluded_by_config; + /* * stats */ @@ -287,6 +422,17 @@ } } +static inline int oe_size_greater_than(struct packing_data *pack, + const struct object_entry *lhs, + unsigned long rhs) +{ + if (lhs->size_valid) + return lhs->size_ > rhs; + if (rhs < pack->oe_size_limit) /* rhs < 2^x <= lhs ? */ + return 1; + return oe_get_size_slow(pack, lhs) > rhs; +} + /* Return 0 if we will bust the pack-size limit */ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_entry *entry, unsigned long limit, int usable_delta) @@ -303,7 +449,8 @@ if (!usable_delta) { if (oe_type(entry) == OBJ_BLOB && oe_size_greater_than(&to_pack, entry, big_file_threshold) && - (st = open_istream(&entry->idx.oid, &type, &size, NULL)) != NULL) + (st = open_istream(the_repository, &entry->idx.oid, &type, + &size, NULL)) != NULL) buf = NULL; else { buf = read_object_file(&entry->idx.oid, &type, &size); @@ -404,7 +551,7 @@ { struct packed_git *p = IN_PACK(entry); struct pack_window *w_curs = NULL; - struct revindex_entry *revidx; + uint32_t pos; off_t offset; enum object_type type = oe_type(entry); off_t datalen; @@ -421,10 +568,15 @@ type, entry_size); offset = entry->in_pack_offset; - revidx = find_pack_revindex(p, offset); - datalen = revidx[1].offset - offset; + if (offset_to_pack_pos(p, offset, &pos) < 0) + die(_("write_reuse_object: could not locate %s, expected at " + "offset %"PRIuMAX" in pack %s"), + oid_to_hex(&entry->idx.oid), (uintmax_t)offset, + p->pack_name); + datalen = pack_pos_to_offset(p, pos + 1) - offset; if (!pack_to_stdout && p->index_version > 1 && - check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) { + check_pack_crc(p, &w_curs, offset, datalen, + pack_pos_to_index(p, pos))) { error(_("bad packed object CRC for %s"), oid_to_hex(&entry->idx.oid)); unuse_pack(&w_curs); @@ -614,7 +766,7 @@ if (entry) entry->tagged = 1; - if (!peel_ref(path, &peeled)) { + if (!peel_iterated_oid(oid, &peeled)) { entry = packlist_find(&to_pack, &peeled); if (entry) entry->tagged = 1; @@ -622,6 +774,14 @@ return 0; } +static inline unsigned char oe_layer(struct packing_data *pack, + struct object_entry *e) +{ + if (!pack->layer) + return 0; + return pack->layer[e - pack->objects]; +} + static inline void add_to_write_order(struct object_entry **wo, unsigned int *endp, struct object_entry *e) @@ -784,57 +944,216 @@ return wo; } -static off_t write_reused_pack(struct hashfile *f) + +/* + * A reused set of objects. All objects in a chunk have the same + * relative position in the original packfile and the generated + * packfile. + */ + +static struct reused_chunk { + /* The offset of the first object of this chunk in the original + * packfile. */ + off_t original; + /* The difference for "original" minus the offset of the first object of + * this chunk in the generated packfile. */ + off_t difference; +} *reused_chunks; +static int reused_chunks_nr; +static int reused_chunks_alloc; + +static void record_reused_object(off_t where, off_t offset) +{ + if (reused_chunks_nr && reused_chunks[reused_chunks_nr-1].difference == offset) + return; + + ALLOC_GROW(reused_chunks, reused_chunks_nr + 1, + reused_chunks_alloc); + reused_chunks[reused_chunks_nr].original = where; + reused_chunks[reused_chunks_nr].difference = offset; + reused_chunks_nr++; +} + +/* + * Binary search to find the chunk that "where" is in. Note + * that we're not looking for an exact match, just the first + * chunk that contains it (which implicitly ends at the start + * of the next chunk. + */ +static off_t find_reused_offset(off_t where) +{ + int lo = 0, hi = reused_chunks_nr; + while (lo < hi) { + int mi = lo + ((hi - lo) / 2); + if (where == reused_chunks[mi].original) + return reused_chunks[mi].difference; + if (where < reused_chunks[mi].original) + hi = mi; + else + lo = mi + 1; + } + + /* + * The first chunk starts at zero, so we can't have gone below + * there. + */ + assert(lo); + return reused_chunks[lo-1].difference; +} + +static void write_reused_pack_one(size_t pos, struct hashfile *out, + struct pack_window **w_curs) { - unsigned char buffer[8192]; - off_t to_write, total; - int fd; + off_t offset, next, cur; + enum object_type type; + unsigned long size; - if (!is_pack_valid(reuse_packfile)) - die(_("packfile is invalid: %s"), reuse_packfile->pack_name); + offset = pack_pos_to_offset(reuse_packfile, pos); + next = pack_pos_to_offset(reuse_packfile, pos + 1); - fd = git_open(reuse_packfile->pack_name); - if (fd < 0) - die_errno(_("unable to open packfile for reuse: %s"), - reuse_packfile->pack_name); + record_reused_object(offset, offset - hashfile_total(out)); - if (lseek(fd, sizeof(struct pack_header), SEEK_SET) == -1) - die_errno(_("unable to seek in reused packfile")); + cur = offset; + type = unpack_object_header(reuse_packfile, w_curs, &cur, &size); + assert(type >= 0); - if (reuse_packfile_offset < 0) - reuse_packfile_offset = reuse_packfile->pack_size - the_hash_algo->rawsz; + if (type == OBJ_OFS_DELTA) { + off_t base_offset; + off_t fixup; - total = to_write = reuse_packfile_offset - sizeof(struct pack_header); + unsigned char header[MAX_PACK_OBJECT_HEADER]; + unsigned len; - while (to_write) { - int read_pack = xread(fd, buffer, sizeof(buffer)); + base_offset = get_delta_base(reuse_packfile, w_curs, &cur, type, offset); + assert(base_offset != 0); - if (read_pack <= 0) - die_errno(_("unable to read from reused packfile")); + /* Convert to REF_DELTA if we must... */ + if (!allow_ofs_delta) { + uint32_t base_pos; + struct object_id base_oid; + + if (offset_to_pack_pos(reuse_packfile, base_offset, &base_pos) < 0) + die(_("expected object at offset %"PRIuMAX" " + "in pack %s"), + (uintmax_t)base_offset, + reuse_packfile->pack_name); + + nth_packed_object_id(&base_oid, reuse_packfile, + pack_pos_to_index(reuse_packfile, base_pos)); + + len = encode_in_pack_object_header(header, sizeof(header), + OBJ_REF_DELTA, size); + hashwrite(out, header, len); + hashwrite(out, base_oid.hash, the_hash_algo->rawsz); + copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur); + return; + } - if (read_pack > to_write) - read_pack = to_write; + /* Otherwise see if we need to rewrite the offset... */ + fixup = find_reused_offset(offset) - + find_reused_offset(base_offset); + if (fixup) { + unsigned char ofs_header[10]; + unsigned i, ofs_len; + off_t ofs = offset - base_offset - fixup; + + len = encode_in_pack_object_header(header, sizeof(header), + OBJ_OFS_DELTA, size); + + i = sizeof(ofs_header) - 1; + ofs_header[i] = ofs & 127; + while (ofs >>= 7) + ofs_header[--i] = 128 | (--ofs & 127); + + ofs_len = sizeof(ofs_header) - i; + + hashwrite(out, header, len); + hashwrite(out, ofs_header + sizeof(ofs_header) - ofs_len, ofs_len); + copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur); + return; + } - hashwrite(f, buffer, read_pack); - to_write -= read_pack; + /* ...otherwise we have no fixup, and can write it verbatim */ + } + + copy_pack_data(out, reuse_packfile, w_curs, offset, next - offset); +} + +static size_t write_reused_pack_verbatim(struct hashfile *out, + struct pack_window **w_curs) +{ + size_t pos = 0; + + while (pos < reuse_packfile_bitmap->word_alloc && + reuse_packfile_bitmap->words[pos] == (eword_t)~0) + pos++; + + if (pos) { + off_t to_write; + + written = (pos * BITS_IN_EWORD); + to_write = pack_pos_to_offset(reuse_packfile, written) + - sizeof(struct pack_header); + + /* We're recording one chunk, not one object. */ + record_reused_object(sizeof(struct pack_header), 0); + hashflush(out); + copy_pack_data(out, reuse_packfile, w_curs, + sizeof(struct pack_header), to_write); - /* - * We don't know the actual number of objects written, - * only how many bytes written, how many bytes total, and - * how many objects total. So we can fake it by pretending all - * objects we are writing are the same size. This gives us a - * smooth progress meter, and at the end it matches the true - * answer. - */ - written = reuse_packfile_objects * - (((double)(total - to_write)) / total); display_progress(progress_state, written); } + return pos; +} + +static void write_reused_pack(struct hashfile *f) +{ + size_t i = 0; + uint32_t offset; + struct pack_window *w_curs = NULL; + + if (allow_ofs_delta) + i = write_reused_pack_verbatim(f, &w_curs); + + for (; i < reuse_packfile_bitmap->word_alloc; ++i) { + eword_t word = reuse_packfile_bitmap->words[i]; + size_t pos = (i * BITS_IN_EWORD); + + for (offset = 0; offset < BITS_IN_EWORD; ++offset) { + if ((word >> offset) == 0) + break; + + offset += ewah_bit_ctz64(word >> offset); + /* + * Can use bit positions directly, even for MIDX + * bitmaps. See comment in try_partial_reuse() + * for why. + */ + write_reused_pack_one(pos + offset, f, &w_curs); + display_progress(progress_state, ++written); + } + } - close(fd); - written = reuse_packfile_objects; - display_progress(progress_state, written); - return reuse_packfile_offset - sizeof(struct pack_header); + unuse_pack(&w_curs); +} + +static void write_excluded_by_configs(void) +{ + struct oidset_iter iter; + const struct object_id *oid; + + oidset_iter_init(&excluded_by_config, &iter); + while ((oid = oidset_iter_next(&iter))) { + struct configured_exclusion *ex = + oidmap_get(&configured_exclusions, oid); + + if (!ex) + BUG("configured exclusion wasn't configured"); + write_in_full(1, ex->pack_hash_hex, strlen(ex->pack_hash_hex)); + write_in_full(1, " ", 1); + write_in_full(1, ex->uri, strlen(ex->uri)); + write_in_full(1, "\n", 1); + } } static const char no_split_warning[] = N_( @@ -856,7 +1175,7 @@ write_order = compute_write_order(); do { - struct object_id oid; + unsigned char hash[GIT_MAX_RAWSZ]; char *pack_tmp_name = NULL; if (pack_to_stdout) @@ -867,11 +1186,9 @@ offset = write_pack_header(f, nr_remaining); if (reuse_packfile) { - off_t packfile_size; assert(pack_to_stdout); - - packfile_size = write_reused_pack(f); - offset += packfile_size; + write_reused_pack(f); + offset = hashfile_total(f); } nr_written = 0; @@ -887,13 +1204,13 @@ * If so, rewrite it like in fast-import */ if (pack_to_stdout) { - finalize_hashfile(f, oid.hash, CSUM_HASH_IN_STREAM | CSUM_CLOSE); + finalize_hashfile(f, hash, CSUM_HASH_IN_STREAM | CSUM_CLOSE); } else if (nr_written == nr_remaining) { - finalize_hashfile(f, oid.hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE); + finalize_hashfile(f, hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE); } else { - int fd = finalize_hashfile(f, oid.hash, 0); - fixup_pack_header_footer(fd, oid.hash, pack_tmp_name, - nr_written, oid.hash, offset); + int fd = finalize_hashfile(f, hash, 0); + fixup_pack_header_footer(fd, hash, pack_tmp_name, + nr_written, hash, offset); close(fd); if (write_bitmap_index) { if (write_bitmap_index != WRITE_BITMAP_QUIET) @@ -905,6 +1222,7 @@ if (!pack_to_stdout) { struct stat st; struct strbuf tmpname = STRBUF_INIT; + char *idx_tmp_name = NULL; /* * Packs are runtime accessed in their mtime @@ -925,35 +1243,41 @@ warning_errno(_("failed utime() on %s"), pack_tmp_name); } - strbuf_addf(&tmpname, "%s-", base_name); + strbuf_addf(&tmpname, "%s-%s.", base_name, + hash_to_hex(hash)); if (write_bitmap_index) { - bitmap_writer_set_checksum(oid.hash); + bitmap_writer_set_checksum(hash); bitmap_writer_build_type_index( &to_pack, written_list, nr_written); } - finish_tmp_packfile(&tmpname, pack_tmp_name, + stage_tmp_packfiles(&tmpname, pack_tmp_name, written_list, nr_written, - &pack_idx_opts, oid.hash); + &pack_idx_opts, hash, &idx_tmp_name); if (write_bitmap_index) { - strbuf_addf(&tmpname, "%s.bitmap", oid_to_hex(&oid)); + size_t tmpname_len = tmpname.len; + strbuf_addstr(&tmpname, "bitmap"); stop_progress(&progress_state); bitmap_writer_show_progress(progress); - bitmap_writer_reuse_bitmaps(&to_pack); bitmap_writer_select_commits(indexed_commits, indexed_commits_nr, -1); - bitmap_writer_build(&to_pack); + if (bitmap_writer_build(&to_pack) < 0) + die(_("failed to write bitmap index")); bitmap_writer_finish(written_list, nr_written, tmpname.buf, write_bitmap_options); write_bitmap_index = 0; + strbuf_setlen(&tmpname, tmpname_len); } + rename_tmp_packfile_idx(&tmpname, &idx_tmp_name); + + free(idx_tmp_name); strbuf_release(&tmpname); free(pack_tmp_name); - puts(oid_to_hex(&oid)); + puts(hash_to_hex(hash)); } /* mark written objects as written to previous pack */ @@ -1000,6 +1324,10 @@ { struct object_entry *entry; + if (reuse_packfile_bitmap && + bitmap_walk_contains(bitmap_git, reuse_packfile_bitmap, oid)) + return 1; + entry = packlist_find(&to_pack, oid); if (!entry) return 0; @@ -1013,7 +1341,8 @@ return 1; } -static int want_found_object(int exclude, struct packed_git *p) +static int want_found_object(const struct object_id *oid, int exclude, + struct packed_git *p) { if (exclude) return 1; @@ -1029,27 +1358,82 @@ * make sure no copy of this object appears in _any_ pack that makes us * to omit the object, so we need to check all the packs. * - * We can however first check whether these options can possible matter; + * We can however first check whether these options can possibly matter; * if they do not matter we know we want the object in generated pack. * Otherwise, we signal "-1" at the end to tell the caller that we do * not know either way, and it needs to check more packs. */ - if (!ignore_packed_keep_on_disk && - !ignore_packed_keep_in_core && - (!local || !have_non_local_packs)) - return 1; + /* + * Objects in packs borrowed from elsewhere are discarded regardless of + * if they appear in other packs that weren't borrowed. + */ if (local && !p->pack_local) return 0; - if (p->pack_local && - ((ignore_packed_keep_on_disk && p->pack_keep) || - (ignore_packed_keep_in_core && p->pack_keep_in_core))) - return 0; + + /* + * Then handle .keep first, as we have a fast(er) path there. + */ + if (ignore_packed_keep_on_disk || ignore_packed_keep_in_core) { + /* + * Set the flags for the kept-pack cache to be the ones we want + * to ignore. + * + * That is, if we are ignoring objects in on-disk keep packs, + * then we want to search through the on-disk keep and ignore + * the in-core ones. + */ + unsigned flags = 0; + if (ignore_packed_keep_on_disk) + flags |= ON_DISK_KEEP_PACKS; + if (ignore_packed_keep_in_core) + flags |= IN_CORE_KEEP_PACKS; + + if (ignore_packed_keep_on_disk && p->pack_keep) + return 0; + if (ignore_packed_keep_in_core && p->pack_keep_in_core) + return 0; + if (has_object_kept_pack(oid, flags)) + return 0; + } + + /* + * At this point we know definitively that either we don't care about + * keep-packs, or the object is not in one. Keep checking other + * conditions... + */ + if (!local || !have_non_local_packs) + return 1; /* we don't know yet; keep looking for more packs */ return -1; } +static int want_object_in_pack_one(struct packed_git *p, + const struct object_id *oid, + int exclude, + struct packed_git **found_pack, + off_t *found_offset) +{ + off_t offset; + + if (p == *found_pack) + offset = *found_offset; + else + offset = find_pack_entry_one(oid->hash, p); + + if (offset) { + if (!*found_pack) { + if (!is_pack_valid(p)) + return -1; + *found_offset = offset; + *found_pack = p; + } + return want_found_object(oid, exclude, p); + } + return -1; +} + /* * Check whether we want the object in the pack (e.g., we do not want * objects found in non-local stores if the "--local" option was used). @@ -1077,7 +1461,7 @@ * are present we will determine the answer right now. */ if (*found_pack) { - want = want_found_object(exclude, *found_pack); + want = want_found_object(oid, exclude, *found_pack); if (want != -1) return want; } @@ -1085,50 +1469,38 @@ for (m = get_multi_pack_index(the_repository); m; m = m->next) { struct pack_entry e; if (fill_midx_entry(the_repository, oid, &e, m)) { - struct packed_git *p = e.p; - off_t offset; - - if (p == *found_pack) - offset = *found_offset; - else - offset = find_pack_entry_one(oid->hash, p); - - if (offset) { - if (!*found_pack) { - if (!is_pack_valid(p)) - continue; - *found_offset = offset; - *found_pack = p; - } - want = want_found_object(exclude, p); - if (want != -1) - return want; - } + want = want_object_in_pack_one(e.p, oid, exclude, found_pack, found_offset); + if (want != -1) + return want; } } list_for_each(pos, get_packed_git_mru(the_repository)) { struct packed_git *p = list_entry(pos, struct packed_git, mru); - off_t offset; + want = want_object_in_pack_one(p, oid, exclude, found_pack, found_offset); + if (!exclude && want > 0) + list_move(&p->mru, + get_packed_git_mru(the_repository)); + if (want != -1) + return want; + } - if (p == *found_pack) - offset = *found_offset; - else - offset = find_pack_entry_one(oid->hash, p); + if (uri_protocols.nr) { + struct configured_exclusion *ex = + oidmap_get(&configured_exclusions, oid); + int i; + const char *p; - if (offset) { - if (!*found_pack) { - if (!is_pack_valid(p)) - continue; - *found_offset = offset; - *found_pack = p; - } - want = want_found_object(exclude, p); - if (!exclude && want > 0) - list_move(&p->mru, - get_packed_git_mru(the_repository)); - if (want != -1) - return want; + if (ex) { + for (i = 0; i < uri_protocols.nr; i++) { + if (skip_prefix(ex->uri, + uri_protocols.items[i].string, + &p) && + *p == ':') { + oidset_insert(&excluded_by_config, oid); + return 0; + } + } } } @@ -1441,7 +1813,7 @@ } } - it = xcalloc(1, sizeof(*it)); + CALLOC_ARRAY(it, 1); it->next = pbase_tree; pbase_tree = it; @@ -1486,23 +1858,17 @@ * deltify other objects against, in order to avoid * circular deltas. */ -static int can_reuse_delta(const unsigned char *base_sha1, +static int can_reuse_delta(const struct object_id *base_oid, struct object_entry *delta, struct object_entry **base_out) { struct object_entry *base; - struct object_id base_oid; - - if (!base_sha1) - return 0; - - oidread(&base_oid, base_sha1); /* * First see if we're already sending the base (or it's explicitly in * our "excluded" list). */ - base = packlist_find(&to_pack, &base_oid); + base = packlist_find(&to_pack, base_oid); if (base) { if (!in_same_island(&delta->idx.oid, &base->idx.oid)) return 0; @@ -1515,9 +1881,9 @@ * even if it was buried too deep in history to make it into the * packing list. */ - if (thin && bitmap_has_oid_in_uninteresting(bitmap_git, &base_oid)) { + if (thin && bitmap_has_oid_in_uninteresting(bitmap_git, base_oid)) { if (use_delta_islands) { - if (!in_same_island(&delta->idx.oid, &base_oid)) + if (!in_same_island(&delta->idx.oid, base_oid)) return 0; } *base_out = NULL; @@ -1527,14 +1893,36 @@ return 0; } -static void check_object(struct object_entry *entry) +static void prefetch_to_pack(uint32_t object_index_start) { + struct oid_array to_fetch = OID_ARRAY_INIT; + uint32_t i; + + for (i = object_index_start; i < to_pack.nr_objects; i++) { + struct object_entry *entry = to_pack.objects + i; + + if (!oid_object_info_extended(the_repository, + &entry->idx.oid, + NULL, + OBJECT_INFO_FOR_PREFETCH)) + continue; + oid_array_append(&to_fetch, &entry->idx.oid); + } + promisor_remote_get_direct(the_repository, + to_fetch.oid, to_fetch.nr); + oid_array_clear(&to_fetch); +} + +static void check_object(struct object_entry *entry, uint32_t object_index) { unsigned long canonical_size; + enum object_type type; + struct object_info oi = {.typep = &type, .sizep = &canonical_size}; if (IN_PACK(entry)) { struct packed_git *p = IN_PACK(entry); struct pack_window *w_curs = NULL; - const unsigned char *base_ref = NULL; + int have_base = 0; + struct object_id base_ref; struct object_entry *base_entry; unsigned long used, used_0; unsigned long avail; @@ -1575,9 +1963,13 @@ unuse_pack(&w_curs); return; case OBJ_REF_DELTA: - if (reuse_delta && !entry->preferred_base) - base_ref = use_pack(p, &w_curs, - entry->in_pack_offset + used, NULL); + if (reuse_delta && !entry->preferred_base) { + oidread(&base_ref, + use_pack(p, &w_curs, + entry->in_pack_offset + used, + NULL)); + have_base = 1; + } entry->in_pack_header_size = used + the_hash_algo->rawsz; break; case OBJ_OFS_DELTA: @@ -1603,17 +1995,19 @@ goto give_up; } if (reuse_delta && !entry->preferred_base) { - struct revindex_entry *revidx; - revidx = find_pack_revindex(p, ofs); - if (!revidx) + uint32_t pos; + if (offset_to_pack_pos(p, ofs, &pos) < 0) goto give_up; - base_ref = nth_packed_object_sha1(p, revidx->nr); + if (!nth_packed_object_id(&base_ref, p, + pack_pos_to_index(p, pos))) + have_base = 1; } entry->in_pack_header_size = used + used_0; break; } - if (can_reuse_delta(base_ref, entry, &base_entry)) { + if (have_base && + can_reuse_delta(&base_ref, entry, &base_entry)) { oe_set_type(entry, entry->in_pack_type); SET_SIZE(entry, in_pack_size); /* delta size */ SET_DELTA_SIZE(entry, in_pack_size); @@ -1623,7 +2017,7 @@ entry->delta_sibling_idx = base_entry->delta_child_idx; SET_DELTA_CHILD(base_entry, entry); } else { - SET_DELTA_EXT(entry, base_ref); + SET_DELTA_EXT(entry, &base_ref); } unuse_pack(&w_curs); @@ -1656,8 +2050,18 @@ unuse_pack(&w_curs); } - oe_set_type(entry, - oid_object_info(the_repository, &entry->idx.oid, &canonical_size)); + if (oid_object_info_extended(the_repository, &entry->idx.oid, &oi, + OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_LOOKUP_REPLACE) < 0) { + if (has_promisor_remote()) { + prefetch_to_pack(object_index); + if (oid_object_info_extended(the_repository, &entry->idx.oid, &oi, + OBJECT_INFO_SKIP_FETCH_OBJECT | OBJECT_INFO_LOOKUP_REPLACE) < 0) + type = -1; + } else { + type = -1; + } + } + oe_set_type(entry, type); if (entry->type_valid) { SET_SIZE(entry, canonical_size); } else { @@ -1870,14 +2274,14 @@ progress_state = start_progress(_("Counting objects"), to_pack.nr_objects); - sorted_by_offset = xcalloc(to_pack.nr_objects, sizeof(struct object_entry *)); + CALLOC_ARRAY(sorted_by_offset, to_pack.nr_objects); for (i = 0; i < to_pack.nr_objects; i++) sorted_by_offset[i] = to_pack.objects + i; QSORT(sorted_by_offset, to_pack.nr_objects, pack_offset_sort); for (i = 0; i < to_pack.nr_objects; i++) { struct object_entry *entry = sorted_by_offset[i]; - check_object(entry); + check_object(entry, i); if (entry->type_valid && oe_size_greater_than(&to_pack, entry, big_file_threshold)) entry->no_try_delta = 1; @@ -1980,6 +2384,26 @@ * progress_mutex for protection. */ +static inline int oe_size_less_than(struct packing_data *pack, + const struct object_entry *lhs, + unsigned long rhs) +{ + if (lhs->size_valid) + return lhs->size_ < rhs; + if (rhs < pack->oe_size_limit) /* rhs < 2^x <= lhs ? */ + return 0; + return oe_get_size_slow(pack, lhs) < rhs; +} + +static inline void oe_set_tree_depth(struct packing_data *pack, + struct object_entry *e, + unsigned int tree_depth) +{ + if (!pack->tree_depth) + CALLOC_ARRAY(pack->tree_depth, pack->nr_alloc); + pack->tree_depth[e - pack->objects] = tree_depth; +} + /* * Return the size of the object without doing any delta * reconstruction (so non-deltas are true object sizes, but deltas @@ -2202,7 +2626,7 @@ struct unpacked *array; unsigned long mem_usage = 0; - array = xcalloc(window, sizeof(struct unpacked)); + CALLOC_ARRAY(array, window); for (;;) { struct object_entry *entry; @@ -2439,7 +2863,7 @@ if (progress > pack_to_stdout) fprintf_ln(stderr, _("Delta compression using up to %d threads"), delta_search_threads); - p = xcalloc(delta_search_threads, sizeof(*p)); + CALLOC_ARRAY(p, delta_search_threads); /* Partition the work amongst work threads. */ for (i = 0; i < delta_search_threads; i++) { @@ -2552,6 +2976,13 @@ free(p); } +static int obj_is_packed(const struct object_id *oid) +{ + return packlist_find(&to_pack, oid) || + (reuse_packfile_bitmap && + bitmap_walk_contains(bitmap_git, reuse_packfile_bitmap, oid)); +} + static void add_tag_chain(const struct object_id *oid) { struct tag *tag; @@ -2563,7 +2994,7 @@ * it was included via bitmaps, we would not have parsed it * previously). */ - if (packlist_find(&to_pack, oid)) + if (obj_is_packed(oid)) return; tag = lookup_tag(the_repository, oid); @@ -2581,13 +3012,11 @@ } } -static int add_ref_tag(const char *path, const struct object_id *oid, int flag, void *cb_data) +static int add_ref_tag(const char *tag, const struct object_id *oid, int flag, void *cb_data) { struct object_id peeled; - if (starts_with(path, "refs/tags/") && /* is a tag? */ - !peel_ref(path, &peeled) && /* peelable? */ - packlist_find(&to_pack, &peeled)) /* object packed? */ + if (!peel_iterated_oid(oid, &peeled) && obj_is_packed(&peeled)) add_tag_chain(oid); return 0; } @@ -2655,6 +3084,7 @@ if (nr_deltas && n > 1) { unsigned nr_done = 0; + if (progress) progress_state = start_progress(_("Compressing objects"), nr_deltas); @@ -2699,6 +3129,10 @@ use_bitmap_index_default = git_config_bool(k, v); return 0; } + if (!strcmp(k, "pack.allowpackreuse")) { + allow_pack_reuse = git_config_bool(k, v); + return 0; + } if (!strcmp(k, "pack.threads")) { delta_search_threads = git_config_int(k, v); if (delta_search_threads < 0) @@ -2717,9 +3151,241 @@ pack_idx_opts.version); return 0; } + if (!strcmp(k, "pack.writereverseindex")) { + if (git_config_bool(k, v)) + pack_idx_opts.flags |= WRITE_REV; + else + pack_idx_opts.flags &= ~WRITE_REV; + return 0; + } + if (!strcmp(k, "uploadpack.blobpackfileuri")) { + struct configured_exclusion *ex = xmalloc(sizeof(*ex)); + const char *oid_end, *pack_end; + /* + * Stores the pack hash. This is not a true object ID, but is + * of the same form. + */ + struct object_id pack_hash; + + if (parse_oid_hex(v, &ex->e.oid, &oid_end) || + *oid_end != ' ' || + parse_oid_hex(oid_end + 1, &pack_hash, &pack_end) || + *pack_end != ' ') + die(_("value of uploadpack.blobpackfileuri must be " + "of the form ' ' (got '%s')"), v); + if (oidmap_get(&configured_exclusions, &ex->e.oid)) + die(_("object already configured in another " + "uploadpack.blobpackfileuri (got '%s')"), v); + ex->pack_hash_hex = xcalloc(1, pack_end - oid_end); + memcpy(ex->pack_hash_hex, oid_end + 1, pack_end - oid_end - 1); + ex->uri = xstrdup(pack_end + 1); + oidmap_put(&configured_exclusions, ex); + } return git_default_config(k, v, cb); } +/* Counters for trace2 output when in --stdin-packs mode. */ +static int stdin_packs_found_nr; +static int stdin_packs_hints_nr; + +static int add_object_entry_from_pack(const struct object_id *oid, + struct packed_git *p, + uint32_t pos, + void *_data) +{ + struct rev_info *revs = _data; + struct object_info oi = OBJECT_INFO_INIT; + off_t ofs; + enum object_type type; + + display_progress(progress_state, ++nr_seen); + + if (have_duplicate_entry(oid, 0)) + return 0; + + ofs = nth_packed_object_offset(p, pos); + 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); + } + + stdin_packs_found_nr++; + + create_object_entry(oid, type, 0, 0, 0, p, ofs); + + return 0; +} + +static void show_commit_pack_hint(struct commit *commit, void *_data) +{ + /* nothing to do; commits don't have a namehash */ +} + +static void show_object_pack_hint(struct object *object, const char *name, + void *_data) +{ + struct object_entry *oe = packlist_find(&to_pack, &object->oid); + if (!oe) + return; + + /* + * Our 'to_pack' list was constructed by iterating all objects packed in + * included packs, and so doesn't have a non-zero hash field that you + * would typically pick up during a reachability traversal. + * + * Make a best-effort attempt to fill in the ->hash and ->no_try_delta + * here using a now in order to perhaps improve the delta selection + * process. + */ + oe->hash = pack_name_hash(name); + oe->no_try_delta = name && no_try_delta(name); + + stdin_packs_hints_nr++; +} + +static int pack_mtime_cmp(const void *_a, const void *_b) +{ + struct packed_git *a = ((const struct string_list_item*)_a)->util; + struct packed_git *b = ((const struct string_list_item*)_b)->util; + + /* + * order packs by descending mtime so that objects are laid out + * roughly as newest-to-oldest + */ + if (a->mtime < b->mtime) + return 1; + else if (b->mtime < a->mtime) + return -1; + else + return 0; +} + +static void read_packs_list_from_stdin(void) +{ + struct strbuf buf = STRBUF_INIT; + struct string_list include_packs = STRING_LIST_INIT_DUP; + struct string_list exclude_packs = STRING_LIST_INIT_DUP; + struct string_list_item *item = NULL; + + struct packed_git *p; + struct rev_info revs; + + repo_init_revisions(the_repository, &revs, NULL); + /* + * Use a revision walk to fill in the namehash of objects in the include + * packs. To save time, we'll avoid traversing through objects that are + * in excluded packs. + * + * That may cause us to avoid populating all of the namehash fields of + * all included objects, but our goal is best-effort, since this is only + * an optimization during delta selection. + */ + revs.no_kept_objects = 1; + revs.keep_pack_cache_flags |= IN_CORE_KEEP_PACKS; + revs.blob_objects = 1; + revs.tree_objects = 1; + revs.tag_objects = 1; + revs.ignore_missing_links = 1; + + while (strbuf_getline(&buf, stdin) != EOF) { + if (!buf.len) + continue; + + if (*buf.buf == '^') + string_list_append(&exclude_packs, buf.buf + 1); + else + string_list_append(&include_packs, buf.buf); + + strbuf_reset(&buf); + } + + string_list_sort(&include_packs); + string_list_sort(&exclude_packs); + + for (p = get_all_packs(the_repository); p; p = p->next) { + const char *pack_name = pack_basename(p); + + item = string_list_lookup(&include_packs, pack_name); + if (!item) + item = string_list_lookup(&exclude_packs, pack_name); + + if (item) + item->util = p; + } + + /* + * Arguments we got on stdin may not even be packs. First + * check that to avoid segfaulting later on in + * e.g. pack_mtime_cmp(), excluded packs are handled below. + * + * Since we first parsed our STDIN and then sorted the input + * lines the pack we error on will be whatever line happens to + * sort first. This is lazy, it's enough that we report one + * bad case here, we don't need to report the first/last one, + * or all of them. + */ + for_each_string_list_item(item, &include_packs) { + struct packed_git *p = item->util; + if (!p) + die(_("could not find pack '%s'"), item->string); + } + + /* + * Then, handle all of the excluded packs, marking them as + * kept in-core so that later calls to add_object_entry() + * discards any objects that are also found in excluded packs. + */ + for_each_string_list_item(item, &exclude_packs) { + struct packed_git *p = item->util; + if (!p) + die(_("could not find pack '%s'"), item->string); + p->pack_keep_in_core = 1; + } + + /* + * Order packs by ascending mtime; use QSORT directly to access the + * string_list_item's ->util pointer, which string_list_sort() does not + * provide. + */ + QSORT(include_packs.items, include_packs.nr, pack_mtime_cmp); + + 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, + FOR_EACH_OBJECT_PACK_ORDER); + } + + if (prepare_revision_walk(&revs)) + die(_("revision walk setup failed")); + traverse_commit_list(&revs, + show_commit_pack_hint, + show_object_pack_hint, + NULL); + + trace2_data_intmax("pack-objects", the_repository, "stdin_packs_found", + stdin_packs_found_nr); + trace2_data_intmax("pack-objects", the_repository, "stdin_packs_hints", + stdin_packs_hints_nr); + + strbuf_release(&buf); + string_list_clear(&include_packs, 0); + string_list_clear(&exclude_packs, 0); +} + static void read_object_list_from_stdin(void) { char line[GIT_MAX_HEXSZ + 1 + PATH_MAX + 2]; @@ -2752,13 +3418,9 @@ } } -/* Remember to update object flag allocation in object.h */ -#define OBJECT_ADDED (1u<<20) - static void show_commit(struct commit *commit, void *data) { add_object_entry(&commit->object.oid, OBJ_COMMIT, NULL, 0); - commit->object.flags |= OBJECT_ADDED; if (write_bitmap_index) index_commit_for_bitmap(commit); @@ -2771,7 +3433,6 @@ { add_preferred_base_object(name); add_object_entry(&obj->oid, obj->type, name, 0); - obj->flags |= OBJECT_ADDED; if (use_delta_islands) { const char *p; @@ -2797,7 +3458,7 @@ * Quietly ignore ALL missing objects. This avoids problems with * staging them now and getting an odd error later. */ - if (!has_object_file(&obj->oid)) + if (!has_object(the_repository, &obj->oid, 0)) return; show_object(obj, name, data); @@ -2811,7 +3472,7 @@ * Quietly ignore EXPECTED missing objects. This avoids problems with * staging them now and getting an odd error later. */ - if (!has_object_file(&obj->oid) && is_promisor_object(&obj->oid)) + if (!has_object(the_repository, &obj->oid, 0) && is_promisor_object(&obj->oid)) return; show_object(obj, name, data); @@ -2852,79 +3513,23 @@ add_preferred_base(&commit->object.oid); } -struct in_pack_object { - off_t offset; - struct object *object; -}; - -struct in_pack { - unsigned int alloc; - unsigned int nr; - struct in_pack_object *array; -}; - -static void mark_in_pack_object(struct object *object, struct packed_git *p, struct in_pack *in_pack) -{ - in_pack->array[in_pack->nr].offset = find_pack_entry_one(object->oid.hash, p); - in_pack->array[in_pack->nr].object = object; - in_pack->nr++; -} - -/* - * Compare the objects in the offset order, in order to emulate the - * "git rev-list --objects" output that produced the pack originally. - */ -static int ofscmp(const void *a_, const void *b_) +static int add_object_in_unpacked_pack(const struct object_id *oid, + struct packed_git *pack, + uint32_t pos, + void *_data) { - struct in_pack_object *a = (struct in_pack_object *)a_; - struct in_pack_object *b = (struct in_pack_object *)b_; - - if (a->offset < b->offset) - return -1; - else if (a->offset > b->offset) - return 1; - else - return oidcmp(&a->object->oid, &b->object->oid); + add_object_entry(oid, OBJ_NONE, "", 0); + return 0; } static void add_objects_in_unpacked_packs(void) { - struct packed_git *p; - struct in_pack in_pack; - uint32_t i; - - memset(&in_pack, 0, sizeof(in_pack)); - - for (p = get_all_packs(the_repository); p; p = p->next) { - struct object_id oid; - struct object *o; - - if (!p->pack_local || p->pack_keep || p->pack_keep_in_core) - continue; - if (open_pack_index(p)) - die(_("cannot open pack index")); - - ALLOC_GROW(in_pack.array, - in_pack.nr + p->num_objects, - in_pack.alloc); - - for (i = 0; i < p->num_objects; i++) { - nth_packed_object_oid(&oid, p, i); - o = lookup_unknown_object(&oid); - if (!(o->flags & OBJECT_ADDED)) - mark_in_pack_object(o, p, &in_pack); - o->flags |= OBJECT_ADDED; - } - } - - if (in_pack.nr) { - QSORT(in_pack.array, in_pack.nr, ofscmp); - for (i = 0; i < in_pack.nr; i++) { - struct object *o = in_pack.array[i].object; - add_object_entry(&o->oid, o->type, "", 0); - } - } - free(in_pack.array); + if (for_each_packed_object(add_object_in_unpacked_pack, NULL, + FOR_EACH_OBJECT_PACK_ORDER | + FOR_EACH_OBJECT_LOCAL_ONLY | + FOR_EACH_OBJECT_SKIP_IN_CORE_KEPT_PACKS | + FOR_EACH_OBJECT_SKIP_ON_DISK_KEPT_PACKS)) + die(_("cannot open pack index")); } static int add_loose_object(const struct object_id *oid, const char *path, @@ -3003,6 +3608,7 @@ { struct packed_git *p; uint32_t i; + uint32_t loosened_objects_nr = 0; struct object_id oid; for (p = get_all_packs(the_repository); p; p = p->next) { @@ -3013,14 +3619,19 @@ die(_("cannot open pack index")); for (i = 0; i < p->num_objects; i++) { - nth_packed_object_oid(&oid, p, i); + nth_packed_object_id(&oid, p, i); if (!packlist_find(&to_pack, &oid) && !has_sha1_pack_kept_or_nonlocal(&oid) && - !loosened_object_can_be_discarded(&oid, p->mtime)) + !loosened_object_can_be_discarded(&oid, p->mtime)) { if (force_object_loose(&oid, p->mtime)) die(_("unable to force loose object")); + loosened_objects_nr++; + } } } + + trace2_data_intmax("pack-objects", the_repository, + "loosen_unused_packed_objects/loosened", loosened_objects_nr); } /* @@ -3030,8 +3641,8 @@ */ static int pack_options_allow_reuse(void) { - return pack_to_stdout && - allow_ofs_delta && + return allow_pack_reuse && + pack_to_stdout && !ignore_packed_keep_on_disk && !ignore_packed_keep_in_core && (!local || !have_non_local_packs) && @@ -3040,7 +3651,7 @@ static int get_object_list_from_bitmap(struct rev_info *revs) { - if (!(bitmap_git = prepare_bitmap_walk(revs))) + if (!(bitmap_git = prepare_bitmap_walk(revs, &filter_options, 0))) return -1; if (pack_options_allow_reuse() && @@ -3048,13 +3659,15 @@ bitmap_git, &reuse_packfile, &reuse_packfile_objects, - &reuse_packfile_offset)) { + &reuse_packfile_bitmap)) { assert(reuse_packfile_objects); nr_result += reuse_packfile_objects; - display_progress(progress_state, nr_result); + nr_seen += reuse_packfile_objects; + display_progress(progress_state, nr_seen); } - traverse_bitmap_commit_list(bitmap_git, &add_object_entry_from_bitmap); + traverse_bitmap_commit_list(bitmap_git, revs, + &add_object_entry_from_bitmap); return 0; } @@ -3070,6 +3683,37 @@ oid_array_append(&recent_objects, &commit->object.oid); } +static int mark_bitmap_preferred_tip(const char *refname, + const struct object_id *oid, int flags, + void *_data) +{ + struct object_id peeled; + struct object *object; + + if (!peel_iterated_oid(oid, &peeled)) + oid = &peeled; + + object = parse_object_or_die(oid, refname); + if (object->type == OBJ_COMMIT) + object->flags |= NEEDS_BITMAP; + + return 0; +} + +static void mark_bitmap_preferred_tips(void) +{ + struct string_list_item *item; + const struct string_list *preferred_tips; + + preferred_tips = bitmap_preferred_tips(the_repository); + if (!preferred_tips) + return; + + for_each_string_list_item(item, preferred_tips) { + for_each_ref_in(item->string, mark_bitmap_preferred_tip, NULL); + } +} + static void get_object_list(int ac, const char **av) { struct rev_info revs; @@ -3105,7 +3749,7 @@ if (starts_with(line, "--shallow ")) { struct object_id oid; if (get_oid_hex(line + 10, &oid)) - die("not an SHA-1 '%s'", line + 10); + die("not an object name '%s'", line + 10); register_shallow(the_repository, &oid); use_bitmap_index = 0; continue; @@ -3124,6 +3768,9 @@ if (use_delta_islands) load_delta_islands(the_repository, progress); + if (write_bitmap_index) + mark_bitmap_preferred_tips(); + if (prepare_revision_walk(&revs)) die(_("revision walk setup failed")); mark_edges_uninteresting(&revs, show_edge, sparse); @@ -3219,9 +3866,10 @@ int use_internal_rev_list = 0; int shallow = 0; int all_progress_implied = 0; - struct argv_array rp = ARGV_ARRAY_INIT; + struct strvec rp = STRVEC_INIT; int rev_list_unpacked = 0, rev_list_all = 0, rev_list_reflog = 0; int rev_list_index = 0; + int stdin_packs = 0; struct string_list keep_pack_list = STRING_LIST_INIT_NODUP; struct option pack_objects_options[] = { OPT_SET_INT('q', "quiet", &progress, @@ -3233,9 +3881,9 @@ OPT_BOOL(0, "all-progress-implied", &all_progress_implied, N_("similar to --all-progress when progress meter is shown")), - { OPTION_CALLBACK, 0, "index-version", NULL, N_("[,]"), + OPT_CALLBACK_F(0, "index-version", NULL, N_("[,]"), N_("write the pack index file in the specified idx format version"), - PARSE_OPT_NONEG, option_parse_index_version }, + PARSE_OPT_NONEG, option_parse_index_version), OPT_MAGNITUDE(0, "max-pack-size", &pack_size_limit, N_("maximum size of each output pack file")), OPT_BOOL(0, "local", &local, @@ -3272,6 +3920,8 @@ OPT_SET_INT_F(0, "indexed-objects", &rev_list_index, N_("include objects referred to by the index"), 1, PARSE_OPT_NONEG), + OPT_BOOL(0, "stdin-packs", &stdin_packs, + N_("read packs from stdin")), OPT_BOOL(0, "stdout", &pack_to_stdout, N_("output pack to stdout")), OPT_BOOL(0, "include-tag", &include_tag, @@ -3280,9 +3930,9 @@ N_("keep unreachable objects")), OPT_BOOL(0, "pack-loose-unreachable", &pack_loose_unreachable, N_("pack loose unreachable objects")), - { OPTION_CALLBACK, 0, "unpack-unreachable", NULL, N_("time"), + OPT_CALLBACK_F(0, "unpack-unreachable", NULL, N_("time"), N_("unpack unreachable objects newer than