diff options
author | Vincent Ambo <mail@tazj.in> | 2020-11-21T18·20+0100 |
---|---|---|
committer | Vincent Ambo <mail@tazj.in> | 2020-11-21T18·45+0100 |
commit | f4609b896fac842433bd495c166d5987852a6a73 (patch) | |
tree | 95511c465c54c4f5d27e5d39ce187e2a1dd82bd3 /third_party/git/builtin | |
parent | 082c006c04343a78d87b6c6ab3608c25d6213c3f (diff) |
merge(3p/git): Merge git subtree at v2.29.2 r/1890
This also bumps the stable nixpkgs to 20.09 as of 2020-11-21, because there is some breakage in the git build related to the netrc credentials helper which someone has taken care of in nixpkgs. The stable channel is not used for anything other than git, so this should be fine. Change-Id: I3575a19dab09e1e9556cf8231d717de9890484fb
Diffstat (limited to 'third_party/git/builtin')
86 files changed, 12820 insertions, 4207 deletions
diff --git a/third_party/git/builtin/add.c b/third_party/git/builtin/add.c index dd18e5c9b670..a825887c503d 100644 --- a/third_party/git/builtin/add.c +++ b/third_party/git/builtin/add.c @@ -18,8 +18,9 @@ #include "diffcore.h" #include "revision.h" #include "bulk-checkin.h" -#include "argv-array.h" +#include "strvec.h" #include "submodule.h" +#include "add-interactive.h" static const char * const builtin_add_usage[] = { N_("git add [<options>] [--] <pathspec>..."), @@ -28,6 +29,9 @@ static const char * const builtin_add_usage[] = { static int patch_interactive, add_interactive, edit_interactive; static int take_worktree_changes; static int add_renormalize; +static int pathspec_file_nul; +static const char *pathspec_from_file; +static int legacy_stash_p; /* support for the scripted `git stash` */ struct update_callback_data { int flags; @@ -184,24 +188,58 @@ 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) { + 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")) + 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; @@ -298,10 +336,10 @@ static struct option builtin_add_options[] = { 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")), @@ -309,6 +347,10 @@ static struct option builtin_add_options[] = { 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(), }; @@ -319,6 +361,7 @@ static int add_config(const char *var, const char *value, void *cb) ignore_add_errors = git_config_bool(var, value); return 0; } + return git_default_config(var, value, cb); } @@ -369,7 +412,10 @@ static int add_files(struct dir_struct *dir, int flags) 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_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; } @@ -402,11 +448,28 @@ int cmd_add(int argc, const char **argv, const char *prefix) builtin_add_usage, PARSE_OPT_KEEP_ARGV0); if (patch_interactive) add_interactive = 1; - if (add_interactive) - exit(interactive_add(argc - 1, argv + 1, prefix, patch_interactive)); + if (add_interactive) { + if (pathspec_from_file) + die(_("--pathspec-from-file is incompatible with --interactive/--patch")); + exit(interactive_add(argv + 1, prefix, patch_interactive)); + } + if (legacy_stash_p) { + struct pathspec pathspec; - if (edit_interactive) + 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) { + if (pathspec_from_file) + die(_("--pathspec-from-file is incompatible with --edit")); return(edit_patch(argc, argv, prefix)); + } argc--; argv++; @@ -418,10 +481,6 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (addremove && take_worktree_changes) die(_("-A and -u are mutually incompatible")); - if (!take_worktree_changes && addremove_explicit < 0 && argc) - /* Turn "git add pathspec..." to "git add -A pathspec..." */ - addremove = 1; - if (!show_only && ignore_missing) die(_("Option --ignore-missing can only be used together with --dry-run")); @@ -434,19 +493,6 @@ int cmd_add(int argc, const char **argv, const char *prefix) hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); - flags = ((verbose ? ADD_CACHE_VERBOSE : 0) | - (show_only ? ADD_CACHE_PRETEND : 0) | - (intent_to_add ? ADD_CACHE_INTENT : 0) | - (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) | - (!(addremove || take_worktree_changes) - ? ADD_CACHE_IGNORE_REMOVAL : 0)); - - if (require_pathspec && argc == 0) { - fprintf(stderr, _("Nothing specified, nothing added.\n")); - fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n")); - return 0; - } - /* * Check the "pathspec '%s' did not match any files" block * below before enabling new magic. @@ -456,17 +502,49 @@ int cmd_add(int argc, const char **argv, const char *prefix) PATHSPEC_SYMLINK_LEADING_PATH, prefix, argv); + if (pathspec_from_file) { + if (pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&pathspec, PATHSPEC_ATTR, + PATHSPEC_PREFER_FULL | + PATHSPEC_SYMLINK_LEADING_PATH, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + + if (require_pathspec && pathspec.nr == 0) { + fprintf(stderr, _("Nothing specified, nothing added.\n")); + if (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; + } + + if (!take_worktree_changes && addremove_explicit < 0 && pathspec.nr) + /* Turn "git add pathspec..." to "git add -A pathspec..." */ + addremove = 1; + + flags = ((verbose ? ADD_CACHE_VERBOSE : 0) | + (show_only ? ADD_CACHE_PRETEND : 0) | + (intent_to_add ? ADD_CACHE_INTENT : 0) | + (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) | + (!(addremove || take_worktree_changes) + ? ADD_CACHE_IGNORE_REMOVAL : 0)); + if (read_cache_preload(&pathspec) < 0) die(_("index file corrupt")); die_in_unpopulated_submodule(&the_index, prefix); die_path_inside_submodule(&the_index, &pathspec); + dir_init(&dir); if (add_new_files) { 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); @@ -539,7 +617,7 @@ finish: COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("Unable to write new index file")); + dir_clear(&dir); UNLEAK(pathspec); - UNLEAK(dir); return exit_status; } diff --git a/third_party/git/builtin/am.c b/third_party/git/builtin/am.c index 1aea657a7f0b..4949535a7f1f 100644 --- a/third_party/git/builtin/am.c +++ b/third_party/git/builtin/am.c @@ -24,7 +24,6 @@ #include "sequencer.h" #include "revision.h" #include "merge-recursive.h" -#include "revision.h" #include "log-tree.h" #include "notes-utils.h" #include "rerere.h" @@ -82,6 +81,11 @@ enum signoff_type { 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; @@ -94,6 +98,8 @@ struct am_state { char *author_name; char *author_email; char *author_date; + char *committer_name; + char *committer_email; char *msg; size_t msg_len; @@ -112,7 +118,7 @@ struct am_state { int keep; /* enum keep_type */ int message_id; int scissors; /* enum scissors_type */ - struct argv_array git_apply_opts; + struct strvec git_apply_opts; const char *resolvemsg; int committer_date_is_author_date; int ignore_date; @@ -126,6 +132,8 @@ struct am_state { */ static void am_state_init(struct am_state *state) { + const char *committer; + struct ident_split id; int gpgsign; memset(state, 0, sizeof(*state)); @@ -142,10 +150,18 @@ static void am_state_init(struct am_state *state) state->scissors = SCISSORS_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; + + committer = git_committer_info(IDENT_STRICT); + if (split_ident_line(&id, committer, strlen(committer)) < 0) + die(_("invalid committer: %s"), committer); + state->committer_name = + xmemdupz(id.name_begin, id.name_end - id.name_begin); + state->committer_email = + xmemdupz(id.mail_begin, id.mail_end - id.mail_begin); } /** @@ -157,8 +173,10 @@ static void am_state_release(struct am_state *state) free(state->author_name); free(state->author_email); free(state->author_date); + free(state->committer_name); + free(state->committer_email); free(state->msg); - argv_array_clear(&state->git_apply_opts); + strvec_clear(&state->git_apply_opts); } /** @@ -394,8 +412,8 @@ static void am_load(struct am_state *state) state->scissors = SCISSORS_UNSET; 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")); @@ -448,8 +466,8 @@ static int run_post_rewrite_hook(const struct am_state *state) 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; @@ -647,16 +665,16 @@ static int split_mail_mbox(struct am_state *state, const char **paths, 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) @@ -783,7 +801,7 @@ static int split_mail_stgit_series(struct am_state *state, const char **paths, 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; @@ -801,16 +819,16 @@ static int split_mail_stgit_series(struct am_state *state, const char **paths, 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; } @@ -998,7 +1016,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, } write_state_text(state, "scissors", str); - sq_quote_argv(&sb, state->git_apply_opts.argv); + sq_quote_argv(&sb, state->git_apply_opts.v); write_state_text(state, "apply-opt", sb.buf); if (state->rebasing) @@ -1072,19 +1090,6 @@ static const char *msgnum(const struct am_state *state) } /** - * Refresh and write index. - */ -static void refresh_and_write_cache(void) -{ - struct lock_file lock_file = LOCK_INIT; - - hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); - refresh_cache(REFRESH_QUIET); - if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) - die(_("unable to write index file")); -} - -/** * Dies with a user-friendly message on how to proceed after resolving the * problem. This message can be overridden with state->resolvemsg. */ @@ -1272,7 +1277,9 @@ static void get_commit_info(struct am_state *state, struct commit *commit) buffer = logmsg_reencode(commit, NULL, get_commit_output_encoding()); ident_line = find_commit_header(buffer, "author", &ident_len); - + if (!ident_line) + die(_("missing author line in commit %s"), + oid_to_hex(&commit->object.oid)); if (split_ident_line(&id, ident_line, ident_len) < 0) die(_("invalid ident line: %.*s"), (int)ident_len, ident_line); @@ -1397,8 +1404,8 @@ static int parse_mail_rebase(struct am_state *state, const char *mail) */ 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; @@ -1407,10 +1414,10 @@ static int run_apply(const struct am_state *state, const char *index_file) 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); @@ -1433,12 +1440,12 @@ static int run_apply(const struct am_state *state, const char *index_file) 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) @@ -1461,10 +1468,10 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f 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; @@ -1538,7 +1545,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa o.branch1 = "HEAD"; their_tree_name = xstrfmt("%.*s", linelen(state->msg), state->msg); o.branch2 = their_tree_name; - o.detect_directory_renames = 0; + o.detect_directory_renames = MERGE_DIRECTORY_RENAMES_NONE; if (state->quiet) o.verbosity = 0; @@ -1563,7 +1570,7 @@ static void do_commit(const struct am_state *state) 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)) @@ -1587,11 +1594,15 @@ static void do_commit(const struct am_state *state) 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(state->committer_name, + state->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"); @@ -1683,7 +1694,7 @@ static int do_interactive(struct am_state *state) 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); } } @@ -1698,12 +1709,12 @@ static int do_interactive(struct am_state *state) */ 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")); - refresh_and_write_cache(); + if (refresh_and_write_cache(REFRESH_QUIET, 0, 0) < 0) + die(_("unable to write index file")); if (repo_index_has_changes(the_repository, NULL, &sb)) { write_state_bool(state, "dirtyindex", 1); @@ -1774,7 +1785,7 @@ static void am_run(struct am_state *state, int resume) linelen(state->msg), state->msg); if (advice_amworkdir) - advise(_("Use 'git am --show-current-patch' to see the failed patch")); + advise(_("Use 'git am --show-current-patch=diff' to see the failed patch")); die_user_resolve(state); } @@ -1802,7 +1813,7 @@ next: 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); } } @@ -2072,7 +2083,7 @@ static void am_abort(struct am_state *state) 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; @@ -2089,7 +2100,17 @@ static int show_patch(struct am_state *state) 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); @@ -2129,7 +2150,7 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int return 0; } -enum resume_mode { +enum resume_type { RESUME_FALSE = 0, RESUME_APPLY, RESUME_RESOLVED, @@ -2139,6 +2160,47 @@ enum resume_mode { 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; @@ -2156,7 +2218,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) 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; @@ -2225,24 +2287,26 @@ int cmd_am(int argc, const char **argv, const char *prefix) 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, + OPT_CMDMODE(0, "abort", &resume.mode, N_("restore the original branch and abort the patching operation."), RESUME_ABORT), - OPT_CMDMODE(0, "quit", &resume, + 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")), @@ -2292,17 +2356,17 @@ int cmd_am(int argc, const char **argv, const char *prefix) * 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; /* @@ -2311,7 +2375,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) * 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; @@ -2322,25 +2386,25 @@ int cmd_am(int argc, const char **argv, const char *prefix) 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; @@ -2361,7 +2425,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) 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 --git a/third_party/git/builtin/annotate.c b/third_party/git/builtin/annotate.c index da413ae0d178..58ff977a2314 100644 --- a/third_party/git/builtin/annotate.c +++ b/third_party/git/builtin/annotate.c @@ -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 --git a/third_party/git/builtin/bisect--helper.c b/third_party/git/builtin/bisect--helper.c index 1fbe156e67a4..7512b880f0d6 100644 --- a/third_party/git/builtin/bisect--helper.c +++ b/third_party/git/builtin/bisect--helper.c @@ -4,22 +4,23 @@ #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 const char * const git_bisect_helper_usage[] = { - N_("git bisect--helper --next-all [--no-checkout]"), + N_("git bisect--helper --next-all"), N_("git bisect--helper --write-terms <bad_term> <good_term>"), N_("git bisect--helper --bisect-clean-state"), N_("git bisect--helper --bisect-reset [<commit>]"), @@ -27,11 +28,19 @@ static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --bisect-check-and-set-terms <command> <good_term> <bad_term>"), N_("git bisect--helper --bisect-next-check <good_term> <bad_term> [<term>]"), N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"), - N_("git bisect--helper --bisect-start [--term-{old,good}=<term> --term-{new,bad}=<term>]" - "[--no-checkout] [<bad> [<good>...]] [--] [<paths>...]"), + N_("git bisect--helper --bisect-start [--term-{new,bad}=<term> --term-{old,good}=<term>]" + " [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]"), + N_("git bisect--helper --bisect-next"), + N_("git bisect--helper --bisect-auto-next"), + N_("git bisect--helper --bisect-autostart"), 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 +61,10 @@ static void set_terms(struct bisect_terms *terms, const char *bad, 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 +85,52 @@ static int one_of(const char *term, ...) return res; } +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); +} + +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; +} + +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 check_term_format(const char *term, const char *orig_term) { int res; @@ -104,7 +161,6 @@ static int check_term_format(const char *term, const char *orig_term) static int write_terms(const char *bad, const char *good) { - FILE *fp = NULL; int res; if (!strcmp(bad, good)) @@ -113,13 +169,9 @@ static int write_terms(const char *bad, const char *good) 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 = write_to_file(git_path_bisect_terms(), "%s\n%s\n", bad, good); - res = fprintf(fp, "%s\n%s\n", bad, good); - res |= fclose(fp); - return (res < 0) ? -1 : 0; + return res; } static int is_expected_rev(const char *expected_hex) @@ -164,18 +216,19 @@ static int bisect_reset(const char *commit) 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 <commit>'."), branch.buf); strbuf_release(&branch); - argv_array_clear(&argv); - return error(_("could not check out original" - " HEAD '%s'. Try 'git bisect" - " reset <commit>'."), branch.buf); + strvec_clear(&argv); + return -1; } - argv_array_clear(&argv); + strvec_clear(&argv); } strbuf_release(&branch); @@ -205,31 +258,31 @@ static int bisect_write(const char *state, const char *rev, 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; } @@ -243,7 +296,7 @@ finish: if (fp) fclose(fp); strbuf_release(&tag); - return retval; + return res; } static int check_and_set_terms(struct bisect_terms *terms, const char *cmd) @@ -281,35 +334,23 @@ static int mark_good(const char *refname, const struct object_id *oid, return 1; } -static const char *need_bad_and_good_revision_warning = +static const char need_bad_and_good_revision_warning[] = N_("You 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 const char *need_bisect_start_warning = +static const char need_bisect_start_warning[] = N_("You need to start by \"git bisect start\".\n" "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)) { @@ -320,7 +361,7 @@ static int bisect_next_check(const struct bisect_terms *terms, */ 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 @@ -328,21 +369,35 @@ static int bisect_next_check(const struct bisect_terms *terms, */ 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) @@ -396,7 +451,7 @@ static int bisect_terms(struct bisect_terms *terms, const char *option) 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; @@ -404,25 +459,162 @@ static int bisect_append_log_quoted(const char **argv) 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_start(struct bisect_terms *terms, int no_checkout, - const char **argv, int argc) +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 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 int 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, res = 0; struct string_list revs = STRING_LIST_INIT_DUP; struct string_list states = STRING_LIST_INIT_DUP; struct strbuf start_head = STRBUF_INIT; @@ -450,11 +642,16 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout, 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; @@ -462,25 +659,26 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout, 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; @@ -518,12 +716,12 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout, 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 " "<valid-branch>'."), start_head.buf); @@ -569,14 +767,17 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout, */ 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 = -1; goto finish; } } @@ -588,26 +789,58 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout, for (i = 0; i < states.nr; i++) if (bisect_write(states.items[i].string, revs.items[i].string, terms, 1)) { - retval = -1; + res = -1; goto finish; } if (must_write_terms && write_terms(terms->term_bad, terms->term_good)) { - retval = -1; + res = -1; goto finish; } - retval = bisect_append_log_quoted(argv); - if (retval) - retval = -1; + res = bisect_append_log_quoted(argv); + if (res) + res = -1; finish: string_list_clear(&revs, 0); string_list_clear(&states, 0); strbuf_release(&start_head); strbuf_release(&bisect_names); - return retval; + 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; } int cmd_bisect__helper(int argc, const char **argv, const char *prefix) @@ -622,9 +855,12 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) CHECK_AND_SET_TERMS, BISECT_NEXT_CHECK, BISECT_TERMS, - BISECT_START + BISECT_START, + BISECT_AUTOSTART, + BISECT_NEXT, + BISECT_AUTO_NEXT } 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), @@ -646,8 +882,12 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) 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-auto-next", &cmdmode, + N_("verify the next bisection state then checkout the next bisection commit"), BISECT_AUTO_NEXT), + OPT_CMDMODE(0, "bisect-autostart", &cmdmode, + N_("start the bisection if it has not yet been started"), BISECT_AUTOSTART), OPT_BOOL(0, "no-log", &nolog, N_("no log for BISECT_WRITE")), OPT_END() @@ -663,7 +903,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) switch (cmdmode) { case NEXT_ALL: - return bisect_next_all(the_repository, prefix, no_checkout); + res = bisect_next_all(the_repository, prefix); + break; case WRITE_TERMS: if (argc != 2) return error(_("--write-terms requires two arguments")); @@ -704,11 +945,37 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) 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_AUTO_NEXT: + if (argc) + return error(_("--bisect-auto-next requires 0 arguments")); + get_terms(&terms); + res = bisect_auto_next(&terms, prefix); + break; + case BISECT_AUTOSTART: + if (argc) + return error(_("--bisect-autostart does not accept arguments")); + set_terms(&terms, "bad", "good"); + res = bisect_autostart(&terms); 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 --git a/third_party/git/builtin/blame.c b/third_party/git/builtin/blame.c index 7ebd51ae4d2e..7112be2afe16 100644 --- a/third_party/git/builtin/blame.c +++ b/third_party/git/builtin/blame.c @@ -26,8 +26,8 @@ #include "progress.h" #include "object-store.h" #include "blame.h" -#include "string-list.h" #include "refs.h" +#include "tag.h" static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>"); @@ -320,18 +320,18 @@ static const char *format_time(timestamp_t time, const char *tz_str, return time_buf.buf; } -#define OUTPUT_ANNOTATE_COMPAT 001 -#define OUTPUT_LONG_OBJECT_NAME 002 -#define OUTPUT_RAW_TIMESTAMP 004 -#define OUTPUT_PORCELAIN 010 -#define OUTPUT_SHOW_NAME 020 -#define OUTPUT_SHOW_NUMBER 040 -#define OUTPUT_SHOW_SCORE 0100 -#define OUTPUT_NO_AUTHOR 0200 -#define OUTPUT_SHOW_EMAIL 0400 -#define OUTPUT_LINE_PORCELAIN 01000 -#define OUTPUT_COLOR_LINE 02000 -#define OUTPUT_SHOW_AGE_WITH_COLOR 04000 +#define OUTPUT_ANNOTATE_COMPAT (1U<<0) +#define OUTPUT_LONG_OBJECT_NAME (1U<<1) +#define OUTPUT_RAW_TIMESTAMP (1U<<2) +#define OUTPUT_PORCELAIN (1U<<3) +#define OUTPUT_SHOW_NAME (1U<<4) +#define OUTPUT_SHOW_NUMBER (1U<<5) +#define OUTPUT_SHOW_SCORE (1U<<6) +#define OUTPUT_NO_AUTHOR (1U<<7) +#define OUTPUT_SHOW_EMAIL (1U<<8) +#define OUTPUT_LINE_PORCELAIN (1U<<9) +#define OUTPUT_COLOR_LINE (1U<<10) +#define OUTPUT_SHOW_AGE_WITH_COLOR (1U<<11) static void emit_porcelain_details(struct blame_origin *suspect, int repeat) { @@ -460,7 +460,7 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int for (cnt = 0; cnt < ent->num_lines; cnt++) { char ch; - int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? GIT_SHA1_HEXSZ : abbrev; + int length = (opt & OUTPUT_LONG_OBJECT_NAME) ? the_hash_algo->hexsz : abbrev; if (opt & OUTPUT_COLOR_LINE) { if (cnt > 0) { @@ -804,6 +804,26 @@ static int is_a_rev(const char *name) 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); + 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) @@ -816,10 +836,12 @@ static void build_ignorelist(struct blame_scoreboard *sb, 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); } @@ -843,7 +865,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) 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('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")), @@ -862,19 +884,11 @@ int cmd_blame(int argc, const char **argv, const char *prefix) OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("Ignore revisions from <file>")), 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), - - /* - * The following two options are parsed by parse_revision_opt() - * and are only included here to get included in the "-h" - * output: - */ - { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, NULL, 0, parse_opt_unknown_cb }, - 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 <file> instead of calling git-rev-list")), OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'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_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_("n,m"), N_("Process only line range n,m, counting from 1")), OPT__ABBREV(&abbrev), OPT_END() @@ -885,6 +899,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) struct range_set ranges; unsigned int range_i; long anchor; + const int hexsz = the_hash_algo->hexsz; setup_default_color_by_age(); git_config(git_blame_config, &output_option); @@ -931,11 +946,11 @@ parse_done: } else if (show_progress < 0) show_progress = isatty(2); - if (0 < abbrev && abbrev < GIT_SHA1_HEXSZ) + if (0 < abbrev && abbrev < hexsz) /* one more abbrev length is needed for the boundary commit */ abbrev++; else if (!abbrev) - abbrev = GIT_SHA1_HEXSZ; + abbrev = hexsz; if (revs_file && read_ancestry(revs_file)) die_errno("reading graft file '%s' failed", revs_file); @@ -1072,6 +1087,14 @@ parse_done: string_list_clear(&ignore_revs_file_list, 0); string_list_clear(&ignore_rev_list, 0); setup_scoreboard(&sb, path, &o); + + /* + * Changed-path Bloom filters are disabled when looking + * for copies. + */ + if (!(opt & PICKAXE_BLAME_COPY)) + setup_blame_bloom_data(&sb, path); + lno = sb.num_lines; if (lno && !range_list.nr) @@ -1175,5 +1198,7 @@ parse_done: printf("num get patch: %d\n", sb.num_get_patch); printf("num commits: %d\n", sb.num_commits); } + + cleanup_scoreboard(&sb); return 0; } diff --git a/third_party/git/builtin/branch.c b/third_party/git/builtin/branch.c index 2ef214632f02..efb30b882069 100644 --- a/third_party/git/builtin/branch.c +++ b/third_party/git/builtin/branch.c @@ -26,7 +26,7 @@ #include "commit-reach.h" static const char * const builtin_branch_usage[] = { - N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"), + N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"), N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"), N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."), N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"), @@ -468,7 +468,7 @@ static void print_current_branch_name(void) 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++) { @@ -624,7 +624,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"), BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN), OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")), - OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("Unset the upstream info")), + OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("unset the upstream info")), OPT__COLOR(&branch_use_color, N_("use colored output")), OPT_SET_INT('r', "remotes", &filter.kind, N_("act on remote-tracking branches"), FILTER_REFS_REMOTES), @@ -653,10 +653,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) 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 +688,12 @@ int cmd_branch(int argc, const char **argv, const char *prefix) !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) @@ -739,7 +737,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) */ if (!sorting) sorting = ref_default_sorting(); - sorting->ignore_case = icase; + ref_sorting_icase_all(sorting, icase); print_ref_list(&filter, sorting, &format); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); diff --git a/third_party/git/builtin/bugreport.c b/third_party/git/builtin/bugreport.c new file mode 100644 index 000000000000..3ad4b9b62e84 --- /dev/null +++ b/third_party/git/builtin/bugreport.c @@ -0,0 +1,194 @@ +#include "builtin.h" +#include "parse-options.h" +#include "strbuf.h" +#include "help.h" +#include "compat/compiler.h" +#include "run-command.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 : "<unset>"); +} + +static void get_populated_hooks(struct strbuf *hook_info, int nongit) +{ + /* + * NEEDSWORK: Doesn't look like there is a list of all possible hooks; + * so below is a transcription of `git help hooks`. Later, this should + * be replaced with some programmatically generated list (generated from + * doc or else taken from some library which tells us about all the + * hooks) + */ + static const char *hook[] = { + "applypatch-msg", + "pre-applypatch", + "post-applypatch", + "pre-commit", + "pre-merge-commit", + "prepare-commit-msg", + "commit-msg", + "post-commit", + "pre-rebase", + "post-checkout", + "post-merge", + "pre-push", + "pre-receive", + "update", + "post-receive", + "post-update", + "push-to-checkout", + "pre-auto-gc", + "post-rewrite", + "sendemail-validate", + "fsmonitor-watchman", + "p4-pre-submit", + "post-index-change", + }; + int i; + + if (nongit) { + strbuf_addstr(hook_info, + _("not run from a git repository - no hooks to show\n")); + return; + } + + for (i = 0; i < ARRAY_SIZE(hook); i++) + if (find_hook(hook[i])) + strbuf_addf(hook_info, "%s\n", hook[i]); +} + +static const char * const bugreport_usage[] = { + N_("git bugreport [-o|--output-directory <file>] [-s|--suffix <format>]"), + 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); + char *option_output = NULL; + char *option_suffix = "%Y-%m-%d-%H%M"; + const char *user_relative_path = NULL; + + 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 */ + strbuf_addstr(&report_path, + prefix_filename(prefix, + option_output ? option_output : "")); + strbuf_complete(&report_path, '/'); + + strbuf_addstr(&report_path, "git-bugreport-"); + strbuf_addftime(&report_path, option_suffix, localtime(&now), 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 = open(report_path.buf, O_CREAT | O_EXCL | O_WRONLY, 0666); + + if (report < 0) + die(_("couldn't create a new file at '%s'"), report_path.buf); + + 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); + + UNLEAK(buffer); + UNLEAK(report_path); + return !!launch_editor(report_path.buf, NULL, NULL); +} diff --git a/third_party/git/builtin/bundle.c b/third_party/git/builtin/bundle.c index 1ea4bfdfc198..ea6948110b0f 100644 --- a/third_party/git/builtin/bundle.c +++ b/third_party/git/builtin/bundle.c @@ -1,4 +1,6 @@ #include "builtin.h" +#include "strvec.h" +#include "parse-options.h" #include "cache.h" #include "bundle.h" @@ -9,59 +11,187 @@ * bundle supporting "fetch", "pull", and "ls-remote". */ -static const char builtin_bundle_usage[] = - "git bundle create <file> <git-rev-list args>\n" - " or: git bundle verify <file>\n" - " or: git bundle list-heads <file> [<refname>...]\n" - " or: git bundle unbundle <file> [<refname>...]"; +static const char * const builtin_bundle_usage[] = { + N_("git bundle create [<options>] <file> <git-rev-list args>"), + N_("git bundle verify [<options>] <file>"), + N_("git bundle list-heads <file> [<refname>...]"), + N_("git bundle unbundle <file> [<refname>...]"), + NULL +}; -int cmd_bundle(int argc, const char **argv, const char *prefix) -{ +static const char * const builtin_bundle_create_usage[] = { + N_("git bundle create [<options>] <file> <git-rev-list args>"), + NULL +}; + +static const char * const builtin_bundle_verify_usage[] = { + N_("git bundle verify [<options>] <file>"), + NULL +}; + +static const char * const builtin_bundle_list_heads_usage[] = { + N_("git bundle list-heads <file> [<refname>...]"), + NULL +}; + +static const char * const builtin_bundle_unbundle_usage[] = { + N_("git bundle unbundle <file> [<refname>...]"), + 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) { + int newargc; + newargc = parse_options(argc, argv, NULL, options, usagestr, + PARSE_OPT_STOP_AT_NON_OPTION); + if (argc < 1) + usage_with_options(usagestr, options); + *bundle_file = prefix_filename(prefix, argv[0]); + return newargc; +} + +static int cmd_bundle_create(int argc, const char **argv, const char *prefix) { + int all_progress_implied = 0; + int progress = isatty(STDERR_FILENO); + struct strvec pack_opts; + int version = -1; + + struct option options[] = { + OPT_SET_INT('q', "quiet", &progress, + N_("do not show progress meter"), 0), + OPT_SET_INT(0, "progress", &progress, + N_("show progress meter"), 1), + OPT_SET_INT(0, "all-progress", &progress, + N_("show progress meter during object writing phase"), 2), + 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; + + argc = parse_options_cmd_bundle(argc, argv, prefix, + builtin_bundle_create_usage, options, &bundle_file); + /* bundle internals use argv[1] as further parameters */ + + strvec_init(&pack_opts); + if (progress == 0) + strvec_push(&pack_opts, "--quiet"); + else if (progress == 1) + strvec_push(&pack_opts, "--progress"); + else if (progress == 2) + strvec_push(&pack_opts, "--all-progress"); + if (progress && 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, version); +} + +static int cmd_bundle_verify(int argc, const char **argv, const char *prefix) { struct bundle_header header; - const char *cmd, *bundle_file; int bundle_fd = -1; + int quiet = 0; - if (argc < 3) - usage(builtin_bundle_usage); + struct option options[] = { + OPT_BOOL('q', "quiet", &quiet, + N_("do not show bundle details")), + OPT_END() + }; + const char* bundle_file; - cmd = argv[1]; - bundle_file = prefix_filename(prefix, argv[2]); - argc -= 2; - argv += 2; + 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 (strcmp(cmd, "create") && (bundle_fd = - read_bundle_header(bundle_file, &header)) < 0) + if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) + return 1; + close(bundle_fd); + if (verify_bundle(the_repository, &header, !quiet)) return 1; + fprintf(stderr, _("%s is okay\n"), bundle_file); + return 0; +} - if (!strcmp(cmd, "verify")) { - close(bundle_fd); - if (argc != 1) { - usage(builtin_bundle_usage); - return 1; - } - if (verify_bundle(the_repository, &header, 1)) - return 1; - fprintf(stderr, _("%s is okay\n"), bundle_file); - return 0; - } - if (!strcmp(cmd, "list-heads")) { - close(bundle_fd); - return !!list_bundle_refs(&header, argc, argv); +static int cmd_bundle_list_heads(int argc, const char **argv, const char *prefix) { + struct bundle_header header; + int bundle_fd = -1; + + struct option options[] = { + OPT_END() + }; + const 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; + close(bundle_fd); + return !!list_bundle_refs(&header, argc, argv); +} + +static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix) { + struct bundle_header header; + int bundle_fd = -1; + + struct option options[] = { + OPT_END() + }; + const char* bundle_file; + + 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 (!startup_info->have_repository) + die(_("Need a repository to unbundle.")); + return !!unbundle(the_repository, &header, bundle_fd, 0) || + list_bundle_refs(&header, argc, argv); +} + +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; + + argc = parse_options(argc, argv, prefix, options, builtin_bundle_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + packet_trace_identity("bundle"); + + if (argc < 2) + usage_with_options(builtin_bundle_usage, options); + + else if (!strcmp(argv[0], "create")) + result = cmd_bundle_create(argc, argv, prefix); + else if (!strcmp(argv[0], "verify")) + result = cmd_bundle_verify(argc, argv, prefix); + else if (!strcmp(argv[0], "list-heads")) + result = cmd_bundle_list_heads(argc, argv, prefix); + else if (!strcmp(argv[0], "unbundle")) + result = cmd_bundle_unbundle(argc, argv, prefix); + else { + error(_("Unknown subcommand: %s"), argv[0]); + usage_with_options(builtin_bundle_usage, options); } - if (!strcmp(cmd, "create")) { - if (argc < 2) { - usage(builtin_bundle_usage); - return 1; - } - if (!startup_info->have_repository) - die(_("Need a repository to create a bundle.")); - return !!create_bundle(the_repository, bundle_file, argc, argv); - } else if (!strcmp(cmd, "unbundle")) { - if (!startup_info->have_repository) - die(_("Need a repository to unbundle.")); - return !!unbundle(the_repository, &header, bundle_fd, 0) || - list_bundle_refs(&header, argc, argv); - } else - usage(builtin_bundle_usage); + return result ? 1 : 0; } diff --git a/third_party/git/builtin/cat-file.c b/third_party/git/builtin/cat-file.c index 995d47c85aad..5ebf13359e83 100644 --- a/third_party/git/builtin/cat-file.c +++ b/third_party/git/builtin/cat-file.c @@ -12,9 +12,10 @@ #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" struct batch_options { int enabled; @@ -41,7 +42,10 @@ static int filter_object(const char *path, unsigned mode, 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); @@ -261,7 +265,7 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len, 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)); @@ -524,8 +528,8 @@ static int batch_objects(struct batch_options *opt) if (opt->all_objects) { struct object_cb_data cb; - if (repository_format_partial_clone) - warning("This repository has extensions.partialClone set. Some objects may not be loaded."); + if (has_promisor_remote()) + warning("This repository uses promisor remotes. Some objects may not be loaded."); cb.opt = opt; cb.expand = &data; @@ -592,7 +596,7 @@ static int batch_objects(struct batch_options *opt) static const char * const cat_file_usage[] = { N_("git cat-file (-t [--allow-unknown-type] | -s [--allow-unknown-type] | -e | -p | <type> | --textconv | --filters) [--path=<path>] <object>"), - N_("git cat-file (--batch | --batch-check) [--follow-symlinks] [--textconv | --filters]"), + N_("git cat-file (--batch[=<format>] | --batch-check[=<format>]) [--follow-symlinks] [--textconv | --filters]"), NULL }; @@ -646,14 +650,14 @@ int cmd_cat_file(int argc, const char **argv, const char *prefix) 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 --git a/third_party/git/builtin/check-ignore.c b/third_party/git/builtin/check-ignore.c index 599097304b77..3c652748d58c 100644 --- a/third_party/git/builtin/check-ignore.c +++ b/third_party/git/builtin/check-ignore.c @@ -32,19 +32,19 @@ static const struct option check_ignore_options[] = { OPT_END() }; -static void output_exclude(const char *path, struct exclude *exclude) +static void output_pattern(const char *path, struct path_pattern *pattern) { - char *bang = (exclude && exclude->flags & EXC_FLAG_NEGATIVE) ? "!" : ""; - char *slash = (exclude && exclude->flags & EXC_FLAG_MUSTBEDIR) ? "/" : ""; + char *bang = (pattern && pattern->flags & PATTERN_FLAG_NEGATIVE) ? "!" : ""; + char *slash = (pattern && pattern->flags & PATTERN_FLAG_MUSTBEDIR) ? "/" : ""; if (!nul_term_line) { if (!verbose) { write_name_quoted(path, stdout, '\n'); } else { - if (exclude) { - quote_c_style(exclude->el->src, NULL, stdout, 0); + if (pattern) { + quote_c_style(pattern->pl->src, NULL, stdout, 0); printf(":%d:%s%s%s\t", - exclude->srcpos, - bang, exclude->pattern, slash); + pattern->srcpos, + bang, pattern->pattern, slash); } else { printf("::\t"); @@ -56,11 +56,11 @@ static void output_exclude(const char *path, struct exclude *exclude) if (!verbose) { printf("%s%c", path, '\0'); } else { - if (exclude) + if (pattern) printf("%s%c%d%c%s%s%s%c%s%c", - exclude->el->src, '\0', - exclude->srcpos, '\0', - bang, exclude->pattern, slash, '\0', + pattern->pl->src, '\0', + pattern->srcpos, '\0', + bang, pattern->pattern, slash, '\0', path, '\0'); else printf("%c%c%c%s%c", '\0', '\0', '\0', path, '\0'); @@ -74,7 +74,7 @@ static int check_ignore(struct dir_struct *dir, const char *full_path; char *seen; int num_ignored = 0, i; - struct exclude *exclude; + struct path_pattern *pattern; struct pathspec pathspec; if (!argc) { @@ -103,15 +103,18 @@ static int check_ignore(struct dir_struct *dir, seen = find_pathspecs_matching_against_index(&pathspec, &the_index); for (i = 0; i < pathspec.nr; i++) { full_path = pathspec.items[i].match; - exclude = NULL; + pattern = NULL; if (!seen[i]) { int dtype = DT_UNKNOWN; - exclude = last_exclude_matching(dir, &the_index, + pattern = last_matching_pattern(dir, &the_index, full_path, &dtype); + if (!verbose && pattern && + pattern->flags & PATTERN_FLAG_NEGATIVE) + pattern = NULL; } - if (!quiet && (exclude || show_non_matching)) - output_exclude(pathspec.items[i].original, exclude); - if (exclude) + if (!quiet && (pattern || show_non_matching)) + output_pattern(pathspec.items[i].original, pattern); + if (pattern) num_ignored++; } free(seen); @@ -177,7 +180,7 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix) if (!no_index && read_cache() < 0) die(_("index file corrupt")); - memset(&dir, 0, sizeof(dir)); + dir_init(&dir); setup_standard_excludes(&dir); if (stdin_paths) { @@ -187,7 +190,7 @@ int cmd_check_ignore(int argc, const char **argv, const char *prefix) maybe_flush_or_die(stdout, "ignore to stdout"); } - clear_directory(&dir); + dir_clear(&dir); return !num_ignored; } diff --git a/third_party/git/builtin/checkout-index.c b/third_party/git/builtin/checkout-index.c index 1ac1cc290ed7..a854fd16e779 100644 --- a/third_party/git/builtin/checkout-index.c +++ b/third_party/git/builtin/checkout-index.c @@ -177,9 +177,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) N_("write the content to temporary files")), OPT_STRING(0, "prefix", &state.base_dir, N_("string"), N_("when creating files, prepend <string>")), - { 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() }; diff --git a/third_party/git/builtin/checkout.c b/third_party/git/builtin/checkout.c index 6123f732a2c8..0951f8fee5cc 100644 --- a/third_party/git/builtin/checkout.c +++ b/third_party/git/builtin/checkout.c @@ -70,6 +70,8 @@ struct checkout_opts { int checkout_worktree; const char *ignore_unmerged_opt; int ignore_unmerged; + int pathspec_file_nul; + const char *pathspec_from_file; const char *new_branch; const char *new_branch_force; @@ -86,6 +88,19 @@ struct checkout_opts { 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) { @@ -126,6 +141,7 @@ static int update_some(const struct object_id *oid, struct strbuf *base, if (pos >= 0) { struct cache_entry *old = active_cache[pos]; if (ce->ce_mode == old->ce_mode && + !ce_intent_to_add(old) && oideq(&ce->oid, &old->oid)) { old->ce_flags |= CE_UPDATE; discard_cache_entry(ce); @@ -223,6 +239,8 @@ static int checkout_merged(int pos, const struct checkout *state, int *nr_checko 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) { @@ -243,13 +261,12 @@ static int checkout_merged(int pos, const struct checkout *state, int *nr_checko 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); @@ -334,7 +351,8 @@ static void mark_ce_for_checkout_no_overlay(struct cache_entry *ce, } } -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; @@ -345,6 +363,10 @@ static int checkout_worktree(const struct checkout_opts *opts) state.refresh_cache = 1; state.istate = &the_index; + init_checkout_metadata(&state.meta, info->refname, + info->commit ? &info->commit->object.oid : &info->oid, + NULL); + enable_delayed_checkout(&state); for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; @@ -393,7 +415,7 @@ static int checkout_worktree(const struct checkout_opts *opts) } 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; @@ -459,7 +481,7 @@ static int checkout_paths(const struct checkout_opts *opts, else BUG("either flag must have been set, worktree=%d, index=%d", opts->checkout_worktree, opts->checkout_index); - return run_add_interactive(revision, patch_mode, &opts->pathspec); + return run_add_interactive(new_branch_info->name, patch_mode, &opts->pathspec); } repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); @@ -520,7 +542,9 @@ static int checkout_paths(const struct checkout_opts *opts, /* 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); /* * Allow updating the index when checking out from the index. @@ -581,7 +605,8 @@ static void describe_detached_head(const char *msg, struct commit *commit) } 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; @@ -596,6 +621,9 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o, 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)) { @@ -615,21 +643,17 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o, } } -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); @@ -658,7 +682,7 @@ static int merge_working_tree(const struct checkout_opts *opts, } 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 { @@ -687,6 +711,10 @@ static int merge_working_tree(const struct checkout_opts *opts, topts.quiet = opts->merge && old_branch_info->commit; topts.verbose_update = opts->show_progress; topts.fn = twoway_merge; + init_checkout_metadata(&topts.meta, new_branch_info->refname, + new_branch_info->commit ? + &new_branch_info->commit->object.oid : + &new_branch_info->oid, NULL); if (opts->overwrite_ignore) { topts.dir = xcalloc(1, sizeof(*topts.dir)); topts.dir->flags |= DIR_SHOW_IGNORED; @@ -708,11 +736,11 @@ static int merge_working_tree(const struct checkout_opts *opts, * give up or do a real merge, depending on * whether the merge flag was used. */ - struct tree *result; struct tree *work; struct tree *old_tree; struct merge_options o; struct strbuf sb = STRBUF_INIT; + struct strbuf old_commit_shortname = STRBUF_INIT; if (!opts->merge) return 1; @@ -730,13 +758,6 @@ static int merge_working_tree(const struct checkout_opts *opts, "the following files:\n%s"), sb.buf); strbuf_release(&sb); - if (repo_index_has_changes(the_repository, - get_commit_tree(old_branch_info->commit), - &sb)) - warning(_("staged changes in the following files may be lost: %s"), - sb.buf); - strbuf_release(&sb); - /* Do more real merge */ /* @@ -751,36 +772,35 @@ static int merge_working_tree(const struct checkout_opts *opts, */ 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_tree_from_memory(&o); + 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; + if (old_branch_info->name == NULL) { + strbuf_add_unique_abbrev(&old_commit_shortname, + &old_branch_info->commit->object.oid, + DEFAULT_ABBREV); + o.ancestor = old_commit_shortname.buf; + } o.branch1 = new_branch_info->name; o.branch2 = "local"; ret = merge_trees(&o, new_tree, work, - old_tree, - &result); + old_tree); if (ret < 0) 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) return ret; } @@ -861,7 +881,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, 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. */ @@ -1100,8 +1120,10 @@ static void setup_new_branch_info_and_source_tree( 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) { @@ -1113,12 +1135,43 @@ static void setup_new_branch_info_and_source_tree( } } +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_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/<name>\n" + "\n" + "If you'd like to always have checkouts of an ambiguous <name> 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; @@ -1223,13 +1276,9 @@ static int parse_branchname_arg(int argc, const char **argv, 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) */ @@ -1296,7 +1345,7 @@ static void die_expecting_a_branch(const struct branch_info *branch_info) 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)) @@ -1431,9 +1480,9 @@ static struct option *add_common_options(struct checkout_opts *opts, { 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"), @@ -1480,6 +1529,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts, OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")), OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree, N_("do not limit pathspecs to sparse entries only")), + OPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&opts->pathspec_file_nul), OPT_END() }; struct option *newopts = parse_options_concat(prevopts, options); @@ -1487,12 +1538,14 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts, 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)); @@ -1530,7 +1583,8 @@ static int checkout_main(int argc, const char **argv, const char *prefix, } 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")); @@ -1549,16 +1603,16 @@ static int checkout_main(int argc, const char **argv, const char *prefix, 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; @@ -1566,7 +1620,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix, 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, "--")) @@ -1575,7 +1629,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix, 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; } @@ -1600,8 +1654,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix, 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) { @@ -1618,10 +1671,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix, die(_("reference is not a tree: %s"), opts->from_treeish); } - if (opts->accept_pathspec && !opts->empty_pathspec_ok && !argc && - !opts->patch_mode) /* patch mode is special */ - die(_("you must specify path(s) to restore")); - if (argc) { parse_pathspec(&opts->pathspec, 0, opts->patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0, @@ -1634,17 +1683,42 @@ static int checkout_main(int argc, const char **argv, const char *prefix, * 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); if (opts->force_detach) die(_("git checkout: --detach does not take a path argument '%s'"), argv[0]); + } + + if (opts->pathspec_from_file) { + if (opts->pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + if (opts->force_detach) + die(_("--pathspec-from-file is incompatible with --detach")); + + if (opts->patch_mode) + die(_("--pathspec-from-file is incompatible with --patch")); + + parse_pathspec_file(&opts->pathspec, 0, + 0, + prefix, opts->pathspec_from_file, opts->pathspec_file_nul); + } else if (opts->pathspec_file_nul) { + 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" "checking out of the index.")); + } else { + if (opts->accept_pathspec && !opts->empty_pathspec_ok && + !opts->patch_mode) /* patch mode is special */ + die(_("you must specify path(s) to restore")); } if (opts->new_branch) { @@ -1659,28 +1733,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix, } 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/<name>\n" - "\n" - "If you'd like to always have checkouts of an ambiguous <name> 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) @@ -1714,6 +1770,15 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) opts.checkout_index = -2; /* default on */ opts.checkout_worktree = -2; /* default on */ + if (argc == 3 && !strcmp(argv[1], "-b")) { + /* + * User ran 'git checkout -b <branch>' and expects + * the same behavior as 'git switch -c <branch>'. + */ + opts.switch_branch_doing_nothing_is_ok = 0; + opts.only_merge_on_switching_branches = 1; + } + options = parse_options_dup(checkout_options); options = add_common_options(&opts, options); options = add_common_switch_branch_options(&opts, options); @@ -1757,6 +1822,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix) 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 --git a/third_party/git/builtin/clean.c b/third_party/git/builtin/clean.c index d5579da716e0..687ab473c20c 100644 --- a/third_party/git/builtin/clean.c +++ b/third_party/git/builtin/clean.c @@ -18,6 +18,7 @@ #include "color.h" #include "pathspec.h" #include "help.h" +#include "prompt.h" static int force = -1; /* unset */ static int interactive; @@ -158,9 +159,10 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, *dir_gone = 1; - if ((force_flag & REMOVE_DIR_KEEP_NESTED_GIT) && is_nonbare_repository_dir(path)) { + 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); } @@ -175,7 +177,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, 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; @@ -200,7 +202,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, 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; @@ -208,11 +210,11 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, } 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; @@ -236,7 +238,7 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, *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; @@ -264,7 +266,7 @@ static void pretty_print_dels(void) 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); } @@ -419,7 +421,6 @@ static int find_unique(const char *choice, struct menu_stuff *menu_stuff) return found; } - /* * Parse user input, and return choice(s) for menu (menu_stuff). * @@ -579,9 +580,7 @@ static int *list_and_choose(struct menu_opts *opts, struct menu_stuff *stuff) 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; } @@ -648,7 +647,7 @@ static int filter_by_patterns_cmd(void) struct strbuf confirm = STRBUF_INIT; struct strbuf **ignore_list; struct string_list_item *item; - struct exclude_list *el; + struct pattern_list *pl; int changed = -1, i; for (;;) { @@ -661,17 +660,15 @@ static int filter_by_patterns_cmd(void) 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)); - el = add_exclude_list(&dir, EXC_CMDL, "manual exclude"); + dir_init(&dir); + pl = add_pattern_list(&dir, EXC_CMDL, "manual exclude"); ignore_list = strbuf_split_max(&confirm, ' ', 0); for (i = 0; ignore_list[i]; i++) { @@ -679,7 +676,7 @@ static int filter_by_patterns_cmd(void) if (!ignore_list[i]->len) continue; - add_exclude(ignore_list[i]->buf, "", 0, el, -(i+1)); + add_pattern(ignore_list[i]->buf, "", 0, pl, -(i+1)); } changed = 0; @@ -701,7 +698,7 @@ static int filter_by_patterns_cmd(void) } strbuf_list_free(ignore_list); - clear_directory(&dir); + dir_clear(&dir); } strbuf_release(&confirm); @@ -756,12 +753,10 @@ static int ask_each_cmd(void) 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; } @@ -901,7 +896,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) struct pathspec pathspec; struct strbuf buf = STRBUF_INIT; struct string_list exclude_list = STRING_LIST_INIT_NODUP; - struct exclude_list *el; + struct pattern_list *pl; struct string_list_item *item; const char *qname; struct option options[] = { @@ -911,8 +906,8 @@ int cmd_clean(int argc, const char **argv, const char *prefix) 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 <pattern> to ignore rules"), PARSE_OPT_NONEG, exclude_cb }, + OPT_CALLBACK_F('e', "exclude", &exclude_list, N_("pattern"), + N_("add <pattern> 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")), @@ -928,13 +923,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) 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")); - + dir_init(&dir); if (!interactive && !dry_run && !force) { if (config_set) die(_("clean.requireForce set to true and neither -i, -n, nor -f given; " @@ -946,21 +935,64 @@ int cmd_clean(int argc, const char **argv, const char *prefix) if (force > 1) rm_flags = 0; + else + dir.flags |= DIR_SKIP_NESTED_GIT; dir.flags |= DIR_SHOW_OTHER_DIRECTORIES; - if (remove_directories) - dir.flags |= DIR_SHOW_IGNORED_TOO | DIR_KEEP_UNTRACKED_CONTENTS; + 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 + * recurse within those. + */ + remove_directories = 1; + } + + 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); - - el = add_exclude_list(&dir, EXC_CMDL, "--exclude option"); + pl = add_pattern_list(&dir, EXC_CMDL, "--exclude option"); for (i = 0; i < exclude_list.nr; i++) - add_exclude(exclude_list.items[i].string, "", 0, el, -(i+1)); + add_pattern(exclude_list.items[i].string, "", 0, pl, -(i+1)); parse_pathspec(&pathspec, 0, PATHSPEC_PREFER_CWD, @@ -978,12 +1010,6 @@ int cmd_clean(int argc, const char **argv, const char *prefix) 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); @@ -995,11 +1021,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) 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(); @@ -1007,6 +1029,7 @@ int cmd_clean(int argc, const char **argv, const char *prefix) for_each_string_list_item(item, &del_list) { struct stat st; + strbuf_reset(&abs_path); if (prefix) strbuf_addstr(&abs_path, prefix); @@ -1024,23 +1047,22 @@ int cmd_clean(int argc, const char **argv, const char *prefix) 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); } } - strbuf_reset(&abs_path); } strbuf_release(&abs_path); diff --git a/third_party/git/builtin/clone.c b/third_party/git/builtin/clone.c index f665b28ccccf..391aa41075ee 100644 --- a/third_party/git/builtin/clone.c +++ b/third_party/git/builtin/clone.c @@ -32,7 +32,6 @@ #include "connected.h" #include "packfile.h" #include "list-objects-filter-options.h" -#include "object-store.h" /* * Overall FIXMEs: @@ -60,6 +59,7 @@ static const char *real_git_dir; static char *option_upload_pack = "git-upload-pack"; static int option_verbosity; static int option_progress = -1; +static int option_sparse_checkout; static enum transport_family family; static struct string_list option_config = STRING_LIST_INIT_NODUP; static struct string_list option_required_reference = STRING_LIST_INIT_NODUP; @@ -102,10 +102,10 @@ static struct option builtin_clone_options[] = { 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"), @@ -147,6 +147,8 @@ static struct option builtin_clone_options[] = { OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), OPT_BOOL(0, "remote-submodules", &option_remote_submodules, N_("any cloned submodules will use their remote-tracking branch")), + OPT_BOOL(0, "sparse", &option_sparse_checkout, + N_("initialize sparse-checkout file to include only files at root")), OPT_END() }; @@ -418,6 +420,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, struct dir_iterator *iter; int iter_status; unsigned int flags; + struct strbuf realpath = STRBUF_INIT; mkdir_if_missing(dest->buf, 0777); @@ -452,7 +455,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, 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); @@ -466,6 +470,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, 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) @@ -637,7 +643,9 @@ static void write_followtags(const struct ref *refs, const char *msg) 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); @@ -670,8 +678,7 @@ static void update_remote_refs(const struct ref *refs, 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; @@ -680,7 +687,6 @@ static void update_remote_refs(const struct ref *refs, 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")); @@ -734,6 +740,27 @@ static void update_head(const struct ref *our, const struct ref *remote, } } +static int git_sparse_checkout_init(const char *repo) +{ + struct strvec argv = STRVEC_INIT; + int result = 0; + strvec_pushl(&argv, "-C", repo, "sparse-checkout", "init", NULL); + + /* + * We must apply the setting in the current process + * for the later checkout to use the sparse-checkout file. + */ + core_apply_sparse_checkout = 1; + + if (run_command_v_opt(argv.v, RUN_GIT_CMD)) { + error(_("failed to initialize sparse-checkout")); + result = 1; + } + + strvec_clear(&argv); + return result; +} + static int checkout(int submodule_progress) { struct object_id oid; @@ -756,11 +783,11 @@ static int checkout(int submodule_progress) if (!strcmp(head, "HEAD")) { if (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(); @@ -775,6 +802,7 @@ static int checkout(int submodule_progress) 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); @@ -782,35 +810,42 @@ static int checkout(int submodule_progress) 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", sha1_to_hex(null_sha1), + 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", "--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; @@ -900,7 +935,7 @@ static void dissociate_from_references(void) free(alternates); } -static int dir_exists(const char *path) +static int path_exists(const char *path) { struct stat sb; return !stat(path, &sb); @@ -910,15 +945,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix) { int is_bundle = 0, is_local; const char *repo_name, *repo, *work_tree, *git_dir; - char *path, *dir; - int dest_exists; + char *path, *dir, *display_repo = NULL; + int dest_exists, real_dest_exists = 0; const struct ref *refs, *remote_head; const struct ref *remote_head_points_at; 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/"; @@ -926,9 +960,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) int err = 0, complete_refs_before_fetch = 1; int submodule_progress; - struct argv_array ref_prefixes = ARGV_ARRAY_INIT; - - fetch_if_missing = 0; + struct strvec ref_prefixes = STRVEC_INIT; packet_trace_identity("clone"); argc = parse_options(argc, argv, prefix, builtin_clone_options, @@ -967,10 +999,11 @@ int cmd_clone(int argc, const char **argv, const char *prefix) path = get_repo_path(repo_name, &is_bundle); if (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) @@ -982,18 +1015,28 @@ int cmd_clone(int argc, const char **argv, const char *prefix) dir = guess_dir_name(repo_name, is_bundle, option_bare); strip_trailing_slashes(dir); - dest_exists = dir_exists(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; else { work_tree = getenv("GIT_WORK_TREE"); - if (work_tree && dir_exists(work_tree)) + if (work_tree && path_exists(work_tree)) die(_("working tree '%s' already exists."), work_tree); } @@ -1021,7 +1064,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } if (real_git_dir) { - if (dir_exists(real_git_dir)) + if (real_dest_exists) junk_git_dir_flags |= REMOVE_DIR_KEEP_TOPLEVEL; junk_git_dir = real_git_dir; } else { @@ -1075,7 +1118,8 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } } - 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) git_dir = real_git_dir; @@ -1107,11 +1151,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_required_reference.nr || option_optional_reference.nr) setup_reference(); + if (option_sparse_checkout && git_sparse_checkout_init(dir)) + return 1; + remote = remote_get(option_origin); - 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); @@ -1160,29 +1206,36 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport->server_options = &server_options; if (filter_options.choice) { - struct strbuf expanded_filter_spec = STRBUF_INIT; - expand_list_objects_filter_spec(&filter_options, - &expanded_filter_spec); + const char *spec = + expand_list_objects_filter_spec(&filter_options); transport_set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, - expanded_filter_spec.buf); + spec); transport_set_option(transport, TRANS_OPT_FROM_PROMISOR, "1"); - strbuf_release(&expanded_filter_spec); } if (transport->smart_options && !deepen && !filter_options.choice) transport->smart_options->check_self_contained_and_connected = 1; - argv_array_push(&ref_prefixes, "HEAD"); + strvec_push(&ref_prefixes, "HEAD"); refspec_ref_prefixes(&remote->fetch, &ref_prefixes); if (option_branch) expand_ref_prefix(&ref_prefixes, option_branch); if (!option_no_tags) - argv_array_push(&ref_prefixes, "refs/tags/"); + strvec_push(&ref_prefixes, "refs/tags/"); refs = transport_get_remote_refs(transport, &ref_prefixes); 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 @@ -1229,9 +1282,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote_head_points_at = NULL; remote_head = NULL; option_no_checkout = 1; - if (!option_bare) - install_branch_config(0, "master", option_origin, - "refs/heads/master"); + if (!option_bare) { + const char *branch = git_default_branch_name(); + char *ref = xstrfmt("refs/heads/%s", branch); + + install_branch_config(0, branch, option_origin, ref); + free(ref); + } } write_refspec_config(src_ref_prefix, our_head_points_at, @@ -1247,7 +1304,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) 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); @@ -1268,15 +1325,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } junk_mode = JUNK_LEAVE_REPO; - fetch_if_missing = 1; err = checkout(submodule_progress); strbuf_release(&reflog_msg); strbuf_release(&branch_top); strbuf_release(&key); - strbuf_release(&default_refspec); junk_mode = JUNK_LEAVE_ALL; - argv_array_clear(&ref_prefixes); + strvec_clear(&ref_prefixes); return err; } diff --git a/third_party/git/builtin/commit-graph.c b/third_party/git/builtin/commit-graph.c index 38027b83d9d8..78fa08f43af5 100644 --- a/third_party/git/builtin/commit-graph.c +++ b/third_party/git/builtin/commit-graph.c @@ -6,27 +6,28 @@ #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 [--object-dir <objdir>]"), - N_("git commit-graph read [--object-dir <objdir>]"), - N_("git commit-graph verify [--object-dir <objdir>] [--shallow]"), - N_("git commit-graph write [--object-dir <objdir>] [--append|--split] [--reachable|--stdin-packs|--stdin-commits] <split options>"), + N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"), + N_("git commit-graph write [--object-dir <objdir>] [--append] " + "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] " + "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] " + "<split options>"), NULL }; static const char * const builtin_commit_graph_verify_usage[] = { - N_("git commit-graph verify [--object-dir <objdir>] [--shallow]"), - NULL -}; - -static const char * const builtin_commit_graph_read_usage[] = { - N_("git commit-graph read [--object-dir <objdir>]"), + N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"), NULL }; static const char * const builtin_commit_graph_write_usage[] = { - N_("git commit-graph write [--object-dir <objdir>] [--append|--split] [--reachable|--stdin-packs|--stdin-commits] <split options>"), + N_("git commit-graph write [--object-dir <objdir>] [--append] " + "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] " + "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] " + "<split options>"), NULL }; @@ -38,11 +39,36 @@ static struct opts_commit_graph { int append; int split; int shallow; + int progress; + int enable_changed_paths; } opts; +static struct object_directory *find_odb(struct repository *r, + const char *obj_dir) +{ + struct object_directory *odb; + char *obj_dir_real = real_pathdup(obj_dir, 1); + struct strbuf odb_path_real = STRBUF_INIT; + + prepare_alt_odb(r); + for (odb = r->objects->odb; odb; odb = odb->next) { + strbuf_realpath(&odb_path_real, odb->path, 1); + if (!strcmp(obj_dir_real, odb_path_real.buf)) + break; + } + + free(obj_dir_real); + strbuf_release(&odb_path_real); + + if (!odb) + die(_("could not find object directory matching %s"), obj_dir); + return odb; +} + 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; @@ -55,9 +81,13 @@ static int graph_verify(int argc, const char **argv) 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_END(), }; + trace2_cmd_mode("verify"); + + opts.progress = isatty(2); argc = parse_options(argc, argv, NULL, builtin_commit_graph_verify_options, builtin_commit_graph_verify_usage, 0); @@ -66,8 +96,11 @@ static int graph_verify(int argc, const char **argv) opts.obj_dir = get_object_directory(); if (opts.shallow) flags |= COMMIT_GRAPH_VERIFY_SHALLOW; + 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); @@ -75,9 +108,9 @@ static int graph_verify(int argc, const char **argv) FREE_AND_NULL(graph_name); if (open_ok) - graph = load_commit_graph_one_fd_st(fd, &st); - 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) @@ -87,74 +120,90 @@ static int graph_verify(int argc, const char **argv) return verify_commit_graph(the_repository, graph, flags); } -static int graph_read(int argc, const char **argv) +extern int read_replace_refs; +static struct commit_graph_opts write_opts; + +static int write_option_parse_split(const struct option *opt, const char *arg, + int unset) { - struct commit_graph *graph = NULL; - char *graph_name; - int open_ok; - int fd; - struct stat st; + enum commit_graph_split_flags *flags = opt->value; - static struct option builtin_commit_graph_read_options[] = { - OPT_STRING(0, "object-dir", &opts.obj_dir, - N_("dir"), - N_("The object directory to store the graph")), - OPT_END(), - }; + BUG_ON_OPT_NEG(unset); - argc = parse_options(argc, argv, NULL, - builtin_commit_graph_read_options, - builtin_commit_graph_read_usage, 0); + opts.split = 1; + if (!arg) + return 0; - if (!opts.obj_dir) - opts.obj_dir = get_object_directory(); + 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); - graph_name = get_commit_graph_filename(opts.obj_dir); + return 0; +} - open_ok = open_commit_graph(graph_name, &fd, &st); - if (!open_ok) - die_errno(_("Could not open commit-graph '%s'"), graph_name); +static int read_one_commit(struct oidset *commits, struct progress *progress, + const char *hash) +{ + struct object *result; + struct object_id oid; + const char *end; - graph = load_commit_graph_one_fd_st(fd, &st); - if (!graph) - return 1; + if (parse_oid_hex(hash, &oid, &end)) + return error(_("unexpected non-hex object ID: %s"), hash); - FREE_AND_NULL(graph_name); + 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); - printf("header: %08x %d %d %d %d\n", - ntohl(*(uint32_t*)graph->data), - *(unsigned char*)(graph->data + 4), - *(unsigned char*)(graph->data + 5), - *(unsigned char*)(graph->data + 6), - *(unsigned char*)(graph->data + 7)); - printf("num_commits: %u\n", graph->num_commits); - printf("chunks:"); - - if (graph->chunk_oid_fanout) - printf(" oid_fanout"); - if (graph->chunk_oid_lookup) - printf(" oid_lookup"); - if (graph->chunk_commit_data) - printf(" commit_metadata"); - if (graph->chunk_extra_edges) - printf(" extra_edges"); - printf("\n"); + display_progress(progress, oidset_size(commits)); - UNLEAK(graph); + return 0; +} +static int write_option_max_new_filters(const struct option *opt, + const char *arg, + int unset) +{ + int *to = opt->value; + if (unset) + *to = -1; + else { + const char *s; + *to = strtol(arg, (char **)&s, 10); + if (*s) + return error(_("%s expects a numerical value"), + optname(opt, opt->flags)); + } return 0; } -extern int read_replace_refs; -static struct split_commit_graph_opts split_opts; +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; - unsigned int flags = COMMIT_GRAPH_PROGRESS; + 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, @@ -168,20 +217,35 @@ static int graph_write(int argc, const char **argv) 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, "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_BOOL(0, "progress", &opts.progress, N_("force progress reporting")), + 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_END(), }; - split_opts.size_multiple = 2; - split_opts.max_commits = 0; - split_opts.expire_time = 0; + opts.progress = isatty(2); + 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, @@ -192,41 +256,56 @@ static int graph_write(int argc, const char **argv) if (!opts.obj_dir) opts.obj_dir = get_object_directory(); if (opts.append) - flags |= COMMIT_GRAPH_APPEND; + flags |= COMMIT_GRAPH_WRITE_APPEND; if (opts.split) - flags |= COMMIT_GRAPH_SPLIT; + 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; - - UNLEAK(buf); + 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; + } + } + + 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: + string_list_clear(&pack_indexes, 0); + strbuf_release(&buf); return result; } @@ -249,9 +328,9 @@ int cmd_commit_graph(int argc, const char **argv, const char *prefix) builtin_commit_graph_usage, PARSE_OPT_STOP_AT_NON_OPTION); + save_commit_buffer = 0; + if (argc > 0) { - if (!strcmp(argv[0], "read")) - return graph_read(argc, argv); if (!strcmp(argv[0], "verify")) return graph_verify(argc, argv); if (!strcmp(argv[0], "write")) diff --git a/third_party/git/builtin/commit-tree.c b/third_party/git/builtin/commit-tree.c index b866d8395104..1031b9a491c5 100644 --- a/third_party/git/builtin/commit-tree.c +++ b/third_party/git/builtin/commit-tree.c @@ -108,15 +108,15 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) 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 --git a/third_party/git/builtin/commit.c b/third_party/git/builtin/commit.c index ae7aaf6dc683..1dfd799ec518 100644 --- a/third_party/git/builtin/commit.c +++ b/third_party/git/builtin/commit.c @@ -59,6 +59,9 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\ " 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"); @@ -107,9 +110,9 @@ 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; static int config_commit_verbose = -1; /* unspecified */ -static int no_post_rewrite, allow_empty_message; +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; +static char *sign_commit, *pathspec_from_file; /* * The default commit message cleanup mode will remove the lines @@ -122,7 +125,6 @@ static enum commit_msg_cleanup_mode cleanup_mode; 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; @@ -179,12 +181,7 @@ static void determine_whence(struct wt_status *s) { 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; @@ -329,7 +326,7 @@ static void refresh_cache_or_die(int refresh_flags) 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; @@ -343,11 +340,31 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix PATHSPEC_PREFER_FULL, prefix, argv); + if (pathspec_from_file) { + if (interactive) + die(_("--pathspec-from-file is incompatible with --interactive/--patch")); + + if (all) + die(_("--pathspec-from-file with -a does not make sense")); + + if (pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&pathspec, 0, + PATHSPEC_PREFER_FULL, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + + if (!pathspec.nr && (also || (only && !amend && !allow_empty))) + 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); @@ -355,12 +372,16 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix 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 @@ -453,8 +474,10 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix 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)) @@ -510,7 +533,7 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int s->nowarn = nowarn; s->is_initial = get_oid(s->reference, &oid) ? 1 : 0; if (!s->is_initial) - hashcpy(s->sha1_commit, oid.hash); + oidcpy(&s->oid_commit, &oid); s->status_format = status_format; s->ignore_submodule_arg = ignore_submodule_arg; @@ -537,7 +560,7 @@ static void export_one(const char *var, const char *s, const char *e, int hack) struct strbuf buf = STRBUF_INIT; if (hack) strbuf_addch(&buf, hack); - strbuf_addf(&buf, "%.*s", (int)(e - s), s); + strbuf_add(&buf, s, e - s); setenv(var, buf.buf, 1); strbuf_release(&buf); } @@ -771,7 +794,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, */ 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"; } @@ -824,21 +847,19 @@ static int prepare_to_commit(const char *index_file, const char *prefix, 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"); @@ -944,16 +965,20 @@ static int prepare_to_commit(const char *index_file, const char *prefix, */ if (!committable && whence != FROM_MERGE && !allow_empty && !(amend && is_a_merge(current_head))) { + s->hints = 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; } @@ -978,15 +1003,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix, 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 && @@ -1156,8 +1181,10 @@ static int parse_and_validate_options(int argc, const char *argv[], 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")); @@ -1179,7 +1206,8 @@ static int parse_and_validate_options(int argc, const char *argv[], 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); @@ -1188,7 +1216,8 @@ static int parse_and_validate_options(int argc, const char *argv[], 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); } @@ -1198,8 +1227,6 @@ static int parse_and_validate_options(int argc, const char *argv[], if (also + only + all + interactive > 1) die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); - if (argc == 0 && (also || (only && !amend && !allow_empty))) - die(_("No paths with --include/--only does not make sense.")); cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor); handle_untracked_files_arg(s); @@ -1214,13 +1241,13 @@ static int parse_and_validate_options(int argc, const char *argv[], 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(); @@ -1343,9 +1370,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) 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), @@ -1364,9 +1391,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) 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(), }; @@ -1406,7 +1433,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) s.is_initial = get_oid(s.reference, &oid) ? 1 : 0; if (!s.is_initial) - hashcpy(s.sha1_commit, oid.hash); + oidcpy(&s.oid_commit, &oid); s.ignore_submodule_arg = ignore_submodule_arg; s.status_format = status_format; @@ -1463,31 +1490,8 @@ static int git_commit_config(const char *k, const char *v, void *cb) return git_status_config(k, v, s); } -int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...) -{ - struct argv_array hook_env = ARGV_ARRAY_INIT; - va_list args; - int ret; - - argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file); - - /* - * Let the hook know that no editor will be launched. - */ - if (!editor_is_used) - argv_array_push(&hook_env, "GIT_EDITOR=:"); - - va_start(args, name); - ret = run_hook_ve(hook_env.argv,name, args); - va_end(args); - argv_array_clear(&hook_env); - - return ret; -} - 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")), @@ -1535,6 +1539,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "amend", &amend, N_("amend previous commit")), OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), /* end commit contents options */ OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty, @@ -1578,8 +1584,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) 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. */ @@ -1628,8 +1634,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix) 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); } @@ -1656,7 +1664,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } 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; @@ -1664,8 +1672,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } 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")); } @@ -1689,12 +1697,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix) "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); @@ -1710,6 +1716,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) &oid, flags); } + apply_autostash(git_path_merge_autostash(the_repository)); + UNLEAK(err); UNLEAK(sb); return 0; diff --git a/third_party/git/builtin/config.c b/third_party/git/builtin/config.c index 98d65bc0ad4f..963d65fd3fc1 100644 --- a/third_party/git/builtin/config.c +++ b/third_party/git/builtin/config.c @@ -29,10 +29,11 @@ static int use_worktree_config; 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; #define ACTION_GET (1<<0) #define ACTION_GET_ALL (1<<1) @@ -64,6 +65,7 @@ static int show_origin; #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 +95,8 @@ static int option_parse_type(const struct option *opt, const char *arg, 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")) @@ -148,13 +152,15 @@ static struct option builtin_config_options[] = { 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 +184,34 @@ static void check_argc(int argc, int min, int max) 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 +231,8 @@ struct strbuf_list { 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 +254,12 @@ static int format_config(struct strbuf *buf, const char *key_, const char *value 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) @@ -395,6 +421,13 @@ static char *normalize_value(const char *key, const char *value) 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)) @@ -612,16 +645,21 @@ int cmd_config(int argc, const char **argv, const char *prefix) 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) { @@ -637,6 +675,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) */ 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 +686,14 @@ int cmd_config(int argc, const char **argv, const char *prefix) free(xdg_config); } } - else if (use_system_config) + else if (use_system_config) { given_config_source.file = git_etc_gitconfig(); - else if (use_local_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 +704,18 @@ int cmd_config(int argc, const char **argv, const char *prefix) "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 +725,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) config_options.git_dir = get_git_dir(); } - if (end_null) { + if (end_nul) { term = '\0'; delim = '\n'; key_delim = '\n'; diff --git a/third_party/git/builtin/credential-cache--daemon.c b/third_party/git/builtin/credential-cache--daemon.c new file mode 100644 index 000000000000..c61f123a3b81 --- /dev/null +++ b/third_party/git/builtin/credential-cache--daemon.c @@ -0,0 +1,318 @@ +#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) +{ + int fd; + + fd = unix_stream_listen(socket_path); + 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] <socket_path>", + 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] <action>", + "", + "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 --git a/third_party/git/builtin/credential-cache.c b/third_party/git/builtin/credential-cache.c new file mode 100644 index 000000000000..9b3f70990597 --- /dev/null +++ b/third_party/git/builtin/credential-cache.c @@ -0,0 +1,157 @@ +#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 + +static int send_request(const char *socket, const struct strbuf *out) +{ + int got_data = 0; + int fd = unix_stream_connect(socket); + + 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 && errno == ECONNRESET)) + 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 (errno != ENOENT && errno != ECONNREFUSED) + 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 = expand_user_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 [<options>] <action>", + 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] <action>", + "", + "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 --git a/third_party/git/builtin/credential-store.c b/third_party/git/builtin/credential-store.c new file mode 100644 index 000000000000..5331ab151a6f --- /dev/null +++ b/third_party/git/builtin/credential-store.c @@ -0,0 +1,195 @@ +#include "builtin.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) +{ + if (hold_lock_file_for_update(&credential_lock, fn, 0) < 0) + die_errno("unable to get credential storage lock"); + 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 [<options>] <action>", + 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 <path>"), + 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 = expand_user_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 --git a/third_party/git/builtin/describe.c b/third_party/git/builtin/describe.c index 200154297d5e..7668591d575a 100644 --- a/third_party/git/builtin/describe.c +++ b/third_party/git/builtin/describe.c @@ -12,10 +12,9 @@ #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 "revision.h" #include "list-objects.h" #include "commit-slab.h" @@ -55,6 +54,7 @@ struct commit_name { 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; }; @@ -64,19 +64,22 @@ static const char *prio_names[] = { }; static int commit_name_neq(const void *unused_cmp_data, - const void *entry, - const void *entry_or_key, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, const void *peeled) { - const struct commit_name *cn1 = entry; - const struct commit_name *cn2 = entry_or_key; + const struct commit_name *cn1, *cn2; + + cn1 = container_of(eptr, const struct commit_name, entry); + cn2 = container_of(entry_or_key, const struct commit_name, entry); return !oideq(&cn1->peeled, peeled ? peeled : &cn2->peeled); } static inline struct commit_name *find_commit_name(const struct object_id *peeled) { - return hashmap_get_from_hash(&names, oidhash(peeled), peeled); + return hashmap_get_entry_from_hash(&names, oidhash(peeled), peeled, + struct commit_name, entry); } static int replace_name(struct commit_name *e, @@ -123,13 +126,14 @@ static void add_to_known_names(const char *path, if (!e) { e = xmalloc(sizeof(struct commit_name)); oidcpy(&e->peeled, peeled); - hashmap_entry_init(e, oidhash(peeled)); - hashmap_add(&names, e); + hashmap_entry_init(&e->entry, oidhash(peeled)); + hashmap_add(&names, &e->entry); e->path = NULL; } e->tag = tag; e->prio = prio; e->name_checked = 0; + e->misnamed = 0; oidcpy(&e->oid, oid); free(e->path); e->path = xstrdup(path); @@ -273,10 +277,11 @@ static void append_name(struct commit_name *n, struct strbuf *dst) 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; } @@ -312,8 +317,8 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) * Exact match to an existing ref. */ append_name(n, dst); - if (longformat) - append_suffix(0, n->tag ? &n->tag->tagged->oid : oid, dst); + if (n->misnamed || longformat) + append_suffix(0, n->tag ? get_tagged_oid(n->tag) : oid, dst); if (suffix) strbuf_addstr(dst, suffix); return; @@ -330,8 +335,8 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) struct commit_name *n; init_commit_names(&commit_names); - n = hashmap_iter_first(&names, &iter); - for (; n; n = hashmap_iter_next(&iter)) { + hashmap_for_each_entry(&names, &iter, n, + entry /* member name */) { c = lookup_commit_reference_gently(the_repository, &n->peeled, 1); if (c) @@ -374,11 +379,25 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) 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; @@ -447,7 +466,7 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) } 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); @@ -482,15 +501,15 @@ static void process_object(struct object *obj, const char *path, void *data) static void describe_blob(struct object_id oid, struct strbuf *dst) { struct rev_info revs; - struct argv_array args = ARGV_ARRAY_INIT; + 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)) @@ -575,26 +594,26 @@ int cmd_describe(int argc, const char **argv, const char *prefix) 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); @@ -605,7 +624,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) 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; @@ -627,7 +646,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) } 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(); @@ -639,8 +658,8 @@ int cmd_describe(int argc, const char **argv, const char *prefix) 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 --git a/third_party/git/builtin/diff-files.c b/third_party/git/builtin/diff-files.c index 86ae474fbfb4..1e352dd8f77c 100644 --- a/third_party/git/builtin/diff-files.c +++ b/third_party/git/builtin/diff-files.c @@ -28,6 +28,13 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ repo_init_revisions(the_repository, &rev, prefix); rev.abbrev = 0; + + /* + * 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; + precompose_argv(argc, argv); argc = setup_revisions(argc, argv, &rev, NULL); diff --git a/third_party/git/builtin/diff-tree.c b/third_party/git/builtin/diff-tree.c index cb9ea793675c..802363d0a229 100644 --- a/third_party/git/builtin/diff-tree.c +++ b/third_party/git/builtin/diff-tree.c @@ -109,6 +109,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) 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; if (argc == 2 && !strcmp(argv[1], "-h")) @@ -127,6 +128,14 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) precompose_argv(argc, argv); 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; diff --git a/third_party/git/builtin/diff.c b/third_party/git/builtin/diff.c index 42ac803091e6..cd4083fed96e 100644 --- a/third_party/git/builtin/diff.c +++ b/third_party/git/builtin/diff.c @@ -6,6 +6,7 @@ #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" @@ -17,13 +18,19 @@ #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 [<options>] [<commit> [<commit>]] [--] [<path>...]"; +"git diff [<options>] [<commit>] [--] [<path>...]\n" +" or: git diff [<options>] --cached [<commit>] [--] [<path>...]\n" +" or: git diff [<options>] <commit> [<commit>...] <commit> [--] [<path>...]\n" +" or: git diff [<options>] <commit>...<commit>] [--] [<path>...]\n" +" or: git diff [<options>] <blob> <blob>]\n" +" or: git diff [<options>] --no-index [--] <path> <path>]\n" +COMMON_DIFF_OPTIONS_HELP; static const char *blob_path(struct object_array_entry *entry) { @@ -196,8 +203,7 @@ static int builtin_diff_combined(struct rev_info *revs, revs->dense_combined_merges = revs->combine_merges = 1; 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; } @@ -254,6 +260,108 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv 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 +371,29 @@ int cmd_diff(int argc, const char **argv, const char *prefix) 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: @@ -382,6 +500,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } } + 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 +515,8 @@ int cmd_diff(int argc, const char **argv, const char *prefix) 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 +558,12 @@ int cmd_diff(int argc, const char **argv, const char *prefix) 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 --git a/third_party/git/builtin/difftool.c b/third_party/git/builtin/difftool.c index 16eb8b70ea33..7ac432b88193 100644 --- a/third_party/git/builtin/difftool.c +++ b/third_party/git/builtin/difftool.c @@ -18,7 +18,7 @@ #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" @@ -125,12 +125,15 @@ struct working_tree_entry { }; static int working_tree_entry_cmp(const void *unused_cmp_data, - const void *entry, - const void *entry_or_key, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, const void *unused_keydata) { - const struct working_tree_entry *a = entry; - const struct working_tree_entry *b = entry_or_key; + const struct working_tree_entry *a, *b; + + a = container_of(eptr, const struct working_tree_entry, entry); + b = container_of(entry_or_key, const struct working_tree_entry, entry); + return strcmp(a->path, b->path); } @@ -145,12 +148,14 @@ struct pair_entry { }; static int pair_cmp(const void *unused_cmp_data, - const void *entry, - const void *entry_or_key, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, const void *unused_keydata) { - const struct pair_entry *a = entry; - const struct pair_entry *b = entry_or_key; + const struct pair_entry *a, *b; + + a = container_of(eptr, const struct pair_entry, entry); + b = container_of(entry_or_key, const struct pair_entry, entry); return strcmp(a->path, b->path); } @@ -161,14 +166,14 @@ static void add_left_or_right(struct hashmap *map, const char *path, struct pair_entry *e, *existing; FLEX_ALLOC_STR(e, path, path); - hashmap_entry_init(e, strhash(path)); - existing = hashmap_get(map, e, NULL); + hashmap_entry_init(&e->entry, strhash(path)); + existing = hashmap_get_entry(map, e, entry, NULL); if (existing) { free(e); e = existing; } else { e->left[0] = e->right[0] = '\0'; - hashmap_add(map, e); + hashmap_add(map, &e->entry); } strlcpy(is_right ? e->right : e->left, content, PATH_MAX); } @@ -179,12 +184,14 @@ struct path_entry { }; static int path_entry_cmp(const void *unused_cmp_data, - const void *entry, - const void *entry_or_key, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, const void *key) { - const struct path_entry *a = entry; - const struct path_entry *b = entry_or_key; + const struct path_entry *a, *b; + + a = container_of(eptr, const struct path_entry, entry); + b = container_of(entry_or_key, const struct path_entry, entry); return strcmp(a->path, key ? key : b->path); } @@ -203,10 +210,10 @@ static void changed_files(struct hashmap *result, const char *index_path, 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; @@ -218,9 +225,9 @@ static void changed_files(struct hashmap *result, const char *index_path, /* 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; @@ -234,8 +241,8 @@ static void changed_files(struct hashmap *result, const char *index_path, while (!strbuf_getline_nul(&buf, fp)) { struct path_entry *entry; FLEX_ALLOC_STR(entry, path, buf.buf); - hashmap_entry_init(entry, strhash(buf.buf)); - hashmap_add(result, entry); + hashmap_entry_init(&entry->entry, strhash(buf.buf)); + hashmap_add(result, &entry->entry); } fclose(fp); if (finish_command(&diff_files)) @@ -386,10 +393,10 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, child.clean_on_exit = 1; child.dir = prefix; child.out = -1; - argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z", - NULL); + strvec_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z", + NULL); for (i = 0; i < argc; i++) - argv_array_push(&child.args, argv[i]); + strvec_push(&child.args, argv[i]); if (start_command(&child)) die("could not obtain raw diff"); fp = xfdopen(child.out, "r"); @@ -461,12 +468,13 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, /* Avoid duplicate working_tree entries */ FLEX_ALLOC_STR(entry, path, dst_path); - hashmap_entry_init(entry, strhash(dst_path)); - if (hashmap_get(&working_tree_dups, entry, NULL)) { + hashmap_entry_init(&entry->entry, strhash(dst_path)); + if (hashmap_get(&working_tree_dups, &entry->entry, + NULL)) { free(entry); continue; } - hashmap_add(&working_tree_dups, entry); + hashmap_add(&working_tree_dups, &entry->entry); if (!use_wt_file(workdir, dst_path, &roid)) { if (checkout_path(rmode, &roid, dst_path, @@ -530,8 +538,8 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, * temporary file to both the left and right directories to show the * change in the recorded SHA1 for the submodule. */ - hashmap_iter_init(&submodules, &iter); - while ((entry = hashmap_iter_next(&iter))) { + 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); @@ -549,8 +557,8 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, * shows only the link itself, not the contents of the link target. * This loop replicates that behavior. */ - hashmap_iter_init(&symlinks2, &iter); - while ((entry = hashmap_iter_next(&iter))) { + 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); @@ -659,7 +667,7 @@ finish: static int run_file_diff(int prompt, const char *prefix, int argc, const char **argv) { - struct argv_array args = ARGV_ARRAY_INIT; + struct strvec args = STRVEC_INIT; const char *env[] = { "GIT_PAGER=", "GIT_EXTERNAL_DIFF=git-difftool--helper", NULL, NULL @@ -672,10 +680,10 @@ static int run_file_diff(int prompt, const char *prefix, env[2] = "GIT_DIFFTOOL_NO_PROMPT=true"; - argv_array_push(&args, "diff"); + strvec_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); + strvec_push(&args, argv[i]); + ret = run_command_v_opt_cd_env(args.v, RUN_GIT_CMD, prefix, env); exit(ret); } diff --git a/third_party/git/builtin/env--helper.c b/third_party/git/builtin/env--helper.c index 23c214fff6d6..27349098b074 100644 --- a/third_party/git/builtin/env--helper.c +++ b/third_party/git/builtin/env--helper.c @@ -7,18 +7,22 @@ static char const * const env__helper_usage[] = { 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 cmd_env__helper(int argc, const char **argv, const char *prefix) 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 --git a/third_party/git/builtin/fast-export.c b/third_party/git/builtin/fast-export.c index f541f55d333b..d2e33f500521 100644 --- a/third_party/git/builtin/fast-export.c +++ b/third_party/git/builtin/fast-export.c @@ -40,10 +40,12 @@ static int no_data; static int full_tree; static int reference_excluded_commits; static int show_original_ids; +static int mark_tags; static struct string_list extra_refs = STRING_LIST_INIT_NODUP; 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, @@ -119,19 +121,33 @@ static int has_unshown_parent(struct commit *commit) 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 void *va, const void *vb, - const void *unused_keydata) + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, + const void *keydata) { - const struct anonymized_entry *a = va, *b = vb; - return a->orig_len != b->orig_len || - memcmp(a->orig, b->orig, a->orig_len); + const struct anonymized_entry *a, *b; + + a = container_of(eptr, 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; + } + + b = container_of(entry_or_key, const struct anonymized_entry, hash); + return strcmp(a->orig, b->orig); } /* @@ -139,31 +155,39 @@ static int anonymized_entry_cmp(const void *unused_cmp_data, * 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, memhash(orig, *len)); + hashmap_entry_init(&key.hash, memhash(orig, len)); key.orig = orig; - key.orig_len = *len; - ret = hashmap_get(map, &key, 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; - hashmap_put(map, ret); + ret->anon = generate(data); + hashmap_put(map, &ret->hash); } - *len = ret->anon_len; return ret->anon; } @@ -175,13 +199,13 @@ static const void *anonymize_mem(struct hashmap *map, */ 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++); @@ -287,7 +311,8 @@ static void export_blob(const struct object_id *oid) 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)) < 0) die("oid mismatch in blob %s", oid_to_hex(oid)); object = parse_object_buffer(the_repository, oid, type, size, buf, &eaten); @@ -354,12 +379,12 @@ static void print_path_1(const char *path) 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) @@ -376,20 +401,23 @@ static void print_path(const char *path) } } -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, @@ -448,9 +476,9 @@ static void show_filemodify(struct diff_queue_struct *q, */ 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); @@ -486,12 +514,12 @@ static const char *find_encoding(const char *begin, const char *end) 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) @@ -510,13 +538,6 @@ static const char *anonymize_refname(const char *refname) 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)) { @@ -539,14 +560,13 @@ static char *anonymize_commit_message(const char *old) 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 <user%d@example.com>", counter, counter); counter++; - return strbuf_detach(&out, len); + return strbuf_detach(&out, NULL); } /* @@ -556,6 +576,7 @@ static void *anonymize_ident(const void *old, size_t *len) */ 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; @@ -581,9 +602,9 @@ static void anonymize_ident_line(const char **beg, const char **end) 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 { @@ -705,9 +726,10 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, 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++; } @@ -722,12 +744,12 @@ static void handle_commit(struct commit *commit, struct rev_info *rev, 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, @@ -797,8 +819,8 @@ static void handle_tag(const char *name, struct tag *tag) 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); } } @@ -842,25 +864,39 @@ static void handle_tag(const char *name, struct tag *tag) free(buf); return; case REWRITE: - if (tagged->type != OBJ_COMMIT) { - die("tag %s tags unexported %s!", - oid_to_hex(&tag->object.oid), - type_name(tagged->type)); - } - p = rewrite_commit((struct commit *)tagged); - if (!p) { - printf("reset %s\nfrom %s\n\n", - name, oid_to_hex(&null_oid)); - free(buf); - return; + if (tagged->type == OBJ_TAG && !mark_tags) { + die(_("Error: Cannot export nested tags unless --mark-tags is specified.")); + } else if (tagged->type == OBJ_COMMIT) { + p = rewrite_commit((struct commit *)tagged); + if (!p) { + printf("reset %s\nfrom %s\n\n", + name, oid_to_hex(&null_oid)); + free(buf); + return; + } + tagged_mark = get_object_mark(&p->object); + } else { + /* tagged->type is either OBJ_BLOB or OBJ_TAG */ + tagged_mark = get_object_mark(tagged); } - tagged_mark = get_object_mark(&p->object); } } - if (starts_with(name, "refs/tags/")) - name += 10; - printf("tag %s\nfrom :%d\n", name, tagged_mark); + if (tagged->type == OBJ_TAG) { + printf("reset %s\nfrom %s\n\n", + name, oid_to_hex(&null_oid)); + } + skip_prefix(name, "refs/tags/", &name); + printf("tag %s\n", name); + if (mark_tags) { + mark_next_object(&tag->object); + printf("mark :%"PRIu32"\n", last_idnum); + } + if (tagged_mark) + printf("from :%d\n", tagged_mark); + else + printf("from %s\n", oid_to_hex(&tagged->oid)); + if (show_original_ids) printf("original-oid %s\n", oid_to_hex(&tag->object.oid)); printf("%.*s%sdata %d\n%.*s\n", @@ -907,7 +943,7 @@ static void get_tags_and_duplicates(struct rev_cmdline_info *info) 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) { @@ -990,7 +1026,7 @@ static void handle_tags_and_duplicates(struct string_list *extras) /* * 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 @@ -1047,11 +1083,16 @@ static void export_marks(char *file) error("Unable to write marks file %s.", file); } -static void import_marks(char *input_file) +static void import_marks(char *input_file, int check_exists) { char line[512]; - FILE *f = xfopen(input_file, "r"); + FILE *f; + struct stat sb; + if (check_exists && stat(input_file, &sb)) + return; + + f = xfopen(input_file, "r"); while (fgets(line, sizeof(line), f)) { uint32_t mark; char *line_end, *mark_end; @@ -1110,12 +1151,45 @@ static void handle_deletes(void) } } +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; struct object_array commits = OBJECT_ARRAY_INIT; struct commit *commit; - char *export_filename = NULL, *import_filename = NULL; + char *export_filename = NULL, + *import_filename = NULL, + *import_filename_if_exists = NULL; uint32_t lastimportid; struct string_list refspecs_list = STRING_LIST_INIT_NODUP; struct string_list paths_of_changed_objects = STRING_LIST_INIT_DUP; @@ -1135,6 +1209,10 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) N_("Dump marks to this file")), OPT_STRING(0, "import-marks", &import_filename, N_("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")), OPT_BOOL(0, "fake-missing-tagger", &fake_missing_tagger, N_("Fake a tagger when tags lack one")), OPT_BOOL(0, "full-tree", &full_tree, @@ -1145,10 +1223,15 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) OPT_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"), 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 <from> to <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")), OPT_BOOL(0, "show-original-ids", &show_original_ids, N_("Show original object ids of blobs/commits")), + OPT_BOOL(0, "mark-tags", &mark_tags, + N_("Label tags with mark ids")), OPT_END() }; @@ -1170,6 +1253,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) 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; @@ -1182,8 +1268,12 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (use_done_feature) printf("feature done\n"); + if (import_filename && import_filename_if_exists) + die(_("Cannot pass both --import-marks and --import-marks-if-exists")); if (import_filename) - import_marks(import_filename); + import_marks(import_filename, 0); + else if (import_filename_if_exists) + import_marks(import_filename_if_exists, 1); lastimportid = last_idnum; if (import_filename && revs.prune_data.nr) diff --git a/third_party/git/builtin/fast-import.c b/third_party/git/builtin/fast-import.c new file mode 100644 index 000000000000..1bf50a73dc35 --- /dev/null +++ b/third_party/git/builtin/fast-import.c @@ -0,0 +1,3634 @@ +#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<<PACK_ID_BITS)-1) +#define DEPTH_BITS 13 +#define MAX_DEPTH ((1<<DEPTH_BITS)-1) + +/* + * We abuse the setuid bit on directories to mean "do not delta". + */ +#define NO_DELTA S_ISUID + +/* + * The amount of additional space required in order to write an object into the + * current pack. This is the hash lengths at the end of the pack, plus the + * length of one object ID. + */ +#define PACK_SIZE_THRESHOLD (the_hash_algo->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 *s, uintmax_t idnum, struct object_entry *oe) +{ + while ((idnum >> s->shift) >= 1024) { + s = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set)); + s->shift = marks->shift + 10; + s->data.sets[0] = marks; + marks = 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_fn(oid.hash, &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_fn(oid.hash, &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; + hashcpy(e->versions[0].oid.hash, (unsigned char *)c); + hashcpy(e->versions[1].oid.hash, (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. */ + /* <dataref> 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); + } + + /* <commit-ish> */ + 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 <object> 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 <object> 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 == ':') { /* <mark> */ + e = find_mark(marks, parse_mark_ref_space(p)); + if (!e) + die("Unknown mark: %s", command_buf.buf); + oidcpy(&oid, &e->idx.oid); + } else { /* <sha1> */ + 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 (<tree-ish> SP)? <path> */ + 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++; + ms = xcalloc(1, sizeof(*ms)); + string_list_insert(list, s)->util = ms; + + fp = fopen(f, "r"); + if (!fp) + die_errno("cannot read '%s'", f); + read_mark_file(ms, fp, insert_oid_entry); + fclose(fp); +} + +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 (skip_prefix(feature, "rewrite-submodules-from=", &arg)) { + } 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=<f>] [--max-pack-size=<n>] [--big-file-threshold=<n>] [--depth=<n>] [--active-branches=<n>] [--export-marks=<marks.file>]"; + +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); + atom_table = xcalloc(atom_table_sz, sizeof(struct atom_str*)); + branch_table = xcalloc(branch_table_sz, sizeof(struct branch*)); + avail_tree_table = xcalloc(avail_tree_table_sz, sizeof(struct avail_tree_content*)); + 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 --git a/third_party/git/builtin/fetch-pack.c b/third_party/git/builtin/fetch-pack.c index dc1485c8aa1b..58b7c1fbdc47 100644 --- a/third_party/git/builtin/fetch-pack.c +++ b/third_party/git/builtin/fetch-pack.c @@ -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 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) 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 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) } 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 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) 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,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) 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 +231,15 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) } 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 --git a/third_party/git/builtin/fetch.c b/third_party/git/builtin/fetch.c index 717dd14e8961..f9c3c49f14d9 100644 --- a/third_party/git/builtin/fetch.c +++ b/third_party/git/builtin/fetch.c @@ -7,6 +7,7 @@ #include "refs.h" #include "refspec.h" #include "object-store.h" +#include "oidset.h" #include "commit.h" #include "builtin.h" #include "string-list.h" @@ -18,11 +19,15 @@ #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" #include "commit-reach.h" +#include "branch.h" +#include "promisor-remote.h" +#include "commit-graph.h" +#include "shallow.h" #define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000) @@ -50,11 +55,14 @@ static int fetch_prune_tags_config = -1; /* unspecified */ static int prune_tags = -1; /* unspecified */ #define PRUNE_TAGS_BY_DEFAULT 0 /* do we prune tags by default? */ -static int all, append, dry_run, force, keep, multiple, update_head_ok, verbosity, deepen_relative; +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_children = 1; +static int max_jobs = -1, submodule_fetch_jobs_config = -1; +static int fetch_parallel_config = 1; static enum transport_family family; static const char *depth; static const char *deepen_since; @@ -71,6 +79,8 @@ static struct refspec refmap = REFSPEC_INIT_FETCH; static struct list_objects_filter_options filter_options; 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 git_fetch_config(const char *k, const char *v, void *cb) { @@ -96,13 +106,20 @@ static int git_fetch_config(const char *k, const char *v, void *cb) } if (!strcmp(k, "submodule.fetchjobs")) { - max_children = parse_submodule_fetchjobs(k, v); + submodule_fetch_jobs_config = parse_submodule_fetchjobs(k, v); return 0; } else if (!strcmp(k, "fetch.recursesubmodules")) { recurse_submodules = parse_fetch_recurse_submodules_arg(k, v); return 0; } + if (!strcmp(k, "fetch.parallel")) { + fetch_parallel_config = git_config_int(k, v); + if (fetch_parallel_config < 0) + die(_("fetch.parallel cannot be negative")); + return 0; + } + return git_default_config(k, v, cb); } @@ -123,6 +140,8 @@ static struct option builtin_fetch_options[] = { OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "all", &all, N_("fetch from all remotes")), + OPT_BOOL(0, "set-upstream", &set_upstream, + N_("set upstream for git pull/fetch")), OPT_BOOL('a', "append", &append, N_("append to .git/FETCH_HEAD instead of overwriting")), OPT_STRING(0, "upload-pack", &upload_pack, N_("path"), @@ -134,17 +153,19 @@ static struct option builtin_fetch_options[] = { N_("fetch all tags and associated objects"), TAGS_SET), OPT_SET_INT('n', NULL, &tags, N_("do not fetch all tags (--no-tags)"), TAGS_UNSET), - OPT_INTEGER('j', "jobs", &max_children, + OPT_INTEGER('j', "jobs", &max_jobs, N_("number of submodules fetched in parallel")), 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")), @@ -162,15 +183,15 @@ static struct option builtin_fetch_options[] = { 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), @@ -179,10 +200,16 @@ static struct option builtin_fetch_options[] = { OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"), N_("report that we have only objects reachable from this object")), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), + OPT_BOOL(0, "auto-maintenance", &enable_auto_gc, + N_("run 'maintenance --auto' after fetching")), OPT_BOOL(0, "auto-gc", &enable_auto_gc, - N_("run 'gc --auto' after fetching")), + N_("run 'maintenance --auto' after fetching")), OPT_BOOL(0, "show-forced-updates", &fetch_show_forced_updates, N_("check for forced-updates on all updated branches")), + OPT_BOOL(0, "write-commit-graph", &fetch_write_commit_graph, + N_("write the commit-graph after fetching")), + OPT_BOOL(0, "stdin", &stdin_refspecs, + N_("accept refspecs from stdin")), OPT_END() }; @@ -239,32 +266,31 @@ static void add_merge_config(struct ref **head, } } -static int will_fetch(struct ref **head, const unsigned char *sha1) +static void create_fetch_oidset(struct ref **head, struct oidset *out) { struct ref *rm = *head; while (rm) { - if (hasheq(rm->old_oid.hash, sha1)) - return 1; + oidset_insert(out, &rm->old_oid); rm = rm->next; } - return 0; } struct refname_hash_entry { - struct hashmap_entry ent; /* must be the first member */ + struct hashmap_entry ent; struct object_id oid; int ignore; char refname[FLEX_ARRAY]; }; static int refname_hash_entry_cmp(const void *hashmap_cmp_fn_data, - const void *e1_, - const void *e2_, + const struct hashmap_entry *eptr, + const struct hashmap_entry *entry_or_key, const void *keydata) { - const struct refname_hash_entry *e1 = e1_; - const struct refname_hash_entry *e2 = e2_; + const struct refname_hash_entry *e1, *e2; + e1 = container_of(eptr, const struct refname_hash_entry, ent); + e2 = container_of(entry_or_key, const struct refname_hash_entry, ent); return strcmp(e1->refname, keydata ? keydata : e2->refname); } @@ -276,9 +302,9 @@ static struct refname_hash_entry *refname_hash_add(struct hashmap *map, size_t len = strlen(refname); FLEX_ALLOC_MEM(ent, refname, refname, len); - hashmap_entry_init(ent, strhash(refname)); + hashmap_entry_init(&ent->ent, strhash(refname)); oidcpy(&ent->oid, oid); - hashmap_add(map, ent); + hashmap_add(map, &ent->ent); return ent; } @@ -313,13 +339,16 @@ static void find_non_local_tags(const struct ref *refs, { struct hashmap existing_refs; struct hashmap remote_refs; + struct oidset fetch_oids = OIDSET_INIT; struct string_list remote_refs_list = STRING_LIST_INIT_NODUP; 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); + create_fetch_oidset(head, &fetch_oids); for_each_ref(add_one_refname, &existing_refs); for (ref = refs; ref; ref = ref->next) { @@ -334,11 +363,10 @@ static void find_non_local_tags(const struct ref *refs, */ if (ends_with(ref->name, "^{}")) { if (item && - !has_object_file_with_flags(&ref->old_oid, - OBJECT_INFO_QUICK) && - !will_fetch(head, ref->old_oid.hash) && - !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) && - !will_fetch(head, item->oid.hash)) + !has_object_file_with_flags(&ref->old_oid, quick_flags) && + !oidset_contains(&fetch_oids, &ref->old_oid) && + !has_object_file_with_flags(&item->oid, quick_flags) && + !oidset_contains(&fetch_oids, &item->oid)) clear_item(item); item = NULL; continue; @@ -351,8 +379,8 @@ static void find_non_local_tags(const struct ref *refs, * fetch. */ if (item && - !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) && - !will_fetch(head, item->oid.hash)) + !has_object_file_with_flags(&item->oid, quick_flags) && + !oidset_contains(&fetch_oids, &item->oid)) clear_item(item); item = NULL; @@ -365,15 +393,15 @@ static void find_non_local_tags(const struct ref *refs, item = refname_hash_add(&remote_refs, ref->name, &ref->old_oid); string_list_insert(&remote_refs_list, ref->name); } - hashmap_free(&existing_refs, 1); + hashmap_free_entries(&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) && - !will_fetch(head, item->oid.hash)) + !has_object_file_with_flags(&item->oid, quick_flags) && + !oidset_contains(&fetch_oids, &item->oid)) clear_item(item); /* @@ -383,8 +411,10 @@ static void find_non_local_tags(const struct ref *refs, for_each_string_list_item(remote_ref_item, &remote_refs_list) { const char *refname = remote_ref_item->string; struct ref *rm; + unsigned int hash = strhash(refname); - item = hashmap_get_from_hash(&remote_refs, strhash(refname), refname); + item = hashmap_get_entry_from_hash(&remote_refs, hash, refname, + struct refname_hash_entry, ent); if (!item) BUG("unseen remote ref?"); @@ -398,8 +428,9 @@ static void find_non_local_tags(const struct ref *refs, **tail = rm; *tail = &rm->next; } - hashmap_free(&remote_refs, 1); + hashmap_free_entries(&remote_refs, struct refname_hash_entry, ent); string_list_clear(&remote_refs_list, 0); + oidset_clear(&fetch_oids); } static struct ref *get_ref_map(struct remote *remote, @@ -416,6 +447,7 @@ static struct ref *get_ref_map(struct remote *remote, struct ref *orefs = NULL, **oref_tail = &orefs; struct hashmap existing_refs; + int existing_refs_populated = 0; if (rs->nr) { struct refspec *fetch_refspec; @@ -507,26 +539,41 @@ static struct ref *get_ref_map(struct remote *remote, 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) { const char *refname = rm->peer_ref->name; struct refname_hash_entry *peer_item; + unsigned int hash = strhash(refname); - peer_item = hashmap_get_from_hash(&existing_refs, - strhash(refname), - 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); if (peer_item) { struct object_id *old_oid = &peer_item->oid; oidcpy(&rm->peer_ref->old_oid, old_oid); } } } - hashmap_free(&existing_refs, 1); + if (existing_refs_populated) + hashmap_free_entries(&existing_refs, struct refname_hash_entry, ent); return ref_map; } @@ -621,7 +668,7 @@ static void prepare_format_display(struct ref *ref_map) struct ref *rm; const char *format = "full"; - git_config_get_string_const("fetch.output", &format); + git_config_get_string_tmp("fetch.output", &format); if (!strcasecmp(format, "full")) compact_format = 0; else if (!strcasecmp(format, "compact")) @@ -869,7 +916,9 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, const char *what, *kind; struct ref *rm; char *url; - const char *filename = dry_run ? "/dev/null" : git_path_fetch_head(the_repository); + const char *filename = (!write_fetch_head + ? "/dev/null" + : git_path_fetch_head(the_repository)); int want_status; int summary_width = transport_summary_width(ref_map); @@ -883,8 +932,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, 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; } @@ -927,25 +978,21 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, 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 = ""; what = ""; } - else if (starts_with(rm->name, "refs/heads/")) { + else if (skip_prefix(rm->name, "refs/heads/", &what)) kind = "branch"; - what = rm->name + 11; - } - else if (starts_with(rm->name, "refs/tags/")) { + else if (skip_prefix(rm->name, "refs/tags/", &what)) kind = "tag"; - what = rm->name + 10; - } - else if (starts_with(rm->name, "refs/remotes/")) { + else if (skip_prefix(rm->name, "refs/remotes/", &what)) kind = "remote-tracking branch"; - what = rm->name + 13; - } else { kind = ""; what = rm->name; @@ -990,11 +1037,17 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, rc |= update_local_ref(ref, 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) { fprintf(stderr, _("From %.*s\n"), @@ -1054,7 +1107,8 @@ static int check_exist_and_connected(struct ref *ref_map) * we need all direct targets to exist. */ for (r = rm; r; r = r->next) { - if (!has_object_file(&r->old_oid)) + if (!has_object_file_with_flags(&r->old_oid, + OBJECT_INFO_SKIP_FETCH_OBJECT)) return -1; } @@ -1065,8 +1119,11 @@ static int check_exist_and_connected(struct ref *ref_map) static int fetch_refs(struct transport *transport, struct ref *ref_map) { int ret = check_exist_and_connected(ref_map); - if (ret) + 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) /* * Keep the new pack's ".keep" file around to allow the caller @@ -1082,11 +1139,14 @@ 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 = store_updated_refs(transport->url, - transport->remote->name, - connectivity_checked, - ref_map); + 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); return ret; } @@ -1238,13 +1298,10 @@ static struct transport *prepare_transport(struct remote *remote, int deepen) if (update_shallow) set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes"); if (filter_options.choice) { - struct strbuf expanded_filter_spec = STRBUF_INIT; - expand_list_objects_filter_spec(&filter_options, - &expanded_filter_spec); - set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, - expanded_filter_spec.buf); + const char *spec = + expand_list_objects_filter_spec(&filter_options); + set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, spec); set_option(transport, TRANS_OPT_FROM_PROMISOR, "1"); - strbuf_release(&expanded_filter_spec); } if (negotiation_tip.nr) { if (transport->smart_options) @@ -1292,7 +1349,7 @@ static int do_fetch(struct transport *transport, int autotags = (transport->remote->fetch_tags == 1); int retcode = 0; const struct ref *remote_refs; - struct argv_array ref_prefixes = ARGV_ARRAY_INIT; + struct strvec ref_prefixes = STRVEC_INIT; int must_list_refs = 1; if (tags == TAGS_DEFAULT) { @@ -1303,7 +1360,7 @@ static int do_fetch(struct transport *transport, } /* if not appending, truncate FETCH_HEAD */ - if (!append && !dry_run) { + if (!append && write_fetch_head) { retcode = truncate_fetch_head(); if (retcode) goto cleanup; @@ -1330,16 +1387,18 @@ static int do_fetch(struct transport *transport, if (tags == TAGS_SET || tags == TAGS_DEFAULT) { must_list_refs = 1; - if (ref_prefixes.argc) - argv_array_push(&ref_prefixes, "refs/tags/"); + if (ref_prefixes.nr) + strvec_push(&ref_prefixes, "refs/tags/"); } - if (must_list_refs) + if (must_list_refs) { + trace2_region_enter("fetch", "remote_refs", the_repository); remote_refs = transport_get_remote_refs(transport, &ref_prefixes); - else + trace2_region_leave("fetch", "remote_refs", the_repository); + } else remote_refs = NULL; - argv_array_clear(&ref_prefixes); + strvec_clear(&ref_prefixes); ref_map = get_ref_map(transport->remote, remote_refs, rs, tags, &autotags); @@ -1367,6 +1426,51 @@ static int do_fetch(struct transport *transport, retcode = 1; goto cleanup; } + + if (set_upstream) { + struct branch *branch = branch_get("HEAD"); + struct ref *rm; + struct ref *source_ref = NULL; + + /* + * We're setting the upstream configuration for the + * current branch. The relevant upstream is the + * fetched branch that is meant to be merged with the + * current one, i.e. the one fetched to FETCH_HEAD. + * + * When there are several such branches, consider the + * request ambiguous and err on the safe side by doing + * nothing and just emit a warning. + */ + for (rm = ref_map; rm; rm = rm->next) { + if (!rm->peer_ref) { + if (source_ref) { + warning(_("multiple branches detected, incompatible with --set-upstream")); + goto skip; + } else { + source_ref = rm; + } + } + } + if (source_ref) { + if (!strcmp(source_ref->name, "HEAD") || + starts_with(source_ref->name, "refs/heads/")) + install_branch_config(0, + branch->name, + transport->remote->name, + source_ref->name); + else if (starts_with(source_ref->name, "refs/remotes/")) + warning(_("not setting upstream for a remote remote-tracking branch")); + else if (starts_with(source_ref->name, "refs/tags/")) + warning(_("not setting upstream for a remote tag")); + else + warning(_("unknown branch type")); + } else { + warning(_("no source branch found.\n" + "you need to specify exactly one branch with the --set-upstream option.")); + } + } + skip: free_refs(ref_map); /* if neither --no-tags nor --tags was specified, do automated tag @@ -1432,65 +1536,138 @@ static int add_remote_or_group(const char *name, struct string_list *list) 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 */ + +struct parallel_fetch_state { + const char **argv; + struct string_list *remotes; + int next, result; +}; + +static int fetch_next_remote(struct child_process *cp, struct strbuf *out, + void *cb, void **task_cb) +{ + struct parallel_fetch_state *state = cb; + char *remote; + + if (state->next < 0 || state->next >= state->remotes->nr) + return 0; + + remote = state->remotes->items[state->next++].string; + *task_cb = remote; + strvec_pushv(&cp->args, state->argv); + strvec_push(&cp->args, remote); + cp->git_cmd = 1; + + if (verbosity >= 0) + printf(_("Fetching %s\n"), remote); + + return 1; +} + +static int fetch_failed_to_start(struct strbuf *out, void *cb, void *task_cb) +{ + struct parallel_fetch_state *state = cb; + const char *remote = task_cb; + + state->result = error(_("Could not fetch %s"), remote); + + return 0; } -static int fetch_multiple(struct string_list *list) +static int fetch_finished(int result, struct strbuf *out, + void *cb, void *task_cb) +{ + struct parallel_fetch_state *state = cb; + const char *remote = task_cb; + + if (result) { + strbuf_addf(out, _("could not fetch '%s' (exit code: %d)\n"), + remote, result); + state->result = -1; + } + + return 0; +} + +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", NULL); + strvec_pushl(&argv, "fetch", "--append", "--no-auto-gc", + "--no-write-commit-graph", NULL); add_options_to_argv(&argv); - for (i = 0; i < list->nr; i++) { - const char *name = list->items[i].string; - argv_array_push(&argv, name); - if (verbosity >= 0) - printf(_("Fetching %s\n"), name); - if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { - error(_("Could not fetch %s"), name); - result = 1; + if (max_children != 1 && list->nr != 1) { + struct parallel_fetch_state state = { argv.v, list, 0, 0 }; + + strvec_push(&argv, "--end-of-options"); + result = run_processes_parallel_tr2(max_children, + &fetch_next_remote, + &fetch_failed_to_start, + &fetch_finished, + &state, + "fetch", "parallel/fetch"); + + if (!result) + result = state.result; + } else + for (i = 0; i < list->nr; i++) { + const char *name = list->items[i].string; + strvec_push(&argv, name); + if (verbosity >= 0) + printf(_("Fetching %s\n"), name); + if (run_command_v_opt(argv.v, RUN_GIT_CMD)) { + error(_("Could not fetch %s"), name); + result = 1; + } + strvec_pop(&argv); } - argv_array_pop(&argv); - } - argv_array_clear(&argv); - return result; + strvec_clear(&argv); + return !!result; } /* @@ -1510,41 +1687,32 @@ static inline void fetch_one_setup_partial(struct remote *remote) * If no prior partial clone/fetch and the current fetch DID NOT * request a partial-fetch, do a normal fetch. */ - if (!repository_format_partial_clone && !filter_options.choice) + if (!has_promisor_remote() && !filter_options.choice) return; /* - * If this is the FIRST partial-fetch request, we enable partial - * on this repo and remember the given filter-spec as the default - * for subsequent fetches to this remote. + * 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 if there is currently no default filter-spec. */ - if (!repository_format_partial_clone && filter_options.choice) { + if (filter_options.choice) { partial_clone_register(remote->name, &filter_options); return; } /* - * We are currently limited to only ONE promisor remote and only - * allow partial-fetches from the promisor remote. - */ - if (strcmp(remote->name, repository_format_partial_clone)) { - if (filter_options.choice) - die(_("--filter can only be used with the remote " - "configured in extensions.partialClone")); - return; - } - - /* * Do a partial-fetch from the promisor remote using either the * explicitly given filter-spec or inherit the filter-spec from * the config. */ if (!filter_options.choice) - partial_clone_get_default_filter_spec(&filter_options); + partial_clone_get_default_filter_spec(&filter_options, remote->name); 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; @@ -1587,20 +1755,24 @@ static int fetch_one(struct remote *remote, int argc, const char **argv, int pru 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; @@ -1622,22 +1794,31 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) 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"); - fetch_if_missing = 0; - /* 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(&max_children, &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 (deepen_relative) { if (deepen_relative < 0) @@ -1661,8 +1842,9 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (depth || deepen_since || deepen_not.nr) deepen = 1; - if (filter_options.choice && !repository_format_partial_clone) - 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) @@ -1695,19 +1877,35 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) } if (remote) { - if (filter_options.choice || repository_format_partial_clone) + 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; + if (filter_options.choice) die(_("--filter can only be used with the remote " "configured in extensions.partialclone")); + + if (stdin_refspecs) + die(_("--stdin can only be used when fetching " + "from one remote")); + + if (max_children < 0) + max_children = fetch_parallel_config; + /* TODO should this also die if we have a previous partial-clone? */ - result = fetch_multiple(&list); + result = fetch_multiple(&list, max_children); } 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) + max_children = submodule_fetch_jobs_config; + if (max_children < 0) + max_children = fetch_parallel_config; add_options_to_argv(&options); result = fetch_populated_submodules(the_repository, @@ -1717,20 +1915,29 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) recurse_submodules_default, verbosity < 0, max_children); - argv_array_clear(&options); + strvec_clear(&options); } string_list_clear(&list, 0); - close_object_store(the_repository->objects); + prepare_repo_settings(the_repository); + if (fetch_write_commit_graph > 0 || + (fetch_write_commit_graph < 0 && + the_repository->settings.fetch_write_commit_graph)) { + int commit_graph_flags = COMMIT_GRAPH_WRITE_SPLIT; + + if (progress) + commit_graph_flags |= COMMIT_GRAPH_WRITE_PROGRESS; - 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); + write_commit_graph_reachable(the_repository->objects->odb, + commit_graph_flags, + NULL); } + close_object_store(the_repository->objects); + + if (enable_auto_gc) + run_auto_maintenance(verbosity < 0); + return result; } diff --git a/third_party/git/builtin/fmt-merge-msg.c b/third_party/git/builtin/fmt-merge-msg.c index a4615587fd79..48a8699de728 100644 --- a/third_party/git/builtin/fmt-merge-msg.c +++ b/third_party/git/builtin/fmt-merge-msg.c @@ -1,667 +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 <message>] [--log[=<n>] | --no-log] [--file <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; - 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 (starts_with(line, "branch ")) { - origin_data->is_local_branch = 1; - origin = line + 7; - string_list_append(&src_data->branch, origin); - src_data->head_status |= 2; - } else if (starts_with(line, "tag ")) { - origin = line; - string_list_append(&src_data->tag, origin + 4); - 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 strbuf sig = STRBUF_INIT; - - if (!buf || type != OBJ_TAG) - goto next; - len = parse_signature(buf, size); - - if (size == len) - ; /* merely annotated */ - else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig, NULL)) { - if (!sig.len) - 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 --git a/third_party/git/builtin/for-each-ref.c b/third_party/git/builtin/for-each-ref.c index 465153e85337..9d1ecda2b8f3 100644 --- a/third_party/git/builtin/for-each-ref.c +++ b/third_party/git/builtin/for-each-ref.c @@ -9,7 +9,7 @@ static char const * const for_each_ref_usage[] = { N_("git for-each-ref [<options>] [<pattern>]"), N_("git for-each-ref [--points-at <object>]"), - N_("git for-each-ref [(--merged | --no-merged) [<commit>]]"), + N_("git for-each-ref [--merged [<commit>]] [--no-merged [<commit>]]"), N_("git for-each-ref [--contains [<commit>]] [--no-contains [<commit>]]"), NULL }; @@ -70,7 +70,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) if (!sorting) sorting = ref_default_sorting(); - sorting->ignore_case = icase; + ref_sorting_icase_all(sorting, icase); filter.ignore_case = icase; filter.name_patterns = argv; diff --git a/third_party/git/builtin/fsck.c b/third_party/git/builtin/fsck.c index 18403a94fa42..fbf26cafcfd7 100644 --- a/third_party/git/builtin/fsck.c +++ b/third_party/git/builtin/fsck.c @@ -49,41 +49,22 @@ static int name_objects; #define ERROR_PACK 04 #define ERROR_REFS 010 #define ERROR_COMMIT_GRAPH 020 +#define ERROR_MULTI_PACK_INDEX 040 -static const char *describe_object(struct object *obj) +static const char *describe_object(const struct object_id *oid) { - static struct strbuf bufs[] = { - STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT - }; - static int b = 0; - struct strbuf *buf; - char *name = NULL; - - if (name_objects) - name = lookup_decoration(fsck_walk_options.object_names, obj); - - buf = bufs + b; - b = (b + 1) % ARRAY_SIZE(bufs); - strbuf_reset(buf); - strbuf_addstr(buf, oid_to_hex(&obj->oid)); - if (name) - strbuf_addf(buf, " (%s)", name); - - return buf->buf; + return fsck_describe_object(&fsck_walk_options, oid); } -static const char *printable_type(struct object *obj) +static const char *printable_type(const struct object_id *oid, + enum object_type type) { const char *ret; - if (obj->type == OBJ_NONE) { - enum object_type type = oid_object_info(the_repository, - &obj->oid, NULL); - if (type > 0) - object_as_type(the_repository, obj, type, 0); - } + if (type == OBJ_NONE) + type = oid_object_info(the_repository, oid, NULL); - ret = type_name(obj->type); + ret = type_name(type); if (!ret) ret = _("unknown"); @@ -118,26 +99,32 @@ static int objerror(struct object *obj, const char *err) errors_found |= ERROR_OBJECT; /* TRANSLATORS: e.g. error in tree 01bfda: <more explanation> */ fprintf_ln(stderr, _("error in %s %s: %s"), - printable_type(obj), describe_object(obj), err); + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid), err); return -1; } static int fsck_error_func(struct fsck_options *o, - struct object *obj, int type, const char *message) + const struct object_id *oid, + enum object_type object_type, + int msg_type, const char *message) { - switch (type) { + switch (msg_type) { case FSCK_WARN: /* TRANSLATORS: e.g. warning in tree 01bfda: <more explanation> */ fprintf_ln(stderr, _("warning in %s %s: %s"), - printable_type(obj), describe_object(obj), message); + printable_type(oid, object_type), + describe_object(oid), message); return 0; case FSCK_ERROR: /* TRANSLATORS: e.g. error in tree 01bfda: <more explanation> */ fprintf_ln(stderr, _("error in %s %s: %s"), - printable_type(obj), describe_object(obj), message); + printable_type(oid, object_type), + describe_object(oid), message); return 1; default: - BUG("%d (FSCK_IGNORE?) should never trigger this callback", type); + BUG("%d (FSCK_IGNORE?) should never trigger this callback", + msg_type); } } @@ -155,7 +142,8 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt if (!obj) { /* ... these references to parent->fld are safe here */ printf_ln(_("broken link from %7s %s"), - printable_type(parent), describe_object(parent)); + printable_type(&parent->oid, parent->type), + describe_object(&parent->oid)); printf_ln(_("broken link from %7s %s"), (type == OBJ_ANY ? _("unknown") : type_name(type)), _("unknown")); @@ -180,13 +168,13 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt 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), - describe_object(parent), - printable_type(obj), - describe_object(obj)); + printable_type(&parent->oid, parent->type), + describe_object(&parent->oid), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); errors_found |= ERROR_REACHABLE; } return 1; @@ -253,7 +241,7 @@ static void mark_unreachable_referents(const struct object_id *oid) 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; @@ -292,8 +280,9 @@ static void check_reachable_object(struct object *obj) return; if (has_object_pack(&obj->oid)) return; /* it is in pack - forget about it */ - printf_ln(_("missing %s %s"), printable_type(obj), - describe_object(obj)); + printf_ln(_("missing %s %s"), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); errors_found |= ERROR_REACHABLE; return; } @@ -318,8 +307,9 @@ static void check_unreachable_object(struct object *obj) * since this is something that is prunable. */ if (show_unreachable) { - printf_ln(_("unreachable %s %s"), printable_type(obj), - describe_object(obj)); + printf_ln(_("unreachable %s %s"), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); return; } @@ -337,12 +327,13 @@ static void check_unreachable_object(struct object *obj) */ if (!(obj->flags & USED)) { if (show_dangling) - printf_ln(_("dangling %s %s"), printable_type(obj), - describe_object(obj)); + printf_ln(_("dangling %s %s"), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); if (write_lost_and_found) { char *filename = git_pathdup("lost-found/%s/%s", obj->type == OBJ_COMMIT ? "commit" : "other", - describe_object(obj)); + describe_object(&obj->oid)); FILE *f; if (safe_create_leading_directories_const(filename)) { @@ -355,7 +346,7 @@ static void check_unreachable_object(struct object *obj) if (stream_blob_to_fd(fileno(f), &obj->oid, NULL, 1)) die_errno(_("could not write '%s'"), filename); } else - fprintf(f, "%s\n", describe_object(obj)); + fprintf(f, "%s\n", describe_object(&obj->oid)); if (fclose(f)) die_errno(_("could not finish '%s'"), filename); @@ -374,7 +365,7 @@ static void check_unreachable_object(struct object *obj) static void check_object(struct object *obj) { if (verbose) - fprintf_ln(stderr, _("Checking %s"), describe_object(obj)); + fprintf_ln(stderr, _("Checking %s"), describe_object(&obj->oid)); if (obj->flags & REACHABLE) check_reachable_object(obj); @@ -432,7 +423,8 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size) if (verbose) fprintf_ln(stderr, _("Checking %s %s"), - printable_type(obj), describe_object(obj)); + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); if (fsck_walk(obj, NULL, &fsck_obj_options)) objerror(obj, _("broken links")); @@ -445,7 +437,7 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size) if (!commit->parents && show_root) printf_ln(_("root %s"), - describe_object(&commit->object)); + describe_object(&commit->object.oid)); } if (obj->type == OBJ_TAG) { @@ -453,10 +445,10 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size) if (show_tags && tag->tagged) { printf_ln(_("tagged %s %s (%s) in %s"), - printable_type(tag->tagged), - describe_object(tag->tagged), + printable_type(&tag->tagged->oid, tag->tagged->type), + describe_object(&tag->tagged->oid), tag->tag, - describe_object(&tag->object)); + describe_object(&tag->object.oid)); } } @@ -499,10 +491,10 @@ static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid, if (!is_null_oid(oid)) { obj = lookup_object(the_repository, oid); if (obj && (obj->flags & HAS_OBJ)) { - if (timestamp && name_objects) - add_decoration(fsck_walk_options.object_names, - obj, - xstrfmt("%s@{%"PRItime"}", refname, timestamp)); + if (timestamp) + fsck_put_object_name(&fsck_walk_options, oid, + "%s@{%"PRItime"}", + refname, timestamp); obj->flags |= USED; mark_object_reachable(obj); } else if (!is_promisor_object(oid)) { @@ -566,9 +558,8 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid, } default_refs++; obj->flags |= USED; - if (name_objects) - add_decoration(fsck_walk_options.object_names, - obj, xstrdup(refname)); + fsck_put_object_name(&fsck_walk_options, + oid, "%s", refname); mark_object_reachable(obj); return 0; @@ -586,7 +577,7 @@ static void get_default_heads(void) 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; @@ -742,9 +733,7 @@ static int fsck_cache_tree(struct cache_tree *it) return 1; } obj->flags |= USED; - if (name_objects) - add_decoration(fsck_walk_options.object_names, - obj, xstrdup(":")); + fsck_put_object_name(&fsck_walk_options, &it->oid, ":"); mark_object_reachable(obj); if (obj->type != OBJ_TREE) err |= objerror(obj, _("non-tree in cache-tree")); @@ -830,8 +819,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) } if (name_objects) - fsck_walk_options.object_names = - xcalloc(1, sizeof(struct decoration)); + fsck_enable_object_names(&fsck_walk_options); git_config(fsck_config, NULL); @@ -890,9 +878,8 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) } obj->flags |= USED; - if (name_objects) - add_decoration(fsck_walk_options.object_names, - obj, xstrdup(arg)); + fsck_put_object_name(&fsck_walk_options, &oid, + "%s", arg); mark_object_reachable(obj); continue; } @@ -928,10 +915,8 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) continue; obj = &blob->object; obj->flags |= USED; - if (name_objects) - add_decoration(fsck_walk_options.object_names, - obj, - xstrfmt(":%s", active_cache[i]->name)); + fsck_put_object_name(&fsck_walk_options, &obj->oid, + ":%s", active_cache[i]->name); mark_object_reachable(obj); } if (active_cache_tree) @@ -968,7 +953,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) 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 --git a/third_party/git/builtin/gc.c b/third_party/git/builtin/gc.c index c18efadda53e..090959350e06 100644 --- a/third_party/git/builtin/gc.c +++ b/third_party/git/builtin/gc.c @@ -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" @@ -27,6 +27,8 @@ #include "pack-objects.h" #include "blob.h" #include "tree.h" +#include "promisor-remote.h" +#include "refs.h" #define FAILED_RUN "failed to run %s" @@ -41,7 +43,6 @@ static int aggressive_depth = 50; static int aggressive_window = 250; static int gc_auto_threshold = 6700; static int gc_auto_pack_limit = 50; -static int gc_write_commit_graph; static int detach_auto = 1; static timestamp_t gc_log_expire_time; static const char *gc_log_expire = "1.day.ago"; @@ -50,12 +51,12 @@ static const char *prune_worktrees_expire = "3.months.ago"; 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 pack_refs_cmd = STRVEC_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; @@ -148,7 +149,6 @@ static void gc_config(void) git_config_get_int("gc.aggressivedepth", &aggressive_depth); git_config_get_int("gc.auto", &gc_auto_threshold); git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit); - git_config_get_bool("gc.writecommitgraph", &gc_write_commit_graph); git_config_get_bool("gc.autodetach", &detach_auto); git_config_get_expiry("gc.pruneexpire", &prune_expire); git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire); @@ -312,18 +312,18 @@ static uint64_t estimate_repack_memory(struct packed_git *pack) 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) @@ -332,7 +332,7 @@ static void add_repack_all_option(struct string_list *keep_pack) 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) @@ -459,7 +459,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) /* * Returns 0 if there was no previous error and gc can proceed, 1 if * gc should not proceed due to an error in the last run. Prints a - * message and returns -1 if an error occured while reading gc.log + * message and returns -1 if an error occurred while reading gc.log */ static int report_last_gc_error(void) { @@ -515,11 +515,11 @@ static void gc_before_repack(void) 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 && run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD)) + die(FAILED_RUN, pack_refs_cmd.v[0]); - 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) @@ -553,12 +553,12 @@ int cmd_gc(int argc, const char **argv, const char *prefix) 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(&pack_refs_cmd, "pack-refs", "--all", "--prune", 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(); @@ -577,14 +577,14 @@ int cmd_gc(int argc, const char **argv, const char *prefix) 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) { /* @@ -602,7 +602,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (detach_auto) { int ret = report_last_gc_error(); if (ret < 0) - /* an I/O error occured, already reported */ + /* an I/O error occurred, already reported */ exit(128); if (ret == 1) /* Last gc --auto failed. Skip this one. */ @@ -654,29 +654,29 @@ int cmd_gc(int argc, const char **argv, const char *prefix) 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)) + 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"); - if (repository_format_partial_clone) - 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, "--no-progress"); + if (has_promisor_remote()) + 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); @@ -685,11 +685,11 @@ int cmd_gc(int argc, const char **argv, const char *prefix) clean_pack_garbage(); } - if (gc_write_commit_graph && - write_commit_graph_reachable(get_object_directory(), - !quiet && !daemonized ? COMMIT_GRAPH_PROGRESS : 0, - NULL)) - return 1; + prepare_repo_settings(the_repository); + if (the_repository->settings.gc_write_commit_graph == 1) + write_commit_graph_reachable(the_repository->objects->odb, + !quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0, + NULL); if (auto_gc && too_many_loose_objects()) warning(_("There are too many unreachable loose objects; " @@ -700,3 +700,339 @@ int cmd_gc(int argc, const char **argv, const char *prefix) return 0; } + +static const char * const builtin_maintenance_run_usage[] = { + N_("git maintenance run [--auto] [--[no-]quiet] [--task=<task>]"), + NULL +}; + +struct maintenance_run_opts { + int auto_flag; + int quiet; +}; + +/* Remember to update object flag allocation in object.h */ +#define SEEN (1u<<0) + +struct cg_auto_data { + int num_not_in_graph; + int limit; +}; + +static int dfs_on_ref(const char *refname, + const struct object_id *oid, int flags, + void *cb_data) +{ + struct cg_auto_data *data = (struct cg_auto_data *)cb_data; + int result = 0; + struct object_id peeled; + struct commit_list *stack = NULL; + struct commit *commit; + + if (!peel_ref(refname, &peeled)) + oid = &peeled; + if (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT) + return 0; + + commit = lookup_commit(the_repository, oid); + if (!commit) + return 0; + if (parse_commit(commit)) + return 0; + + commit_list_append(commit, &stack); + + while (!result && stack) { + struct commit_list *parent; + + commit = pop_commit(&stack); + + for (parent = commit->parents; parent; parent = parent->next) { + if (parse_commit(parent->item) || + commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH || + parent->item->object.flags & SEEN) + continue; + + parent->item->object.flags |= SEEN; + data->num_not_in_graph++; + + if (data->num_not_in_graph >= data->limit) { + result = 1; + break; + } + + commit_list_append(parent->item, &stack); + } + } + + free_commit_list(stack); + return result; +} + +static int should_write_commit_graph(void) +{ + int result; + struct cg_auto_data data; + + data.num_not_in_graph = 0; + data.limit = 100; + git_config_get_int("maintenance.commit-graph.auto", + &data.limit); + + if (!data.limit) + return 0; + if (data.limit < 0) + return 1; + + result = for_each_ref(dfs_on_ref, &data); + + clear_commit_marks_all(SEEN); + + return result; +} + +static int run_write_commit_graph(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = 1; + strvec_pushl(&child.args, "commit-graph", "write", + "--split", "--reachable", NULL); + + if (opts->quiet) + strvec_push(&child.args, "--no-progress"); + + return !!run_command(&child); +} + +static int maintenance_task_commit_graph(struct maintenance_run_opts *opts) +{ + close_object_store(the_repository->objects); + if (run_write_commit_graph(opts)) { + error(_("failed to write commit-graph")); + return 1; + } + + return 0; +} + +static int maintenance_task_gc(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = 1; + strvec_push(&child.args, "gc"); + + if (opts->auto_flag) + strvec_push(&child.args, "--auto"); + if (opts->quiet) + strvec_push(&child.args, "--quiet"); + else + strvec_push(&child.args, "--no-quiet"); + + close_object_store(the_repository->objects); + return run_command(&child); +} + +typedef int maintenance_task_fn(struct maintenance_run_opts *opts); + +/* + * An auto condition function returns 1 if the task should run + * and 0 if the task should NOT run. See needs_to_gc() for an + * example. + */ +typedef int maintenance_auto_fn(void); + +struct maintenance_task { + const char *name; + maintenance_task_fn *fn; + maintenance_auto_fn *auto_condition; + unsigned enabled:1; + + /* -1 if not selected. */ + int selected_order; +}; + +enum maintenance_task_label { + TASK_GC, + TASK_COMMIT_GRAPH, + + /* Leave as final value */ + TASK__COUNT +}; + +static struct maintenance_task tasks[] = { + [TASK_GC] = { + "gc", + maintenance_task_gc, + need_to_gc, + 1, + }, + [TASK_COMMIT_GRAPH] = { + "commit-graph", + maintenance_task_commit_graph, + should_write_commit_graph, + }, +}; + +static int compare_tasks_by_selection(const void *a_, const void *b_) +{ + const struct maintenance_task *a, *b; + + a = (const struct maintenance_task *)&a_; + b = (const struct maintenance_task *)&b_; + + return b->selected_order - a->selected_order; +} + +static int maintenance_run_tasks(struct maintenance_run_opts *opts) +{ + int i, found_selected = 0; + int result = 0; + struct lock_file lk; + struct repository *r = the_repository; + char *lock_path = xstrfmt("%s/maintenance", r->objects->odb->path); + + if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) { + /* + * Another maintenance command is running. + * + * If --auto was provided, then it is likely due to a + * recursive process stack. Do not report an error in + * that case. + */ + if (!opts->auto_flag && !opts->quiet) + warning(_("lock file '%s' exists, skipping maintenance"), + lock_path); + free(lock_path); + return 0; + } + free(lock_path); + + for (i = 0; !found_selected && i < TASK__COUNT; i++) + found_selected = tasks[i].selected_order >= 0; + + if (found_selected) + QSORT(tasks, TASK__COUNT, compare_tasks_by_selection); + + for (i = 0; i < TASK__COUNT; i++) { + if (found_selected && tasks[i].selected_order < 0) + continue; + + if (!found_selected && !tasks[i].enabled) + continue; + + if (opts->auto_flag && + (!tasks[i].auto_condition || + !tasks[i].auto_condition())) + continue; + + trace2_region_enter("maintenance", tasks[i].name, r); + if (tasks[i].fn(opts)) { + error(_("task '%s' failed"), tasks[i].name); + result = 1; + } + trace2_region_leave("maintenance", tasks[i].name, r); + } + + rollback_lock_file(&lk); + return result; +} + +static void initialize_task_config(void) +{ + int i; + struct strbuf config_name = STRBUF_INIT; + gc_config(); + + for (i = 0; i < TASK__COUNT; i++) { + int config_value; + + strbuf_setlen(&config_name, 0); + strbuf_addf(&config_name, "maintenance.%s.enabled", + tasks[i].name); + + if (!git_config_get_bool(config_name.buf, &config_value)) + tasks[i].enabled = config_value; + } + + strbuf_release(&config_name); +} + +static int task_option_parse(const struct option *opt, + const char *arg, int unset) +{ + int i, num_selected = 0; + struct maintenance_task *task = NULL; + + BUG_ON_OPT_NEG(unset); + + for (i = 0; i < TASK__COUNT; i++) { + if (tasks[i].selected_order >= 0) + num_selected++; + if (!strcasecmp(tasks[i].name, arg)) { + task = &tasks[i]; + } + } + + if (!task) { + error(_("'%s' is not a valid task"), arg); + return 1; + } + + if (task->selected_order >= 0) { + error(_("task '%s' cannot be selected multiple times"), arg); + return 1; + } + + task->selected_order = num_selected + 1; + + return 0; +} + +static int maintenance_run(int argc, const char **argv, const char *prefix) +{ + int i; + struct maintenance_run_opts opts; + struct option builtin_maintenance_run_options[] = { + OPT_BOOL(0, "auto", &opts.auto_flag, + N_("run tasks based on the state of the repository")), + OPT_BOOL(0, "quiet", &opts.quiet, + N_("do not report progress or other information over stderr")), + OPT_CALLBACK_F(0, "task", NULL, N_("task"), + N_("run a specific task"), + PARSE_OPT_NONEG, task_option_parse), + OPT_END() + }; + memset(&opts, 0, sizeof(opts)); + + opts.quiet = !isatty(2); + initialize_task_config(); + + for (i = 0; i < TASK__COUNT; i++) + tasks[i].selected_order = -1; + + argc = parse_options(argc, argv, prefix, + builtin_maintenance_run_options, + builtin_maintenance_run_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (argc != 0) + usage_with_options(builtin_maintenance_run_usage, + builtin_maintenance_run_options); + return maintenance_run_tasks(&opts); +} + +static const char builtin_maintenance_usage[] = N_("git maintenance run [<options>]"); + +int cmd_maintenance(int argc, const char **argv, const char *prefix) +{ + if (argc < 2 || + (argc == 2 && !strcmp(argv[1], "-h"))) + usage(builtin_maintenance_usage); + + if (!strcmp(argv[1], "run")) + return maintenance_run(argc - 1, argv + 1, prefix); + + die(_("invalid subcommand: %s"), argv[1]); +} diff --git a/third_party/git/builtin/grep.c b/third_party/git/builtin/grep.c index 560051784ef7..c8037388c6e7 100644 --- a/third_party/git/builtin/grep.c +++ b/third_party/git/builtin/grep.c @@ -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 [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"), @@ -32,7 +33,6 @@ static char const * const grep_usage[] = { static int recurse_submodules; -#define GREP_NUM_THREADS_DEFAULT 8 static int num_threads; static pthread_t *threads; @@ -91,8 +91,11 @@ static pthread_cond_t cond_result; 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 +103,6 @@ static void add_work(struct grep_opt *opt, const struct grep_source *gs) } 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); @@ -200,12 +200,12 @@ static void start_threads(struct grep_opt *opt) 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); @@ -257,12 +257,12 @@ static int wait_all(void) 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 +295,36 @@ static int grep_cmd_config(const char *var, const char *value, void *cb) 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,13 +334,7 @@ static int grep_oid(struct grep_opt *opt, const struct object_id *oid, 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_name(opt, filename, tree_name_len, &pathbuf); grep_source_init(&gs, GREP_SOURCE_OID, pathbuf.buf, path, oid); strbuf_release(&pathbuf); @@ -344,11 +360,7 @@ static int grep_file(struct grep_opt *opt, const char *filename) 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_name(opt, filename, 0, &buf); grep_source_init(&gs, GREP_SOURCE_FILE, buf.buf, filename, filename); strbuf_release(&buf); @@ -385,7 +397,7 @@ static void run_pager(struct grep_opt *opt, const char *prefix) 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; @@ -403,34 +415,32 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, static int grep_submodule(struct grep_opt *opt, const struct pathspec *pathspec, const struct object_id *oid, - const char *filename, const char *path) + const char *filename, const char *path, int cached) { struct repository subrepo; struct repository *superproject = opt->repo; - const struct submodule *sub = submodule_from_path(superproject, - &null_oid, path); + const struct submodule *sub; 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(); + sub = submodule_from_path(superproject, &null_oid, path); - 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(); + if (repo_submodule_init(&subrepo, superproject, sub)) return 0; - } - 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 @@ -443,7 +453,7 @@ static int grep_submodule(struct grep_opt *opt, * object. */ add_to_alternates_memory(subrepo.objects->odb->path); - grep_read_unlock(); + obj_read_unlock(); memcpy(&subopt, opt, sizeof(subopt)); subopt.repo = &subrepo; @@ -455,14 +465,12 @@ static int grep_submodule(struct grep_opt *opt, unsigned long size; struct strbuf base = STRBUF_INIT; - object = parse_object_or_die(oid, oid_to_hex(oid)); - - grep_read_lock(); + obj_read_lock(); + object = parse_object_or_die(oid, NULL); + obj_read_unlock(); data = read_object_with_reference(&subrepo, &object->oid, tree_type, &size, NULL); - grep_read_unlock(); - if (!data) die(_("unable to read tree (%s)"), oid_to_hex(&object->oid)); @@ -475,7 +483,7 @@ static int grep_submodule(struct grep_opt *opt, strbuf_release(&base); free(data); } else { - hit = grep_cache(&subopt, pathspec, 1); + hit = grep_cache(&subopt, pathspec, cached); } repo_clear(&subrepo); @@ -523,7 +531,8 @@ static int grep_cache(struct grep_opt *opt, } } else if (recurse_submodules && S_ISGITLINK(ce->ce_mode) && submodule_path_match(repo->index, pathspec, name.buf, NULL)) { - hit |= grep_submodule(opt, pathspec, NULL, ce->name, ce->name); + hit |= grep_submodule(opt, pathspec, NULL, ce->name, + ce->name, cached); } else { continue; } @@ -586,7 +595,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, 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)); @@ -598,7 +607,8 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, free(data); } else if (recurse_submodules && S_ISGITLINK(entry.mode)) { hit |= grep_submodule(opt, pathspec, &entry.oid, - base->buf, base->buf + tn_len); + base->buf, base->buf + tn_len, + 1); /* ignored */ } strbuf_setlen(base, old_baselen); @@ -623,12 +633,9 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, 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)); @@ -657,13 +664,18 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, 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(); /* 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)) { @@ -681,7 +693,7 @@ static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec, struct dir_struct dir; int i, hit = 0; - memset(&dir, 0, sizeof(dir)); + dir_init(&dir); if (!use_index) dir.flags |= DIR_NO_GITLINKS; if (exc_std) @@ -689,12 +701,11 @@ static int grep_directory(struct grep_opt *opt, const struct pathspec *pathspec, 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; } @@ -896,20 +907,20 @@ int cmd_grep(int argc, const char **argv, const char *prefix) 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 <pattern>"), 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 <pattern>"), 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, @@ -956,6 +967,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) /* die the same way as if we did it at the beginning */ setup_git_directory(); } + /* Ignore --recurse-submodules if --no-index is given or implied */ + if (!use_index) + recurse_submodules = 0; /* * skip a -- separator; we know it cannot be @@ -1060,7 +1074,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) 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; @@ -1070,7 +1087,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } 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) @@ -1079,6 +1096,17 @@ int cmd_grep(int argc, const char **argv, const char *prefix) && (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 { /* @@ -1108,14 +1136,11 @@ int cmd_grep(int argc, const char **argv, const char *prefix) strbuf_addf(&buf, "+/%s%s", strcmp("less", pager) ? "" : "*", opt.pattern_list->pattern); - string_list_append(&path_list, buf.buf); - strbuf_detach(&buf, NULL); + string_list_append(&path_list, + strbuf_detach(&buf, NULL)); } } - if (recurse_submodules && (!use_index || untracked)) - die(_("option not supported with --recurse-submodules")); - if (!show_in_pager && !opt.status_only) setup_pager(); @@ -1145,5 +1170,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) run_pager(&opt, prefix); clear_pathspec(&pathspec); free_grep_patterns(&opt); + grep_destroy(); return !hit; } diff --git a/third_party/git/builtin/help.c b/third_party/git/builtin/help.c index e5590d7787c5..bb339f0fc802 100644 --- a/third_party/git/builtin/help.c +++ b/third_party/git/builtin/help.c @@ -8,6 +8,7 @@ #include "parse-options.h" #include "run-command.h" #include "column.h" +#include "config-list.h" #include "help.h" #include "alias.h" @@ -62,6 +63,91 @@ static const char * const builtin_help_usage[] = { 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(int for_human) +{ + struct slot_expansion slot_expansions[] = { + { "advice", "*", list_config_advices }, + { "color.branch", "<slot>", list_config_color_branch_slots }, + { "color.decorate", "<slot>", list_config_color_decorate_slots }, + { "color.diff", "<slot>", list_config_color_diff_slots }, + { "color.grep", "<slot>", list_config_color_grep_slots }, + { "color.interactive", "<slot>", list_config_color_interactive_slots }, + { "color.remote", "<slot>", list_config_color_sideband_slots }, + { "color.status", "<slot>", list_config_color_status_slots }, + { "fsck", "<msg-id>", list_config_fsck_msg_ids }, + { "receive.fsck", "<msg-id>", list_config_fsck_msg_ids }, + { NULL, NULL, NULL } + }; + const char **p; + struct slot_expansion *e; + struct string_list keys = STRING_LIST_INIT_DUP; + 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; + + if (for_human) { + puts(var); + continue; + } + + wildcard = strchr(var, '*'); + tag = strchr(var, '<'); + + if (!wildcard && !tag) { + puts(var); + continue; + } + + if (wildcard && !tag) + cut = wildcard; + else if (!wildcard && tag) + cut = tag; + else + cut = wildcard < tag ? wildcard : tag; + + /* + * We may produce duplicates, but that's up to + * git-completion.bash to handle + */ + printf("%.*s\n", (int)(cut - var), var); + } + string_list_clear(&keys, 0); +} + static enum help_format parse_help_format(const char *format) { if (!strcmp(format, "man")) @@ -242,7 +328,7 @@ static int add_man_viewer_cmd(const char *name, 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; @@ -493,7 +579,7 @@ int cmd_help(int argc, const char **argv, const char *prefix) } if (show_guides) - list_common_guides_help(); + list_guides_help(); if (show_all || show_guides) { printf("%s\n", _(git_more_info_string)); diff --git a/third_party/git/builtin/index-pack.c b/third_party/git/builtin/index-pack.c index 0d55f73b0b44..0d03cb442df3 100644 --- a/third_party/git/builtin/index-pack.c +++ b/third_party/git/builtin/index-pack.c @@ -14,7 +14,7 @@ #include "thread-utils.h" #include "packfile.h" #include "object-store.h" -#include "fetch-object.h" +#include "promisor-remote.h" static const char index_pack_usage[] = "git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])"; @@ -33,19 +33,61 @@ struct object_stat { }; 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; }; @@ -117,10 +159,6 @@ static pthread_mutex_t deepest_delta_mutex; #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,7 +182,6 @@ static void init_thread(void) 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); @@ -167,7 +204,6 @@ static void cleanup_thread(void) 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++) @@ -364,56 +400,42 @@ static void set_thread_data(struct thread_local *data) 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) @@ -614,7 +636,7 @@ static int compare_ofs_delta_bases(off_t offset1, off_t offset2, 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 +646,8 @@ static int find_ofs_delta(const off_t offset, enum object_type type) 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 +660,9 @@ static int find_ofs_delta(const off_t offset, enum object_type type) } 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 +690,7 @@ static int compare_ref_delta_bases(const struct object_id *oid1, 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 +700,8 @@ static int find_ref_delta(const struct object_id *oid, enum object_type type) 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 +714,9 @@ static int find_ref_delta(const struct object_id *oid, enum object_type type) } 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 +779,8 @@ static int check_collison(struct object_entry *entry) 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 +888,15 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, } /* - * 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. - * - * 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. + * Ensure that this node has been reconstructed and return its contents. * - * 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 +913,7 @@ static void *get_base_data(struct base_data *c) 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 +929,7 @@ static void *get_base_data(struct base_data *c) 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 +937,27 @@ static void *get_base_data(struct base_data *c) 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 +970,26 @@ static void resolve_delta(struct object_entry *delta_obj, 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 +1010,135 @@ static int compare_ref_delta_entry(const void *a, const void *b) 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; } @@ -1196,6 +1239,7 @@ static void resolve_deltas(void) 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 +1254,7 @@ static void resolve_deltas(void) 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); } /* @@ -1352,7 +1388,7 @@ static void fix_unresolved_deltas(struct hashfile *f) sorted_by_pos[i] = &ref_deltas[i]; QSORT(sorted_by_pos, nr_ref_deltas, delta_pos_compare); - if (repository_format_partial_clone) { + if (has_promisor_remote()) { /* * Prefetch the delta bases. */ @@ -1365,30 +1401,36 @@ static void fix_unresolved_deltas(struct hashfile *f) continue; oid_array_append(&to_fetch, &d->oid); } - if (to_fetch.nr) - fetch_objects(repository_format_partial_clone, - 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))) 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); @@ -1490,11 +1532,11 @@ static void final(const char *final_pack_name, const char *curr_pack_name, } if (!from_stdin) { - printf("%s\n", sha1_to_hex(hash)); + printf("%s\n", hash_to_hex(hash)); } else { struct strbuf buf = STRBUF_INIT; - strbuf_addf(&buf, "%s\t%s\n", report, sha1_to_hex(hash)); + strbuf_addf(&buf, "%s\t%s\n", report, hash_to_hex(hash)); write_or_die(1, buf.buf, buf.len); strbuf_release(&buf); @@ -1552,13 +1594,9 @@ static void read_v2_anomalous_offsets(struct packed_git *p, { 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 */ + idx1 = (((const uint32_t *)((const uint8_t *)p->index_data + p->crc_offset)) + p->num_objects /* CRC32 table */ ); @@ -1668,6 +1706,7 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) 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 @@ -1761,6 +1800,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) 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 usage(index_pack_usage); continue; @@ -1777,6 +1821,8 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) 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); @@ -1791,9 +1837,22 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) 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); diff --git a/third_party/git/builtin/init-db.c b/third_party/git/builtin/init-db.c index 944ec77fe103..01bc648d416f 100644 --- a/third_party/git/builtin/init-db.c +++ b/third_party/git/builtin/init-db.c @@ -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,6 +21,8 @@ #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; @@ -176,13 +179,34 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree) 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) { struct stat st1; struct strbuf buf = STRBUF_INIT; char *path; - char repo_version_string[10]; char junk[2]; int reinit; int filemode; @@ -233,21 +257,29 @@ static int create_default_files(const char *template_path, 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(); + + 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"); @@ -335,17 +367,40 @@ static void separate_git_dir(const char *git_dir, const char *git_link) 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,12 +411,12 @@ int init_db(const char *git_dir, const char *real_git_dir, 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; @@ -378,9 +433,15 @@ int init_db(const char *git_dir, const char *real_git_dir, * 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); - reinit = create_default_files(template_dir, original_git_dir); + validate_hash_algorithm(&repo_fmt, hash); + + reinit = create_default_files(template_dir, original_git_dir, + initial_branch, &repo_fmt); + if (reinit && initial_branch) + warning(_("re-init: ignored --initial-branch=%s"), + initial_branch); create_object_directory(); @@ -482,6 +543,9 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) 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,11 +558,18 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) 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); @@ -546,6 +617,12 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) 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 +644,30 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) 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/<id>/ 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 +689,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) 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 +700,6 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) 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 --git a/third_party/git/builtin/interpret-trailers.c b/third_party/git/builtin/interpret-trailers.c index f101d092b883..84748eafc01b 100644 --- a/third_party/git/builtin/interpret-trailers.c +++ b/third_party/git/builtin/interpret-trailers.c @@ -105,8 +105,8 @@ int cmd_interpret_trailers(int argc, const char **argv, const char *prefix) 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 --git a/third_party/git/builtin/log.c b/third_party/git/builtin/log.c index 44b10b341541..0a7ed4bef92b 100644 --- a/third_party/git/builtin/log.c +++ b/third_party/git/builtin/log.c @@ -33,10 +33,10 @@ #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 /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -45,6 +45,7 @@ static int default_abbrev_commit; 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; @@ -150,6 +151,7 @@ static void cmd_log_init_defaults(struct rev_info *rev) rev->show_root_diff = default_show_root; rev->subject_prefix = fmt_patch_subject_prefix; rev->show_signature = default_show_signature; + rev->encode_email_headers = default_encode_email_headers; rev->diffopt.flags.allow_textconv = 1; if (default_date_mode) @@ -163,21 +165,24 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, 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_ALIAS(0, "mailmap", "use-mailmap"), OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include, N_("pattern"), N_("only decorate refs that match <pattern>")), OPT_STRING_LIST(0, "decorate-refs-exclude", &decorate_refs_exclude, N_("pattern"), N_("do not decorate refs that match <pattern>")), - { OPTION_CALLBACK, 0, "decorate", NULL, NULL, N_("decorate options"), - PARSE_OPT_OPTARG, decorate_callback}, + OPT_CALLBACK_F(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"), log_line_range_callback), @@ -207,7 +212,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, if (!rev->show_notes_given && (!rev->pretty_given || w.notes)) rev->show_notes = 1; if (rev->show_notes) - init_display_notes(&rev->notes_opt); + load_display_notes(&rev->notes_opt); if ((rev->diffopt.pickaxe_opts & DIFF_PICKAXE_KINDS_MASK) || rev->diffopt.filter || rev->diffopt.flags.follow_renames) @@ -235,7 +240,19 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, } 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); } @@ -437,6 +454,10 @@ static int git_log_config(const char *var, const char *value, void *cb) 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.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; @@ -577,8 +598,8 @@ static int show_tree_object(const struct object_id *oid, 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 */ + if (rev->ignore_merges < 0) { + /* There was no "-m" variant on the command line */ rev->ignore_merges = 0; if (!rev->first_parent_only && !rev->combine_merges) { /* No "--first-parent", "-c", or "--cc" */ @@ -627,6 +648,7 @@ int cmd_show(int argc, const char **argv, const char *prefix) break; case OBJ_TAG: { struct tag *t = (struct tag *)o; + struct object_id *oid = get_tagged_oid(t); if (rev.shown_one) putchar('\n'); @@ -638,10 +660,10 @@ int cmd_show(int argc, const char **argv, const char *prefix) rev.shown_one = 1; if (ret) break; - o = parse_object(the_repository, &t->tagged->oid); + o = parse_object(the_repository, oid); if (!o) ret = error(_("could not read object %s"), - oid_to_hex(&t->tagged->oid)); + oid_to_hex(oid)); objects[i].item = o; i--; break; @@ -709,8 +731,7 @@ static void log_setup_revisions_tweak(struct rev_info *rev, 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) + if (rev->first_parent_only && rev->ignore_merges < 0) rev->ignore_merges = 0; } @@ -764,28 +785,62 @@ static void add_header(const char *value) item->string[len] = '\0'; } -#define THREAD_SHALLOW 1 -#define THREAD_DEEP 2 -static int thread; +enum cover_setting { + COVER_UNSET, + COVER_OFF, + COVER_ON, + COVER_AUTO +}; + +enum thread_level { + THREAD_UNSET, + THREAD_SHALLOW, + THREAD_DEEP +}; + +enum cover_from_description { + COVER_FROM_NONE, + COVER_FROM_MESSAGE, + COVER_FROM_SUBJECT, + 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; -static int config_cover_letter; +static enum cover_setting config_cover_letter; static const char *config_output_directory; +static enum cover_from_description cover_from_description_mode = COVER_FROM_MESSAGE; +static int show_notes; +static struct display_notes_opt notes_opt; -enum { - COVER_UNSET, - COVER_OFF, - COVER_ON, - COVER_AUTO -}; +static enum cover_from_description parse_cover_from_description(const char *arg) +{ + if (!arg || !strcmp(arg, "default")) + return COVER_FROM_MESSAGE; + else if (!strcmp(arg, "none")) + return COVER_FROM_NONE; + else if (!strcmp(arg, "message")) + return COVER_FROM_MESSAGE; + else if (!strcmp(arg, "subject")) + return COVER_FROM_SUBJECT; + else if (!strcmp(arg, "auto")) + return COVER_FROM_AUTO; + else + die(_("%s: invalid cover from description mode"), arg); +} static int git_format_config(const char *var, const char *value, void *cb) { - struct rev_info *rev = cb; - if (!strcmp(var, "format.headers")) { if (!value) die(_("format.headers without value")); @@ -835,7 +890,7 @@ static int git_format_config(const char *var, const char *value, void *cb) thread = THREAD_SHALLOW; return 0; } - thread = git_config_bool(var, value) && THREAD_SHALLOW; + thread = git_config_bool(var, value) ? THREAD_SHALLOW : THREAD_UNSET; return 0; } if (!strcmp(var, "format.signoff")) { @@ -857,7 +912,11 @@ static int git_format_config(const char *var, const char *value, void *cb) 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")) { @@ -872,19 +931,17 @@ static int git_format_config(const char *var, const char *value, void *cb) return 0; } if (!strcmp(var, "format.notes")) { - struct strbuf buf = STRBUF_INIT; int b = git_parse_maybe_bool(value); - if (!b) - return 0; - rev->show_notes = 1; - if (b < 0) { - strbuf_addstr(&buf, value); - expand_notes_ref(&buf); - string_list_append(&rev->notes_opt.extra_notes_refs, - strbuf_detach(&buf, NULL)); - } else { - rev->notes_opt.use_default_notes = 1; - } + if (b < 0) + enable_ref_display_notes(¬es_opt, &show_notes, value); + else if (b) + enable_default_display_notes(¬es_opt, &show_notes); + else + disable_display_notes(¬es_opt, &show_notes); + return 0; + } + if (!strcmp(var, "format.coverfromdescription")) { + cover_from_description_mode = parse_cover_from_description(value); return 0; } @@ -993,20 +1050,6 @@ static void print_signature(FILE *file) putc('\n', file); } -static void add_branch_description(struct strbuf *buf, const char *branch_name) -{ - struct strbuf desc = STRBUF_INIT; - if (!branch_name || !*branch_name) - return; - read_branch_desc(&desc, branch_name); - if (desc.len) { - strbuf_addch(buf, '\n'); - strbuf_addbuf(buf, &desc); - strbuf_addch(buf, '\n'); - } - strbuf_release(&desc); -} - static char *find_branch_name(struct rev_info *rev) { int i, positive = -1; @@ -1027,7 +1070,7 @@ static char *find_branch_name(struct rev_info *rev) 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); @@ -1053,6 +1096,63 @@ static void show_diffstat(struct rev_info *rev, fprintf(rev->diffopt.file, "\n"); } +static void prepare_cover_text(struct pretty_print_context *pp, + const char *branch_name, + struct strbuf *sb, + const char *encoding, + int need_8bit_cte) +{ + const char *subject = "*** SUBJECT HERE ***"; + const char *body = "*** BLURB HERE ***"; + struct strbuf description_sb = STRBUF_INIT; + struct strbuf subject_sb = STRBUF_INIT; + + if (cover_from_description_mode == COVER_FROM_NONE) + goto do_pp; + + if (branch_name && *branch_name) + read_branch_desc(&description_sb, branch_name); + if (!description_sb.len) + goto do_pp; + + if (cover_from_description_mode == COVER_FROM_SUBJECT || + cover_from_description_mode == COVER_FROM_AUTO) + body = format_subject(&subject_sb, description_sb.buf, " "); + + if (cover_from_description_mode == COVER_FROM_MESSAGE || + (cover_from_description_mode == COVER_FROM_AUTO && + subject_sb.len > COVER_FROM_AUTO_MAX_SUBJECT_LEN)) + body = description_sb.buf; + else + subject = subject_sb.buf; + +do_pp: + pp_title_line(pp, &subject, sb, encoding, need_8bit_cte); + pp_remainder(pp, &body, sb, 0); + + strbuf_release(&description_sb); + strbuf_release(&subject_sb); +} + +static int get_notes_refs(struct string_list_item *item, void *arg) +{ + strvec_pushf(arg, "--notes=%s", item->string); + return 0; +} + +static void get_notes_args(struct strvec *arg, struct rev_info *rev) +{ + if (!rev->show_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)) { + 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, struct commit *origin, int nr, struct commit **list, @@ -1060,8 +1160,6 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, int quiet) { const char *committer; - const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n"; - const char *msg; struct shortlog log; struct strbuf sb = STRBUF_INIT; int i; @@ -1091,15 +1189,12 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, if (!branch_name) branch_name = find_branch_name(rev); - msg = body; pp.fmt = CMIT_FMT_EMAIL; pp.date_mode.type = DATE_RFC2822; pp.rev = rev; pp.print_email_subject = 1; pp_user_info(&pp, NULL, &sb, committer, encoding); - pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte); - pp_remainder(&pp, &msg, &sb, 0); - add_branch_description(&sb, branch_name); + prepare_cover_text(&pp, branch_name, &sb, encoding, need_8bit_cte); fprintf(rev->diffopt.file, "%s\n", sb.buf); strbuf_release(&sb); @@ -1110,6 +1205,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, 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]); @@ -1121,7 +1217,8 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, 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) { @@ -1130,13 +1227,16 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, * can be added later if deemed desirable. */ struct diff_options opts; + struct strvec other_arg = STRVEC_INIT; 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); + rev->creation_factor, 1, &opts, &other_arg); + strvec_clear(&other_arg); } } @@ -1248,9 +1348,9 @@ static int output_directory_callback(const struct option *opt, const char *arg, static int thread_callback(const struct option *opt, const char *arg, int unset) { - int *thread = (int *)opt->value; + enum thread_level *thread = (enum thread_level *)opt->value; if (unset) - *thread = 0; + *thread = THREAD_UNSET; else if (!arg || !strcmp(arg, "shallow")) *thread = THREAD_SHALLOW; else if (!strcmp(arg, "deep")) @@ -1297,7 +1397,7 @@ static int header_callback(const struct option *opt, const char *arg, int unset) string_list_clear(&extra_to, 0); string_list_clear(&extra_cc, 0); } else { - add_header(arg); + add_header(arg); } return 0; } @@ -1335,6 +1435,23 @@ static int from_callback(const struct option *opt, const char *arg, int unset) 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; @@ -1347,13 +1464,36 @@ static struct commit *get_base_commit(const char *base_commit, { 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")) || base_auto) { + } else { struct branch *curr_branch = branch_get(NULL); const char *upstream = branch_get_upstream(curr_branch, NULL); if (upstream) { @@ -1361,19 +1501,32 @@ static struct commit *get_base_commit(const char *base_commit, 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=<base-commit-id> 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=<base-commit-id> manually")); + else + return NULL; } } @@ -1390,8 +1543,14 @@ static struct commit *get_base_commit(const char *base_commit, 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; } @@ -1401,12 +1560,24 @@ static struct commit *get_base_commit(const char *base_commit, 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); @@ -1506,16 +1677,20 @@ static void infer_range_diff_ranges(struct strbuf *r1, struct commit *head) { const char *head_oid = oid_to_hex(&head->object.oid); + int prev_is_range = !!strstr(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); } } @@ -1541,9 +1716,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int use_patch_format = 0; int quiet = 0; int reroll_count = -1; + 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; @@ -1555,12 +1732,12 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) 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 }, + PARSE_OPT_NOARG | PARSE_OPT_NONEG, no_numbered_callback), OPT_BOOL('s', "signoff", &do_signoff, N_("add Signed-off-by:")), OPT_BOOL(0, "stdout", &use_stdout, N_("print patches to standard out")), @@ -1574,18 +1751,21 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("start numbering patches at <n> instead of 1")), OPT_INTEGER('v', "reroll-count", &reroll_count, N_("mark the series as Nth re-roll")), - { OPTION_CALLBACK, 0, "rfc", &rev, NULL, + OPT_CALLBACK_F(0, "rfc", &rev, NULL, N_("Use [RFC PATCH] instead of [PATCH]"), - PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback }, - { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"), + 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")), + OPT_CALLBACK_F(0, "subject-prefix", &rev, N_("prefix"), N_("Use [<prefix>] instead of [PATCH]"), - PARSE_OPT_NONEG, subject_prefix_callback }, - { OPTION_CALLBACK, 'o', "output-directory", &output_directory, + PARSE_OPT_NONEG, subject_prefix_callback), + OPT_CALLBACK_F('o', "output-directory", &output_directory, N_("dir"), N_("store resulting files in <dir>"), - 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, @@ -1596,31 +1776,30 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) 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 <ident> (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 <message-id>")), - { 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")), @@ -1640,9 +1819,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) extra_to.strdup_strings = 1; extra_cc.strdup_strings = 1; init_log_defaults(); + init_display_notes(¬es_opt); + git_config(git_format_config, NULL); repo_init_revisions(the_repository, &rev, prefix); - git_config(git_format_config, &rev); + 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; @@ -1668,6 +1851,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); + if (cover_from_description_arg) + cover_from_description_mode = parse_cover_from_description(cover_from_description_arg); + if (0 < reroll_count) { struct strbuf sprefix = STRBUF_INIT; strbuf_addf(&sprefix, "%s v%d", @@ -1754,7 +1940,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.diffopt.flags.binary = 1; if (rev.show_notes) - init_display_notes(&rev.notes_opt); + load_display_notes(&rev.notes_opt); if (!output_directory && !use_stdout) output_directory = config_output_directory; @@ -1765,10 +1951,26 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) setup_pager(); if (output_directory) { + int saved; 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 <outdir> as 'outside of gitdir', therefore avoid + * applying adjust_shared_perm in s-c-l-d. + */ + saved = get_shared_repository(); + set_shared_repository(0); + switch (safe_create_leading_directories_const(output_directory)) { + case SCLD_OK: + case SCLD_EXISTS: + break; + default: + die(_("could not create leading directories " + "of '%s'"), output_directory); + } + set_shared_repository(saved); if (mkdir(output_directory, 0777) < 0 && errno != EEXIST) die_errno(_("could not create directory '%s'"), output_directory); @@ -1897,8 +2099,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } memset(&bases, 0, sizeof(bases)); - if (base_commit || base_auto) { - 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); diff --git a/third_party/git/builtin/ls-files.c b/third_party/git/builtin/ls-files.c index 670e8fb93c93..c8eae899b82a 100644 --- a/third_party/git/builtin/ls-files.c +++ b/third_party/git/builtin/ls-files.c @@ -128,8 +128,9 @@ static void show_dir_entry(const struct index_state *istate, 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); @@ -492,7 +493,7 @@ static int option_parse_exclude_from(const struct option *opt, BUG_ON_OPT_NEG(unset); exc_given = 1; - add_excludes_from_file(dir, arg); + add_patterns_from_file(dir, arg); return 0; } @@ -516,7 +517,7 @@ 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; - struct exclude_list *el; + 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 */ @@ -554,18 +555,18 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) 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"), + PARSE_OPT_NONEG, option_parse_exclude), + OPT_CALLBACK_F('X', "exclude-from", &dir, N_("file"), N_("exclude patterns are read from <file>"), - PARSE_OPT_NONEG, option_parse_exclude_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 <file>")), - { 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), @@ -583,7 +584,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(ls_files_usage, builtin_ls_files_options); - memset(&dir, 0, sizeof(dir)); + dir_init(&dir); prefix = cmd_prefix; if (prefix) prefix_len = strlen(prefix); @@ -594,9 +595,9 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) argc = parse_options(argc, argv, prefix, builtin_ls_files_options, ls_files_usage, 0); - el = add_exclude_list(&dir, EXC_CMDL, "--exclude option"); + pl = add_pattern_list(&dir, EXC_CMDL, "--exclude option"); for (i = 0; i < exclude_list.nr; i++) { - add_exclude(exclude_list.items[i].string, "", 0, el, --exclude_args); + add_pattern(exclude_list.items[i].string, "", 0, pl, --exclude_args); } if (show_tag || show_valid_bit || show_fsmonitor_bit) { tag_cached = "H "; @@ -687,6 +688,6 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) return bad ? 1 : 0; } - UNLEAK(dir); + dir_clear(&dir); return 0; } diff --git a/third_party/git/builtin/ls-remote.c b/third_party/git/builtin/ls-remote.c index 6ef519514bd1..092917eca29b 100644 --- a/third_party/git/builtin/ls-remote.c +++ b/third_party/git/builtin/ls-remote.c @@ -45,7 +45,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) int show_symref_target = 0; const char *uploadpack = NULL; const char **pattern = NULL; - struct argv_array ref_prefixes = ARGV_ARRAY_INIT; + struct strvec ref_prefixes = STRVEC_INIT; int i; struct string_list server_options = STRING_LIST_INIT_DUP; @@ -83,6 +83,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) PARSE_OPT_STOP_AT_NON_OPTION); dest = argv[0]; + UNLEAK(sorting); + if (argc > 1) { int i; pattern = xcalloc(argc, sizeof(const char *)); @@ -92,9 +94,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) } if (flags & REF_TAGS) - argv_array_push(&ref_prefixes, "refs/tags/"); + strvec_push(&ref_prefixes, "refs/tags/"); if (flags & REF_HEADS) - argv_array_push(&ref_prefixes, "refs/heads/"); + strvec_push(&ref_prefixes, "refs/heads/"); remote = remote_get(dest); if (!remote) { @@ -107,7 +109,6 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) if (get_url) { printf("%s\n", *remote->url); - UNLEAK(sorting); return 0; } @@ -118,10 +119,12 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) transport->server_options = &server_options; ref = transport_get_remote_refs(transport, &ref_prefixes); - if (transport_disconnect(transport)) { - UNLEAK(sorting); - return 1; + if (ref) { + int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); + repo_set_hash_algo(the_repository, hash_algo); } + if (transport_disconnect(transport)) + return 1; if (!dest && !quiet) fprintf(stderr, "From %s\n", *remote->url); @@ -146,7 +149,6 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) status = 0; /* we found something */ } - UNLEAK(sorting); ref_array_clear(&ref_array); return status; } diff --git a/third_party/git/builtin/merge-base.c b/third_party/git/builtin/merge-base.c index e3f8da13b69b..6719ac198dc2 100644 --- a/third_party/git/builtin/merge-base.c +++ b/third_party/git/builtin/merge-base.c @@ -114,26 +114,16 @@ static int handle_is_ancestor(int argc, const char **argv) 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 --git a/third_party/git/builtin/merge-recursive.c b/third_party/git/builtin/merge-recursive.c index 5b910e351e4d..a4bfd8fc51d6 100644 --- a/third_party/git/builtin/merge-recursive.c +++ b/third_party/git/builtin/merge-recursive.c @@ -1,3 +1,4 @@ +#include "cache.h" #include "builtin.h" #include "commit.h" #include "tag.h" @@ -63,6 +64,9 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) if (argc - i != 3) /* "--" "<head>" "<remote>" */ die(_("not handling anything other than two heads merge.")); + if (repo_read_index_unmerged(the_repository)) + die_resolve_conflict("merge"); + o.branch1 = argv[++i]; o.branch2 = argv[++i]; diff --git a/third_party/git/builtin/merge-tree.c b/third_party/git/builtin/merge-tree.c index 97b54caeb900..e72714a5a87d 100644 --- a/third_party/git/builtin/merge-tree.c +++ b/third_party/git/builtin/merge-tree.c @@ -180,8 +180,9 @@ static struct merge_list *create_entry(unsigned stage, unsigned mode, const stru static char *traverse_path(const struct traverse_info *info, const struct name_entry *n) { - char *path = xmallocz(traverse_path_len(info, n) + the_hash_algo->rawsz); - return make_traverse_path(path, info, n); + struct strbuf buf = STRBUF_INIT; + strbuf_make_traverse_path(&buf, info, n->path, n->pathlen); + return strbuf_detach(&buf, NULL); } static void resolve(const struct traverse_info *info, struct name_entry *ours, struct name_entry *result) diff --git a/third_party/git/builtin/merge.c b/third_party/git/builtin/merge.c index e2ccbc44e204..9d5359edc2f7 100644 --- a/third_party/git/builtin/merge.c +++ b/third_party/git/builtin/merge.c @@ -40,6 +40,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) @@ -62,6 +63,7 @@ static int show_diffstat = 1, shortlog_len = -1, squash; 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 +72,6 @@ static const char **xopts; 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,7 +82,8 @@ static int show_progress = -1; static int default_to_upstream = 1; static int signoff; static const char *sign_commit; -static int verify_msg = 1; +static int autostash; +static int no_verify; static struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, @@ -240,9 +242,9 @@ static int option_parse_n(const struct option *opt, } 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,9 +287,10 @@ static struct option builtin_merge_options[] = { 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, "verify", &verify_msg, N_("verify commit-msg hook")), + OPT_BOOL(0, "no-verify", &no_verify, N_("bypass pre-merge-commit and commit-msg hooks")), OPT_END() }; @@ -446,7 +449,6 @@ static void finish(struct commit *head_commit, 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); /* @@ -454,7 +456,7 @@ static void finish(struct commit *head_commit, * 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 +476,7 @@ static void finish(struct commit *head_commit, /* 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); } @@ -497,7 +500,7 @@ static void merge_name(const char *remote, struct strbuf *msg) 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); @@ -596,10 +599,12 @@ static void parse_branch_merge_options(char *bmo) 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 +620,6 @@ static int git_merge_config(const char *k, const char *v, void *cb) 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 +634,11 @@ static int git_merge_config(const char *k, const char *v, void *cb) } 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); @@ -688,16 +696,13 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, struct commit_list *remoteheads, struct commit *head) { - struct lock_file lock = LOCK_INIT; const char *head_arg = "HEAD"; - hold_locked_index(&lock, LOCK_DIE_ON_ERROR); - refresh_cache(REFRESH_QUIET); - if (write_locked_index(&the_index, &lock, - COMMIT_LOCK | SKIP_IF_UNCHANGED)) + 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")) { + struct lock_file lock = LOCK_INIT; int clean, x; struct commit *result; struct commit_list *reversed = NULL; @@ -713,7 +718,6 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, if (!strcmp(strategy, "subtree")) o.subtree_shift = ""; - o.renormalize = option_renormalize; o.show_rename_progress = show_progress == -1 ? isatty(2) : show_progress; @@ -816,6 +820,18 @@ static void write_merge_heads(struct commit_list *); static void prepare_to_commit(struct commit_list *remoteheads) { struct strbuf msg = STRBUF_INIT; + const char *index_file = get_index_file(); + + if (!no_verify && run_commit_hook(0 < option_edit, index_file, "pre-merge-commit", NULL)) + abort_commit(remoteheads, NULL); + /* + * Re-read the index as pre-merge-commit hook could have updated it, + * 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")) + discard_cache(); + read_cache_from(index_file); strbuf_addbuf(&msg, &merge_msg); if (squash) BUG("the control must not reach here under --squash"); @@ -842,7 +858,7 @@ static void prepare_to_commit(struct commit_list *remoteheads) abort_commit(remoteheads, NULL); } - if (verify_msg && run_commit_hook(0 < option_edit, get_index_file(), + if (!no_verify && run_commit_hook(0 < option_edit, get_index_file(), "commit-msg", git_path_merge_msg(the_repository), NULL)) abort_commit(remoteheads, NULL); @@ -860,12 +876,8 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads) { struct object_id result_tree, result_commit; struct commit_list *parents, **pptr = &parents; - struct lock_file lock = LOCK_INIT; - hold_locked_index(&lock, LOCK_DIE_ON_ERROR); - refresh_cache(REFRESH_QUIET); - if (write_locked_index(&the_index, &lock, - COMMIT_LOCK | SKIP_IF_UNCHANGED)) + if (refresh_and_write_cache(REFRESH_QUIET, SKIP_IF_UNCHANGED, 0) < 0) return error(_("Unable to write index.")); write_tree_trivial(&result_tree); @@ -1273,6 +1285,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) 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"), @@ -1281,8 +1294,17 @@ int cmd_merge(int argc, const char **argv, const char *prefix) 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; } @@ -1326,7 +1348,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) else die(_("You have not concluded your merge (MERGE_HEAD exists).")); } - if (file_exists(git_path_cherry_pick_head(the_repository))) { + if (ref_exists("CHERRY_PICK_HEAD")) { if (advice_resolve_conflict) die(_("You have not concluded your cherry-pick (CHERRY_PICK_HEAD exists).\n" "Please, commit your changes before you merge.")); @@ -1392,7 +1414,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) 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); @@ -1415,7 +1438,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) 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); } } @@ -1503,6 +1527,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix) 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, @@ -1569,6 +1597,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (fast_forward == FF_ONLY) die(_("Not possible to fast-forward, aborting.")); + 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); @@ -1619,7 +1652,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) } 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; @@ -1663,9 +1696,11 @@ int cmd_merge(int argc, const char **argv, const char *prefix) 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) diff --git a/third_party/git/builtin/mktag.c b/third_party/git/builtin/mktag.c index 6fb7dc8578d6..4982d3a93efb 100644 --- a/third_party/git/builtin/mktag.c +++ b/third_party/git/builtin/mktag.c @@ -29,8 +29,11 @@ static int verify_object(const struct object_id *oid, const char *expected_type) 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); + if (type == type_from_string(expected_type)) { + ret = check_object_signature(the_repository, repl, + buffer, size, + expected_type); + } free(buffer); } return ret; diff --git a/third_party/git/builtin/multi-pack-index.c b/third_party/git/builtin/multi-pack-index.c index b1ea1a6aa177..5bf88cd2a8e2 100644 --- a/third_party/git/builtin/multi-pack-index.c +++ b/third_party/git/builtin/multi-pack-index.c @@ -6,21 +6,25 @@ #include "trace2.h" static char const * const builtin_multi_pack_index_usage[] = { - N_("git multi-pack-index [--object-dir=<dir>] (write|verify|expire|repack --batch-size=<size>)"), + N_("git multi-pack-index [<options>] (write|verify|expire|repack --batch-size=<size>)"), NULL }; static struct opts_multi_pack_index { const char *object_dir; unsigned long batch_size; + int progress; } opts; int cmd_multi_pack_index(int argc, const char **argv, const char *prefix) { + unsigned flags = 0; + 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")), 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_END(), @@ -28,12 +32,15 @@ int cmd_multi_pack_index(int argc, const char **argv, 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); 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, @@ -47,16 +54,17 @@ int cmd_multi_pack_index(int argc, const char **argv, trace2_cmd_mode(argv[0]); if (!strcmp(argv[0], "repack")) - return midx_repack(the_repository, opts.object_dir, (size_t)opts.batch_size); + 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); + return write_midx_file(opts.object_dir, flags); if (!strcmp(argv[0], "verify")) - return verify_midx_file(the_repository, opts.object_dir); + return verify_midx_file(the_repository, opts.object_dir, flags); if (!strcmp(argv[0], "expire")) - return expire_midx_packs(the_repository, opts.object_dir); + return expire_midx_packs(the_repository, opts.object_dir, flags); die(_("unrecognized subcommand: %s"), argv[0]); } diff --git a/third_party/git/builtin/mv.c b/third_party/git/builtin/mv.c index be15ba7044e0..7dac714af908 100644 --- a/third_party/git/builtin/mv.c +++ b/third_party/git/builtin/mv.c @@ -132,6 +132,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix) struct stat st; struct string_list src_for_dst = STRING_LIST_INIT_NODUP; struct lock_file lock_file = LOCK_INIT; + struct cache_entry *ce; git_config(git_default_config, NULL); @@ -220,9 +221,11 @@ int cmd_mv(int argc, const char **argv, const char *prefix) } argc += last - first; } - } else if (cache_name_pos(src, length) < 0) + } else if (!(ce = cache_file_exists(src, length, ignore_case))) { 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) { diff --git a/third_party/git/builtin/name-rev.c b/third_party/git/builtin/name-rev.c index c785fe16bade..725dd0451913 100644 --- a/third_party/git/builtin/name-rev.c +++ b/third_party/git/builtin/name-rev.c @@ -6,20 +6,25 @@ #include "tag.h" #include "refs.h" #include "parse-options.h" +#include "prio-queue.h" #include "sha1-lookup.h" #include "commit-slab.h" -#define CUTOFF_DATE_SLOP 86400 /* one day */ +/* + * One day. See the 'name a rev shortly after epoch' test in t6120 when + * changing this value + */ +#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; @@ -27,16 +32,16 @@ static struct commit_rev_name rev_names; /* 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, @@ -75,68 +80,135 @@ static int is_better_name(struct rev_name *name, return 0; } -static void name_rev(struct commit *commit, - const char *tip_name, timestamp_t taggerdate, - int generation, int distance, int from_tag, - int deref) +static struct rev_name *create_or_update_name(struct commit *commit, + timestamp_t taggerdate, + int generation, int distance, + int from_tag) { - struct rev_name *name = get_commit_rev_name(commit); - struct commit_list *parents; - int parent_number = 1; - char *to_free = NULL; - - parse_commit(commit); + struct rev_name *name = commit_rev_name_at(&rev_names, commit); + + 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); + } - if (commit->date < cutoff) - return; + name->taggerdate = taggerdate; + name->generation = generation; + name->distance = distance; + name->from_tag = from_tag; - if (deref) { - tip_name = to_free = xstrfmt("%s^0", tip_name); + return name; +} - if (generation) - die("generation: %d, but deref?", generation); +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); +} - if (name == NULL) { - name = xmalloc(sizeof(rev_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; - } else { - free(to_free); +static void name_rev(struct commit *start_commit, + const char *tip_name, timestamp_t taggerdate, + int from_tag, int deref) +{ + struct prio_queue queue; + struct commit *commit; + struct commit **parents_to_queue = NULL; + size_t parents_to_queue_nr, parents_to_queue_alloc = 0; + struct rev_name *start_name; + + parse_commit(start_commit); + if (start_commit->date < cutoff) return; - } - for (parents = commit->parents; - parents; - parents = parents->next, parent_number++) { - if (parent_number > 1) { - size_t len; - char *new_name; - - strip_suffix(tip_name, "^0", &len); - if (generation > 0) - new_name = xstrfmt("%.*s~%d^%d", (int)len, tip_name, - generation, parent_number); - else - new_name = xstrfmt("%.*s^%d", (int)len, tip_name, - parent_number); - - name_rev(parents->item, new_name, taggerdate, 0, - distance + MERGE_TRAVERSAL_WEIGHT, - from_tag, 0); - } else { - name_rev(parents->item, tip_name, taggerdate, - generation + 1, distance + 1, - from_tag, 0); + 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); + + while ((commit = prio_queue_get(&queue))) { + struct rev_name *name = get_commit_rev_name(commit); + struct commit_list *parents; + int parent_number = 1; + + parents_to_queue_nr = 0; + + for (parents = commit->parents; + parents; + parents = parents->next, parent_number++) { + struct commit *parent = parents->item; + struct rev_name *parent_name; + int generation, distance; + + parse_commit(parent); + if (parent->date < cutoff) + continue; + + if (parent_number > 1) { + generation = 0; + distance = name->distance + MERGE_TRAVERSAL_WEIGHT; + } else { + generation = name->generation + 1; + distance = name->distance + 1; + } + + 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); + parents_to_queue[parents_to_queue_nr] = parent; + parents_to_queue_nr++; + } } + + /* The first parent must come out first from the prio_queue */ + while (parents_to_queue_nr) + prio_queue_put(&queue, + parents_to_queue[--parents_to_queue_nr]); } + + clear_prio_queue(&queue); + free(parents_to_queue); } static int subpath_matches(const char *path, const char *filter) @@ -157,10 +229,10 @@ static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous) { if (shorten_unambiguous) refname = shorten_unambiguous_ref(refname, 0); - else if (starts_with(refname, "refs/heads/")) - refname = refname + 11; - else if (starts_with(refname, "refs/")) - refname = refname + 5; + else if (skip_prefix(refname, "refs/heads/", &refname)) + ; /* refname already advanced */ + else + skip_prefix(refname, "refs/", &refname); return refname; } @@ -175,6 +247,10 @@ static struct tip_table { 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; @@ -182,13 +258,18 @@ static struct tip_table { } 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; } @@ -199,12 +280,30 @@ static int tipcmp(const void *a_, const void *b_) 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/")) @@ -253,8 +352,6 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo 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) @@ -264,18 +361,35 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo 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 = ((struct commit *)o)->date; - path = name_ref_abbrev(path, can_abbreviate_output); - name_rev(commit, xstrdup(path), taggerdate, 0, 0, - from_tag, deref); + taggerdate = commit->date; } + + add_to_tip_table(oid, path, can_abbreviate_output, commit, taggerdate, + from_tag, deref); return 0; } +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 unsigned char *nth_tip_table_ent(size_t ix, void *table_) { struct tip_table_entry *table = table_; @@ -305,11 +419,11 @@ static const char *get_exact_ref_match(const struct object *o) 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; @@ -317,11 +431,10 @@ static const char *get_rev_name(const struct object *o, struct strbuf *buf) if (!n->generation) return n->tip_name; else { - int len = strlen(n->tip_name); - if (len > 2 && !strcmp(n->tip_name + len - 2, "^0")) - len -= 2; strbuf_reset(buf); - strbuf_addf(buf, "%.*s~%d", len, n->tip_name, n->generation); + strbuf_addstr(buf, n->tip_name); + strbuf_strip_suffix(buf, "^0"); + strbuf_addf(buf, "~%d", n->generation); return buf->buf; } } @@ -408,7 +521,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) 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 <pattern>")), @@ -481,9 +594,15 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) add_object_array(object, *argv, &revs); } - if (cutoff) - cutoff = cutoff - CUTOFF_DATE_SLOP; + if (cutoff) { + /* check for undeflow */ + if (cutoff > TIME_MIN + CUTOFF_DATE_SLOP) + cutoff = cutoff - CUTOFF_DATE_SLOP; + else + cutoff = TIME_MIN; + } for_each_ref(name_ref, &data); + name_tips(); if (transform_stdin) { char buffer[2048]; diff --git a/third_party/git/builtin/notes.c b/third_party/git/builtin/notes.c index 02e97f55c5a0..2987c08a2e92 100644 --- a/third_party/git/builtin/notes.c +++ b/third_party/git/builtin/notes.c @@ -406,18 +406,18 @@ static int add(int argc, const char **argv, const char *prefix) 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), @@ -513,7 +513,7 @@ static int copy(int argc, const char **argv, const char *prefix) } } - if (argc < 2) { + if (argc < 1) { error(_("too few parameters")); usage_with_options(git_notes_copy_usage, options); } @@ -572,18 +572,18 @@ static int append_edit(int argc, const char **argv, const char *prefix) 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() @@ -622,7 +622,7 @@ static int append_edit(int argc, const char **argv, const char *prefix) 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); @@ -745,7 +745,7 @@ static int merge_commit(struct notes_merge_options *o) 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); diff --git a/third_party/git/builtin/pack-objects.c b/third_party/git/builtin/pack-objects.c index 76ce9069467e..5617c01b5aae 100644 --- a/third_party/git/builtin/pack-objects.c +++ b/third_party/git/builtin/pack-objects.c @@ -26,14 +26,16 @@ #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" #define IN_PACK(obj) oe_in_pack(&to_pack, obj) #define SIZE(obj) oe_size(&to_pack, obj) @@ -92,10 +94,11 @@ static struct progress *progress_state; 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 +118,8 @@ static unsigned long window_memory_limit = 0; 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 +128,15 @@ enum missing_action { 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 */ @@ -163,7 +177,7 @@ static void *get_delta(struct object_entry *entry) delta_buf = diff_delta(base_buf, base_size, buf, size, &delta_size, 0); /* - * We succesfully computed this delta once but dropped it for + * We successfully computed this delta once but dropped it for * memory reasons. Something is very wrong if this time we * recompute and create a different delta. */ @@ -303,7 +317,8 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent 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); @@ -610,12 +625,12 @@ static int mark_tagged(const char *path, const struct object_id *oid, int flag, void *cb_data) { struct object_id peeled; - struct object_entry *entry = packlist_find(&to_pack, oid, NULL); + struct object_entry *entry = packlist_find(&to_pack, oid); if (entry) entry->tagged = 1; if (!peel_ref(path, &peeled)) { - entry = packlist_find(&to_pack, &peeled, NULL); + entry = packlist_find(&to_pack, &peeled); if (entry) entry->tagged = 1; } @@ -784,57 +799,205 @@ static struct object_entry **compute_write_order(void) 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 offset of the first object of this chunk in the generated + * packfile minus "original". */ + 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) { - unsigned char buffer[8192]; - off_t to_write, total; - int fd; + if (reused_chunks_nr && reused_chunks[reused_chunks_nr-1].difference == offset) + return; - if (!is_pack_valid(reuse_packfile)) - die(_("packfile is invalid: %s"), reuse_packfile->pack_name); + 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++; +} - fd = git_open(reuse_packfile->pack_name); - if (fd < 0) - die_errno(_("unable to open packfile for reuse: %s"), - reuse_packfile->pack_name); +/* + * 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; +} - if (lseek(fd, sizeof(struct pack_header), SEEK_SET) == -1) - die_errno(_("unable to seek in reused packfile")); +static void write_reused_pack_one(size_t pos, struct hashfile *out, + struct pack_window **w_curs) +{ + off_t offset, next, cur; + enum object_type type; + unsigned long size; - if (reuse_packfile_offset < 0) - reuse_packfile_offset = reuse_packfile->pack_size - the_hash_algo->rawsz; + offset = reuse_packfile->revindex[pos].offset; + next = reuse_packfile->revindex[pos + 1].offset; - total = to_write = reuse_packfile_offset - sizeof(struct pack_header); + record_reused_object(offset, offset - hashfile_total(out)); - while (to_write) { - int read_pack = xread(fd, buffer, sizeof(buffer)); + cur = offset; + type = unpack_object_header(reuse_packfile, w_curs, &cur, &size); + assert(type >= 0); - if (read_pack <= 0) - die_errno(_("unable to read from reused packfile")); + if (type == OBJ_OFS_DELTA) { + off_t base_offset; + off_t fixup; - if (read_pack > to_write) - read_pack = to_write; + unsigned char header[MAX_PACK_OBJECT_HEADER]; + unsigned len; - hashwrite(f, buffer, read_pack); - to_write -= read_pack; + base_offset = get_delta_base(reuse_packfile, w_curs, &cur, type, offset); + assert(base_offset != 0); + + /* Convert to REF_DELTA if we must... */ + if (!allow_ofs_delta) { + int base_pos = find_revindex_position(reuse_packfile, base_offset); + struct object_id base_oid; + + nth_packed_object_id(&base_oid, reuse_packfile, + reuse_packfile->revindex[base_pos].nr); + + 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; + } + + /* 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; + } + + /* ...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 = reuse_packfile->revindex[written].offset + - 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; - close(fd); - written = reuse_packfile_objects; - display_progress(progress_state, written); - return reuse_packfile_offset - sizeof(struct pack_header); + 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); + write_reused_pack_one(pos + offset, f, &w_curs); + display_progress(progress_state, ++written); + } + } + + 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_( @@ -867,11 +1030,9 @@ static void write_pack_file(void) 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; @@ -996,12 +1157,15 @@ static int no_try_delta(const char *path) * few lines later when we want to add the new entry. */ static int have_duplicate_entry(const struct object_id *oid, - int exclude, - uint32_t *index_pos) + int exclude) { struct object_entry *entry; - entry = packlist_find(&to_pack, oid, index_pos); + 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; @@ -1133,6 +1297,25 @@ static int want_object_in_pack(const struct object_id *oid, } } + if (uri_protocols.nr) { + struct configured_exclusion *ex = + oidmap_get(&configured_exclusions, oid); + int i; + const char *p; + + 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; + } + } + } + } + return 1; } @@ -1141,13 +1324,12 @@ static void create_object_entry(const struct object_id *oid, uint32_t hash, int exclude, int no_try_delta, - uint32_t index_pos, struct packed_git *found_pack, off_t found_offset) { struct object_entry *entry; - entry = packlist_alloc(&to_pack, oid->hash, index_pos); + entry = packlist_alloc(&to_pack, oid); entry->hash = hash; oe_set_type(entry, type); if (exclude) @@ -1171,11 +1353,10 @@ static int add_object_entry(const struct object_id *oid, enum object_type type, { struct packed_git *found_pack = NULL; off_t found_offset = 0; - uint32_t index_pos; display_progress(progress_state, ++nr_seen); - if (have_duplicate_entry(oid, exclude, &index_pos)) + if (have_duplicate_entry(oid, exclude)) return 0; if (!want_object_in_pack(oid, exclude, &found_pack, &found_offset)) { @@ -1190,7 +1371,7 @@ static int add_object_entry(const struct object_id *oid, enum object_type type, create_object_entry(oid, type, pack_name_hash(name), exclude, name && no_try_delta(name), - index_pos, found_pack, found_offset); + found_pack, found_offset); return 1; } @@ -1199,17 +1380,15 @@ static int add_object_entry_from_bitmap(const struct object_id *oid, int flags, uint32_t name_hash, struct packed_git *pack, off_t offset) { - uint32_t index_pos; - display_progress(progress_state, ++nr_seen); - if (have_duplicate_entry(oid, 0, &index_pos)) + if (have_duplicate_entry(oid, 0)) return 0; if (!want_object_in_pack(oid, 0, &pack, &offset)) return 0; - create_object_entry(oid, type, name_hash, 0, 0, index_pos, pack, offset); + create_object_entry(oid, type, name_hash, 0, 0, pack, offset); return 1; } @@ -1491,23 +1670,17 @@ static void cleanup_preferred_base(void) * 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, NULL); + base = packlist_find(&to_pack, base_oid); if (base) { if (!in_same_island(&delta->idx.oid, &base->idx.oid)) return 0; @@ -1520,9 +1693,9 @@ static int can_reuse_delta(const unsigned char *base_sha1, * 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; @@ -1532,14 +1705,36 @@ static int can_reuse_delta(const unsigned char *base_sha1, 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; @@ -1580,9 +1775,13 @@ static void check_object(struct object_entry *entry) 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: @@ -1612,13 +1811,15 @@ static void check_object(struct object_entry *entry) revidx = find_pack_revindex(p, ofs); if (!revidx) goto give_up; - base_ref = nth_packed_object_sha1(p, revidx->nr); + if (!nth_packed_object_id(&base_ref, p, revidx->nr)) + 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); @@ -1628,7 +1829,7 @@ static void check_object(struct object_entry *entry) 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); @@ -1661,8 +1862,18 @@ static void check_object(struct object_entry *entry) 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 { @@ -1882,7 +2093,7 @@ static void get_object_details(void) 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; @@ -2342,15 +2553,6 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, free(array); } -static void try_to_free_from_threads(size_t size) -{ - packing_data_lock(&to_pack); - release_pack_memory(size); - packing_data_unlock(&to_pack); -} - -static try_to_free_t old_try_to_free_routine; - /* * The main object list is split into smaller lists, each is handed to * one worker. @@ -2391,12 +2593,10 @@ static void init_threaded_search(void) pthread_mutex_init(&cache_mutex, NULL); pthread_mutex_init(&progress_mutex, NULL); pthread_cond_init(&progress_cond, NULL); - old_try_to_free_routine = set_try_to_free_routine(try_to_free_from_threads); } static void cleanup_threaded_search(void) { - set_try_to_free_routine(old_try_to_free_routine); pthread_cond_destroy(&progress_cond); pthread_mutex_destroy(&cache_mutex); pthread_mutex_destroy(&progress_mutex); @@ -2568,6 +2768,13 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, 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; @@ -2579,7 +2786,7 @@ static void add_tag_chain(const struct object_id *oid) * it was included via bitmaps, we would not have parsed it * previously). */ - if (packlist_find(&to_pack, oid, NULL)) + if (obj_is_packed(oid)) return; tag = lookup_tag(the_repository, oid); @@ -2603,7 +2810,7 @@ static int add_ref_tag(const char *path, const struct object_id *oid, int flag, if (starts_with(path, "refs/tags/") && /* is a tag? */ !peel_ref(path, &peeled) && /* peelable? */ - packlist_find(&to_pack, &peeled, NULL)) /* object packed? */ + obj_is_packed(&peeled)) /* object packed? */ add_tag_chain(oid); return 0; } @@ -2671,6 +2878,7 @@ static void prepare_pack(int window, int depth) if (nr_deltas && n > 1) { unsigned nr_done = 0; + if (progress) progress_state = start_progress(_("Compressing objects"), nr_deltas); @@ -2715,8 +2923,8 @@ static int git_pack_config(const char *k, const char *v, void *cb) use_bitmap_index_default = git_config_bool(k, v); return 0; } - if (!strcmp(k, "pack.usesparse")) { - sparse = git_config_bool(k, v); + if (!strcmp(k, "pack.allowpackreuse")) { + allow_pack_reuse = git_config_bool(k, v); return 0; } if (!strcmp(k, "pack.threads")) { @@ -2737,6 +2945,29 @@ static int git_pack_config(const char *k, const char *v, void *cb) pack_idx_opts.version); 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 '<object-hash> <pack-hash> <uri>' (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); } @@ -2803,7 +3034,7 @@ static void show_object(struct object *obj, const char *name, void *data) for (p = strchr(name, '/'); p; p = strchr(p + 1, '/')) depth++; - ent = packlist_find(&to_pack, &obj->oid, NULL); + ent = packlist_find(&to_pack, &obj->oid); if (ent && depth > oe_tree_depth(&to_pack, ent)) oe_set_tree_depth(&to_pack, ent, depth); } @@ -2817,7 +3048,7 @@ static void show_object__ma_allow_any(struct object *obj, const char *name, void * 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); @@ -2831,7 +3062,7 @@ static void show_object__ma_allow_promisor(struct object *obj, const char *name, * 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); @@ -2929,7 +3160,7 @@ static void add_objects_in_unpacked_packs(void) in_pack.alloc); for (i = 0; i < p->num_objects; i++) { - nth_packed_object_oid(&oid, p, i); + nth_packed_object_id(&oid, p, i); o = lookup_unknown_object(&oid); if (!(o->flags & OBJECT_ADDED)) mark_in_pack_object(o, p, &in_pack); @@ -3033,8 +3264,8 @@ static void loosen_unused_packed_objects(void) die(_("cannot open pack index")); for (i = 0; i < p->num_objects; i++) { - nth_packed_object_oid(&oid, p, i); - if (!packlist_find(&to_pack, &oid, NULL) && + 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)) if (force_object_loose(&oid, p->mtime)) @@ -3050,8 +3281,8 @@ static void loosen_unused_packed_objects(void) */ 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) && @@ -3060,7 +3291,7 @@ static int pack_options_allow_reuse(void) 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))) return -1; if (pack_options_allow_reuse() && @@ -3068,13 +3299,14 @@ static int get_object_list_from_bitmap(struct rev_info *revs) 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); } - 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; } @@ -3125,7 +3357,7 @@ static void get_object_list(int ac, const char **av) 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; @@ -3239,7 +3471,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) 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; struct string_list keep_pack_list = STRING_LIST_INIT_NODUP; @@ -3253,9 +3485,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) 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_("<version>[,<offset>]"), + OPT_CALLBACK_F(0, "index-version", NULL, N_("<version>[,<offset>]"), 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, @@ -3300,9 +3532,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) 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 <time>"), - PARSE_OPT_OPTARG, option_parse_unpack_unreachable }, + PARSE_OPT_OPTARG, option_parse_unpack_unreachable), OPT_BOOL(0, "sparse", &sparse, N_("use the sparse reachability algorithm")), OPT_BOOL(0, "thin", &thin, @@ -3327,13 +3559,16 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) N_("write a bitmap index if possible"), WRITE_BITMAP_QUIET, PARSE_OPT_HIDDEN), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), - { OPTION_CALLBACK, 0, "missing", NULL, N_("action"), + OPT_CALLBACK_F(0, "missing", NULL, N_("action"), N_("handling for missing objects"), PARSE_OPT_NONEG, - option_parse_missing_action }, + option_parse_missing_action), OPT_BOOL(0, "exclude-promisor-objects", &exclude_promisor_objects, N_("do not pack objects in promisor packfiles")), OPT_BOOL(0, "delta-islands", &use_delta_islands, N_("respect islands during delta compression")), + OPT_STRING_LIST(0, "uri-protocol", &uri_protocols, + N_("protocol"), + N_("exclude any configured uploadpack.blobpackfileuri with this protocol")), OPT_END(), }; @@ -3342,7 +3577,11 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) read_replace_refs = 0; - sparse = git_env_bool("GIT_TEST_PACK_SPARSE", 0); + sparse = git_env_bool("GIT_TEST_PACK_SPARSE", -1); + prepare_repo_settings(the_repository); + if (sparse < 0) + sparse = the_repository->settings.pack_use_sparse; + reset_pack_idx_option(&pack_idx_opts); git_config(git_pack_config, NULL); @@ -3368,36 +3607,36 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) cache_max_small_delta_size = (1U << OE_Z_DELTA_BITS) - 1; } - argv_array_push(&rp, "pack-objects"); + strvec_push(&rp, "pack-objects"); if (thin) { use_internal_rev_list = 1; - argv_array_push(&rp, shallow + strvec_push(&rp, shallow ? "--objects-edge-aggressive" : "--objects-edge"); } else - argv_array_push(&rp, "--objects"); + strvec_push(&rp, "--objects"); if (rev_list_all) { use_internal_rev_list = 1; - argv_array_push(&rp, "--all"); + strvec_push(&rp, "--all"); } if (rev_list_reflog) { use_internal_rev_list = 1; - argv_array_push(&rp, "--reflog"); + strvec_push(&rp, "--reflog"); } if (rev_list_index) { use_internal_rev_list = 1; - argv_array_push(&rp, "--indexed-objects"); + strvec_push(&rp, "--indexed-objects"); } if (rev_list_unpacked) { use_internal_rev_list = 1; - argv_array_push(&rp, "--unpacked"); + strvec_push(&rp, "--unpacked"); } if (exclude_promisor_objects) { use_internal_rev_list = 1; fetch_if_missing = 0; - argv_array_push(&rp, "--exclude-promisor-objects"); + strvec_push(&rp, "--exclude-promisor-objects"); } if (unpack_unreachable || keep_unreachable || pack_loose_unreachable) use_internal_rev_list = 1; @@ -3434,7 +3673,6 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (filter_options.choice) { if (!pack_to_stdout) die(_("cannot use --filter without --stdout")); - use_bitmap_index = 0; } /* @@ -3460,7 +3698,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) write_bitmap_index = 0; if (use_delta_islands) - argv_array_push(&rp, "--topo-order"); + strvec_push(&rp, "--topo-order"); if (progress && all_progress_implied) progress = 2; @@ -3498,8 +3736,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (!use_internal_rev_list) read_object_list_from_stdin(); else { - get_object_list(rp.argc, rp.argv); - argv_array_clear(&rp); + get_object_list(rp.nr, rp.v); + strvec_clear(&rp); } cleanup_preferred_base(); if (include_tag && nr_result) @@ -3519,13 +3757,16 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } trace2_region_enter("pack-objects", "write-pack-file", the_repository); + write_excluded_by_configs(); write_pack_file(); trace2_region_leave("pack-objects", "write-pack-file", the_repository); if (progress) fprintf_ln(stderr, _("Total %"PRIu32" (delta %"PRIu32")," - " reused %"PRIu32" (delta %"PRIu32")"), - written, written_delta, reused, reused_delta); + " reused %"PRIu32" (delta %"PRIu32")," + " pack-reused %"PRIu32), + written, written_delta, reused, reused_delta, + reuse_packfile_objects); return 0; } diff --git a/third_party/git/builtin/patch-id.c b/third_party/git/builtin/patch-id.c index bd28b80b2d0f..822ffff51fbd 100644 --- a/third_party/git/builtin/patch-id.c +++ b/third_party/git/builtin/patch-id.c @@ -1,16 +1,12 @@ +#include "cache.h" #include "builtin.h" #include "config.h" #include "diff.h" static void flush_current_id(int patchlen, struct object_id *id, struct object_id *result) { - char name[50]; - - if (!patchlen) - return; - - memcpy(name, oid_to_hex(id), GIT_SHA1_HEXSZ + 1); - printf("%s %s\n", oid_to_hex(result), name); + if (patchlen) + printf("%s %s\n", oid_to_hex(result), oid_to_hex(id)); } static int remove_space(char *line) @@ -60,9 +56,9 @@ static int get_one_patchid(struct object_id *next_oid, struct object_id *result, { int patchlen = 0, found_next = 0; int before = -1, after = -1; - git_SHA_CTX ctx; + git_hash_ctx ctx; - git_SHA1_Init(&ctx); + the_hash_algo->init_fn(&ctx); oidclr(result); while (strbuf_getwholeline(line_buf, stdin, '\n') != EOF) { @@ -122,7 +118,7 @@ static int get_one_patchid(struct object_id *next_oid, struct object_id *result, /* Compute the sha without whitespace */ len = remove_space(line); patchlen += len; - git_SHA1_Update(&ctx, line, len); + the_hash_algo->update_fn(&ctx, line, len); } if (!found_next) diff --git a/third_party/git/builtin/prune-packed.c b/third_party/git/builtin/prune-packed.c index 48c5e78e339d..b7b9281a8cea 100644 --- a/third_party/git/builtin/prune-packed.c +++ b/third_party/git/builtin/prune-packed.c @@ -1,54 +1,12 @@ #include "builtin.h" -#include "cache.h" -#include "progress.h" #include "parse-options.h" -#include "packfile.h" -#include "object-store.h" +#include "prune-packed.h" static const char * const prune_packed_usage[] = { N_("git prune-packed [-n | --dry-run] [-q | --quiet]"), NULL }; -static struct progress *progress; - -static int prune_subdir(unsigned int nr, const char *path, void *data) -{ - int *opts = data; - display_progress(progress, nr + 1); - if (!(*opts & PRUNE_PACKED_DRY_RUN)) - rmdir(path); - return 0; -} - -static int prune_object(const struct object_id *oid, const char *path, - void *data) -{ - int *opts = data; - - if (!has_object_pack(oid)) - return 0; - - if (*opts & PRUNE_PACKED_DRY_RUN) - printf("rm -f %s\n", path); - else - unlink_or_warn(path); - return 0; -} - -void prune_packed_objects(int opts) -{ - if (opts & PRUNE_PACKED_VERBOSE) - progress = start_delayed_progress(_("Removing duplicate objects"), 256); - - for_each_loose_file_in_objdir(get_object_directory(), - prune_object, NULL, prune_subdir, &opts); - - /* Ensure we show 100% before finishing progress */ - display_progress(progress, 256); - stop_progress(&progress); -} - int cmd_prune_packed(int argc, const char **argv, const char *prefix) { int opts = isatty(2) ? PRUNE_PACKED_VERBOSE : 0; diff --git a/third_party/git/builtin/prune.c b/third_party/git/builtin/prune.c index 2b76872ad220..02c6ab7cbaaf 100644 --- a/third_party/git/builtin/prune.c +++ b/third_party/git/builtin/prune.c @@ -6,7 +6,9 @@ #include "reachable.h" #include "parse-options.h" #include "progress.h" +#include "prune-packed.h" #include "object-store.h" +#include "shallow.h" static const char * const prune_usage[] = { N_("git prune [-n] [-v] [--progress] [--expire <time>] [--] [<head>...]"), diff --git a/third_party/git/builtin/pull.c b/third_party/git/builtin/pull.c index f1eaf6e6edb1..425950f46973 100644 --- a/third_party/git/builtin/pull.c +++ b/third_party/git/builtin/pull.c @@ -12,9 +12,10 @@ #include "parse-options.h" #include "exec-cmd.h" #include "run-command.h" -#include "sha1-array.h" +#include "oid-array.h" #include "remote.h" #include "dir.h" +#include "rebase.h" #include "refs.h" #include "refspec.h" #include "revision.h" @@ -26,15 +27,6 @@ #include "commit-reach.h" #include "sequencer.h" -enum rebase_type { - REBASE_INVALID = -1, - REBASE_FALSE = 0, - REBASE_TRUE, - REBASE_PRESERVE, - REBASE_MERGES, - REBASE_INTERACTIVE -}; - /** * Parses the value of --rebase. If value is a false value, returns * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is @@ -45,22 +37,9 @@ enum rebase_type { static enum rebase_type parse_config_rebase(const char *key, const char *value, int fatal) { - int v = git_parse_maybe_bool(value); - - if (!v) - return REBASE_FALSE; - else if (v > 0) - return REBASE_TRUE; - else if (!strcmp(value, "preserve") || !strcmp(value, "p")) - return REBASE_PRESERVE; - else if (!strcmp(value, "merges") || !strcmp(value, "m")) - return REBASE_MERGES; - else if (!strcmp(value, "interactive") || !strcmp(value, "i")) - return REBASE_INTERACTIVE; - /* - * Please update _git_config() in git-completion.bash when you - * add new rebase modes. - */ + enum rebase_type v = rebase_parse_value(value); + if (v != REBASE_INVALID) + return v; if (fatal) die(_("Invalid value for %s: %s"), key, value); @@ -107,8 +86,9 @@ static char *opt_ff; static char *opt_verify_signatures; static int opt_autostash = -1; static int config_autostash; -static struct argv_array opt_strategies = ARGV_ARRAY_INIT; -static struct argv_array opt_strategy_opts = ARGV_ARRAY_INIT; +static int check_trust_level = 1; +static struct strvec opt_strategies = STRVEC_INIT; +static struct strvec opt_strategy_opts = STRVEC_INIT; static char *opt_gpg_sign; static int opt_allow_unrelated_histories; @@ -129,6 +109,8 @@ static char *opt_refmap; static char *opt_ipv4; static char *opt_ipv6; static int opt_show_forced_updates = -1; +static char *set_upstream; +static struct strvec opt_fetch = STRVEC_INIT; static struct option pull_options[] = { /* Shared options */ @@ -136,17 +118,17 @@ static struct option pull_options[] = { OPT_PASSTHRU(0, "progress", &opt_progress, NULL, N_("force progress reporting"), PARSE_OPT_NOARG), - { OPTION_CALLBACK, 0, "recurse-submodules", + OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules, N_("on-demand"), N_("control for recursive fetching of submodules"), - PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules }, + PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules), /* Options passed to git-merge or git-rebase */ OPT_GROUP(N_("Options related to merging")), - { OPTION_CALLBACK, 'r', "rebase", &opt_rebase, + OPT_CALLBACK_F('r', "rebase", &opt_rebase, "(false|true|merges|preserve|interactive)", N_("incorporate changes by rebasing rather than merging"), - PARSE_OPT_OPTARG, parse_opt_rebase }, + PARSE_OPT_OPTARG, parse_opt_rebase), OPT_PASSTHRU('n', NULL, &opt_diffstat, NULL, N_("do not show a diffstat at the end of the merge"), PARSE_OPT_NOARG | PARSE_OPT_NONEG), @@ -182,7 +164,7 @@ static struct option pull_options[] = { N_("verify that the named commit has a valid GPG signature"), PARSE_OPT_NOARG), OPT_BOOL(0, "autostash", &opt_autostash, - N_("automatically stash/stash pop before and after rebase")), + N_("automatically stash/stash pop before and after")), OPT_PASSTHRU_ARGV('s', "strategy", &opt_strategies, N_("strategy"), N_("merge strategy to use"), 0), @@ -226,6 +208,15 @@ static struct option pull_options[] = { OPT_PASSTHRU(0, "depth", &opt_depth, N_("depth"), N_("deepen history of shallow clone"), 0), + OPT_PASSTHRU_ARGV(0, "shallow-since", &opt_fetch, N_("time"), + N_("deepen history of shallow repository based on time"), + 0), + OPT_PASSTHRU_ARGV(0, "shallow-exclude", &opt_fetch, N_("revision"), + N_("deepen history of shallow clone, excluding rev"), + 0), + OPT_PASSTHRU_ARGV(0, "deepen", &opt_fetch, N_("n"), + N_("deepen history of shallow clone"), + 0), OPT_PASSTHRU(0, "unshallow", &opt_unshallow, NULL, N_("convert to a complete repository"), PARSE_OPT_NONEG | PARSE_OPT_NOARG), @@ -235,14 +226,24 @@ static struct option pull_options[] = { OPT_PASSTHRU(0, "refmap", &opt_refmap, N_("refmap"), N_("specify fetch refmap"), PARSE_OPT_NONEG), + OPT_PASSTHRU_ARGV('o', "server-option", &opt_fetch, + N_("server-specific"), + N_("option to transmit"), + 0), OPT_PASSTHRU('4', "ipv4", &opt_ipv4, NULL, N_("use IPv4 addresses only"), PARSE_OPT_NOARG), OPT_PASSTHRU('6', "ipv6", &opt_ipv6, NULL, N_("use IPv6 addresses only"), PARSE_OPT_NOARG), + OPT_PASSTHRU_ARGV(0, "negotiation-tip", &opt_fetch, N_("revision"), + N_("report that we have only objects reachable from this object"), + 0), OPT_BOOL(0, "show-forced-updates", &opt_show_forced_updates, N_("check for forced-updates on all updated branches")), + OPT_PASSTHRU(0, "set-upstream", &set_upstream, NULL, + N_("set upstream for git pull/fetch"), + PARSE_OPT_NOARG), OPT_END() }; @@ -250,25 +251,25 @@ static struct option pull_options[] = { /** * Pushes "-q" or "-v" switches into arr to match the opt_verbosity level. */ -static void argv_push_verbosity(struct argv_array *arr) +static void argv_push_verbosity(struct strvec *arr) { int verbosity; for (verbosity = opt_verbosity; verbosity > 0; verbosity--) - argv_array_push(arr, "-v"); + strvec_push(arr, "-v"); for (verbosity = opt_verbosity; verbosity < 0; verbosity++) - argv_array_push(arr, "-q"); + strvec_push(arr, "-q"); } /** * Pushes "-f" switches into arr to match the opt_force level. */ -static void argv_push_force(struct argv_array *arr) +static void argv_push_force(struct strvec *arr) { int force = opt_force; while (force-- > 0) - argv_array_push(arr, "-f"); + strvec_push(arr, "-f"); } /** @@ -343,6 +344,21 @@ static enum rebase_type config_get_rebase(void) if (!git_config_get_value("pull.rebase", &value)) return parse_config_rebase("pull.rebase", value, 1); + if (opt_verbosity >= 0 && !opt_ff) { + warning(_("Pulling without specifying how to reconcile divergent branches is\n" + "discouraged. You can squelch this message by running one of the following\n" + "commands sometime before your next pull:\n" + "\n" + " git config pull.rebase false # merge (the default strategy)\n" + " git config pull.rebase true # rebase\n" + " git config pull.ff only # fast-forward only\n" + "\n" + "You can replace \"git config\" with \"git config --global\" to set a default\n" + "preference for all repositories. You can also pass --rebase, --no-rebase,\n" + "or --ff-only on the command line to override the configured default per\n" + "invocation.\n")); + } + return REBASE_FALSE; } @@ -351,6 +367,8 @@ static enum rebase_type config_get_rebase(void) */ static int git_pull_config(const char *var, const char *value, void *cb) { + int status; + if (!strcmp(var, "rebase.autostash")) { config_autostash = git_config_bool(var, value); return 0; @@ -358,7 +376,14 @@ static int git_pull_config(const char *var, const char *value, void *cb) recurse_submodules = git_config_bool(var, value) ? RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF; return 0; + } else if (!strcmp(var, "gpg.mintrustlevel")) { + check_trust_level = 0; } + + status = git_gpg_config(var, value, cb); + if (status) + return status; + return git_default_config(var, value, cb); } @@ -498,72 +523,75 @@ static void parse_repo_refspecs(int argc, const char **argv, const char **repo, */ static int run_fetch(const char *repo, const char **refspecs) { - struct argv_array args = ARGV_ARRAY_INIT; + struct strvec args = STRVEC_INIT; int ret; - argv_array_pushl(&args, "fetch", "--update-head-ok", NULL); + strvec_pushl(&args, "fetch", "--update-head-ok", NULL); /* Shared options */ argv_push_verbosity(&args); if (opt_progress) - argv_array_push(&args, opt_progress); + strvec_push(&args, opt_progress); /* Options passed to git-fetch */ if (opt_all) - argv_array_push(&args, opt_all); + strvec_push(&args, opt_all); if (opt_append) - argv_array_push(&args, opt_append); + strvec_push(&args, opt_append); if (opt_upload_pack) - argv_array_push(&args, opt_upload_pack); + strvec_push(&args, opt_upload_pack); argv_push_force(&args); if (opt_tags) - argv_array_push(&args, opt_tags); + strvec_push(&args, opt_tags); if (opt_prune) - argv_array_push(&args, opt_prune); + strvec_push(&args, opt_prune); if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT) switch (recurse_submodules) { case RECURSE_SUBMODULES_ON: - argv_array_push(&args, "--recurse-submodules=on"); + strvec_push(&args, "--recurse-submodules=on"); break; case RECURSE_SUBMODULES_OFF: - argv_array_push(&args, "--recurse-submodules=no"); + strvec_push(&args, "--recurse-submodules=no"); break; case RECURSE_SUBMODULES_ON_DEMAND: - argv_array_push(&args, "--recurse-submodules=on-demand"); + strvec_push(&args, "--recurse-submodules=on-demand"); break; default: BUG("submodule recursion option not understood"); } if (max_children) - argv_array_push(&args, max_children); + strvec_push(&args, max_children); if (opt_dry_run) - argv_array_push(&args, "--dry-run"); + strvec_push(&args, "--dry-run"); if (opt_keep) - argv_array_push(&args, opt_keep); + strvec_push(&args, opt_keep); if (opt_depth) - argv_array_push(&args, opt_depth); + strvec_push(&args, opt_depth); if (opt_unshallow) - argv_array_push(&args, opt_unshallow); + strvec_push(&args, opt_unshallow); if (opt_update_shallow) - argv_array_push(&args, opt_update_shallow); + strvec_push(&args, opt_update_shallow); if (opt_refmap) - argv_array_push(&args, opt_refmap); + strvec_push(&args, opt_refmap); if (opt_ipv4) - argv_array_push(&args, opt_ipv4); + strvec_push(&args, opt_ipv4); if (opt_ipv6) - argv_array_push(&args, opt_ipv6); + strvec_push(&args, opt_ipv6); if (opt_show_forced_updates > 0) - argv_array_push(&args, "--show-forced-updates"); + strvec_push(&args, "--show-forced-updates"); else if (opt_show_forced_updates == 0) - argv_array_push(&args, "--no-show-forced-updates"); + strvec_push(&args, "--no-show-forced-updates"); + if (set_upstream) + strvec_push(&args, set_upstream); + strvec_pushv(&args, opt_fetch.v); if (repo) { - argv_array_push(&args, repo); - argv_array_pushv(&args, refspecs); + strvec_push(&args, repo); + strvec_pushv(&args, refspecs); } else if (*refspecs) BUG("refspecs without repo?"); - ret = run_command_v_opt(args.argv, RUN_GIT_CMD); - argv_array_clear(&args); + ret = run_command_v_opt(args.v, RUN_GIT_CMD); + strvec_clear(&args); return ret; } @@ -581,7 +609,8 @@ static int pull_into_void(const struct object_id *merge_head, die(_("unable to access commit %s"), oid_to_hex(merge_head)); - verify_merge_signature(commit, opt_verbosity); + verify_merge_signature(commit, opt_verbosity, + check_trust_level); } /* @@ -607,8 +636,8 @@ static int rebase_submodules(void) cp.git_cmd = 1; cp.no_stdin = 1; - argv_array_pushl(&cp.args, "submodule", "update", - "--recursive", "--rebase", NULL); + strvec_pushl(&cp.args, "submodule", "update", + "--recursive", "--rebase", NULL); argv_push_verbosity(&cp.args); return run_command(&cp); @@ -620,8 +649,8 @@ static int update_submodules(void) cp.git_cmd = 1; cp.no_stdin = 1; - argv_array_pushl(&cp.args, "submodule", "update", - "--recursive", "--checkout", NULL); + strvec_pushl(&cp.args, "submodule", "update", + "--recursive", "--checkout", NULL); argv_push_verbosity(&cp.args); return run_command(&cp); @@ -633,44 +662,48 @@ static int update_submodules(void) static int run_merge(void) { int ret; - struct argv_array args = ARGV_ARRAY_INIT; + struct strvec args = STRVEC_INIT; - argv_array_pushl(&args, "merge", NULL); + strvec_pushl(&args, "merge", NULL); /* Shared options */ argv_push_verbosity(&args); if (opt_progress) - argv_array_push(&args, opt_progress); + strvec_push(&args, opt_progress); /* Options passed to git-merge */ if (opt_diffstat) - argv_array_push(&args, opt_diffstat); + strvec_push(&args, opt_diffstat); if (opt_log) - argv_array_push(&args, opt_log); + strvec_push(&args, opt_log); if (opt_signoff) - argv_array_push(&args, opt_signoff); + strvec_push(&args, opt_signoff); if (opt_squash) - argv_array_push(&args, opt_squash); + strvec_push(&args, opt_squash); if (opt_commit) - argv_array_push(&args, opt_commit); + strvec_push(&args, opt_commit); if (opt_edit) - argv_array_push(&args, opt_edit); + strvec_push(&args, opt_edit); if (cleanup_arg) - argv_array_pushf(&args, "--cleanup=%s", cleanup_arg); + strvec_pushf(&args, "--cleanup=%s", cleanup_arg); if (opt_ff) - argv_array_push(&args, opt_ff); + strvec_push(&args, opt_ff); if (opt_verify_signatures) - argv_array_push(&args, opt_verify_signatures); - argv_array_pushv(&args, opt_strategies.argv); - argv_array_pushv(&args, opt_strategy_opts.argv); + strvec_push(&args, opt_verify_signatures); + strvec_pushv(&args, opt_strategies.v); + strvec_pushv(&args, opt_strategy_opts.v); if (opt_gpg_sign) - argv_array_push(&args, opt_gpg_sign); + strvec_push(&args, opt_gpg_sign); + if (opt_autostash == 0) + strvec_push(&args, "--no-autostash"); + else if (opt_autostash == 1) + strvec_push(&args, "--autostash"); if (opt_allow_unrelated_histories > 0) - argv_array_push(&args, "--allow-unrelated-histories"); + strvec_push(&args, "--allow-unrelated-histories"); - argv_array_push(&args, "FETCH_HEAD"); - ret = run_command_v_opt(args.argv, RUN_GIT_CMD); - argv_array_clear(&args); + strvec_push(&args, "FETCH_HEAD"); + ret = run_command_v_opt(args.v, RUN_GIT_CMD); + strvec_clear(&args); return ret; } @@ -767,8 +800,8 @@ static int get_rebase_fork_point(struct object_id *fork_point, const char *repo, if (!remote_branch) return -1; - argv_array_pushl(&cp.args, "merge-base", "--fork-point", - remote_branch, curr_branch->name, NULL); + strvec_pushl(&cp.args, "merge-base", "--fork-point", + remote_branch, curr_branch->name, NULL); cp.no_stdin = 1; cp.no_stderr = 1; cp.git_cmd = 1; @@ -828,48 +861,48 @@ static int run_rebase(const struct object_id *curr_head, { int ret; struct object_id oct_merge_base; - struct argv_array args = ARGV_ARRAY_INIT; + struct strvec args = STRVEC_INIT; if (!get_octopus_merge_base(&oct_merge_base, curr_head, merge_head, fork_point)) if (!is_null_oid(fork_point) && oideq(&oct_merge_base, fork_point)) fork_point = NULL; - argv_array_push(&args, "rebase"); + strvec_push(&args, "rebase"); /* Shared options */ argv_push_verbosity(&args); /* Options passed to git-rebase */ if (opt_rebase == REBASE_MERGES) - argv_array_push(&args, "--rebase-merges"); + strvec_push(&args, "--rebase-merges"); else if (opt_rebase == REBASE_PRESERVE) - argv_array_push(&args, "--preserve-merges"); + strvec_push(&args, "--preserve-merges"); else if (opt_rebase == REBASE_INTERACTIVE) - argv_array_push(&args, "--interactive"); + strvec_push(&args, "--interactive"); if (opt_diffstat) - argv_array_push(&args, opt_diffstat); - argv_array_pushv(&args, opt_strategies.argv); - argv_array_pushv(&args, opt_strategy_opts.argv); + strvec_push(&args, opt_diffstat); + strvec_pushv(&args, opt_strategies.v); + strvec_pushv(&args, opt_strategy_opts.v); if (opt_gpg_sign) - argv_array_push(&args, opt_gpg_sign); + strvec_push(&args, opt_gpg_sign); if (opt_autostash == 0) - argv_array_push(&args, "--no-autostash"); + strvec_push(&args, "--no-autostash"); else if (opt_autostash == 1) - argv_array_push(&args, "--autostash"); + strvec_push(&args, "--autostash"); if (opt_verify_signatures && !strcmp(opt_verify_signatures, "--verify-signatures")) warning(_("ignoring --verify-signatures for rebase")); - argv_array_push(&args, "--onto"); - argv_array_push(&args, oid_to_hex(merge_head)); + strvec_push(&args, "--onto"); + strvec_push(&args, oid_to_hex(merge_head)); if (fork_point && !is_null_oid(fork_point)) - argv_array_push(&args, oid_to_hex(fork_point)); + strvec_push(&args, oid_to_hex(fork_point)); else - argv_array_push(&args, oid_to_hex(merge_head)); + strvec_push(&args, oid_to_hex(merge_head)); - ret = run_command_v_opt(args.argv, RUN_GIT_CMD); - argv_array_clear(&args); + ret = run_command_v_opt(args.v, RUN_GIT_CMD); + strvec_clear(&args); return ret; } @@ -912,9 +945,6 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (get_oid("HEAD", &orig_head)) oidclr(&orig_head); - if (!opt_rebase && opt_autostash != -1) - die(_("--[no-]autostash option is only valid with --rebase.")); - autostash = config_autostash; if (opt_rebase) { if (opt_autostash != -1) @@ -980,6 +1010,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (opt_rebase) { int ret = 0; + int ran_ff = 0; if ((recurse_submodules == RECURSE_SUBMODULES_ON || recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) && submodule_touches_in_range(the_repository, &rebase_fork_point, &curr_head)) @@ -993,13 +1024,17 @@ int cmd_pull(int argc, const char **argv, const char *prefix) commit_list_insert(head, &list); merge_head = lookup_commit_reference(the_repository, &merge_heads.oid[0]); - if (is_descendant_of(merge_head, list)) { + if (repo_is_descendant_of(the_repository, + merge_head, list)) { /* we can fast-forward this without invoking rebase */ opt_ff = "--ff-only"; + ran_ff = 1; ret = run_merge(); } + free_commit_list(list); } - ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point); + if (!ran_ff) + ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point); if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON || recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)) diff --git a/third_party/git/builtin/push.c b/third_party/git/builtin/push.c index 021dd3b1e489..6da3a8e5d30a 100644 --- a/third_party/git/builtin/push.c +++ b/third_party/git/builtin/push.c @@ -61,40 +61,41 @@ static struct refspec rs = REFSPEC_INIT_PUSH; static struct string_list push_options_config = STRING_LIST_INIT_DUP; -static const char *map_refspec(const char *ref, - struct remote *remote, struct ref *local_refs) +static void refspec_append_mapped(struct refspec *refspec, const char *ref, + struct remote *remote, struct ref *local_refs) { + const char *branch_name; struct ref *matched = NULL; /* Does "ref" uniquely name our ref? */ - if (count_refspec_match(ref, local_refs, &matched) != 1) - return ref; + if (count_refspec_match(ref, local_refs, &matched) != 1) { + refspec_append(refspec, ref); + return; + } if (remote->push.nr) { struct refspec_item query; memset(&query, 0, sizeof(struct refspec_item)); query.src = matched->name; if (!query_refspecs(&remote->push, &query) && query.dst) { - struct strbuf buf = STRBUF_INIT; - strbuf_addf(&buf, "%s%s:%s", - query.force ? "+" : "", - query.src, query.dst); - return strbuf_detach(&buf, NULL); + refspec_appendf(refspec, "%s%s:%s", + query.force ? "+" : "", + query.src, query.dst); + return; } } if (push_default == PUSH_DEFAULT_UPSTREAM && - starts_with(matched->name, "refs/heads/")) { - struct branch *branch = branch_get(matched->name + 11); + skip_prefix(matched->name, "refs/heads/", &branch_name)) { + struct branch *branch = branch_get(branch_name); if (branch->merge_nr == 1 && branch->merge[0]->src) { - struct strbuf buf = STRBUF_INIT; - strbuf_addf(&buf, "%s:%s", - ref, branch->merge[0]->src); - return strbuf_detach(&buf, NULL); + refspec_appendf(refspec, "%s:%s", + ref, branch->merge[0]->src); + return; } } - return ref; + refspec_append(refspec, ref); } static void set_refspecs(const char **refs, int nr, const char *repo) @@ -106,30 +107,26 @@ static void set_refspecs(const char **refs, int nr, const char *repo) for (i = 0; i < nr; i++) { const char *ref = refs[i]; if (!strcmp("tag", ref)) { - struct strbuf tagref = STRBUF_INIT; if (nr <= ++i) die(_("tag shorthand without <tag>")); ref = refs[i]; if (deleterefs) - strbuf_addf(&tagref, ":refs/tags/%s", ref); + refspec_appendf(&rs, ":refs/tags/%s", ref); else - strbuf_addf(&tagref, "refs/tags/%s", ref); - ref = strbuf_detach(&tagref, NULL); + refspec_appendf(&rs, "refs/tags/%s", ref); } else if (deleterefs) { - struct strbuf delref = STRBUF_INIT; if (strchr(ref, ':')) die(_("--delete only accepts plain target ref names")); - strbuf_addf(&delref, ":%s", ref); - ref = strbuf_detach(&delref, NULL); + refspec_appendf(&rs, ":%s", ref); } else if (!strchr(ref, ':')) { if (!remote) { /* lazily grab remote and local_refs */ remote = remote_get(repo); local_refs = get_local_heads(); } - ref = map_refspec(ref, remote, local_refs); - } - refspec_append(&rs, ref); + refspec_append_mapped(&rs, ref, remote, local_refs); + } else + refspec_append(&rs, ref); } } @@ -143,8 +140,8 @@ static int push_url_of_remote(struct remote *remote, const char ***url_p) return remote->url_nr; } -static NORETURN int die_push_simple(struct branch *branch, - struct remote *remote) +static NORETURN void die_push_simple(struct branch *branch, + struct remote *remote) { /* * There's no point in using shorten_unambiguous_ref here, @@ -191,8 +188,6 @@ static const char message_detached_head_die[] = static void setup_push_upstream(struct remote *remote, struct branch *branch, int triangular, int simple) { - struct strbuf refspec = STRBUF_INIT; - if (!branch) die(_(message_detached_head_die), remote->name); if (!branch->merge_nr || !branch->merge || !branch->remote_name) @@ -218,18 +213,14 @@ static void setup_push_upstream(struct remote *remote, struct branch *branch, die_push_simple(branch, remote); } - strbuf_addf(&refspec, "%s:%s", branch->refname, branch->merge[0]->src); - refspec_append(&rs, refspec.buf); + refspec_appendf(&rs, "%s:%s", branch->refname, branch->merge[0]->src); } static void setup_push_current(struct remote *remote, struct branch *branch) { - struct strbuf refspec = STRBUF_INIT; - if (!branch) die(_(message_detached_head_die), remote->name); - strbuf_addf(&refspec, "%s:%s", branch->refname, branch->refname); - refspec_append(&rs, refspec.buf); + refspec_appendf(&rs, "%s:%s", branch->refname, branch->refname); } static int is_workflow_triangular(struct remote *remote) @@ -339,6 +330,7 @@ static int push_with_options(struct transport *transport, struct refspec *rs, { int err; unsigned int reject_reasons; + char *anon_url = transport_anonymize_url(transport->url); transport_set_verbosity(transport, verbosity, progress); transport->family = family; @@ -356,16 +348,19 @@ static int push_with_options(struct transport *transport, struct refspec *rs, } if (verbosity > 0) - fprintf(stderr, _("Pushing to %s\n"), transport->url); + fprintf(stderr, _("Pushing to %s\n"), anon_url); + trace2_region_enter("push", "transport_push", the_repository); err = transport_push(the_repository, transport, rs, flags, &reject_reasons); + trace2_region_leave("push", "transport_push", the_repository); if (err != 0) { fprintf(stderr, "%s", push_get_color(PUSH_COLOR_ERROR)); - error(_("failed to push some refs to '%s'"), transport->url); + error(_("failed to push some refs to '%s'"), anon_url); fprintf(stderr, "%s", push_get_color(PUSH_COLOR_RESET)); } err |= transport_disconnect(transport); + free(anon_url); if (!err) return 0; @@ -384,31 +379,15 @@ static int push_with_options(struct transport *transport, struct refspec *rs, return 1; } -static int do_push(const char *repo, int flags, - const struct string_list *push_options) +static int do_push(int flags, + const struct string_list *push_options, + struct remote *remote) { int i, errs; - struct remote *remote = pushremote_get(repo); const char **url; int url_nr; struct refspec *push_refspec = &rs; - if (!remote) { - if (repo) - die(_("bad repository '%s'"), repo); - die(_("No configured push destination.\n" - "Either specify the URL from the command-line or configure a remote repository using\n" - "\n" - " git remote add <name> <url>\n" - "\n" - "and then push using the remote name\n" - "\n" - " git push <name>\n")); - } - - if (remote->mirror) - flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE); - if (push_options->nr) flags |= TRANSPORT_PUSH_OPTIONS; @@ -447,10 +426,8 @@ static int option_parse_recurse_submodules(const struct option *opt, if (unset) *recurse_submodules = RECURSE_SUBMODULES_OFF; - else if (arg) - *recurse_submodules = parse_push_recurse_submodules_arg(opt->long_name, arg); else - die("%s missing parameter", opt->long_name); + *recurse_submodules = parse_push_recurse_submodules_arg(opt->long_name, arg); return 0; } @@ -548,6 +525,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) struct string_list push_options_cmdline = STRING_LIST_INIT_DUP; struct string_list *push_options; const struct string_list_item *item; + struct remote *remote; struct option options[] = { OPT__VERBOSITY(&verbosity), @@ -560,13 +538,11 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT('n' , "dry-run", &flags, N_("dry run"), TRANSPORT_PUSH_DRY_RUN), OPT_BIT( 0, "porcelain", &flags, N_("machine-readable output"), TRANSPORT_PUSH_PORCELAIN), OPT_BIT('f', "force", &flags, N_("force updates"), TRANSPORT_PUSH_FORCE), - { OPTION_CALLBACK, - 0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"), - N_("require old value of ref to be at this value"), - PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, parseopt_push_cas_option }, - { OPTION_CALLBACK, 0, "recurse-submodules", &recurse_submodules, "(check|on-demand|no)", - N_("control recursive pushing of submodules"), - PARSE_OPT_OPTARG, option_parse_recurse_submodules }, + OPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"), + N_("require old value of ref to be at this value"), + PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, parseopt_push_cas_option), + OPT_CALLBACK(0, "recurse-submodules", &recurse_submodules, "(check|on-demand|no)", + N_("control recursive pushing of submodules"), option_parse_recurse_submodules), OPT_BOOL_F( 0 , "thin", &thin, N_("use thin pack"), PARSE_OPT_NOCOMPLETE), OPT_STRING( 0 , "receive-pack", &receivepack, "receive-pack", N_("receive pack program")), OPT_STRING( 0 , "exec", &receivepack, "receive-pack", N_("receive pack program")), @@ -578,9 +554,8 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_BIT(0, "no-verify", &flags, N_("bypass pre-push hook"), TRANSPORT_PUSH_NO_HOOK), OPT_BIT(0, "follow-tags", &flags, N_("push missing but relevant tags"), TRANSPORT_PUSH_FOLLOW_TAGS), - { OPTION_CALLBACK, - 0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"), - PARSE_OPT_OPTARG, option_parse_push_signed }, + OPT_CALLBACK_F(0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"), + PARSE_OPT_OPTARG, option_parse_push_signed), OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC), OPT_STRING_LIST('o', "push-option", &push_options_cmdline, N_("server-specific"), N_("option to transmit")), OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"), @@ -602,20 +577,6 @@ int cmd_push(int argc, const char **argv, const char *prefix) die(_("--delete is incompatible with --all, --mirror and --tags")); if (deleterefs && argc < 2) die(_("--delete doesn't make sense without any refs")); - if (flags & TRANSPORT_PUSH_ALL) { - if (tags) - die(_("--all and --tags are incompatible")); - if (argc >= 2) - die(_("--all can't be combined with refspecs")); - } - if (flags & TRANSPORT_PUSH_MIRROR) { - if (tags) - die(_("--mirror and --tags are incompatible")); - if (argc >= 2) - die(_("--mirror can't be combined with refspecs")); - } - if ((flags & TRANSPORT_PUSH_ALL) && (flags & TRANSPORT_PUSH_MIRROR)) - die(_("--all and --mirror are incompatible")); if (recurse_submodules == RECURSE_SUBMODULES_CHECK) flags |= TRANSPORT_RECURSE_SUBMODULES_CHECK; @@ -632,11 +593,43 @@ int cmd_push(int argc, const char **argv, const char *prefix) set_refspecs(argv + 1, argc - 1, repo); } + remote = pushremote_get(repo); + if (!remote) { + if (repo) + die(_("bad repository '%s'"), repo); + die(_("No configured push destination.\n" + "Either specify the URL from the command-line or configure a remote repository using\n" + "\n" + " git remote add <name> <url>\n" + "\n" + "and then push using the remote name\n" + "\n" + " git push <name>\n")); + } + + if (remote->mirror) + flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE); + + if (flags & TRANSPORT_PUSH_ALL) { + if (tags) + die(_("--all and --tags are incompatible")); + if (argc >= 2) + die(_("--all can't be combined with refspecs")); + } + if (flags & TRANSPORT_PUSH_MIRROR) { + if (tags) + die(_("--mirror and --tags are incompatible")); + if (argc >= 2) + die(_("--mirror can't be combined with refspecs")); + } + if ((flags & TRANSPORT_PUSH_ALL) && (flags & TRANSPORT_PUSH_MIRROR)) + die(_("--all and --mirror are incompatible")); + for_each_string_list_item(item, push_options) if (strchr(item->string, '\n')) die(_("push options must not have new line characters")); - rc = do_push(repo, flags, push_options); + rc = do_push(flags, push_options, remote); string_list_clear(&push_options_cmdline, 0); string_list_clear(&push_options_config, 0); if (rc == -1) diff --git a/third_party/git/builtin/range-diff.c b/third_party/git/builtin/range-diff.c index 9202e7554476..24c4162f7446 100644 --- a/third_party/git/builtin/range-diff.c +++ b/third_party/git/builtin/range-diff.c @@ -15,12 +15,16 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) { int creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT; struct diff_options diffopt = { NULL }; + struct strvec other_arg = STRVEC_INIT; int simple_color = -1; struct option range_diff_options[] = { OPT_INTEGER(0, "creation-factor", &creation_factor, N_("Percentage by which creation is weighted")), OPT_BOOL(0, "no-dual-color", &simple_color, N_("use simple diff colors")), + OPT_PASSTHRU_ARGV(0, "notes", &other_arg, + N_("notes"), N_("passed to 'git log'"), + PARSE_OPT_OPTARG), OPT_END() }; struct option *options; @@ -78,8 +82,9 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) FREE_AND_NULL(options); res = show_range_diff(range1.buf, range2.buf, creation_factor, - simple_color < 1, &diffopt); + simple_color < 1, &diffopt, &other_arg); + strvec_clear(&other_arg); strbuf_release(&range1); strbuf_release(&range2); diff --git a/third_party/git/builtin/read-tree.c b/third_party/git/builtin/read-tree.c index ca5e655d2f8b..485e7b047948 100644 --- a/third_party/git/builtin/read-tree.c +++ b/third_party/git/builtin/read-tree.c @@ -120,9 +120,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) int prefix_set = 0; struct lock_file lock_file = LOCK_INIT; const struct option read_tree_options[] = { - { OPTION_CALLBACK, 0, "index-output", NULL, N_("file"), + OPT_CALLBACK_F(0, "index-output", NULL, N_("file"), N_("write resulting index to <file>"), - PARSE_OPT_NONEG, index_output_cb }, + PARSE_OPT_NONEG, index_output_cb), OPT_BOOL(0, "empty", &read_empty, N_("only empty the index")), OPT__VERBOSE(&opts.verbose_update, N_("be verbose")), @@ -140,10 +140,10 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) PARSE_OPT_NONEG }, OPT_BOOL('u', NULL, &opts.update, N_("update working tree with merge result")), - { OPTION_CALLBACK, 0, "exclude-per-directory", &opts, + OPT_CALLBACK_F(0, "exclude-per-directory", &opts, N_("gitignore"), N_("allow explicitly ignored files to be overwritten"), - PARSE_OPT_NONEG, exclude_per_directory_cb }, + PARSE_OPT_NONEG, exclude_per_directory_cb), OPT_BOOL('i', NULL, &opts.index_only, N_("don't check the working tree after merging")), OPT__DRY_RUN(&opts.dry_run, N_("don't update the index or the work tree")), @@ -151,9 +151,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) N_("skip applying sparse checkout filter")), OPT_BOOL(0, "debug-unpack", &opts.debug_unpack, N_("debug unpack-trees")), - { 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__QUIET(&opts.quiet, N_("suppress feedback messages")), OPT_END() }; @@ -185,7 +185,7 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) if (opts.reset || opts.merge || opts.prefix) { if (read_cache_unmerged() && (opts.prefix || opts.merge)) - die("You need to resolve your current index first"); + die(_("You need to resolve your current index first")); stage = opts.merge = 1; } resolve_undo_clear(); diff --git a/third_party/git/builtin/rebase.c b/third_party/git/builtin/rebase.c index 670096c065f5..eeca53382f79 100644 --- a/third_party/git/builtin/rebase.c +++ b/third_party/git/builtin/rebase.c @@ -8,7 +8,7 @@ #include "builtin.h" #include "run-command.h" #include "exec-cmd.h" -#include "argv-array.h" +#include "strvec.h" #include "dir.h" #include "packfile.h" #include "refs.h" @@ -27,10 +27,13 @@ #include "branch.h" #include "sequencer.h" #include "rebase-interactive.h" +#include "reset.h" + +#define DEFAULT_REFLOG_ACTION "rebase" static char const * const builtin_rebase_usage[] = { - N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " - "[<upstream>] [<branch>]"), + N_("git rebase [-i] [options] [--exec <cmd>] " + "[--onto <newbase> | --keep-base] [<upstream> [<branch>]]"), N_("git rebase [-i] [options] [--exec <cmd>] [--onto <newbase>] " "--root [<branch>]"), N_("git rebase --continue | --abort | --skip | --edit-todo"), @@ -44,14 +47,22 @@ static GIT_PATH_FUNC(merge_dir, "rebase-merge") enum rebase_type { REBASE_UNSPECIFIED = -1, - REBASE_AM, + REBASE_APPLY, REBASE_MERGE, - REBASE_INTERACTIVE, REBASE_PRESERVE_MERGES }; +enum empty_type { + EMPTY_UNSPECIFIED = -1, + EMPTY_DROP, + EMPTY_KEEP, + EMPTY_ASK +}; + struct rebase_options { enum rebase_type type; + enum empty_type empty; + const char *default_backend; const char *state_dir; struct commit *upstream; const char *upstream_name; @@ -62,7 +73,7 @@ struct rebase_options { const char *onto_name; const char *revisions; const char *switch_to; - int root; + int root, root_with_onto; struct object_id *squash_onto; struct commit *restrict_revision; int dont_finish_rebase; @@ -73,7 +84,7 @@ struct rebase_options { REBASE_FORCE = 1<<3, REBASE_INTERACTIVE_EXPLICIT = 1<<4, } flags; - struct argv_array git_am_opts; + struct strvec git_am_opts; const char *action; int signoff; int allow_rerere_autoupdate; @@ -81,6 +92,8 @@ struct rebase_options { int autosquash; char *gpg_sign_opt; int autostash; + int committer_date_is_author_date; + int ignore_date; char *cmd; int allow_empty_message; int rebase_merges, rebase_cousins; @@ -88,12 +101,16 @@ struct rebase_options { struct strbuf git_format_patch_opt; int reschedule_failed_exec; int use_legacy_rebase; + int reapply_cherry_picks; }; #define REBASE_OPTIONS_INIT { \ .type = REBASE_UNSPECIFIED, \ + .empty = EMPTY_UNSPECIFIED, \ + .keep_empty = 1, \ + .default_backend = "merge", \ .flags = REBASE_NO_QUIET, \ - .git_am_opts = ARGV_ARRAY_INIT, \ + .git_am_opts = STRVEC_INIT, \ .git_format_patch_opt = STRBUF_INIT \ } @@ -110,13 +127,25 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) replay.allow_rerere_auto = opts->allow_rerere_autoupdate; replay.allow_empty = 1; replay.allow_empty_message = opts->allow_empty_message; + replay.drop_redundant_commits = (opts->empty == EMPTY_DROP); + replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP); + replay.quiet = !(opts->flags & REBASE_NO_QUIET); replay.verbose = opts->flags & REBASE_VERBOSE; replay.reschedule_failed_exec = opts->reschedule_failed_exec; + replay.committer_date_is_author_date = + opts->committer_date_is_author_date; + replay.ignore_date = opts->ignore_date; replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt); replay.strategy = opts->strategy; + if (opts->strategy_opts) parse_strategy_opts(&replay, opts->strategy_opts); + if (opts->squash_onto) { + oidcpy(&replay.squash_onto, opts->squash_onto); + replay.have_squash_onto = 1; + } + return replay; } @@ -241,21 +270,17 @@ static int edit_todo_file(unsigned flags) } static int get_revision_ranges(struct commit *upstream, struct commit *onto, - const char **head_hash, + struct object_id *orig_head, const char **head_hash, char **revisions, char **shortrevisions) { struct commit *base_rev = upstream ? upstream : onto; const char *shorthead; - struct object_id orig_head; - if (get_oid("HEAD", &orig_head)) - return error(_("no HEAD?")); - - *head_hash = find_unique_abbrev(&orig_head, GIT_MAX_HEXSZ); + *head_hash = find_unique_abbrev(orig_head, GIT_MAX_HEXSZ); *revisions = xstrfmt("%s...%s", oid_to_hex(&base_rev->object.oid), *head_hash); - shorthead = find_unique_abbrev(&orig_head, DEFAULT_ABBREV); + shorthead = find_unique_abbrev(orig_head, DEFAULT_ABBREV); if (upstream) { const char *shortrev; @@ -304,17 +329,13 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) int ret; const char *head_hash = NULL; char *revisions = NULL, *shortrevisions = NULL; - struct argv_array make_script_args = ARGV_ARRAY_INIT; + struct strvec make_script_args = STRVEC_INIT; struct todo_list todo_list = TODO_LIST_INIT; struct replay_opts replay = get_replay_opts(opts); struct string_list commands = STRING_LIST_INIT_DUP; - if (prepare_branch_to_be_rebased(the_repository, &replay, - opts->switch_to)) - return -1; - - if (get_revision_ranges(opts->upstream, opts->onto, &head_hash, - &revisions, &shortrevisions)) + if (get_revision_ranges(opts->upstream, opts->onto, &opts->orig_head, + &head_hash, &revisions, &shortrevisions)) return -1; if (init_basic_state(&replay, @@ -330,13 +351,13 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) write_file(path_squash_onto(), "%s\n", oid_to_hex(opts->squash_onto)); - argv_array_pushl(&make_script_args, "", revisions, NULL); + strvec_pushl(&make_script_args, "", revisions, NULL); if (opts->restrict_revision) - argv_array_push(&make_script_args, - oid_to_hex(&opts->restrict_revision->object.oid)); + strvec_pushf(&make_script_args, "^%s", + oid_to_hex(&opts->restrict_revision->object.oid)); ret = sequencer_make_script(the_repository, &todo_list.buf, - make_script_args.argc, make_script_args.argv, + make_script_args.nr, make_script_args.v, flags); if (ret) @@ -357,12 +378,12 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) free(revisions); free(shortrevisions); todo_list_release(&todo_list); - argv_array_clear(&make_script_args); + strvec_clear(&make_script_args); return ret; } -static int run_rebase_interactive(struct rebase_options *opts, +static int run_sequencer_rebase(struct rebase_options *opts, enum action command) { unsigned flags = 0; @@ -374,7 +395,9 @@ static int run_rebase_interactive(struct rebase_options *opts, flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0; flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0; flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0; + flags |= opts->root_with_onto ? TODO_LIST_ROOT_WITH_ONTO : 0; flags |= command == ACTION_SHORTEN_OIDS ? TODO_LIST_SHORTEN_IDS : 0; + flags |= opts->reapply_cherry_picks ? TODO_LIST_REAPPLY_CHERRY_PICKS : 0; switch (command) { case ACTION_NONE: { @@ -403,7 +426,7 @@ static int run_rebase_interactive(struct rebase_options *opts, struct child_process cmd = CHILD_PROCESS_INIT; cmd.git_cmd = 1; - argv_array_pushl(&cmd.args, "show", "REBASE_HEAD", "--", NULL); + strvec_pushl(&cmd.args, "show", "REBASE_HEAD", "--", NULL); ret = run_command(&cmd); break; @@ -433,6 +456,20 @@ static int run_rebase_interactive(struct rebase_options *opts, return ret; } +static void imply_merge(struct rebase_options *opts, const char *option); +static int parse_opt_keep_empty(const struct option *opt, const char *arg, + int unset) +{ + struct rebase_options *opts = opt->value; + + BUG_ON_OPT_ARG(arg); + + imply_merge(opts, unset ? "--no-keep-empty" : "--keep-empty"); + opts->keep_empty = !unset; + opts->type = REBASE_MERGE; + return 0; +} + static const char * const builtin_rebase_interactive_usage[] = { N_("git rebase--interactive [<options>]"), NULL @@ -446,9 +483,13 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) struct option options[] = { OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"), REBASE_FORCE), - OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")), - OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message, - N_("allow commits with empty messages")), + OPT_CALLBACK_F('k', "keep-empty", &options, NULL, + N_("keep commits which start empty"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, + parse_opt_keep_empty), + OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message, + N_("allow commits with empty messages"), + PARSE_OPT_HIDDEN), OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")), OPT_BOOL(0, "rebase-cousins", &opts.rebase_cousins, N_("keep original branch points of cousins")), @@ -518,28 +559,26 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) warning(_("--[no-]rebase-cousins has no effect without " "--rebase-merges")); - return !!run_rebase_interactive(&opts, command); + return !!run_sequencer_rebase(&opts, command); } -static int is_interactive(struct rebase_options *opts) +static int is_merge(struct rebase_options *opts) { - return opts->type == REBASE_INTERACTIVE || + return opts->type == REBASE_MERGE || opts->type == REBASE_PRESERVE_MERGES; } -static void imply_interactive(struct rebase_options *opts, const char *option) +static void imply_merge(struct rebase_options *opts, const char *option) { switch (opts->type) { - case REBASE_AM: - die(_("%s requires an interactive rebase"), option); + case REBASE_APPLY: + die(_("%s requires the merge backend"), option); break; - case REBASE_INTERACTIVE: + case REBASE_MERGE: case REBASE_PRESERVE_MERGES: break; - case REBASE_MERGE: - /* we now implement --merge via --interactive */ default: - opts->type = REBASE_INTERACTIVE; /* implied */ + opts->type = REBASE_MERGE; /* implied */ break; } } @@ -560,15 +599,6 @@ static const char *state_dir_path(const char *filename, struct rebase_options *o return path.buf; } -/* Read one file, then strip line endings */ -static int read_one(const char *path, struct strbuf *buf) -{ - if (strbuf_read_file(buf, path, 0) < 0) - return error_errno(_("could not read '%s'"), path); - strbuf_trim_trailing_newline(buf); - return 0; -} - /* Initialize the rebase options from the state directory. */ static int read_basic_state(struct rebase_options *opts) { @@ -576,8 +606,10 @@ static int read_basic_state(struct rebase_options *opts) struct strbuf buf = STRBUF_INIT; struct object_id oid; - if (read_one(state_dir_path("head-name", opts), &head_name) || - read_one(state_dir_path("onto", opts), &buf)) + if (!read_oneliner(&head_name, state_dir_path("head-name", opts), + READ_ONELINER_WARN_MISSING) || + !read_oneliner(&buf, state_dir_path("onto", opts), + READ_ONELINER_WARN_MISSING)) return -1; opts->head_name = starts_with(head_name.buf, "refs/") ? xstrdup(head_name.buf) : NULL; @@ -593,9 +625,11 @@ static int read_basic_state(struct rebase_options *opts) */ strbuf_reset(&buf); if (file_exists(state_dir_path("orig-head", opts))) { - if (read_one(state_dir_path("orig-head", opts), &buf)) + if (!read_oneliner(&buf, state_dir_path("orig-head", opts), + READ_ONELINER_WARN_MISSING)) return -1; - } else if (read_one(state_dir_path("head", opts), &buf)) + } else if (!read_oneliner(&buf, state_dir_path("head", opts), + READ_ONELINER_WARN_MISSING)) return -1; if (get_oid(buf.buf, &opts->orig_head)) return error(_("invalid orig-head: '%s'"), buf.buf); @@ -615,8 +649,8 @@ static int read_basic_state(struct rebase_options *opts) if (file_exists(state_dir_path("allow_rerere_autoupdate", opts))) { strbuf_reset(&buf); - if (read_one(state_dir_path("allow_rerere_autoupdate", opts), - &buf)) + if (!read_oneliner(&buf, state_dir_path("allow_rerere_autoupdate", opts), + READ_ONELINER_WARN_MISSING)) return -1; if (!strcmp(buf.buf, "--rerere-autoupdate")) opts->allow_rerere_autoupdate = RERERE_AUTOUPDATE; @@ -629,8 +663,8 @@ static int read_basic_state(struct rebase_options *opts) if (file_exists(state_dir_path("gpg_sign_opt", opts))) { strbuf_reset(&buf); - if (read_one(state_dir_path("gpg_sign_opt", opts), - &buf)) + if (!read_oneliner(&buf, state_dir_path("gpg_sign_opt", opts), + READ_ONELINER_WARN_MISSING)) return -1; free(opts->gpg_sign_opt); opts->gpg_sign_opt = xstrdup(buf.buf); @@ -638,7 +672,8 @@ static int read_basic_state(struct rebase_options *opts) if (file_exists(state_dir_path("strategy", opts))) { strbuf_reset(&buf); - if (read_one(state_dir_path("strategy", opts), &buf)) + if (!read_oneliner(&buf, state_dir_path("strategy", opts), + READ_ONELINER_WARN_MISSING)) return -1; free(opts->strategy); opts->strategy = xstrdup(buf.buf); @@ -646,7 +681,8 @@ static int read_basic_state(struct rebase_options *opts) if (file_exists(state_dir_path("strategy_opts", opts))) { strbuf_reset(&buf); - if (read_one(state_dir_path("strategy_opts", opts), &buf)) + if (!read_oneliner(&buf, state_dir_path("strategy_opts", opts), + READ_ONELINER_WARN_MISSING)) return -1; free(opts->strategy_opts); opts->strategy_opts = xstrdup(buf.buf); @@ -665,8 +701,8 @@ static int rebase_write_basic_state(struct rebase_options *opts) opts->onto ? oid_to_hex(&opts->onto->object.oid) : ""); write_file(state_dir_path("orig-head", opts), "%s", oid_to_hex(&opts->orig_head)); - write_file(state_dir_path("quiet", opts), "%s", - opts->flags & REBASE_NO_QUIET ? "" : "t"); + if (!(opts->flags & REBASE_NO_QUIET)) + write_file(state_dir_path("quiet", opts), "%s", ""); if (opts->flags & REBASE_VERBOSE) write_file(state_dir_path("verbose", opts), "%s", ""); if (opts->strategy) @@ -684,71 +720,25 @@ static int rebase_write_basic_state(struct rebase_options *opts) write_file(state_dir_path("gpg_sign_opt", opts), "%s", opts->gpg_sign_opt); if (opts->signoff) - write_file(state_dir_path("strategy", opts), "--signoff"); + write_file(state_dir_path("signoff", opts), "--signoff"); return 0; } -static int apply_autostash(struct rebase_options *opts) -{ - const char *path = state_dir_path("autostash", opts); - struct strbuf autostash = STRBUF_INIT; - struct child_process stash_apply = CHILD_PROCESS_INIT; - - if (!file_exists(path)) - return 0; - - if (read_one(path, &autostash)) - return error(_("Could not read '%s'"), path); - /* Ensure that the hash is not mistaken for a number */ - strbuf_addstr(&autostash, "^0"); - argv_array_pushl(&stash_apply.args, - "stash", "apply", autostash.buf, NULL); - stash_apply.git_cmd = 1; - stash_apply.no_stderr = stash_apply.no_stdout = - stash_apply.no_stdin = 1; - if (!run_command(&stash_apply)) - printf(_("Applied autostash.\n")); - else { - struct argv_array args = ARGV_ARRAY_INIT; - int res = 0; - - argv_array_pushl(&args, - "stash", "store", "-m", "autostash", "-q", - autostash.buf, NULL); - if (run_command_v_opt(args.argv, RUN_GIT_CMD)) - res = error(_("Cannot store %s"), autostash.buf); - argv_array_clear(&args); - strbuf_release(&autostash); - if (res) - return res; - - fprintf(stderr, - _("Applying autostash resulted in conflicts.\n" - "Your changes are safe in the stash.\n" - "You can run \"git stash pop\" or \"git stash drop\" " - "at any time.\n")); - } - - strbuf_release(&autostash); - return 0; -} - static int finish_rebase(struct rebase_options *opts) { struct strbuf dir = STRBUF_INIT; - const char *argv_gc_auto[] = { "gc", "--auto", NULL }; int ret = 0; delete_ref(NULL, "REBASE_HEAD", NULL, REF_NO_DEREF); - apply_autostash(opts); + apply_autostash(state_dir_path("autostash", opts)); close_object_store(the_repository->objects); /* - * We ignore errors in 'gc --auto', since the + * We ignore errors in 'git maintenance run --auto', since the * user should see them. */ - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); - if (opts->type == REBASE_INTERACTIVE) { + run_auto_maintenance(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE))); + if (opts->type == REBASE_MERGE) { struct replay_opts replay = REPLAY_OPTS_INIT; replay.action = REPLAY_INTERACTIVE_REBASE; @@ -786,143 +776,6 @@ static void add_var(struct strbuf *buf, const char *name, const char *value) } } -#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION" - -#define RESET_HEAD_DETACH (1<<0) -#define RESET_HEAD_HARD (1<<1) -#define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2) -#define RESET_HEAD_REFS_ONLY (1<<3) -#define RESET_ORIG_HEAD (1<<4) - -static int reset_head(struct object_id *oid, const char *action, - const char *switch_to_branch, unsigned flags, - const char *reflog_orig_head, const char *reflog_head) -{ - unsigned detach_head = flags & RESET_HEAD_DETACH; - unsigned reset_hard = flags & RESET_HEAD_HARD; - unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK; - unsigned refs_only = flags & RESET_HEAD_REFS_ONLY; - unsigned update_orig_head = flags & RESET_ORIG_HEAD; - struct object_id head_oid; - struct tree_desc desc[2] = { { NULL }, { NULL } }; - struct lock_file lock = LOCK_INIT; - struct unpack_trees_options unpack_tree_opts; - struct tree *tree; - const char *reflog_action; - struct strbuf msg = STRBUF_INIT; - size_t prefix_len; - struct object_id *orig = NULL, oid_orig, - *old_orig = NULL, oid_old_orig; - int ret = 0, nr = 0; - - if (switch_to_branch && !starts_with(switch_to_branch, "refs/")) - BUG("Not a fully qualified branch: '%s'", switch_to_branch); - - if (!refs_only && hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) { - ret = -1; - goto leave_reset_head; - } - - if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) { - ret = error(_("could not determine HEAD revision")); - goto leave_reset_head; - } - - if (!oid) - oid = &head_oid; - - if (refs_only) - goto reset_head_refs; - - memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts)); - setup_unpack_trees_porcelain(&unpack_tree_opts, action); - unpack_tree_opts.head_idx = 1; - unpack_tree_opts.src_index = the_repository->index; - unpack_tree_opts.dst_index = the_repository->index; - unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge; - unpack_tree_opts.update = 1; - unpack_tree_opts.merge = 1; - if (!detach_head) - unpack_tree_opts.reset = 1; - - if (repo_read_index_unmerged(the_repository) < 0) { - ret = error(_("could not read index")); - goto leave_reset_head; - } - - if (!reset_hard && !fill_tree_descriptor(the_repository, &desc[nr++], &head_oid)) { - ret = error(_("failed to find tree of %s"), - oid_to_hex(&head_oid)); - goto leave_reset_head; - } - - if (!fill_tree_descriptor(the_repository, &desc[nr++], oid)) { - ret = error(_("failed to find tree of %s"), oid_to_hex(oid)); - goto leave_reset_head; - } - - if (unpack_trees(nr, desc, &unpack_tree_opts)) { - ret = -1; - goto leave_reset_head; - } - - tree = parse_tree_indirect(oid); - prime_cache_tree(the_repository, the_repository->index, tree); - - if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK) < 0) { - ret = error(_("could not write index")); - goto leave_reset_head; - } - -reset_head_refs: - reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); - strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase"); - prefix_len = msg.len; - - if (update_orig_head) { - if (!get_oid("ORIG_HEAD", &oid_old_orig)) - old_orig = &oid_old_orig; - if (!get_oid("HEAD", &oid_orig)) { - orig = &oid_orig; - if (!reflog_orig_head) { - strbuf_addstr(&msg, "updating ORIG_HEAD"); - reflog_orig_head = msg.buf; - } - update_ref(reflog_orig_head, "ORIG_HEAD", orig, - old_orig, 0, UPDATE_REFS_MSG_ON_ERR); - } else if (old_orig) - delete_ref(NULL, "ORIG_HEAD", old_orig, 0); - } - - if (!reflog_head) { - strbuf_setlen(&msg, prefix_len); - strbuf_addstr(&msg, "updating HEAD"); - reflog_head = msg.buf; - } - if (!switch_to_branch) - ret = update_ref(reflog_head, "HEAD", oid, orig, - detach_head ? REF_NO_DEREF : 0, - UPDATE_REFS_MSG_ON_ERR); - else { - ret = update_ref(reflog_head, switch_to_branch, oid, - NULL, 0, UPDATE_REFS_MSG_ON_ERR); - if (!ret) - ret = create_symref("HEAD", switch_to_branch, - reflog_head); - } - if (run_hook) - run_hook_le(NULL, "post-checkout", - oid_to_hex(orig ? orig : &null_oid), - oid_to_hex(oid), "1", NULL); - -leave_reset_head: - strbuf_release(&msg); - rollback_lock_file(&lock); - while (nr) - free((void *)desc[--nr].buffer); - return ret; -} - static int move_to_original_branch(struct rebase_options *opts) { struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT; @@ -938,8 +791,10 @@ static int move_to_original_branch(struct rebase_options *opts) opts->head_name, oid_to_hex(&opts->onto->object.oid)); strbuf_addf(&head_reflog, "rebase finished: returning to %s", opts->head_name); - ret = reset_head(NULL, "", opts->head_name, RESET_HEAD_REFS_ONLY, - orig_head_reflog.buf, head_reflog.buf); + ret = reset_head(the_repository, NULL, "", opts->head_name, + RESET_HEAD_REFS_ONLY, + orig_head_reflog.buf, head_reflog.buf, + DEFAULT_REFLOG_ACTION); strbuf_release(&orig_head_reflog); strbuf_release(&head_reflog); @@ -962,13 +817,13 @@ static int run_am(struct rebase_options *opts) char *rebased_patches; am.git_cmd = 1; - argv_array_push(&am.args, "am"); + strvec_push(&am.args, "am"); if (opts->action && !strcmp("continue", opts->action)) { - argv_array_push(&am.args, "--resolved"); - argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg); + strvec_push(&am.args, "--resolved"); + strvec_pushf(&am.args, "--resolvemsg=%s", resolvemsg); if (opts->gpg_sign_opt) - argv_array_push(&am.args, opts->gpg_sign_opt); + strvec_push(&am.args, opts->gpg_sign_opt); status = run_command(&am); if (status) return status; @@ -976,8 +831,8 @@ static int run_am(struct rebase_options *opts) return move_to_original_branch(opts); } if (opts->action && !strcmp("skip", opts->action)) { - argv_array_push(&am.args, "--skip"); - argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg); + strvec_push(&am.args, "--skip"); + strvec_pushf(&am.args, "--resolvemsg=%s", resolvemsg); status = run_command(&am); if (status) return status; @@ -985,7 +840,7 @@ static int run_am(struct rebase_options *opts) return move_to_original_branch(opts); } if (opts->action && !strcmp("show-current-patch", opts->action)) { - argv_array_push(&am.args, "--show-current-patch"); + strvec_push(&am.args, "--show-current-patch"); return run_command(&am); } @@ -1003,31 +858,33 @@ static int run_am(struct rebase_options *opts) status = error_errno(_("could not open '%s' for writing"), rebased_patches); free(rebased_patches); - argv_array_clear(&am.args); + strvec_clear(&am.args); return status; } format_patch.git_cmd = 1; - argv_array_pushl(&format_patch.args, "format-patch", "-k", "--stdout", - "--full-index", "--cherry-pick", "--right-only", - "--src-prefix=a/", "--dst-prefix=b/", "--no-renames", - "--no-cover-letter", "--pretty=mboxrd", "--topo-order", NULL); + strvec_pushl(&format_patch.args, "format-patch", "-k", "--stdout", + "--full-index", "--cherry-pick", "--right-only", + "--src-prefix=a/", "--dst-prefix=b/", "--no-renames", + "--no-cover-letter", "--pretty=mboxrd", "--topo-order", + "--no-base", NULL); if (opts->git_format_patch_opt.len) - argv_array_split(&format_patch.args, - opts->git_format_patch_opt.buf); - argv_array_push(&format_patch.args, revisions.buf); + strvec_split(&format_patch.args, + opts->git_format_patch_opt.buf); + strvec_push(&format_patch.args, revisions.buf); if (opts->restrict_revision) - argv_array_pushf(&format_patch.args, "^%s", - oid_to_hex(&opts->restrict_revision->object.oid)); + strvec_pushf(&format_patch.args, "^%s", + oid_to_hex(&opts->restrict_revision->object.oid)); status = run_command(&format_patch); if (status) { unlink(rebased_patches); free(rebased_patches); - argv_array_clear(&am.args); + strvec_clear(&am.args); - reset_head(&opts->orig_head, "checkout", opts->head_name, 0, - "HEAD", NULL); + reset_head(the_repository, &opts->orig_head, "checkout", + opts->head_name, 0, + "HEAD", NULL, DEFAULT_REFLOG_ACTION); error(_("\ngit encountered an error while preparing the " "patches to replay\n" "these revisions:\n" @@ -1045,20 +902,20 @@ static int run_am(struct rebase_options *opts) status = error_errno(_("could not open '%s' for reading"), rebased_patches); free(rebased_patches); - argv_array_clear(&am.args); + strvec_clear(&am.args); return status; } - argv_array_pushv(&am.args, opts->git_am_opts.argv); - argv_array_push(&am.args, "--rebasing"); - argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg); - argv_array_push(&am.args, "--patch-format=mboxrd"); + strvec_pushv(&am.args, opts->git_am_opts.v); + strvec_push(&am.args, "--rebasing"); + strvec_pushf(&am.args, "--resolvemsg=%s", resolvemsg); + strvec_push(&am.args, "--patch-format=mboxrd"); if (opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE) - argv_array_push(&am.args, "--rerere-autoupdate"); + strvec_push(&am.args, "--rerere-autoupdate"); else if (opts->allow_rerere_autoupdate == RERERE_NOAUTOUPDATE) - argv_array_push(&am.args, "--no-rerere-autoupdate"); + strvec_push(&am.args, "--no-rerere-autoupdate"); if (opts->gpg_sign_opt) - argv_array_push(&am.args, opts->gpg_sign_opt); + strvec_push(&am.args, opts->gpg_sign_opt); status = run_command(&am); unlink(rebased_patches); free(rebased_patches); @@ -1080,8 +937,8 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action) int status; const char *backend, *backend_func; - if (opts->type == REBASE_INTERACTIVE) { - /* Run builtin interactive rebase */ + if (opts->type == REBASE_MERGE) { + /* Run sequencer-based rebase */ setenv("GIT_CHERRY_PICK_HELP", resolvemsg, 1); if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) { setenv("GIT_SEQUENCE_EDITOR", ":", 1); @@ -1094,11 +951,11 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action) opts->gpg_sign_opt = tmp; } - status = run_rebase_interactive(opts, action); + status = run_sequencer_rebase(opts, action); goto finished_rebase; } - if (opts->type == REBASE_AM) { + if (opts->type == REBASE_APPLY) { status = run_am(opts); goto finished_rebase; } @@ -1118,9 +975,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action) add_var(&script_snippet, "revisions", opts->revisions); add_var(&script_snippet, "restrict_revision", opts->restrict_revision ? oid_to_hex(&opts->restrict_revision->object.oid) : NULL); - add_var(&script_snippet, "GIT_QUIET", - opts->flags & REBASE_NO_QUIET ? "" : "t"); - sq_quote_argv_pretty(&buf, opts->git_am_opts.argv); + sq_quote_argv_pretty(&buf, opts->git_am_opts.v); add_var(&script_snippet, "git_am_opt", buf.buf); strbuf_release(&buf); add_var(&script_snippet, "verbose", @@ -1155,7 +1010,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action) add_var(&script_snippet, "git_format_patch_opt", opts->git_format_patch_opt.buf); - if (is_interactive(opts) && + if (is_merge(opts) && !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) { strbuf_addstr(&script_snippet, "GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; "); @@ -1180,15 +1035,15 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action) finished_rebase: if (opts->dont_finish_rebase) ; /* do nothing */ - else if (opts->type == REBASE_INTERACTIVE) - ; /* interactive rebase cleans up after itself */ + else if (opts->type == REBASE_MERGE) + ; /* merge backend cleans up after itself */ else if (status == 0) { if (!file_exists(state_dir_path("stopped-sha", opts))) finish_rebase(opts); } else if (status == 2) { struct strbuf dir = STRBUF_INIT; - apply_autostash(opts); + apply_autostash(state_dir_path("autostash", opts)); strbuf_addstr(&dir, opts->state_dir); remove_dir_recursively(&dir, 0); strbuf_release(&dir); @@ -1239,6 +1094,10 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "rebase.backend")) { + return git_config_string(&opts->default_backend, var, value); + } + return git_default_config(var, value, data); } @@ -1260,28 +1119,60 @@ static int is_linear_history(struct commit *from, struct commit *to) return 1; } -static int can_fast_forward(struct commit *onto, struct object_id *head_oid, - struct object_id *merge_base) +static int can_fast_forward(struct commit *onto, struct commit *upstream, + struct commit *restrict_revision, + struct object_id *head_oid, struct object_id *merge_base) { struct commit *head = lookup_commit(the_repository, head_oid); - struct commit_list *merge_bases; - int res; + struct commit_list *merge_bases = NULL; + int res = 0; if (!head) - return 0; + goto done; merge_bases = get_merge_bases(onto, head); - if (merge_bases && !merge_bases->next) { - oidcpy(merge_base, &merge_bases->item->object.oid); - res = oideq(merge_base, &onto->object.oid); - } else { + if (!merge_bases || merge_bases->next) { oidcpy(merge_base, &null_oid); - res = 0; + goto done; } + + oidcpy(merge_base, &merge_bases->item->object.oid); + if (!oideq(merge_base, &onto->object.oid)) + goto done; + + if (restrict_revision && !oideq(&restrict_revision->object.oid, merge_base)) + goto done; + + if (!upstream) + goto done; + + free_commit_list(merge_bases); + merge_bases = get_merge_bases(upstream, head); + if (!merge_bases || merge_bases->next) + goto done; + + if (!oideq(&onto->object.oid, &merge_bases->item->object.oid)) + goto done; + + res = 1; + +done: free_commit_list(merge_bases); return res && is_linear_history(onto, head); } +static int parse_opt_am(const struct option *opt, const char *arg, int unset) +{ + struct rebase_options *opts = opt->value; + + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + + opts->type = REBASE_APPLY; + + return 0; +} + /* -i followed by -m is still -i */ static int parse_opt_merge(const struct option *opt, const char *arg, int unset) { @@ -1290,7 +1181,7 @@ static int parse_opt_merge(const struct option *opt, const char *arg, int unset) BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(arg); - if (!is_interactive(opts)) + if (!is_merge(opts)) opts->type = REBASE_MERGE; return 0; @@ -1305,12 +1196,35 @@ static int parse_opt_interactive(const struct option *opt, const char *arg, BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(arg); - opts->type = REBASE_INTERACTIVE; + opts->type = REBASE_MERGE; opts->flags |= REBASE_INTERACTIVE_EXPLICIT; return 0; } +static enum empty_type parse_empty_value(const char *value) +{ + if (!strcasecmp(value, "drop")) + return EMPTY_DROP; + else if (!strcasecmp(value, "keep")) + return EMPTY_KEEP; + else if (!strcasecmp(value, "ask")) + return EMPTY_ASK; + + die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value); +} + +static int parse_opt_empty(const struct option *opt, const char *arg, int unset) +{ + struct rebase_options *options = opt->value; + enum empty_type value = parse_empty_value(arg); + + BUG_ON_OPT_NEG(unset); + + options->empty = value; + return 0; +} + static void NORETURN error_on_missing_default_upstream(void) { struct branch *current_branch = branch_get(NULL); @@ -1346,14 +1260,14 @@ static void set_reflog_action(struct rebase_options *options) const char *env; struct strbuf buf = STRBUF_INIT; - if (!is_interactive(options)) + if (!is_merge(options)) return; env = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); if (env && strcmp("rebase", env)) return; /* only override it if it is "rebase" */ - strbuf_addf(&buf, "rebase -i (%s)", options->action); + strbuf_addf(&buf, "rebase (%s)", options->action); setenv(GIT_REFLOG_ACTION_ENVIRONMENT, buf.buf, 1); strbuf_release(&buf); } @@ -1370,17 +1284,18 @@ static int check_exec_cmd(const char *cmd) return 0; } - int cmd_rebase(int argc, const char **argv, const char *prefix) { struct rebase_options options = REBASE_OPTIONS_INIT; const char *branch_name; int ret, flags, total_argc, in_progress = 0; + int keep_base = 0; int ok_to_skip_pre_rebase = 0; struct strbuf msg = STRBUF_INIT; struct strbuf revisions = STRBUF_INIT; struct strbuf buf = STRBUF_INIT; struct object_id merge_base; + int ignore_whitespace = 0; enum action action = ACTION_NONE; const char *gpg_sign = NULL; struct string_list exec = STRING_LIST_INIT_NODUP; @@ -1390,15 +1305,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct object_id squash_onto; char *squash_onto_name = NULL; int reschedule_failed_exec = -1; + int allow_preemptive_ff = 1; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), N_("rebase onto given branch instead of upstream")), + OPT_BOOL(0, "keep-base", &keep_base, + N_("use the merge-base of upstream and branch as the current base")), OPT_BOOL(0, "no-verify", &ok_to_skip_pre_rebase, N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", &options.flags, N_("be quiet. implies --no-stat"), - REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT), + REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT), OPT_BIT('v', "verbose", &options.flags, N_("display a diffstat of what changed upstream"), REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT), @@ -1407,16 +1325,17 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, OPT_BOOL(0, "signoff", &options.signoff, N_("add a Signed-off-by: line to each commit")), - OPT_PASSTHRU_ARGV(0, "ignore-whitespace", &options.git_am_opts, - NULL, N_("passed to 'git am'"), - PARSE_OPT_NOARG), - OPT_PASSTHRU_ARGV(0, "committer-date-is-author-date", - &options.git_am_opts, NULL, - N_("passed to 'git am'"), PARSE_OPT_NOARG), - OPT_PASSTHRU_ARGV(0, "ignore-date", &options.git_am_opts, NULL, - N_("passed to 'git am'"), PARSE_OPT_NOARG), + OPT_BOOL(0, "committer-date-is-author-date", + &options.committer_date_is_author_date, + N_("make committer date match author date")), + OPT_BOOL(0, "reset-author-date", &options.ignore_date, + N_("ignore author date and use current date")), + OPT_HIDDEN_BOOL(0, "ignore-date", &options.ignore_date, + N_("synonym of --reset-author-date")), OPT_PASSTHRU_ARGV('C', NULL, &options.git_am_opts, N_("n"), N_("passed to 'git apply'"), 0), + OPT_BOOL(0, "ignore-whitespace", &ignore_whitespace, + N_("ignore changes in whitespace")), OPT_PASSTHRU_ARGV(0, "whitespace", &options.git_am_opts, N_("action"), N_("passed to 'git apply'"), 0), OPT_BIT('f', "force-rebase", &options.flags, @@ -1439,34 +1358,44 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "show-current-patch", &action, N_("show the patch file being applied or merged"), ACTION_SHOW_CURRENT_PATCH), - { OPTION_CALLBACK, 'm', "merge", &options, NULL, + OPT_CALLBACK_F(0, "apply", &options, NULL, + N_("use apply strategies to rebase"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + parse_opt_am), + OPT_CALLBACK_F('m', "merge", &options, NULL, N_("use merging strategies to rebase"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, - parse_opt_merge }, - { OPTION_CALLBACK, 'i', "interactive", &options, NULL, + parse_opt_merge), + OPT_CALLBACK_F('i', "interactive", &options, NULL, N_("let the user edit the list of commits to rebase"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, - parse_opt_interactive }, - OPT_SET_INT('p', "preserve-merges", &options.type, - N_("(DEPRECATED) try to recreate merges instead of " - "ignoring them"), REBASE_PRESERVE_MERGES), + parse_opt_interactive), + OPT_SET_INT_F('p', "preserve-merges", &options.type, + N_("(DEPRECATED) try to recreate merges instead of " + "ignoring them"), + REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN), OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate), - OPT_BOOL('k', "keep-empty", &options.keep_empty, - N_("preserve empty commits during rebase")), + OPT_CALLBACK_F(0, "empty", &options, "{drop,keep,ask}", + N_("how to handle commits that become empty"), + PARSE_OPT_NONEG, parse_opt_empty), + OPT_CALLBACK_F('k', "keep-empty", &options, NULL, + N_("keep commits which start empty"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, + parse_opt_keep_empty), OPT_BOOL(0, "autosquash", &options.autosquash, N_("move commits that begin with " "squash!/fixup! under -i")), { OPTION_STRING, 'S', "gpg-sign", &gpg_sign, N_("key-id"), N_("GPG-sign commits"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, - OPT_BOOL(0, "autostash", &options.autostash, - N_("automatically stash/stash pop before and after")), + OPT_AUTOSTASH(&options.autostash), OPT_STRING_LIST('x', "exec", &exec, N_("exec"), N_("add exec lines after each commit of the " "editable list")), - OPT_BOOL(0, "allow-empty-message", - &options.allow_empty_message, - N_("allow rebasing commits with empty messages")), + OPT_BOOL_F(0, "allow-empty-message", + &options.allow_empty_message, + N_("allow rebasing commits with empty messages"), + PARSE_OPT_HIDDEN), {OPTION_STRING, 'r', "rebase-merges", &rebase_merges, N_("mode"), N_("try to rebase merges instead of skipping them"), @@ -1484,6 +1413,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "reschedule-failed-exec", &reschedule_failed_exec, N_("automatically re-schedule any `exec` that fails")), + OPT_BOOL(0, "reapply-cherry-picks", &options.reapply_cherry_picks, + N_("apply all changes, even those already present upstream")), OPT_END(), }; int i; @@ -1494,6 +1425,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.allow_empty_message = 1; git_config(rebase_config, &options); + /* options.gpg_sign_opt will be either "-S" or NULL */ + gpg_sign = options.gpg_sign_opt ? "" : NULL; + FREE_AND_NULL(options.gpg_sign_opt); if (options.use_legacy_rebase || !git_env_bool("GIT_TEST_REBASE_USE_BUILTIN", -1)) @@ -1506,7 +1440,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("It looks like 'git am' is in progress. Cannot rebase.")); if (is_directory(apply_dir())) { - options.type = REBASE_AM; + options.type = REBASE_APPLY; options.state_dir = apply_dir(); } else if (is_directory(merge_dir())) { strbuf_reset(&buf); @@ -1518,7 +1452,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_reset(&buf); strbuf_addf(&buf, "%s/interactive", merge_dir()); if(file_exists(buf.buf)) { - options.type = REBASE_INTERACTIVE; + options.type = REBASE_MERGE; options.flags |= REBASE_INTERACTIVE_EXPLICIT; } else options.type = REBASE_MERGE; @@ -1547,16 +1481,26 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) warning(_("git rebase --preserve-merges is deprecated. " "Use --rebase-merges instead.")); + if (keep_base) { + if (options.onto_name) + die(_("cannot combine '--keep-base' with '--onto'")); + if (options.root) + die(_("cannot combine '--keep-base' with '--root'")); + } + + if (options.root && fork_point > 0) + die(_("cannot combine '--root' with '--fork-point'")); + if (action != ACTION_NONE && !in_progress) die(_("No rebase in progress?")); setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); - if (action == ACTION_EDIT_TODO && !is_interactive(&options)) + if (action == ACTION_EDIT_TODO && !is_merge(&options)) die(_("The --edit-todo action can only be used during " "interactive rebase.")); if (trace2_is_enabled()) { - if (is_interactive(&options)) + if (is_merge(&options)) trace2_cmd_mode("interactive"); else if (exec.nr) trace2_cmd_mode("interactive-exec"); @@ -1604,8 +1548,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) rerere_clear(the_repository, &merge_rr); string_list_clear(&merge_rr, 1); - if (reset_head(NULL, "reset", NULL, RESET_HEAD_HARD, - NULL, NULL) < 0) + if (reset_head(the_repository, NULL, "reset", NULL, RESET_HEAD_HARD, + NULL, NULL, DEFAULT_REFLOG_ACTION) < 0) die(_("could not discard worktree changes")); remove_branch_state(the_repository, 0); if (read_basic_state(&options)) @@ -1622,9 +1566,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (read_basic_state(&options)) exit(1); - if (reset_head(&options.orig_head, "reset", + if (reset_head(the_repository, &options.orig_head, "reset", options.head_name, RESET_HEAD_HARD, - NULL, NULL) < 0) + NULL, NULL, DEFAULT_REFLOG_ACTION) < 0) die(_("could not move back to %s"), oid_to_hex(&options.orig_head)); remove_branch_state(the_repository, 0); @@ -1632,7 +1576,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) goto cleanup; } case ACTION_QUIT: { - if (options.type == REBASE_INTERACTIVE) { + save_autostash(state_dir_path("autostash", &options)); + if (options.type == REBASE_MERGE) { struct replay_opts replay = REPLAY_OPTS_INIT; replay.action = REPLAY_INTERACTIVE_REBASE; @@ -1681,13 +1626,20 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) state_dir_base, cmd_live_rebase, buf.buf); } - for (i = 0; i < options.git_am_opts.argc; i++) { - const char *option = options.git_am_opts.argv[i], *p; - if (!strcmp(option, "--committer-date-is-author-date") || - !strcmp(option, "--ignore-date") || - !strcmp(option, "--whitespace=fix") || + if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) || + (action != ACTION_NONE) || + (exec.nr > 0) || + options.autosquash) { + allow_preemptive_ff = 0; + } + if (options.committer_date_is_author_date || options.ignore_date) + options.flags |= REBASE_FORCE; + + for (i = 0; i < options.git_am_opts.nr; i++) { + const char *option = options.git_am_opts.v[i], *p; + if (!strcmp(option, "--whitespace=fix") || !strcmp(option, "--whitespace=strip")) - options.flags |= REBASE_FORCE; + allow_preemptive_ff = 0; else if (skip_prefix(option, "-C", &p)) { while (*p) if (!isdigit(*(p++))) @@ -1705,20 +1657,21 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) exit(1); if (!(options.flags & REBASE_NO_QUIET)) - argv_array_push(&options.git_am_opts, "-q"); + strvec_push(&options.git_am_opts, "-q"); + + if (options.empty != EMPTY_UNSPECIFIED) + imply_merge(&options, "--empty"); - if (options.keep_empty) - imply_interactive(&options, "--keep-empty"); + if (options.reapply_cherry_picks) + imply_merge(&options, "--reapply-cherry-picks"); - if (gpg_sign) { - free(options.gpg_sign_opt); + if (gpg_sign) options.gpg_sign_opt = xstrfmt("-S%s", gpg_sign); - } if (exec.nr) { int i; - imply_interactive(&options, "--exec"); + imply_merge(&options, "--exec"); strbuf_reset(&buf); for (i = 0; i < exec.nr; i++) @@ -1734,7 +1687,24 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) else if (strcmp("no-rebase-cousins", rebase_merges)) die(_("Unknown mode: %s"), rebase_merges); options.rebase_merges = 1; - imply_interactive(&options, "--rebase-merges"); + imply_merge(&options, "--rebase-merges"); + } + + if (options.type == REBASE_APPLY) { + if (ignore_whitespace) + strvec_push(&options.git_am_opts, + "--ignore-whitespace"); + if (options.committer_date_is_author_date) + strvec_push(&options.git_am_opts, + "--committer-date-is-author-date"); + if (options.ignore_date) + strvec_push(&options.git_am_opts, "--ignore-date"); + } else { + /* REBASE_MERGE and PRESERVE_MERGES */ + if (ignore_whitespace) { + string_list_append(&strategy_options, + "ignore-space-change"); + } } if (strategy_options.nr) { @@ -1753,10 +1723,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (options.strategy) { options.strategy = xstrdup(options.strategy); switch (options.type) { - case REBASE_AM: + case REBASE_APPLY: die(_("--strategy requires --merge or --interactive")); case REBASE_MERGE: - case REBASE_INTERACTIVE: case REBASE_PRESERVE_MERGES: /* compatible */ break; @@ -1769,52 +1738,70 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } if (options.type == REBASE_MERGE) - imply_interactive(&options, "--merge"); + imply_merge(&options, "--merge"); if (options.root && !options.onto_name) - imply_interactive(&options, "--root without --onto"); + imply_merge(&options, "--root without --onto"); if (isatty(2) && options.flags & REBASE_NO_QUIET) strbuf_addstr(&options.git_format_patch_opt, " --progress"); + if (options.git_am_opts.nr || options.type == REBASE_APPLY) { + /* all am options except -q are compatible only with --apply */ + for (i = options.git_am_opts.nr - 1; i >= 0; i--) + if (strcmp(options.git_am_opts.v[i], "-q")) + break; + + if (i >= 0) { + if (is_merge(&options)) + die(_("cannot combine apply options with " + "merge options")); + else + options.type = REBASE_APPLY; + } + } + + if (options.type == REBASE_UNSPECIFIED) { + if (!strcmp(options.default_backend, "merge")) + imply_merge(&options, "--merge"); + else if (!strcmp(options.default_backend, "apply")) + options.type = REBASE_APPLY; + else + die(_("Unknown rebase backend: %s"), + options.default_backend); + } + switch (options.type) { case REBASE_MERGE: - case REBASE_INTERACTIVE: case REBASE_PRESERVE_MERGES: options.state_dir = merge_dir(); break; - case REBASE_AM: + case REBASE_APPLY: options.state_dir = apply_dir(); break; default: - /* the default rebase backend is `--am` */ - options.type = REBASE_AM; - options.state_dir = apply_dir(); - break; + BUG("options.type was just set above; should be unreachable."); } - if (reschedule_failed_exec > 0 && !is_interactive(&options)) + if (options.empty == EMPTY_UNSPECIFIED) { + if (options.flags & REBASE_INTERACTIVE_EXPLICIT) + options.empty = EMPTY_ASK; + else if (exec.nr > 0) + options.empty = EMPTY_KEEP; + else + options.empty = EMPTY_DROP; + } + if (reschedule_failed_exec > 0 && !is_merge(&options)) die(_("--reschedule-failed-exec requires " "--exec or --interactive")); if (reschedule_failed_exec >= 0) options.reschedule_failed_exec = reschedule_failed_exec; - if (options.git_am_opts.argc) { - /* all am options except -q are compatible only with --am */ - for (i = options.git_am_opts.argc - 1; i >= 0; i--) - if (strcmp(options.git_am_opts.argv[i], "-q")) - break; - - if (is_interactive(&options) && i >= 0) - die(_("cannot combine am options with either " - "interactive or merge options")); - } - if (options.signoff) { if (options.type == REBASE_PRESERVE_MERGES) die("cannot combine '--signoff' with " "'--preserve-merges'"); - argv_array_push(&options.git_am_opts, "--signoff"); + strvec_push(&options.git_am_opts, "--signoff"); options.flags |= REBASE_FORCE; } @@ -1833,15 +1820,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) "'--reschedule-failed-exec'")); } - if (options.rebase_merges) { - if (strategy_options.nr) - die(_("cannot combine '--rebase-merges' with " - "'--strategy-option'")); - if (options.strategy) - die(_("cannot combine '--rebase-merges' with " - "'--strategy'")); - } - if (!options.root) { if (argc < 1) { struct branch *branch; @@ -1872,7 +1850,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.squash_onto = &squash_onto; options.onto_name = squash_onto_name = xstrdup(oid_to_hex(&squash_onto)); - } + } else + options.root_with_onto = 1; + options.upstream_name = NULL; options.upstream = NULL; if (argc > 1) @@ -1882,12 +1862,22 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } /* Make sure the branch to rebase onto is valid. */ - if (!options.onto_name) + if (keep_base) { + strbuf_reset(&buf); + strbuf_addstr(&buf, options.upstream_name); + strbuf_addstr(&buf, "..."); + options.onto_name = xstrdup(buf.buf); + } else if (!options.onto_name) options.onto_name = options.upstream_name; if (strstr(options.onto_name, "...")) { - if (get_oid_mb(options.onto_name, &merge_base) < 0) - die(_("'%s': need exactly one merge base"), - options.onto_name); + if (get_oid_mb(options.onto_name, &merge_base) < 0) { + if (keep_base) + die(_("'%s': need exactly one merge base with branch"), + options.upstream_name); + else + die(_("'%s': need exactly one merge base"), + options.onto_name); + } options.onto = lookup_commit_or_die(&merge_base, options.onto_name); } else { @@ -1912,10 +1902,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* Is it a local branch? */ strbuf_reset(&buf); strbuf_addf(&buf, "refs/heads/%s", branch_name); - if (!read_ref(buf.buf, &options.orig_head)) + if (!read_ref(buf.buf, &options.orig_head)) { + die_if_checked_out(buf.buf, 1); options.head_name = xstrdup(buf.buf); /* If not is it a valid ref (branch or commit)? */ - else if (!get_oid(branch_name, &options.orig_head)) + } else if (!get_oid(branch_name, &options.orig_head)) options.head_name = NULL; else die(_("fatal: no such branch/commit '%s'"), @@ -1953,60 +1944,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("could not read index")); if (options.autostash) { - struct lock_file lock_file = LOCK_INIT; - int fd; - - fd = hold_locked_index(&lock_file, 0); - refresh_cache(REFRESH_QUIET); - if (0 <= fd) - repo_update_index_if_able(the_repository, &lock_file); - rollback_lock_file(&lock_file); - - if (has_unstaged_changes(the_repository, 1) || - has_uncommitted_changes(the_repository, 1)) { - const char *autostash = - state_dir_path("autostash", &options); - struct child_process stash = CHILD_PROCESS_INIT; - struct object_id oid; - struct commit *head = - lookup_commit_reference(the_repository, - &options.orig_head); - - argv_array_pushl(&stash.args, - "stash", "create", "autostash", NULL); - stash.git_cmd = 1; - stash.no_stdin = 1; - strbuf_reset(&buf); - if (capture_command(&stash, &buf, GIT_MAX_HEXSZ)) - die(_("Cannot autostash")); - strbuf_trim_trailing_newline(&buf); - if (get_oid(buf.buf, &oid)) - die(_("Unexpected stash response: '%s'"), - buf.buf); - strbuf_reset(&buf); - strbuf_add_unique_abbrev(&buf, &oid, DEFAULT_ABBREV); - - if (safe_create_leading_directories_const(autostash)) - die(_("Could not create directory for '%s'"), - options.state_dir); - write_file(autostash, "%s", oid_to_hex(&oid)); - printf(_("Created autostash: %s\n"), buf.buf); - if (reset_head(&head->object.oid, "reset --hard", - NULL, RESET_HEAD_HARD, NULL, NULL) < 0) - die(_("could not reset --hard")); - printf(_("HEAD is now at %s"), - find_unique_abbrev(&head->object.oid, - DEFAULT_ABBREV)); - strbuf_reset(&buf); - pp_commit_easy(CMIT_FMT_ONELINE, head, &buf); - if (buf.len > 0) - printf(" %s", buf.buf); - putchar('\n'); - - if (discard_index(the_repository->index) < 0 || - repo_read_index(the_repository) < 0) - die(_("could not read index")); - } + create_autostash(the_repository, state_dir_path("autostash", &options), + DEFAULT_REFLOG_ACTION); } if (require_clean_work_tree(the_repository, "rebase", @@ -2022,34 +1961,30 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* * Check if we are already based on onto with linear history, - * but this should be done only when upstream and onto are the same - * and if this is not an interactive rebase. + * in which case we could fast-forward without replacing the commits + * with new commits recreated by replaying their changes. + * + * Note that can_fast_forward() initializes merge_base, so we have to + * call it before checking allow_preemptive_ff. */ - if (can_fast_forward(options.onto, &options.orig_head, &merge_base) && - !is_interactive(&options) && !options.restrict_revision && - options.upstream && - !oidcmp(&options.upstream->object.oid, &options.onto->object.oid)) { + if (can_fast_forward(options.onto, options.upstream, options.restrict_revision, + &options.orig_head, &merge_base) && + allow_preemptive_ff) { int flag; if (!(options.flags & REBASE_FORCE)) { /* Lazily switch to the target branch if needed... */ if (options.switch_to) { - struct object_id oid; - - if (get_oid(options.switch_to, &oid) < 0) { - ret = !!error(_("could not parse '%s'"), - options.switch_to); - goto cleanup; - } - strbuf_reset(&buf); strbuf_addf(&buf, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.switch_to); - if (reset_head(&oid, "checkout", + if (reset_head(the_repository, + &options.orig_head, "checkout", options.head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK, - NULL, buf.buf) < 0) { + NULL, buf.buf, + DEFAULT_REFLOG_ACTION) < 0) { ret = !!error(_("could not switch to " "%s"), options.switch_to); @@ -2111,7 +2046,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) diff_flush(&opts); } - if (is_interactive(&options)) + if (is_merge(&options)) goto run_rebase; /* Detach HEAD and reset the tree */ @@ -2121,10 +2056,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); - if (reset_head(&options.onto->object.oid, "checkout", NULL, + if (reset_head(the_repository, &options.onto->object.oid, "checkout", NULL, RESET_HEAD_DETACH | RESET_ORIG_HEAD | RESET_HEAD_RUN_POST_CHECKOUT_HOOK, - NULL, msg.buf)) + NULL, msg.buf, DEFAULT_REFLOG_ACTION)) die(_("Could not detach HEAD")); strbuf_release(&msg); @@ -2139,8 +2074,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "rebase finished: %s onto %s", options.head_name ? options.head_name : "detached HEAD", oid_to_hex(&options.onto->object.oid)); - reset_head(NULL, "Fast-forwarded", options.head_name, - RESET_HEAD_REFS_ONLY, "HEAD", msg.buf); + reset_head(the_repository, NULL, "Fast-forwarded", options.head_name, + RESET_HEAD_REFS_ONLY, "HEAD", msg.buf, + DEFAULT_REFLOG_ACTION); strbuf_release(&msg); ret = !!finish_rebase(&options); goto cleanup; diff --git a/third_party/git/builtin/receive-pack.c b/third_party/git/builtin/receive-pack.c index dcf385511f07..bb9909c52e4e 100644 --- a/third_party/git/builtin/receive-pack.c +++ b/third_party/git/builtin/receive-pack.c @@ -13,9 +13,9 @@ #include "remote.h" #include "connect.h" #include "string-list.h" -#include "sha1-array.h" +#include "oid-array.h" #include "connected.h" -#include "argv-array.h" +#include "strvec.h" #include "version.h" #include "tag.h" #include "gpg-interface.h" @@ -27,6 +27,8 @@ #include "object-store.h" #include "protocol.h" #include "commit-reach.h" +#include "worktree.h" +#include "shallow.h" static const char * const receive_pack_usage[] = { N_("git receive-pack <git-dir>"), @@ -55,6 +57,7 @@ static int advertise_push_options; static int unpack_limit = 100; static off_t max_input_size; static int report_status; +static int report_status_v2; static int use_sideband; static int use_atomic; static int use_push_options; @@ -95,6 +98,17 @@ static int keepalive_in_sec = 5; static struct tmp_objdir *tmp_objdir; +static struct proc_receive_ref { + unsigned int want_add:1, + want_delete:1, + want_modify:1, + negative_ref:1; + char *ref_prefix; + struct proc_receive_ref *next; +} *proc_receive_ref; + +static void proc_receive_ref_append(const char *prefix); + static enum deny_action parse_deny_action(const char *var, const char *value) { if (value) { @@ -227,6 +241,13 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.procreceiverefs") == 0) { + if (!value) + return config_error_nonbool(var); + proc_receive_ref_append(value); + return 0; + } + return git_default_config(var, value, cb); } @@ -238,7 +259,7 @@ static void show_ref(const char *path, const struct object_id *oid) struct strbuf cap = STRBUF_INIT; strbuf_addstr(&cap, - "report-status delete-refs side-band-64k quiet"); + "report-status report-status-v2 delete-refs side-band-64k quiet"); if (advertise_atomic_push) strbuf_addstr(&cap, " atomic"); if (prefer_ofs_delta) @@ -247,6 +268,7 @@ static void show_ref(const char *path, const struct object_id *oid) strbuf_addf(&cap, " push-cert=%s", push_cert_nonce); if (advertise_push_options) strbuf_addstr(&cap, " push-options"); + strbuf_addf(&cap, " object-format=%s", the_hash_algo->name); strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized()); packet_write_fmt(1, "%s %s%c%s\n", oid_to_hex(oid), path, 0, cap.buf); @@ -307,17 +329,94 @@ static void write_head_info(void) packet_flush(1); } +#define RUN_PROC_RECEIVE_SCHEDULED 1 +#define RUN_PROC_RECEIVE_RETURNED 2 struct command { struct command *next; const char *error_string; + struct ref_push_report *report; unsigned int skip_update:1, - did_not_exist:1; + did_not_exist:1, + run_proc_receive:2; int index; struct object_id old_oid; struct object_id new_oid; char ref_name[FLEX_ARRAY]; /* more */ }; +static void proc_receive_ref_append(const char *prefix) +{ + struct proc_receive_ref *ref_pattern; + char *p; + int len; + + ref_pattern = xcalloc(1, sizeof(struct proc_receive_ref)); + p = strchr(prefix, ':'); + if (p) { + while (prefix < p) { + if (*prefix == 'a') + ref_pattern->want_add = 1; + else if (*prefix == 'd') + ref_pattern->want_delete = 1; + else if (*prefix == 'm') + ref_pattern->want_modify = 1; + else if (*prefix == '!') + ref_pattern->negative_ref = 1; + prefix++; + } + prefix++; + } else { + ref_pattern->want_add = 1; + ref_pattern->want_delete = 1; + ref_pattern->want_modify = 1; + } + len = strlen(prefix); + while (len && prefix[len - 1] == '/') + len--; + ref_pattern->ref_prefix = xmemdupz(prefix, len); + if (!proc_receive_ref) { + proc_receive_ref = ref_pattern; + } else { + struct proc_receive_ref *end; + + end = proc_receive_ref; + while (end->next) + end = end->next; + end->next = ref_pattern; + } +} + +static int proc_receive_ref_matches(struct command *cmd) +{ + struct proc_receive_ref *p; + + if (!proc_receive_ref) + return 0; + + for (p = proc_receive_ref; p; p = p->next) { + const char *match = p->ref_prefix; + const char *remains; + + if (!p->want_add && is_null_oid(&cmd->old_oid)) + continue; + else if (!p->want_delete && is_null_oid(&cmd->new_oid)) + continue; + else if (!p->want_modify && + !is_null_oid(&cmd->old_oid) && + !is_null_oid(&cmd->new_oid)) + continue; + + if (skip_prefix(cmd->ref_name, match, &remains) && + (!*remains || *remains == '/')) { + if (!p->negative_ref) + return 1; + } else if (p->negative_ref) { + return 1; + } + } + return 0; +} + static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2))); static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2))); @@ -417,24 +516,22 @@ static int copy_to_sideband(int in, int out, void *arg) return 0; } -#define HMAC_BLOCK_SIZE 64 - -static void hmac_sha1(unsigned char *out, +static void hmac_hash(unsigned char *out, const char *key_in, size_t key_len, const char *text, size_t text_len) { - unsigned char key[HMAC_BLOCK_SIZE]; - unsigned char k_ipad[HMAC_BLOCK_SIZE]; - unsigned char k_opad[HMAC_BLOCK_SIZE]; + unsigned char key[GIT_MAX_BLKSZ]; + unsigned char k_ipad[GIT_MAX_BLKSZ]; + unsigned char k_opad[GIT_MAX_BLKSZ]; int i; - git_SHA_CTX ctx; + git_hash_ctx ctx; /* RFC 2104 2. (1) */ - memset(key, '\0', HMAC_BLOCK_SIZE); - if (HMAC_BLOCK_SIZE < key_len) { - git_SHA1_Init(&ctx); - git_SHA1_Update(&ctx, key_in, key_len); - git_SHA1_Final(key, &ctx); + memset(key, '\0', GIT_MAX_BLKSZ); + if (the_hash_algo->blksz < key_len) { + the_hash_algo->init_fn(&ctx); + the_hash_algo->update_fn(&ctx, key_in, key_len); + the_hash_algo->final_fn(key, &ctx); } else { memcpy(key, key_in, key_len); } @@ -446,29 +543,29 @@ static void hmac_sha1(unsigned char *out, } /* RFC 2104 2. (3) & (4) */ - git_SHA1_Init(&ctx); - git_SHA1_Update(&ctx, k_ipad, sizeof(k_ipad)); - git_SHA1_Update(&ctx, text, text_len); - git_SHA1_Final(out, &ctx); + the_hash_algo->init_fn(&ctx); + the_hash_algo->update_fn(&ctx, k_ipad, sizeof(k_ipad)); + the_hash_algo->update_fn(&ctx, text, text_len); + the_hash_algo->final_fn(out, &ctx); /* RFC 2104 2. (6) & (7) */ - git_SHA1_Init(&ctx); - git_SHA1_Update(&ctx, k_opad, sizeof(k_opad)); - git_SHA1_Update(&ctx, out, GIT_SHA1_RAWSZ); - git_SHA1_Final(out, &ctx); + the_hash_algo->init_fn(&ctx); + the_hash_algo->update_fn(&ctx, k_opad, sizeof(k_opad)); + the_hash_algo->update_fn(&ctx, out, the_hash_algo->rawsz); + the_hash_algo->final_fn(out, &ctx); } static char *prepare_push_cert_nonce(const char *path, timestamp_t stamp) { struct strbuf buf = STRBUF_INIT; - unsigned char sha1[GIT_SHA1_RAWSZ]; + unsigned char hash[GIT_MAX_RAWSZ]; strbuf_addf(&buf, "%s:%"PRItime, path, stamp); - hmac_sha1(sha1, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed)); + hmac_hash(hash, buf.buf, buf.len, cert_nonce_seed, strlen(cert_nonce_seed)); strbuf_release(&buf); - /* RFC 2104 5. HMAC-SHA1-80 */ - strbuf_addf(&buf, "%"PRItime"-%.*s", stamp, GIT_SHA1_HEXSZ, sha1_to_hex(sha1)); + /* RFC 2104 5. HMAC-SHA1 or HMAC-SHA256 */ + strbuf_addf(&buf, "%"PRItime"-%.*s", stamp, (int)the_hash_algo->hexsz, hash_to_hex(hash)); return strbuf_detach(&buf, NULL); } @@ -500,12 +597,27 @@ static char *find_header(const char *msg, size_t len, const char *key, return NULL; } +/* + * Return zero if a and b are equal up to n bytes and nonzero if they are not. + * This operation is guaranteed to run in constant time to avoid leaking data. + */ +static int constant_memequal(const char *a, const char *b, size_t n) +{ + int res = 0; + size_t i; + + for (i = 0; i < n; i++) + res |= a[i] ^ b[i]; + return res; +} + static const char *check_nonce(const char *buf, size_t len) { char *nonce = find_header(buf, len, "nonce", NULL); timestamp_t stamp, ostamp; char *bohmac, *expect = NULL; const char *retval = NONCE_BAD; + size_t noncelen; if (!nonce) { retval = NONCE_MISSING; @@ -547,8 +659,14 @@ static const char *check_nonce(const char *buf, size_t len) goto leave; } + noncelen = strlen(nonce); expect = prepare_push_cert_nonce(service_dir, stamp); - if (strcmp(expect, nonce)) { + if (noncelen != strlen(expect)) { + /* This is not even the right size. */ + retval = NONCE_BAD; + goto leave; + } + if (constant_memequal(expect, nonce, noncelen)) { /* Not what we would have signed earlier */ retval = NONCE_BAD; goto leave; @@ -645,31 +763,32 @@ static void prepare_push_cert_sha1(struct child_process *proc) nonce_status = check_nonce(push_cert.buf, bogs); } if (!is_null_oid(&push_cert_oid)) { - argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT=%s", - oid_to_hex(&push_cert_oid)); - argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_SIGNER=%s", - sigcheck.signer ? sigcheck.signer : ""); - argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_KEY=%s", - sigcheck.key ? sigcheck.key : ""); - argv_array_pushf(&proc->env_array, "GIT_PUSH_CERT_STATUS=%c", - sigcheck.result); + strvec_pushf(&proc->env_array, "GIT_PUSH_CERT=%s", + oid_to_hex(&push_cert_oid)); + strvec_pushf(&proc->env_array, "GIT_PUSH_CERT_SIGNER=%s", + sigcheck.signer ? sigcheck.signer : ""); + strvec_pushf(&proc->env_array, "GIT_PUSH_CERT_KEY=%s", + sigcheck.key ? sigcheck.key : ""); + strvec_pushf(&proc->env_array, "GIT_PUSH_CERT_STATUS=%c", + sigcheck.result); if (push_cert_nonce) { - argv_array_pushf(&proc->env_array, - "GIT_PUSH_CERT_NONCE=%s", - push_cert_nonce); - argv_array_pushf(&proc->env_array, - "GIT_PUSH_CERT_NONCE_STATUS=%s", - nonce_status); + strvec_pushf(&proc->env_array, + "GIT_PUSH_CERT_NONCE=%s", + push_cert_nonce); + strvec_pushf(&proc->env_array, + "GIT_PUSH_CERT_NONCE_STATUS=%s", + nonce_status); if (nonce_status == NONCE_SLOP) - argv_array_pushf(&proc->env_array, - "GIT_PUSH_CERT_NONCE_SLOP=%ld", - nonce_stamp_slop); + strvec_pushf(&proc->env_array, + "GIT_PUSH_CERT_NONCE_SLOP=%ld", + nonce_stamp_slop); } } } struct receive_hook_feed_state { struct command *cmd; + struct ref_push_report *report; int skip_broken; struct strbuf buf; const struct string_list *push_options; @@ -698,16 +817,16 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, if (feed_state->push_options) { int i; for (i = 0; i < feed_state->push_options->nr; i++) - argv_array_pushf(&proc.env_array, - "GIT_PUSH_OPTION_%d=%s", i, - feed_state->push_options->items[i].string); - argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d", - feed_state->push_options->nr); + strvec_pushf(&proc.env_array, + "GIT_PUSH_OPTION_%d=%s", i, + feed_state->push_options->items[i].string); + strvec_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d", + feed_state->push_options->nr); } else - argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT"); + strvec_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT"); if (tmp_objdir) - argv_array_pushv(&proc.env_array, tmp_objdir_env(tmp_objdir)); + strvec_pushv(&proc.env_array, tmp_objdir_env(tmp_objdir)); if (use_sideband) { memset(&muxer, 0, sizeof(muxer)); @@ -757,11 +876,31 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep) cmd = cmd->next; if (!cmd) return -1; /* EOF */ + if (!bufp) + return 0; /* OK, can feed something. */ strbuf_reset(&state->buf); - strbuf_addf(&state->buf, "%s %s %s\n", - oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid), - cmd->ref_name); - state->cmd = cmd->next; + if (!state->report) + state->report = cmd->report; + if (state->report) { + struct object_id *old_oid; + struct object_id *new_oid; + const char *ref_name; + + old_oid = state->report->old_oid ? state->report->old_oid : &cmd->old_oid; + new_oid = state->report->new_oid ? state->report->new_oid : &cmd->new_oid; + ref_name = state->report->ref_name ? state->report->ref_name : cmd->ref_name; + strbuf_addf(&state->buf, "%s %s %s\n", + oid_to_hex(old_oid), oid_to_hex(new_oid), + ref_name); + state->report = state->report->next; + if (!state->report) + state->cmd = cmd->next; + } else { + strbuf_addf(&state->buf, "%s %s %s\n", + oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid), + cmd->ref_name); + state->cmd = cmd->next; + } if (bufp) { *bufp = state->buf.buf; *sizep = state->buf.len; @@ -780,6 +919,7 @@ static int run_receive_hook(struct command *commands, strbuf_init(&state.buf, 0); state.cmd = commands; state.skip_broken = skip_broken; + state.report = NULL; if (feed_receive_hook(&state, NULL, NULL)) return 0; state.cmd = commands; @@ -818,14 +958,266 @@ static int run_update_hook(struct command *cmd) return finish_command(&proc); } -static int is_ref_checked_out(const char *ref) +static struct command *find_command_by_refname(struct command *list, + const char *refname) { - if (is_bare_repository()) - return 0; + for (; list; list = list->next) + if (!strcmp(list->ref_name, refname)) + return list; + return NULL; +} - if (!head_name) - return 0; - return !strcmp(head_name, ref); +static int read_proc_receive_report(struct packet_reader *reader, + struct command *commands, + struct strbuf *errmsg) +{ + struct command *cmd; + struct command *hint = NULL; + struct ref_push_report *report = NULL; + int new_report = 0; + int code = 0; + int once = 0; + + for (;;) { + struct object_id old_oid, new_oid; + const char *head; + const char *refname; + char *p; + + if (packet_reader_read(reader) != PACKET_READ_NORMAL) + break; + + head = reader->line; + p = strchr(head, ' '); + if (!p) { + strbuf_addf(errmsg, "proc-receive reported incomplete status line: '%s'\n", head); + code = -1; + continue; + } + *p++ = '\0'; + if (!strcmp(head, "option")) { + const char *key, *val; + + if (!hint || !(report || new_report)) { + if (!once++) + strbuf_addstr(errmsg, "proc-receive reported 'option' without a matching 'ok/ng' directive\n"); + code = -1; + continue; + } + if (new_report) { + if (!hint->report) { + hint->report = xcalloc(1, sizeof(struct ref_push_report)); + report = hint->report; + } else { + report = hint->report; + while (report->next) + report = report->next; + report->next = xcalloc(1, sizeof(struct ref_push_report)); + report = report->next; + } + new_report = 0; + } + key = p; + p = strchr(key, ' '); + if (p) + *p++ = '\0'; + val = p; + if (!strcmp(key, "refname")) + report->ref_name = xstrdup_or_null(val); + else if (!strcmp(key, "old-oid") && val && + !parse_oid_hex(val, &old_oid, &val)) + report->old_oid = oiddup(&old_oid); + else if (!strcmp(key, "new-oid") && val && + !parse_oid_hex(val, &new_oid, &val)) + report->new_oid = oiddup(&new_oid); + else if (!strcmp(key, "forced-update")) + report->forced_update = 1; + else if (!strcmp(key, "fall-through")) + /* Fall through, let 'receive-pack' to execute it. */ + hint->run_proc_receive = 0; + continue; + } + + report = NULL; + new_report = 0; + refname = p; + p = strchr(refname, ' '); + if (p) + *p++ = '\0'; + if (strcmp(head, "ok") && strcmp(head, "ng")) { + strbuf_addf(errmsg, "proc-receive reported bad status '%s' on ref '%s'\n", + head, refname); + code = -1; + continue; + } + + /* first try searching at our hint, falling back to all refs */ + if (hint) + hint = find_command_by_refname(hint, refname); + if (!hint) + hint = find_command_by_refname(commands, refname); + if (!hint) { + strbuf_addf(errmsg, "proc-receive reported status on unknown ref: %s\n", + refname); + code = -1; + continue; + } + if (!hint->run_proc_receive) { + strbuf_addf(errmsg, "proc-receive reported status on unexpected ref: %s\n", + refname); + code = -1; + continue; + } + hint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED; + if (!strcmp(head, "ng")) { + if (p) + hint->error_string = xstrdup(p); + else + hint->error_string = "failed"; + code = -1; + continue; + } + new_report = 1; + } + + for (cmd = commands; cmd; cmd = cmd->next) + if (cmd->run_proc_receive && !cmd->error_string && + !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) { + cmd->error_string = "proc-receive failed to report status"; + code = -1; + } + return code; +} + +static int run_proc_receive_hook(struct command *commands, + const struct string_list *push_options) +{ + struct child_process proc = CHILD_PROCESS_INIT; + struct async muxer; + struct command *cmd; + const char *argv[2]; + struct packet_reader reader; + struct strbuf cap = STRBUF_INIT; + struct strbuf errmsg = STRBUF_INIT; + int hook_use_push_options = 0; + int version = 0; + int code; + + argv[0] = find_hook("proc-receive"); + if (!argv[0]) { + rp_error("cannot find hook 'proc-receive'"); + return -1; + } + argv[1] = NULL; + + proc.argv = argv; + proc.in = -1; + proc.out = -1; + proc.trace2_hook_name = "proc-receive"; + + if (use_sideband) { + memset(&muxer, 0, sizeof(muxer)); + muxer.proc = copy_to_sideband; + muxer.in = -1; + code = start_async(&muxer); + if (code) + return code; + proc.err = muxer.in; + } else { + proc.err = 0; + } + + code = start_command(&proc); + if (code) { + if (use_sideband) + finish_async(&muxer); + return code; + } + + sigchain_push(SIGPIPE, SIG_IGN); + + /* Version negotiaton */ + packet_reader_init(&reader, proc.out, NULL, 0, + PACKET_READ_CHOMP_NEWLINE | + PACKET_READ_GENTLE_ON_EOF); + if (use_atomic) + strbuf_addstr(&cap, " atomic"); + if (use_push_options) + strbuf_addstr(&cap, " push-options"); + if (cap.len) { + packet_write_fmt(proc.in, "version=1%c%s\n", '\0', cap.buf + 1); + strbuf_release(&cap); + } else { + packet_write_fmt(proc.in, "version=1\n"); + } + packet_flush(proc.in); + + for (;;) { + int linelen; + + if (packet_reader_read(&reader) != PACKET_READ_NORMAL) + break; + + if (reader.pktlen > 8 && starts_with(reader.line, "version=")) { + version = atoi(reader.line + 8); + linelen = strlen(reader.line); + if (linelen < reader.pktlen) { + const char *feature_list = reader.line + linelen + 1; + if (parse_feature_request(feature_list, "push-options")) + hook_use_push_options = 1; + } + } + } + + if (version != 1) { + strbuf_addf(&errmsg, "proc-receive version '%d' is not supported", + version); + code = -1; + goto cleanup; + } + + /* Send commands */ + for (cmd = commands; cmd; cmd = cmd->next) { + if (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string) + continue; + packet_write_fmt(proc.in, "%s %s %s", + oid_to_hex(&cmd->old_oid), + oid_to_hex(&cmd->new_oid), + cmd->ref_name); + } + packet_flush(proc.in); + + /* Send push options */ + if (hook_use_push_options) { + struct string_list_item *item; + + for_each_string_list_item(item, push_options) + packet_write_fmt(proc.in, "%s", item->string); + packet_flush(proc.in); + } + + /* Read result from proc-receive */ + code = read_proc_receive_report(&reader, commands, &errmsg); + +cleanup: + close(proc.in); + close(proc.out); + if (use_sideband) + finish_async(&muxer); + if (finish_command(&proc)) + code = -1; + if (errmsg.len >0) { + char *p = errmsg.buf; + + p += errmsg.len - 1; + if (*p == '\n') + *p = '\0'; + rp_error("%s", errmsg.buf); + strbuf_release(&errmsg); + } + sigchain_pop(SIGPIPE); + + return code; } static char *refuse_unconfigured_deny_msg = @@ -866,7 +1258,7 @@ static void refuse_unconfigured_deny_delete_current(void) static int command_singleton_iterator(void *cb_data, struct object_id *oid); static int update_shallow_ref(struct command *cmd, struct shallow_info *si) { - struct lock_file shallow_lock = LOCK_INIT; + struct shallow_lock shallow_lock = SHALLOW_LOCK_INIT; struct oid_array extra = OID_ARRAY_INIT; struct check_connected_options opt = CHECK_CONNECTED_INIT; uint32_t mask = 1 << (cmd->index % 32); @@ -883,12 +1275,12 @@ static int update_shallow_ref(struct command *cmd, struct shallow_info *si) opt.env = tmp_objdir_env(tmp_objdir); setup_alternate_shallow(&shallow_lock, &opt.shallow_file, &extra); if (check_connected(command_singleton_iterator, cmd, &opt)) { - rollback_lock_file(&shallow_lock); + rollback_shallow_file(the_repository, &shallow_lock); oid_array_clear(&extra); return -1; } - commit_lock_file(&shallow_lock); + commit_shallow_file(the_repository, &shallow_lock); /* * Make sure setup_alternate_shallow() for the next ref does @@ -919,7 +1311,7 @@ static int head_has_history(void) } static const char *push_to_deploy(unsigned char *sha1, - struct argv_array *env, + struct strvec *env, const char *work_tree) { const char *update_refresh[] = { @@ -938,7 +1330,7 @@ static const char *push_to_deploy(unsigned char *sha1, struct child_process child = CHILD_PROCESS_INIT; child.argv = update_refresh; - child.env = env->argv; + child.env = env->v; child.dir = work_tree; child.no_stdin = 1; child.stdout_to_stderr = 1; @@ -949,7 +1341,7 @@ static const char *push_to_deploy(unsigned char *sha1, /* run_command() does not clean up completely; reinitialize */ child_process_init(&child); child.argv = diff_files; - child.env = env->argv; + child.env = env->v; child.dir = work_tree; child.no_stdin = 1; child.stdout_to_stderr = 1; @@ -962,7 +1354,7 @@ static const char *push_to_deploy(unsigned char *sha1, child_process_init(&child); child.argv = diff_index; - child.env = env->argv; + child.env = env->v; child.no_stdin = 1; child.no_stdout = 1; child.stdout_to_stderr = 0; @@ -970,10 +1362,10 @@ static const char *push_to_deploy(unsigned char *sha1, if (run_command(&child)) return "Working directory has staged changes"; - read_tree[3] = sha1_to_hex(sha1); + read_tree[3] = hash_to_hex(sha1); child_process_init(&child); child.argv = read_tree; - child.env = env->argv; + child.env = env->v; child.dir = work_tree; child.no_stdin = 1; child.no_stdout = 1; @@ -987,35 +1379,45 @@ static const char *push_to_deploy(unsigned char *sha1, static const char *push_to_checkout_hook = "push-to-checkout"; -static const char *push_to_checkout(unsigned char *sha1, - struct argv_array *env, +static const char *push_to_checkout(unsigned char *hash, + struct strvec *env, const char *work_tree) { - argv_array_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree)); - if (run_hook_le(env->argv, push_to_checkout_hook, - sha1_to_hex(sha1), NULL)) + strvec_pushf(env, "GIT_WORK_TREE=%s", absolute_path(work_tree)); + if (run_hook_le(env->v, push_to_checkout_hook, + hash_to_hex(hash), NULL)) return "push-to-checkout hook declined"; else return NULL; } -static const char *update_worktree(unsigned char *sha1) +static const char *update_worktree(unsigned char *sha1, const struct worktree *worktree) { - const char *retval; - const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : ".."; - struct argv_array env = ARGV_ARRAY_INIT; + const char *retval, *work_tree, *git_dir = NULL; + struct strvec env = STRVEC_INIT; + + if (worktree && worktree->path) + work_tree = worktree->path; + else if (git_work_tree_cfg) + work_tree = git_work_tree_cfg; + else + work_tree = ".."; if (is_bare_repository()) return "denyCurrentBranch = updateInstead needs a worktree"; + if (worktree) + git_dir = get_worktree_git_dir(worktree); + if (!git_dir) + git_dir = get_git_dir(); - argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir())); + strvec_pushf(&env, "GIT_DIR=%s", absolute_path(git_dir)); if (!find_hook(push_to_checkout_hook)) retval = push_to_deploy(sha1, &env, work_tree); else retval = push_to_checkout(sha1, &env, work_tree); - argv_array_clear(&env); + strvec_clear(&env); return retval; } @@ -1028,6 +1430,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) struct object_id *old_oid = &cmd->old_oid; struct object_id *new_oid = &cmd->new_oid; int do_update_worktree = 0; + const struct worktree *worktree = is_bare_repository() ? NULL : find_shared_symref("HEAD", name); /* only refs/... are allowed */ if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) { @@ -1039,7 +1442,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) free(namespaced_name); namespaced_name = strbuf_detach(&namespaced_name_buf, NULL); - if (is_ref_checked_out(namespaced_name)) { + if (worktree) { switch (deny_current_branch) { case DENY_IGNORE: break; @@ -1071,7 +1474,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) return "deletion prohibited"; } - if (head_name && !strcmp(namespaced_name, head_name)) { + if (worktree || (head_name && !strcmp(namespaced_name, head_name))) { switch (deny_delete_current) { case DENY_IGNORE: break; @@ -1120,7 +1523,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) } if (do_update_worktree) { - ret = update_worktree(new_oid->hash); + ret = update_worktree(new_oid->hash, find_shared_symref("HEAD", name)); if (ret) return ret; } @@ -1182,11 +1585,11 @@ static void run_update_post_hook(struct command *commands) for (cmd = commands; cmd; cmd = cmd->next) { if (cmd->error_string || cmd->did_not_exist) continue; - if (!proc.args.argc) - argv_array_push(&proc.args, hook); - argv_array_push(&proc.args, cmd->ref_name); + if (!proc.args.nr) + strvec_push(&proc.args, hook); + strvec_push(&proc.args, cmd->ref_name); } - if (!proc.args.argc) + if (!proc.args.nr) return; proc.no_stdin = 1; @@ -1392,7 +1795,7 @@ static void execute_commands_non_atomic(struct command *commands, struct strbuf err = STRBUF_INIT; for (cmd = commands; cmd; cmd = cmd->next) { - if (!should_process_cmd(cmd)) + if (!should_process_cmd(cmd) || cmd->run_proc_receive) continue; transaction = ref_transaction_begin(&err); @@ -1432,7 +1835,7 @@ static void execute_commands_atomic(struct command *commands, } for (cmd = commands; cmd; cmd = cmd->next) { - if (!should_process_cmd(cmd)) + if (!should_process_cmd(cmd) || cmd->run_proc_receive) continue; cmd->error_string = update(cmd, si); @@ -1468,6 +1871,7 @@ static void execute_commands(struct command *commands, struct iterate_data data; struct async muxer; int err_fd = 0; + int run_proc_receive = 0; if (unpacker_error) { for (cmd = commands; cmd; cmd = cmd->next) @@ -1497,6 +1901,22 @@ static void execute_commands(struct command *commands, reject_updates_to_hidden(commands); + /* + * Try to find commands that have special prefix in their reference names, + * and mark them to run an external "proc-receive" hook later. + */ + if (proc_receive_ref) { + for (cmd = commands; cmd; cmd = cmd->next) { + if (!should_process_cmd(cmd)) + continue; + + if (proc_receive_ref_matches(cmd)) { + cmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED; + run_proc_receive = 1; + } + } + } + if (run_receive_hook(commands, "pre-receive", 0, push_options)) { for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) @@ -1523,6 +1943,14 @@ static void execute_commands(struct command *commands, free(head_name_to_free); head_name = head_name_to_free = resolve_refdup("HEAD", 0, NULL, NULL); + if (run_proc_receive && + run_proc_receive_hook(commands, push_options)) + for (cmd = commands; cmd; cmd = cmd->next) + if (!cmd->error_string && + !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED) && + (cmd->run_proc_receive || use_atomic)) + cmd->error_string = "fail to run proc-receive hook"; + if (use_atomic) execute_commands_atomic(commands, si); else @@ -1602,8 +2030,12 @@ static struct command *read_head_info(struct packet_reader *reader, linelen = strlen(reader->line); if (linelen < reader->pktlen) { const char *feature_list = reader->line + linelen + 1; + const char *hash = NULL; + int len = 0; if (parse_feature_request(feature_list, "report-status")) report_status = 1; + if (parse_feature_request(feature_list, "report-status-v2")) + report_status_v2 = 1; if (parse_feature_request(feature_list, "side-band-64k")) use_sideband = LARGE_PACKET_MAX; if (parse_feature_request(feature_list, "quiet")) @@ -1614,6 +2046,13 @@ static struct command *read_head_info(struct packet_reader *reader, if (advertise_push_options && parse_feature_request(feature_list, "push-options")) use_push_options = 1; + hash = parse_feature_value(feature_list, "object-format", &len, NULL); + if (!hash) { + hash = hash_algos[GIT_HASH_SHA1].name; + len = strlen(hash); + } + if (xstrncmpz(the_hash_algo->name, hash, len)) + die("error: unsupported object format '%s'", hash); } if (!strcmp(reader->line, "push-cert")) { @@ -1683,10 +2122,10 @@ static const char *parse_pack_header(struct pack_header *hdr) static const char *pack_lockfile; -static void push_header_arg(struct argv_array *args, struct pack_header *hdr) +static void push_header_arg(struct strvec *args, struct pack_header *hdr) { - argv_array_pushf(args, "--pack_header=%"PRIu32",%"PRIu32, - ntohl(hdr->hdr_version), ntohl(hdr->hdr_entries)); + strvec_pushf(args, "--pack_header=%"PRIu32",%"PRIu32, + ntohl(hdr->hdr_version), ntohl(hdr->hdr_entries)); } static const char *unpack(int err_fd, struct shallow_info *si) @@ -1710,8 +2149,8 @@ static const char *unpack(int err_fd, struct shallow_info *si) if (si->nr_ours || si->nr_theirs) { alt_shallow_file = setup_temporary_shallow(si->shallow); - argv_array_push(&child.args, "--shallow-file"); - argv_array_push(&child.args, alt_shallow_file); + strvec_push(&child.args, "--shallow-file"); + strvec_push(&child.args, alt_shallow_file); } tmp_objdir = tmp_objdir_create(); @@ -1730,16 +2169,16 @@ static const char *unpack(int err_fd, struct shallow_info *si) tmp_objdir_add_as_alternate(tmp_objdir); if (ntohl(hdr.hdr_entries) < unpack_limit) { - argv_array_push(&child.args, "unpack-objects"); + strvec_push(&child.args, "unpack-objects"); push_header_arg(&child.args, &hdr); if (quiet) - argv_array_push(&child.args, "-q"); + strvec_push(&child.args, "-q"); if (fsck_objects) - argv_array_pushf(&child.args, "--strict%s", - fsck_msg_types.buf); + strvec_pushf(&child.args, "--strict%s", + fsck_msg_types.buf); if (max_input_size) - argv_array_pushf(&child.args, "--max-input-size=%"PRIuMAX, - (uintmax_t)max_input_size); + strvec_pushf(&child.args, "--max-input-size=%"PRIuMAX, + (uintmax_t)max_input_size); child.no_stdout = 1; child.err = err_fd; child.git_cmd = 1; @@ -1749,28 +2188,28 @@ static const char *unpack(int err_fd, struct shallow_info *si) } else { char hostname[HOST_NAME_MAX + 1]; - argv_array_pushl(&child.args, "index-pack", "--stdin", NULL); + strvec_pushl(&child.args, "index-pack", "--stdin", NULL); push_header_arg(&child.args, &hdr); if (xgethostname(hostname, sizeof(hostname))) xsnprintf(hostname, sizeof(hostname), "localhost"); - argv_array_pushf(&child.args, - "--keep=receive-pack %"PRIuMAX" on %s", - (uintmax_t)getpid(), - hostname); + strvec_pushf(&child.args, + "--keep=receive-pack %"PRIuMAX" on %s", + (uintmax_t)getpid(), + hostname); if (!quiet && err_fd) - argv_array_push(&child.args, "--show-resolving-progress"); + strvec_push(&child.args, "--show-resolving-progress"); if (use_sideband) - argv_array_push(&child.args, "--report-end-of-input"); + strvec_push(&child.args, "--report-end-of-input"); if (fsck_objects) - argv_array_pushf(&child.args, "--strict%s", - fsck_msg_types.buf); + strvec_pushf(&child.args, "--strict%s", + fsck_msg_types.buf); if (!reject_thin) - argv_array_push(&child.args, "--fix-thin"); + strvec_push(&child.args, "--fix-thin"); if (max_input_size) - argv_array_pushf(&child.args, "--max-input-size=%"PRIuMAX, - (uintmax_t)max_input_size); + strvec_pushf(&child.args, "--max-input-size=%"PRIuMAX, + (uintmax_t)max_input_size); child.out = -1; child.err = err_fd; child.git_cmd = 1; @@ -1915,6 +2354,51 @@ static void report(struct command *commands, const char *unpack_status) strbuf_release(&buf); } +static void report_v2(struct command *commands, const char *unpack_status) +{ + struct command *cmd; + struct strbuf buf = STRBUF_INIT; + struct ref_push_report *report; + + packet_buf_write(&buf, "unpack %s\n", + unpack_status ? unpack_status : "ok"); + for (cmd = commands; cmd; cmd = cmd->next) { + int count = 0; + + if (cmd->error_string) { + packet_buf_write(&buf, "ng %s %s\n", + cmd->ref_name, + cmd->error_string); + continue; + } + packet_buf_write(&buf, "ok %s\n", + cmd->ref_name); + for (report = cmd->report; report; report = report->next) { + if (count++ > 0) + packet_buf_write(&buf, "ok %s\n", + cmd->ref_name); + if (report->ref_name) + packet_buf_write(&buf, "option refname %s\n", + report->ref_name); + if (report->old_oid) + packet_buf_write(&buf, "option old-oid %s\n", + oid_to_hex(report->old_oid)); + if (report->new_oid) + packet_buf_write(&buf, "option new-oid %s\n", + oid_to_hex(report->new_oid)); + if (report->forced_update) + packet_buf_write(&buf, "option forced-update\n"); + } + } + packet_buf_flush(&buf); + + if (use_sideband) + send_sideband(1, 1, buf.buf, buf.len, use_sideband); + else + write_or_die(1, buf.buf, buf.len); + strbuf_release(&buf); +} + static int delete_only(struct command *commands) { struct command *cmd; @@ -2023,7 +2507,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) &push_options); if (pack_lockfile) unlink_or_warn(pack_lockfile); - if (report_status) + if (report_status_v2) + report_v2(commands, unpack_status); + else if (report_status) report(commands, unpack_status); run_receive_hook(commands, "post-receive", 1, &push_options); diff --git a/third_party/git/builtin/reflog.c b/third_party/git/builtin/reflog.c index 4d3430900d06..ca1d8079f320 100644 --- a/third_party/git/builtin/reflog.c +++ b/third_party/git/builtin/reflog.c @@ -459,7 +459,7 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len) static int reflog_expire_config(const char *var, const char *value, void *cb) { const char *pattern, *key; - int pattern_len; + size_t pattern_len; timestamp_t expire; int slot; struct reflog_expire_cfg *ent; @@ -560,15 +560,16 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) for (i = 1; i < argc; i++) { const char *arg = argv[i]; + if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) flags |= EXPIRE_REFLOGS_DRY_RUN; - else if (starts_with(arg, "--expire=")) { - if (parse_expiry_date(arg + 9, &cb.cmd.expire_total)) + else if (skip_prefix(arg, "--expire=", &arg)) { + if (parse_expiry_date(arg, &cb.cmd.expire_total)) die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_TOTAL; } - else if (starts_with(arg, "--expire-unreachable=")) { - if (parse_expiry_date(arg + 21, &cb.cmd.expire_unreachable)) + else if (skip_prefix(arg, "--expire-unreachable=", &arg)) { + if (parse_expiry_date(arg, &cb.cmd.expire_unreachable)) die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_UNREACH; } @@ -614,7 +615,7 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) int i; memset(&collected, 0, sizeof(collected)); - worktrees = get_worktrees(0); + worktrees = get_worktrees(); for (p = worktrees; *p; p++) { if (!all_worktrees && !(*p)->is_current) continue; diff --git a/third_party/git/builtin/remote-ext.c b/third_party/git/builtin/remote-ext.c index 6a9127a33c01..fd3538d4f0e6 100644 --- a/third_party/git/builtin/remote-ext.c +++ b/third_party/git/builtin/remote-ext.c @@ -117,12 +117,12 @@ static char *strip_escapes(const char *str, const char *service, } } -static void parse_argv(struct argv_array *out, const char *arg, const char *service) +static void parse_argv(struct strvec *out, const char *arg, const char *service) { while (*arg) { char *expanded = strip_escapes(arg, service, &arg); if (expanded) - argv_array_push(out, expanded); + strvec_push(out, expanded); free(expanded); } } diff --git a/third_party/git/builtin/remote.c b/third_party/git/builtin/remote.c index 5591cef77543..64b4b551eb02 100644 --- a/third_party/git/builtin/remote.c +++ b/third_party/git/builtin/remote.c @@ -6,10 +6,11 @@ #include "string-list.h" #include "strbuf.h" #include "run-command.h" +#include "rebase.h" #include "refs.h" #include "refspec.h" #include "object-store.h" -#include "argv-array.h" +#include "strvec.h" #include "commit-reach.h" static const char * const builtin_remote_usage[] = { @@ -169,9 +170,9 @@ static int add(int argc, const char **argv) OPT_STRING_LIST('t', "track", &track, N_("branch"), N_("branch(es) to track")), OPT_STRING('m', "master", &master, N_("branch"), N_("master branch")), - { OPTION_CALLBACK, 0, "mirror", &mirror, "(push|fetch)", + OPT_CALLBACK_F(0, "mirror", &mirror, "(push|fetch)", N_("set up remote as a mirror to push to or fetch from"), - PARSE_OPT_OPTARG | PARSE_OPT_COMP_ARG, parse_mirror_opt }, + PARSE_OPT_OPTARG | PARSE_OPT_COMP_ARG, parse_mirror_opt), OPT_END() }; @@ -248,9 +249,8 @@ static int add(int argc, const char **argv) struct branch_info { char *remote_name; struct string_list merge; - enum { - NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE, REBASE_MERGES - } rebase; + enum rebase_type rebase; + char *push_remote_name; }; static struct string_list branch_list = STRING_LIST_INIT_NODUP; @@ -264,59 +264,69 @@ static const char *abbrev_ref(const char *name, const char *prefix) static int config_read_branches(const char *key, const char *value, void *cb) { - if (starts_with(key, "branch.")) { - const char *orig_key = key; - char *name; - struct string_list_item *item; - struct branch_info *info; - enum { REMOTE, MERGE, REBASE } type; - size_t key_len; - - key += 7; - if (strip_suffix(key, ".remote", &key_len)) { - name = xmemdupz(key, key_len); - type = REMOTE; - } else if (strip_suffix(key, ".merge", &key_len)) { - name = xmemdupz(key, key_len); - type = MERGE; - } else if (strip_suffix(key, ".rebase", &key_len)) { - name = xmemdupz(key, key_len); - type = REBASE; - } else - return 0; + const char *orig_key = key; + char *name; + struct string_list_item *item; + struct branch_info *info; + enum { REMOTE, MERGE, REBASE, PUSH_REMOTE } type; + size_t key_len; - item = string_list_insert(&branch_list, name); + if (!starts_with(key, "branch.")) + return 0; - if (!item->util) - item->util = xcalloc(1, sizeof(struct branch_info)); - info = item->util; - if (type == REMOTE) { - if (info->remote_name) - warning(_("more than one %s"), orig_key); - info->remote_name = xstrdup(value); - } else if (type == MERGE) { - char *space = strchr(value, ' '); - value = abbrev_branch(value); - while (space) { - char *merge; - merge = xstrndup(value, space - value); - string_list_append(&info->merge, merge); - value = abbrev_branch(space + 1); - space = strchr(value, ' '); - } - string_list_append(&info->merge, xstrdup(value)); - } else { - int v = git_parse_maybe_bool(value); - if (v >= 0) - info->rebase = v; - else if (!strcmp(value, "preserve")) - info->rebase = NORMAL_REBASE; - else if (!strcmp(value, "merges")) - info->rebase = REBASE_MERGES; - else if (!strcmp(value, "interactive")) - info->rebase = INTERACTIVE_REBASE; + key += strlen("branch."); + if (strip_suffix(key, ".remote", &key_len)) + type = REMOTE; + else if (strip_suffix(key, ".merge", &key_len)) + type = MERGE; + else if (strip_suffix(key, ".rebase", &key_len)) + type = REBASE; + else if (strip_suffix(key, ".pushremote", &key_len)) + type = PUSH_REMOTE; + else + return 0; + name = xmemdupz(key, key_len); + + item = string_list_insert(&branch_list, name); + + if (!item->util) + item->util = xcalloc(1, sizeof(struct branch_info)); + info = item->util; + switch (type) { + case REMOTE: + if (info->remote_name) + warning(_("more than one %s"), orig_key); + info->remote_name = xstrdup(value); + break; + case MERGE: { + char *space = strchr(value, ' '); + value = abbrev_branch(value); + while (space) { + char *merge; + merge = xstrndup(value, space - value); + string_list_append(&info->merge, merge); + value = abbrev_branch(space + 1); + space = strchr(value, ' '); } + string_list_append(&info->merge, xstrdup(value)); + break; + } + case REBASE: + /* + * Consider invalid values as false and check the + * truth value with >= REBASE_TRUE. + */ + info->rebase = rebase_parse_value(value); + break; + case PUSH_REMOTE: + if (info->push_remote_name) + warning(_("more than one %s"), orig_key); + info->push_remote_name = xstrdup(value); + break; + default: + BUG("unexpected type=%d", type); } + return 0; } @@ -468,6 +478,7 @@ static int get_head_names(const struct ref *remote_refs, struct ref_states *stat struct ref *fetch_map = NULL, **fetch_map_tail = &fetch_map; struct refspec_item refspec; + memset(&refspec, 0, sizeof(refspec)); refspec.force = 0; refspec.pattern = 1; refspec.src = refspec.dst = "refs/heads/*"; @@ -605,6 +616,56 @@ static int migrate_file(struct remote *remote) return 0; } +struct push_default_info +{ + const char *old_name; + enum config_scope scope; + struct strbuf origin; + int linenr; +}; + +static int config_read_push_default(const char *key, const char *value, + void *cb) +{ + struct push_default_info* info = cb; + if (strcmp(key, "remote.pushdefault") || + !value || strcmp(value, info->old_name)) + return 0; + + info->scope = current_config_scope(); + strbuf_reset(&info->origin); + strbuf_addstr(&info->origin, current_config_name()); + info->linenr = current_config_line(); + + return 0; +} + +static void handle_push_default(const char* old_name, const char* new_name) +{ + struct push_default_info push_default = { + old_name, CONFIG_SCOPE_UNKNOWN, STRBUF_INIT, -1 }; + git_config(config_read_push_default, &push_default); + if (push_default.scope >= CONFIG_SCOPE_COMMAND) + ; /* pass */ + else if (push_default.scope >= CONFIG_SCOPE_LOCAL) { + int result = git_config_set_gently("remote.pushDefault", + new_name); + if (new_name && result && result != CONFIG_NOTHING_SET) + die(_("could not set '%s'"), "remote.pushDefault"); + else if (!new_name && result && result != CONFIG_NOTHING_SET) + die(_("could not unset '%s'"), "remote.pushDefault"); + } else if (push_default.scope >= CONFIG_SCOPE_SYSTEM) { + /* warn */ + warning(_("The %s configuration remote.pushDefault in:\n" + "\t%s:%d\n" + "now names the non-existent remote '%s'"), + config_scope_name(push_default.scope), + push_default.origin.buf, push_default.linenr, + old_name); + } +} + + static int mv(int argc, const char **argv) { struct option options[] = { @@ -680,6 +741,11 @@ static int mv(int argc, const char **argv) strbuf_addf(&buf, "branch.%s.remote", item->string); git_config_set(buf.buf, rename.new_name); } + if (info->push_remote_name && !strcmp(info->push_remote_name, rename.old_name)) { + strbuf_reset(&buf); + strbuf_addf(&buf, "branch.%s.pushremote", item->string); + git_config_set(buf.buf, rename.new_name); + } } if (!refspec_updated) @@ -693,9 +759,8 @@ static int mv(int argc, const char **argv) for (i = 0; i < remote_branches.nr; i++) { struct string_list_item *item = remote_branches.items + i; int flag = 0; - struct object_id oid; - read_ref_full(item->string, RESOLVE_REF_READING, &oid, &flag); + read_ref_full(item->string, RESOLVE_REF_READING, NULL, &flag); if (!(flag & REF_ISSYMREF)) continue; if (delete_ref(NULL, item->string, NULL, REF_NO_DEREF)) @@ -736,6 +801,9 @@ static int mv(int argc, const char **argv) die(_("creating '%s' failed"), buf.buf); } string_list_clear(&remote_branches, 1); + + handle_push_default(rename.old_name, rename.new_name); + return 0; } @@ -782,6 +850,13 @@ static int rm(int argc, const char **argv) die(_("could not unset '%s'"), buf.buf); } } + if (info->push_remote_name && !strcmp(info->push_remote_name, remote->name)) { + strbuf_reset(&buf); + strbuf_addf(&buf, "branch.%s.pushremote", item->string); + result = git_config_set_gently(buf.buf, NULL); + if (result && result != CONFIG_NOTHING_SET) + die(_("could not unset '%s'"), buf.buf); + } } /* @@ -814,6 +889,8 @@ static int rm(int argc, const char **argv) strbuf_addf(&buf, "remote.%s", remote->name); if (git_config_rename_section(buf.buf, NULL) < 1) return error(_("Could not remove config section '%s'"), buf.buf); + + handle_push_default(remote->name, NULL); } return result; @@ -944,7 +1021,7 @@ static int add_local_to_show_info(struct string_list_item *branch_item, void *cb return 0; if ((n = strlen(branch_item->string)) > show_info->width) show_info->width = n; - if (branch_info->rebase) + if (branch_info->rebase >= REBASE_TRUE) show_info->any_rebase = 1; item = string_list_insert(show_info->list, branch_item->string); @@ -961,16 +1038,16 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data) int width = show_info->width + 4; int i; - if (branch_info->rebase && branch_info->merge.nr > 1) { + if (branch_info->rebase >= REBASE_TRUE && branch_info->merge.nr > 1) { error(_("invalid branch.%s.merge; cannot rebase onto > 1 branch"), item->string); return 0; } printf(" %-*s ", show_info->width, item->string); - if (branch_info->rebase) { + if (branch_info->rebase >= REBASE_TRUE) { const char *msg; - if (branch_info->rebase == INTERACTIVE_REBASE) + if (branch_info->rebase == REBASE_INTERACTIVE) msg = _("rebases interactively onto remote %s"); else if (branch_info->rebase == REBASE_MERGES) msg = _("rebases interactively (with merges) onto " @@ -1279,7 +1356,7 @@ static int set_head(int argc, const char **argv) result |= error(_("Not a valid ref: %s"), buf2.buf); else if (create_symref(buf.buf, buf2.buf, "remote set-head")) result |= error(_("Could not setup %s"), buf.buf); - if (opt_a) + else if (opt_a) printf("%s/HEAD set to %s\n", argv[0], head_name); free(head_name); } @@ -1375,35 +1452,35 @@ static int update(int argc, const char **argv) N_("prune remotes after fetching")), OPT_END() }; - struct argv_array fetch_argv = ARGV_ARRAY_INIT; + struct strvec fetch_argv = STRVEC_INIT; int default_defined = 0; int retval; argc = parse_options(argc, argv, NULL, options, builtin_remote_update_usage, PARSE_OPT_KEEP_ARGV0); - argv_array_push(&fetch_argv, "fetch"); + strvec_push(&fetch_argv, "fetch"); if (prune != -1) - argv_array_push(&fetch_argv, prune ? "--prune" : "--no-prune"); + strvec_push(&fetch_argv, prune ? "--prune" : "--no-prune"); if (verbose) - argv_array_push(&fetch_argv, "-v"); - argv_array_push(&fetch_argv, "--multiple"); + strvec_push(&fetch_argv, "-v"); + strvec_push(&fetch_argv, "--multiple"); if (argc < 2) - argv_array_push(&fetch_argv, "default"); + strvec_push(&fetch_argv, "default"); for (i = 1; i < argc; i++) - argv_array_push(&fetch_argv, argv[i]); + strvec_push(&fetch_argv, argv[i]); - if (strcmp(fetch_argv.argv[fetch_argv.argc-1], "default") == 0) { + if (strcmp(fetch_argv.v[fetch_argv.nr-1], "default") == 0) { git_config(get_remote_default, &default_defined); if (!default_defined) { - argv_array_pop(&fetch_argv); - argv_array_push(&fetch_argv, "--all"); + strvec_pop(&fetch_argv); + strvec_push(&fetch_argv, "--all"); } } - retval = run_command_v_opt(fetch_argv.argv, RUN_GIT_CMD); - argv_array_clear(&fetch_argv); + retval = run_command_v_opt(fetch_argv.v, RUN_GIT_CMD); + strvec_clear(&fetch_argv); return retval; } diff --git a/third_party/git/builtin/repack.c b/third_party/git/builtin/repack.c index 632c0c0a7942..01e7767c7928 100644 --- a/third_party/git/builtin/repack.c +++ b/third_party/git/builtin/repack.c @@ -7,10 +7,13 @@ #include "sigchain.h" #include "strbuf.h" #include "string-list.h" -#include "argv-array.h" +#include "strvec.h" #include "midx.h" #include "packfile.h" +#include "prune-packed.h" #include "object-store.h" +#include "promisor-remote.h" +#include "shallow.h" static int delta_base_offset = 1; static int pack_kept_objects = -1; @@ -130,7 +133,11 @@ static void get_non_kept_pack_filenames(struct string_list *fname_list, static void remove_redundant_pack(const char *dir_name, const char *base_name) { struct strbuf buf = STRBUF_INIT; - strbuf_addf(&buf, "%s/%s.pack", dir_name, base_name); + struct multi_pack_index *m = get_local_multi_pack_index(the_repository); + strbuf_addf(&buf, "%s.pack", base_name); + if (m && midx_contains_pack(m, buf.buf)) + clear_midx_file(the_repository); + strbuf_insertf(&buf, 0, "%s/", dir_name); unlink_pack_path(buf.buf, 1); strbuf_release(&buf); } @@ -150,28 +157,28 @@ struct pack_objects_args { static void prepare_pack_objects(struct child_process *cmd, const struct pack_objects_args *args) { - argv_array_push(&cmd->args, "pack-objects"); + strvec_push(&cmd->args, "pack-objects"); if (args->window) - argv_array_pushf(&cmd->args, "--window=%s", args->window); + strvec_pushf(&cmd->args, "--window=%s", args->window); if (args->window_memory) - argv_array_pushf(&cmd->args, "--window-memory=%s", args->window_memory); + strvec_pushf(&cmd->args, "--window-memory=%s", args->window_memory); if (args->depth) - argv_array_pushf(&cmd->args, "--depth=%s", args->depth); + strvec_pushf(&cmd->args, "--depth=%s", args->depth); if (args->threads) - argv_array_pushf(&cmd->args, "--threads=%s", args->threads); + strvec_pushf(&cmd->args, "--threads=%s", args->threads); if (args->max_pack_size) - argv_array_pushf(&cmd->args, "--max-pack-size=%s", args->max_pack_size); + strvec_pushf(&cmd->args, "--max-pack-size=%s", args->max_pack_size); if (args->no_reuse_delta) - argv_array_pushf(&cmd->args, "--no-reuse-delta"); + strvec_pushf(&cmd->args, "--no-reuse-delta"); if (args->no_reuse_object) - argv_array_pushf(&cmd->args, "--no-reuse-object"); + strvec_pushf(&cmd->args, "--no-reuse-object"); if (args->local) - argv_array_push(&cmd->args, "--local"); + strvec_push(&cmd->args, "--local"); if (args->quiet) - argv_array_push(&cmd->args, "--quiet"); + strvec_push(&cmd->args, "--quiet"); if (delta_base_offset) - argv_array_push(&cmd->args, "--delta-base-offset"); - argv_array_push(&cmd->args, packtmp); + strvec_push(&cmd->args, "--delta-base-offset"); + strvec_push(&cmd->args, packtmp); cmd->git_cmd = 1; cmd->out = -1; } @@ -190,7 +197,7 @@ static int write_oid(const struct object_id *oid, struct packed_git *pack, die(_("could not start pack-objects to repack promisor objects")); } - xwrite(cmd->in, oid_to_hex(oid), GIT_SHA1_HEXSZ); + xwrite(cmd->in, oid_to_hex(oid), the_hash_algo->hexsz); xwrite(cmd->in, "\n", 1); return 0; } @@ -232,6 +239,13 @@ static void repack_promisor_objects(const struct pack_objects_args *args, /* * pack-objects creates the .pack and .idx files, but not the * .promisor file. Create the .promisor file, which is empty. + * + * NEEDSWORK: fetch-pack sometimes generates non-empty + * .promisor files containing the ref names and associated + * hashes at the point of generation of the corresponding + * packfile, but this would not preserve their contents. Maybe + * concatenate the contents of all .promisor files instead of + * just creating a new empty file. */ promisor_name = mkpathdup("%s-%s.promisor", packtmp, line.buf); @@ -276,7 +290,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix) int keep_unreachable = 0; struct string_list keep_pack_list = STRING_LIST_INIT_NODUP; int no_update_server_info = 0; - int midx_cleared = 0; struct pack_objects_args po_args = {NULL}; struct option builtin_repack_options[] = { @@ -351,24 +364,24 @@ int cmd_repack(int argc, const char **argv, const char *prefix) prepare_pack_objects(&cmd, &po_args); - argv_array_push(&cmd.args, "--keep-true-parents"); + strvec_push(&cmd.args, "--keep-true-parents"); if (!pack_kept_objects) - argv_array_push(&cmd.args, "--honor-pack-keep"); + strvec_push(&cmd.args, "--honor-pack-keep"); for (i = 0; i < keep_pack_list.nr; i++) - argv_array_pushf(&cmd.args, "--keep-pack=%s", - keep_pack_list.items[i].string); - argv_array_push(&cmd.args, "--non-empty"); - argv_array_push(&cmd.args, "--all"); - argv_array_push(&cmd.args, "--reflog"); - argv_array_push(&cmd.args, "--indexed-objects"); - if (repository_format_partial_clone) - argv_array_push(&cmd.args, "--exclude-promisor-objects"); + strvec_pushf(&cmd.args, "--keep-pack=%s", + keep_pack_list.items[i].string); + strvec_push(&cmd.args, "--non-empty"); + strvec_push(&cmd.args, "--all"); + strvec_push(&cmd.args, "--reflog"); + strvec_push(&cmd.args, "--indexed-objects"); + if (has_promisor_remote()) + strvec_push(&cmd.args, "--exclude-promisor-objects"); if (write_bitmaps > 0) - argv_array_push(&cmd.args, "--write-bitmap-index"); + strvec_push(&cmd.args, "--write-bitmap-index"); else if (write_bitmaps < 0) - argv_array_push(&cmd.args, "--write-bitmap-index-quiet"); + strvec_push(&cmd.args, "--write-bitmap-index-quiet"); if (use_delta_islands) - argv_array_push(&cmd.args, "--delta-islands"); + strvec_push(&cmd.args, "--delta-islands"); if (pack_everything & ALL_INTO_ONE) { get_non_kept_pack_filenames(&existing_packs, &keep_pack_list); @@ -377,23 +390,23 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (existing_packs.nr && delete_redundant) { if (unpack_unreachable) { - argv_array_pushf(&cmd.args, - "--unpack-unreachable=%s", - unpack_unreachable); - argv_array_push(&cmd.env_array, "GIT_REF_PARANOIA=1"); + strvec_pushf(&cmd.args, + "--unpack-unreachable=%s", + unpack_unreachable); + strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1"); } else if (pack_everything & LOOSEN_UNREACHABLE) { - argv_array_push(&cmd.args, - "--unpack-unreachable"); + strvec_push(&cmd.args, + "--unpack-unreachable"); } else if (keep_unreachable) { - argv_array_push(&cmd.args, "--keep-unreachable"); - argv_array_push(&cmd.args, "--pack-loose-unreachable"); + strvec_push(&cmd.args, "--keep-unreachable"); + strvec_push(&cmd.args, "--pack-loose-unreachable"); } else { - argv_array_push(&cmd.env_array, "GIT_REF_PARANOIA=1"); + strvec_push(&cmd.env_array, "GIT_REF_PARANOIA=1"); } } } else { - argv_array_push(&cmd.args, "--unpacked"); - argv_array_push(&cmd.args, "--incremental"); + strvec_push(&cmd.args, "--unpacked"); + strvec_push(&cmd.args, "--incremental"); } cmd.no_stdin = 1; @@ -429,11 +442,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix) for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { char *fname, *fname_old; - if (!midx_cleared) { - clear_midx_file(the_repository); - midx_cleared = 1; - } - fname = mkpathdup("%s/pack-%s%s", packdir, item->string, exts[ext].name); if (!file_exists(fname)) { @@ -561,7 +569,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) remove_temporary_files(); if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) - write_midx_file(get_object_directory()); + write_midx_file(get_object_directory(), 0); string_list_clear(&names, 0); string_list_clear(&rollback, 0); diff --git a/third_party/git/builtin/replace.c b/third_party/git/builtin/replace.c index 644b21ca8d57..cd4876591174 100644 --- a/third_party/git/builtin/replace.c +++ b/third_party/git/builtin/replace.c @@ -228,13 +228,13 @@ static int export_object(const struct object_id *oid, enum object_type type, if (fd < 0) return error_errno(_("unable to open %s for writing"), filename); - argv_array_push(&cmd.args, "--no-replace-objects"); - argv_array_push(&cmd.args, "cat-file"); + strvec_push(&cmd.args, "--no-replace-objects"); + strvec_push(&cmd.args, "cat-file"); if (raw) - argv_array_push(&cmd.args, type_name(type)); + strvec_push(&cmd.args, type_name(type)); else - argv_array_push(&cmd.args, "-p"); - argv_array_push(&cmd.args, oid_to_hex(oid)); + strvec_push(&cmd.args, "-p"); + strvec_push(&cmd.args, oid_to_hex(oid)); cmd.git_cmd = 1; cmd.out = fd; @@ -272,7 +272,7 @@ static int import_object(struct object_id *oid, enum object_type type, return error(_("unable to spawn mktree")); } - if (strbuf_read(&result, cmd.out, 41) < 0) { + if (strbuf_read(&result, cmd.out, the_hash_algo->hexsz + 1) < 0) { error_errno(_("unable to read from mktree")); close(fd); close(cmd.out); @@ -358,14 +358,15 @@ static int replace_parents(struct strbuf *buf, int argc, const char **argv) struct strbuf new_parents = STRBUF_INIT; const char *parent_start, *parent_end; int i; + const unsigned hexsz = the_hash_algo->hexsz; /* find existing parents */ parent_start = buf->buf; - parent_start += GIT_SHA1_HEXSZ + 6; /* "tree " + "hex sha1" + "\n" */ + parent_start += hexsz + 6; /* "tree " + "hex sha1" + "\n" */ parent_end = parent_start; while (starts_with(parent_end, "parent ")) - parent_end += 48; /* "parent " + "hex sha1" + "\n" */ + parent_end += hexsz + 8; /* "parent " + "hex sha1" + "\n" */ /* prepare new parents */ for (i = 0; i < argc; i++) { @@ -408,7 +409,8 @@ static int check_one_mergetag(struct commit *commit, struct tag *tag; int i; - hash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &tag_oid); + hash_object_file(the_hash_algo, extra->value, extra->len, + type_name(OBJ_TAG), &tag_oid); tag = lookup_tag(the_repository, &tag_oid); if (!tag) return error(_("bad mergetag in commit '%s'"), ref); @@ -421,7 +423,7 @@ static int check_one_mergetag(struct commit *commit, if (get_oid(mergetag_data->argv[i], &oid) < 0) return error(_("not a valid object name: '%s'"), mergetag_data->argv[i]); - if (oideq(&tag->tagged->oid, &oid)) + if (oideq(get_tagged_oid(tag), &oid)) return 0; /* found */ } @@ -500,7 +502,7 @@ static int convert_graft_file(int force) const char *graft_file = get_graft_file(the_repository); FILE *fp = fopen_or_warn(graft_file, "r"); struct strbuf buf = STRBUF_INIT, err = STRBUF_INIT; - struct argv_array args = ARGV_ARRAY_INIT; + struct strvec args = STRVEC_INIT; if (!fp) return -1; @@ -510,10 +512,10 @@ static int convert_graft_file(int force) if (*buf.buf == '#') continue; - argv_array_split(&args, buf.buf); - if (args.argc && create_graft(args.argc, args.argv, force, 1)) + strvec_split(&args, buf.buf); + if (args.nr && create_graft(args.nr, args.v, force, 1)) strbuf_addf(&err, "\n\t%s", buf.buf); - argv_array_clear(&args); + strvec_clear(&args); } fclose(fp); diff --git a/third_party/git/builtin/reset.c b/third_party/git/builtin/reset.c index fdd572168b51..c635b062c3a7 100644 --- a/third_party/git/builtin/reset.c +++ b/third_party/git/builtin/reset.c @@ -30,8 +30,9 @@ static const char * const git_reset_usage[] = { N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"), - N_("git reset [-q] [<tree-ish>] [--] <paths>..."), - N_("git reset --patch [<tree-ish>] [--] [<paths>...]"), + N_("git reset [-q] [<tree-ish>] [--] <pathspec>..."), + N_("git reset [-q] [--pathspec-from-file [--pathspec-file-nul]] [<tree-ish>]"), + N_("git reset --patch [<tree-ish>] [--] [<pathspec>...]"), NULL }; @@ -45,7 +46,7 @@ static inline int is_merge(void) return !access(git_path_merge_head(the_repository), F_OK); } -static int reset_index(const struct object_id *oid, int reset_type, int quiet) +static int reset_index(const char *ref, const struct object_id *oid, int reset_type, int quiet) { int i, nr = 0; struct tree_desc desc[2]; @@ -59,6 +60,7 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet) opts.dst_index = &the_index; opts.fn = oneway_merge; opts.merge = 1; + init_checkout_metadata(&opts.meta, ref, oid, NULL); if (!quiet) opts.verbose_update = 1; switch (reset_type) { @@ -284,8 +286,8 @@ static int git_reset_config(const char *var, const char *value, void *cb) int cmd_reset(int argc, const char **argv, const char *prefix) { int reset_type = NONE, update_ref_status = 0, quiet = 0; - int patch_mode = 0, unborn; - const char *rev; + int patch_mode = 0, pathspec_file_nul = 0, unborn; + const char *rev, *pathspec_from_file = NULL; struct object_id oid; struct pathspec pathspec; int intent_to_add = 0; @@ -300,12 +302,14 @@ int cmd_reset(int argc, const char **argv, const char *prefix) N_("reset HEAD, index and working tree"), MERGE), OPT_SET_INT(0, "keep", &reset_type, N_("reset HEAD but keep local changes"), KEEP), - { OPTION_CALLBACK, 0, "recurse-submodules", NULL, + OPT_CALLBACK_F(0, "recurse-submodules", NULL, "reset", "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('p', "patch", &patch_mode, N_("select hunks interactively")), OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that removed paths will be added later")), + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END() }; @@ -316,11 +320,25 @@ int cmd_reset(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_DASHDASH); parse_args(&pathspec, argv, prefix, patch_mode, &rev); + if (pathspec_from_file) { + if (patch_mode) + die(_("--pathspec-from-file is incompatible with --patch")); + + if (pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&pathspec, 0, + PATHSPEC_PREFER_FULL, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid); if (unborn) { /* reset on unborn branch: treat as reset to empty tree */ oidcpy(&oid, the_hash_algo->empty_tree); - } else if (!pathspec.nr) { + } else if (!pathspec.nr && !patch_mode) { struct commit *commit; if (get_oid_committish(rev, &oid)) die(_("Failed to resolve '%s' as a valid revision."), rev); @@ -401,11 +419,20 @@ int cmd_reset(int argc, const char **argv, const char *prefix) } } } else { - int err = reset_index(&oid, reset_type, quiet); + struct object_id dummy; + char *ref = NULL; + int err; + + dwim_ref(rev, strlen(rev), &dummy, &ref, 0); + if (ref && !starts_with(ref, "refs/")) + ref = NULL; + + err = reset_index(ref, &oid, reset_type, quiet); if (reset_type == KEEP && !err) - err = reset_index(&oid, MIXED, quiet); + err = reset_index(ref, &oid, MIXED, quiet); if (err) die(_("Could not reset index file to revision '%s'."), rev); + free(ref); } if (write_locked_index(&the_index, &lock, COMMIT_LOCK)) diff --git a/third_party/git/builtin/rev-list.c b/third_party/git/builtin/rev-list.c index 301ccb970bb3..25c6c3b38d4b 100644 --- a/third_party/git/builtin/rev-list.c +++ b/third_party/git/builtin/rev-list.c @@ -18,7 +18,6 @@ #include "reflog-walk.h" #include "oidset.h" #include "packfile.h" -#include "object-store.h" static const char rev_list_usage[] = "git rev-list [OPTION] <commit-id>... [ -- paths... ]\n" @@ -254,11 +253,26 @@ static int finish_object(struct object *obj, const char *name, void *cb_data) static void show_object(struct object *obj, const char *name, void *cb_data) { struct rev_list_info *info = cb_data; + struct rev_info *revs = info->revs; + if (finish_object(obj, name, cb_data)) return; display_progress(progress, ++progress_counter); if (info->flags & REV_LIST_QUIET) return; + + if (revs->count) { + /* + * The object count is always accumulated in the .count_right + * field for traversal that is not a left-right traversal, + * and cmd_rev_list() made sure that a .count request that + * wants to count non-commit objects, which is handled by + * the show_object() callback, does not ask for .left_right. + */ + revs->count_right++; + return; + } + if (arg_show_object_names) show_object_with_name(stdout, obj, name); else @@ -365,6 +379,79 @@ static inline int parse_missing_action_value(const char *value) return 0; } +static int try_bitmap_count(struct rev_info *revs, + struct list_objects_filter_options *filter) +{ + uint32_t commit_count = 0, + tag_count = 0, + tree_count = 0, + blob_count = 0; + int max_count; + struct bitmap_index *bitmap_git; + + /* This function only handles counting, not general traversal. */ + if (!revs->count) + return -1; + + /* + * A bitmap result can't know left/right, etc, because we don't + * actually traverse. + */ + if (revs->left_right || revs->cherry_mark) + return -1; + + /* + * If we're counting reachable objects, we can't handle a max count of + * commits to traverse, since we don't know which objects go with which + * commit. + */ + if (revs->max_count >= 0 && + (revs->tag_objects || revs->tree_objects || revs->blob_objects)) + return -1; + + /* + * This must be saved before doing any walking, since the revision + * machinery will count it down to zero while traversing. + */ + max_count = revs->max_count; + + bitmap_git = prepare_bitmap_walk(revs, filter); + if (!bitmap_git) + return -1; + + count_bitmap_commit_list(bitmap_git, &commit_count, + revs->tree_objects ? &tree_count : NULL, + revs->blob_objects ? &blob_count : NULL, + revs->tag_objects ? &tag_count : NULL); + if (max_count >= 0 && max_count < commit_count) + commit_count = max_count; + + printf("%d\n", commit_count + tree_count + blob_count + tag_count); + free_bitmap_index(bitmap_git); + return 0; +} + +static int try_bitmap_traversal(struct rev_info *revs, + struct list_objects_filter_options *filter) +{ + struct bitmap_index *bitmap_git; + + /* + * We can't use a bitmap result with a traversal limit, since the set + * of commits we'd get would be essentially random. + */ + if (revs->max_count >= 0) + return -1; + + bitmap_git = prepare_bitmap_walk(revs, filter); + if (!bitmap_git) + return -1; + + traverse_bitmap_commit_list(bitmap_git, revs, &show_object_fast); + free_bitmap_index(bitmap_git); + return 0; +} + int cmd_rev_list(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -471,10 +558,6 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) parse_list_objects_filter(&filter_options, arg); if (filter_options.choice && !revs.blob_objects) die(_("object filtering requires --objects")); - if (filter_options.choice == LOFC_SPARSE_OID && - !filter_options.sparse_oid_value) - die(_("invalid sparse value '%s'"), - filter_options.filter_spec); continue; } if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) { @@ -526,8 +609,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (revs.show_notes) die(_("rev-list does not support display of notes")); - if (filter_options.choice && use_bitmap_index) - die(_("cannot combine --use-bitmap-index with object filtering")); + if (revs.count && + (revs.tag_objects || revs.tree_objects || revs.blob_objects) && + (revs.left_right || revs.cherry_mark)) + die(_("marked counting is incompatible with --objects")); save_commit_buffer = (revs.verbose_header || revs.grep_filter.pattern_list || @@ -538,28 +623,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (show_progress) progress = start_delayed_progress(show_progress, 0); - if (use_bitmap_index && !revs.prune) { - if (revs.count && !revs.left_right && !revs.cherry_mark) { - uint32_t commit_count; - int max_count = revs.max_count; - struct bitmap_index *bitmap_git; - if ((bitmap_git = prepare_bitmap_walk(&revs))) { - count_bitmap_commit_list(bitmap_git, &commit_count, NULL, NULL, NULL); - if (max_count >= 0 && max_count < commit_count) - commit_count = max_count; - printf("%d\n", commit_count); - free_bitmap_index(bitmap_git); - return 0; - } - } else if (revs.max_count < 0 && - revs.tag_objects && revs.tree_objects && revs.blob_objects) { - struct bitmap_index *bitmap_git; - if ((bitmap_git = prepare_bitmap_walk(&revs))) { - traverse_bitmap_commit_list(bitmap_git, &show_object_fast); - free_bitmap_index(bitmap_git); - return 0; - } - } + if (use_bitmap_index) { + if (!try_bitmap_count(&revs, &filter_options)) + return 0; + if (!try_bitmap_traversal(&revs, &filter_options)) + return 0; } if (prepare_revision_walk(&revs)) @@ -569,8 +637,15 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (bisect_list) { int reaches, all; + unsigned bisect_flags = 0; + + if (bisect_find_all) + bisect_flags |= FIND_BISECTION_ALL; + + if (revs.first_parent_only) + bisect_flags |= FIND_BISECTION_FIRST_PARENT_ONLY; - find_bisection(&revs.commits, &reaches, &all, bisect_find_all); + find_bisection(&revs.commits, &reaches, &all, bisect_flags); if (bisect_show_vars) return show_bisect_vars(&info, reaches, all); diff --git a/third_party/git/builtin/rev-parse.c b/third_party/git/builtin/rev-parse.c index f8bbe6d47ec3..ed200c8af128 100644 --- a/third_party/git/builtin/rev-parse.c +++ b/third_party/git/builtin/rev-parse.c @@ -16,6 +16,7 @@ #include "split-index.h" #include "submodule.h" #include "commit-reach.h" +#include "shallow.h" #define DO_REVS 1 #define DO_NOREV 2 @@ -135,7 +136,7 @@ static void show_rev(int type, const struct object_id *oid, const char *name) struct object_id discard; char *full; - switch (dwim_ref(name, strlen(name), &discard, &full)) { + switch (dwim_ref(name, strlen(name), &discard, &full, 0)) { case 0: /* * Not found -- not a ref. We could @@ -593,6 +594,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) const char *name = NULL; struct object_context unused; struct strbuf buf = STRBUF_INIT; + const int hexsz = the_hash_algo->hexsz; if (argc > 1 && !strcmp("--parseopt", argv[1])) return cmd_parseopt(argc - 1, argv + 1, prefix); @@ -730,8 +732,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) abbrev = strtoul(arg, NULL, 10); if (abbrev < MINIMUM_ABBREV) abbrev = MINIMUM_ABBREV; - else if (40 <= abbrev) - abbrev = 40; + else if (hexsz <= abbrev) + abbrev = hexsz; continue; } if (!strcmp(arg, "--sq")) { @@ -802,12 +804,15 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) const char *work_tree = get_git_work_tree(); if (work_tree) puts(work_tree); + else + die("this operation must be run in a work tree"); continue; } if (!strcmp(arg, "--show-superproject-working-tree")) { - const char *superproject = get_superproject_working_tree(); - if (superproject) - puts(superproject); + struct strbuf superproject = STRBUF_INIT; + if (get_superproject_working_tree(&superproject)) + puts(superproject.buf); + strbuf_release(&superproject); continue; } if (!strcmp(arg, "--show-prefix")) { @@ -854,7 +859,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (!gitdir && !prefix) gitdir = ".git"; if (gitdir) { - puts(real_path(gitdir)); + struct strbuf realpath = STRBUF_INIT; + strbuf_realpath(&realpath, gitdir, 1); + puts(realpath.buf); + strbuf_release(&realpath); continue; } } @@ -918,6 +926,17 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) show_datestring("--min-age=", arg); continue; } + if (opt_with_value(arg, "--show-object-format", &arg)) { + const char *val = arg ? arg : "storage"; + + if (strcmp(val, "storage") && + strcmp(val, "input") && + strcmp(val, "output")) + die("unknown mode for --show-object-format: %s", + arg); + puts(the_hash_algo->name); + continue; + } if (show_flag(arg) && verify) die_no_single_rev(quiet); continue; diff --git a/third_party/git/builtin/rm.c b/third_party/git/builtin/rm.c index 19ce95a901bc..4858631e0f02 100644 --- a/third_party/git/builtin/rm.c +++ b/third_party/git/builtin/rm.c @@ -235,7 +235,8 @@ static int check_local_mod(struct object_id *head, int index_only) } static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0; -static int ignore_unmatch = 0; +static int ignore_unmatch = 0, pathspec_file_nul; +static char *pathspec_from_file; static struct option builtin_rm_options[] = { OPT__DRY_RUN(&show_only, N_("dry run")), @@ -245,6 +246,8 @@ static struct option builtin_rm_options[] = { OPT_BOOL('r', NULL, &recursive, N_("allow recursive removal")), OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch, N_("exit with a zero status even if nothing matched")), + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END(), }; @@ -259,8 +262,24 @@ int cmd_rm(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_rm_options, builtin_rm_usage, 0); - if (!argc) - usage_with_options(builtin_rm_usage, builtin_rm_options); + + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_CWD, + prefix, argv); + + if (pathspec_from_file) { + if (pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&pathspec, 0, + PATHSPEC_PREFER_CWD, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + + if (!pathspec.nr) + die(_("No pathspec was given. Which files should I remove?")); if (!index_only) setup_work_tree(); @@ -270,9 +289,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die(_("index file corrupt")); - parse_pathspec(&pathspec, 0, - PATHSPEC_PREFER_CWD, - prefix, argv); refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &pathspec, NULL, NULL); seen = xcalloc(pathspec.nr, 1); diff --git a/third_party/git/builtin/send-pack.c b/third_party/git/builtin/send-pack.c index 098ebf22d0d6..7af148d7332f 100644 --- a/third_party/git/builtin/send-pack.c +++ b/third_party/git/builtin/send-pack.c @@ -11,7 +11,7 @@ #include "quote.h" #include "transport.h" #include "version.h" -#include "sha1-array.h" +#include "oid-array.h" #include "gpg-interface.h" #include "gettext.h" #include "protocol.h" @@ -29,10 +29,12 @@ static struct send_pack_args args; static void print_helper_status(struct ref *ref) { struct strbuf buf = STRBUF_INIT; + struct ref_push_report *report; for (; ref; ref = ref->next) { const char *msg = NULL; const char *res; + int count = 0; switch(ref->status) { case REF_STATUS_NONE: @@ -94,6 +96,23 @@ static void print_helper_status(struct ref *ref) } strbuf_addch(&buf, '\n'); + if (ref->status == REF_STATUS_OK) { + for (report = ref->report; report; report = report->next) { + if (count++ > 0) + strbuf_addf(&buf, "ok %s\n", ref->name); + if (report->ref_name) + strbuf_addf(&buf, "option refname %s\n", + report->ref_name); + if (report->old_oid) + strbuf_addf(&buf, "option old-oid %s\n", + oid_to_hex(report->old_oid)); + if (report->new_oid) + strbuf_addf(&buf, "option new-oid %s\n", + oid_to_hex(report->new_oid)); + if (report->forced_update) + strbuf_addstr(&buf, "option forced-update\n"); + } + } write_or_die(1, buf.buf, buf.len); } strbuf_release(&buf); @@ -165,9 +184,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) OPT_BOOL('n' , "dry-run", &dry_run, N_("dry run")), OPT_BOOL(0, "mirror", &send_mirror, N_("mirror all refs")), OPT_BOOL('f', "force", &force_update, N_("force updates")), - { OPTION_CALLBACK, - 0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"), - PARSE_OPT_OPTARG, option_parse_push_signed }, + OPT_CALLBACK_F(0, "signed", &push_cert, "(yes|no|if-asked)", N_("GPG sign the push"), + PARSE_OPT_OPTARG, option_parse_push_signed), OPT_STRING_LIST(0, "push-option", &push_options, N_("server-specific"), N_("option to transmit")), @@ -177,10 +195,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "stateless-rpc", &stateless_rpc, N_("use stateless RPC protocol")), OPT_BOOL(0, "stdin", &from_stdin, N_("read refs from stdin")), OPT_BOOL(0, "helper-status", &helper_status, N_("print status from remote helper")), - { OPTION_CALLBACK, - 0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"), + OPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"), N_("require old value of ref to be at this value"), - PARSE_OPT_OPTARG, parseopt_push_cas_option }, + PARSE_OPT_OPTARG, parseopt_push_cas_option), OPT_END() }; diff --git a/third_party/git/builtin/shortlog.c b/third_party/git/builtin/shortlog.c index 65cd41392c1c..0a5c4968f64e 100644 --- a/third_party/git/builtin/shortlog.c +++ b/third_party/git/builtin/shortlog.c @@ -9,6 +9,7 @@ #include "mailmap.h" #include "shortlog.h" #include "parse-options.h" +#include "trailer.h" static char const * const shortlog_usage[] = { N_("git shortlog [<options>] [<revision-range>] [[--] <path>...]"), @@ -49,12 +50,12 @@ static int compare_by_list(const void *a1, const void *a2) } static void insert_one_record(struct shortlog *log, - const char *author, + const char *ident, const char *oneline) { struct string_list_item *item; - item = string_list_insert(&log->list, author); + item = string_list_insert(&log->list, ident); if (log->summary) item->util = (void *)(UTIL_TO_INT(item) + 1); @@ -97,8 +98,8 @@ static void insert_one_record(struct shortlog *log, } } -static int parse_stdin_author(struct shortlog *log, - struct strbuf *out, const char *in) +static int parse_ident(struct shortlog *log, + struct strbuf *out, const char *in) { const char *mailbuf, *namebuf; size_t namelen, maillen; @@ -122,18 +123,33 @@ static int parse_stdin_author(struct shortlog *log, static void read_from_stdin(struct shortlog *log) { - struct strbuf author = STRBUF_INIT; - struct strbuf mapped_author = STRBUF_INIT; + struct strbuf ident = STRBUF_INIT; + struct strbuf mapped_ident = STRBUF_INIT; struct strbuf oneline = STRBUF_INIT; static const char *author_match[2] = { "Author: ", "author " }; static const char *committer_match[2] = { "Commit: ", "committer " }; const char **match; - match = log->committer ? committer_match : author_match; - while (strbuf_getline_lf(&author, stdin) != EOF) { + if (HAS_MULTI_BITS(log->groups)) + die(_("using multiple --group options with stdin is not supported")); + + switch (log->groups) { + case SHORTLOG_GROUP_AUTHOR: + match = author_match; + break; + case SHORTLOG_GROUP_COMMITTER: + match = committer_match; + break; + case SHORTLOG_GROUP_TRAILER: + die(_("using --group=trailer with stdin is not supported")); + default: + BUG("unhandled shortlog group"); + } + + while (strbuf_getline_lf(&ident, stdin) != EOF) { const char *v; - if (!skip_prefix(author.buf, match[0], &v) && - !skip_prefix(author.buf, match[1], &v)) + if (!skip_prefix(ident.buf, match[0], &v) && + !skip_prefix(ident.buf, match[1], &v)) continue; while (strbuf_getline_lf(&oneline, stdin) != EOF && oneline.len) @@ -142,23 +158,118 @@ static void read_from_stdin(struct shortlog *log) !oneline.len) ; /* discard blanks */ - strbuf_reset(&mapped_author); - if (parse_stdin_author(log, &mapped_author, v) < 0) + strbuf_reset(&mapped_ident); + if (parse_ident(log, &mapped_ident, v) < 0) continue; - insert_one_record(log, mapped_author.buf, oneline.buf); + insert_one_record(log, mapped_ident.buf, oneline.buf); } - strbuf_release(&author); - strbuf_release(&mapped_author); + strbuf_release(&ident); + strbuf_release(&mapped_ident); strbuf_release(&oneline); } +struct strset_item { + struct hashmap_entry ent; + char value[FLEX_ARRAY]; +}; + +struct strset { + struct hashmap map; +}; + +#define STRSET_INIT { { NULL } } + +static int strset_item_hashcmp(const void *hash_data, + const struct hashmap_entry *entry, + const struct hashmap_entry *entry_or_key, + const void *keydata) +{ + const struct strset_item *a, *b; + + a = container_of(entry, const struct strset_item, ent); + if (keydata) + return strcmp(a->value, keydata); + + b = container_of(entry_or_key, const struct strset_item, ent); + return strcmp(a->value, b->value); +} + +/* + * Adds "str" to the set if it was not already present; returns true if it was + * already there. + */ +static int strset_check_and_add(struct strset *ss, const char *str) +{ + unsigned int hash = strhash(str); + struct strset_item *item; + + if (!ss->map.table) + hashmap_init(&ss->map, strset_item_hashcmp, NULL, 0); + + if (hashmap_get_from_hash(&ss->map, hash, str)) + return 1; + + FLEX_ALLOC_STR(item, value, str); + hashmap_entry_init(&item->ent, hash); + hashmap_add(&ss->map, &item->ent); + return 0; +} + +static void strset_clear(struct strset *ss) +{ + if (!ss->map.table) + return; + hashmap_free_entries(&ss->map, struct strset_item, ent); +} + +static void insert_records_from_trailers(struct shortlog *log, + struct strset *dups, + struct commit *commit, + struct pretty_print_context *ctx, + const char *oneline) +{ + struct trailer_iterator iter; + const char *commit_buffer, *body; + struct strbuf ident = STRBUF_INIT; + + /* + * Using format_commit_message("%B") would be simpler here, but + * this saves us copying the message. + */ + commit_buffer = logmsg_reencode(commit, NULL, ctx->output_encoding); + body = strstr(commit_buffer, "\n\n"); + if (!body) + return; + + trailer_iterator_init(&iter, body); + while (trailer_iterator_advance(&iter)) { + const char *value = iter.val.buf; + + if (!string_list_has_string(&log->trailers, iter.key.buf)) + continue; + + strbuf_reset(&ident); + if (!parse_ident(log, &ident, value)) + value = ident.buf; + + if (strset_check_and_add(dups, value)) + continue; + insert_one_record(log, value, oneline); + } + trailer_iterator_release(&iter); + + strbuf_release(&ident); + unuse_commit_buffer(commit, commit_buffer); +} + void shortlog_add_commit(struct shortlog *log, struct commit *commit) { - struct strbuf author = STRBUF_INIT; + struct strbuf ident = STRBUF_INIT; struct strbuf oneline = STRBUF_INIT; + struct strset dups = STRSET_INIT; struct pretty_print_context ctx = {0}; - const char *fmt; + const char *oneline_str; ctx.fmt = CMIT_FMT_USERFORMAT; ctx.abbrev = log->abbrev; @@ -166,21 +277,38 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) ctx.date_mode.type = DATE_NORMAL; ctx.output_encoding = get_log_output_encoding(); - fmt = log->committer ? - (log->email ? "%cN <%cE>" : "%cN") : - (log->email ? "%aN <%aE>" : "%aN"); - - format_commit_message(commit, fmt, &author, &ctx); if (!log->summary) { if (log->user_format) pretty_print_commit(&ctx, commit, &oneline); else format_commit_message(commit, "%s", &oneline, &ctx); } + oneline_str = oneline.len ? oneline.buf : "<none>"; + + if (log->groups & SHORTLOG_GROUP_AUTHOR) { + strbuf_reset(&ident); + format_commit_message(commit, + log->email ? "%aN <%aE>" : "%aN", + &ident, &ctx); + if (!HAS_MULTI_BITS(log->groups) || + !strset_check_and_add(&dups, ident.buf)) + insert_one_record(log, ident.buf, oneline_str); + } + if (log->groups & SHORTLOG_GROUP_COMMITTER) { + strbuf_reset(&ident); + format_commit_message(commit, + log->email ? "%cN <%cE>" : "%cN", + &ident, &ctx); + if (!HAS_MULTI_BITS(log->groups) || + !strset_check_and_add(&dups, ident.buf)) + insert_one_record(log, ident.buf, oneline_str); + } + if (log->groups & SHORTLOG_GROUP_TRAILER) { + insert_records_from_trailers(log, &dups, commit, &ctx, oneline_str); + } - insert_one_record(log, author.buf, oneline.len ? oneline.buf : "<none>"); - - strbuf_release(&author); + strset_clear(&dups); + strbuf_release(&ident); strbuf_release(&oneline); } @@ -241,6 +369,28 @@ static int parse_wrap_args(const struct option *opt, const char *arg, int unset) return 0; } +static int parse_group_option(const struct option *opt, const char *arg, int unset) +{ + struct shortlog *log = opt->value; + const char *field; + + if (unset) { + log->groups = 0; + string_list_clear(&log->trailers, 0); + } else if (!strcasecmp(arg, "author")) + log->groups |= SHORTLOG_GROUP_AUTHOR; + else if (!strcasecmp(arg, "committer")) + log->groups |= SHORTLOG_GROUP_COMMITTER; + else if (skip_prefix(arg, "trailer:", &field)) { + log->groups |= SHORTLOG_GROUP_TRAILER; + string_list_append(&log->trailers, field); + } else + return error(_("unknown group type: %s"), arg); + + return 0; +} + + void shortlog_init(struct shortlog *log) { memset(log, 0, sizeof(*log)); @@ -251,6 +401,8 @@ void shortlog_init(struct shortlog *log) log->wrap = DEFAULT_WRAPLEN; log->in1 = DEFAULT_INDENT1; log->in2 = DEFAULT_INDENT2; + log->trailers.strdup_strings = 1; + log->trailers.cmp = strcasecmp; } int cmd_shortlog(int argc, const char **argv, const char *prefix) @@ -260,17 +412,20 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) int nongit = !startup_info->have_repository; const struct option options[] = { - OPT_BOOL('c', "committer", &log.committer, - N_("Group by committer rather than author")), + OPT_BIT('c', "committer", &log.groups, + N_("Group by committer rather than author"), + SHORTLOG_GROUP_COMMITTER), OPT_BOOL('n', "numbered", &log.sort_by_number, N_("sort output according to the number of commits per author")), OPT_BOOL('s', "summary", &log.summary, N_("Suppress commit descriptions, only provides commit count")), OPT_BOOL('e', "email", &log.email, N_("Show the email address of each author")), - { OPTION_CALLBACK, 'w', NULL, &log, N_("<w>[,<i1>[,<i2>]]"), + OPT_CALLBACK_F('w', NULL, &log, N_("<w>[,<i1>[,<i2>]]"), N_("Linewrap output"), PARSE_OPT_OPTARG, - &parse_wrap_args }, + &parse_wrap_args), + OPT_CALLBACK(0, "group", &log, N_("field"), + N_("Group by field"), parse_group_option), OPT_END(), }; @@ -311,6 +466,10 @@ parse_done: log.abbrev = rev.abbrev; log.file = rev.diffopt.file; + if (!log.groups) + log.groups = SHORTLOG_GROUP_AUTHOR; + string_list_sort(&log.trailers); + /* assume HEAD if from a tty */ if (!nongit && !rev.pending.nr && isatty(0)) add_head_to_pending(&rev); diff --git a/third_party/git/builtin/show-branch.c b/third_party/git/builtin/show-branch.c index 35d7f51c236d..d6d2dabeca87 100644 --- a/third_party/git/builtin/show-branch.c +++ b/third_party/git/builtin/show-branch.c @@ -4,7 +4,7 @@ #include "refs.h" #include "builtin.h" #include "color.h" -#include "argv-array.h" +#include "strvec.h" #include "parse-options.h" #include "dir.h" #include "commit-slab.h" @@ -20,7 +20,7 @@ static const char* show_branch_usage[] = { static int showbranch_use_color = -1; -static struct argv_array default_args = ARGV_ARRAY_INIT; +static struct strvec default_args = STRVEC_INIT; /* * TODO: convert this use of commit->object.flags to commit-slab @@ -536,7 +536,7 @@ static void append_one_rev(const char *av) append_ref(av, &revkey, 0); return; } - if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) { + if (strpbrk(av, "*?[")) { /* glob style match */ int saved_matches = ref_name_cnt; @@ -561,9 +561,9 @@ static int git_show_branch_config(const char *var, const char *value, void *cb) * default_arg is now passed to parse_options(), so we need to * mimic the real argv a bit better. */ - if (!default_args.argc) - argv_array_push(&default_args, "show-branch"); - argv_array_push(&default_args, value); + if (!default_args.nr) + strvec_push(&default_args, "show-branch"); + strvec_push(&default_args, value); return 0; } @@ -671,11 +671,11 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) N_("topologically sort, maintaining date order " "where possible"), REV_SORT_BY_COMMIT_DATE), - { OPTION_CALLBACK, 'g', "reflog", &reflog_base, N_("<n>[,<base>]"), + OPT_CALLBACK_F('g', "reflog", &reflog_base, N_("<n>[,<base>]"), N_("show <n> most recent ref-log entries starting at " "base"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, - parse_reflog_param }, + parse_reflog_param), OPT_END() }; @@ -684,9 +684,9 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) git_config(git_show_branch_config, NULL); /* If nothing is specified, try the default first */ - if (ac == 1 && default_args.argc) { - ac = default_args.argc; - av = default_args.argv; + if (ac == 1 && default_args.nr) { + ac = default_args.nr; + av = default_args.v; } ac = parse_options(ac, av, prefix, builtin_show_branch_options, @@ -741,7 +741,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) die(Q_("only %d entry can be shown at one time.", "only %d entries can be shown at one time.", MAX_REVS), MAX_REVS); - if (!dwim_ref(*av, strlen(*av), &oid, &ref)) + if (!dwim_ref(*av, strlen(*av), &oid, &ref, 0)) die(_("no such ref %s"), *av); /* Has the base been specified? */ diff --git a/third_party/git/builtin/show-index.c b/third_party/git/builtin/show-index.c index a6e678809efe..8106b03a6b32 100644 --- a/third_party/git/builtin/show-index.c +++ b/third_party/git/builtin/show-index.c @@ -1,9 +1,12 @@ #include "builtin.h" #include "cache.h" #include "pack.h" +#include "parse-options.h" -static const char show_index_usage[] = -"git show-index"; +static const char *const show_index_usage[] = { + "git show-index [--object-format=<hash-algorithm>]", + NULL +}; int cmd_show_index(int argc, const char **argv, const char *prefix) { @@ -11,9 +14,26 @@ int cmd_show_index(int argc, const char **argv, const char *prefix) unsigned nr; unsigned int version; static unsigned int top_index[256]; + unsigned hashsz; + const char *hash_name = NULL; + int hash_algo; + const struct option show_index_options[] = { + OPT_STRING(0, "object-format", &hash_name, N_("hash-algorithm"), + N_("specify the hash algorithm to use")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, show_index_options, show_index_usage, 0); + + if (hash_name) { + hash_algo = hash_algo_by_name(hash_name); + if (hash_algo == GIT_HASH_UNKNOWN) + die(_("Unknown hash algorithm")); + repo_set_hash_algo(the_repository, hash_algo); + } + + hashsz = the_hash_algo->rawsz; - if (argc != 1) - usage(show_index_usage); if (fread(top_index, 2 * 4, 1, stdin) != 1) die("unable to read header"); if (top_index[0] == htonl(PACK_IDX_SIGNATURE)) { @@ -36,23 +56,23 @@ int cmd_show_index(int argc, const char **argv, const char *prefix) } if (version == 1) { for (i = 0; i < nr; i++) { - unsigned int offset, entry[6]; + unsigned int offset, entry[(GIT_MAX_RAWSZ + 4) / sizeof(unsigned int)]; - if (fread(entry, 4 + 20, 1, stdin) != 1) + if (fread(entry, 4 + hashsz, 1, stdin) != 1) die("unable to read entry %u/%u", i, nr); offset = ntohl(entry[0]); - printf("%u %s\n", offset, sha1_to_hex((void *)(entry+1))); + printf("%u %s\n", offset, hash_to_hex((void *)(entry+1))); } } else { unsigned off64_nr = 0; struct { - unsigned char sha1[20]; + struct object_id oid; uint32_t crc; uint32_t off; } *entries; ALLOC_ARRAY(entries, nr); for (i = 0; i < nr; i++) - if (fread(entries[i].sha1, 20, 1, stdin) != 1) + if (fread(entries[i].oid.hash, hashsz, 1, stdin) != 1) die("unable to read sha1 %u/%u", i, nr); for (i = 0; i < nr; i++) if (fread(&entries[i].crc, 4, 1, stdin) != 1) @@ -77,7 +97,7 @@ int cmd_show_index(int argc, const char **argv, const char *prefix) } printf("%" PRIuMAX " %s (%08"PRIx32")\n", (uintmax_t) offset, - sha1_to_hex(entries[i].sha1), + oid_to_hex(&entries[i].oid), ntohl(entries[i].crc)); } free(entries); diff --git a/third_party/git/builtin/show-ref.c b/third_party/git/builtin/show-ref.c index 6456da70cc2c..ae60b4acf2f4 100644 --- a/third_party/git/builtin/show-ref.c +++ b/third_party/git/builtin/show-ref.c @@ -169,15 +169,15 @@ static const struct option show_ref_options[] = { N_("show the HEAD reference, even if it would be filtered out")), OPT_BOOL('d', "dereference", &deref_tags, N_("dereference tags into object IDs")), - { OPTION_CALLBACK, 's', "hash", &abbrev, N_("n"), - N_("only show SHA1 hash using <n> digits"), - PARSE_OPT_OPTARG, &hash_callback }, + OPT_CALLBACK_F('s', "hash", &abbrev, N_("n"), + N_("only show SHA1 hash using <n> digits"), + PARSE_OPT_OPTARG, &hash_callback), OPT__ABBREV(&abbrev), OPT__QUIET(&quiet, N_("do not print results to stdout (useful with --verify)")), - { OPTION_CALLBACK, 0, "exclude-existing", &exclude_existing_arg, - N_("pattern"), N_("show refs from stdin that aren't in local repository"), - PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback }, + OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_arg, + N_("pattern"), N_("show refs from stdin that aren't in local repository"), + PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback), OPT_END() }; diff --git a/third_party/git/builtin/sparse-checkout.c b/third_party/git/builtin/sparse-checkout.c new file mode 100644 index 000000000000..e3140db2a0a6 --- /dev/null +++ b/third_party/git/builtin/sparse-checkout.c @@ -0,0 +1,663 @@ +#include "builtin.h" +#include "config.h" +#include "dir.h" +#include "parse-options.h" +#include "pathspec.h" +#include "repository.h" +#include "run-command.h" +#include "strbuf.h" +#include "string-list.h" +#include "cache.h" +#include "cache-tree.h" +#include "lockfile.h" +#include "resolve-undo.h" +#include "unpack-trees.h" +#include "wt-status.h" +#include "quote.h" + +static const char *empty_base = ""; + +static char const * const builtin_sparse_checkout_usage[] = { + N_("git sparse-checkout (init|list|set|add|reapply|disable) <options>"), + NULL +}; + +static char *get_sparse_checkout_filename(void) +{ + return git_pathdup("info/sparse-checkout"); +} + +static void write_patterns_to_file(FILE *fp, struct pattern_list *pl) +{ + int i; + + for (i = 0; i < pl->nr; i++) { + struct path_pattern *p = pl->patterns[i]; + + if (p->flags & PATTERN_FLAG_NEGATIVE) + fprintf(fp, "!"); + + fprintf(fp, "%s", p->pattern); + + if (p->flags & PATTERN_FLAG_MUSTBEDIR) + fprintf(fp, "/"); + + fprintf(fp, "\n"); + } +} + +static char const * const builtin_sparse_checkout_list_usage[] = { + N_("git sparse-checkout list"), + NULL +}; + +static int sparse_checkout_list(int argc, const char **argv) +{ + static struct option builtin_sparse_checkout_list_options[] = { + OPT_END(), + }; + struct pattern_list pl; + char *sparse_filename; + int res; + + argc = parse_options(argc, argv, NULL, + builtin_sparse_checkout_list_options, + builtin_sparse_checkout_list_usage, 0); + + memset(&pl, 0, sizeof(pl)); + + pl.use_cone_patterns = core_sparse_checkout_cone; + + sparse_filename = get_sparse_checkout_filename(); + res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL); + free(sparse_filename); + + if (res < 0) { + warning(_("this worktree is not sparse (sparse-checkout file may not exist)")); + return 0; + } + + if (pl.use_cone_patterns) { + int i; + struct pattern_entry *pe; + struct hashmap_iter iter; + struct string_list sl = STRING_LIST_INIT_DUP; + + hashmap_for_each_entry(&pl.recursive_hashmap, &iter, pe, ent) { + /* pe->pattern starts with "/", skip it */ + string_list_insert(&sl, pe->pattern + 1); + } + + string_list_sort(&sl); + + for (i = 0; i < sl.nr; i++) { + quote_c_style(sl.items[i].string, NULL, stdout, 0); + printf("\n"); + } + + return 0; + } + + write_patterns_to_file(stdout, &pl); + clear_pattern_list(&pl); + + return 0; +} + +static int update_working_directory(struct pattern_list *pl) +{ + enum update_sparsity_result result; + struct unpack_trees_options o; + struct lock_file lock_file = LOCK_INIT; + struct repository *r = the_repository; + + /* If no branch has been checked out, there are no updates to make. */ + if (is_index_unborn(r->index)) + return UPDATE_SPARSITY_SUCCESS; + + memset(&o, 0, sizeof(o)); + o.verbose_update = isatty(2); + o.update = 1; + o.head_idx = -1; + o.src_index = r->index; + o.dst_index = r->index; + o.skip_sparse_checkout = 0; + o.pl = pl; + + setup_work_tree(); + + repo_hold_locked_index(r, &lock_file, LOCK_DIE_ON_ERROR); + + setup_unpack_trees_porcelain(&o, "sparse-checkout"); + result = update_sparsity(&o); + clear_unpack_trees_porcelain(&o); + + if (result == UPDATE_SPARSITY_WARNINGS) + /* + * We don't do any special handling of warnings from untracked + * files in the way or dirty entries that can't be removed. + */ + result = UPDATE_SPARSITY_SUCCESS; + if (result == UPDATE_SPARSITY_SUCCESS) + write_locked_index(r->index, &lock_file, COMMIT_LOCK); + else + rollback_lock_file(&lock_file); + + return result; +} + +static char *escaped_pattern(char *pattern) +{ + char *p = pattern; + struct strbuf final = STRBUF_INIT; + + while (*p) { + if (is_glob_special(*p)) + strbuf_addch(&final, '\\'); + + strbuf_addch(&final, *p); + p++; + } + + return strbuf_detach(&final, NULL); +} + +static void write_cone_to_file(FILE *fp, struct pattern_list *pl) +{ + int i; + struct pattern_entry *pe; + struct hashmap_iter iter; + struct string_list sl = STRING_LIST_INIT_DUP; + struct strbuf parent_pattern = STRBUF_INIT; + + hashmap_for_each_entry(&pl->parent_hashmap, &iter, pe, ent) { + if (hashmap_get_entry(&pl->recursive_hashmap, pe, ent, NULL)) + continue; + + if (!hashmap_contains_parent(&pl->recursive_hashmap, + pe->pattern, + &parent_pattern)) + string_list_insert(&sl, pe->pattern); + } + + string_list_sort(&sl); + string_list_remove_duplicates(&sl, 0); + + fprintf(fp, "/*\n!/*/\n"); + + for (i = 0; i < sl.nr; i++) { + char *pattern = escaped_pattern(sl.items[i].string); + + if (strlen(pattern)) + fprintf(fp, "%s/\n!%s/*/\n", pattern, pattern); + free(pattern); + } + + string_list_clear(&sl, 0); + + hashmap_for_each_entry(&pl->recursive_hashmap, &iter, pe, ent) { + if (!hashmap_contains_parent(&pl->recursive_hashmap, + pe->pattern, + &parent_pattern)) + string_list_insert(&sl, pe->pattern); + } + + strbuf_release(&parent_pattern); + + string_list_sort(&sl); + string_list_remove_duplicates(&sl, 0); + + for (i = 0; i < sl.nr; i++) { + char *pattern = escaped_pattern(sl.items[i].string); + fprintf(fp, "%s/\n", pattern); + free(pattern); + } +} + +static int write_patterns_and_update(struct pattern_list *pl) +{ + char *sparse_filename; + FILE *fp; + int fd; + struct lock_file lk = LOCK_INIT; + int result; + + sparse_filename = get_sparse_checkout_filename(); + + if (safe_create_leading_directories(sparse_filename)) + die(_("failed to create directory for sparse-checkout file")); + + fd = hold_lock_file_for_update(&lk, sparse_filename, + LOCK_DIE_ON_ERROR); + + result = update_working_directory(pl); + if (result) { + rollback_lock_file(&lk); + free(sparse_filename); + clear_pattern_list(pl); + update_working_directory(NULL); + return result; + } + + fp = xfdopen(fd, "w"); + + if (core_sparse_checkout_cone) + write_cone_to_file(fp, pl); + else + write_patterns_to_file(fp, pl); + + fflush(fp); + commit_lock_file(&lk); + + free(sparse_filename); + clear_pattern_list(pl); + + return 0; +} + +enum sparse_checkout_mode { + MODE_NO_PATTERNS = 0, + MODE_ALL_PATTERNS = 1, + MODE_CONE_PATTERNS = 2, +}; + +static int set_config(enum sparse_checkout_mode mode) +{ + const char *config_path; + + if (upgrade_repository_format(1) < 0) + die(_("unable to upgrade repository format to enable worktreeConfig")); + if (git_config_set_gently("extensions.worktreeConfig", "true")) { + error(_("failed to set extensions.worktreeConfig setting")); + return 1; + } + + config_path = git_path("config.worktree"); + git_config_set_in_file_gently(config_path, + "core.sparseCheckout", + mode ? "true" : NULL); + + git_config_set_in_file_gently(config_path, + "core.sparseCheckoutCone", + mode == MODE_CONE_PATTERNS ? "true" : NULL); + + return 0; +} + +static char const * const builtin_sparse_checkout_init_usage[] = { + N_("git sparse-checkout init [--cone]"), + NULL +}; + +static struct sparse_checkout_init_opts { + int cone_mode; +} init_opts; + +static int sparse_checkout_init(int argc, const char **argv) +{ + struct pattern_list pl; + char *sparse_filename; + int res; + struct object_id oid; + int mode; + struct strbuf pattern = STRBUF_INIT; + + static struct option builtin_sparse_checkout_init_options[] = { + OPT_BOOL(0, "cone", &init_opts.cone_mode, + N_("initialize the sparse-checkout in cone mode")), + OPT_END(), + }; + + repo_read_index(the_repository); + + argc = parse_options(argc, argv, NULL, + builtin_sparse_checkout_init_options, + builtin_sparse_checkout_init_usage, 0); + + if (init_opts.cone_mode) { + mode = MODE_CONE_PATTERNS; + core_sparse_checkout_cone = 1; + } else + mode = MODE_ALL_PATTERNS; + + if (set_config(mode)) + return 1; + + memset(&pl, 0, sizeof(pl)); + + sparse_filename = get_sparse_checkout_filename(); + res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL); + + /* If we already have a sparse-checkout file, use it. */ + if (res >= 0) { + free(sparse_filename); + core_apply_sparse_checkout = 1; + return update_working_directory(NULL); + } + + if (get_oid("HEAD", &oid)) { + FILE *fp; + + /* assume we are in a fresh repo, but update the sparse-checkout file */ + fp = xfopen(sparse_filename, "w"); + if (!fp) + die(_("failed to open '%s'"), sparse_filename); + + free(sparse_filename); + fprintf(fp, "/*\n!/*/\n"); + fclose(fp); + return 0; + } + + strbuf_addstr(&pattern, "/*"); + add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0); + strbuf_addstr(&pattern, "!/*/"); + add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0); + + return write_patterns_and_update(&pl); +} + +static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *path) +{ + struct pattern_entry *e = xmalloc(sizeof(*e)); + e->patternlen = path->len; + e->pattern = strbuf_detach(path, NULL); + hashmap_entry_init(&e->ent, + ignore_case ? + strihash(e->pattern) : + strhash(e->pattern)); + + hashmap_add(&pl->recursive_hashmap, &e->ent); + + while (e->patternlen) { + char *slash = strrchr(e->pattern, '/'); + char *oldpattern = e->pattern; + size_t newlen; + + if (slash == e->pattern) + break; + + newlen = slash - e->pattern; + e = xmalloc(sizeof(struct pattern_entry)); + e->patternlen = newlen; + e->pattern = xstrndup(oldpattern, newlen); + hashmap_entry_init(&e->ent, + ignore_case ? + strihash(e->pattern) : + strhash(e->pattern)); + + if (!hashmap_get_entry(&pl->parent_hashmap, e, ent, NULL)) + hashmap_add(&pl->parent_hashmap, &e->ent); + } +} + +static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl) +{ + strbuf_trim(line); + + strbuf_trim_trailing_dir_sep(line); + + if (strbuf_normalize_path(line)) + die(_("could not normalize path %s"), line->buf); + + if (!line->len) + return; + + if (line->buf[0] != '/') + strbuf_insertstr(line, 0, "/"); + + insert_recursive_pattern(pl, line); +} + +static char const * const builtin_sparse_checkout_set_usage[] = { + N_("git sparse-checkout (set|add) (--stdin | <patterns>)"), + NULL +}; + +static struct sparse_checkout_set_opts { + int use_stdin; +} set_opts; + +static void add_patterns_from_input(struct pattern_list *pl, + int argc, const char **argv) +{ + int i; + if (core_sparse_checkout_cone) { + struct strbuf line = STRBUF_INIT; + + hashmap_init(&pl->recursive_hashmap, pl_hashmap_cmp, NULL, 0); + hashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0); + pl->use_cone_patterns = 1; + + if (set_opts.use_stdin) { + struct strbuf unquoted = STRBUF_INIT; + while (!strbuf_getline(&line, stdin)) { + if (line.buf[0] == '"') { + strbuf_reset(&unquoted); + if (unquote_c_style(&unquoted, line.buf, NULL)) + die(_("unable to unquote C-style string '%s'"), + line.buf); + + strbuf_swap(&unquoted, &line); + } + + strbuf_to_cone_pattern(&line, pl); + } + + strbuf_release(&unquoted); + } else { + for (i = 0; i < argc; i++) { + strbuf_setlen(&line, 0); + strbuf_addstr(&line, argv[i]); + strbuf_to_cone_pattern(&line, pl); + } + } + } else { + if (set_opts.use_stdin) { + struct strbuf line = STRBUF_INIT; + + while (!strbuf_getline(&line, stdin)) { + size_t len; + char *buf = strbuf_detach(&line, &len); + add_pattern(buf, empty_base, 0, pl, 0); + } + } else { + for (i = 0; i < argc; i++) + add_pattern(argv[i], empty_base, 0, pl, 0); + } + } +} + +enum modify_type { + REPLACE, + ADD, +}; + +static void add_patterns_cone_mode(int argc, const char **argv, + struct pattern_list *pl) +{ + struct strbuf buffer = STRBUF_INIT; + struct pattern_entry *pe; + struct hashmap_iter iter; + struct pattern_list existing; + char *sparse_filename = get_sparse_checkout_filename(); + + add_patterns_from_input(pl, argc, argv); + + memset(&existing, 0, sizeof(existing)); + existing.use_cone_patterns = core_sparse_checkout_cone; + + if (add_patterns_from_file_to_list(sparse_filename, "", 0, + &existing, NULL)) + die(_("unable to load existing sparse-checkout patterns")); + free(sparse_filename); + + hashmap_for_each_entry(&existing.recursive_hashmap, &iter, pe, ent) { + if (!hashmap_contains_parent(&pl->recursive_hashmap, + pe->pattern, &buffer) || + !hashmap_contains_parent(&pl->parent_hashmap, + pe->pattern, &buffer)) { + strbuf_reset(&buffer); + strbuf_addstr(&buffer, pe->pattern); + insert_recursive_pattern(pl, &buffer); + } + } + + clear_pattern_list(&existing); + strbuf_release(&buffer); +} + +static void add_patterns_literal(int argc, const char **argv, + struct pattern_list *pl) +{ + char *sparse_filename = get_sparse_checkout_filename(); + if (add_patterns_from_file_to_list(sparse_filename, "", 0, + pl, NULL)) + die(_("unable to load existing sparse-checkout patterns")); + free(sparse_filename); + add_patterns_from_input(pl, argc, argv); +} + +static int modify_pattern_list(int argc, const char **argv, enum modify_type m) +{ + int result; + int changed_config = 0; + struct pattern_list pl; + memset(&pl, 0, sizeof(pl)); + + switch (m) { + case ADD: + if (core_sparse_checkout_cone) + add_patterns_cone_mode(argc, argv, &pl); + else + add_patterns_literal(argc, argv, &pl); + break; + + case REPLACE: + add_patterns_from_input(&pl, argc, argv); + break; + } + + if (!core_apply_sparse_checkout) { + set_config(MODE_ALL_PATTERNS); + core_apply_sparse_checkout = 1; + changed_config = 1; + } + + result = write_patterns_and_update(&pl); + + if (result && changed_config) + set_config(MODE_NO_PATTERNS); + + clear_pattern_list(&pl); + return result; +} + +static int sparse_checkout_set(int argc, const char **argv, const char *prefix, + enum modify_type m) +{ + static struct option builtin_sparse_checkout_set_options[] = { + OPT_BOOL(0, "stdin", &set_opts.use_stdin, + N_("read patterns from standard in")), + OPT_END(), + }; + + repo_read_index(the_repository); + + argc = parse_options(argc, argv, prefix, + builtin_sparse_checkout_set_options, + builtin_sparse_checkout_set_usage, + PARSE_OPT_KEEP_UNKNOWN); + + return modify_pattern_list(argc, argv, m); +} + +static char const * const builtin_sparse_checkout_reapply_usage[] = { + N_("git sparse-checkout reapply"), + NULL +}; + +static int sparse_checkout_reapply(int argc, const char **argv) +{ + static struct option builtin_sparse_checkout_reapply_options[] = { + OPT_END(), + }; + + argc = parse_options(argc, argv, NULL, + builtin_sparse_checkout_reapply_options, + builtin_sparse_checkout_reapply_usage, 0); + + repo_read_index(the_repository); + return update_working_directory(NULL); +} + +static char const * const builtin_sparse_checkout_disable_usage[] = { + N_("git sparse-checkout disable"), + NULL +}; + +static int sparse_checkout_disable(int argc, const char **argv) +{ + static struct option builtin_sparse_checkout_disable_options[] = { + OPT_END(), + }; + struct pattern_list pl; + struct strbuf match_all = STRBUF_INIT; + + argc = parse_options(argc, argv, NULL, + builtin_sparse_checkout_disable_options, + builtin_sparse_checkout_disable_usage, 0); + + repo_read_index(the_repository); + + memset(&pl, 0, sizeof(pl)); + hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0); + hashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0); + pl.use_cone_patterns = 0; + core_apply_sparse_checkout = 1; + + strbuf_addstr(&match_all, "/*"); + add_pattern(strbuf_detach(&match_all, NULL), empty_base, 0, &pl, 0); + + if (update_working_directory(&pl)) + die(_("error while refreshing working directory")); + + clear_pattern_list(&pl); + return set_config(MODE_NO_PATTERNS); +} + +int cmd_sparse_checkout(int argc, const char **argv, const char *prefix) +{ + static struct option builtin_sparse_checkout_options[] = { + OPT_END(), + }; + + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_sparse_checkout_usage, + builtin_sparse_checkout_options); + + argc = parse_options(argc, argv, prefix, + builtin_sparse_checkout_options, + builtin_sparse_checkout_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + git_config(git_default_config, NULL); + + if (argc > 0) { + if (!strcmp(argv[0], "list")) + return sparse_checkout_list(argc, argv); + if (!strcmp(argv[0], "init")) + return sparse_checkout_init(argc, argv); + if (!strcmp(argv[0], "set")) + return sparse_checkout_set(argc, argv, prefix, REPLACE); + if (!strcmp(argv[0], "add")) + return sparse_checkout_set(argc, argv, prefix, ADD); + if (!strcmp(argv[0], "reapply")) + return sparse_checkout_reapply(argc, argv); + if (!strcmp(argv[0], "disable")) + return sparse_checkout_disable(argc, argv); + } + + usage_with_options(builtin_sparse_checkout_usage, + builtin_sparse_checkout_options); +} diff --git a/third_party/git/builtin/stash.c b/third_party/git/builtin/stash.c index b5a301f24d7a..3f811f30506a 100644 --- a/third_party/git/builtin/stash.c +++ b/third_party/git/builtin/stash.c @@ -7,7 +7,7 @@ #include "cache-tree.h" #include "unpack-trees.h" #include "merge-recursive.h" -#include "argv-array.h" +#include "strvec.h" #include "run-command.h" #include "dir.h" #include "rerere.h" @@ -27,6 +27,7 @@ static const char * const git_stash_usage[] = { N_("git stash clear"), N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" " [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n" + " [--pathspec-from-file=<file> [--pathspec-file-nul]]\n" " [--] [<pathspec>...]]"), N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" " [-u|--include-untracked] [-a|--all] [<message>]"), @@ -184,7 +185,7 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv) end_of_rev = strchrnul(revision, '@'); strbuf_add(&symbolic, revision, end_of_rev - revision); - ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref); + ret = dwim_ref(symbolic.buf, symbolic.len, &dummy, &expanded_ref, 0); strbuf_release(&symbolic); switch (ret) { case 0: /* Not found, but valid ref */ @@ -276,8 +277,8 @@ static int diff_tree_binary(struct strbuf *out, struct object_id *w_commit) * however it should be done together with apply_cached. */ cp.git_cmd = 1; - argv_array_pushl(&cp.args, "diff-tree", "--binary", NULL); - argv_array_pushf(&cp.args, "%s^2^..%s^2", w_commit_hex, w_commit_hex); + strvec_pushl(&cp.args, "diff-tree", "--binary", NULL); + strvec_pushf(&cp.args, "%s^2^..%s^2", w_commit_hex, w_commit_hex); return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); } @@ -292,7 +293,7 @@ static int apply_cached(struct strbuf *out) * buffer. */ cp.git_cmd = 1; - argv_array_pushl(&cp.args, "apply", "--cached", NULL); + strvec_pushl(&cp.args, "apply", "--cached", NULL); return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0); } @@ -305,7 +306,7 @@ static int reset_head(void) * API for resetting. */ cp.git_cmd = 1; - argv_array_push(&cp.args, "reset"); + strvec_push(&cp.args, "reset"); return run_command(&cp); } @@ -334,9 +335,9 @@ static int get_newly_staged(struct strbuf *out, struct object_id *c_tree) * converted together with update_index. */ cp.git_cmd = 1; - argv_array_pushl(&cp.args, "diff-index", "--cached", "--name-only", - "--diff-filter=A", NULL); - argv_array_push(&cp.args, c_tree_hex); + strvec_pushl(&cp.args, "diff-index", "--cached", "--name-only", + "--diff-filter=A", NULL); + strvec_push(&cp.args, c_tree_hex); return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); } @@ -349,7 +350,7 @@ static int update_index(struct strbuf *out) * function exposed in order to remove this forking. */ cp.git_cmd = 1; - argv_array_pushl(&cp.args, "update-index", "--add", "--stdin", NULL); + strvec_pushl(&cp.args, "update-index", "--add", "--stdin", NULL); return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0); } @@ -364,10 +365,10 @@ static int restore_untracked(struct object_id *u_tree) * run_command to fork processes that will not interfere. */ cp.git_cmd = 1; - argv_array_push(&cp.args, "read-tree"); - argv_array_push(&cp.args, oid_to_hex(u_tree)); - argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", - stash_index_path.buf); + strvec_push(&cp.args, "read-tree"); + strvec_push(&cp.args, oid_to_hex(u_tree)); + strvec_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); if (run_command(&cp)) { remove_path(stash_index_path.buf); return -1; @@ -375,9 +376,9 @@ static int restore_untracked(struct object_id *u_tree) child_process_init(&cp); cp.git_cmd = 1; - argv_array_pushl(&cp.args, "checkout-index", "--all", NULL); - argv_array_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", - stash_index_path.buf); + strvec_pushl(&cp.args, "checkout-index", "--all", NULL); + strvec_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); res = run_command(&cp); remove_path(stash_index_path.buf); @@ -396,7 +397,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, const struct object_id *bases[1]; read_cache_preload(NULL); - if (refresh_cache(REFRESH_QUIET)) + if (refresh_and_write_cache(REFRESH_QUIET, 0, 0)) return -1; if (write_cache_as_tree(&c_tree, 0, NULL)) @@ -427,6 +428,8 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, return error(_("could not save index tree")); reset_head(); + discard_cache(); + read_cache(); } } @@ -481,13 +484,12 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, if (ret) return -1; + /* read back the result of update_index() back from the disk */ discard_cache(); + read_cache(); } - if (quiet) { - if (refresh_cache(REFRESH_QUIET)) - warning("could not refresh index"); - } else { + if (!quiet) { struct child_process cp = CHILD_PROCESS_INIT; /* @@ -497,7 +499,11 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, */ cp.git_cmd = 1; cp.dir = prefix; - argv_array_push(&cp.args, "status"); + strvec_pushf(&cp.env_array, GIT_WORK_TREE_ENVIRONMENT"=%s", + absolute_path(get_git_work_tree())); + strvec_pushf(&cp.env_array, GIT_DIR_ENVIRONMENT"=%s", + absolute_path(get_git_dir())); + strvec_push(&cp.args, "status"); run_command(&cp); } @@ -540,9 +546,9 @@ static int do_drop_stash(struct stash_info *info, int quiet) */ cp_reflog.git_cmd = 1; - argv_array_pushl(&cp_reflog.args, "reflog", "delete", "--updateref", - "--rewrite", NULL); - argv_array_push(&cp_reflog.args, info->revision.buf); + strvec_pushl(&cp_reflog.args, "reflog", "delete", "--updateref", + "--rewrite", NULL); + strvec_push(&cp_reflog.args, info->revision.buf); ret = run_command(&cp_reflog); if (!ret) { if (!quiet) @@ -560,8 +566,8 @@ static int do_drop_stash(struct stash_info *info, int quiet) cp.git_cmd = 1; /* Even though --quiet is specified, rev-parse still outputs the hash */ cp.no_stdout = 1; - argv_array_pushl(&cp.args, "rev-parse", "--verify", "--quiet", NULL); - argv_array_pushf(&cp.args, "%s@{0}", ref_stash); + strvec_pushl(&cp.args, "rev-parse", "--verify", "--quiet", NULL); + strvec_pushf(&cp.args, "%s@{0}", ref_stash); ret = run_command(&cp); /* do_clear_stash if we just dropped the last stash entry */ @@ -657,9 +663,9 @@ static int branch_stash(int argc, const char **argv, const char *prefix) return -1; cp.git_cmd = 1; - argv_array_pushl(&cp.args, "checkout", "-b", NULL); - argv_array_push(&cp.args, branch); - argv_array_push(&cp.args, oid_to_hex(&info.b_commit)); + strvec_pushl(&cp.args, "checkout", "-b", NULL); + strvec_push(&cp.args, branch); + strvec_push(&cp.args, oid_to_hex(&info.b_commit)); ret = run_command(&cp); if (!ret) ret = do_apply_stash(prefix, &info, 1, 0); @@ -686,16 +692,17 @@ static int list_stash(int argc, const char **argv, const char *prefix) return 0; cp.git_cmd = 1; - argv_array_pushl(&cp.args, "log", "--format=%gd: %gs", "-g", - "--first-parent", "-m", NULL); - argv_array_pushv(&cp.args, argv); - argv_array_push(&cp.args, ref_stash); - argv_array_push(&cp.args, "--"); + strvec_pushl(&cp.args, "log", "--format=%gd: %gs", "-g", + "--first-parent", "-m", NULL); + strvec_pushv(&cp.args, argv); + strvec_push(&cp.args, ref_stash); + strvec_push(&cp.args, "--"); return run_command(&cp); } static int show_stat = 1; static int show_patch; +static int use_legacy_stash; static int git_stash_config(const char *var, const char *value, void *cb) { @@ -707,7 +714,11 @@ static int git_stash_config(const char *var, const char *value, void *cb) show_patch = git_config_bool(var, value); return 0; } - return git_default_config(var, value, cb); + if (!strcmp(var, "stash.usebuiltin")) { + use_legacy_stash = !git_config_bool(var, value); + return 0; + } + return git_diff_basic_config(var, value, cb); } static int show_stash(int argc, const char **argv, const char *prefix) @@ -716,8 +727,8 @@ static int show_stash(int argc, const char **argv, const char *prefix) int ret = 0; struct stash_info info; struct rev_info rev; - struct argv_array stash_args = ARGV_ARRAY_INIT; - struct argv_array revision_args = ARGV_ARRAY_INIT; + struct strvec stash_args = STRVEC_INIT; + struct strvec revision_args = STRVEC_INIT; struct option options[] = { OPT_END() }; @@ -726,16 +737,16 @@ static int show_stash(int argc, const char **argv, const char *prefix) git_config(git_diff_ui_config, NULL); init_revisions(&rev, prefix); - argv_array_push(&revision_args, argv[0]); + strvec_push(&revision_args, argv[0]); for (i = 1; i < argc; i++) { if (argv[i][0] != '-') - argv_array_push(&stash_args, argv[i]); + strvec_push(&stash_args, argv[i]); else - argv_array_push(&revision_args, argv[i]); + strvec_push(&revision_args, argv[i]); } - ret = get_stash_info(&info, stash_args.argc, stash_args.argv); - argv_array_clear(&stash_args); + ret = get_stash_info(&info, stash_args.nr, stash_args.v); + strvec_clear(&stash_args); if (ret) return -1; @@ -743,8 +754,7 @@ static int show_stash(int argc, const char **argv, const char *prefix) * The config settings are applied only if there are not passed * any options. */ - if (revision_args.argc == 1) { - git_config(git_stash_config, NULL); + if (revision_args.nr == 1) { if (show_stat) rev.diffopt.output_format = DIFF_FORMAT_DIFFSTAT; @@ -757,7 +767,7 @@ static int show_stash(int argc, const char **argv, const char *prefix) } } - argc = setup_revisions(revision_args.argc, revision_args.argv, &rev, NULL); + argc = setup_revisions(revision_args.nr, revision_args.v, &rev, NULL); if (argc > 1) { free_stash_info(&info); usage_with_options(git_stash_show_usage, options); @@ -832,12 +842,12 @@ static int store_stash(int argc, const char **argv, const char *prefix) return do_store_stash(&obj, stash_msg, quiet); } -static void add_pathspecs(struct argv_array *args, +static void add_pathspecs(struct strvec *args, const struct pathspec *ps) { int i; for (i = 0; i < ps->nr; i++) - argv_array_push(args, ps->items[i].original); + strvec_push(args, ps->items[i].original); } /* @@ -851,33 +861,23 @@ static int get_untracked_files(const struct pathspec *ps, int include_untracked, struct strbuf *untracked_files) { int i; - int max_len; int found = 0; - char *seen; struct dir_struct dir; - memset(&dir, 0, sizeof(dir)); + dir_init(&dir); if (include_untracked != INCLUDE_ALL_FILES) setup_standard_excludes(&dir); - seen = xcalloc(ps->nr, 1); - - max_len = fill_directory(&dir, the_repository->index, ps); + fill_directory(&dir, the_repository->index, ps); for (i = 0; i < dir.nr; i++) { struct dir_entry *ent = dir.entries[i]; - if (dir_path_match(&the_index, ent, ps, max_len, seen)) { - found++; - strbuf_addstr(untracked_files, ent->name); - /* NUL-terminate: will be fed to update-index -z */ - strbuf_addch(untracked_files, '\0'); - } - free(ent); + found++; + strbuf_addstr(untracked_files, ent->name); + /* NUL-terminate: will be fed to update-index -z */ + strbuf_addch(untracked_files, '\0'); } - free(seen); - free(dir.entries); - free(dir.ignored); - clear_directory(&dir); + dir_clear(&dir); return found; } @@ -957,9 +957,9 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg, struct index_state istate = { NULL }; cp_upd_index.git_cmd = 1; - argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add", - "--remove", "--stdin", NULL); - argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", + strvec_pushl(&cp_upd_index.args, "update-index", "-z", "--add", + "--remove", "--stdin", NULL); + strvec_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", stash_index_path.buf); strbuf_addf(&untracked_msg, "untracked files on %s\n", msg->buf); @@ -993,32 +993,35 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, { int ret = 0; struct child_process cp_read_tree = CHILD_PROCESS_INIT; - struct child_process cp_add_i = CHILD_PROCESS_INIT; struct child_process cp_diff_tree = CHILD_PROCESS_INIT; struct index_state istate = { NULL }; + char *old_index_env = NULL, *old_repo_index_file; remove_path(stash_index_path.buf); cp_read_tree.git_cmd = 1; - argv_array_pushl(&cp_read_tree.args, "read-tree", "HEAD", NULL); - argv_array_pushf(&cp_read_tree.env_array, "GIT_INDEX_FILE=%s", - stash_index_path.buf); + strvec_pushl(&cp_read_tree.args, "read-tree", "HEAD", NULL); + strvec_pushf(&cp_read_tree.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); if (run_command(&cp_read_tree)) { ret = -1; goto done; } /* Find out what the user wants. */ - cp_add_i.git_cmd = 1; - argv_array_pushl(&cp_add_i.args, "add--interactive", "--patch=stash", - "--", NULL); - add_pathspecs(&cp_add_i.args, ps); - argv_array_pushf(&cp_add_i.env_array, "GIT_INDEX_FILE=%s", - stash_index_path.buf); - if (run_command(&cp_add_i)) { - ret = -1; - goto done; - } + old_repo_index_file = the_repository->index_file; + the_repository->index_file = stash_index_path.buf; + old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT)); + setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1); + + ret = run_add_interactive(NULL, "--patch=stash", ps); + + the_repository->index_file = old_repo_index_file; + if (old_index_env && *old_index_env) + setenv(INDEX_ENVIRONMENT, old_index_env, 1); + else + unsetenv(INDEX_ENVIRONMENT); + FREE_AND_NULL(old_index_env); /* State of the working tree. */ if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0, @@ -1028,8 +1031,8 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, } cp_diff_tree.git_cmd = 1; - argv_array_pushl(&cp_diff_tree.args, "diff-tree", "-p", "HEAD", - oid_to_hex(&info->w_tree), "--", NULL); + strvec_pushl(&cp_diff_tree.args, "diff-tree", "-p", "-U1", "HEAD", + oid_to_hex(&info->w_tree), "--", NULL); if (pipe_command(&cp_diff_tree, NULL, 0, out_patch, 0, NULL, 0)) { ret = -1; goto done; @@ -1082,10 +1085,11 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps } cp_upd_index.git_cmd = 1; - argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add", - "--remove", "--stdin", NULL); - argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", - stash_index_path.buf); + strvec_pushl(&cp_upd_index.args, "update-index", + "--ignore-skip-worktree-entries", + "-z", "--add", "--remove", "--stdin", NULL); + strvec_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", + stash_index_path.buf); if (pipe_command(&cp_upd_index, diff_output.buf, diff_output.len, NULL, 0, NULL, 0)) { @@ -1129,7 +1133,10 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b prepare_fallback_ident("git stash", "git@stash"); read_cache_preload(NULL); - refresh_cache(REFRESH_QUIET); + if (refresh_and_write_cache(REFRESH_QUIET, 0, 0) < 0) { + ret = -1; + goto done; + } if (get_oid("HEAD", &info->b_commit)) { if (!quiet) @@ -1290,7 +1297,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q free(ps_matched); } - if (refresh_cache(REFRESH_QUIET)) { + if (refresh_and_write_cache(REFRESH_QUIET, 0, 0)) { ret = -1; goto done; } @@ -1332,10 +1339,10 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; - argv_array_pushl(&cp.args, "clean", "--force", - "--quiet", "-d", NULL); + strvec_pushl(&cp.args, "clean", "--force", + "--quiet", "-d", NULL); if (include_untracked == INCLUDE_ALL_FILES) - argv_array_push(&cp.args, "-x"); + strvec_push(&cp.args, "-x"); if (run_command(&cp)) { ret = -1; goto done; @@ -1349,12 +1356,12 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q struct strbuf out = STRBUF_INIT; cp_add.git_cmd = 1; - argv_array_push(&cp_add.args, "add"); + strvec_push(&cp_add.args, "add"); if (!include_untracked) - argv_array_push(&cp_add.args, "-u"); + strvec_push(&cp_add.args, "-u"); if (include_untracked == INCLUDE_ALL_FILES) - argv_array_push(&cp_add.args, "--force"); - argv_array_push(&cp_add.args, "--"); + strvec_push(&cp_add.args, "--force"); + strvec_push(&cp_add.args, "--"); add_pathspecs(&cp_add.args, ps); if (run_command(&cp_add)) { ret = -1; @@ -1362,9 +1369,9 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q } cp_diff.git_cmd = 1; - argv_array_pushl(&cp_diff.args, "diff-index", "-p", - "--cached", "--binary", "HEAD", "--", - NULL); + strvec_pushl(&cp_diff.args, "diff-index", "-p", + "--cached", "--binary", "HEAD", "--", + NULL); add_pathspecs(&cp_diff.args, ps); if (pipe_command(&cp_diff, NULL, 0, &out, 0, NULL, 0)) { ret = -1; @@ -1372,8 +1379,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q } cp_apply.git_cmd = 1; - argv_array_pushl(&cp_apply.args, "apply", "--index", - "-R", NULL); + strvec_pushl(&cp_apply.args, "apply", "--index", + "-R", NULL); if (pipe_command(&cp_apply, out.buf, out.len, NULL, 0, NULL, 0)) { ret = -1; @@ -1382,8 +1389,8 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q } else { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; - argv_array_pushl(&cp.args, "reset", "--hard", "-q", - NULL); + strvec_pushl(&cp.args, "reset", "--hard", "-q", + "--no-recurse-submodules", NULL); if (run_command(&cp)) { ret = -1; goto done; @@ -1394,10 +1401,10 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; - argv_array_pushl(&cp.args, "checkout", "--no-overlay", - oid_to_hex(&info.i_tree), "--", NULL); + strvec_pushl(&cp.args, "checkout", "--no-overlay", + oid_to_hex(&info.i_tree), "--", NULL); if (!ps->nr) - argv_array_push(&cp.args, ":/"); + strvec_push(&cp.args, ":/"); else add_pathspecs(&cp.args, ps); if (run_command(&cp)) { @@ -1410,7 +1417,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; - argv_array_pushl(&cp.args, "apply", "-R", NULL); + strvec_pushl(&cp.args, "apply", "-R", NULL); if (pipe_command(&cp, patch.buf, patch.len, NULL, 0, NULL, 0)) { if (!quiet) @@ -1424,7 +1431,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; - argv_array_pushl(&cp.args, "reset", "-q", "--", NULL); + strvec_pushl(&cp.args, "reset", "-q", "--", NULL); add_pathspecs(&cp.args, ps); if (run_command(&cp)) { ret = -1; @@ -1439,13 +1446,17 @@ done: return ret; } -static int push_stash(int argc, const char **argv, const char *prefix) +static int push_stash(int argc, const char **argv, const char *prefix, + int push_assumed) { + int force_assume = 0; int keep_index = -1; int patch_mode = 0; int include_untracked = 0; int quiet = 0; + int pathspec_file_nul = 0; const char *stash_msg = NULL; + const char *pathspec_from_file = NULL; struct pathspec ps; struct option options[] = { OPT_BOOL('k', "keep-index", &keep_index, @@ -1459,16 +1470,45 @@ static int push_stash(int argc, const char **argv, const char *prefix) N_("include ignore files"), 2), OPT_STRING('m', "message", &stash_msg, N_("message"), N_("stash message")), + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END() }; - if (argc) + if (argc) { + force_assume = !strcmp(argv[0], "-p"); argc = parse_options(argc, argv, prefix, options, git_stash_push_usage, - 0); + PARSE_OPT_KEEP_DASHDASH); + } + + if (argc) { + if (!strcmp(argv[0], "--")) { + argc--; + argv++; + } else if (push_assumed && !force_assume) { + die("subcommand wasn't specified; 'push' can't be assumed due to unexpected token '%s'", + argv[0]); + } + } parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN, prefix, argv); + + if (pathspec_from_file) { + if (patch_mode) + die(_("--pathspec-from-file is incompatible with --patch")); + + if (ps.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&ps, 0, + PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode, include_untracked); } @@ -1513,55 +1553,22 @@ static int save_stash(int argc, const char **argv, const char *prefix) return ret; } -static int use_builtin_stash(void) -{ - struct child_process cp = CHILD_PROCESS_INIT; - struct strbuf out = STRBUF_INIT; - int ret, env = git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1); - - if (env != -1) - return env; - - argv_array_pushl(&cp.args, - "config", "--bool", "stash.usebuiltin", NULL); - cp.git_cmd = 1; - if (capture_command(&cp, &out, 6)) { - strbuf_release(&out); - return 1; - } - - strbuf_trim(&out); - ret = !strcmp("true", out.buf); - strbuf_release(&out); - return ret; -} - int cmd_stash(int argc, const char **argv, const char *prefix) { - int i = -1; pid_t pid = getpid(); const char *index_file; - struct argv_array args = ARGV_ARRAY_INIT; + struct strvec args = STRVEC_INIT; struct option options[] = { OPT_END() }; - if (!use_builtin_stash()) { - const char *path = mkpath("%s/git-legacy-stash", - git_exec_path()); - - if (sane_execvp(path, (char **)argv) < 0) - die_errno(_("could not exec %s"), path); - else - BUG("sane_execvp() returned???"); - } - - prefix = setup_git_directory(); - trace_repo_setup(prefix); - setup_work_tree(); + git_config(git_stash_config, NULL); - git_config(git_diff_basic_config, NULL); + if (use_legacy_stash || + !git_env_bool("GIT_TEST_STASH_USE_BUILTIN", -1)) + warning(_("the stash.useBuiltin support has been removed!\n" + "See its entry in 'git help config' for details.")); argc = parse_options(argc, argv, prefix, options, git_stash_usage, PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); @@ -1571,7 +1578,7 @@ int cmd_stash(int argc, const char **argv, const char *prefix) (uintmax_t)pid); if (!argc) - return !!push_stash(0, NULL, prefix); + return !!push_stash(0, NULL, prefix, 0); else if (!strcmp(argv[0], "apply")) return !!apply_stash(argc, argv, prefix); else if (!strcmp(argv[0], "clear")) @@ -1591,45 +1598,15 @@ int cmd_stash(int argc, const char **argv, const char *prefix) else if (!strcmp(argv[0], "create")) return !!create_stash(argc, argv, prefix); else if (!strcmp(argv[0], "push")) - return !!push_stash(argc, argv, prefix); + return !!push_stash(argc, argv, prefix, 0); else if (!strcmp(argv[0], "save")) return !!save_stash(argc, argv, prefix); else if (*argv[0] != '-') usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), git_stash_usage, options); - if (strcmp(argv[0], "-p")) { - while (++i < argc && strcmp(argv[i], "--")) { - /* - * `akpqu` is a string which contains all short options, - * except `-m` which is verified separately. - */ - if ((strlen(argv[i]) == 2) && *argv[i] == '-' && - strchr("akpqu", argv[i][1])) - continue; - - if (!strcmp(argv[i], "--all") || - !strcmp(argv[i], "--keep-index") || - !strcmp(argv[i], "--no-keep-index") || - !strcmp(argv[i], "--patch") || - !strcmp(argv[i], "--quiet") || - !strcmp(argv[i], "--include-untracked")) - continue; - - /* - * `-m` and `--message=` are verified separately because - * they need to be immediately followed by a string - * (i.e.`-m"foobar"` or `--message="foobar"`). - */ - if (starts_with(argv[i], "-m") || - starts_with(argv[i], "--message=")) - continue; - - usage_with_options(git_stash_usage, options); - } - } - - argv_array_push(&args, "push"); - argv_array_pushv(&args, argv); - return !!push_stash(args.argc, args.argv, prefix); + /* Assume 'stash push' */ + strvec_push(&args, "push"); + strvec_pushv(&args, argv); + return !!push_stash(args.nr, args.v, prefix, 1); } diff --git a/third_party/git/builtin/submodule--helper.c b/third_party/git/builtin/submodule--helper.c index 909e77e802d3..c30896c89788 100644 --- a/third_party/git/builtin/submodule--helper.c +++ b/third_party/git/builtin/submodule--helper.c @@ -19,6 +19,8 @@ #include "diffcore.h" #include "diff.h" #include "object-store.h" +#include "dir.h" +#include "advice.h" #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -292,9 +294,9 @@ static char *compute_rev_name(const char *sub_path, const char* object_id) cp.git_cmd = 1; cp.no_stderr = 1; - argv_array_push(&cp.args, "describe"); - argv_array_pushv(&cp.args, *d); - argv_array_push(&cp.args, object_id); + strvec_push(&cp.args, "describe"); + strvec_pushv(&cp.args, *d); + strvec_push(&cp.args, object_id); if (!capture_command(&cp, &sb, 0)) { strbuf_strip_suffix(&sb, "\n"); @@ -424,7 +426,7 @@ static int module_list(int argc, const char **argv, const char *prefix) const struct cache_entry *ce = list.entries[i]; if (ce_stage(ce)) - printf("%06o %s U\t", ce->ce_mode, sha1_to_hex(null_sha1)); + printf("%06o %s U\t", ce->ce_mode, oid_to_hex(&null_oid)); else printf("%06o %s %d\t", ce->ce_mode, oid_to_hex(&ce->oid), ce_stage(ce)); @@ -442,19 +444,19 @@ static void for_each_listed_submodule(const struct module_list *list, fn(list->entries[i], cb_data); } -struct cb_foreach { +struct foreach_cb { int argc; const char **argv; const char *prefix; int quiet; int recursive; }; -#define CB_FOREACH_INIT { 0 } +#define FOREACH_CB_INIT { 0 } static void runcommand_in_submodule_cb(const struct cache_entry *list_item, void *cb_data) { - struct cb_foreach *info = cb_data; + struct foreach_cb *info = cb_data; const char *path = list_item->name; const struct object_id *ce_oid = &list_item->oid; @@ -493,12 +495,12 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item, char *toplevel = xgetcwd(); struct strbuf sb = STRBUF_INIT; - argv_array_pushf(&cp.env_array, "name=%s", sub->name); - argv_array_pushf(&cp.env_array, "sm_path=%s", path); - argv_array_pushf(&cp.env_array, "displaypath=%s", displaypath); - argv_array_pushf(&cp.env_array, "sha1=%s", - oid_to_hex(ce_oid)); - argv_array_pushf(&cp.env_array, "toplevel=%s", toplevel); + strvec_pushf(&cp.env_array, "name=%s", sub->name); + strvec_pushf(&cp.env_array, "sm_path=%s", path); + strvec_pushf(&cp.env_array, "displaypath=%s", displaypath); + strvec_pushf(&cp.env_array, "sha1=%s", + oid_to_hex(ce_oid)); + strvec_pushf(&cp.env_array, "toplevel=%s", toplevel); /* * Since the path variable was accessible from the script @@ -507,15 +509,15 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item, * on windows. And since environment variables are * case-insensitive in windows, it interferes with the * existing PATH variable. Hence, to avoid that, we expose - * path via the args argv_array and not via env_array. + * path via the args strvec and not via env_array. */ sq_quote_buf(&sb, path); - argv_array_pushf(&cp.args, "path=%s; %s", - sb.buf, info->argv[0]); + strvec_pushf(&cp.args, "path=%s; %s", + sb.buf, info->argv[0]); strbuf_release(&sb); free(toplevel); } else { - argv_array_pushv(&cp.args, info->argv); + strvec_pushv(&cp.args, info->argv); } if (!info->quiet) @@ -532,16 +534,16 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item, cpr.dir = path; prepare_submodule_repo_env(&cpr.env_array); - argv_array_pushl(&cpr.args, "--super-prefix", NULL); - argv_array_pushf(&cpr.args, "%s/", displaypath); - argv_array_pushl(&cpr.args, "submodule--helper", "foreach", "--recursive", - NULL); + strvec_pushl(&cpr.args, "--super-prefix", NULL); + strvec_pushf(&cpr.args, "%s/", displaypath); + strvec_pushl(&cpr.args, "submodule--helper", "foreach", "--recursive", + NULL); if (info->quiet) - argv_array_push(&cpr.args, "--quiet"); + strvec_push(&cpr.args, "--quiet"); - argv_array_push(&cpr.args, "--"); - argv_array_pushv(&cpr.args, info->argv); + strvec_push(&cpr.args, "--"); + strvec_pushv(&cpr.args, info->argv); if (run_command(&cpr)) die(_("run_command returned non-zero status while " @@ -555,7 +557,7 @@ cleanup: static int module_foreach(int argc, const char **argv, const char *prefix) { - struct cb_foreach info = CB_FOREACH_INIT; + struct foreach_cb info = FOREACH_CB_INIT; struct pathspec pathspec; struct module_list list = MODULE_LIST_INIT; @@ -610,7 +612,6 @@ struct init_cb { const char *prefix; unsigned int flags; }; - #define INIT_CB_INIT { NULL, 0 } static void init_submodule(const char *path, const char *prefix, @@ -740,7 +741,6 @@ struct status_cb { const char *prefix; unsigned int flags; }; - #define STATUS_CB_INIT { NULL, 0 } static void print_status(unsigned int flags, char state, const char *path, @@ -777,9 +777,11 @@ static void status_submodule(const char *path, const struct object_id *ce_oid, unsigned int flags) { char *displaypath; - struct argv_array diff_files_args = ARGV_ARRAY_INIT; + struct strvec diff_files_args = STRVEC_INIT; struct rev_info rev; int diff_files_result; + struct strbuf buf = STRBUF_INIT; + const char *git_dir; if (!submodule_from_path(the_repository, &null_oid, path)) die(_("no submodule mapping found in .gitmodules for path '%s'"), @@ -792,21 +794,30 @@ static void status_submodule(const char *path, const struct object_id *ce_oid, goto cleanup; } - if (!is_submodule_active(the_repository, path)) { + strbuf_addf(&buf, "%s/.git", path); + git_dir = read_gitfile(buf.buf); + if (!git_dir) + git_dir = buf.buf; + + if (!is_submodule_active(the_repository, path) || + !is_git_directory(git_dir)) { print_status(flags, '-', path, ce_oid, displaypath); + strbuf_release(&buf); goto cleanup; } + strbuf_release(&buf); - argv_array_pushl(&diff_files_args, "diff-files", - "--ignore-submodules=dirty", "--quiet", "--", - path, NULL); + strvec_pushl(&diff_files_args, "diff-files", + "--ignore-submodules=dirty", "--quiet", "--", + path, NULL); git_config(git_diff_basic_config, NULL); - repo_init_revisions(the_repository, &rev, prefix); + + repo_init_revisions(the_repository, &rev, NULL); rev.abbrev = 0; - diff_files_args.argc = setup_revisions(diff_files_args.argc, - diff_files_args.argv, - &rev, NULL); + diff_files_args.nr = setup_revisions(diff_files_args.nr, + diff_files_args.v, + &rev, NULL); diff_files_result = run_diff_files(&rev, 0); if (!diff_result_code(&rev.diffopt, diff_files_result)) { @@ -836,23 +847,23 @@ static void status_submodule(const char *path, const struct object_id *ce_oid, cpr.dir = path; prepare_submodule_repo_env(&cpr.env_array); - argv_array_push(&cpr.args, "--super-prefix"); - argv_array_pushf(&cpr.args, "%s/", displaypath); - argv_array_pushl(&cpr.args, "submodule--helper", "status", - "--recursive", NULL); + strvec_push(&cpr.args, "--super-prefix"); + strvec_pushf(&cpr.args, "%s/", displaypath); + strvec_pushl(&cpr.args, "submodule--helper", "status", + "--recursive", NULL); if (flags & OPT_CACHED) - argv_array_push(&cpr.args, "--cached"); + strvec_push(&cpr.args, "--cached"); if (flags & OPT_QUIET) - argv_array_push(&cpr.args, "--quiet"); + strvec_push(&cpr.args, "--quiet"); if (run_command(&cpr)) die(_("failed to recurse into submodule '%s'"), path); } cleanup: - argv_array_clear(&diff_files_args); + strvec_clear(&diff_files_args); free(displaypath); } @@ -916,11 +927,437 @@ static int module_name(int argc, const char **argv, const char *prefix) return 0; } +struct module_cb { + unsigned int mod_src; + unsigned int mod_dst; + struct object_id oid_src; + struct object_id oid_dst; + char status; + const char *sm_path; +}; +#define MODULE_CB_INIT { 0, 0, NULL, NULL, '\0', NULL } + +struct module_cb_list { + struct module_cb **entries; + int alloc, nr; +}; +#define MODULE_CB_LIST_INIT { NULL, 0, 0 } + +struct summary_cb { + int argc; + const char **argv; + const char *prefix; + unsigned int cached: 1; + unsigned int for_status: 1; + unsigned int files: 1; + int summary_limit; +}; +#define SUMMARY_CB_INIT { 0, NULL, NULL, 0, 0, 0, 0 } + +enum diff_cmd { + DIFF_INDEX, + DIFF_FILES +}; + +static char *verify_submodule_committish(const char *sm_path, + const char *committish) +{ + struct child_process cp_rev_parse = CHILD_PROCESS_INIT; + struct strbuf result = STRBUF_INIT; + + cp_rev_parse.git_cmd = 1; + cp_rev_parse.dir = sm_path; + prepare_submodule_repo_env(&cp_rev_parse.env_array); + strvec_pushl(&cp_rev_parse.args, "rev-parse", "-q", "--short", NULL); + strvec_pushf(&cp_rev_parse.args, "%s^0", committish); + strvec_push(&cp_rev_parse.args, "--"); + + if (capture_command(&cp_rev_parse, &result, 0)) + return NULL; + + strbuf_trim_trailing_newline(&result); + return strbuf_detach(&result, NULL); +} + +static void print_submodule_summary(struct summary_cb *info, char *errmsg, + int total_commits, const char *displaypath, + const char *src_abbrev, const char *dst_abbrev, + struct module_cb *p) +{ + if (p->status == 'T') { + if (S_ISGITLINK(p->mod_dst)) + printf(_("* %s %s(blob)->%s(submodule)"), + displaypath, src_abbrev, dst_abbrev); + else + printf(_("* %s %s(submodule)->%s(blob)"), + displaypath, src_abbrev, dst_abbrev); + } else { + printf("* %s %s...%s", + displaypath, src_abbrev, dst_abbrev); + } + + if (total_commits < 0) + printf(":\n"); + else + printf(" (%d):\n", total_commits); + + if (errmsg) { + printf(_("%s"), errmsg); + } else if (total_commits > 0) { + struct child_process cp_log = CHILD_PROCESS_INIT; + + cp_log.git_cmd = 1; + cp_log.dir = p->sm_path; + prepare_submodule_repo_env(&cp_log.env_array); + strvec_pushl(&cp_log.args, "log", NULL); + + if (S_ISGITLINK(p->mod_src) && S_ISGITLINK(p->mod_dst)) { + if (info->summary_limit > 0) + strvec_pushf(&cp_log.args, "-%d", + info->summary_limit); + + strvec_pushl(&cp_log.args, "--pretty= %m %s", + "--first-parent", NULL); + strvec_pushf(&cp_log.args, "%s...%s", + src_abbrev, dst_abbrev); + } else if (S_ISGITLINK(p->mod_dst)) { + strvec_pushl(&cp_log.args, "--pretty= > %s", + "-1", dst_abbrev, NULL); + } else { + strvec_pushl(&cp_log.args, "--pretty= < %s", + "-1", src_abbrev, NULL); + } + run_command(&cp_log); + } + printf("\n"); +} + +static void generate_submodule_summary(struct summary_cb *info, + struct module_cb *p) +{ + char *displaypath, *src_abbrev = NULL, *dst_abbrev; + int missing_src = 0, missing_dst = 0; + char *errmsg = NULL; + int total_commits = -1; + + if (!info->cached && oideq(&p->oid_dst, &null_oid)) { + if (S_ISGITLINK(p->mod_dst)) { + struct ref_store *refs = get_submodule_ref_store(p->sm_path); + if (refs) + refs_head_ref(refs, handle_submodule_head_ref, &p->oid_dst); + } else if (S_ISLNK(p->mod_dst) || S_ISREG(p->mod_dst)) { + struct stat st; + int fd = open(p->sm_path, O_RDONLY); + + if (fd < 0 || fstat(fd, &st) < 0 || + index_fd(&the_index, &p->oid_dst, fd, &st, OBJ_BLOB, + p->sm_path, 0)) + error(_("couldn't hash object from '%s'"), p->sm_path); + } else { + /* for a submodule removal (mode:0000000), don't warn */ + if (p->mod_dst) + warning(_("unexpected mode %o\n"), p->mod_dst); + } + } + + if (S_ISGITLINK(p->mod_src)) { + if (p->status != 'D') + src_abbrev = verify_submodule_committish(p->sm_path, + oid_to_hex(&p->oid_src)); + if (!src_abbrev) { + missing_src = 1; + /* + * As `rev-parse` failed, we fallback to getting + * the abbreviated hash using oid_src. We do + * this as we might still need the abbreviated + * hash in cases like a submodule type change, etc. + */ + src_abbrev = xstrndup(oid_to_hex(&p->oid_src), 7); + } + } else { + /* + * The source does not point to a submodule. + * So, we fallback to getting the abbreviation using + * oid_src as we might still need the abbreviated + * hash in cases like submodule add, etc. + */ + src_abbrev = xstrndup(oid_to_hex(&p->oid_src), 7); + } + + if (S_ISGITLINK(p->mod_dst)) { + dst_abbrev = verify_submodule_committish(p->sm_path, + oid_to_hex(&p->oid_dst)); + if (!dst_abbrev) { + missing_dst = 1; + /* + * As `rev-parse` failed, we fallback to getting + * the abbreviated hash using oid_dst. We do + * this as we might still need the abbreviated + * hash in cases like a submodule type change, etc. + */ + dst_abbrev = xstrndup(oid_to_hex(&p->oid_dst), 7); + } + } else { + /* + * The destination does not point to a submodule. + * So, we fallback to getting the abbreviation using + * oid_dst as we might still need the abbreviated + * hash in cases like a submodule removal, etc. + */ + dst_abbrev = xstrndup(oid_to_hex(&p->oid_dst), 7); + } + + displaypath = get_submodule_displaypath(p->sm_path, info->prefix); + + if (!missing_src && !missing_dst) { + struct child_process cp_rev_list = CHILD_PROCESS_INIT; + struct strbuf sb_rev_list = STRBUF_INIT; + + strvec_pushl(&cp_rev_list.args, "rev-list", + "--first-parent", "--count", NULL); + if (S_ISGITLINK(p->mod_src) && S_ISGITLINK(p->mod_dst)) + strvec_pushf(&cp_rev_list.args, "%s...%s", + src_abbrev, dst_abbrev); + else + strvec_push(&cp_rev_list.args, S_ISGITLINK(p->mod_src) ? + src_abbrev : dst_abbrev); + strvec_push(&cp_rev_list.args, "--"); + + cp_rev_list.git_cmd = 1; + cp_rev_list.dir = p->sm_path; + prepare_submodule_repo_env(&cp_rev_list.env_array); + + if (!capture_command(&cp_rev_list, &sb_rev_list, 0)) + total_commits = atoi(sb_rev_list.buf); + + strbuf_release(&sb_rev_list); + } else { + /* + * Don't give error msg for modification whose dst is not + * submodule, i.e., deleted or changed to blob + */ + if (S_ISGITLINK(p->mod_dst)) { + struct strbuf errmsg_str = STRBUF_INIT; + if (missing_src && missing_dst) { + strbuf_addf(&errmsg_str, " Warn: %s doesn't contain commits %s and %s\n", + displaypath, oid_to_hex(&p->oid_src), + oid_to_hex(&p->oid_dst)); + } else { + strbuf_addf(&errmsg_str, " Warn: %s doesn't contain commit %s\n", + displaypath, missing_src ? + oid_to_hex(&p->oid_src) : + oid_to_hex(&p->oid_dst)); + } + errmsg = strbuf_detach(&errmsg_str, NULL); + } + } + + print_submodule_summary(info, errmsg, total_commits, + displaypath, src_abbrev, + dst_abbrev, p); + + free(displaypath); + free(src_abbrev); + free(dst_abbrev); +} + +static void prepare_submodule_summary(struct summary_cb *info, + struct module_cb_list *list) +{ + int i; + for (i = 0; i < list->nr; i++) { + const struct submodule *sub; + struct module_cb *p = list->entries[i]; + struct strbuf sm_gitdir = STRBUF_INIT; + + if (p->status == 'D' || p->status == 'T') { + generate_submodule_summary(info, p); + continue; + } + + if (info->for_status && p->status != 'A' && + (sub = submodule_from_path(the_repository, + &null_oid, p->sm_path))) { + char *config_key = NULL; + const char *value; + int ignore_all = 0; + + config_key = xstrfmt("submodule.%s.ignore", + sub->name); + if (!git_config_get_string_tmp(config_key, &value)) + ignore_all = !strcmp(value, "all"); + else if (sub->ignore) + ignore_all = !strcmp(sub->ignore, "all"); + + free(config_key); + if (ignore_all) + continue; + } + + /* Also show added or modified modules which are checked out */ + strbuf_addstr(&sm_gitdir, p->sm_path); + if (is_nonbare_repository_dir(&sm_gitdir)) + generate_submodule_summary(info, p); + strbuf_release(&sm_gitdir); + } +} + +static void submodule_summary_callback(struct diff_queue_struct *q, + struct diff_options *options, + void *data) +{ + int i; + struct module_cb_list *list = data; + for (i = 0; i < q->nr; i++) { + struct diff_filepair *p = q->queue[i]; + struct module_cb *temp; + + if (!S_ISGITLINK(p->one->mode) && !S_ISGITLINK(p->two->mode)) + continue; + temp = (struct module_cb*)malloc(sizeof(struct module_cb)); + temp->mod_src = p->one->mode; + temp->mod_dst = p->two->mode; + temp->oid_src = p->one->oid; + temp->oid_dst = p->two->oid; + temp->status = p->status; + temp->sm_path = xstrdup(p->one->path); + + ALLOC_GROW(list->entries, list->nr + 1, list->alloc); + list->entries[list->nr++] = temp; + } +} + +static const char *get_diff_cmd(enum diff_cmd diff_cmd) +{ + switch (diff_cmd) { + case DIFF_INDEX: return "diff-index"; + case DIFF_FILES: return "diff-files"; + default: BUG("bad diff_cmd value %d", diff_cmd); + } +} + +static int compute_summary_module_list(struct object_id *head_oid, + struct summary_cb *info, + enum diff_cmd diff_cmd) +{ + struct strvec diff_args = STRVEC_INIT; + struct rev_info rev; + struct module_cb_list list = MODULE_CB_LIST_INIT; + + strvec_push(&diff_args, get_diff_cmd(diff_cmd)); + if (info->cached) + strvec_push(&diff_args, "--cached"); + strvec_pushl(&diff_args, "--ignore-submodules=dirty", "--raw", NULL); + if (head_oid) + strvec_push(&diff_args, oid_to_hex(head_oid)); + strvec_push(&diff_args, "--"); + if (info->argc) + strvec_pushv(&diff_args, info->argv); + + git_config(git_diff_basic_config, NULL); + init_revisions(&rev, info->prefix); + rev.abbrev = 0; + precompose_argv(diff_args.nr, diff_args.v); + setup_revisions(diff_args.nr, diff_args.v, &rev, NULL); + rev.diffopt.output_format = DIFF_FORMAT_NO_OUTPUT | DIFF_FORMAT_CALLBACK; + rev.diffopt.format_callback = submodule_summary_callback; + rev.diffopt.format_callback_data = &list; + + if (!info->cached) { + if (diff_cmd == DIFF_INDEX) + setup_work_tree(); + if (read_cache_preload(&rev.diffopt.pathspec) < 0) { + perror("read_cache_preload"); + return -1; + } + } else if (read_cache() < 0) { + perror("read_cache"); + return -1; + } + + if (diff_cmd == DIFF_INDEX) + run_diff_index(&rev, info->cached); + else + run_diff_files(&rev, 0); + prepare_submodule_summary(info, &list); + strvec_clear(&diff_args); + return 0; +} + +static int module_summary(int argc, const char **argv, const char *prefix) +{ + struct summary_cb info = SUMMARY_CB_INIT; + int cached = 0; + int for_status = 0; + int files = 0; + int summary_limit = -1; + enum diff_cmd diff_cmd = DIFF_INDEX; + struct object_id head_oid; + int ret; + + struct option module_summary_options[] = { + OPT_BOOL(0, "cached", &cached, + N_("use the commit stored in the index instead of the submodule HEAD")), + OPT_BOOL(0, "files", &files, + N_("to compare the commit in the index with that in the submodule HEAD")), + OPT_BOOL(0, "for-status", &for_status, + N_("skip submodules with 'ignore_config' value set to 'all'")), + OPT_INTEGER('n', "summary-limit", &summary_limit, + N_("limit the summary size")), + OPT_END() + }; + + const char *const git_submodule_helper_usage[] = { + N_("git submodule--helper summary [<options>] [<commit>] [--] [<path>]"), + NULL + }; + + argc = parse_options(argc, argv, prefix, module_summary_options, + git_submodule_helper_usage, 0); + + if (!summary_limit) + return 0; + + if (!get_oid(argc ? argv[0] : "HEAD", &head_oid)) { + if (argc) { + argv++; + argc--; + } + } else if (!argc || !strcmp(argv[0], "HEAD")) { + /* before the first commit: compare with an empty tree */ + oidcpy(&head_oid, the_hash_algo->empty_tree); + if (argc) { + argv++; + argc--; + } + } else { + if (get_oid("HEAD", &head_oid)) + die(_("could not fetch a revision for HEAD")); + } + + if (files) { + if (cached) + die(_("--cached and --files are mutually exclusive")); + diff_cmd = DIFF_FILES; + } + + info.argc = argc; + info.argv = argv; + info.prefix = prefix; + info.cached = !!cached; + info.files = !!files; + info.for_status = !!for_status; + info.summary_limit = summary_limit; + + ret = compute_summary_module_list((diff_cmd == DIFF_INDEX) ? &head_oid : NULL, + &info, diff_cmd); + return ret; +} + struct sync_cb { const char *prefix; unsigned int flags; }; - #define SYNC_CB_INIT { NULL, 0 } static void sync_submodule(const char *path, const char *prefix, @@ -982,8 +1419,8 @@ static void sync_submodule(const char *path, const char *prefix, prepare_submodule_repo_env(&cp.env_array); cp.git_cmd = 1; cp.dir = path; - argv_array_pushl(&cp.args, "submodule--helper", - "print-default-remote", NULL); + strvec_pushl(&cp.args, "submodule--helper", + "print-default-remote", NULL); strbuf_reset(&sb); if (capture_command(&cp, &sb, 0)) @@ -1008,13 +1445,13 @@ static void sync_submodule(const char *path, const char *prefix, cpr.dir = path; prepare_submodule_repo_env(&cpr.env_array); - argv_array_push(&cpr.args, "--super-prefix"); - argv_array_pushf(&cpr.args, "%s/", displaypath); - argv_array_pushl(&cpr.args, "submodule--helper", "sync", - "--recursive", NULL); + strvec_push(&cpr.args, "--super-prefix"); + strvec_pushf(&cpr.args, "%s/", displaypath); + strvec_pushl(&cpr.args, "submodule--helper", "sync", + "--recursive", NULL); if (flags & OPT_QUIET) - argv_array_push(&cpr.args, "--quiet"); + strvec_push(&cpr.args, "--quiet"); if (run_command(&cpr)) die(_("failed to recurse into submodule '%s'"), @@ -1114,8 +1551,8 @@ static void deinit_submodule(const char *path, const char *prefix, if (!(flags & OPT_FORCE)) { struct child_process cp_rm = CHILD_PROCESS_INIT; cp_rm.git_cmd = 1; - argv_array_pushl(&cp_rm.args, "rm", "-qn", - path, NULL); + strvec_pushl(&cp_rm.args, "rm", "-qn", + path, NULL); if (run_command(&cp_rm)) die(_("Submodule work tree '%s' contains local " @@ -1143,8 +1580,8 @@ static void deinit_submodule(const char *path, const char *prefix, displaypath); cp_config.git_cmd = 1; - argv_array_pushl(&cp_config.args, "config", "--get-regexp", NULL); - argv_array_pushf(&cp_config.args, "submodule.%s\\.", sub->name); + strvec_pushl(&cp_config.args, "config", "--get-regexp", NULL); + strvec_pushf(&cp_config.args, "submodule.%s\\.", sub->name); /* remove the .git/config entries (unless the user already did it) */ if (!capture_command(&cp_config, &sb_config, 0) && sb_config.len) { @@ -1222,32 +1659,36 @@ static int module_deinit(int argc, const char **argv, const char *prefix) static int clone_submodule(const char *path, const char *gitdir, const char *url, const char *depth, struct string_list *reference, int dissociate, - int quiet, int progress) + int quiet, int progress, int single_branch) { struct child_process cp = CHILD_PROCESS_INIT; - argv_array_push(&cp.args, "clone"); - argv_array_push(&cp.args, "--no-checkout"); + strvec_push(&cp.args, "clone"); + strvec_push(&cp.args, "--no-checkout"); if (quiet) - argv_array_push(&cp.args, "--quiet"); + strvec_push(&cp.args, "--quiet"); if (progress) - argv_array_push(&cp.args, "--progress"); + strvec_push(&cp.args, "--progress"); if (depth && *depth) - argv_array_pushl(&cp.args, "--depth", depth, NULL); + strvec_pushl(&cp.args, "--depth", depth, NULL); if (reference->nr) { struct string_list_item *item; for_each_string_list_item(item, reference) - argv_array_pushl(&cp.args, "--reference", - item->string, NULL); + strvec_pushl(&cp.args, "--reference", + item->string, NULL); } if (dissociate) - argv_array_push(&cp.args, "--dissociate"); + strvec_push(&cp.args, "--dissociate"); if (gitdir && *gitdir) - argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL); + strvec_pushl(&cp.args, "--separate-git-dir", gitdir, NULL); + if (single_branch >= 0) + strvec_push(&cp.args, single_branch ? + "--single-branch" : + "--no-single-branch"); - argv_array_push(&cp.args, "--"); - argv_array_push(&cp.args, url); - argv_array_push(&cp.args, path); + strvec_push(&cp.args, "--"); + strvec_push(&cp.args, url); + strvec_push(&cp.args, path); cp.git_cmd = 1; prepare_submodule_repo_env(&cp.env_array); @@ -1268,6 +1709,13 @@ struct submodule_alternate_setup { #define SUBMODULE_ALTERNATE_SETUP_INIT { NULL, \ SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL } +static const char alternate_error_advice[] = N_( +"An alternate computed from a superproject's alternate is invalid.\n" +"To allow Git to clone without an alternate in such a case, set\n" +"submodule.alternateErrorStrategy to 'info' or, equivalently, clone with\n" +"'--reference-if-able' instead of '--reference'." +); + static int add_possible_reference_from_superproject( struct object_directory *odb, void *sas_cb) { @@ -1299,6 +1747,8 @@ static int add_possible_reference_from_superproject( } else { switch (sas->error_mode) { case SUBMODULE_ALTERNATE_ERROR_DIE: + if (advice_submodule_alternate_error_strategy_die) + advise(_(alternate_error_advice)); die(_("submodule '%s' cannot add alternate: %s"), sas->submodule_name, err.buf); case SUBMODULE_ALTERNATE_ERROR_INFO: @@ -1359,8 +1809,9 @@ static int module_clone(int argc, const char **argv, const char *prefix) char *p, *path = NULL, *sm_gitdir; struct strbuf sb = STRBUF_INIT; struct string_list reference = STRING_LIST_INIT_NODUP; - int dissociate = 0; + int dissociate = 0, require_init = 0; char *sm_alternate = NULL, *error_strategy = NULL; + int single_branch = -1; struct option module_clone_options[] = { OPT_STRING(0, "prefix", &prefix, @@ -1386,12 +1837,17 @@ static int module_clone(int argc, const char **argv, const char *prefix) OPT__QUIET(&quiet, "Suppress output for cloning a submodule"), OPT_BOOL(0, "progress", &progress, N_("force cloning progress")), + OPT_BOOL(0, "require-init", &require_init, + N_("disallow cloning into non-empty directory")), + OPT_BOOL(0, "single-branch", &single_branch, + N_("clone only one branch, HEAD or --branch")), OPT_END() }; const char *const git_submodule_helper_usage[] = { N_("git submodule--helper clone [--prefix=<path>] [--quiet] " "[--reference <repository>] [--name <name>] [--depth <depth>] " + "[--single-branch] " "--url <url> --path <path>"), NULL }; @@ -1413,6 +1869,10 @@ static int module_clone(int argc, const char **argv, const char *prefix) } else path = xstrdup(path); + if (validate_submodule_git_dir(sm_gitdir, name) < 0) + die(_("refusing to create/use '%s' in another submodule's " + "git dir"), sm_gitdir); + if (!file_exists(sm_gitdir)) { if (safe_create_leading_directories_const(sm_gitdir) < 0) die(_("could not create directory '%s'"), sm_gitdir); @@ -1420,10 +1880,12 @@ static int module_clone(int argc, const char **argv, const char *prefix) prepare_possible_alternates(name, &reference); if (clone_submodule(path, sm_gitdir, url, depth, &reference, dissociate, - quiet, progress)) + quiet, progress, single_branch)) die(_("clone of '%s' into submodule path '%s' failed"), url, path); } else { + if (require_init && !access(path, X_OK) && !is_empty_dir(path)) + die(_("directory not empty: '%s'"), path); if (safe_create_leading_directories_const(path) < 0) die(_("could not create directory '%s'"), path); strbuf_addf(&sb, "%s/index", sm_gitdir); @@ -1473,11 +1935,13 @@ static void determine_submodule_update_strategy(struct repository *r, if (parse_submodule_update_strategy(update, out) < 0) die(_("Invalid update mode '%s' for submodule path '%s'"), update, path); - } else if (!repo_config_get_string_const(r, key, &val)) { + } else if (!repo_config_get_string_tmp(r, key, &val)) { if (parse_submodule_update_strategy(val, out) < 0) die(_("Invalid update mode '%s' configured for submodule path '%s'"), val, path); } else if (sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) { + if (sub->update_strategy.type == SM_UPDATE_COMMAND) + BUG("how did we read update = !command from .gitmodules?"); out->type = sub->update_strategy.type; out->command = sub->update_strategy.command; } else @@ -1536,9 +2000,11 @@ struct submodule_update_clone { int recommend_shallow; struct string_list references; int dissociate; + unsigned require_init; const char *depth; const char *recursive_prefix; const char *prefix; + int single_branch; /* to be consumed by git-submodule.sh */ struct update_clone_data *update_clone; @@ -1553,10 +2019,14 @@ struct submodule_update_clone { int max_jobs; }; -#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \ - SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \ - NULL, NULL, NULL, \ - NULL, 0, 0, 0, NULL, 0, 0, 1} +#define SUBMODULE_UPDATE_CLONE_INIT { \ + .list = MODULE_LIST_INIT, \ + .update = SUBMODULE_UPDATE_STRATEGY_INIT, \ + .recommend_shallow = -1, \ + .references = STRING_LIST_INIT_DUP, \ + .single_branch = -1, \ + .max_jobs = 1, \ +} static void next_submodule_warn_missing(struct submodule_update_clone *suc, @@ -1621,7 +2091,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, } key = xstrfmt("submodule.%s.update", sub->name); - if (!repo_config_get_string_const(the_repository, key, &update_string)) { + if (!repo_config_get_string_tmp(the_repository, key, &update_string)) { update_type = parse_submodule_update_type(update_string); } else { update_type = sub->update_strategy.type; @@ -1644,7 +2114,7 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, strbuf_reset(&sb); strbuf_addf(&sb, "submodule.%s.url", sub->name); - if (repo_config_get_string_const(the_repository, sb.buf, &url)) { + if (repo_config_get_string_tmp(the_repository, sb.buf, &url)) { if (starts_with_dot_slash(sub->url) || starts_with_dot_dot_slash(sub->url)) { url = compute_submodule_clone_url(sub->url); @@ -1671,32 +2141,38 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, child->no_stdin = 1; child->stdout_to_stderr = 1; child->err = -1; - argv_array_push(&child->args, "submodule--helper"); - argv_array_push(&child->args, "clone"); + strvec_push(&child->args, "submodule--helper"); + strvec_push(&child->args, "clone"); if (suc->progress) - argv_array_push(&child->args, "--progress"); + strvec_push(&child->args, "--progress"); if (suc->quiet) - argv_array_push(&child->args, "--quiet"); + strvec_push(&child->args, "--quiet"); if (suc->prefix) - argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL); + strvec_pushl(&child->args, "--prefix", suc->prefix, NULL); if (suc->recommend_shallow && sub->recommend_shallow == 1) - argv_array_push(&child->args, "--depth=1"); - argv_array_pushl(&child->args, "--path", sub->path, NULL); - argv_array_pushl(&child->args, "--name", sub->name, NULL); - argv_array_pushl(&child->args, "--url", url, NULL); + strvec_push(&child->args, "--depth=1"); + if (suc->require_init) + strvec_push(&child->args, "--require-init"); + strvec_pushl(&child->args, "--path", sub->path, NULL); + strvec_pushl(&child->args, "--name", sub->name, NULL); + strvec_pushl(&child->args, "--url", url, NULL); if (suc->references.nr) { struct string_list_item *item; for_each_string_list_item(item, &suc->references) - argv_array_pushl(&child->args, "--reference", item->string, NULL); + strvec_pushl(&child->args, "--reference", item->string, NULL); } if (suc->dissociate) - argv_array_push(&child->args, "--dissociate"); + strvec_push(&child->args, "--dissociate"); if (suc->depth) - argv_array_push(&child->args, suc->depth); + strvec_push(&child->args, suc->depth); + if (suc->single_branch >= 0) + strvec_push(&child->args, suc->single_branch ? + "--single-branch" : + "--no-single-branch"); cleanup: - strbuf_reset(&displaypath_sb); - strbuf_reset(&sb); + strbuf_release(&displaypath_sb); + strbuf_release(&sb); if (need_free_url) free((void*)url); @@ -1870,11 +2346,15 @@ static int update_clone(int argc, const char **argv, const char *prefix) OPT__QUIET(&suc.quiet, N_("don't print cloning progress")), OPT_BOOL(0, "progress", &suc.progress, N_("force cloning progress")), + OPT_BOOL(0, "require-init", &suc.require_init, + N_("disallow cloning into non-empty directory")), + OPT_BOOL(0, "single-branch", &suc.single_branch, + N_("clone only one branch, HEAD or --branch")), OPT_END() }; const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper update_clone [--prefix=<path>] [<path>...]"), + N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"), NULL }; suc.prefix = prefix; @@ -1920,12 +2400,12 @@ static const char *remote_submodule_branch(const char *path) return NULL; key = xstrfmt("submodule.%s.branch", sub->name); - if (repo_config_get_string_const(the_repository, key, &branch)) + if (repo_config_get_string_tmp(the_repository, key, &branch)) branch = sub->branch; free(key); if (!branch) - return "master"; + return "HEAD"; if (!strcmp(branch, ".")) { const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL); @@ -2045,7 +2525,7 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix) { const struct submodule *sub; const char *path; - char *cw; + const char *cw; struct repository subrepo; if (argc != 2) @@ -2060,7 +2540,7 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix) if (repo_submodule_init(&subrepo, the_repository, sub)) die(_("could not get a repository handle for submodule '%s'"), path); - if (!repo_config_get_string(&subrepo, "core.worktree", &cw)) { + if (!repo_config_get_string_tmp(&subrepo, "core.worktree", &cw)) { char *cfg_file, *abs_path; const char *rel_path; struct strbuf sb = STRBUF_INIT; @@ -2190,6 +2670,80 @@ static int module_config(int argc, const char **argv, const char *prefix) usage_with_options(git_submodule_helper_usage, module_config_options); } +static int module_set_url(int argc, const char **argv, const char *prefix) +{ + int quiet = 0; + const char *newurl; + const char *path; + char *config_name; + + struct option options[] = { + OPT__QUIET(&quiet, N_("Suppress output for setting url of a submodule")), + OPT_END() + }; + const char *const usage[] = { + N_("git submodule--helper set-url [--quiet] <path> <newurl>"), + NULL + }; + + argc = parse_options(argc, argv, prefix, options, usage, 0); + + if (argc != 2 || !(path = argv[0]) || !(newurl = argv[1])) + usage_with_options(usage, options); + + config_name = xstrfmt("submodule.%s.url", path); + + config_set_in_gitmodules_file_gently(config_name, newurl); + sync_submodule(path, prefix, quiet ? OPT_QUIET : 0); + + free(config_name); + + return 0; +} + +static int module_set_branch(int argc, const char **argv, const char *prefix) +{ + int opt_default = 0, ret; + const char *opt_branch = NULL; + const char *path; + char *config_name; + + /* + * We accept the `quiet` option for uniformity across subcommands, + * though there is nothing to make less verbose in this subcommand. + */ + struct option options[] = { + OPT_NOOP_NOARG('q', "quiet"), + OPT_BOOL('d', "default", &opt_default, + N_("set the default tracking branch to master")), + OPT_STRING('b', "branch", &opt_branch, N_("branch"), + N_("set the default tracking branch")), + OPT_END() + }; + const char *const usage[] = { + N_("git submodule--helper set-branch [-q|--quiet] (-d|--default) <path>"), + N_("git submodule--helper set-branch [-q|--quiet] (-b|--branch) <branch> <path>"), + NULL + }; + + argc = parse_options(argc, argv, prefix, options, usage, 0); + + if (!opt_branch && !opt_default) + die(_("--branch or --default required")); + + if (opt_branch && opt_default) + die(_("--branch and --default are mutually exclusive")); + + if (argc != 1 || !(path = argv[0])) + usage_with_options(usage, options); + + config_name = xstrfmt("submodule.%s.branch", path); + ret = config_set_in_gitmodules_file_gently(config_name, opt_branch); + + free(config_name); + return !!ret; +} + #define SUPPORT_SUPER_PREFIX (1<<0) struct cmd_struct { @@ -2214,12 +2768,15 @@ static struct cmd_struct commands[] = { {"print-default-remote", print_default_remote, 0}, {"sync", module_sync, SUPPORT_SUPER_PREFIX}, {"deinit", module_deinit, 0}, + {"summary", module_summary, SUPPORT_SUPER_PREFIX}, {"remote-branch", resolve_remote_submodule_branch, 0}, {"push-check", push_check, 0}, {"absorb-git-dirs", absorb_git_dirs, SUPPORT_SUPER_PREFIX}, {"is-active", is_active, 0}, {"check-name", check_name, 0}, {"config", module_config, 0}, + {"set-url", module_set_url, 0}, + {"set-branch", module_set_branch, 0}, }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) diff --git a/third_party/git/builtin/tag.c b/third_party/git/builtin/tag.c index e0a4c2538284..ecf011776dc0 100644 --- a/third_party/git/builtin/tag.c +++ b/third_party/git/builtin/tag.c @@ -17,7 +17,7 @@ #include "diff.h" #include "revision.h" #include "gpg-interface.h" -#include "sha1-array.h" +#include "oid-array.h" #include "column.h" #include "ref-filter.h" @@ -26,7 +26,7 @@ static const char * const git_tag_usage[] = { "\t\t<tagname> [<head>]"), N_("git tag -d <tagname>..."), N_("git tag -l [-n[<num>]] [--contains <commit>] [--no-contains <commit>] [--points-at <object>]\n" - "\t\t[--format=<format>] [--[no-]merged [<commit>]] [<pattern>...]"), + "\t\t[--format=<format>] [--merged <commit>] [--no-merged <commit>] [<pattern>...]"), N_("git tag -v [--format=<format>] <tagname>..."), NULL }; @@ -231,8 +231,9 @@ static void create_tag(const struct object_id *object, const char *object_ref, if (type <= OBJ_NONE) die(_("bad object type.")); - if (type == OBJ_TAG && advice_nested_tag) - advise(_(message_advice_nested_tag), tag, object_ref); + if (type == OBJ_TAG) + advise_if_enabled(ADVICE_NESTED_TAG, _(message_advice_nested_tag), + tag, object_ref); strbuf_addf(&header, "object %s\n" @@ -409,8 +410,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_GROUP(N_("Tag creation options")), OPT_BOOL('a', "annotate", &annotate, N_("annotated tag, needs a message")), - { OPTION_CALLBACK, 'm', "message", &msg, N_("message"), - N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg }, + OPT_CALLBACK_F('m', "message", &msg, N_("message"), + N_("tag message"), PARSE_OPT_NONEG, parse_msg_arg), OPT_FILENAME('F', "file", &msgfile, N_("read message from file")), OPT_BOOL('e', "edit", &edit_flag, N_("force edit of tag message")), OPT_BOOL('s', "sign", &opt.sign, N_("annotated and GPG-signed tag")), @@ -456,8 +457,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (argc == 0) cmdmode = 'l'; else if (filter.with_commit || filter.no_commit || - filter.points_at.nr || filter.merge_commit || - filter.lines != -1) + filter.reachable_from || filter.unreachable_from || + filter.points_at.nr || filter.lines != -1) cmdmode = 'l'; } @@ -484,7 +485,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) } if (!sorting) sorting = ref_default_sorting(); - sorting->ignore_case = icase; + ref_sorting_icase_all(sorting, icase); filter.ignore_case = icase; if (cmdmode == 'l') { int ret; @@ -508,7 +509,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) die(_("--no-contains option is only allowed in list mode")); if (filter.points_at.nr) die(_("--points-at option is only allowed in list mode")); - if (filter.merge_commit) + if (filter.reachable_from || filter.unreachable_from) die(_("--merged and --no-merged options are only allowed in list mode")); if (cmdmode == 'd') return for_each_tag_name(argv, delete_tag, NULL); diff --git a/third_party/git/builtin/unpack-objects.c b/third_party/git/builtin/unpack-objects.c index a87a4bfd2c55..dd4a75e030d2 100644 --- a/third_party/git/builtin/unpack-objects.c +++ b/third_party/git/builtin/unpack-objects.c @@ -24,6 +24,7 @@ static off_t consumed_bytes; static off_t max_input_size; static git_hash_ctx ctx; static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; +static struct progress *progress; /* * When running under --strict mode, objects whose reachability are @@ -92,6 +93,7 @@ static void use(int bytes) consumed_bytes += bytes; if (max_input_size && consumed_bytes > max_input_size) die(_("pack exceeds maximum allowed size")); + display_throughput(progress, consumed_bytes); } static void *get_data(unsigned long size) @@ -263,7 +265,8 @@ static void write_object(unsigned nr, enum object_type type, } else { struct object *obj; int eaten; - hash_object_file(buf, size, type_name(type), &obj_list[nr].oid); + hash_object_file(the_hash_algo, buf, size, type_name(type), + &obj_list[nr].oid); added_object(nr, type, buf, size); obj = parse_object_buffer(the_repository, &obj_list[nr].oid, type, size, buf, @@ -484,7 +487,6 @@ static void unpack_one(unsigned nr) static void unpack_all(void) { int i; - struct progress *progress = NULL; struct pack_header *hdr = fill(sizeof(struct pack_header)); nr_objects = ntohl(hdr->hdr_entries); diff --git a/third_party/git/builtin/update-index.c b/third_party/git/builtin/update-index.c index dff2f4b83720..79087bccea4b 100644 --- a/third_party/git/builtin/update-index.c +++ b/third_party/git/builtin/update-index.c @@ -35,6 +35,7 @@ static int verbose; static int mark_valid_only; static int mark_skip_worktree_only; static int mark_fsmonitor_only; +static int ignore_skip_worktree_entries; #define MARK_FLAG 1 #define UNMARK_FLAG 2 static struct strbuf mtime_dir = STRBUF_INIT; @@ -381,7 +382,8 @@ static int process_path(const char *path, struct stat *st, int stat_errno) * so updating it does not make sense. * On the other hand, removing it from index should work */ - if (allow_remove && remove_file_from_cache(path)) + if (!ignore_skip_worktree_entries && allow_remove && + remove_file_from_cache(path)) return error("%s: cannot remove from the index", path); return 0; } @@ -966,6 +968,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) struct parse_opt_ctx_t ctx; strbuf_getline_fn getline_fn; int parseopt_state = PARSE_OPT_UNKNOWN; + struct repository *r = the_repository; struct option options[] = { OPT_BIT('q', NULL, &refresh_args.flags, N_("continue refresh even when index needs update"), @@ -982,14 +985,14 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) OPT_BIT(0, "unmerged", &refresh_args.flags, N_("refresh even if index contains unmerged entries"), REFRESH_UNMERGED), - {OPTION_CALLBACK, 0, "refresh", &refresh_args, NULL, + OPT_CALLBACK_F(0, "refresh", &refresh_args, NULL, N_("refresh stat information"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, - refresh_callback}, - {OPTION_CALLBACK, 0, "really-refresh", &refresh_args, NULL, + refresh_callback), + OPT_CALLBACK_F(0, "really-refresh", &refresh_args, NULL, N_("like --refresh, but ignore assume-unchanged setting"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, - really_refresh_callback}, + really_refresh_callback), {OPTION_LOWLEVEL_CALLBACK, 0, "cacheinfo", NULL, N_("<mode>,<object>,<path>"), N_("add the specified entry to the index"), @@ -997,10 +1000,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, NULL, 0, cacheinfo_callback}, - {OPTION_CALLBACK, 0, "chmod", &set_executable_bit, "(+|-)x", + OPT_CALLBACK_F(0, "chmod", &set_executable_bit, "(+|-)x", N_("override the executable bit of the listed files"), PARSE_OPT_NONEG, - chmod_callback}, + chmod_callback), {OPTION_SET_INT, 0, "assume-unchanged", &mark_valid_only, NULL, N_("mark files as \"not changing\""), PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG}, @@ -1013,6 +1016,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) {OPTION_SET_INT, 0, "no-skip-worktree", &mark_skip_worktree_only, NULL, N_("clear skip-worktree bit"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, + OPT_BOOL(0, "ignore-skip-worktree-entries", &ignore_skip_worktree_entries, + N_("do not touch index-only entries")), OPT_SET_INT(0, "info-only", &info_only, N_("add to index only; do not add content to object database"), 1), OPT_SET_INT(0, "force-remove", &force_remove, @@ -1040,10 +1045,10 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) REFRESH_IGNORE_MISSING), OPT_SET_INT(0, "verbose", &verbose, N_("report actions to standard output"), 1), - {OPTION_CALLBACK, 0, "clear-resolve-undo", NULL, NULL, + OPT_CALLBACK_F(0, "clear-resolve-undo", NULL, NULL, N_("(for porcelains) forget saved unresolved conflicts"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, - resolve_undo_clear_callback}, + resolve_undo_clear_callback), OPT_INTEGER(0, "index-version", &preferred_index_format, N_("write index in this format")), OPT_BOOL(0, "split-index", &split_index, @@ -1180,11 +1185,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) remove_split_index(&the_index); } + prepare_repo_settings(r); switch (untracked_cache) { case UC_UNSPECIFIED: break; case UC_DISABLE: - if (git_config_get_untracked_cache() == 1) + if (r->settings.core_untracked_cache == UNTRACKED_CACHE_WRITE) warning(_("core.untrackedCache is set to true; " "remove or change it, if you really want to " "disable the untracked cache")); @@ -1196,7 +1202,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) return !test_if_untracked_cache_is_supported(); case UC_ENABLE: case UC_FORCE: - if (git_config_get_untracked_cache() == 0) + if (r->settings.core_untracked_cache == UNTRACKED_CACHE_REMOVE) warning(_("core.untrackedCache is set to false; " "remove or change it, if you really want to " "enable the untracked cache")); diff --git a/third_party/git/builtin/update-ref.c b/third_party/git/builtin/update-ref.c index 2d8f7f05785d..8a2df4459c66 100644 --- a/third_party/git/builtin/update-ref.c +++ b/third_party/git/builtin/update-ref.c @@ -4,7 +4,7 @@ #include "builtin.h" #include "parse-options.h" #include "quote.h" -#include "argv-array.h" +#include "strvec.h" static const char * const git_update_ref_usage[] = { N_("git update-ref [<options>] -d <refname> [<old-val>]"), @@ -50,7 +50,7 @@ static const char *parse_arg(const char *next, struct strbuf *arg) * the argument. Die if C-quoting is malformed or the reference name * is invalid. */ -static char *parse_refname(struct strbuf *input, const char **next) +static char *parse_refname(const char **next) { struct strbuf ref = STRBUF_INIT; @@ -95,7 +95,7 @@ static char *parse_refname(struct strbuf *input, const char **next) * provided but cannot be converted to a SHA-1, die. flags can * include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY. */ -static int parse_next_oid(struct strbuf *input, const char **next, +static int parse_next_oid(const char **next, const char *end, struct object_id *oid, const char *command, const char *refname, int flags) @@ -103,7 +103,7 @@ static int parse_next_oid(struct strbuf *input, const char **next, struct strbuf arg = STRBUF_INIT; int ret = 0; - if (*next == input->buf + input->len) + if (*next == end) goto eof; if (line_termination) { @@ -128,7 +128,7 @@ static int parse_next_oid(struct strbuf *input, const char **next, die("%s %s: expected NUL but got: %s", command, refname, *next); (*next)++; - if (*next == input->buf + input->len) + if (*next == end) goto eof; strbuf_addstr(&arg, *next); *next += arg.len; @@ -178,23 +178,23 @@ static int parse_next_oid(struct strbuf *input, const char **next, * depending on how line_termination is set. */ -static const char *parse_cmd_update(struct ref_transaction *transaction, - struct strbuf *input, const char *next) +static void parse_cmd_update(struct ref_transaction *transaction, + const char *next, const char *end) { struct strbuf err = STRBUF_INIT; char *refname; struct object_id new_oid, old_oid; int have_old; - refname = parse_refname(input, &next); + refname = parse_refname(&next); if (!refname) die("update: missing <ref>"); - if (parse_next_oid(input, &next, &new_oid, "update", refname, + if (parse_next_oid(&next, end, &new_oid, "update", refname, PARSE_SHA1_ALLOW_EMPTY)) die("update %s: missing <newvalue>", refname); - have_old = !parse_next_oid(input, &next, &old_oid, "update", refname, + have_old = !parse_next_oid(&next, end, &old_oid, "update", refname, PARSE_SHA1_OLD); if (*next != line_termination) @@ -209,22 +209,20 @@ static const char *parse_cmd_update(struct ref_transaction *transaction, update_flags = default_flags; free(refname); strbuf_release(&err); - - return next; } -static const char *parse_cmd_create(struct ref_transaction *transaction, - struct strbuf *input, const char *next) +static void parse_cmd_create(struct ref_transaction *transaction, + const char *next, const char *end) { struct strbuf err = STRBUF_INIT; char *refname; struct object_id new_oid; - refname = parse_refname(input, &next); + refname = parse_refname(&next); if (!refname) die("create: missing <ref>"); - if (parse_next_oid(input, &next, &new_oid, "create", refname, 0)) + if (parse_next_oid(&next, end, &new_oid, "create", refname, 0)) die("create %s: missing <newvalue>", refname); if (is_null_oid(&new_oid)) @@ -241,23 +239,21 @@ static const char *parse_cmd_create(struct ref_transaction *transaction, update_flags = default_flags; free(refname); strbuf_release(&err); - - return next; } -static const char *parse_cmd_delete(struct ref_transaction *transaction, - struct strbuf *input, const char *next) +static void parse_cmd_delete(struct ref_transaction *transaction, + const char *next, const char *end) { struct strbuf err = STRBUF_INIT; char *refname; struct object_id old_oid; int have_old; - refname = parse_refname(input, &next); + refname = parse_refname(&next); if (!refname) die("delete: missing <ref>"); - if (parse_next_oid(input, &next, &old_oid, "delete", refname, + if (parse_next_oid(&next, end, &old_oid, "delete", refname, PARSE_SHA1_OLD)) { have_old = 0; } else { @@ -277,22 +273,20 @@ static const char *parse_cmd_delete(struct ref_transaction *transaction, update_flags = default_flags; free(refname); strbuf_release(&err); - - return next; } -static const char *parse_cmd_verify(struct ref_transaction *transaction, - struct strbuf *input, const char *next) +static void parse_cmd_verify(struct ref_transaction *transaction, + const char *next, const char *end) { struct strbuf err = STRBUF_INIT; char *refname; struct object_id old_oid; - refname = parse_refname(input, &next); + refname = parse_refname(&next); if (!refname) die("verify: missing <ref>"); - if (parse_next_oid(input, &next, &old_oid, "verify", refname, + if (parse_next_oid(&next, end, &old_oid, "verify", refname, PARSE_SHA1_OLD)) oidclr(&old_oid); @@ -306,50 +300,179 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction, update_flags = default_flags; free(refname); strbuf_release(&err); - - return next; } -static const char *parse_cmd_option(struct strbuf *input, const char *next) +static void parse_cmd_option(struct ref_transaction *transaction, + const char *next, const char *end) { const char *rest; if (skip_prefix(next, "no-deref", &rest) && *rest == line_termination) update_flags |= REF_NO_DEREF; else die("option unknown: %s", next); - return rest; } -static void update_refs_stdin(struct ref_transaction *transaction) +static void parse_cmd_start(struct ref_transaction *transaction, + const char *next, const char *end) +{ + if (*next != line_termination) + die("start: extra input: %s", next); + puts("start: ok"); +} + +static void parse_cmd_prepare(struct ref_transaction *transaction, + const char *next, const char *end) +{ + struct strbuf error = STRBUF_INIT; + if (*next != line_termination) + die("prepare: extra input: %s", next); + if (ref_transaction_prepare(transaction, &error)) + die("prepare: %s", error.buf); + puts("prepare: ok"); +} + +static void parse_cmd_abort(struct ref_transaction *transaction, + const char *next, const char *end) +{ + struct strbuf error = STRBUF_INIT; + if (*next != line_termination) + die("abort: extra input: %s", next); + if (ref_transaction_abort(transaction, &error)) + die("abort: %s", error.buf); + puts("abort: ok"); +} + +static void parse_cmd_commit(struct ref_transaction *transaction, + const char *next, const char *end) +{ + struct strbuf error = STRBUF_INIT; + if (*next != line_termination) + die("commit: extra input: %s", next); + if (ref_transaction_commit(transaction, &error)) + die("commit: %s", error.buf); + puts("commit: ok"); + ref_transaction_free(transaction); +} + +enum update_refs_state { + /* Non-transactional state open for updates. */ + UPDATE_REFS_OPEN, + /* A transaction has been started. */ + UPDATE_REFS_STARTED, + /* References are locked and ready for commit */ + UPDATE_REFS_PREPARED, + /* Transaction has been committed or closed. */ + UPDATE_REFS_CLOSED, +}; + +static const struct parse_cmd { + const char *prefix; + void (*fn)(struct ref_transaction *, const char *, const char *); + unsigned args; + enum update_refs_state state; +} command[] = { + { "update", parse_cmd_update, 3, UPDATE_REFS_OPEN }, + { "create", parse_cmd_create, 2, UPDATE_REFS_OPEN }, + { "delete", parse_cmd_delete, 2, UPDATE_REFS_OPEN }, + { "verify", parse_cmd_verify, 2, UPDATE_REFS_OPEN }, + { "option", parse_cmd_option, 1, UPDATE_REFS_OPEN }, + { "start", parse_cmd_start, 0, UPDATE_REFS_STARTED }, + { "prepare", parse_cmd_prepare, 0, UPDATE_REFS_PREPARED }, + { "abort", parse_cmd_abort, 0, UPDATE_REFS_CLOSED }, + { "commit", parse_cmd_commit, 0, UPDATE_REFS_CLOSED }, +}; + +static void update_refs_stdin(void) { - struct strbuf input = STRBUF_INIT; - const char *next; + struct strbuf input = STRBUF_INIT, err = STRBUF_INIT; + enum update_refs_state state = UPDATE_REFS_OPEN; + struct ref_transaction *transaction; + int i, j; + + transaction = ref_transaction_begin(&err); + if (!transaction) + die("%s", err.buf); - if (strbuf_read(&input, 0, 1000) < 0) - die_errno("could not read from stdin"); - next = input.buf; /* Read each line dispatch its command */ - while (next < input.buf + input.len) { - if (*next == line_termination) + while (!strbuf_getwholeline(&input, stdin, line_termination)) { + const struct parse_cmd *cmd = NULL; + + if (*input.buf == line_termination) die("empty command in input"); - else if (isspace(*next)) - die("whitespace before command: %s", next); - else if (skip_prefix(next, "update ", &next)) - next = parse_cmd_update(transaction, &input, next); - else if (skip_prefix(next, "create ", &next)) - next = parse_cmd_create(transaction, &input, next); - else if (skip_prefix(next, "delete ", &next)) - next = parse_cmd_delete(transaction, &input, next); - else if (skip_prefix(next, "verify ", &next)) - next = parse_cmd_verify(transaction, &input, next); - else if (skip_prefix(next, "option ", &next)) - next = parse_cmd_option(&input, next); - else - die("unknown command: %s", next); - - next++; + else if (isspace(*input.buf)) + die("whitespace before command: %s", input.buf); + + for (i = 0; i < ARRAY_SIZE(command); i++) { + const char *prefix = command[i].prefix; + char c; + + if (!starts_with(input.buf, prefix)) + continue; + + /* + * If the command has arguments, verify that it's + * followed by a space. Otherwise, it shall be followed + * by a line terminator. + */ + c = command[i].args ? ' ' : line_termination; + if (input.buf[strlen(prefix)] != c) + continue; + + cmd = &command[i]; + break; + } + if (!cmd) + die("unknown command: %s", input.buf); + + /* + * Read additional arguments if NUL-terminated. Do not raise an + * error in case there is an early EOF to let the command + * handle missing arguments with a proper error message. + */ + for (j = 1; line_termination == '\0' && j < cmd->args; j++) + if (strbuf_appendwholeline(&input, stdin, line_termination)) + break; + + switch (state) { + case UPDATE_REFS_OPEN: + case UPDATE_REFS_STARTED: + /* Do not downgrade a transaction to a non-transaction. */ + if (cmd->state >= state) + state = cmd->state; + break; + case UPDATE_REFS_PREPARED: + if (cmd->state != UPDATE_REFS_CLOSED) + die("prepared transactions can only be closed"); + state = cmd->state; + break; + case UPDATE_REFS_CLOSED: + die("transaction is closed"); + break; + } + + cmd->fn(transaction, input.buf + strlen(cmd->prefix) + !!cmd->args, + input.buf + input.len); + } + + switch (state) { + case UPDATE_REFS_OPEN: + /* Commit by default if no transaction was requested. */ + if (ref_transaction_commit(transaction, &err)) + die("%s", err.buf); + ref_transaction_free(transaction); + break; + case UPDATE_REFS_STARTED: + case UPDATE_REFS_PREPARED: + /* If using a transaction, we want to abort it. */ + if (ref_transaction_abort(transaction, &err)) + die("%s", err.buf); + break; + case UPDATE_REFS_CLOSED: + /* Otherwise no need to do anything, the transaction was closed already. */ + break; } + strbuf_release(&err); strbuf_release(&input); } @@ -384,21 +507,11 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) } if (read_stdin) { - struct strbuf err = STRBUF_INIT; - struct ref_transaction *transaction; - - transaction = ref_transaction_begin(&err); - if (!transaction) - die("%s", err.buf); if (delete || argc > 0) usage_with_options(git_update_ref_usage, options); if (end_null) line_termination = '\0'; - update_refs_stdin(transaction); - if (ref_transaction_commit(transaction, &err)) - die("%s", err.buf); - ref_transaction_free(transaction); - strbuf_release(&err); + update_refs_stdin(); return 0; } diff --git a/third_party/git/builtin/upload-archive.c b/third_party/git/builtin/upload-archive.c index 018879737aee..24654b4c9bf0 100644 --- a/third_party/git/builtin/upload-archive.c +++ b/third_party/git/builtin/upload-archive.c @@ -7,7 +7,7 @@ #include "pkt-line.h" #include "sideband.h" #include "run-command.h" -#include "argv-array.h" +#include "strvec.h" static const char upload_archive_usage[] = "git upload-archive <repo>"; @@ -19,7 +19,7 @@ static const char deadchild[] = int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) { - struct argv_array sent_argv = ARGV_ARRAY_INIT; + struct strvec sent_argv = STRVEC_INIT; const char *arg_cmd = "argument "; if (argc != 2 || !strcmp(argv[1], "-h")) @@ -31,21 +31,21 @@ int cmd_upload_archive_writer(int argc, const char **argv, const char *prefix) init_archivers(); /* put received options in sent_argv[] */ - argv_array_push(&sent_argv, "git-upload-archive"); + strvec_push(&sent_argv, "git-upload-archive"); for (;;) { char *buf = packet_read_line(0, NULL); if (!buf) break; /* got a flush */ - if (sent_argv.argc > MAX_ARGS) + if (sent_argv.nr > MAX_ARGS) die("Too many options (>%d)", MAX_ARGS - 1); if (!starts_with(buf, arg_cmd)) die("'argument' token or flush expected"); - argv_array_push(&sent_argv, buf + strlen(arg_cmd)); + strvec_push(&sent_argv, buf + strlen(arg_cmd)); } /* parse all options sent by the client */ - return write_archive(sent_argv.argc, sent_argv.argv, prefix, + return write_archive(sent_argv.nr, sent_argv.v, prefix, the_repository, NULL, 1); } diff --git a/third_party/git/builtin/verify-pack.c b/third_party/git/builtin/verify-pack.c index c2a1a5c50484..05c52135946b 100644 --- a/third_party/git/builtin/verify-pack.c +++ b/third_party/git/builtin/verify-pack.c @@ -7,21 +7,26 @@ #define VERIFY_PACK_VERBOSE 01 #define VERIFY_PACK_STAT_ONLY 02 -static int verify_one_pack(const char *path, unsigned int flags) +static int verify_one_pack(const char *path, unsigned int flags, const char *hash_algo) { struct child_process index_pack = CHILD_PROCESS_INIT; - const char *argv[] = {"index-pack", NULL, NULL, NULL }; + struct strvec *argv = &index_pack.args; struct strbuf arg = STRBUF_INIT; int verbose = flags & VERIFY_PACK_VERBOSE; int stat_only = flags & VERIFY_PACK_STAT_ONLY; int err; + strvec_push(argv, "index-pack"); + if (stat_only) - argv[1] = "--verify-stat-only"; + strvec_push(argv, "--verify-stat-only"); else if (verbose) - argv[1] = "--verify-stat"; + strvec_push(argv, "--verify-stat"); else - argv[1] = "--verify"; + strvec_push(argv, "--verify"); + + if (hash_algo) + strvec_pushf(argv, "--object-format=%s", hash_algo); /* * In addition to "foo.pack" we accept "foo.idx" and "foo"; @@ -31,9 +36,8 @@ static int verify_one_pack(const char *path, unsigned int flags) if (strbuf_strip_suffix(&arg, ".idx") || !ends_with(arg.buf, ".pack")) strbuf_addstr(&arg, ".pack"); - argv[2] = arg.buf; + strvec_push(argv, arg.buf); - index_pack.argv = argv; index_pack.git_cmd = 1; err = run_command(&index_pack); @@ -60,12 +64,15 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix) { int err = 0; unsigned int flags = 0; + const char *object_format = NULL; int i; const struct option verify_pack_options[] = { OPT_BIT('v', "verbose", &flags, N_("verbose"), VERIFY_PACK_VERBOSE), OPT_BIT('s', "stat-only", &flags, N_("show statistics only"), VERIFY_PACK_STAT_ONLY), + OPT_STRING(0, "object-format", &object_format, N_("hash"), + N_("specify the hash algorithm to use")), OPT_END() }; @@ -75,7 +82,7 @@ int cmd_verify_pack(int argc, const char **argv, const char *prefix) if (argc < 1) usage_with_options(verify_pack_usage, verify_pack_options); for (i = 0; i < argc; i++) { - if (verify_one_pack(argv[i], flags)) + if (verify_one_pack(argv[i], flags, object_format)) err = 1; } diff --git a/third_party/git/builtin/worktree.c b/third_party/git/builtin/worktree.c index a5bb02b2076a..99abaeec6c6e 100644 --- a/third_party/git/builtin/worktree.c +++ b/third_party/git/builtin/worktree.c @@ -4,13 +4,12 @@ #include "builtin.h" #include "dir.h" #include "parse-options.h" -#include "argv-array.h" +#include "strvec.h" #include "branch.h" #include "refs.h" #include "run-command.h" #include "sigchain.h" #include "submodule.h" -#include "refs.h" #include "utf8.h" #include "worktree.h" @@ -68,7 +67,12 @@ static void delete_worktrees_dir_if_empty(void) rmdir(git_path("worktrees")); /* ignore failed removal */ } -static int prune_worktree(const char *id, struct strbuf *reason) +/* + * Return true if worktree entry should be pruned, along with the reason for + * pruning. Otherwise, return false and the worktree's path, or NULL if it + * cannot be determined. Caller is responsible for freeing returned path. + */ +static int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath) { struct stat st; char *path; @@ -76,20 +80,21 @@ static int prune_worktree(const char *id, struct strbuf *reason) size_t len; ssize_t read_result; + *wtpath = NULL; if (!is_directory(git_path("worktrees/%s", id))) { - strbuf_addf(reason, _("Removing worktrees/%s: not a valid directory"), id); + strbuf_addstr(reason, _("not a valid directory")); return 1; } if (file_exists(git_path("worktrees/%s/locked", id))) return 0; if (stat(git_path("worktrees/%s/gitdir", id), &st)) { - strbuf_addf(reason, _("Removing worktrees/%s: gitdir file does not exist"), id); + strbuf_addstr(reason, _("gitdir file does not exist")); return 1; } fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY); if (fd < 0) { - strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"), - id, strerror(errno)); + strbuf_addf(reason, _("unable to read gitdir file (%s)"), + strerror(errno)); return 1; } len = xsize_t(st.st_size); @@ -97,8 +102,8 @@ static int prune_worktree(const char *id, struct strbuf *reason) read_result = read_in_full(fd, path, len); if (read_result < 0) { - strbuf_addf(reason, _("Removing worktrees/%s: unable to read gitdir file (%s)"), - id, strerror(errno)); + strbuf_addf(reason, _("unable to read gitdir file (%s)"), + strerror(errno)); close(fd); free(path); return 1; @@ -107,53 +112,103 @@ static int prune_worktree(const char *id, struct strbuf *reason) if (read_result != len) { strbuf_addf(reason, - _("Removing worktrees/%s: short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"), - id, (uintmax_t)len, (uintmax_t)read_result); + _("short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"), + (uintmax_t)len, (uintmax_t)read_result); free(path); return 1; } while (len && (path[len - 1] == '\n' || path[len - 1] == '\r')) len--; if (!len) { - strbuf_addf(reason, _("Removing worktrees/%s: invalid gitdir file"), id); + strbuf_addstr(reason, _("invalid gitdir file")); free(path); return 1; } path[len] = '\0'; if (!file_exists(path)) { - free(path); if (stat(git_path("worktrees/%s/index", id), &st) || st.st_mtime <= expire) { - strbuf_addf(reason, _("Removing worktrees/%s: gitdir file points to non-existent location"), id); + strbuf_addstr(reason, _("gitdir file points to non-existent location")); + free(path); return 1; } else { + *wtpath = path; return 0; } } - free(path); + *wtpath = path; return 0; } +static void prune_worktree(const char *id, const char *reason) +{ + if (show_only || verbose) + printf_ln(_("Removing %s/%s: %s"), "worktrees", id, reason); + if (!show_only) + delete_git_dir(id); +} + +static int prune_cmp(const void *a, const void *b) +{ + const struct string_list_item *x = a; + const struct string_list_item *y = b; + int c; + + if ((c = fspathcmp(x->string, y->string))) + return c; + /* + * paths same; prune_dupes() removes all but the first worktree entry + * having the same path, so sort main worktree ('util' is NULL) above + * linked worktrees ('util' not NULL) since main worktree can't be + * removed + */ + if (!x->util) + return -1; + if (!y->util) + return 1; + /* paths same; sort by .git/worktrees/<id> */ + return strcmp(x->util, y->util); +} + +static void prune_dups(struct string_list *l) +{ + int i; + + QSORT(l->items, l->nr, prune_cmp); + for (i = 1; i < l->nr; i++) { + if (!fspathcmp(l->items[i].string, l->items[i - 1].string)) + prune_worktree(l->items[i].util, "duplicate entry"); + } +} + static void prune_worktrees(void) { struct strbuf reason = STRBUF_INIT; + struct strbuf main_path = STRBUF_INIT; + struct string_list kept = STRING_LIST_INIT_NODUP; DIR *dir = opendir(git_path("worktrees")); struct dirent *d; if (!dir) return; while ((d = readdir(dir)) != NULL) { + char *path; if (is_dot_or_dotdot(d->d_name)) continue; strbuf_reset(&reason); - if (!prune_worktree(d->d_name, &reason)) - continue; - if (show_only || verbose) - printf("%s\n", reason.buf); - if (show_only) - continue; - delete_git_dir(d->d_name); + if (should_prune_worktree(d->d_name, &reason, &path)) + prune_worktree(d->d_name, reason.buf); + else if (path) + string_list_append(&kept, path)->util = xstrdup(d->d_name); } closedir(dir); + + strbuf_add_absolute_path(&main_path, get_git_common_dir()); + /* massage main worktree absolute path to match 'gitdir' content */ + strbuf_strip_suffix(&main_path, "/."); + string_list_append(&kept, strbuf_detach(&main_path, NULL)); + prune_dups(&kept); + string_list_clear(&kept, 1); + if (!show_only) delete_worktrees_dir_if_empty(); strbuf_release(&reason); @@ -225,59 +280,55 @@ static const char *worktree_basename(const char *path, int *olen) return name; } -static void validate_worktree_add(const char *path, const struct add_opts *opts) +/* check that path is viable location for worktree */ +static void check_candidate_path(const char *path, + int force, + struct worktree **worktrees, + const char *cmd) { - struct worktree **worktrees; struct worktree *wt; int locked; if (file_exists(path) && !is_empty_dir(path)) die(_("'%s' already exists"), path); - worktrees = get_worktrees(0); - /* - * find_worktree()'s suffix matching may undesirably find the main - * rather than a linked worktree (for instance, when the basenames - * of the main worktree and the one being created are the same). - * We're only interested in linked worktrees, so skip the main - * worktree with +1. - */ - wt = find_worktree(worktrees + 1, NULL, path); + wt = find_worktree_by_path(worktrees, path); if (!wt) - goto done; + return; locked = !!worktree_lock_reason(wt); - if ((!locked && opts->force) || (locked && opts->force > 1)) { + if ((!locked && force) || (locked && force > 1)) { if (delete_git_dir(wt->id)) - die(_("unable to re-add worktree '%s'"), path); - goto done; + die(_("unusable worktree destination '%s'"), path); + return; } if (locked) - die(_("'%s' is a missing but locked worktree;\nuse 'add -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), path); + die(_("'%s' is a missing but locked worktree;\nuse '%s -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), cmd, path); else - die(_("'%s' is a missing but already registered worktree;\nuse 'add -f' to override, or 'prune' or 'remove' to clear"), path); - -done: - free_worktrees(worktrees); + die(_("'%s' is a missing but already registered worktree;\nuse '%s -f' to override, or 'prune' or 'remove' to clear"), cmd, path); } static int add_worktree(const char *path, const char *refname, const struct add_opts *opts) { struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; - struct strbuf sb = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT, realpath = STRBUF_INIT; const char *name; struct child_process cp = CHILD_PROCESS_INIT; - struct argv_array child_env = ARGV_ARRAY_INIT; + struct strvec child_env = STRVEC_INIT; unsigned int counter = 0; int len, ret; struct strbuf symref = STRBUF_INIT; struct commit *commit = NULL; int is_branch = 0; struct strbuf sb_name = STRBUF_INIT; + struct worktree **worktrees; - validate_worktree_add(path, opts); + worktrees = get_worktrees(); + check_candidate_path(path, opts->force, worktrees, "add"); + free_worktrees(worktrees); + worktrees = NULL; /* is 'refname' a branch or commit? */ if (!opts->detach && !strbuf_check_branch_ref(&symref, refname) && @@ -338,9 +389,11 @@ static int add_worktree(const char *path, const char *refname, strbuf_reset(&sb); strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); - write_file(sb.buf, "%s", real_path(sb_git.buf)); + strbuf_realpath(&realpath, sb_git.buf, 1); + write_file(sb.buf, "%s", realpath.buf); + strbuf_realpath(&realpath, get_git_common_dir(), 1); write_file(sb_git.buf, "gitdir: %s/worktrees/%s", - real_path(get_git_common_dir()), name); + realpath.buf, name); /* * This is to keep resolve_ref() happy. We need a valid HEAD * or is_git_directory() will reject the directory. Any value which @@ -350,37 +403,37 @@ static int add_worktree(const char *path, const char *refname, */ strbuf_reset(&sb); strbuf_addf(&sb, "%s/HEAD", sb_repo.buf); - write_file(sb.buf, "%s", sha1_to_hex(null_sha1)); + write_file(sb.buf, "%s", oid_to_hex(&null_oid)); strbuf_reset(&sb); strbuf_addf(&sb, "%s/commondir", sb_repo.buf); write_file(sb.buf, "../.."); - argv_array_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf); - argv_array_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path); + strvec_pushf(&child_env, "%s=%s", GIT_DIR_ENVIRONMENT, sb_git.buf); + strvec_pushf(&child_env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, path); cp.git_cmd = 1; if (!is_branch) - argv_array_pushl(&cp.args, "update-ref", "HEAD", - oid_to_hex(&commit->object.oid), NULL); + strvec_pushl(&cp.args, "update-ref", "HEAD", + oid_to_hex(&commit->object.oid), NULL); else { - argv_array_pushl(&cp.args, "symbolic-ref", "HEAD", - symref.buf, NULL); + strvec_pushl(&cp.args, "symbolic-ref", "HEAD", + symref.buf, NULL); if (opts->quiet) - argv_array_push(&cp.args, "--quiet"); + strvec_push(&cp.args, "--quiet"); } - cp.env = child_env.argv; + cp.env = child_env.v; ret = run_command(&cp); if (ret) goto done; if (opts->checkout) { cp.argv = NULL; - argv_array_clear(&cp.args); - argv_array_pushl(&cp.args, "reset", "--hard", NULL); + strvec_clear(&cp.args); + strvec_pushl(&cp.args, "reset", "--hard", "--no-recurse-submodules", NULL); if (opts->quiet) - argv_array_push(&cp.args, "--quiet"); - cp.env = child_env.argv; + strvec_push(&cp.args, "--quiet"); + cp.env = child_env.v; ret = run_command(&cp); if (ret) goto done; @@ -412,20 +465,21 @@ done: cp.env = env; cp.argv = NULL; cp.trace2_hook_name = "post-checkout"; - argv_array_pushl(&cp.args, absolute_path(hook), - oid_to_hex(&null_oid), - oid_to_hex(&commit->object.oid), - "1", NULL); + strvec_pushl(&cp.args, absolute_path(hook), + oid_to_hex(&null_oid), + oid_to_hex(&commit->object.oid), + "1", NULL); ret = run_command(&cp); } } - argv_array_clear(&child_env); + strvec_clear(&child_env); strbuf_release(&sb); strbuf_release(&symref); strbuf_release(&sb_repo); strbuf_release(&sb_git); strbuf_release(&sb_name); + strbuf_release(&realpath); return ret; } @@ -501,7 +555,7 @@ static int add(int ac, const char **av, const char *prefix) N_("create a new branch")), OPT_STRING('B', NULL, &new_branch_force, N_("branch"), N_("create or reset a branch")), - OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")), + OPT_BOOL('d', "detach", &opts.detach, N_("detach HEAD at named commit")), OPT_BOOL(0, "checkout", &opts.checkout, N_("populate the new working tree")), OPT_BOOL(0, "lock", &opts.keep_locked, N_("keep the new working tree locked")), OPT__QUIET(&opts.quiet, N_("suppress progress reporting")), @@ -565,15 +619,15 @@ static int add(int ac, const char **av, const char *prefix) if (new_branch) { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; - argv_array_push(&cp.args, "branch"); + strvec_push(&cp.args, "branch"); if (new_branch_force) - argv_array_push(&cp.args, "--force"); + strvec_push(&cp.args, "--force"); if (opts.quiet) - argv_array_push(&cp.args, "--quiet"); - argv_array_push(&cp.args, new_branch); - argv_array_push(&cp.args, branch); + strvec_push(&cp.args, "--quiet"); + strvec_push(&cp.args, new_branch); + strvec_push(&cp.args, branch); if (opt_track) - argv_array_push(&cp.args, opt_track); + strvec_push(&cp.args, opt_track); if (run_command(&cp)) return -1; branch = new_branch; @@ -643,6 +697,23 @@ static void measure_widths(struct worktree **wt, int *abbrev, int *maxlen) } } +static int pathcmp(const void *a_, const void *b_) +{ + const struct worktree *const *a = a_; + const struct worktree *const *b = b_; + return fspathcmp((*a)->path, (*b)->path); +} + +static void pathsort(struct worktree **wt) +{ + int n = 0; + struct worktree **p = wt; + + while (*p++) + n++; + QSORT(wt, n, pathcmp); +} + static int list(int ac, const char **av, const char *prefix) { int porcelain = 0; @@ -656,9 +727,12 @@ static int list(int ac, const char **av, const char *prefix) if (ac) usage_with_options(worktree_usage, options); else { - struct worktree **worktrees = get_worktrees(GWT_SORT_LINKED); + struct worktree **worktrees = get_worktrees(); int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i; + /* sort worktrees by path but keep main worktree at top */ + pathsort(worktrees + 1); + if (!porcelain) measure_widths(worktrees, &abbrev, &path_maxlen); @@ -687,7 +761,7 @@ static int lock_worktree(int ac, const char **av, const char *prefix) if (ac != 1) usage_with_options(worktree_usage, options); - worktrees = get_worktrees(0); + worktrees = get_worktrees(); wt = find_worktree(worktrees, prefix, av[0]); if (!wt) die(_("'%s' is not a working tree"), av[0]); @@ -720,7 +794,7 @@ static int unlock_worktree(int ac, const char **av, const char *prefix) if (ac != 1) usage_with_options(worktree_usage, options); - worktrees = get_worktrees(0); + worktrees = get_worktrees(); wt = find_worktree(worktrees, prefix, av[0]); if (!wt) die(_("'%s' is not a working tree"), av[0]); @@ -794,7 +868,7 @@ static int move_worktree(int ac, const char **av, const char *prefix) strbuf_addstr(&dst, path); free(path); - worktrees = get_worktrees(0); + worktrees = get_worktrees(); wt = find_worktree(worktrees, prefix, av[0]); if (!wt) die(_("'%s' is not a working tree"), av[0]); @@ -809,8 +883,7 @@ static int move_worktree(int ac, const char **av, const char *prefix) strbuf_trim_trailing_dir_sep(&dst); strbuf_addstr(&dst, sep); } - if (file_exists(dst.buf)) - die(_("target '%s' already exists"), dst.buf); + check_candidate_path(dst.buf, force, worktrees, "move"); validate_no_submodules(wt); @@ -851,7 +924,6 @@ static int move_worktree(int ac, const char **av, const char *prefix) static void check_clean_worktree(struct worktree *wt, const char *original_path) { - struct argv_array child_env = ARGV_ARRAY_INIT; struct child_process cp; char buf[1]; int ret; @@ -862,15 +934,14 @@ static void check_clean_worktree(struct worktree *wt, */ validate_no_submodules(wt); - argv_array_pushf(&child_env, "%s=%s/.git", - GIT_DIR_ENVIRONMENT, wt->path); - argv_array_pushf(&child_env, "%s=%s", - GIT_WORK_TREE_ENVIRONMENT, wt->path); - memset(&cp, 0, sizeof(cp)); - argv_array_pushl(&cp.args, "status", - "--porcelain", "--ignore-submodules=none", - NULL); - cp.env = child_env.argv; + child_process_init(&cp); + strvec_pushf(&cp.env_array, "%s=%s/.git", + GIT_DIR_ENVIRONMENT, wt->path); + strvec_pushf(&cp.env_array, "%s=%s", + GIT_WORK_TREE_ENVIRONMENT, wt->path); + strvec_pushl(&cp.args, "status", + "--porcelain", "--ignore-submodules=none", + NULL); cp.git_cmd = 1; cp.dir = wt->path; cp.out = -1; @@ -880,7 +951,7 @@ static void check_clean_worktree(struct worktree *wt, original_path); ret = xread(cp.out, buf, sizeof(buf)); if (ret) - die(_("'%s' is dirty, use --force to delete it"), + die(_("'%s' contains modified or untracked files, use --force to delete it"), original_path); close(cp.out); ret = finish_command(&cp); @@ -921,7 +992,7 @@ static int remove_worktree(int ac, const char **av, const char *prefix) if (ac != 1) usage_with_options(worktree_usage, options); - worktrees = get_worktrees(0); + worktrees = get_worktrees(); wt = find_worktree(worktrees, prefix, av[0]); if (!wt) die(_("'%s' is not a working tree"), av[0]); @@ -957,6 +1028,34 @@ static int remove_worktree(int ac, const char **av, const char *prefix) return ret; } +static void report_repair(int iserr, const char *path, const char *msg, void *cb_data) +{ + if (!iserr) { + printf_ln(_("repair: %s: %s"), msg, path); + } else { + int *exit_status = (int *)cb_data; + fprintf_ln(stderr, _("error: %s: %s"), msg, path); + *exit_status = 1; + } +} + +static int repair(int ac, const char **av, const char *prefix) +{ + const char **p; + const char *self[] = { ".", NULL }; + struct option options[] = { + OPT_END() + }; + int rc = 0; + + ac = parse_options(ac, av, prefix, options, worktree_usage, 0); + repair_worktrees(report_repair, &rc); + p = ac > 0 ? av : self; + for (; *p; p++) + repair_worktree_at_path(*p, report_repair, &rc); + return rc; +} + int cmd_worktree(int ac, const char **av, const char *prefix) { struct option options[] = { @@ -983,5 +1082,7 @@ int cmd_worktree(int ac, const char **av, const char *prefix) return move_worktree(ac - 1, av + 1, prefix); if (!strcmp(av[1], "remove")) return remove_worktree(ac - 1, av + 1, prefix); + if (!strcmp(av[1], "repair")) + return repair(ac - 1, av + 1, prefix); usage_with_options(worktree_usage, options); } |