diff options
Diffstat (limited to 'builtin')
84 files changed, 6171 insertions, 2692 deletions
diff --git a/builtin/add.c b/builtin/add.c index ef6b619c45..f84372964c 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -32,7 +32,6 @@ static int add_renormalize; static int pathspec_file_nul; static int include_sparse; static const char *pathspec_from_file; -static int legacy_stash_p; /* support for the scripted `git stash` */ struct update_callback_data { int flags; @@ -142,8 +141,17 @@ int add_files_to_cache(const char *prefix, rev.diffopt.format_callback_data = &data; rev.diffopt.flags.override_submodule_config = 1; rev.max_count = 0; /* do not compare unmerged paths with stage #2 */ + + /* + * Use an ODB transaction to optimize adding multiple objects. + * This function is invoked from commands other than 'add', which + * may not have their own transaction active. + */ + begin_odb_transaction(); run_diff_files(&rev, DIFF_RACY_IS_MODIFIED); - clear_pathspec(&rev.prune_data); + end_odb_transaction(); + + release_revisions(&rev); return !!data.add_errors; } @@ -237,17 +245,12 @@ int run_add_interactive(const char *revision, const char *patch_mode, int use_builtin_add_i = git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1); - if (use_builtin_add_i < 0) { - int experimental; - if (!git_config_get_bool("add.interactive.usebuiltin", - &use_builtin_add_i)) - ; /* ok */ - else if (!git_config_get_bool("feature.experimental", &experimental) && - experimental) - use_builtin_add_i = 1; - } + if (use_builtin_add_i < 0 && + git_config_get_bool("add.interactive.usebuiltin", + &use_builtin_add_i)) + use_builtin_add_i = 1; - if (use_builtin_add_i == 1) { + if (use_builtin_add_i != 0) { enum add_p_mode mode; if (!patch_mode) @@ -302,15 +305,11 @@ int interactive_add(const char **argv, const char *prefix, int patch) static int edit_patch(int argc, const char **argv, const char *prefix) { char *file = git_pathdup("ADD_EDIT.patch"); - const char *apply_argv[] = { "apply", "--recount", "--cached", - NULL, NULL }; struct child_process child = CHILD_PROCESS_INIT; struct rev_info rev; int out; struct stat st; - apply_argv[3] = file; - git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ if (read_cache() < 0) @@ -338,12 +337,14 @@ static int edit_patch(int argc, const char **argv, const char *prefix) die(_("Empty patch. Aborted.")); child.git_cmd = 1; - child.argv = apply_argv; + strvec_pushl(&child.args, "apply", "--recount", "--cached", file, + NULL); if (run_command(&child)) die(_("Could not apply '%s'"), file); unlink(file); free(file); + release_revisions(&rev); return 0; } @@ -391,8 +392,6 @@ 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(), @@ -510,26 +509,15 @@ int cmd_add(int argc, const char **argv, const char *prefix) add_interactive = 1; if (add_interactive) { if (show_only) - die(_("--dry-run is incompatible with --interactive/--patch")); + die(_("options '%s' and '%s' cannot be used together"), "--dry-run", "--interactive/--patch"); if (pathspec_from_file) - die(_("--pathspec-from-file is incompatible with --interactive/--patch")); + die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--interactive/--patch"); exit(interactive_add(argv + 1, prefix, patch_interactive)); } - if (legacy_stash_p) { - struct pathspec pathspec; - - parse_pathspec(&pathspec, 0, - PATHSPEC_PREFER_FULL | - PATHSPEC_SYMLINK_LEADING_PATH | - PATHSPEC_PREFIX_ORIGIN, - prefix, argv); - - return run_add_interactive(NULL, "--patch=stash", &pathspec); - } if (edit_interactive) { if (pathspec_from_file) - die(_("--pathspec-from-file is incompatible with --edit")); + die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--edit"); return(edit_patch(argc, argv, prefix)); } argc--; @@ -541,10 +529,10 @@ int cmd_add(int argc, const char **argv, const char *prefix) addremove = 0; /* "-u" was given but not "-A" */ if (addremove && take_worktree_changes) - die(_("-A and -u are mutually incompatible")); + die(_("options '%s' and '%s' cannot be used together"), "-A", "-u"); if (!show_only && ignore_missing) - die(_("Option --ignore-missing can only be used together with --dry-run")); + die(_("the option '%s' requires '%s'"), "--ignore-missing", "--dry-run"); if (chmod_arg && ((chmod_arg[0] != '-' && chmod_arg[0] != '+') || chmod_arg[1] != 'x' || chmod_arg[2])) @@ -569,14 +557,14 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (pathspec_from_file) { if (pathspec.nr) - die(_("--pathspec-from-file is incompatible with pathspec arguments")); + die(_("'%s' and pathspec arguments cannot be used together"), "--pathspec-from-file"); 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")); + die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file"); } if (require_pathspec && pathspec.nr == 0) { @@ -687,7 +675,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) string_list_clear(&only_match_skip_worktree, 0); } - plug_bulk_checkin(); + begin_odb_transaction(); if (add_renormalize) exit_status |= renormalize_tracked_files(&pathspec, flags); @@ -699,7 +687,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (chmod_arg && pathspec.nr) exit_status |= chmod_pathspec(&pathspec, chmod_arg[0], show_only); - unplug_bulk_checkin(); + end_odb_transaction(); finish: if (write_locked_index(&the_index, &lock_file, diff --git a/builtin/am.c b/builtin/am.c index 8677ea2348..93bec62afa 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -34,6 +34,7 @@ #include "string-list.h" #include "packfile.h" #include "repository.h" +#include "pretty.h" /** * Returns the length of the first line of msg. @@ -87,6 +88,12 @@ enum show_patch_type { SHOW_PATCH_DIFF = 1, }; +enum empty_action { + STOP_ON_EMPTY_COMMIT = 0, /* output errors and stop in the middle of an am session */ + DROP_EMPTY_COMMIT, /* skip with a notice message, unless "--quiet" has been passed */ + KEEP_EMPTY_COMMIT, /* keep recording as empty commits */ +}; + struct am_state { /* state directory path */ char *dir; @@ -118,6 +125,7 @@ struct am_state { int message_id; int scissors; /* enum scissors_type */ int quoted_cr; /* enum quoted_cr_action */ + int empty_type; /* enum empty_action */ struct strvec git_apply_opts; const char *resolvemsg; int committer_date_is_author_date; @@ -178,6 +186,25 @@ static int am_option_parse_quoted_cr(const struct option *opt, return 0; } +static int am_option_parse_empty(const struct option *opt, + const char *arg, int unset) +{ + int *opt_value = opt->value; + + BUG_ON_OPT_NEG(unset); + + if (!strcmp(arg, "stop")) + *opt_value = STOP_ON_EMPTY_COMMIT; + else if (!strcmp(arg, "drop")) + *opt_value = DROP_EMPTY_COMMIT; + else if (!strcmp(arg, "keep")) + *opt_value = KEEP_EMPTY_COMMIT; + else + return error(_("invalid value for '%s': '%s'"), "--empty", arg); + + return 0; +} + /** * Returns path relative to the am_state directory. */ @@ -448,7 +475,7 @@ static int run_applypatch_msg_hook(struct am_state *state) int ret; assert(state->msg); - ret = run_hook_le(NULL, "applypatch-msg", am_path(state, "final-commit"), NULL); + ret = run_hooks_l("applypatch-msg", am_path(state, "final-commit"), NULL); if (!ret) { FREE_AND_NULL(state->msg); @@ -1126,6 +1153,12 @@ static void NORETURN die_user_resolve(const struct am_state *state) printf_ln(_("When you have resolved this problem, run \"%s --continue\"."), cmdline); printf_ln(_("If you prefer to skip this patch, run \"%s --skip\" instead."), cmdline); + + if (advice_enabled(ADVICE_AM_WORK_DIR) && + is_empty_or_missing_file(am_path(state, "patch")) && + !repo_index_has_changes(the_repository, NULL, NULL)) + printf_ln(_("To record the empty patch as an empty commit, run \"%s --allow-empty\"."), cmdline); + printf_ln(_("To restore the original branch and stop patching, run \"%s --abort\"."), cmdline); } @@ -1248,11 +1281,6 @@ static int parse_mail(struct am_state *state, const char *mail) goto finish; } - if (is_empty_or_missing_file(am_path(state, "patch"))) { - printf_ln(_("Patch is empty.")); - die_user_resolve(state); - } - strbuf_addstr(&msg, "\n\n"); strbuf_addbuf(&msg, &mi.log_message); strbuf_stripspace(&msg, 0); @@ -1369,6 +1397,7 @@ static void write_commit_patch(const struct am_state *state, struct commit *comm add_pending_object(&rev_info, &commit->object, ""); diff_setup_done(&rev_info.diffopt); log_tree_commit(&rev_info, commit); + release_revisions(&rev_info); } /** @@ -1401,6 +1430,7 @@ static void write_index_patch(const struct am_state *state) add_pending_object(&rev_info, &tree->object, ""); diff_setup_done(&rev_info.diffopt); run_diff_index(&rev_info, 1); + release_revisions(&rev_info); } /** @@ -1554,6 +1584,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa add_pending_oid(&rev_info, "HEAD", &our_tree, 0); diff_setup_done(&rev_info.diffopt); run_diff_index(&rev_info, 1); + release_revisions(&rev_info); } if (run_apply(state, index_path)) @@ -1609,7 +1640,7 @@ static void do_commit(const struct am_state *state) const char *reflog_msg, *author, *committer = NULL; struct strbuf sb = STRBUF_INIT; - if (run_hook_le(NULL, "pre-applypatch", NULL)) + if (run_hooks("pre-applypatch")) exit(1); if (write_cache_as_tree(&tree, 0, NULL)) @@ -1661,7 +1692,7 @@ static void do_commit(const struct am_state *state) fclose(fp); } - run_hook_le(NULL, "post-applypatch", NULL); + run_hooks("post-applypatch"); strbuf_release(&sb); } @@ -1763,6 +1794,7 @@ static void am_run(struct am_state *state, int resume) while (state->cur <= state->last) { const char *mail = am_path(state, msgnum(state)); int apply_status; + int to_keep; reset_ident_date(); @@ -1792,8 +1824,29 @@ static void am_run(struct am_state *state, int resume) if (state->interactive && do_interactive(state)) goto next; + to_keep = 0; + if (is_empty_or_missing_file(am_path(state, "patch"))) { + switch (state->empty_type) { + case DROP_EMPTY_COMMIT: + say(state, stdout, _("Skipping: %.*s"), linelen(state->msg), state->msg); + goto next; + break; + case KEEP_EMPTY_COMMIT: + to_keep = 1; + say(state, stdout, _("Creating an empty commit: %.*s"), + linelen(state->msg), state->msg); + break; + case STOP_ON_EMPTY_COMMIT: + printf_ln(_("Patch is empty.")); + die_user_resolve(state); + break; + } + } + if (run_applypatch_msg_hook(state)) exit(1); + if (to_keep) + goto commit; say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg); @@ -1827,6 +1880,7 @@ static void am_run(struct am_state *state, int resume) die_user_resolve(state); } +commit: do_commit(state); next: @@ -1856,19 +1910,24 @@ next: /** * Resume the current am session after patch application failure. The user did * all the hard work, and we do not have to do any patch application. Just - * trust and commit what the user has in the index and working tree. + * trust and commit what the user has in the index and working tree. If `allow_empty` + * is true, commit as an empty commit when index has not changed and lacking a patch. */ -static void am_resolve(struct am_state *state) +static void am_resolve(struct am_state *state, int allow_empty) { validate_resume_state(state); say(state, stdout, _("Applying: %.*s"), linelen(state->msg), state->msg); if (!repo_index_has_changes(the_repository, NULL, NULL)) { - printf_ln(_("No changes - did you forget to use 'git add'?\n" - "If there is nothing left to stage, chances are that something else\n" - "already introduced the same changes; you might want to skip this patch.")); - die_user_resolve(state); + if (allow_empty && is_empty_or_missing_file(am_path(state, "patch"))) { + printf_ln(_("No changes - recorded it as an empty commit.")); + } else { + printf_ln(_("No changes - did you forget to use 'git add'?\n" + "If there is nothing left to stage, chances are that something else\n" + "already introduced the same changes; you might want to skip this patch.")); + die_user_resolve(state); + } } if (unmerged_cache()) { @@ -2184,7 +2243,8 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int * when you add new options */ else - return error(_("Invalid value for --patch-format: %s"), arg); + return error(_("invalid value for '%s': '%s'"), + "--patch-format", arg); return 0; } @@ -2195,7 +2255,8 @@ enum resume_type { RESUME_SKIP, RESUME_ABORT, RESUME_QUIT, - RESUME_SHOW_PATCH + RESUME_SHOW_PATCH, + RESUME_ALLOW_EMPTY, }; struct resume_mode { @@ -2226,13 +2287,14 @@ static int parse_opt_show_current_patch(const struct option *opt, const char *ar break; } if (new_value >= ARRAY_SIZE(valid_modes)) - return error(_("Invalid value for --show-current-patch: %s"), arg); + return error(_("invalid value for '%s': '%s'"), + "--show-current-patch", 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]); + return error(_("options '%s=%s' and '%s=%s' " + "cannot be used together"), + "--show-current-patch", "--show-current-patch", arg, valid_modes[resume->sub_mode]); resume->mode = RESUME_SHOW_PATCH; resume->sub_mode = new_value; @@ -2348,6 +2410,9 @@ int cmd_am(int argc, const char **argv, const char *prefix) 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_CMDMODE(0, "allow-empty", &resume.mode, + N_("record the empty patch as an empty commit"), + RESUME_ALLOW_EMPTY), OPT_BOOL(0, "committer-date-is-author-date", &state.committer_date_is_author_date, N_("lie about committer date")), @@ -2357,6 +2422,9 @@ int cmd_am(int argc, const char **argv, const char *prefix) { OPTION_STRING, 'S', "gpg-sign", &state.sign_commit, N_("key-id"), N_("GPG-sign commits"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, + OPT_CALLBACK_F(STOP_ON_EMPTY_COMMIT, "empty", &state.empty_type, "{stop,drop,keep}", + N_("how to handle empty patches"), + PARSE_OPT_NONEG, am_option_parse_empty), OPT_HIDDEN_BOOL(0, "rebasing", &state.rebasing, N_("(internal use for git-rebase)")), OPT_END() @@ -2453,7 +2521,8 @@ int cmd_am(int argc, const char **argv, const char *prefix) am_run(&state, 1); break; case RESUME_RESOLVED: - am_resolve(&state); + case RESUME_ALLOW_EMPTY: + am_resolve(&state, resume.mode == RESUME_ALLOW_EMPTY ? 1 : 0); break; case RESUME_SKIP: am_skip(&state); diff --git a/builtin/apply.c b/builtin/apply.c index 3f099b9605..555219de40 100644 --- a/builtin/apply.c +++ b/builtin/apply.c @@ -1,7 +1,6 @@ #include "cache.h" #include "builtin.h" #include "parse-options.h" -#include "lockfile.h" #include "apply.h" static const char * const apply_usage[] = { diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 28a2e6a575..8a052c7111 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -22,15 +22,15 @@ static GIT_PATH_FUNC(git_path_bisect_run, "BISECT_RUN") static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --bisect-reset [<commit>]"), - N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"), + "git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]", 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"), + "git bisect--helper --bisect-next", N_("git bisect--helper --bisect-state (bad|new) [<rev>]"), N_("git bisect--helper --bisect-state (good|old) [<rev>...]"), N_("git bisect--helper --bisect-replay <filename>"), N_("git bisect--helper --bisect-skip [(<rev>|<range>)...]"), - N_("git bisect--helper --bisect-visualize"), + "git bisect--helper --bisect-visualize", N_("git bisect--helper --bisect-run <cmd>..."), NULL }; @@ -329,12 +329,12 @@ static int check_and_set_terms(struct bisect_terms *terms, const char *cmd) return 0; } -static int mark_good(const char *refname, const struct object_id *oid, - int flag, void *cb_data) +static int inc_nr(const char *refname, const struct object_id *oid, + int flag, void *cb_data) { - int *m_good = (int *)cb_data; - *m_good = 0; - return 1; + unsigned int *nr = (unsigned int *)cb_data; + (*nr)++; + return 0; } static const char need_bad_and_good_revision_warning[] = @@ -384,23 +384,64 @@ static int decide_next(const struct bisect_terms *terms, vocab_good, vocab_bad, vocab_good, vocab_bad); } -static int bisect_next_check(const struct bisect_terms *terms, - const char *current_term) +static void bisect_status(struct bisect_state *state, + const struct bisect_terms *terms) { - int missing_good = 1, missing_bad = 1; char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad); char *good_glob = xstrfmt("%s-*", terms->term_good); if (ref_exists(bad_ref)) - missing_bad = 0; + state->nr_bad = 1; - for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/", - (void *) &missing_good); + for_each_glob_ref_in(inc_nr, good_glob, "refs/bisect/", + (void *) &state->nr_good); free(good_glob); free(bad_ref); +} + +__attribute__((format (printf, 1, 2))) +static void bisect_log_printf(const char *fmt, ...) +{ + struct strbuf buf = STRBUF_INIT; + va_list ap; + + va_start(ap, fmt); + strbuf_vaddf(&buf, fmt, ap); + va_end(ap); + + printf("%s", buf.buf); + append_to_file(git_path_bisect_log(), "# %s", buf.buf); + + strbuf_release(&buf); +} + +static void bisect_print_status(const struct bisect_terms *terms) +{ + struct bisect_state state = { 0 }; - return decide_next(terms, current_term, missing_good, missing_bad); + bisect_status(&state, terms); + + /* If we had both, we'd already be started, and shouldn't get here. */ + if (state.nr_good && state.nr_bad) + return; + + if (!state.nr_good && !state.nr_bad) + bisect_log_printf(_("status: waiting for both good and bad commits\n")); + else if (state.nr_good) + bisect_log_printf(Q_("status: waiting for bad commit, %d good commit known\n", + "status: waiting for bad commit, %d good commits known\n", + state.nr_good), state.nr_good); + else + bisect_log_printf(_("status: waiting for good commit(s), bad commit known\n")); +} + +static int bisect_next_check(const struct bisect_terms *terms, + const char *current_term) +{ + struct bisect_state state = { 0 }; + bisect_status(&state, terms); + return decide_next(terms, current_term, !state.nr_good, !state.nr_bad); } static int get_terms(struct bisect_terms *terms) @@ -433,7 +474,7 @@ static int bisect_terms(struct bisect_terms *terms, const char *option) if (get_terms(terms)) return error(_("no terms defined")); - if (option == NULL) { + if (!option) { printf(_("Your current terms are %s for the old state\n" "and %s for the new state.\n"), terms->term_good, terms->term_bad); @@ -555,6 +596,7 @@ static int bisect_skipped_commits(struct bisect_terms *terms) reset_revision_walk(); strbuf_release(&commit_name); + release_revisions(&revs); fclose(fp); return 0; } @@ -606,8 +648,10 @@ static enum bisect_error bisect_next(struct bisect_terms *terms, const char *pre static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix) { - if (bisect_next_check(terms, NULL)) + if (bisect_next_check(terms, NULL)) { + bisect_print_status(terms); return BISECT_OK; + } return bisect_next(terms, prefix); } @@ -1041,6 +1085,7 @@ static enum bisect_error bisect_skip(struct bisect_terms *terms, const char **ar oid_to_hex(&commit->object.oid)); reset_revision_walk(); + release_revisions(&revs); } else { strvec_push(&argv_state, argv[i]); } @@ -1089,14 +1134,52 @@ static int bisect_visualize(struct bisect_terms *terms, const char **argv, int a return res; } +static int get_first_good(const char *refname, const struct object_id *oid, + int flag, void *cb_data) +{ + oidcpy(cb_data, oid); + return 1; +} + +static int verify_good(const struct bisect_terms *terms, + const char **quoted_argv) +{ + int rc; + enum bisect_error res; + struct object_id good_rev; + struct object_id current_rev; + char *good_glob = xstrfmt("%s-*", terms->term_good); + int no_checkout = ref_exists("BISECT_HEAD"); + + for_each_glob_ref_in(get_first_good, good_glob, "refs/bisect/", + &good_rev); + free(good_glob); + + if (read_ref(no_checkout ? "BISECT_HEAD" : "HEAD", ¤t_rev)) + return -1; + + res = bisect_checkout(&good_rev, no_checkout); + if (res != BISECT_OK) + return -1; + + printf(_("running %s\n"), quoted_argv[0]); + rc = run_command_v_opt(quoted_argv, RUN_USING_SHELL); + + res = bisect_checkout(¤t_rev, no_checkout); + if (res != BISECT_OK) + return -1; + + return rc; +} + static int bisect_run(struct bisect_terms *terms, const char **argv, int argc) { int res = BISECT_OK; struct strbuf command = STRBUF_INIT; - struct strvec args = STRVEC_INIT; struct strvec run_args = STRVEC_INIT; const char *new_state; int temporary_stdout_fd, saved_stdout; + int is_first_run = 1; if (bisect_next_check(terms, NULL)) return BISECT_FAILED; @@ -1111,16 +1194,37 @@ static int bisect_run(struct bisect_terms *terms, const char **argv, int argc) strvec_push(&run_args, command.buf); while (1) { - strvec_clear(&args); - printf(_("running %s\n"), command.buf); res = run_command_v_opt(run_args.v, RUN_USING_SHELL); + /* + * Exit code 126 and 127 can either come from the shell + * if it was unable to execute or even find the script, + * or from the script itself. Check with a known-good + * revision to avoid trashing the bisect run due to a + * missing or non-executable script. + */ + if (is_first_run && (res == 126 || res == 127)) { + int rc = verify_good(terms, run_args.v); + is_first_run = 0; + if (rc < 0) { + error(_("unable to verify '%s' on good" + " revision"), command.buf); + res = BISECT_FAILED; + break; + } + if (rc == res) { + error(_("bogus exit code %d for good revision"), + rc); + res = BISECT_FAILED; + break; + } + } + if (res < 0 || 128 <= res) { error(_("bisect run failed: exit code %d from" " '%s' is < 0 or >= 128"), res, command.buf); - strbuf_release(&command); - return res; + break; } if (res == 125) @@ -1132,8 +1236,10 @@ static int bisect_run(struct bisect_terms *terms, const char **argv, int argc) temporary_stdout_fd = open(git_path_bisect_run(), O_CREAT | O_WRONLY | O_TRUNC, 0666); - if (temporary_stdout_fd < 0) - return error_errno(_("cannot open file '%s' for writing"), git_path_bisect_run()); + if (temporary_stdout_fd < 0) { + res = error_errno(_("cannot open file '%s' for writing"), git_path_bisect_run()); + break; + } fflush(stdout); saved_stdout = dup(1); @@ -1158,16 +1264,16 @@ static int bisect_run(struct bisect_terms *terms, const char **argv, int argc) res = BISECT_OK; } else if (res) { error(_("bisect run failed: 'git bisect--helper --bisect-state" - " %s' exited with error code %d"), args.v[0], res); + " %s' exited with error code %d"), new_state, res); } else { continue; } - - strbuf_release(&command); - strvec_clear(&args); - strvec_clear(&run_args); - return res; + break; } + + strbuf_release(&command); + strvec_clear(&run_args); + return res; } int cmd_bisect__helper(int argc, const char **argv, const char *prefix) @@ -1209,7 +1315,7 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "bisect-visualize", &cmdmode, N_("visualize the bisection"), BISECT_VISUALIZE), OPT_CMDMODE(0, "bisect-run", &cmdmode, - N_("use <cmd>... to automatically bisect."), BISECT_RUN), + N_("use <cmd>... to automatically bisect"), BISECT_RUN), OPT_BOOL(0, "no-log", &nolog, N_("no log for BISECT_WRITE")), OPT_END() diff --git a/builtin/blame.c b/builtin/blame.c index f9ee3f8c68..02e39420b6 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -721,8 +721,8 @@ static int git_blame_config(const char *var, const char *value, void *cb) } if (!strcmp(var, "color.blame.repeatedlines")) { if (color_parse_mem(value, strlen(value), repeated_meta_color)) - warning(_("invalid color '%s' in color.blame.repeatedLines"), - value); + warning(_("invalid value for '%s': '%s'"), + "color.blame.repeatedLines", value); return 0; } if (!strcmp(var, "color.blame.highlightrecent")) { @@ -739,7 +739,8 @@ static int git_blame_config(const char *var, const char *value, void *cb) coloring_mode &= ~(OUTPUT_COLOR_LINE | OUTPUT_SHOW_AGE_WITH_COLOR); } else { - warning(_("invalid value for blame.coloring")); + warning(_("invalid value for '%s': '%s'"), + "blame.coloring", value); return 0; } } @@ -897,6 +898,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) unsigned int range_i; long anchor; const int hexsz = the_hash_algo->hexsz; + long num_lines = 0; setup_default_color_by_age(); git_config(git_blame_config, &output_option); @@ -934,11 +936,15 @@ int cmd_blame(int argc, const char **argv, const char *prefix) parse_revision_opt(&revs, &ctx, options, blame_opt_usage); } parse_done: + revision_opts_finish(&revs); no_whole_file_rename = !revs.diffopt.flags.follow_renames; xdl_opts |= revs.diffopt.xdl_opts & XDF_INDENT_HEURISTIC; revs.diffopt.flags.follow_renames = 0; argc = parse_options_end(&ctx); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + if (incremental || (output_option & OUTPUT_PORCELAIN)) { if (show_progress > 0) die(_("--progress can't be used with --incremental or porcelain formats")); @@ -1124,7 +1130,10 @@ parse_done: for (range_i = ranges.nr; range_i > 0; --range_i) { const struct range *r = &ranges.ranges[range_i - 1]; ent = blame_entry_prepend(ent, r->start, r->end, o); + num_lines += (r->end - r->start); } + if (!num_lines) + num_lines = sb.num_lines; o->suspects = ent; prio_queue_put(&sb.commits, o->commit); @@ -1153,7 +1162,7 @@ parse_done: sb.found_guilty_entry = &found_guilty_entry; sb.found_guilty_entry_data = π if (show_progress) - pi.progress = start_delayed_progress(_("Blaming lines"), sb.num_lines); + pi.progress = start_delayed_progress(_("Blaming lines"), num_lines); assign_blame(&sb, opt); @@ -1162,7 +1171,7 @@ parse_done: if (!incremental) setup_pager(); else - return 0; + goto cleanup; blame_sort_final(&sb); @@ -1196,6 +1205,8 @@ parse_done: printf("num commits: %d\n", sb.num_commits); } +cleanup: cleanup_scoreboard(&sb); + release_revisions(&revs); return 0; } diff --git a/builtin/branch.c b/builtin/branch.c index 7a1d1eeb07..5d00d0b8d3 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -27,7 +27,8 @@ static const char * const builtin_branch_usage[] = { N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"), - N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"), + N_("git branch [<options>] [-f] [--recurse-submodules] <branch-name> [<start-point>]"), + N_("git branch [<options>] [-l] [<pattern>...]"), N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."), N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"), N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"), @@ -38,6 +39,8 @@ static const char * const builtin_branch_usage[] = { static const char *head; static struct object_id head_oid; +static int recurse_submodules = 0; +static int submodule_propagate_branches = 0; static int branch_use_color = -1; static char branch_colors[][COLOR_MAXLEN] = { @@ -77,12 +80,11 @@ define_list_config_array(color_branch_slots); static int git_branch_config(const char *var, const char *value, void *cb) { const char *slot_name; - struct ref_sorting **sorting_tail = (struct ref_sorting **)cb; if (!strcmp(var, "branch.sort")) { if (!value) return config_error_nonbool(var); - parse_ref_sorting(sorting_tail, value); + string_list_append(cb, value); return 0; } @@ -100,6 +102,15 @@ static int git_branch_config(const char *var, const char *value, void *cb) return config_error_nonbool(var); return color_parse(value, branch_colors[slot]); } + if (!strcmp(var, "submodule.recurse")) { + recurse_submodules = git_config_bool(var, value); + return 0; + } + if (!strcasecmp(var, "submodule.propagateBranches")) { + submodule_propagate_branches = git_config_bool(var, value); + return 0; + } + return git_color_default_config(var, value, cb); } @@ -193,6 +204,7 @@ static void delete_branch_config(const char *branchname) static int delete_branches(int argc, const char **argv, int force, int kinds, int quiet) { + struct worktree **worktrees; struct commit *head_rev = NULL; struct object_id oid; char *name = NULL; @@ -229,6 +241,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, if (!head_rev) die(_("Couldn't look up commit object for HEAD")); } + + worktrees = get_worktrees(); + for (i = 0; i < argc; i++, strbuf_reset(&bname)) { char *target = NULL; int flags = 0; @@ -239,7 +254,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, if (kinds == FILTER_REFS_BRANCHES) { const struct worktree *wt = - find_shared_symref("HEAD", name); + find_shared_symref(worktrees, "HEAD", name); if (wt) { error(_("Cannot delete branch '%s' " "checked out at '%s'"), @@ -300,6 +315,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, free(name); strbuf_release(&bname); + free_worktrees(worktrees); return ret; } @@ -617,15 +633,18 @@ static int edit_branch_description(const char *branch_name) int cmd_branch(int argc, const char **argv, const char *prefix) { - int delete = 0, rename = 0, copy = 0, force = 0, list = 0; - int show_current = 0; - int reflog = 0, edit_description = 0; - int quiet = 0, unset_upstream = 0; + /* possible actions */ + int delete = 0, rename = 0, copy = 0, list = 0, + unset_upstream = 0, show_current = 0, edit_description = 0; const char *new_upstream = NULL; + int noncreate_actions = 0; + /* possible options */ + int reflog = 0, quiet = 0, icase = 0, force = 0, + recurse_submodules_explicit = 0; enum branch_track track; struct ref_filter filter; - int icase = 0; - static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; + static struct ref_sorting *sorting; + struct string_list sorting_options = STRING_LIST_INIT_DUP; struct ref_format format = REF_FORMAT_INIT; struct option options[] = { @@ -633,8 +652,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT__VERBOSE(&filter.verbose, N_("show hash and subject, give twice for upstream branch")), OPT__QUIET(&quiet, N_("suppress informational messages")), - OPT_SET_INT('t', "track", &track, N_("set up tracking mode (see git-pull(1))"), - BRANCH_TRACK_EXPLICIT), + OPT_CALLBACK_F('t', "track", &track, "(direct|inherit)", + N_("set branch tracking configuration"), + PARSE_OPT_OPTARG, + parse_opt_tracking_mode), 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")), @@ -666,10 +687,11 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_MERGED(&filter, N_("print only branches that are merged")), 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), + OPT_REF_SORT(&sorting_options), 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_BOOL(0, "recurse-submodules", &recurse_submodules_explicit, N_("recurse through submodules")), OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")), OPT_END(), }; @@ -683,7 +705,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_branch_usage, options); - git_config(git_branch_config, sorting_tail); + git_config(git_branch_config, &sorting_options); track = git_branch_track; @@ -706,10 +728,23 @@ int cmd_branch(int argc, const char **argv, const char *prefix) filter.reachable_from || filter.unreachable_from || filter.points_at.nr) list = 1; - if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current + - list + edit_description + unset_upstream > 1) + noncreate_actions = !!delete + !!rename + !!copy + !!new_upstream + + !!show_current + !!list + !!edit_description + + !!unset_upstream; + if (noncreate_actions > 1) usage_with_options(builtin_branch_usage, options); + if (recurse_submodules_explicit) { + if (!submodule_propagate_branches) + die(_("branch with --recurse-submodules can only be used if submodule.propagateBranches is enabled")); + if (noncreate_actions) + die(_("--recurse-submodules can only be used to create branches")); + } + + recurse_submodules = + (recurse_submodules || recurse_submodules_explicit) && + submodule_propagate_branches; + if (filter.abbrev == -1) filter.abbrev = DEFAULT_ABBREV; filter.ignore_case = icase; @@ -717,7 +752,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) finalize_colopts(&colopts, -1); if (filter.verbose) { if (explicitly_enable_column(colopts)) - die(_("--column and --verbose are incompatible")); + die(_("options '%s' and '%s' cannot be used together"), "--column", "--verbose"); colopts = 0; } @@ -749,8 +784,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) * local branches 'refs/heads/...' and finally remote-tracking * branches 'refs/remotes/...'. */ - if (!sorting) - sorting = ref_default_sorting(); + sorting = ref_sorting_options(&sorting_options); ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase); ref_sorting_set_sort_flags_all( sorting, REF_SORTING_DETACHED_HEAD_FIRST, 1); @@ -822,12 +856,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (!ref_exists(branch->refname)) die(_("branch '%s' does not exist"), branch->name); - /* - * create_branch takes care of setting up the tracking - * info and making sure new_upstream is correct - */ - create_branch(the_repository, branch->name, new_upstream, - 0, 0, 0, quiet, BRANCH_TRACK_OVERRIDE); + dwim_and_setup_tracking(the_repository, branch->name, + new_upstream, BRANCH_TRACK_OVERRIDE, + quiet); } else if (unset_upstream) { struct branch *branch = branch_get(argv[0]); struct strbuf buf = STRBUF_INIT; @@ -851,7 +882,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix) strbuf_addf(&buf, "branch.%s.merge", branch->name); git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE); strbuf_release(&buf); - } else if (argc > 0 && argc <= 2) { + } else if (!noncreate_actions && argc > 0 && argc <= 2) { + const char *branch_name = argv[0]; + const char *start_name = argc == 2 ? argv[1] : head; + if (filter.kind != FILTER_REFS_BRANCHES) die(_("The -a, and -r, options to 'git branch' do not take a branch name.\n" "Did you mean to use: -a|-r --list <pattern>?")); @@ -859,10 +893,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (track == BRANCH_TRACK_OVERRIDE) die(_("the '--set-upstream' option is no longer supported. Please use '--track' or '--set-upstream-to' instead.")); - create_branch(the_repository, - argv[0], (argc == 2) ? argv[1] : head, - force, 0, reflog, quiet, track); - + if (recurse_submodules) { + create_branches_recursively(the_repository, branch_name, + start_name, NULL, force, + reflog, quiet, track, 0); + return 0; + } + create_branch(the_repository, branch_name, start_name, force, 0, + reflog, quiet, track, 0); } else usage_with_options(builtin_branch_usage, options); diff --git a/builtin/bundle.c b/builtin/bundle.c index 5a85d7cd0f..2adad545a2 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -93,6 +93,7 @@ static int cmd_bundle_create(int argc, const char **argv, const char *prefix) { if (!startup_info->have_repository) die(_("Need a repository to create a bundle.")); ret = !!create_bundle(the_repository, bundle_file, argc, argv, &pack_opts, version); + strvec_clear(&pack_opts); free(bundle_file); return ret; } diff --git a/builtin/cat-file.c b/builtin/cat-file.c index 86fc03242b..50cf38999d 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -17,14 +17,20 @@ #include "object-store.h" #include "promisor-remote.h" +enum batch_mode { + BATCH_MODE_CONTENTS, + BATCH_MODE_INFO, + BATCH_MODE_QUEUE_AND_DISPATCH, +}; + struct batch_options { int enabled; int follow_symlinks; - int print_contents; + enum batch_mode batch_mode; int buffer_output; int all_objects; int unordered; - int cmdmode; /* may be 'w' or 'c' for --filters or --textconv */ + int transform_mode; /* may be 'w' or 'c' for --filters or --textconv */ const char *format; }; @@ -73,14 +79,17 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, struct object_info oi = OBJECT_INFO_INIT; struct strbuf sb = STRBUF_INIT; unsigned flags = OBJECT_INFO_LOOKUP_REPLACE; + unsigned get_oid_flags = GET_OID_RECORD_PATH | GET_OID_ONLY_TO_DIE; const char *path = force_path; + const int opt_cw = (opt == 'c' || opt == 'w'); + if (!path && opt_cw) + get_oid_flags |= GET_OID_REQUIRE_PATH; if (unknown_type) flags |= OBJECT_INFO_ALLOW_UNKNOWN_TYPE; - if (get_oid_with_context(the_repository, obj_name, - GET_OID_RECORD_PATH, - &oid, &obj_context)) + if (get_oid_with_context(the_repository, obj_name, get_oid_flags, &oid, + &obj_context)) die("Not a valid object name %s", obj_name); if (!path) @@ -112,9 +121,6 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, return !has_object_file(&oid); case 'w': - if (!path) - die("git cat-file --filters %s: <object> must be " - "<sha1:path>", obj_name); if (filter_object(path, obj_context.mode, &oid, &buf, &size)) @@ -122,10 +128,6 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, break; case 'c': - if (!path) - die("git cat-file --textconv %s: <object> must be <sha1:path>", - obj_name); - if (textconv_object(the_repository, path, obj_context.mode, &oid, 1, &buf, &size)) break; @@ -154,7 +156,10 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, break; case 0: - if (type_from_string(exp_type) == OBJ_BLOB) { + { + enum object_type exp_type_id = type_from_string(exp_type); + + if (exp_type_id == OBJ_BLOB) { struct object_id blob_oid; if (oid_object_info(the_repository, &oid, NULL) == OBJ_TAG) { char *buffer = read_object_file(&oid, &type, @@ -176,10 +181,10 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name, * fall-back to the usual case. */ } - buf = read_object_with_reference(the_repository, - &oid, exp_type, &size, NULL); + buf = read_object_with_reference(the_repository, &oid, + exp_type_id, &size, NULL); break; - + } default: die("git cat-file: unknown option: %s", exp_type); } @@ -306,19 +311,19 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d if (data->type == OBJ_BLOB) { if (opt->buffer_output) fflush(stdout); - if (opt->cmdmode) { + if (opt->transform_mode) { char *contents; unsigned long size; if (!data->rest) die("missing path for '%s'", oid_to_hex(oid)); - if (opt->cmdmode == 'w') { + if (opt->transform_mode == 'w') { if (filter_object(data->rest, 0100644, oid, &contents, &size)) die("could not convert '%s' %s", oid_to_hex(oid), data->rest); - } else if (opt->cmdmode == 'c') { + } else if (opt->transform_mode == 'c') { enum object_type type; if (!textconv_object(the_repository, data->rest, 0100644, oid, @@ -330,7 +335,7 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d die("could not convert '%s' %s", oid_to_hex(oid), data->rest); } else - BUG("invalid cmdmode: %c", opt->cmdmode); + BUG("invalid transform_mode: %c", opt->transform_mode); batch_write(opt, contents, size); free(contents); } else { @@ -355,6 +360,13 @@ static void print_object_or_die(struct batch_options *opt, struct expand_data *d } } +static void print_default_format(struct strbuf *scratch, struct expand_data *data) +{ + strbuf_addf(scratch, "%s %s %"PRIuMAX"\n", oid_to_hex(&data->oid), + type_name(data->type), + (uintmax_t)data->size); +} + /* * If "pack" is non-NULL, then "offset" is the byte offset within the pack from * which the object may be accessed (though note that we may also rely on @@ -386,11 +398,17 @@ static void batch_object_write(const char *obj_name, } strbuf_reset(scratch); - strbuf_expand(scratch, opt->format, expand_format, data); - strbuf_addch(scratch, '\n'); + + if (!opt->format) { + print_default_format(scratch, data); + } else { + strbuf_expand(scratch, opt->format, expand_format, data); + strbuf_addch(scratch, '\n'); + } + batch_write(opt, scratch->buf, scratch->len); - if (opt->print_contents) { + if (opt->batch_mode == BATCH_MODE_CONTENTS) { print_object_or_die(opt, data); batch_write(opt, "\n", 1); } @@ -512,6 +530,137 @@ static int batch_unordered_packed(const struct object_id *oid, data); } +typedef void (*parse_cmd_fn_t)(struct batch_options *, const char *, + struct strbuf *, struct expand_data *); + +struct queued_cmd { + parse_cmd_fn_t fn; + char *line; +}; + +static void parse_cmd_contents(struct batch_options *opt, + const char *line, + struct strbuf *output, + struct expand_data *data) +{ + opt->batch_mode = BATCH_MODE_CONTENTS; + batch_one_object(line, output, opt, data); +} + +static void parse_cmd_info(struct batch_options *opt, + const char *line, + struct strbuf *output, + struct expand_data *data) +{ + opt->batch_mode = BATCH_MODE_INFO; + batch_one_object(line, output, opt, data); +} + +static void dispatch_calls(struct batch_options *opt, + struct strbuf *output, + struct expand_data *data, + struct queued_cmd *cmd, + int nr) +{ + int i; + + if (!opt->buffer_output) + die(_("flush is only for --buffer mode")); + + for (i = 0; i < nr; i++) + cmd[i].fn(opt, cmd[i].line, output, data); + + fflush(stdout); +} + +static void free_cmds(struct queued_cmd *cmd, size_t *nr) +{ + size_t i; + + for (i = 0; i < *nr; i++) + FREE_AND_NULL(cmd[i].line); + + *nr = 0; +} + + +static const struct parse_cmd { + const char *name; + parse_cmd_fn_t fn; + unsigned takes_args; +} commands[] = { + { "contents", parse_cmd_contents, 1}, + { "info", parse_cmd_info, 1}, + { "flush", NULL, 0}, +}; + +static void batch_objects_command(struct batch_options *opt, + struct strbuf *output, + struct expand_data *data) +{ + struct strbuf input = STRBUF_INIT; + struct queued_cmd *queued_cmd = NULL; + size_t alloc = 0, nr = 0; + + while (!strbuf_getline(&input, stdin)) { + int i; + const struct parse_cmd *cmd = NULL; + const char *p = NULL, *cmd_end; + struct queued_cmd call = {0}; + + if (!input.len) + die(_("empty command in input")); + if (isspace(*input.buf)) + die(_("whitespace before command: '%s'"), input.buf); + + for (i = 0; i < ARRAY_SIZE(commands); i++) { + if (!skip_prefix(input.buf, commands[i].name, &cmd_end)) + continue; + + cmd = &commands[i]; + if (cmd->takes_args) { + if (*cmd_end != ' ') + die(_("%s requires arguments"), + commands[i].name); + + p = cmd_end + 1; + } else if (*cmd_end) { + die(_("%s takes no arguments"), + commands[i].name); + } + + break; + } + + if (!cmd) + die(_("unknown command: '%s'"), input.buf); + + if (!strcmp(cmd->name, "flush")) { + dispatch_calls(opt, output, data, queued_cmd, nr); + free_cmds(queued_cmd, &nr); + } else if (!opt->buffer_output) { + cmd->fn(opt, p, output, data); + } else { + ALLOC_GROW(queued_cmd, nr + 1, alloc); + call.fn = cmd->fn; + call.line = xstrdup_or_null(p); + queued_cmd[nr++] = call; + } + } + + if (opt->buffer_output && + nr && + !git_env_bool("GIT_TEST_CAT_FILE_NO_FLUSH_ON_EXIT", 0)) { + dispatch_calls(opt, output, data, queued_cmd, nr); + free_cmds(queued_cmd, &nr); + } + + free(queued_cmd); + strbuf_release(&input); +} + +#define DEFAULT_FORMAT "%(objectname) %(objecttype) %(objectsize)" + static int batch_objects(struct batch_options *opt) { struct strbuf input = STRBUF_INIT; @@ -520,9 +669,6 @@ static int batch_objects(struct batch_options *opt) int save_warning; int retval = 0; - if (!opt->format) - opt->format = "%(objectname) %(objecttype) %(objectsize)"; - /* * Expand once with our special mark_query flag, which will prime the * object_info to be handed to oid_object_info_extended for each @@ -530,17 +676,22 @@ static int batch_objects(struct batch_options *opt) */ memset(&data, 0, sizeof(data)); data.mark_query = 1; - strbuf_expand(&output, opt->format, expand_format, &data); + strbuf_expand(&output, + opt->format ? opt->format : DEFAULT_FORMAT, + expand_format, + &data); data.mark_query = 0; strbuf_release(&output); - if (opt->cmdmode) + if (opt->transform_mode) data.split_on_whitespace = 1; + if (opt->format && !strcmp(opt->format, DEFAULT_FORMAT)) + opt->format = NULL; /* * If we are printing out the object, then always fill in the type, * since we will want to decide whether or not to stream. */ - if (opt->print_contents) + if (opt->batch_mode == BATCH_MODE_CONTENTS) data.info.typep = &data.type; if (opt->all_objects) { @@ -594,6 +745,11 @@ static int batch_objects(struct batch_options *opt) save_warning = warn_on_object_refname_ambiguity; warn_on_object_refname_ambiguity = 0; + if (opt->batch_mode == BATCH_MODE_QUEUE_AND_DISPATCH) { + batch_objects_command(opt, &output, &data); + goto cleanup; + } + while (strbuf_getline(&input, stdin) != EOF) { if (data.split_on_whitespace) { /* @@ -612,18 +768,13 @@ static int batch_objects(struct batch_options *opt) batch_one_object(input.buf, &output, opt, &data); } + cleanup: strbuf_release(&input); strbuf_release(&output); warn_on_object_refname_ambiguity = save_warning; return retval; } -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[=<format>] | --batch-check[=<format>]) [--follow-symlinks] [--textconv | --filters]"), - NULL -}; - static int git_cat_file_config(const char *var, const char *value, void *cb) { if (userdiff_config(var, value) < 0) @@ -645,7 +796,16 @@ static int batch_option_callback(const struct option *opt, } bo->enabled = 1; - bo->print_contents = !strcmp(opt->long_name, "batch"); + + if (!strcmp(opt->long_name, "batch")) + bo->batch_mode = BATCH_MODE_CONTENTS; + else if (!strcmp(opt->long_name, "batch-check")) + bo->batch_mode = BATCH_MODE_INFO; + else if (!strcmp(opt->long_name, "batch-command")) + bo->batch_mode = BATCH_MODE_QUEUE_AND_DISPATCH; + else + BUG("%s given to batch-option-callback", opt->long_name); + bo->format = arg; return 0; @@ -654,90 +814,142 @@ static int batch_option_callback(const struct option *opt, int cmd_cat_file(int argc, const char **argv, const char *prefix) { int opt = 0; + int opt_cw = 0; + int opt_epts = 0; const char *exp_type = NULL, *obj_name = NULL; struct batch_options batch = {0}; int unknown_type = 0; + const char * const usage[] = { + N_("git cat-file <type> <object>"), + N_("git cat-file (-e | -p) <object>"), + N_("git cat-file (-t | -s) [--allow-unknown-type] <object>"), + N_("git cat-file (--batch | --batch-check | --batch-command) [--batch-all-objects]\n" + " [--buffer] [--follow-symlinks] [--unordered]\n" + " [--textconv | --filters]"), + N_("git cat-file (--textconv | --filters)\n" + " [<rev>:<path|tree-ish> | --path=<path|tree-ish> <rev>]"), + NULL + }; const struct option options[] = { - OPT_GROUP(N_("<type> can be one of: blob, tree, commit, tag")), - OPT_CMDMODE('t', NULL, &opt, N_("show object type"), 't'), - OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'), + /* Simple queries */ + OPT_GROUP(N_("Check object existence or emit object contents")), OPT_CMDMODE('e', NULL, &opt, - N_("exit with zero when there's no error"), 'e'), - OPT_CMDMODE('p', NULL, &opt, N_("pretty-print object's content"), 'p'), - OPT_CMDMODE(0, "textconv", &opt, - N_("for blob objects, run textconv on object's content"), 'c'), - OPT_CMDMODE(0, "filters", &opt, - N_("for blob objects, run filters on object's content"), 'w'), - OPT_STRING(0, "path", &force_path, N_("blob"), - N_("use a specific path for --textconv/--filters")), + N_("check if <object> exists"), 'e'), + OPT_CMDMODE('p', NULL, &opt, N_("pretty-print <object> content"), 'p'), + + OPT_GROUP(N_("Emit [broken] object attributes")), + OPT_CMDMODE('t', NULL, &opt, N_("show object type (one of 'blob', 'tree', 'commit', 'tag', ...)"), 't'), + OPT_CMDMODE('s', NULL, &opt, N_("show object size"), 's'), 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")), - OPT_CALLBACK_F(0, "batch", &batch, "format", - N_("show info and content of objects fed from the standard input"), + /* Batch mode */ + OPT_GROUP(N_("Batch objects requested on stdin (or --batch-all-objects)")), + OPT_CALLBACK_F(0, "batch", &batch, N_("format"), + N_("show full <object> or <rev> contents"), + PARSE_OPT_OPTARG | PARSE_OPT_NONEG, + batch_option_callback), + OPT_CALLBACK_F(0, "batch-check", &batch, N_("format"), + N_("like --batch, but don't emit <contents>"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, batch_option_callback), - OPT_CALLBACK_F(0, "batch-check", &batch, "format", - N_("show info about objects fed from the standard input"), + OPT_CALLBACK_F(0, "batch-command", &batch, N_("format"), + N_("read commands from stdin"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, batch_option_callback), + OPT_CMDMODE(0, "batch-all-objects", &opt, + N_("with --batch[-check]: ignores stdin, batches all known objects"), 'b'), + /* Batch-specific options */ + OPT_GROUP(N_("Change or optimize batch output")), + OPT_BOOL(0, "buffer", &batch.buffer_output, N_("buffer --batch output")), 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, - N_("show all objects with --batch or --batch-check")), + N_("follow in-tree symlinks")), OPT_BOOL(0, "unordered", &batch.unordered, - N_("do not order --batch-all-objects output")), + N_("do not order objects before emitting them")), + /* Textconv options, stand-ole*/ + OPT_GROUP(N_("Emit object (blob or tree) with conversion or filter (stand-alone, or with batch)")), + OPT_CMDMODE(0, "textconv", &opt, + N_("run textconv on object's content"), 'c'), + OPT_CMDMODE(0, "filters", &opt, + N_("run filters on object's content"), 'w'), + OPT_STRING(0, "path", &force_path, N_("blob|tree"), + N_("use a <path> for (--textconv | --filters); Not with 'batch'")), OPT_END() }; git_config(git_cat_file_config, NULL); batch.buffer_output = -1; - argc = parse_options(argc, argv, prefix, options, cat_file_usage, 0); - - if (opt) { - if (batch.enabled && (opt == 'c' || opt == 'w')) - batch.cmdmode = opt; - else if (argc == 1) - obj_name = argv[0]; - else - usage_with_options(cat_file_usage, options); - } - if (!opt && !batch.enabled) { - if (argc == 2) { - exp_type = argv[0]; - obj_name = argv[1]; - } else - usage_with_options(cat_file_usage, options); - } - if (batch.enabled) { - if (batch.cmdmode != opt || argc) - usage_with_options(cat_file_usage, options); - if (batch.cmdmode && batch.all_objects) - die("--batch-all-objects cannot be combined with " - "--textconv nor with --filters"); - } - if ((batch.follow_symlinks || batch.all_objects) && !batch.enabled) { - usage_with_options(cat_file_usage, options); - } + argc = parse_options(argc, argv, prefix, options, usage, 0); + opt_cw = (opt == 'c' || opt == 'w'); + opt_epts = (opt == 'e' || opt == 'p' || opt == 't' || opt == 's'); - if (force_path && opt != 'c' && opt != 'w') { - error("--path=<path> needs --textconv or --filters"); - usage_with_options(cat_file_usage, options); - } + /* --batch-all-objects? */ + if (opt == 'b') + batch.all_objects = 1; - if (force_path && batch.enabled) { - error("--path=<path> incompatible with --batch"); - usage_with_options(cat_file_usage, options); - } + /* Option compatibility */ + if (force_path && !opt_cw) + usage_msg_optf(_("'%s=<%s>' needs '%s' or '%s'"), + usage, options, + "--path", _("path|tree-ish"), "--filters", + "--textconv"); + /* Option compatibility with batch mode */ + if (batch.enabled) + ; + else if (batch.follow_symlinks) + usage_msg_optf(_("'%s' requires a batch mode"), usage, options, + "--follow-symlinks"); + else if (batch.buffer_output >= 0) + usage_msg_optf(_("'%s' requires a batch mode"), usage, options, + "--buffer"); + else if (batch.all_objects) + usage_msg_optf(_("'%s' requires a batch mode"), usage, options, + "--batch-all-objects"); + + /* Batch defaults */ if (batch.buffer_output < 0) batch.buffer_output = batch.all_objects; - if (batch.enabled) + /* Return early if we're in batch mode? */ + if (batch.enabled) { + if (opt_cw) + batch.transform_mode = opt; + else if (opt && opt != 'b') + usage_msg_optf(_("'-%c' is incompatible with batch mode"), + usage, options, opt); + else if (argc) + usage_msg_opt(_("batch modes take no arguments"), usage, + options); + return batch_objects(&batch); + } + + if (opt) { + if (!argc && opt == 'c') + usage_msg_optf(_("<rev> required with '%s'"), + usage, options, "--textconv"); + else if (!argc && opt == 'w') + usage_msg_optf(_("<rev> required with '%s'"), + usage, options, "--filters"); + else if (!argc && opt_epts) + usage_msg_optf(_("<object> required with '-%c'"), + usage, options, opt); + else if (argc == 1) + obj_name = argv[0]; + else + usage_msg_opt(_("too many arguments"), usage, options); + } else if (!argc) { + usage_with_options(usage, options); + } else if (argc != 2) { + usage_msg_optf(_("only two arguments allowed in <type> <object> mode, not %d"), + usage, options, argc); + } else if (argc) { + exp_type = argv[0]; + obj_name = argv[1]; + } if (unknown_type && opt != 't' && opt != 's') die("git cat-file --allow-unknown-type: use with -s or -t"); diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index e21620d964..97e06e8c52 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -7,6 +7,7 @@ #define USE_THE_INDEX_COMPATIBILITY_MACROS #include "builtin.h" #include "config.h" +#include "dir.h" #include "lockfile.h" #include "quote.h" #include "cache-tree.h" @@ -17,6 +18,7 @@ #define CHECKOUT_ALL 4 static int nul_term_line; static int checkout_stage; /* default to checkout stage0 */ +static int ignore_skip_worktree; /* default to 0 */ static int to_tempfile; static char topath[4][TEMPORARY_FILENAME_LENGTH + 1]; @@ -65,6 +67,8 @@ static int checkout_file(const char *name, const char *prefix) int namelen = strlen(name); int pos = cache_name_pos(name, namelen); int has_same_name = 0; + int is_file = 0; + int is_skipped = 1; int did_checkout = 0; int errs = 0; @@ -78,6 +82,12 @@ static int checkout_file(const char *name, const char *prefix) break; has_same_name = 1; pos++; + if (S_ISSPARSEDIR(ce->ce_mode)) + break; + is_file = 1; + if (!ignore_skip_worktree && ce_skip_worktree(ce)) + break; + is_skipped = 0; if (ce_stage(ce) != checkout_stage && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce))) continue; @@ -106,6 +116,11 @@ static int checkout_file(const char *name, const char *prefix) fprintf(stderr, "git checkout-index: %s ", name); if (!has_same_name) fprintf(stderr, "is not in the cache"); + else if (!is_file) + fprintf(stderr, "is a sparse directory"); + else if (is_skipped) + fprintf(stderr, "has skip-worktree enabled; " + "use '--ignore-skip-worktree-bits' to checkout"); else if (checkout_stage) fprintf(stderr, "does not exist at stage %d", checkout_stage); @@ -121,10 +136,27 @@ static int checkout_all(const char *prefix, int prefix_length) int i, errs = 0; struct cache_entry *last_ce = NULL; - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); for (i = 0; i < active_nr ; i++) { struct cache_entry *ce = active_cache[i]; + + if (S_ISSPARSEDIR(ce->ce_mode)) { + if (!ce_skip_worktree(ce)) + BUG("sparse directory '%s' does not have skip-worktree set", ce->name); + + /* + * If the current entry is a sparse directory and skip-worktree + * entries are being checked out, expand the index and continue + * the loop on the current index position (now pointing to the + * first entry inside the expanded sparse directory). + */ + if (ignore_skip_worktree) { + ensure_full_index(&the_index); + ce = active_cache[i]; + } + } + + if (!ignore_skip_worktree && ce_skip_worktree(ce)) + continue; if (ce_stage(ce) != checkout_stage && (CHECKOUT_ALL != checkout_stage || !ce_stage(ce))) continue; @@ -185,6 +217,8 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) struct option builtin_checkout_index_options[] = { OPT_BOOL('a', "all", &all, N_("check out all files in the index")), + OPT_BOOL(0, "ignore-skip-worktree-bits", &ignore_skip_worktree, + N_("do not skip files with skip-worktree set")), OPT__FORCE(&force, N_("force overwrite of existing files"), 0), OPT__QUIET(&quiet, N_("no warning for existing files and files not in index")), @@ -212,6 +246,9 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); prefix_length = prefix ? strlen(prefix) : 0; + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + if (read_cache() < 0) { die("invalid cache"); } diff --git a/builtin/checkout.c b/builtin/checkout.c index cbf73b8c9f..2eefda81d8 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -9,6 +9,7 @@ #include "config.h" #include "diff.h" #include "dir.h" +#include "hook.h" #include "ll-merge.h" #include "lockfile.h" #include "merge-recursive.h" @@ -91,8 +92,8 @@ struct checkout_opts { }; struct branch_info { - const char *name; /* The short name used */ - const char *path; /* The full name of a real branch */ + char *name; /* The short name used */ + 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. */ @@ -103,10 +104,18 @@ struct branch_info { char *checkout; }; +static void branch_info_release(struct branch_info *info) +{ + free(info->name); + free(info->path); + free(info->refname); + free(info->checkout); +} + static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit, int changed) { - return run_hook_le(NULL, "post-checkout", + return run_hooks_l("post-checkout", oid_to_hex(old_commit ? &old_commit->object.oid : null_oid()), oid_to_hex(new_commit ? &new_commit->object.oid : null_oid()), changed ? "1" : "0", NULL); @@ -237,6 +246,7 @@ static int checkout_merged(int pos, const struct checkout *state, struct cache_entry *ce = active_cache[pos]; const char *path = ce->name; mmfile_t ancestor, ours, theirs; + enum ll_merge_result merge_status; int status; struct object_id oid; mmbuffer_t result_buf; @@ -267,13 +277,16 @@ static int checkout_merged(int pos, const struct checkout *state, 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, &ll_opts); + merge_status = ll_merge(&result_buf, path, &ancestor, "base", + &ours, "ours", &theirs, "theirs", + state->istate, &ll_opts); free(ancestor.ptr); free(ours.ptr); free(theirs.ptr); - if (status < 0 || !result_buf.ptr) { + if (merge_status == LL_MERGE_BINARY_CONFLICT) + warning("Cannot merge binary files: %s (%s vs. %s)", + path, "ours", "theirs"); + if (merge_status < 0 || !result_buf.ptr) { free(result_buf.ptr); return error(_("path '%s': cannot merge"), path); } @@ -290,7 +303,7 @@ static int checkout_merged(int pos, const struct checkout *state, * (it also writes the merge result to the object database even * when it may contain conflicts). */ - if (write_object_file(result_buf.ptr, result_buf.size, blob_type, &oid)) + if (write_object_file(result_buf.ptr, result_buf.size, OBJ_BLOB, &oid)) die(_("Unable to add merge result for '%s'"), path); free(result_buf.ptr); ce = make_transient_cache_entry(mode, &oid, path, 2, ce_mem_pool); @@ -456,10 +469,10 @@ static int checkout_paths(const struct checkout_opts *opts, die(_("'%s' cannot be used with updating paths"), "--detach"); if (opts->merge && opts->patch_mode) - die(_("'%s' cannot be used with %s"), "--merge", "--patch"); + die(_("options '%s' and '%s' cannot be used together"), "--merge", "--patch"); if (opts->ignore_unmerged && opts->merge) - die(_("'%s' cannot be used with %s"), + die(_("options '%s' and '%s' cannot be used together"), opts->ignore_unmerged_opt, "-m"); if (opts->new_branch) @@ -616,7 +629,7 @@ static void show_local_changes(struct object *head, diff_setup_done(&rev.diffopt); add_pending_object(&rev, head, NULL); run_diff_index(&rev, 0); - object_array_clear(&rev.pending); + release_revisions(&rev); } static void describe_detached_head(const char *msg, struct commit *commit) @@ -688,9 +701,12 @@ static void setup_branch_path(struct branch_info *branch) repo_get_oid_committish(the_repository, branch->name, &branch->oid); strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL); - if (strcmp(buf.buf, branch->name)) + if (strcmp(buf.buf, branch->name)) { + free(branch->name); branch->name = xstrdup(buf.buf); + } strbuf_splice(&buf, 0, 0, "refs/heads/", 11); + free(branch->path); branch->path = strbuf_detach(&buf, NULL); } @@ -722,6 +738,7 @@ static int merge_working_tree(const struct checkout_opts *opts, struct tree_desc trees[2]; struct tree *tree; struct unpack_trees_options topts; + const struct object_id *old_commit_oid; memset(&topts, 0, sizeof(topts)); topts.head_idx = -1; @@ -749,9 +766,15 @@ static int merge_working_tree(const struct checkout_opts *opts, &new_branch_info->commit->object.oid : &new_branch_info->oid, NULL); topts.preserve_ignored = !opts->overwrite_ignore; - tree = parse_tree_indirect(old_branch_info->commit ? - &old_branch_info->commit->object.oid : - the_hash_algo->empty_tree); + + old_commit_oid = old_branch_info->commit ? + &old_branch_info->commit->object.oid : + the_hash_algo->empty_tree; + tree = parse_tree_indirect(old_commit_oid); + if (!tree) + die(_("unable to parse commit %s"), + oid_to_hex(old_commit_oid)); + init_tree_desc(&trees[0], tree->buffer, tree->size); parse_tree(new_tree); tree = new_tree; @@ -811,7 +834,7 @@ static int merge_working_tree(const struct checkout_opts *opts, if (ret) return ret; o.ancestor = old_branch_info->name; - if (old_branch_info->name == NULL) { + if (!old_branch_info->name) { strbuf_add_unique_abbrev(&old_commit_shortname, &old_branch_info->commit->object.oid, DEFAULT_ABBREV); @@ -874,7 +897,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, int ret; struct strbuf err = STRBUF_INIT; - ret = safe_create_reflog(refname, 1, &err); + ret = safe_create_reflog(refname, &err); if (ret) { fprintf(stderr, _("Can not do reflog for '%s': %s\n"), opts->new_orphan_branch, err.buf); @@ -893,8 +916,11 @@ static void update_refs_for_switch(const struct checkout_opts *opts, opts->new_branch_force ? 1 : 0, opts->new_branch_log, opts->quiet, - opts->track); - new_branch_info->name = opts->new_branch; + opts->track, + 0); + free(new_branch_info->name); + free(new_branch_info->refname); + new_branch_info->name = xstrdup(opts->new_branch); setup_branch_path(new_branch_info); } @@ -1056,14 +1082,14 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne /* Clean up objects used, as they will be reused. */ repo_clear_commit_marks(the_repository, ALL_REV_FLAGS); + release_revisions(&revs); } static int switch_branches(const struct checkout_opts *opts, struct branch_info *new_branch_info) { int ret = 0; - struct branch_info old_branch_info; - void *path_to_free; + struct branch_info old_branch_info = { 0 }; struct object_id rev; int flag, writeout_error = 0; int do_merge = 1; @@ -1071,25 +1097,29 @@ static int switch_branches(const struct checkout_opts *opts, trace2_cmd_mode("branch"); memset(&old_branch_info, 0, sizeof(old_branch_info)); - old_branch_info.path = path_to_free = resolve_refdup("HEAD", 0, &rev, &flag); + old_branch_info.path = resolve_refdup("HEAD", 0, &rev, &flag); if (old_branch_info.path) old_branch_info.commit = lookup_commit_reference_gently(the_repository, &rev, 1); if (!(flag & REF_ISSYMREF)) - old_branch_info.path = NULL; + FREE_AND_NULL(old_branch_info.path); - if (old_branch_info.path) - skip_prefix(old_branch_info.path, "refs/heads/", &old_branch_info.name); + if (old_branch_info.path) { + const char *const prefix = "refs/heads/"; + const char *p; + if (skip_prefix(old_branch_info.path, prefix, &p)) + old_branch_info.name = xstrdup(p); + } if (opts->new_orphan_branch && opts->orphan_from_empty_tree) { if (new_branch_info->name) BUG("'switch --orphan' should never accept a commit as starting point"); new_branch_info->commit = NULL; - new_branch_info->name = "(empty)"; + new_branch_info->name = xstrdup("(empty)"); do_merge = 1; } if (!new_branch_info->name) { - new_branch_info->name = "HEAD"; + new_branch_info->name = xstrdup("HEAD"); new_branch_info->commit = old_branch_info.commit; if (!new_branch_info->commit) die(_("You are on a branch yet to be born")); @@ -1102,7 +1132,7 @@ static int switch_branches(const struct checkout_opts *opts, if (do_merge) { ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error); if (ret) { - free(path_to_free); + branch_info_release(&old_branch_info); return ret; } } @@ -1113,7 +1143,8 @@ static int switch_branches(const struct checkout_opts *opts, update_refs_for_switch(opts, &old_branch_info, new_branch_info); ret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1); - free(path_to_free); + branch_info_release(&old_branch_info); + return ret || writeout_error; } @@ -1145,16 +1176,15 @@ static void setup_new_branch_info_and_source_tree( struct tree **source_tree = &opts->source_tree; struct object_id branch_rev; - new_branch_info->name = arg; + new_branch_info->name = xstrdup(arg); setup_branch_path(new_branch_info); if (!check_refname_format(new_branch_info->path, 0) && !read_ref(new_branch_info->path, &branch_rev)) oidcpy(rev, &branch_rev); - else { - free((char *)new_branch_info->path); - new_branch_info->path = NULL; /* not an existing branch */ - } + else + /* not an existing branch */ + FREE_AND_NULL(new_branch_info->path); new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1); if (!new_branch_info->commit) { @@ -1375,23 +1405,31 @@ static void die_expecting_a_branch(const struct branch_info *branch_info) { struct object_id oid; char *to_free; + int code; 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)) - die(_("a branch is expected, got tag '%s'"), ref); - if (skip_prefix(ref, "refs/remotes/", &ref)) - die(_("a branch is expected, got remote branch '%s'"), ref); - die(_("a branch is expected, got '%s'"), ref); + code = die_message(_("a branch is expected, got tag '%s'"), ref); + else if (skip_prefix(ref, "refs/remotes/", &ref)) + code = die_message(_("a branch is expected, got remote branch '%s'"), ref); + else + code = die_message(_("a branch is expected, got '%s'"), ref); } - if (branch_info->commit) - die(_("a branch is expected, got commit '%s'"), branch_info->name); - /* - * This case should never happen because we already die() on - * non-commit, but just in case. - */ - die(_("a branch is expected, got '%s'"), branch_info->name); + else if (branch_info->commit) + code = die_message(_("a branch is expected, got commit '%s'"), branch_info->name); + else + /* + * This case should never happen because we already die() on + * non-commit, but just in case. + */ + code = die_message(_("a branch is expected, got '%s'"), branch_info->name); + + if (advice_enabled(ADVICE_SUGGEST_DETACHING_HEAD)) + advise(_("If you want to detach HEAD at the commit, try again with the --detach option.")); + + exit(code); } static void die_if_some_operation_in_progress(void) @@ -1517,7 +1555,7 @@ static struct option *add_common_options(struct checkout_opts *opts, 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"), - N_("conflict style (merge or diff3)")), + N_("conflict style (merge, diff3, or zdiff3)")), OPT_END() }; struct option *newopts = parse_options_concat(prevopts, options); @@ -1530,8 +1568,10 @@ static struct option *add_common_switch_branch_options( { struct option options[] = { OPT_BOOL('d', "detach", &opts->force_detach, N_("detach HEAD at named commit")), - OPT_SET_INT('t', "track", &opts->track, N_("set upstream info for new branch"), - BRANCH_TRACK_EXPLICIT), + OPT_CALLBACK_F('t', "track", &opts->track, "(direct|inherit)", + N_("set branch tracking configuration"), + PARSE_OPT_OPTARG, + parse_opt_tracking_mode), OPT__FORCE(&opts->force, N_("force checkout (throw away local modifications)"), PARSE_OPT_NOCOMPLETE), OPT_STRING(0, "orphan", &opts->new_orphan_branch, N_("new-branch"), N_("new unparented branch")), @@ -1574,20 +1614,20 @@ 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[]) + const char * const usagestr[], + struct branch_info *new_branch_info) { - struct branch_info new_branch_info; int parseopt_flags = 0; - memset(&new_branch_info, 0, sizeof(new_branch_info)); opts->overwrite_ignore = 1; opts->prefix = prefix; opts->show_progress = -1; git_config(git_checkout_config, opts); - - prepare_repo_settings(the_repository); - the_repository->settings.command_requires_full_index = 0; + if (the_repository->gitdir) { + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + } opts->track = BRANCH_TRACK_UNSPECIFIED; @@ -1617,11 +1657,11 @@ 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(_("-%c, -%c and --orphan are mutually exclusive"), - cb_option, toupper(cb_option)); + die(_("options '-%c', '-%c', and '%s' cannot be used together"), + cb_option, toupper(cb_option), "--orphan"); if (opts->overlay_mode == 1 && opts->patch_mode) - die(_("-p and --overlay are mutually exclusive")); + die(_("options '%s' and '%s' cannot be used together"), "-p", "--overlay"); if (opts->checkout_index >= 0 || opts->checkout_worktree >= 0) { if (opts->checkout_index < 0) @@ -1688,7 +1728,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); + new_branch_info, opts, &rev); argv += n; argc -= n; } else if (!opts->accept_ref && opts->from_treeish) { @@ -1697,7 +1737,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix, if (get_oid_mb(opts->from_treeish, &rev)) die(_("could not resolve %s"), opts->from_treeish); - setup_new_branch_info_and_source_tree(&new_branch_info, + setup_new_branch_info_and_source_tree(new_branch_info, opts, &rev, opts->from_treeish); @@ -1717,7 +1757,7 @@ 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 && !new_branch_info.commit) + 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); @@ -1728,19 +1768,19 @@ static int checkout_main(int argc, const char **argv, const char *prefix, if (opts->pathspec_from_file) { if (opts->pathspec.nr) - die(_("--pathspec-from-file is incompatible with pathspec arguments")); + die(_("'%s' and pathspec arguments cannot be used together"), "--pathspec-from-file"); if (opts->force_detach) - die(_("--pathspec-from-file is incompatible with --detach")); + die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--detach"); if (opts->patch_mode) - die(_("--pathspec-from-file is incompatible with --patch")); + die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--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")); + die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file"); } opts->pathspec.recursive = 1; @@ -1766,11 +1806,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix, strbuf_release(&buf); } - UNLEAK(opts); if (opts->patch_mode || opts->pathspec.nr) - return checkout_paths(opts, &new_branch_info); + return checkout_paths(opts, new_branch_info); else - return checkout_branch(opts, &new_branch_info); + return checkout_branch(opts, new_branch_info); } int cmd_checkout(int argc, const char **argv, const char *prefix) @@ -1789,6 +1828,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) OPT_END() }; int ret; + struct branch_info new_branch_info = { 0 }; memset(&opts, 0, sizeof(opts)); opts.dwim_new_local_branch = 1; @@ -1819,7 +1859,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix) options = add_checkout_path_options(&opts, options); ret = checkout_main(argc, argv, prefix, &opts, - options, checkout_usage); + options, checkout_usage, &new_branch_info); + branch_info_release(&new_branch_info); + clear_pathspec(&opts.pathspec); FREE_AND_NULL(options); return ret; } @@ -1840,6 +1882,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix) OPT_END() }; int ret; + struct branch_info new_branch_info = { 0 }; memset(&opts, 0, sizeof(opts)); opts.dwim_new_local_branch = 1; @@ -1859,7 +1902,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix) cb_option = 'c'; ret = checkout_main(argc, argv, prefix, &opts, - options, switch_branch_usage); + options, switch_branch_usage, &new_branch_info); + branch_info_release(&new_branch_info); FREE_AND_NULL(options); return ret; } @@ -1881,6 +1925,7 @@ int cmd_restore(int argc, const char **argv, const char *prefix) OPT_END() }; int ret; + struct branch_info new_branch_info = { 0 }; memset(&opts, 0, sizeof(opts)); opts.accept_ref = 0; @@ -1896,7 +1941,8 @@ int cmd_restore(int argc, const char **argv, const char *prefix) options = add_checkout_path_options(&opts, options); ret = checkout_main(argc, argv, prefix, &opts, - options, restore_usage); + options, restore_usage, &new_branch_info); + branch_info_release(&new_branch_info); FREE_AND_NULL(options); return ret; } diff --git a/builtin/clean.c b/builtin/clean.c index 98a2860409..5466636e66 100644 --- a/builtin/clean.c +++ b/builtin/clean.c @@ -36,6 +36,8 @@ static const char *msg_skip_git_dir = N_("Skipping repository %s\n"); static const char *msg_would_skip_git_dir = N_("Would skip repository %s\n"); static const char *msg_warn_remove_failed = N_("failed to remove %s"); static const char *msg_warn_lstat_failed = N_("could not lstat %s\n"); +static const char *msg_skip_cwd = N_("Refusing to remove current working directory\n"); +static const char *msg_would_skip_cwd = N_("Would refuse to remove current working directory\n"); enum color_clean { CLEAN_COLOR_RESET = 0, @@ -153,6 +155,8 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, { DIR *dir; struct strbuf quoted = STRBUF_INIT; + struct strbuf realpath = STRBUF_INIT; + struct strbuf real_ocwd = STRBUF_INIT; struct dirent *e; int res = 0, ret = 0, gone = 1, original_len = path->len, len; struct string_list dels = STRING_LIST_INIT_DUP; @@ -231,16 +235,36 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, strbuf_setlen(path, original_len); if (*dir_gone) { - res = dry_run ? 0 : rmdir(path->buf); - if (!res) - *dir_gone = 1; - else { - int saved_errno = errno; - quote_path(path->buf, prefix, "ed, 0); - errno = saved_errno; - warning_errno(_(msg_warn_remove_failed), quoted.buf); + /* + * Normalize path components in path->buf, e.g. change '\' to + * '/' on Windows. + */ + strbuf_realpath(&realpath, path->buf, 1); + + /* + * path and realpath are absolute; for comparison, we would + * like to transform startup_info->original_cwd to an absolute + * path too. + */ + if (startup_info->original_cwd) + strbuf_realpath(&real_ocwd, + startup_info->original_cwd, 1); + + if (!strbuf_cmp(&realpath, &real_ocwd)) { + printf("%s", dry_run ? _(msg_would_skip_cwd) : _(msg_skip_cwd)); *dir_gone = 0; - ret = 1; + } else { + res = dry_run ? 0 : rmdir(path->buf); + if (!res) + *dir_gone = 1; + else { + int saved_errno = errno; + quote_path(path->buf, prefix, "ed, 0); + errno = saved_errno; + warning_errno(_(msg_warn_remove_failed), quoted.buf); + *dir_gone = 0; + ret = 1; + } } } @@ -250,6 +274,8 @@ static int remove_dirs(struct strbuf *path, const char *prefix, int force_flag, printf(dry_run ? _(msg_would_remove) : _(msg_remove), dels.items[i].string); } out: + strbuf_release(&realpath); + strbuf_release(&real_ocwd); strbuf_release("ed); string_list_clear(&dels, 0); return ret; @@ -983,6 +1009,9 @@ int cmd_clean(int argc, const char **argv, const char *prefix) dir.flags |= DIR_KEEP_UNTRACKED_CONTENTS; } + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + if (read_cache() < 0) die(_("index file corrupt")); diff --git a/builtin/clone.c b/builtin/clone.c index fb377b2765..89a91b0017 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -32,6 +32,8 @@ #include "connected.h" #include "packfile.h" #include "list-objects-filter-options.h" +#include "hook.h" +#include "bundle.h" /* * Overall FIXMEs: @@ -71,6 +73,8 @@ static int option_dissociate; static int max_jobs = -1; static struct string_list option_recurse_submodules = STRING_LIST_INIT_NODUP; static struct list_objects_filter_options filter_options; +static int option_filter_submodules = -1; /* unspecified */ +static int config_filter_submodules = -1; /* unspecified */ static struct string_list server_options = STRING_LIST_INIT_NODUP; static int option_remote_submodules; @@ -150,6 +154,8 @@ static struct option builtin_clone_options[] = { OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"), TRANSPORT_FAMILY_IPV6), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), + OPT_BOOL(0, "also-filter-submodules", &option_filter_submodules, + N_("apply partial clone filters to submodules")), 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, @@ -633,7 +639,7 @@ 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); + strvec_pushl(&argv, "-C", repo, "sparse-checkout", "set", NULL); /* * We must apply the setting in the current process @@ -650,7 +656,7 @@ static int git_sparse_checkout_init(const char *repo) return result; } -static int checkout(int submodule_progress) +static int checkout(int submodule_progress, int filter_submodules) { struct object_id oid; char *head; @@ -695,6 +701,8 @@ static int checkout(int submodule_progress) init_checkout_metadata(&opts.meta, head, &oid, NULL); tree = parse_tree_indirect(&oid); + if (!tree) + die(_("unable to parse commit %s"), oid_to_hex(&oid)); parse_tree(tree); init_tree_desc(&t, tree->buffer, tree->size); if (unpack_trees(1, &t, &opts) < 0) @@ -705,7 +713,7 @@ static int checkout(int submodule_progress) if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); - err |= run_hook_le(NULL, "post-checkout", oid_to_hex(null_oid()), + err |= run_hooks_l("post-checkout", oid_to_hex(null_oid()), oid_to_hex(&oid), "1", NULL); if (!err && (option_recurse_submodules.nr > 0)) { @@ -729,6 +737,10 @@ static int checkout(int submodule_progress) strvec_push(&args, "--no-fetch"); } + if (filter_submodules && filter_options.choice) + strvec_pushf(&args, "--filter=%s", + expand_list_objects_filter_spec(&filter_options)); + if (option_single_branch >= 0) strvec_push(&args, option_single_branch ? "--single-branch" : @@ -749,6 +761,8 @@ static int git_clone_config(const char *k, const char *v, void *cb) } if (!strcmp(k, "clone.rejectshallow")) config_reject_shallow = git_config_bool(k, v); + if (!strcmp(k, "clone.filtersubmodules")) + config_filter_submodules = git_config_bool(k, v); return git_default_config(k, v, cb); } @@ -862,7 +876,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) const struct ref *refs, *remote_head; struct ref *remote_head_points_at = NULL; const struct ref *our_head_points_at; - struct ref *mapped_refs; + struct ref *mapped_refs = NULL; const struct ref *ref; struct strbuf key = STRBUF_INIT; struct strbuf branch_top = STRBUF_INIT, reflog_msg = STRBUF_INIT; @@ -871,6 +885,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) struct remote *remote; int err = 0, complete_refs_before_fetch = 1; int submodule_progress; + int filter_submodules = 0; struct transport_ls_refs_options transport_ls_refs_options = TRANSPORT_LS_REFS_OPTIONS_INIT; @@ -900,10 +915,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_bare) { if (option_origin) - die(_("--bare and --origin %s options are incompatible."), - option_origin); + die(_("options '%s' and '%s %s' cannot be used together"), + "--bare", "--origin", option_origin); if (real_git_dir) - die(_("--bare and --separate-git-dir are incompatible.")); + die(_("options '%s' and '%s' cannot be used together"), "--bare", "--separate-git-dir"); option_no_checkout = 1; } @@ -1067,13 +1082,36 @@ int cmd_clone(int argc, const char **argv, const char *prefix) reject_shallow = option_reject_shallow; /* + * If option_filter_submodules is specified from CLI option, + * ignore config_filter_submodules from git_clone_config. + */ + if (config_filter_submodules != -1) + filter_submodules = config_filter_submodules; + if (option_filter_submodules != -1) + filter_submodules = option_filter_submodules; + + /* + * Exit if the user seems to be doing something silly with submodule + * filter flags (but not with filter configs, as those should be + * set-and-forget). + */ + if (option_filter_submodules > 0 && !filter_options.choice) + die(_("the option '%s' requires '%s'"), + "--also-filter-submodules", "--filter"); + if (option_filter_submodules > 0 && !option_recurse_submodules.nr) + die(_("the option '%s' requires '%s'"), + "--also-filter-submodules", "--recurse-submodules"); + + /* * apply the remote name provided by --origin only after this second * call to git_config, to ensure it overrides all config-based values. */ - if (option_origin != NULL) + if (option_origin) { + free(remote_name); remote_name = xstrdup(option_origin); + } - if (remote_name == NULL) + if (!remote_name) remote_name = xstrdup("origin"); if (!valid_remote_name(remote_name)) @@ -1137,6 +1175,18 @@ int cmd_clone(int argc, const char **argv, const char *prefix) warning(_("--local is ignored")); transport->cloning = 1; + if (is_bundle) { + struct bundle_header header = BUNDLE_HEADER_INIT; + int fd = read_bundle_header(path, &header); + int has_filter = header.filter.choice != LOFC_DISABLED; + + if (fd > 0) + close(fd); + bundle_header_release(&header); + if (has_filter) + die(_("cannot clone from filtered bundle")); + } + transport_set_option(transport, TRANS_OPT_KEEP, "yes"); if (reject_shallow) @@ -1184,7 +1234,10 @@ int cmd_clone(int argc, const char **argv, const char *prefix) refs = transport_get_remote_refs(transport, &transport_ls_refs_options); - if (refs) { + if (refs) + mapped_refs = wanted_peer_refs(refs, &remote->fetch); + + if (mapped_refs) { int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); /* @@ -1193,8 +1246,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) */ 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 * in mapped_refs (see struct transport->get_refs_list @@ -1233,14 +1284,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } else { const char *branch; - char *ref; + const char *ref; + char *ref_free = NULL; if (option_branch) die(_("Remote branch %s not found in upstream %s"), option_branch, remote_name); warning(_("You appear to have cloned an empty repository.")); - mapped_refs = NULL; our_head_points_at = NULL; remote_head_points_at = NULL; remote_head = NULL; @@ -1250,17 +1301,16 @@ int cmd_clone(int argc, const char **argv, const char *prefix) skip_prefix(transport_ls_refs_options.unborn_head_target, "refs/heads/", &branch)) { ref = transport_ls_refs_options.unborn_head_target; - transport_ls_refs_options.unborn_head_target = NULL; create_symref("HEAD", ref, reflog_msg.buf); } else { branch = git_default_branch_name(0); - ref = xstrfmt("refs/heads/%s", branch); + ref_free = xstrfmt("refs/heads/%s", branch); + ref = ref_free; } if (!option_bare) install_branch_config(0, branch, remote_name, ref); - - free(ref); + free(ref_free); } write_refspec_config(src_ref_prefix, our_head_points_at, @@ -1271,7 +1321,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (is_local) clone_local(path, git_dir); - else if (refs && complete_refs_before_fetch) { + else if (mapped_refs && complete_refs_before_fetch) { if (transport_fetch_refs(transport, mapped_refs)) die(_("remote transport reported error")); } @@ -1290,7 +1340,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) */ submodule_progress = transport->progress; - transport_unlock_pack(transport); + transport_unlock_pack(transport, 0); transport_disconnect(transport); if (option_dissociate) { @@ -1299,7 +1349,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } junk_mode = JUNK_LEAVE_REPO; - err = checkout(submodule_progress); + err = checkout(submodule_progress, filter_submodules); free(remote_name); strbuf_release(&reflog_msg); @@ -1312,7 +1362,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) UNLEAK(repo); junk_mode = JUNK_LEAVE_ALL; - strvec_clear(&transport_ls_refs_options.ref_prefixes); - free(transport_ls_refs_options.unborn_head_target); + transport_ls_refs_options_release(&transport_ls_refs_options); return err; } diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index 4247fbde95..51c4040ea6 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -192,7 +192,7 @@ static int git_commit_graph_write_config(const char *var, const char *value, static int graph_write(int argc, const char **argv) { - struct string_list pack_indexes = STRING_LIST_INIT_NODUP; + struct string_list pack_indexes = STRING_LIST_INIT_DUP; struct strbuf buf = STRBUF_INIT; struct oidset commits = OIDSET_INIT; struct object_directory *odb = NULL; @@ -273,8 +273,8 @@ static int graph_write(int argc, const char **argv) if (opts.stdin_packs) { while (strbuf_getline(&buf, stdin) != EOF) - string_list_append(&pack_indexes, - strbuf_detach(&buf, NULL)); + string_list_append_nodup(&pack_indexes, + strbuf_detach(&buf, NULL)); } else if (opts.stdin_commits) { oidset_init(&commits, 0); if (opts.progress) diff --git a/builtin/commit.c b/builtin/commit.c index 883c16256c..fcf9c85947 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -37,6 +37,7 @@ #include "help.h" #include "commit-reach.h" #include "commit-graph.h" +#include "pretty.h" static const char * const builtin_commit_usage[] = { N_("git commit [<options>] [--] <pathspec>..."), @@ -355,19 +356,19 @@ static const char *prepare_index(const char **argv, const char *prefix, if (pathspec_from_file) { if (interactive) - die(_("--pathspec-from-file is incompatible with --interactive/--patch")); + die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--interactive/--patch"); if (all) - die(_("--pathspec-from-file with -a does not make sense")); + die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "-a"); if (pathspec.nr) - die(_("--pathspec-from-file is incompatible with pathspec arguments")); + die(_("'%s' and pathspec arguments cannot be used together"), "--pathspec-from-file"); 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")); + die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file"); } if (!pathspec.nr && (also || (only && !allow_empty && @@ -725,11 +726,13 @@ static int prepare_to_commit(const char *index_file, const char *prefix, int clean_message_contents = (cleanup_mode != COMMIT_MSG_CLEANUP_NONE); int old_display_comment_prefix; int merge_contains_scissors = 0; + int invoked_hook; /* This checks and barfs if author is badly specified */ determine_author_info(author_ident); - if (!no_verify && run_commit_hook(use_editor, index_file, "pre-commit", NULL)) + if (!no_verify && run_commit_hook(use_editor, index_file, &invoked_hook, + "pre-commit", NULL)) return 0; if (squash_message) { @@ -799,7 +802,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (!strcmp(fixup_prefix, "amend")) { if (have_option_m) - die(_("cannot combine -m with --fixup:%s"), fixup_message); + die(_("options '%s' and '%s:%s' cannot be used together"), "-m", "--fixup", fixup_message); prepare_amend_commit(commit, &sb, &ctx); } } else if (!stat(git_path_merge_msg(the_repository), &statbuf)) { @@ -858,7 +861,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } s->fp = fopen_for_writing(git_path_commit_editmsg()); - if (s->fp == NULL) + if (!s->fp) die_errno(_("could not open '%s'"), git_path_commit_editmsg()); /* Ignore status.displayCommentPrefix: we do need comments in COMMIT_EDITMSG. */ @@ -1052,10 +1055,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix, return 0; } - if (!no_verify && hook_exists("pre-commit")) { + if (!no_verify && invoked_hook) { /* - * Re-read the index as pre-commit hook could have updated it, - * and write it out as a tree. We must do this before we invoke + * Re-read the index as the pre-commit-commit hook was invoked + * and could have updated it. We must do this before we invoke * the editor and after we invoke run_status above. */ discard_cache(); @@ -1067,7 +1070,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, return 0; } - if (run_commit_hook(use_editor, index_file, "prepare-commit-msg", + if (run_commit_hook(use_editor, index_file, NULL, "prepare-commit-msg", git_path_commit_editmsg(), hook_arg1, hook_arg2, NULL)) return 0; @@ -1084,7 +1087,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } if (!no_verify && - run_commit_hook(use_editor, index_file, "commit-msg", git_path_commit_editmsg(), NULL)) { + run_commit_hook(use_editor, index_file, NULL, "commit-msg", + git_path_commit_editmsg(), NULL)) { return 0; } @@ -1096,7 +1100,6 @@ static const char *find_author_by_nickname(const char *name) struct rev_info revs; struct commit *commit; struct strbuf buf = STRBUF_INIT; - struct string_list mailmap = STRING_LIST_INIT_NODUP; const char *av[20]; int ac = 0; @@ -1107,7 +1110,8 @@ static const char *find_author_by_nickname(const char *name) av[++ac] = buf.buf; av[++ac] = NULL; setup_revisions(ac, av, &revs, NULL); - revs.mailmap = &mailmap; + revs.mailmap = xmalloc(sizeof(struct string_list)); + string_list_init_nodup(revs.mailmap); read_mailmap(revs.mailmap); if (prepare_revision_walk(&revs)) @@ -1118,7 +1122,7 @@ static const char *find_author_by_nickname(const char *name) ctx.date_mode.type = DATE_NORMAL; strbuf_release(&buf); format_commit_message(commit, "%aN <%aE>", &buf, &ctx); - clear_mailmap(&mailmap); + release_revisions(&revs); return strbuf_detach(&buf, NULL); } die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name); @@ -1193,7 +1197,7 @@ static void finalize_deferred_config(struct wt_status *s) status_format == STATUS_FORMAT_UNSPECIFIED) status_format = STATUS_FORMAT_PORCELAIN; else if (status_format == STATUS_FORMAT_LONG) - die(_("--long and -z are incompatible")); + die(_("options '%s' and '%s' cannot be used together"), "--long", "-z"); } if (use_deferred_config && status_format == STATUS_FORMAT_UNSPECIFIED) @@ -1229,9 +1233,10 @@ static void check_fixup_reword_options(int argc, const char *argv[]) { die(_("You are in the middle of a cherry-pick -- cannot reword.")); } if (argc) - die(_("cannot combine reword option of --fixup with path '%s'"), *argv); + die(_("reword option of '%s' and path '%s' cannot be used together"), "--fixup", *argv); if (patch_interactive || interactive || all || also || only) - die(_("reword option of --fixup is mutually exclusive with --patch/--interactive/--all/--include/--only")); + die(_("reword option of '%s' and '%s' cannot be used together"), + "--fixup", "--patch/--interactive/--all/--include/--only"); } static int parse_and_validate_options(int argc, const char *argv[], @@ -1241,8 +1246,6 @@ static int parse_and_validate_options(int argc, const char *argv[], struct commit *current_head, struct wt_status *s) { - int f = 0; - argc = parse_options(argc, argv, prefix, options, usage, 0); finalize_deferred_config(s); @@ -1250,7 +1253,7 @@ static int parse_and_validate_options(int argc, const char *argv[], force_author = find_author_by_nickname(force_author); if (force_author && renew_authorship) - die(_("Using both --reset-author and --author does not make sense")); + die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author"); if (logfile || have_option_m || use_message) use_editor = 0; @@ -1267,20 +1270,16 @@ static int parse_and_validate_options(int argc, const char *argv[], 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")); - if (use_message) - f++; - if (edit_message) - f++; - if (fixup_message) - f++; - if (logfile) - f++; - if (f > 1) - die(_("Only one of -c/-C/-F/--fixup can be used.")); - if (have_option_m && (edit_message || use_message || logfile)) - die((_("Option -m cannot be combined with -c/-C/-F."))); - if (f || have_option_m) + die(_("options '%s' and '%s' cannot be used together"), "--squash", "--fixup"); + die_for_incompatible_opt4(!!use_message, "-C", + !!edit_message, "-c", + !!logfile, "-F", + !!fixup_message, "--fixup"); + die_for_incompatible_opt4(have_option_m, "-m", + !!edit_message, "-c", + !!use_message, "-C", + !!logfile, "-F"); + if (use_message || edit_message || logfile ||fixup_message || have_option_m) template_file = NULL; if (edit_message) use_message = edit_message; @@ -1305,9 +1304,10 @@ static int parse_and_validate_options(int argc, const char *argv[], if (patch_interactive) interactive = 1; - if (also + only + all + interactive > 1) - die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); - + die_for_incompatible_opt4(also, "-i/--include", + only, "-o/--only", + all, "-a/--all", + interactive, "--interactive/-p/--patch"); if (fixup_message) { /* * We limit --fixup's suboptions to only alpha characters. @@ -1687,6 +1687,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) struct commit *current_head = NULL; struct commit_extra_header *extra = NULL; struct strbuf err = STRBUF_INIT; + int ret = 0; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_commit_usage, builtin_commit_options); @@ -1721,8 +1722,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix) running hooks, writing the trees, and interacting with the user. */ if (!prepare_to_commit(index_file, prefix, current_head, &s, &author_ident)) { + ret = 1; rollback_index_files(); - return 1; + goto cleanup; } /* Determine parents */ @@ -1820,7 +1822,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix) rollback_index_files(); die(_("failed to write commit object")); } - strbuf_release(&author_ident); free_commit_extra_headers(extra); if (update_head_with_reflog(current_head, &oid, reflog_msg, &sb, @@ -1844,7 +1845,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) repo_rerere(the_repository, 0); run_auto_maintenance(quiet); - run_commit_hook(use_editor, get_index_file(), "post-commit", NULL); + run_commit_hook(use_editor, get_index_file(), NULL, "post-commit", + NULL); if (amend && !no_post_rewrite) { commit_post_rewrite(the_repository, current_head, &oid); } @@ -1861,7 +1863,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix) apply_autostash(git_path_merge_autostash(the_repository)); +cleanup: + UNLEAK(author_ident); UNLEAK(err); UNLEAK(sb); - return 0; + return ret; } diff --git a/builtin/config.c b/builtin/config.c index 542d8d02b2..e7b88a9c08 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -151,7 +151,7 @@ static struct option builtin_config_options[] = { OPT_BIT(0, "get-color", &actions, N_("find the color configured: slot [default]"), ACTION_GET_COLOR), OPT_BIT(0, "get-colorbool", &actions, N_("find the color setting: slot [stdout-is-tty]"), ACTION_GET_COLORBOOL), OPT_GROUP(N_("Type")), - OPT_CALLBACK('t', "type", &type, "", N_("value is given this type"), option_parse_type), + OPT_CALLBACK('t', "type", &type, N_("type"), N_("value is given this type"), option_parse_type), 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), @@ -612,7 +612,7 @@ static int get_urlmatch(const char *var, const char *url) strbuf_release(&matched->value); } - string_list_clear(&config.vars, 1); + urlmatch_config_release(&config); string_list_clear(&values, 1); free(config.url.url); diff --git a/builtin/count-objects.c b/builtin/count-objects.c index 3fae474f6f..07b9419596 100644 --- a/builtin/count-objects.c +++ b/builtin/count-objects.c @@ -87,7 +87,7 @@ static int print_alternate(struct object_directory *odb, void *data) } static char const * const count_objects_usage[] = { - N_("git count-objects [-v] [-H | --human-readable]"), + "git count-objects [-v] [-H | --human-readable]", NULL }; diff --git a/builtin/credential.c b/builtin/credential.c index d75dcdc64a..d7b304fa08 100644 --- a/builtin/credential.c +++ b/builtin/credential.c @@ -4,7 +4,7 @@ #include "config.h" static const char usage_msg[] = - "git credential [fill|approve|reject]"; + "git credential (fill|approve|reject)"; int cmd_credential(int argc, const char **argv, const char *prefix) { diff --git a/builtin/describe.c b/builtin/describe.c index e912ba50d7..a76f1a1a7a 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -517,6 +517,7 @@ static void describe_blob(struct object_id oid, struct strbuf *dst) traverse_commit_list(&revs, process_commit, process_object, &pcd); reset_revision_walk(); + release_revisions(&revs); } static void describe(const char *arg, int last_one) @@ -590,7 +591,7 @@ int cmd_describe(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; if (longformat && abbrev == 0) - die(_("--long is incompatible with --abbrev=0")); + die(_("options '%s' and '%s' cannot be used together"), "--long", "--abbrev=0"); if (contains) { struct string_list_item *item; @@ -667,12 +668,13 @@ int cmd_describe(int argc, const char **argv, const char *prefix) suffix = NULL; else suffix = dirty; + release_revisions(&revs); } describe("HEAD", 1); } else if (dirty) { - die(_("--dirty is incompatible with commit-ishes")); + die(_("option '%s' and commit-ishes cannot be used together"), "--dirty"); } else if (broken) { - die(_("--broken is incompatible with commit-ishes")); + die(_("option '%s' and commit-ishes cannot be used together"), "--broken"); } else { while (argc-- > 0) describe(*argv++, argc == 0); diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 70103c4095..2bfaf9ba7a 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -77,8 +77,12 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) if (read_cache_preload(&rev.diffopt.pathspec) < 0) { perror("read_cache_preload"); - return -1; + result = -1; + goto cleanup; } +cleanup: result = run_diff_files(&rev, options); - return diff_result_code(&rev.diffopt, result); + result = diff_result_code(&rev.diffopt, result); + release_revisions(&rev); + return result; } diff --git a/builtin/diff-index.c b/builtin/diff-index.c index 5fd23ab5b6..7d158af6b6 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -70,6 +70,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) return -1; } result = run_diff_index(&rev, option); - UNLEAK(rev); - return diff_result_code(&rev.diffopt, result); + result = diff_result_code(&rev.diffopt, result); + release_revisions(&rev); + return result; } diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index f33d30d57b..116097a404 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -152,7 +152,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) } if (read_stdin && merge_base) - die(_("--stdin and --merge-base are mutually exclusive")); + die(_("options '%s' and '%s' cannot be used together"), "--stdin", "--merge-base"); if (merge_base && opt->pending.nr != 2) die(_("--merge-base only works with two commits")); @@ -195,6 +195,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) int saved_dcctc = 0; opt->diffopt.rotate_to_strict = 0; + opt->diffopt.no_free = 1; if (opt->diffopt.detect_rename) { if (!the_index.cache) repo_read_index(the_repository); @@ -217,6 +218,8 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) } opt->diffopt.degraded_cc_to_c = saved_dcctc; opt->diffopt.needed_rename_limit = saved_nrl; + opt->diffopt.no_free = 0; + diff_free(&opt->diffopt); } return diff_result_code(&opt->diffopt, 0); diff --git a/builtin/diff.c b/builtin/diff.c index dd8ce688ba..54bb3de964 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -28,9 +28,9 @@ static const char builtin_diff_usage[] = "git diff [<options>] [<commit>] [--] [<path>...]\n" " or: git diff [<options>] --cached [--merge-base] [<commit>] [--] [<path>...]\n" " or: git diff [<options>] [--merge-base] <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" +" 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) @@ -352,7 +352,7 @@ static void symdiff_prepare(struct rev_info *rev, struct symdiff *sym) othercount++; continue; } - if (map == NULL) + if (!map) map = bitmap_new(); bitmap_set(map, i); } @@ -437,6 +437,11 @@ int cmd_diff(int argc, const char **argv, const char *prefix) prefix = setup_git_directory_gently(&nongit); + if (!nongit) { + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + } + if (!no_index) { /* * Treat git diff with at least one path outside of the @@ -589,7 +594,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) result = diff_result_code(&rev.diffopt, result); if (1 < rev.diffopt.skip_stat_unmatch) refresh_index_quietly(); - UNLEAK(rev); + release_revisions(&rev); UNLEAK(ent); UNLEAK(blob); return result; diff --git a/builtin/difftool.c b/builtin/difftool.c index 4931c10845..b3c509b8de 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -202,15 +202,10 @@ static void changed_files(struct hashmap *result, const char *index_path, { struct child_process update_index = CHILD_PROCESS_INIT; struct child_process diff_files = CHILD_PROCESS_INIT; - struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT; - const char *git_dir = absolute_path(get_git_dir()), *env[] = { - NULL, NULL - }; + struct strbuf buf = STRBUF_INIT; + const char *git_dir = absolute_path(get_git_dir()); FILE *fp; - strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path); - env[0] = index_env.buf; - strvec_pushl(&update_index.args, "--git-dir", git_dir, "--work-tree", workdir, "update-index", "--really-refresh", "-q", @@ -222,7 +217,7 @@ static void changed_files(struct hashmap *result, const char *index_path, update_index.use_shell = 0; update_index.clean_on_exit = 1; update_index.dir = workdir; - update_index.env = env; + strvec_pushf(&update_index.env, "GIT_INDEX_FILE=%s", index_path); /* Ignore any errors of update-index */ run_command(&update_index); @@ -235,7 +230,7 @@ static void changed_files(struct hashmap *result, const char *index_path, diff_files.clean_on_exit = 1; diff_files.out = -1; diff_files.dir = workdir; - diff_files.env = env; + strvec_pushf(&diff_files.env, "GIT_INDEX_FILE=%s", index_path); if (start_command(&diff_files)) die("could not obtain raw diff"); fp = xfdopen(diff_files.out, "r"); @@ -248,7 +243,6 @@ static void changed_files(struct hashmap *result, const char *index_path, fclose(fp); if (finish_command(&diff_files)) die("diff-files did not exit properly"); - strbuf_release(&index_env); strbuf_release(&buf); } @@ -681,7 +675,7 @@ static int run_file_diff(int prompt, const char *prefix, child->git_cmd = 1; child->dir = prefix; - strvec_pushv(&child->env_array, env); + strvec_pushv(&child->env, env); return run_command(child); } @@ -736,10 +730,11 @@ int cmd_difftool(int argc, const char **argv, const char *prefix) setenv(GIT_DIR_ENVIRONMENT, absolute_path(get_git_dir()), 1); setenv(GIT_WORK_TREE_ENVIRONMENT, absolute_path(get_git_work_tree()), 1); } else if (dir_diff) - die(_("--dir-diff is incompatible with --no-index")); + die(_("options '%s' and '%s' cannot be used together"), "--dir-diff", "--no-index"); - if (use_gui_tool + !!difftool_cmd + !!extcmd > 1) - die(_("--gui, --tool and --extcmd are mutually exclusive")); + die_for_incompatible_opt3(use_gui_tool, "--gui", + !!difftool_cmd, "--tool", + !!extcmd, "--extcmd"); if (use_gui_tool) setenv("GIT_MERGETOOL_GUI", "true", 1); diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 8e2caf7281..e1748fb98b 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -26,7 +26,7 @@ #include "commit-slab.h" static const char *fast_export_usage[] = { - N_("git fast-export [rev-list-opts]"), + N_("git fast-export [<rev-list-opts>]"), NULL }; @@ -107,18 +107,6 @@ static int parse_opt_reencode_mode(const struct option *opt, static struct decoration idnums; static uint32_t last_idnum; - -static int has_unshown_parent(struct commit *commit) -{ - struct commit_list *parent; - - for (parent = commit->parents; parent; parent = parent->next) - if (!(parent->item->object.flags & SHOWN) && - !(parent->item->object.flags & UNINTERESTING)) - return 1; - return 0; -} - struct anonymized_entry { struct hashmap_entry hash; const char *anon; @@ -312,7 +300,7 @@ static void export_blob(const struct object_id *oid) if (!buf) die("could not read blob %s", oid_to_hex(oid)); if (check_object_signature(the_repository, oid, buf, size, - type_name(type), NULL) < 0) + type) < 0) die("oid mismatch in blob %s", oid_to_hex(oid)); object = parse_object_buffer(the_repository, oid, type, size, buf, &eaten); @@ -752,20 +740,6 @@ static char *anonymize_tag(void *data) return strbuf_detach(&out, NULL); } -static void handle_tail(struct object_array *commits, struct rev_info *revs, - struct string_list *paths_of_changed_objects) -{ - struct commit *commit; - while (commits->nr) { - commit = (struct commit *)object_array_pop(commits); - if (has_unshown_parent(commit)) { - /* Queue again, to be handled later */ - add_object_array(&commit->object, NULL, commits); - return; - } - handle_commit(commit, revs, paths_of_changed_objects); - } -} static void handle_tag(const char *name, struct tag *tag) { @@ -1185,7 +1159,6 @@ static int parse_opt_anonymize_map(const struct option *opt, 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, @@ -1254,7 +1227,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) usage_with_options (fast_export_usage, options); if (anonymized_seeds.cmpfn && !anonymize) - die(_("--anonymize-map without --anonymize does not make sense")); + die(_("the option '%s' requires '%s'"), "--anonymize-map", "--anonymize"); if (refspecs_list.nr) { int i; @@ -1269,7 +1242,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) printf("feature done\n"); if (import_filename && import_filename_if_exists) - die(_("Cannot pass both --import-marks and --import-marks-if-exists")); + die(_("options '%s' and '%s' cannot be used together"), "--import-marks", "--import-marks-if-exists"); if (import_filename) import_marks(import_filename, 0); else if (import_filename_if_exists) @@ -1283,18 +1256,14 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (prepare_revision_walk(&revs)) die("revision walk setup failed"); + + revs.reverse = 1; revs.diffopt.format_callback = show_filemodify; revs.diffopt.format_callback_data = &paths_of_changed_objects; revs.diffopt.flags.recursive = 1; - while ((commit = get_revision(&revs))) { - if (has_unshown_parent(commit)) { - add_object_array(&commit->object, NULL, &commits); - } - else { - handle_commit(commit, &revs, &paths_of_changed_objects); - handle_tail(&commits, &revs, &paths_of_changed_objects); - } - } + revs.diffopt.no_free = 1; + while ((commit = get_revision(&revs))) + handle_commit(commit, &revs, &paths_of_changed_objects); handle_tags_and_duplicates(&extra_refs); handle_tags_and_duplicates(&tag_refs); @@ -1307,6 +1276,7 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) printf("done\n"); refspec_clear(&refspecs); + release_revisions(&revs); return 0; } diff --git a/builtin/fast-import.c b/builtin/fast-import.c index 20406f6775..28d3193c38 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -19,6 +19,7 @@ #include "mem-pool.h" #include "commit-reach.h" #include "khash.h" +#include "date.h" #define PACK_ID_BITS 16 #define MAX_PACK_ID ((1<<PACK_ID_BITS)-1) @@ -176,8 +177,9 @@ 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 }; +static struct mem_pool fi_mem_pool = { + .block_alloc = 2*1024*1024 - sizeof(struct mp_block), +}; /* Atom management */ static unsigned int atom_table_sz = 4451; @@ -205,7 +207,9 @@ 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 }; +static struct last_object last_blob = { + .data = STRBUF_INIT, + }; /* Tree management */ static unsigned int tree_entry_alloc = 1000; @@ -231,7 +235,10 @@ static struct tag *last_tag; 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_hist = { + .prev = &cmd_hist, + .next = &cmd_hist, +}; static struct recent_command *cmd_tail = &cmd_hist; static struct recent_command *rc_free; static unsigned int cmd_save = 100; @@ -401,16 +408,18 @@ static void dump_marks(void); static NORETURN void die_nicely(const char *err, va_list params) { + va_list cp; static int zombie; - char message[2 * PATH_MAX]; + report_fn die_message_fn = get_die_message_routine(); - vsnprintf(message, sizeof(message), err, params); - fputs("fatal: ", stderr); - fputs(message, stderr); - fputc('\n', stderr); + va_copy(cp, params); + die_message_fn(err, params); if (!zombie) { + char message[2 * PATH_MAX]; + zombie = 1; + vsnprintf(message, sizeof(message), err, cp); write_crash_report(message); end_packfile(); unkeep_all_packs(); @@ -856,7 +865,7 @@ static void end_packfile(void) struct tag *t; close_pack_windows(pack_data); - finalize_hashfile(pack_file, cur_pack_oid.hash, 0); + finalize_hashfile(pack_file, cur_pack_oid.hash, FSYNC_COMPONENT_PACK, 0); fixup_pack_header_footer(pack_data->pack_fd, pack_data->hash, pack_data->pack_name, object_count, cur_pack_oid.hash, pack_size); @@ -935,8 +944,8 @@ static int store_object( git_hash_ctx c; git_zstream s; - hdrlen = xsnprintf((char *)hdr, sizeof(hdr), "%s %lu", - type_name(type), (unsigned long)dat->len) + 1; + hdrlen = format_object_header((char *)hdr, sizeof(hdr), type, + dat->len); the_hash_algo->init_fn(&c); the_hash_algo->update_fn(&c, hdr, hdrlen); the_hash_algo->update_fn(&c, dat->buf, dat->len); @@ -1089,7 +1098,7 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark) hashfile_checkpoint(pack_file, &checkpoint); offset = checkpoint.offset; - hdrlen = xsnprintf((char *)out_buf, out_sz, "blob %" PRIuMAX, len) + 1; + hdrlen = format_object_header((char *)out_buf, out_sz, OBJ_BLOB, len); the_hash_algo->init_fn(&c); the_hash_algo->update_fn(&c, out_buf, hdrlen); @@ -2481,7 +2490,7 @@ static void note_change_n(const char *p, struct branch *b, unsigned char *old_fa unsigned long size; char *buf = read_object_with_reference(the_repository, &commit_oid, - commit_type, &size, + OBJ_COMMIT, &size, &commit_oid); if (!buf || size < the_hash_algo->hexsz + 6) die("Not a valid commit: %s", p); @@ -2553,7 +2562,7 @@ static void parse_from_existing(struct branch *b) char *buf; buf = read_object_with_reference(the_repository, - &b->oid, commit_type, &size, + &b->oid, OBJ_COMMIT, &size, &b->oid); parse_from_commit(b, buf, size); free(buf); @@ -2649,7 +2658,7 @@ static struct hash_list *parse_merge(unsigned int *count) unsigned long size; char *buf = read_object_with_reference(the_repository, &n->oid, - commit_type, + OBJ_COMMIT, &size, &n->oid); if (!buf || size < the_hash_algo->hexsz + 6) die("Not a valid commit: %s", from); diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index c2d96f4c89..f045bbbe94 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -153,11 +153,15 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) args.from_promisor = 1; continue; } - if (skip_prefix(arg, ("--" CL_ARG__FILTER "="), &arg)) { + if (!strcmp("--refetch", arg)) { + args.refetch = 1; + continue; + } + if (skip_prefix(arg, ("--filter="), &arg)) { parse_list_objects_filter(&args.filter_options, arg); continue; } - if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) { + if (!strcmp(arg, ("--no-filter"))) { list_objects_filter_set_no_filter(&args.filter_options); continue; } diff --git a/builtin/fetch.c b/builtin/fetch.c index f7abbc31ff..ac29c2b1ae 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -28,6 +28,7 @@ #include "promisor-remote.h" #include "commit-graph.h" #include "shallow.h" +#include "worktree.h" #define FORCED_UPDATES_DELAY_WARNING_IN_MS (10 * 1000) @@ -58,7 +59,7 @@ static int prune_tags = -1; /* unspecified */ 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 verbosity, deepen_relative, set_upstream, refetch; static int progress = -1; static int enable_auto_gc = 1; static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen; @@ -75,6 +76,7 @@ static struct transport *gtransport; static struct transport *gsecondary; static const char *submodule_prefix = ""; static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT; +static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT; static int recurse_submodules_default = RECURSE_SUBMODULES_ON_DEMAND; static int shown_url = 0; static struct refspec refmap = REFSPEC_INIT_FETCH; @@ -166,7 +168,7 @@ static struct option builtin_fetch_options[] = { 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")), - OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules, N_("on-demand"), + OPT_CALLBACK_F(0, "recurse-submodules", &recurse_submodules_cli, N_("on-demand"), N_("control recursive fetching of submodules"), PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules), OPT_BOOL(0, "dry-run", &dry_run, @@ -188,6 +190,9 @@ static struct option builtin_fetch_options[] = { OPT_SET_INT_F(0, "unshallow", &unshallow, N_("convert to a complete repository"), 1, PARSE_OPT_NONEG), + OPT_SET_INT_F(0, "refetch", &refetch, + N_("re-fetch without negotiating common commits"), + 1, PARSE_OPT_NONEG), { OPTION_STRING, 0, "submodule-prefix", &submodule_prefix, N_("dir"), N_("prepend this to submodule path output"), PARSE_OPT_HIDDEN }, OPT_CALLBACK_F(0, "recurse-submodules-default", @@ -222,17 +227,22 @@ static struct option builtin_fetch_options[] = { OPT_END() }; -static void unlock_pack(void) +static void unlock_pack(unsigned int flags) { if (gtransport) - transport_unlock_pack(gtransport); + transport_unlock_pack(gtransport, flags); if (gsecondary) - transport_unlock_pack(gsecondary); + transport_unlock_pack(gsecondary, flags); +} + +static void unlock_pack_atexit(void) +{ + unlock_pack(0); } static void unlock_pack_on_signal(int signo) { - unlock_pack(); + unlock_pack(TRANSPORT_UNLOCK_PACK_IN_SIGNAL_HANDLER); sigchain_pop(signo); raise(signo); } @@ -342,7 +352,19 @@ static void clear_item(struct refname_hash_entry *item) item->ignore = 1; } + +static void add_already_queued_tags(const char *refname, + const struct object_id *old_oid, + const struct object_id *new_oid, + void *cb_data) +{ + struct hashmap *queued_tags = cb_data; + if (starts_with(refname, "refs/tags/") && new_oid) + (void) refname_hash_add(queued_tags, refname, new_oid); +} + static void find_non_local_tags(const struct ref *refs, + struct ref_transaction *transaction, struct ref **head, struct ref ***tail) { @@ -360,6 +382,16 @@ static void find_non_local_tags(const struct ref *refs, create_fetch_oidset(head, &fetch_oids); for_each_ref(add_one_refname, &existing_refs); + + /* + * If we already have a transaction, then we need to filter out all + * tags which have already been queued up. + */ + if (transaction) + ref_transaction_for_each_queued_update(transaction, + add_already_queued_tags, + &existing_refs); + for (ref = refs; ref; ref = ref->next) { if (!starts_with(ref->name, "refs/tags/")) continue; @@ -552,7 +584,7 @@ static struct ref *get_ref_map(struct remote *remote, for (i = 0; i < fetch_refspec->nr; i++) get_fetch_map(ref_map, &fetch_refspec->items[i], &oref_tail, 1); } else if (refmap.nr) { - die("--refmap option is only meaningful with command-line refspec(s)."); + die("--refmap option is only meaningful with command-line refspec(s)"); } else { /* Use the defaults */ struct branch *branch = branch_get(NULL); @@ -583,7 +615,7 @@ static struct ref *get_ref_map(struct remote *remote, } else if (!prefetch) { ref_map = get_remote_ref(remote_refs, "HEAD"); if (!ref_map) - die(_("Couldn't find remote ref HEAD")); + die(_("couldn't find remote ref HEAD")); ref_map->fetch_head_status = FETCH_HEAD_MERGE; tail = &ref_map->next; } @@ -593,7 +625,7 @@ static struct ref *get_ref_map(struct remote *remote, /* also fetch all tags */ get_fetch_map(remote_refs, tag_refspec, &tail, 0); else if (tags == TAGS_DEFAULT && *autotags) - find_non_local_tags(remote_refs, &ref_map, &tail); + find_non_local_tags(remote_refs, NULL, &ref_map, &tail); /* Now append any refs to be updated opportunistically: */ *tail = orefs; @@ -757,8 +789,8 @@ static void prepare_format_display(struct ref *ref_map) else if (!strcasecmp(format, "compact")) compact_format = 1; else - die(_("configuration fetch.output contains invalid value %s"), - format); + die(_("invalid value for '%s': '%s'"), + "fetch.output", format); for (rm = ref_map; rm; rm = rm->next) { if (rm->status == REF_STATUS_REJECT_SHALLOW || @@ -848,13 +880,12 @@ static void format_display(struct strbuf *display, char code, static int update_local_ref(struct ref *ref, struct ref_transaction *transaction, - const char *remote, - const struct ref *remote_ref, - struct strbuf *display, - int summary_width) + const char *remote, const struct ref *remote_ref, + struct strbuf *display, int summary_width, + struct worktree **worktrees) { struct commit *current = NULL, *updated; - struct branch *current_branch = branch_get(NULL); + const struct worktree *wt; const char *pretty_ref = prettify_refname(ref->name); int fast_forward = 0; @@ -868,16 +899,17 @@ static int update_local_ref(struct ref *ref, return 0; } - if (current_branch && - !strcmp(ref->name, current_branch->name) && - !(update_head_ok || is_bare_repository()) && - !is_null_oid(&ref->old_oid)) { + if (!update_head_ok && + (wt = find_shared_symref(worktrees, "HEAD", ref->name)) && + !wt->is_bare && !is_null_oid(&ref->old_oid)) { /* * If this is the head, and it's not okay to update * the head, and the old value of the head isn't empty... */ format_display(display, '!', _("[rejected]"), - _("can't fetch in current branch"), + wt->is_current ? + _("can't fetch in current branch") : + _("checked out in another worktree"), remote, pretty_ref, summary_width); return 1; } @@ -995,7 +1027,7 @@ static int open_fetch_head(struct fetch_head *fetch_head) if (write_fetch_head) { fetch_head->fp = fopen(filename, "a"); if (!fetch_head->fp) - return error_errno(_("cannot open %s"), filename); + return error_errno(_("cannot open '%s'"), filename); strbuf_init(&fetch_head->buf, 0); } else { fetch_head->fp = NULL; @@ -1067,30 +1099,29 @@ static void close_fetch_head(struct fetch_head *fetch_head) } static const char warn_show_forced_updates[] = -N_("Fetch normally indicates which branches had a forced update,\n" - "but that check has been disabled. To re-enable, use '--show-forced-updates'\n" - "flag or run 'git config fetch.showForcedUpdates true'."); +N_("fetch normally indicates which branches had a forced update,\n" + "but that check has been disabled; to re-enable, use '--show-forced-updates'\n" + "flag or run 'git config fetch.showForcedUpdates true'"); static const char warn_time_show_forced_updates[] = -N_("It took %.2f seconds to check forced updates. You can use\n" +N_("it took %.2f seconds to check forced updates; you can use\n" "'--no-show-forced-updates' or run 'git config fetch.showForcedUpdates false'\n" - " to avoid this check.\n"); + "to avoid this check\n"); static int store_updated_refs(const char *raw_url, const char *remote_name, - int connectivity_checked, struct ref *ref_map) + int connectivity_checked, + struct ref_transaction *transaction, struct ref *ref_map, + struct fetch_head *fetch_head, struct worktree **worktrees) { - struct fetch_head fetch_head; int url_len, i, rc = 0; struct strbuf note = STRBUF_INIT, err = STRBUF_INIT; - struct ref_transaction *transaction = NULL; const char *what, *kind; struct ref *rm; char *url; int want_status; - int summary_width = transport_summary_width(ref_map); + int summary_width = 0; - rc = open_fetch_head(&fetch_head); - if (rc) - return -1; + if (verbosity >= 0) + summary_width = transport_summary_width(ref_map); if (raw_url) url = transport_anonymize_url(raw_url); @@ -1107,14 +1138,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, } } - if (atomic_fetch) { - transaction = ref_transaction_begin(&err); - if (!transaction) { - error("%s", err.buf); - goto abort; - } - } - prepare_format_display(ref_map); /* @@ -1126,7 +1149,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, want_status <= FETCH_HEAD_IGNORE; want_status++) { for (rm = ref_map; rm; rm = rm->next) { - struct commit *commit = NULL; struct ref *ref = NULL; if (rm->status == REF_STATUS_REJECT_SHALLOW) { @@ -1137,21 +1159,34 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, } /* - * References in "refs/tags/" are often going to point - * to annotated tags, which are not part of the - * commit-graph. We thus only try to look up refs in - * the graph which are not in that namespace to not - * regress performance in repositories with many - * annotated tags. + * When writing FETCH_HEAD we need to determine whether + * we already have the commit or not. If not, then the + * reference is not for merge and needs to be written + * to the reflog after other commits which we already + * have. We're not interested in this property though + * in case FETCH_HEAD is not to be updated, so we can + * skip the classification in that case. */ - if (!starts_with(rm->name, "refs/tags/")) - commit = lookup_commit_in_graph(the_repository, &rm->old_oid); - if (!commit) { - commit = lookup_commit_reference_gently(the_repository, - &rm->old_oid, - 1); - if (!commit) - rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE; + if (fetch_head->fp) { + struct commit *commit = NULL; + + /* + * References in "refs/tags/" are often going to point + * to annotated tags, which are not part of the + * commit-graph. We thus only try to look up refs in + * the graph which are not in that namespace to not + * regress performance in repositories with many + * annotated tags. + */ + if (!starts_with(rm->name, "refs/tags/")) + commit = lookup_commit_in_graph(the_repository, &rm->old_oid); + if (!commit) { + commit = lookup_commit_reference_gently(the_repository, + &rm->old_oid, + 1); + if (!commit) + rm->fetch_head_status = FETCH_HEAD_NOT_FOR_MERGE; + } } if (rm->fetch_head_status != want_status) @@ -1198,14 +1233,15 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, strbuf_addf(¬e, "'%s' of ", what); } - append_fetch_head(&fetch_head, &rm->old_oid, + append_fetch_head(fetch_head, &rm->old_oid, rm->fetch_head_status, note.buf, url, url_len); strbuf_reset(¬e); if (ref) { rc |= update_local_ref(ref, transaction, what, - rm, ¬e, summary_width); + rm, ¬e, summary_width, + worktrees); free(ref); } else if (write_fetch_head || dry_run) { /* @@ -1229,17 +1265,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, } } - if (!rc && transaction) { - rc = ref_transaction_commit(transaction, &err); - if (rc) { - error("%s", err.buf); - goto abort; - } - } - - if (!rc) - commit_fetch_head(&fetch_head); - if (rc & STORE_REF_ERROR_DF_CONFLICT) error(_("some local refs could not be updated; try running\n" " 'git remote prune %s' to remove any old, conflicting " @@ -1257,9 +1282,7 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, abort: strbuf_release(¬e); strbuf_release(&err); - ref_transaction_free(transaction); free(url); - close_fetch_head(&fetch_head); return rc; } @@ -1285,6 +1308,14 @@ static int check_exist_and_connected(struct ref *ref_map) return -1; /* + * Similarly, if we need to refetch, we always want to perform a full + * fetch ignoring existing objects. + */ + if (refetch) + return -1; + + + /* * check_connected() allows objects to merely be promised, but * we need all direct targets to exist. */ @@ -1298,7 +1329,11 @@ static int check_exist_and_connected(struct ref *ref_map) return check_connected(iterate_ref_map, &rm, &opt); } -static int fetch_and_consume_refs(struct transport *transport, struct ref *ref_map) +static int fetch_and_consume_refs(struct transport *transport, + struct ref_transaction *transaction, + struct ref *ref_map, + struct fetch_head *fetch_head, + struct worktree **worktrees) { int connectivity_checked = 1; int ret; @@ -1319,24 +1354,25 @@ static int fetch_and_consume_refs(struct transport *transport, struct ref *ref_m } trace2_region_enter("fetch", "consume_refs", the_repository); - ret = store_updated_refs(transport->url, - transport->remote->name, - connectivity_checked, - ref_map); + ret = store_updated_refs(transport->url, transport->remote->name, + connectivity_checked, transaction, ref_map, + fetch_head, worktrees); trace2_region_leave("fetch", "consume_refs", the_repository); out: - transport_unlock_pack(transport); + transport_unlock_pack(transport, 0); return ret; } -static int prune_refs(struct refspec *rs, struct ref *ref_map, +static int prune_refs(struct refspec *rs, + struct ref_transaction *transaction, + struct ref *ref_map, const char *raw_url) { int url_len, i, result = 0; struct ref *ref, *stale_refs = get_stale_heads(rs, ref_map); + struct strbuf err = STRBUF_INIT; char *url; - int summary_width = transport_summary_width(stale_refs); const char *dangling_msg = dry_run ? _(" (%s will become dangling)") : _(" (%s has become dangling)"); @@ -1355,16 +1391,27 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map, url_len = i - 3; if (!dry_run) { - struct string_list refnames = STRING_LIST_INIT_NODUP; + if (transaction) { + for (ref = stale_refs; ref; ref = ref->next) { + result = ref_transaction_delete(transaction, ref->name, NULL, 0, + "fetch: prune", &err); + if (result) + goto cleanup; + } + } else { + struct string_list refnames = STRING_LIST_INIT_NODUP; - for (ref = stale_refs; ref; ref = ref->next) - string_list_append(&refnames, ref->name); + for (ref = stale_refs; ref; ref = ref->next) + string_list_append(&refnames, ref->name); - result = delete_refs("fetch: prune", &refnames, 0); - string_list_clear(&refnames, 0); + result = delete_refs("fetch: prune", &refnames, 0); + string_list_clear(&refnames, 0); + } } if (verbosity >= 0) { + int summary_width = transport_summary_width(stale_refs); + for (ref = stale_refs; ref; ref = ref->next) { struct strbuf sb = STRBUF_INIT; if (!shown_url) { @@ -1380,23 +1427,26 @@ static int prune_refs(struct refspec *rs, struct ref *ref_map, } } +cleanup: + strbuf_release(&err); free(url); free_refs(stale_refs); return result; } -static void check_not_current_branch(struct ref *ref_map) +static void check_not_current_branch(struct ref *ref_map, + struct worktree **worktrees) { - struct branch *current_branch = branch_get(NULL); - - if (is_bare_repository() || !current_branch) - return; - + const struct worktree *wt; for (; ref_map; ref_map = ref_map->next) - if (ref_map->peer_ref && !strcmp(current_branch->refname, - ref_map->peer_ref->name)) - die(_("Refusing to fetch into current branch %s " - "of non-bare repository"), current_branch->refname); + if (ref_map->peer_ref && + starts_with(ref_map->peer_ref->name, "refs/heads/") && + (wt = find_shared_symref(worktrees, "HEAD", + ref_map->peer_ref->name)) && + !wt->is_bare) + die(_("refusing to fetch into branch '%s' " + "checked out at '%s'"), + ref_map->peer_ref->name, wt->path); } static int truncate_fetch_head(void) @@ -1405,7 +1455,7 @@ static int truncate_fetch_head(void) FILE *fp = fopen_for_writing(filename); if (!fp) - return error_errno(_("cannot open %s"), filename); + return error_errno(_("cannot open '%s'"), filename); fclose(fp); return 0; } @@ -1414,10 +1464,10 @@ static void set_option(struct transport *transport, const char *name, const char { int r = transport_set_option(transport, name, value); if (r < 0) - die(_("Option \"%s\" value \"%s\" is not valid for %s"), + die(_("option \"%s\" value \"%s\" is not valid for %s"), name, value, transport->url); if (r > 0) - warning(_("Option \"%s\" is ignored for %s\n"), + warning(_("option \"%s\" is ignored for %s\n"), name, transport->url); } @@ -1451,7 +1501,7 @@ static void add_negotiation_tips(struct git_transport_options *smart_options) old_nr = oids->nr; for_each_glob_ref(add_oid, s, oids); if (old_nr == oids->nr) - warning("Ignoring --negotiation-tip=%s because it does not match any refs", + warning("ignoring --negotiation-tip=%s because it does not match any refs", s); } smart_options->negotiation_tips = oids; @@ -1479,6 +1529,8 @@ static struct transport *prepare_transport(struct remote *remote, int deepen) set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, "yes"); if (update_shallow) set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes"); + if (refetch) + set_option(transport, TRANS_OPT_REFETCH, "yes"); if (filter_options.choice) { const char *spec = expand_list_objects_filter_spec(&filter_options); @@ -1489,14 +1541,18 @@ static struct transport *prepare_transport(struct remote *remote, int deepen) if (transport->smart_options) add_negotiation_tips(transport->smart_options); else - warning("Ignoring --negotiation-tip because the protocol does not support it."); + warning("ignoring --negotiation-tip because the protocol does not support it"); } return transport; } -static void backfill_tags(struct transport *transport, struct ref *ref_map) +static int backfill_tags(struct transport *transport, + struct ref_transaction *transaction, + struct ref *ref_map, + struct fetch_head *fetch_head, + struct worktree **worktrees) { - int cannot_reuse; + int retcode, cannot_reuse; /* * Once we have set TRANS_OPT_DEEPEN_SINCE, we can't unset it @@ -1515,24 +1571,30 @@ static void backfill_tags(struct transport *transport, struct ref *ref_map) transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, NULL); transport_set_option(transport, TRANS_OPT_DEPTH, "0"); transport_set_option(transport, TRANS_OPT_DEEPEN_RELATIVE, NULL); - fetch_and_consume_refs(transport, ref_map); + retcode = fetch_and_consume_refs(transport, transaction, ref_map, fetch_head, worktrees); if (gsecondary) { transport_disconnect(gsecondary); gsecondary = NULL; } + + return retcode; } static int do_fetch(struct transport *transport, struct refspec *rs) { - struct ref *ref_map; + struct ref_transaction *transaction = NULL; + struct ref *ref_map = NULL; int autotags = (transport->remote->fetch_tags == 1); int retcode = 0; const struct ref *remote_refs; struct transport_ls_refs_options transport_ls_refs_options = TRANSPORT_LS_REFS_OPTIONS_INIT; int must_list_refs = 1; + struct worktree **worktrees = get_worktrees(); + struct fetch_head fetch_head = { 0 }; + struct strbuf err = STRBUF_INIT; if (tags == TAGS_DEFAULT) { if (transport->remote->fetch_tags == 2) @@ -1583,12 +1645,24 @@ static int do_fetch(struct transport *transport, } else remote_refs = NULL; - strvec_clear(&transport_ls_refs_options.ref_prefixes); + transport_ls_refs_options_release(&transport_ls_refs_options); ref_map = get_ref_map(transport->remote, remote_refs, rs, tags, &autotags); if (!update_head_ok) - check_not_current_branch(ref_map); + check_not_current_branch(ref_map, worktrees); + + retcode = open_fetch_head(&fetch_head); + if (retcode) + goto cleanup; + + if (atomic_fetch) { + transaction = ref_transaction_begin(&err); + if (!transaction) { + retcode = error("%s", err.buf); + goto cleanup; + } + } if (tags == TAGS_DEFAULT && autotags) transport_set_option(transport, TRANS_OPT_FOLLOWTAGS, "1"); @@ -1599,19 +1673,61 @@ static int do_fetch(struct transport *transport, * don't care whether --tags was specified. */ if (rs->nr) { - prune_refs(rs, ref_map, transport->url); + retcode = prune_refs(rs, transaction, ref_map, transport->url); } else { - prune_refs(&transport->remote->fetch, - ref_map, - transport->url); + retcode = prune_refs(&transport->remote->fetch, + transaction, ref_map, + transport->url); } + if (retcode != 0) + retcode = 1; } - if (fetch_and_consume_refs(transport, ref_map)) { - free_refs(ref_map); + + if (fetch_and_consume_refs(transport, transaction, ref_map, &fetch_head, worktrees)) { retcode = 1; goto cleanup; } + /* + * If neither --no-tags nor --tags was specified, do automated tag + * following. + */ + if (tags == TAGS_DEFAULT && autotags) { + struct ref *tags_ref_map = NULL, **tail = &tags_ref_map; + + find_non_local_tags(remote_refs, transaction, &tags_ref_map, &tail); + if (tags_ref_map) { + /* + * If backfilling of tags fails then we want to tell + * the user so, but we have to continue regardless to + * populate upstream information of the references we + * have already fetched above. The exception though is + * when `--atomic` is passed: in that case we'll abort + * the transaction and don't commit anything. + */ + if (backfill_tags(transport, transaction, tags_ref_map, + &fetch_head, worktrees)) + retcode = 1; + } + + free_refs(tags_ref_map); + } + + if (transaction) { + if (retcode) + goto cleanup; + + retcode = ref_transaction_commit(transaction, &err); + if (retcode) { + error("%s", err.buf); + ref_transaction_free(transaction); + transaction = NULL; + goto cleanup; + } + } + + commit_fetch_head(&fetch_head); + if (set_upstream) { struct branch *branch = branch_get("HEAD"); struct ref *rm; @@ -1631,13 +1747,23 @@ static int do_fetch(struct transport *transport, if (!rm->peer_ref) { if (source_ref) { warning(_("multiple branches detected, incompatible with --set-upstream")); - goto skip; + goto cleanup; } else { source_ref = rm; } } } if (source_ref) { + if (!branch) { + const char *shortname = source_ref->name; + skip_prefix(shortname, "refs/heads/", &shortname); + + warning(_("could not set upstream of HEAD to '%s' from '%s' when " + "it does not point to any branch."), + shortname, transport->remote->name); + goto cleanup; + } + if (!strcmp(source_ref->name, "HEAD") || starts_with(source_ref->name, "refs/heads/")) install_branch_config(0, @@ -1651,25 +1777,21 @@ static int do_fetch(struct transport *transport, else warning(_("unknown branch type")); } else { - warning(_("no source branch found.\n" - "you need to specify exactly one branch with the --set-upstream option.")); + 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 - * following ... */ - if (tags == TAGS_DEFAULT && autotags) { - struct ref **tail = &ref_map; - ref_map = NULL; - find_non_local_tags(remote_refs, &ref_map, &tail); - if (ref_map) - backfill_tags(transport, ref_map); - free_refs(ref_map); +cleanup: + if (retcode && transaction) { + ref_transaction_abort(transaction, &err); + error("%s", err.buf); } - cleanup: + close_fetch_head(&fetch_head); + strbuf_release(&err); + free_refs(ref_map); + free_worktrees(worktrees); return retcode; } @@ -1790,7 +1912,7 @@ 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); + state->result = error(_("could not fetch %s"), remote); return 0; } @@ -1845,7 +1967,7 @@ static int fetch_multiple(struct string_list *list, int max_children) if (verbosity >= 0) printf(_("Fetching %s\n"), name); if (run_command_v_opt(argv.v, RUN_GIT_CMD)) { - error(_("Could not fetch %s"), name); + error(_("could not fetch %s"), name); result = 1; } strvec_pop(&argv); @@ -1906,8 +2028,8 @@ static int fetch_one(struct remote *remote, int argc, const char **argv, int remote_via_config = remote_is_configured(remote, 0); if (!remote) - die(_("No remote repository specified. Please, specify either a URL or a\n" - "remote name from which new revisions should be fetched.")); + die(_("no remote repository specified; please specify either a URL or a\n" + "remote name from which new revisions should be fetched")); gtransport = prepare_transport(remote, 1); @@ -1942,7 +2064,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv, if (!strcmp(argv[i], "tag")) { i++; if (i >= argc) - die(_("You need to specify a tag name.")); + die(_("you need to specify a tag name")); refspec_appendf(&rs, "refs/tags/%s:refs/tags/%s", argv[i], argv[i]); @@ -1962,7 +2084,7 @@ static int fetch_one(struct remote *remote, int argc, const char **argv, gtransport->server_options = &server_options; sigchain_push_common(unlock_pack_on_signal); - atexit(unlock_pack); + atexit(unlock_pack_atexit); sigchain_push(SIGPIPE, SIG_IGN); exit_code = do_fetch(gtransport, &rs); sigchain_pop(SIGPIPE); @@ -1993,9 +2115,35 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) } git_config(git_fetch_config, NULL); + if (the_repository->gitdir) { + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + } argc = parse_options(argc, argv, prefix, builtin_fetch_options, builtin_fetch_usage, 0); + + if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT) + recurse_submodules = recurse_submodules_cli; + + if (negotiate_only) { + switch (recurse_submodules_cli) { + case RECURSE_SUBMODULES_OFF: + case RECURSE_SUBMODULES_DEFAULT: + /* + * --negotiate-only should never recurse into + * submodules. Skip it by setting recurse_submodules to + * RECURSE_SUBMODULES_OFF. + */ + recurse_submodules = RECURSE_SUBMODULES_OFF; + break; + + default: + die(_("options '%s' and '%s' cannot be used together"), + "--negotiate-only", "--recurse-submodules"); + } + } + if (recurse_submodules != RECURSE_SUBMODULES_OFF) { int *sfjc = submodule_fetch_jobs_config == -1 ? &submodule_fetch_jobs_config : NULL; @@ -2006,18 +2154,18 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) } if (negotiate_only && !negotiation_tip.nr) - die(_("--negotiate-only needs one or more --negotiate-tip=*")); + die(_("--negotiate-only needs one or more --negotiation-tip=*")); if (deepen_relative) { if (deepen_relative < 0) - die(_("Negative depth in --deepen is not supported")); + die(_("negative depth in --deepen is not supported")); if (depth) - die(_("--deepen and --depth are mutually exclusive")); + die(_("options '%s' and '%s' cannot be used together"), "--deepen", "--depth"); depth = xstrfmt("%d", deepen_relative); } if (unshallow) { if (depth) - die(_("--depth and --unshallow cannot be used together")); + die(_("options '%s' and '%s' cannot be used together"), "--depth", "--unshallow"); else if (!is_repository_shallow(the_repository)) die(_("--unshallow on a complete repository does not make sense")); else @@ -2040,6 +2188,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) else if (argc > 1) die(_("fetch --all does not make sense with refspecs")); (void) for_each_remote(get_one_remote_for_fetch, &list); + + /* do not do fetch_multiple() of one */ + if (list.nr == 1) + remote = remote_get(list.items[0].string); } else if (argc == 0) { /* No arguments -- use default remote */ remote = remote_get(NULL); @@ -2047,14 +2199,15 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) /* All arguments are assumed to be remotes or groups */ for (i = 0; i < argc; i++) if (!add_remote_or_group(argv[i], &list)) - die(_("No such remote or remote group: %s"), argv[i]); + die(_("no such remote or remote group: %s"), + argv[i]); } else { /* Single remote or group */ (void) add_remote_or_group(argv[0], &list); if (list.nr > 1) { /* More than one remote */ if (argc > 1) - die(_("Fetching a group and specifying refspecs does not make sense")); + die(_("fetching a group and specifying refspecs does not make sense")); } else { /* Zero or one remotes */ remote = remote_get(argv[0]); @@ -2075,8 +2228,9 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) if (gtransport->smart_options) { gtransport->smart_options->acked_commits = &acked_commits; } else { - warning(_("Protocol does not support --negotiate-only, exiting.")); - return 1; + warning(_("protocol does not support --negotiate-only, exiting")); + result = 1; + goto cleanup; } if (server_options.nr) gtransport->server_options = &server_options; @@ -2112,7 +2266,17 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) result = fetch_multiple(&list, max_children); } - if (!result && (recurse_submodules != RECURSE_SUBMODULES_OFF)) { + + /* + * This is only needed after fetch_one(), which does not fetch + * submodules by itself. + * + * When we fetch from multiple remotes, fetch_multiple() has + * already updated submodules to grab commits necessary for + * the fetched history from each remote, so there is no need + * to fetch submodules from here. + */ + if (!result && remote && (recurse_submodules != RECURSE_SUBMODULES_OFF)) { struct strvec options = STRVEC_INIT; int max_children = max_jobs; @@ -2122,17 +2286,26 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) max_children = fetch_parallel_config; add_options_to_argv(&options); - result = fetch_populated_submodules(the_repository, - &options, - submodule_prefix, - recurse_submodules, - recurse_submodules_default, - verbosity < 0, - max_children); + result = fetch_submodules(the_repository, + &options, + submodule_prefix, + recurse_submodules, + recurse_submodules_default, + verbosity < 0, + max_children); strvec_clear(&options); } - string_list_clear(&list, 0); + /* + * Skip irrelevant tasks because we know objects were not + * fetched. + * + * NEEDSWORK: as a future optimization, we can return early + * whenever objects were not fetched e.g. if we already have all + * of them. + */ + if (negotiate_only) + goto cleanup; prepare_repo_settings(the_repository); if (fetch_write_commit_graph > 0 || @@ -2148,8 +2321,27 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) NULL); } - if (enable_auto_gc) + if (enable_auto_gc) { + if (refetch) { + /* + * Hint auto-maintenance strongly to encourage repacking, + * but respect config settings disabling it. + */ + int opt_val; + if (git_config_get_int("gc.autopacklimit", &opt_val)) + opt_val = -1; + if (opt_val != 0) + git_config_push_parameter("gc.autoPackLimit=1"); + + if (git_config_get_int("maintenance.incremental-repack.auto", &opt_val)) + opt_val = -1; + if (opt_val != 0) + git_config_push_parameter("maintenance.incremental-repack.auto=-1"); + } run_auto_maintenance(verbosity < 0); + } + cleanup: + string_list_clear(&list, 0); return result; } diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index 48a8699de7..8d8fd393f8 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -12,6 +12,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) { const char *inpath = NULL; const char *message = NULL; + char *into_name = NULL; int shortlog_len = -1; struct option options[] = { { OPTION_INTEGER, 0, "log", &shortlog_len, N_("n"), @@ -23,6 +24,8 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) DEFAULT_MERGE_LOG_LEN }, OPT_STRING('m', "message", &message, N_("text"), N_("use <text> as start of message")), + OPT_STRING(0, "into-name", &into_name, N_("name"), + N_("use <name> instead of the real target branch")), OPT_FILENAME('F', "file", &inpath, N_("file to read from")), OPT_END() }; @@ -56,6 +59,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix) opts.add_title = !message; opts.credit_people = 1; opts.shortlog_len = shortlog_len; + opts.into_name = into_name; ret = fmt_merge_msg(&input, &output, &opts); if (ret) diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 16a2c7d57c..6f62f40d12 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -17,7 +17,8 @@ static char const * const for_each_ref_usage[] = { int cmd_for_each_ref(int argc, const char **argv, const char *prefix) { int i; - struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; + struct ref_sorting *sorting; + struct string_list sorting_options = STRING_LIST_INIT_DUP; int maxcount = 0, icase = 0; struct ref_array array; struct ref_filter filter; @@ -39,7 +40,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) OPT_INTEGER( 0 , "count", &maxcount, N_("show only <n> matched refs")), OPT_STRING( 0 , "format", &format.format, N_("format"), N_("format to use for the output")), OPT__COLOR(&format.use_color, N_("respect format colors")), - OPT_REF_SORT(sorting_tail), + OPT_REF_SORT(&sorting_options), OPT_CALLBACK(0, "points-at", &filter.points_at, N_("object"), N_("print only refs which points at the given object"), parse_opt_object_name), @@ -70,8 +71,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) if (verify_ref_format(&format)) usage_with_options(for_each_ref_usage, opts); - if (!sorting) - sorting = ref_default_sorting(); + sorting = ref_sorting_options(&sorting_options); ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase); filter.ignore_case = icase; diff --git a/builtin/fsck.c b/builtin/fsck.c index 27b9e78094..9e54892311 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -944,15 +944,13 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) if (the_repository->settings.core_commit_graph) { struct child_process commit_graph_verify = CHILD_PROCESS_INIT; - const char *verify_argv[] = { "commit-graph", "verify", NULL, NULL, NULL }; prepare_alt_odb(the_repository); for (odb = the_repository->objects->odb; odb; odb = odb->next) { child_process_init(&commit_graph_verify); - commit_graph_verify.argv = verify_argv; commit_graph_verify.git_cmd = 1; - verify_argv[2] = "--object-dir"; - verify_argv[3] = odb->path; + strvec_pushl(&commit_graph_verify.args, "commit-graph", + "verify", "--object-dir", odb->path, NULL); if (run_command(&commit_graph_verify)) errors_found |= ERROR_COMMIT_GRAPH; } @@ -960,15 +958,13 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) if (the_repository->settings.core_multi_pack_index) { struct child_process midx_verify = CHILD_PROCESS_INIT; - const char *midx_argv[] = { "multi-pack-index", "verify", NULL, NULL, NULL }; prepare_alt_odb(the_repository); for (odb = the_repository->objects->odb; odb; odb = odb->next) { child_process_init(&midx_verify); - midx_verify.argv = midx_argv; midx_verify.git_cmd = 1; - midx_argv[2] = "--object-dir"; - midx_argv[3] = odb->path; + strvec_pushl(&midx_verify.args, "multi-pack-index", + "verify", "--object-dir", odb->path, NULL); if (run_command(&midx_verify)) errors_found |= ERROR_MULTI_PACK_INDEX; } diff --git a/builtin/fsmonitor--daemon.c b/builtin/fsmonitor--daemon.c new file mode 100644 index 0000000000..2c109cf8b3 --- /dev/null +++ b/builtin/fsmonitor--daemon.c @@ -0,0 +1,1577 @@ +#include "builtin.h" +#include "config.h" +#include "parse-options.h" +#include "fsmonitor.h" +#include "fsmonitor-ipc.h" +#include "compat/fsmonitor/fsm-health.h" +#include "compat/fsmonitor/fsm-listen.h" +#include "fsmonitor--daemon.h" +#include "simple-ipc.h" +#include "khash.h" +#include "pkt-line.h" + +static const char * const builtin_fsmonitor__daemon_usage[] = { + N_("git fsmonitor--daemon start [<options>]"), + N_("git fsmonitor--daemon run [<options>]"), + N_("git fsmonitor--daemon stop"), + N_("git fsmonitor--daemon status"), + NULL +}; + +#ifdef HAVE_FSMONITOR_DAEMON_BACKEND +/* + * Global state loaded from config. + */ +#define FSMONITOR__IPC_THREADS "fsmonitor.ipcthreads" +static int fsmonitor__ipc_threads = 8; + +#define FSMONITOR__START_TIMEOUT "fsmonitor.starttimeout" +static int fsmonitor__start_timeout_sec = 60; + +#define FSMONITOR__ANNOUNCE_STARTUP "fsmonitor.announcestartup" +static int fsmonitor__announce_startup = 0; + +static int fsmonitor_config(const char *var, const char *value, void *cb) +{ + if (!strcmp(var, FSMONITOR__IPC_THREADS)) { + int i = git_config_int(var, value); + if (i < 1) + return error(_("value of '%s' out of range: %d"), + FSMONITOR__IPC_THREADS, i); + fsmonitor__ipc_threads = i; + return 0; + } + + if (!strcmp(var, FSMONITOR__START_TIMEOUT)) { + int i = git_config_int(var, value); + if (i < 0) + return error(_("value of '%s' out of range: %d"), + FSMONITOR__START_TIMEOUT, i); + fsmonitor__start_timeout_sec = i; + return 0; + } + + if (!strcmp(var, FSMONITOR__ANNOUNCE_STARTUP)) { + int is_bool; + int i = git_config_bool_or_int(var, value, &is_bool); + if (i < 0) + return error(_("value of '%s' not bool or int: %d"), + var, i); + fsmonitor__announce_startup = i; + return 0; + } + + return git_default_config(var, value, cb); +} + +/* + * Acting as a CLIENT. + * + * Send a "quit" command to the `git-fsmonitor--daemon` (if running) + * and wait for it to shutdown. + */ +static int do_as_client__send_stop(void) +{ + struct strbuf answer = STRBUF_INIT; + int ret; + + ret = fsmonitor_ipc__send_command("quit", &answer); + + /* The quit command does not return any response data. */ + strbuf_release(&answer); + + if (ret) + return ret; + + trace2_region_enter("fsm_client", "polling-for-daemon-exit", NULL); + while (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING) + sleep_millisec(50); + trace2_region_leave("fsm_client", "polling-for-daemon-exit", NULL); + + return 0; +} + +static int do_as_client__status(void) +{ + enum ipc_active_state state = fsmonitor_ipc__get_state(); + + switch (state) { + case IPC_STATE__LISTENING: + printf(_("fsmonitor-daemon is watching '%s'\n"), + the_repository->worktree); + return 0; + + default: + printf(_("fsmonitor-daemon is not watching '%s'\n"), + the_repository->worktree); + return 1; + } +} + +enum fsmonitor_cookie_item_result { + FCIR_ERROR = -1, /* could not create cookie file ? */ + FCIR_INIT, + FCIR_SEEN, + FCIR_ABORT, +}; + +struct fsmonitor_cookie_item { + struct hashmap_entry entry; + char *name; + enum fsmonitor_cookie_item_result result; +}; + +static int cookies_cmp(const void *data, const struct hashmap_entry *he1, + const struct hashmap_entry *he2, const void *keydata) +{ + const struct fsmonitor_cookie_item *a = + container_of(he1, const struct fsmonitor_cookie_item, entry); + const struct fsmonitor_cookie_item *b = + container_of(he2, const struct fsmonitor_cookie_item, entry); + + return strcmp(a->name, keydata ? keydata : b->name); +} + +static enum fsmonitor_cookie_item_result with_lock__wait_for_cookie( + struct fsmonitor_daemon_state *state) +{ + /* assert current thread holding state->main_lock */ + + int fd; + struct fsmonitor_cookie_item *cookie; + struct strbuf cookie_pathname = STRBUF_INIT; + struct strbuf cookie_filename = STRBUF_INIT; + enum fsmonitor_cookie_item_result result; + int my_cookie_seq; + + CALLOC_ARRAY(cookie, 1); + + my_cookie_seq = state->cookie_seq++; + + strbuf_addf(&cookie_filename, "%i-%i", getpid(), my_cookie_seq); + + strbuf_addbuf(&cookie_pathname, &state->path_cookie_prefix); + strbuf_addbuf(&cookie_pathname, &cookie_filename); + + cookie->name = strbuf_detach(&cookie_filename, NULL); + cookie->result = FCIR_INIT; + hashmap_entry_init(&cookie->entry, strhash(cookie->name)); + + hashmap_add(&state->cookies, &cookie->entry); + + trace_printf_key(&trace_fsmonitor, "cookie-wait: '%s' '%s'", + cookie->name, cookie_pathname.buf); + + /* + * Create the cookie file on disk and then wait for a notification + * that the listener thread has seen it. + */ + fd = open(cookie_pathname.buf, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) { + error_errno(_("could not create fsmonitor cookie '%s'"), + cookie->name); + + cookie->result = FCIR_ERROR; + goto done; + } + + /* + * Technically, close() and unlink() can fail, but we don't + * care here. We only created the file to trigger a watch + * event from the FS to know that when we're up to date. + */ + close(fd); + unlink(cookie_pathname.buf); + + /* + * Technically, this is an infinite wait (well, unless another + * thread sends us an abort). I'd like to change this to + * use `pthread_cond_timedwait()` and return an error/timeout + * and let the caller do the trivial response thing, but we + * don't have that routine in our thread-utils. + * + * After extensive beta testing I'm not really worried about + * this. Also note that the above open() and unlink() calls + * will cause at least two FS events on that path, so the odds + * of getting stuck are pretty slim. + */ + while (cookie->result == FCIR_INIT) + pthread_cond_wait(&state->cookies_cond, + &state->main_lock); + +done: + hashmap_remove(&state->cookies, &cookie->entry, NULL); + + result = cookie->result; + + free(cookie->name); + free(cookie); + strbuf_release(&cookie_pathname); + + return result; +} + +/* + * Mark these cookies as _SEEN and wake up the corresponding client threads. + */ +static void with_lock__mark_cookies_seen(struct fsmonitor_daemon_state *state, + const struct string_list *cookie_names) +{ + /* assert current thread holding state->main_lock */ + + int k; + int nr_seen = 0; + + for (k = 0; k < cookie_names->nr; k++) { + struct fsmonitor_cookie_item key; + struct fsmonitor_cookie_item *cookie; + + key.name = cookie_names->items[k].string; + hashmap_entry_init(&key.entry, strhash(key.name)); + + cookie = hashmap_get_entry(&state->cookies, &key, entry, NULL); + if (cookie) { + trace_printf_key(&trace_fsmonitor, "cookie-seen: '%s'", + cookie->name); + cookie->result = FCIR_SEEN; + nr_seen++; + } + } + + if (nr_seen) + pthread_cond_broadcast(&state->cookies_cond); +} + +/* + * Set _ABORT on all pending cookies and wake up all client threads. + */ +static void with_lock__abort_all_cookies(struct fsmonitor_daemon_state *state) +{ + /* assert current thread holding state->main_lock */ + + struct hashmap_iter iter; + struct fsmonitor_cookie_item *cookie; + int nr_aborted = 0; + + hashmap_for_each_entry(&state->cookies, &iter, cookie, entry) { + trace_printf_key(&trace_fsmonitor, "cookie-abort: '%s'", + cookie->name); + cookie->result = FCIR_ABORT; + nr_aborted++; + } + + if (nr_aborted) + pthread_cond_broadcast(&state->cookies_cond); +} + +/* + * Requests to and from a FSMonitor Protocol V2 provider use an opaque + * "token" as a virtual timestamp. Clients can request a summary of all + * created/deleted/modified files relative to a token. In the response, + * clients receive a new token for the next (relative) request. + * + * + * Token Format + * ============ + * + * The contents of the token are private and provider-specific. + * + * For the built-in fsmonitor--daemon, we define a token as follows: + * + * "builtin" ":" <token_id> ":" <sequence_nr> + * + * The "builtin" prefix is used as a namespace to avoid conflicts + * with other providers (such as Watchman). + * + * The <token_id> is an arbitrary OPAQUE string, such as a GUID, + * UUID, or {timestamp,pid}. It is used to group all filesystem + * events that happened while the daemon was monitoring (and in-sync + * with the filesystem). + * + * Unlike FSMonitor Protocol V1, it is not defined as a timestamp + * and does not define less-than/greater-than relationships. + * (There are too many race conditions to rely on file system + * event timestamps.) + * + * The <sequence_nr> is a simple integer incremented whenever the + * daemon needs to make its state public. For example, if 1000 file + * system events come in, but no clients have requested the data, + * the daemon can continue to accumulate file changes in the same + * bin and does not need to advance the sequence number. However, + * as soon as a client does arrive, the daemon needs to start a new + * bin and increment the sequence number. + * + * The sequence number serves as the boundary between 2 sets + * of bins -- the older ones that the client has already seen + * and the newer ones that it hasn't. + * + * When a new <token_id> is created, the <sequence_nr> is reset to + * zero. + * + * + * About Token Ids + * =============== + * + * A new token_id is created: + * + * [1] each time the daemon is started. + * + * [2] any time that the daemon must re-sync with the filesystem + * (such as when the kernel drops or we miss events on a very + * active volume). + * + * [3] in response to a client "flush" command (for dropped event + * testing). + * + * When a new token_id is created, the daemon is free to discard all + * cached filesystem events associated with any previous token_ids. + * Events associated with a non-current token_id will never be sent + * to a client. A token_id change implicitly means that the daemon + * has gap in its event history. + * + * Therefore, clients that present a token with a stale (non-current) + * token_id will always be given a trivial response. + */ +struct fsmonitor_token_data { + struct strbuf token_id; + struct fsmonitor_batch *batch_head; + struct fsmonitor_batch *batch_tail; + uint64_t client_ref_count; +}; + +struct fsmonitor_batch { + struct fsmonitor_batch *next; + uint64_t batch_seq_nr; + const char **interned_paths; + size_t nr, alloc; + time_t pinned_time; +}; + +static struct fsmonitor_token_data *fsmonitor_new_token_data(void) +{ + static int test_env_value = -1; + static uint64_t flush_count = 0; + struct fsmonitor_token_data *token; + struct fsmonitor_batch *batch; + + CALLOC_ARRAY(token, 1); + batch = fsmonitor_batch__new(); + + strbuf_init(&token->token_id, 0); + token->batch_head = batch; + token->batch_tail = batch; + token->client_ref_count = 0; + + if (test_env_value < 0) + test_env_value = git_env_bool("GIT_TEST_FSMONITOR_TOKEN", 0); + + if (!test_env_value) { + struct timeval tv; + struct tm tm; + time_t secs; + + gettimeofday(&tv, NULL); + secs = tv.tv_sec; + gmtime_r(&secs, &tm); + + strbuf_addf(&token->token_id, + "%"PRIu64".%d.%4d%02d%02dT%02d%02d%02d.%06ldZ", + flush_count++, + getpid(), + tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, + (long)tv.tv_usec); + } else { + strbuf_addf(&token->token_id, "test_%08x", test_env_value++); + } + + /* + * We created a new <token_id> and are starting a new series + * of tokens with a zero <seq_nr>. + * + * Since clients cannot guess our new (non test) <token_id> + * they will always receive a trivial response (because of the + * mismatch on the <token_id>). The trivial response will + * tell them our new <token_id> so that subsequent requests + * will be relative to our new series. (And when sending that + * response, we pin the current head of the batch list.) + * + * Even if the client correctly guesses the <token_id>, their + * request of "builtin:<token_id>:0" asks for all changes MORE + * RECENT than batch/bin 0. + * + * This implies that it is a waste to accumulate paths in the + * initial batch/bin (because they will never be transmitted). + * + * So the daemon could be running for days and watching the + * file system, but doesn't need to actually accumulate any + * paths UNTIL we need to set a reference point for a later + * relative request. + * + * However, it is very useful for testing to always have a + * reference point set. Pin batch 0 to force early file system + * events to accumulate. + */ + if (test_env_value) + batch->pinned_time = time(NULL); + + return token; +} + +struct fsmonitor_batch *fsmonitor_batch__new(void) +{ + struct fsmonitor_batch *batch; + + CALLOC_ARRAY(batch, 1); + + return batch; +} + +void fsmonitor_batch__free_list(struct fsmonitor_batch *batch) +{ + while (batch) { + struct fsmonitor_batch *next = batch->next; + + /* + * The actual strings within the array of this batch + * are interned, so we don't own them. We only own + * the array. + */ + free(batch->interned_paths); + free(batch); + + batch = next; + } +} + +void fsmonitor_batch__add_path(struct fsmonitor_batch *batch, + const char *path) +{ + const char *interned_path = strintern(path); + + trace_printf_key(&trace_fsmonitor, "event: %s", interned_path); + + ALLOC_GROW(batch->interned_paths, batch->nr + 1, batch->alloc); + batch->interned_paths[batch->nr++] = interned_path; +} + +static void fsmonitor_batch__combine(struct fsmonitor_batch *batch_dest, + const struct fsmonitor_batch *batch_src) +{ + size_t k; + + ALLOC_GROW(batch_dest->interned_paths, + batch_dest->nr + batch_src->nr + 1, + batch_dest->alloc); + + for (k = 0; k < batch_src->nr; k++) + batch_dest->interned_paths[batch_dest->nr++] = + batch_src->interned_paths[k]; +} + +/* + * To keep the batch list from growing unbounded in response to filesystem + * activity, we try to truncate old batches from the end of the list as + * they become irrelevant. + * + * We assume that the .git/index will be updated with the most recent token + * any time the index is updated. And future commands will only ask for + * recent changes *since* that new token. So as tokens advance into the + * future, older batch items will never be requested/needed. So we can + * truncate them without loss of functionality. + * + * However, multiple commands may be talking to the daemon concurrently + * or perform a slow command, so a little "token skew" is possible. + * Therefore, we want this to be a little bit lazy and have a generous + * delay. + * + * The current reader thread walked backwards in time from `token->batch_head` + * back to `batch_marker` somewhere in the middle of the batch list. + * + * Let's walk backwards in time from that marker an arbitrary delay + * and truncate the list there. Note that these timestamps are completely + * artificial (based on when we pinned the batch item) and not on any + * filesystem activity. + * + * Return the obsolete portion of the list after we have removed it from + * the official list so that the caller can free it after leaving the lock. + */ +#define MY_TIME_DELAY_SECONDS (5 * 60) /* seconds */ + +static struct fsmonitor_batch *with_lock__truncate_old_batches( + struct fsmonitor_daemon_state *state, + const struct fsmonitor_batch *batch_marker) +{ + /* assert current thread holding state->main_lock */ + + const struct fsmonitor_batch *batch; + struct fsmonitor_batch *remainder; + + if (!batch_marker) + return NULL; + + trace_printf_key(&trace_fsmonitor, "Truncate: mark (%"PRIu64",%"PRIu64")", + batch_marker->batch_seq_nr, + (uint64_t)batch_marker->pinned_time); + + for (batch = batch_marker; batch; batch = batch->next) { + time_t t; + + if (!batch->pinned_time) /* an overflow batch */ + continue; + + t = batch->pinned_time + MY_TIME_DELAY_SECONDS; + if (t > batch_marker->pinned_time) /* too close to marker */ + continue; + + goto truncate_past_here; + } + + return NULL; + +truncate_past_here: + state->current_token_data->batch_tail = (struct fsmonitor_batch *)batch; + + remainder = ((struct fsmonitor_batch *)batch)->next; + ((struct fsmonitor_batch *)batch)->next = NULL; + + return remainder; +} + +static void fsmonitor_free_token_data(struct fsmonitor_token_data *token) +{ + if (!token) + return; + + assert(token->client_ref_count == 0); + + strbuf_release(&token->token_id); + + fsmonitor_batch__free_list(token->batch_head); + + free(token); +} + +/* + * Flush all of our cached data about the filesystem. Call this if we + * lose sync with the filesystem and miss some notification events. + * + * [1] If we are missing events, then we no longer have a complete + * history of the directory (relative to our current start token). + * We should create a new token and start fresh (as if we just + * booted up). + * + * [2] Some of those lost events may have been for cookie files. We + * should assume the worst and abort them rather letting them starve. + * + * If there are no concurrent threads reading the current token data + * series, we can free it now. Otherwise, let the last reader free + * it. + * + * Either way, the old token data series is no longer associated with + * our state data. + */ +static void with_lock__do_force_resync(struct fsmonitor_daemon_state *state) +{ + /* assert current thread holding state->main_lock */ + + struct fsmonitor_token_data *free_me = NULL; + struct fsmonitor_token_data *new_one = NULL; + + new_one = fsmonitor_new_token_data(); + + if (state->current_token_data->client_ref_count == 0) + free_me = state->current_token_data; + state->current_token_data = new_one; + + fsmonitor_free_token_data(free_me); + + with_lock__abort_all_cookies(state); +} + +void fsmonitor_force_resync(struct fsmonitor_daemon_state *state) +{ + pthread_mutex_lock(&state->main_lock); + with_lock__do_force_resync(state); + pthread_mutex_unlock(&state->main_lock); +} + +/* + * Format an opaque token string to send to the client. + */ +static void with_lock__format_response_token( + struct strbuf *response_token, + const struct strbuf *response_token_id, + const struct fsmonitor_batch *batch) +{ + /* assert current thread holding state->main_lock */ + + strbuf_reset(response_token); + strbuf_addf(response_token, "builtin:%s:%"PRIu64, + response_token_id->buf, batch->batch_seq_nr); +} + +/* + * Parse an opaque token from the client. + * Returns -1 on error. + */ +static int fsmonitor_parse_client_token(const char *buf_token, + struct strbuf *requested_token_id, + uint64_t *seq_nr) +{ + const char *p; + char *p_end; + + strbuf_reset(requested_token_id); + *seq_nr = 0; + + if (!skip_prefix(buf_token, "builtin:", &p)) + return -1; + + while (*p && *p != ':') + strbuf_addch(requested_token_id, *p++); + if (!*p++) + return -1; + + *seq_nr = (uint64_t)strtoumax(p, &p_end, 10); + if (*p_end) + return -1; + + return 0; +} + +KHASH_INIT(str, const char *, int, 0, kh_str_hash_func, kh_str_hash_equal) + +static int do_handle_client(struct fsmonitor_daemon_state *state, + const char *command, + ipc_server_reply_cb *reply, + struct ipc_server_reply_data *reply_data) +{ + struct fsmonitor_token_data *token_data = NULL; + struct strbuf response_token = STRBUF_INIT; + struct strbuf requested_token_id = STRBUF_INIT; + struct strbuf payload = STRBUF_INIT; + uint64_t requested_oldest_seq_nr = 0; + uint64_t total_response_len = 0; + const char *p; + const struct fsmonitor_batch *batch_head; + const struct fsmonitor_batch *batch; + struct fsmonitor_batch *remainder = NULL; + intmax_t count = 0, duplicates = 0; + kh_str_t *shown; + int hash_ret; + int do_trivial = 0; + int do_flush = 0; + int do_cookie = 0; + enum fsmonitor_cookie_item_result cookie_result; + + /* + * We expect `command` to be of the form: + * + * <command> := quit NUL + * | flush NUL + * | <V1-time-since-epoch-ns> NUL + * | <V2-opaque-fsmonitor-token> NUL + */ + + if (!strcmp(command, "quit")) { + /* + * A client has requested over the socket/pipe that the + * daemon shutdown. + * + * Tell the IPC thread pool to shutdown (which completes + * the await in the main thread (which can stop the + * fsmonitor listener thread)). + * + * There is no reply to the client. + */ + return SIMPLE_IPC_QUIT; + + } else if (!strcmp(command, "flush")) { + /* + * Flush all of our cached data and generate a new token + * just like if we lost sync with the filesystem. + * + * Then send a trivial response using the new token. + */ + do_flush = 1; + do_trivial = 1; + + } else if (!skip_prefix(command, "builtin:", &p)) { + /* assume V1 timestamp or garbage */ + + char *p_end; + + strtoumax(command, &p_end, 10); + trace_printf_key(&trace_fsmonitor, + ((*p_end) ? + "fsmonitor: invalid command line '%s'" : + "fsmonitor: unsupported V1 protocol '%s'"), + command); + do_trivial = 1; + + } else { + /* We have "builtin:*" */ + if (fsmonitor_parse_client_token(command, &requested_token_id, + &requested_oldest_seq_nr)) { + trace_printf_key(&trace_fsmonitor, + "fsmonitor: invalid V2 protocol token '%s'", + command); + do_trivial = 1; + + } else { + /* + * We have a V2 valid token: + * "builtin:<token_id>:<seq_nr>" + */ + do_cookie = 1; + } + } + + pthread_mutex_lock(&state->main_lock); + + if (!state->current_token_data) + BUG("fsmonitor state does not have a current token"); + + /* + * Write a cookie file inside the directory being watched in + * an effort to flush out existing filesystem events that we + * actually care about. Suspend this client thread until we + * see the filesystem events for this cookie file. + * + * Creating the cookie lets us guarantee that our FS listener + * thread has drained the kernel queue and we are caught up + * with the kernel. + * + * If we cannot create the cookie (or otherwise guarantee that + * we are caught up), we send a trivial response. We have to + * assume that there might be some very, very recent activity + * on the FS still in flight. + */ + if (do_cookie) { + cookie_result = with_lock__wait_for_cookie(state); + if (cookie_result != FCIR_SEEN) { + error(_("fsmonitor: cookie_result '%d' != SEEN"), + cookie_result); + do_trivial = 1; + } + } + + if (do_flush) + with_lock__do_force_resync(state); + + /* + * We mark the current head of the batch list as "pinned" so + * that the listener thread will treat this item as read-only + * (and prevent any more paths from being added to it) from + * now on. + */ + token_data = state->current_token_data; + batch_head = token_data->batch_head; + ((struct fsmonitor_batch *)batch_head)->pinned_time = time(NULL); + + /* + * FSMonitor Protocol V2 requires that we send a response header + * with a "new current token" and then all of the paths that changed + * since the "requested token". We send the seq_nr of the just-pinned + * head batch so that future requests from a client will be relative + * to it. + */ + with_lock__format_response_token(&response_token, + &token_data->token_id, batch_head); + + reply(reply_data, response_token.buf, response_token.len + 1); + total_response_len += response_token.len + 1; + + trace2_data_string("fsmonitor", the_repository, "response/token", + response_token.buf); + trace_printf_key(&trace_fsmonitor, "response token: %s", + response_token.buf); + + if (!do_trivial) { + if (strcmp(requested_token_id.buf, token_data->token_id.buf)) { + /* + * The client last spoke to a different daemon + * instance -OR- the daemon had to resync with + * the filesystem (and lost events), so reject. + */ + trace2_data_string("fsmonitor", the_repository, + "response/token", "different"); + do_trivial = 1; + + } else if (requested_oldest_seq_nr < + token_data->batch_tail->batch_seq_nr) { + /* + * The client wants older events than we have for + * this token_id. This means that the end of our + * batch list was truncated and we cannot give the + * client a complete snapshot relative to their + * request. + */ + trace_printf_key(&trace_fsmonitor, + "client requested truncated data"); + do_trivial = 1; + } + } + + if (do_trivial) { + pthread_mutex_unlock(&state->main_lock); + + reply(reply_data, "/", 2); + + trace2_data_intmax("fsmonitor", the_repository, + "response/trivial", 1); + + goto cleanup; + } + + /* + * We're going to hold onto a pointer to the current + * token-data while we walk the list of batches of files. + * During this time, we will NOT be under the lock. + * So we ref-count it. + * + * This allows the listener thread to continue prepending + * new batches of items to the token-data (which we'll ignore). + * + * AND it allows the listener thread to do a token-reset + * (and install a new `current_token_data`). + */ + token_data->client_ref_count++; + + pthread_mutex_unlock(&state->main_lock); + + /* + * The client request is relative to the token that they sent, + * so walk the batch list backwards from the current head back + * to the batch (sequence number) they named. + * + * We use khash to de-dup the list of pathnames. + * + * NEEDSWORK: each batch contains a list of interned strings, + * so we only need to do pointer comparisons here to build the + * hash table. Currently, we're still comparing the string + * values. + */ + shown = kh_init_str(); + for (batch = batch_head; + batch && batch->batch_seq_nr > requested_oldest_seq_nr; + batch = batch->next) { + size_t k; + + for (k = 0; k < batch->nr; k++) { + const char *s = batch->interned_paths[k]; + size_t s_len; + + if (kh_get_str(shown, s) != kh_end(shown)) + duplicates++; + else { + kh_put_str(shown, s, &hash_ret); + + trace_printf_key(&trace_fsmonitor, + "send[%"PRIuMAX"]: %s", + count, s); + + /* Each path gets written with a trailing NUL */ + s_len = strlen(s) + 1; + + if (payload.len + s_len >= + LARGE_PACKET_DATA_MAX) { + reply(reply_data, payload.buf, + payload.len); + total_response_len += payload.len; + strbuf_reset(&payload); + } + + strbuf_add(&payload, s, s_len); + count++; + } + } + } + + if (payload.len) { + reply(reply_data, payload.buf, payload.len); + total_response_len += payload.len; + } + + kh_release_str(shown); + + pthread_mutex_lock(&state->main_lock); + + if (token_data->client_ref_count > 0) + token_data->client_ref_count--; + + if (token_data->client_ref_count == 0) { + if (token_data != state->current_token_data) { + /* + * The listener thread did a token-reset while we were + * walking the batch list. Therefore, this token is + * stale and can be discarded completely. If we are + * the last reader thread using this token, we own + * that work. + */ + fsmonitor_free_token_data(token_data); + } else if (batch) { + /* + * We are holding the lock and are the only + * reader of the ref-counted portion of the + * list, so we get the honor of seeing if the + * list can be truncated to save memory. + * + * The main loop did not walk to the end of the + * list, so this batch is the first item in the + * batch-list that is older than the requested + * end-point sequence number. See if the tail + * end of the list is obsolete. + */ + remainder = with_lock__truncate_old_batches(state, + batch); + } + } + + pthread_mutex_unlock(&state->main_lock); + + if (remainder) + fsmonitor_batch__free_list(remainder); + + trace2_data_intmax("fsmonitor", the_repository, "response/length", total_response_len); + trace2_data_intmax("fsmonitor", the_repository, "response/count/files", count); + trace2_data_intmax("fsmonitor", the_repository, "response/count/duplicates", duplicates); + +cleanup: + strbuf_release(&response_token); + strbuf_release(&requested_token_id); + strbuf_release(&payload); + + return 0; +} + +static ipc_server_application_cb handle_client; + +static int handle_client(void *data, + const char *command, size_t command_len, + ipc_server_reply_cb *reply, + struct ipc_server_reply_data *reply_data) +{ + struct fsmonitor_daemon_state *state = data; + int result; + + /* + * The Simple IPC API now supports {char*, len} arguments, but + * FSMonitor always uses proper null-terminated strings, so + * we can ignore the command_len argument. (Trust, but verify.) + */ + if (command_len != strlen(command)) + BUG("FSMonitor assumes text messages"); + + trace_printf_key(&trace_fsmonitor, "requested token: %s", command); + + trace2_region_enter("fsmonitor", "handle_client", the_repository); + trace2_data_string("fsmonitor", the_repository, "request", command); + + result = do_handle_client(state, command, reply, reply_data); + + trace2_region_leave("fsmonitor", "handle_client", the_repository); + + return result; +} + +#define FSMONITOR_DIR "fsmonitor--daemon" +#define FSMONITOR_COOKIE_DIR "cookies" +#define FSMONITOR_COOKIE_PREFIX (FSMONITOR_DIR "/" FSMONITOR_COOKIE_DIR "/") + +enum fsmonitor_path_type fsmonitor_classify_path_workdir_relative( + const char *rel) +{ + if (fspathncmp(rel, ".git", 4)) + return IS_WORKDIR_PATH; + rel += 4; + + if (!*rel) + return IS_DOT_GIT; + if (*rel != '/') + return IS_WORKDIR_PATH; /* e.g. .gitignore */ + rel++; + + if (!fspathncmp(rel, FSMONITOR_COOKIE_PREFIX, + strlen(FSMONITOR_COOKIE_PREFIX))) + return IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX; + + return IS_INSIDE_DOT_GIT; +} + +enum fsmonitor_path_type fsmonitor_classify_path_gitdir_relative( + const char *rel) +{ + if (!fspathncmp(rel, FSMONITOR_COOKIE_PREFIX, + strlen(FSMONITOR_COOKIE_PREFIX))) + return IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX; + + return IS_INSIDE_GITDIR; +} + +static enum fsmonitor_path_type try_classify_workdir_abs_path( + struct fsmonitor_daemon_state *state, + const char *path) +{ + const char *rel; + + if (fspathncmp(path, state->path_worktree_watch.buf, + state->path_worktree_watch.len)) + return IS_OUTSIDE_CONE; + + rel = path + state->path_worktree_watch.len; + + if (!*rel) + return IS_WORKDIR_PATH; /* it is the root dir exactly */ + if (*rel != '/') + return IS_OUTSIDE_CONE; + rel++; + + return fsmonitor_classify_path_workdir_relative(rel); +} + +enum fsmonitor_path_type fsmonitor_classify_path_absolute( + struct fsmonitor_daemon_state *state, + const char *path) +{ + const char *rel; + enum fsmonitor_path_type t; + + t = try_classify_workdir_abs_path(state, path); + if (state->nr_paths_watching == 1) + return t; + if (t != IS_OUTSIDE_CONE) + return t; + + if (fspathncmp(path, state->path_gitdir_watch.buf, + state->path_gitdir_watch.len)) + return IS_OUTSIDE_CONE; + + rel = path + state->path_gitdir_watch.len; + + if (!*rel) + return IS_GITDIR; /* it is the <gitdir> exactly */ + if (*rel != '/') + return IS_OUTSIDE_CONE; + rel++; + + return fsmonitor_classify_path_gitdir_relative(rel); +} + +/* + * We try to combine small batches at the front of the batch-list to avoid + * having a long list. This hopefully makes it a little easier when we want + * to truncate and maintain the list. However, we don't want the paths array + * to just keep growing and growing with realloc, so we insert an arbitrary + * limit. + */ +#define MY_COMBINE_LIMIT (1024) + +void fsmonitor_publish(struct fsmonitor_daemon_state *state, + struct fsmonitor_batch *batch, + const struct string_list *cookie_names) +{ + if (!batch && !cookie_names->nr) + return; + + pthread_mutex_lock(&state->main_lock); + + if (batch) { + struct fsmonitor_batch *head; + + head = state->current_token_data->batch_head; + if (!head) { + BUG("token does not have batch"); + } else if (head->pinned_time) { + /* + * We cannot alter the current batch list + * because: + * + * [a] it is being transmitted to at least one + * client and the handle_client() thread has a + * ref-count, but not a lock on the batch list + * starting with this item. + * + * [b] it has been transmitted in the past to + * at least one client such that future + * requests are relative to this head batch. + * + * So, we can only prepend a new batch onto + * the front of the list. + */ + batch->batch_seq_nr = head->batch_seq_nr + 1; + batch->next = head; + state->current_token_data->batch_head = batch; + } else if (!head->batch_seq_nr) { + /* + * Batch 0 is unpinned. See the note in + * `fsmonitor_new_token_data()` about why we + * don't need to accumulate these paths. + */ + fsmonitor_batch__free_list(batch); + } else if (head->nr + batch->nr > MY_COMBINE_LIMIT) { + /* + * The head batch in the list has never been + * transmitted to a client, but folding the + * contents of the new batch onto it would + * exceed our arbitrary limit, so just prepend + * the new batch onto the list. + */ + batch->batch_seq_nr = head->batch_seq_nr + 1; + batch->next = head; + state->current_token_data->batch_head = batch; + } else { + /* + * We are free to add the paths in the given + * batch onto the end of the current head batch. + */ + fsmonitor_batch__combine(head, batch); + fsmonitor_batch__free_list(batch); + } + } + + if (cookie_names->nr) + with_lock__mark_cookies_seen(state, cookie_names); + + pthread_mutex_unlock(&state->main_lock); +} + +static void *fsm_health__thread_proc(void *_state) +{ + struct fsmonitor_daemon_state *state = _state; + + trace2_thread_start("fsm-health"); + + fsm_health__loop(state); + + trace2_thread_exit(); + return NULL; +} + +static void *fsm_listen__thread_proc(void *_state) +{ + struct fsmonitor_daemon_state *state = _state; + + trace2_thread_start("fsm-listen"); + + trace_printf_key(&trace_fsmonitor, "Watching: worktree '%s'", + state->path_worktree_watch.buf); + if (state->nr_paths_watching > 1) + trace_printf_key(&trace_fsmonitor, "Watching: gitdir '%s'", + state->path_gitdir_watch.buf); + + fsm_listen__loop(state); + + pthread_mutex_lock(&state->main_lock); + if (state->current_token_data && + state->current_token_data->client_ref_count == 0) + fsmonitor_free_token_data(state->current_token_data); + state->current_token_data = NULL; + pthread_mutex_unlock(&state->main_lock); + + trace2_thread_exit(); + return NULL; +} + +static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state) +{ + struct ipc_server_opts ipc_opts = { + .nr_threads = fsmonitor__ipc_threads, + + /* + * We know that there are no other active threads yet, + * so we can let the IPC layer temporarily chdir() if + * it needs to when creating the server side of the + * Unix domain socket. + */ + .uds_disallow_chdir = 0 + }; + int health_started = 0; + int listener_started = 0; + int err = 0; + + /* + * Start the IPC thread pool before the we've started the file + * system event listener thread so that we have the IPC handle + * before we need it. + */ + if (ipc_server_run_async(&state->ipc_server_data, + state->path_ipc.buf, &ipc_opts, + handle_client, state)) + return error_errno( + _("could not start IPC thread pool on '%s'"), + state->path_ipc.buf); + + /* + * Start the fsmonitor listener thread to collect filesystem + * events. + */ + if (pthread_create(&state->listener_thread, NULL, + fsm_listen__thread_proc, state) < 0) { + ipc_server_stop_async(state->ipc_server_data); + err = error(_("could not start fsmonitor listener thread")); + goto cleanup; + } + listener_started = 1; + + /* + * Start the health thread to watch over our process. + */ + if (pthread_create(&state->health_thread, NULL, + fsm_health__thread_proc, state) < 0) { + ipc_server_stop_async(state->ipc_server_data); + err = error(_("could not start fsmonitor health thread")); + goto cleanup; + } + health_started = 1; + + /* + * The daemon is now fully functional in background threads. + * Our primary thread should now just wait while the threads + * do all the work. + */ +cleanup: + /* + * Wait for the IPC thread pool to shutdown (whether by client + * request, from filesystem activity, or an error). + */ + ipc_server_await(state->ipc_server_data); + + /* + * The fsmonitor listener thread may have received a shutdown + * event from the IPC thread pool, but it doesn't hurt to tell + * it again. And wait for it to shutdown. + */ + if (listener_started) { + fsm_listen__stop_async(state); + pthread_join(state->listener_thread, NULL); + } + + if (health_started) { + fsm_health__stop_async(state); + pthread_join(state->health_thread, NULL); + } + + if (err) + return err; + if (state->listen_error_code) + return state->listen_error_code; + if (state->health_error_code) + return state->health_error_code; + return 0; +} + +static int fsmonitor_run_daemon(void) +{ + struct fsmonitor_daemon_state state; + const char *home; + int err; + + memset(&state, 0, sizeof(state)); + + hashmap_init(&state.cookies, cookies_cmp, NULL, 0); + pthread_mutex_init(&state.main_lock, NULL); + pthread_cond_init(&state.cookies_cond, NULL); + state.listen_error_code = 0; + state.health_error_code = 0; + state.current_token_data = fsmonitor_new_token_data(); + + /* Prepare to (recursively) watch the <worktree-root> directory. */ + strbuf_init(&state.path_worktree_watch, 0); + strbuf_addstr(&state.path_worktree_watch, absolute_path(get_git_work_tree())); + state.nr_paths_watching = 1; + + /* + * We create and delete cookie files somewhere inside the .git + * directory to help us keep sync with the file system. If + * ".git" is not a directory, then <gitdir> is not inside the + * cone of <worktree-root>, so set up a second watch to watch + * the <gitdir> so that we get events for the cookie files. + */ + strbuf_init(&state.path_gitdir_watch, 0); + strbuf_addbuf(&state.path_gitdir_watch, &state.path_worktree_watch); + strbuf_addstr(&state.path_gitdir_watch, "/.git"); + if (!is_directory(state.path_gitdir_watch.buf)) { + strbuf_reset(&state.path_gitdir_watch); + strbuf_addstr(&state.path_gitdir_watch, absolute_path(get_git_dir())); + state.nr_paths_watching = 2; + } + + /* + * We will write filesystem syncing cookie files into + * <gitdir>/<fsmonitor-dir>/<cookie-dir>/<pid>-<seq>. + * + * The extra layers of subdirectories here keep us from + * changing the mtime on ".git/" or ".git/foo/" when we create + * or delete cookie files. + * + * There have been problems with some IDEs that do a + * non-recursive watch of the ".git/" directory and run a + * series of commands any time something happens. + * + * For example, if we place our cookie files directly in + * ".git/" or ".git/foo/" then a `git status` (or similar + * command) from the IDE will cause a cookie file to be + * created in one of those dirs. This causes the mtime of + * those dirs to change. This triggers the IDE's watch + * notification. This triggers the IDE to run those commands + * again. And the process repeats and the machine never goes + * idle. + * + * Adding the extra layers of subdirectories prevents the + * mtime of ".git/" and ".git/foo" from changing when a + * cookie file is created. + */ + strbuf_init(&state.path_cookie_prefix, 0); + strbuf_addbuf(&state.path_cookie_prefix, &state.path_gitdir_watch); + + strbuf_addch(&state.path_cookie_prefix, '/'); + strbuf_addstr(&state.path_cookie_prefix, FSMONITOR_DIR); + mkdir(state.path_cookie_prefix.buf, 0777); + + strbuf_addch(&state.path_cookie_prefix, '/'); + strbuf_addstr(&state.path_cookie_prefix, FSMONITOR_COOKIE_DIR); + mkdir(state.path_cookie_prefix.buf, 0777); + + strbuf_addch(&state.path_cookie_prefix, '/'); + + /* + * We create a named-pipe or unix domain socket inside of the + * ".git" directory. (Well, on Windows, we base our named + * pipe in the NPFS on the absolute path of the git + * directory.) + */ + strbuf_init(&state.path_ipc, 0); + strbuf_addstr(&state.path_ipc, absolute_path(fsmonitor_ipc__get_path())); + + /* + * Confirm that we can create platform-specific resources for the + * filesystem listener before we bother starting all the threads. + */ + if (fsm_listen__ctor(&state)) { + err = error(_("could not initialize listener thread")); + goto done; + } + + if (fsm_health__ctor(&state)) { + err = error(_("could not initialize health thread")); + goto done; + } + + /* + * CD out of the worktree root directory. + * + * The common Git startup mechanism causes our CWD to be the + * root of the worktree. On Windows, this causes our process + * to hold a locked handle on the CWD. This prevents the + * worktree from being moved or deleted while the daemon is + * running. + * + * We assume that our FS and IPC listener threads have either + * opened all of the handles that they need or will do + * everything using absolute paths. + */ + home = getenv("HOME"); + if (home && *home && chdir(home)) + die_errno(_("could not cd home '%s'"), home); + + err = fsmonitor_run_daemon_1(&state); + +done: + pthread_cond_destroy(&state.cookies_cond); + pthread_mutex_destroy(&state.main_lock); + fsm_listen__dtor(&state); + fsm_health__dtor(&state); + + ipc_server_free(state.ipc_server_data); + + strbuf_release(&state.path_worktree_watch); + strbuf_release(&state.path_gitdir_watch); + strbuf_release(&state.path_cookie_prefix); + strbuf_release(&state.path_ipc); + + return err; +} + +static int try_to_run_foreground_daemon(int detach_console) +{ + /* + * Technically, we don't need to probe for an existing daemon + * process, since we could just call `fsmonitor_run_daemon()` + * and let it fail if the pipe/socket is busy. + * + * However, this method gives us a nicer error message for a + * common error case. + */ + if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING) + die(_("fsmonitor--daemon is already running '%s'"), + the_repository->worktree); + + if (fsmonitor__announce_startup) { + fprintf(stderr, _("running fsmonitor-daemon in '%s'\n"), + the_repository->worktree); + fflush(stderr); + } + +#ifdef GIT_WINDOWS_NATIVE + if (detach_console) + FreeConsole(); +#endif + + return !!fsmonitor_run_daemon(); +} + +static start_bg_wait_cb bg_wait_cb; + +static int bg_wait_cb(const struct child_process *cp, void *cb_data) +{ + enum ipc_active_state s = fsmonitor_ipc__get_state(); + + switch (s) { + case IPC_STATE__LISTENING: + /* child is "ready" */ + return 0; + + case IPC_STATE__NOT_LISTENING: + case IPC_STATE__PATH_NOT_FOUND: + /* give child more time */ + return 1; + + default: + case IPC_STATE__INVALID_PATH: + case IPC_STATE__OTHER_ERROR: + /* all the time in world won't help */ + return -1; + } +} + +static int try_to_start_background_daemon(void) +{ + struct child_process cp = CHILD_PROCESS_INIT; + enum start_bg_result sbgr; + + /* + * Before we try to create a background daemon process, see + * if a daemon process is already listening. This makes it + * easier for us to report an already-listening error to the + * console, since our spawn/daemon can only report the success + * of creating the background process (and not whether it + * immediately exited). + */ + if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING) + die(_("fsmonitor--daemon is already running '%s'"), + the_repository->worktree); + + if (fsmonitor__announce_startup) { + fprintf(stderr, _("starting fsmonitor-daemon in '%s'\n"), + the_repository->worktree); + fflush(stderr); + } + + cp.git_cmd = 1; + + strvec_push(&cp.args, "fsmonitor--daemon"); + strvec_push(&cp.args, "run"); + strvec_push(&cp.args, "--detach"); + strvec_pushf(&cp.args, "--ipc-threads=%d", fsmonitor__ipc_threads); + + cp.no_stdin = 1; + cp.no_stdout = 1; + cp.no_stderr = 1; + + sbgr = start_bg_command(&cp, bg_wait_cb, NULL, + fsmonitor__start_timeout_sec); + + switch (sbgr) { + case SBGR_READY: + return 0; + + default: + case SBGR_ERROR: + case SBGR_CB_ERROR: + return error(_("daemon failed to start")); + + case SBGR_TIMEOUT: + return error(_("daemon not online yet")); + + case SBGR_DIED: + return error(_("daemon terminated")); + } +} + +int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix) +{ + const char *subcmd; + enum fsmonitor_reason reason; + int detach_console = 0; + + struct option options[] = { + OPT_BOOL(0, "detach", &detach_console, N_("detach from console")), + OPT_INTEGER(0, "ipc-threads", + &fsmonitor__ipc_threads, + N_("use <n> ipc worker threads")), + OPT_INTEGER(0, "start-timeout", + &fsmonitor__start_timeout_sec, + N_("max seconds to wait for background daemon startup")), + + OPT_END() + }; + + git_config(fsmonitor_config, NULL); + + argc = parse_options(argc, argv, prefix, options, + builtin_fsmonitor__daemon_usage, 0); + if (argc != 1) + usage_with_options(builtin_fsmonitor__daemon_usage, options); + subcmd = argv[0]; + + if (fsmonitor__ipc_threads < 1) + die(_("invalid 'ipc-threads' value (%d)"), + fsmonitor__ipc_threads); + + prepare_repo_settings(the_repository); + /* + * If the repo is fsmonitor-compatible, explicitly set IPC-mode + * (without bothering to load the `core.fsmonitor` config settings). + * + * If the repo is not compatible, the repo-settings will be set to + * incompatible rather than IPC, so we can use one of the __get + * routines to detect the discrepancy. + */ + fsm_settings__set_ipc(the_repository); + + reason = fsm_settings__get_reason(the_repository); + if (reason > FSMONITOR_REASON_OK) + die("%s", + fsm_settings__get_incompatible_msg(the_repository, + reason)); + + if (!strcmp(subcmd, "start")) + return !!try_to_start_background_daemon(); + + if (!strcmp(subcmd, "run")) + return !!try_to_run_foreground_daemon(detach_console); + + if (!strcmp(subcmd, "stop")) + return !!do_as_client__send_stop(); + + if (!strcmp(subcmd, "status")) + return !!do_as_client__status(); + + die(_("Unhandled subcommand '%s'"), subcmd); +} + +#else +int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix) +{ + struct option options[] = { + OPT_END() + }; + + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_fsmonitor__daemon_usage, options); + + die(_("fsmonitor--daemon not supported on this platform")); +} +#endif diff --git a/builtin/gc.c b/builtin/gc.c index bcef6a4c8d..4ea70089c9 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -30,8 +30,8 @@ #include "promisor-remote.h" #include "refs.h" #include "remote.h" -#include "object-store.h" #include "exec-cmd.h" +#include "hook.h" #define FAILED_RUN "failed to run %s" @@ -42,6 +42,7 @@ static const char * const builtin_gc_usage[] = { static int pack_refs = 1; static int prune_reflogs = 1; +static int cruft_packs = 0; static int aggressive_depth = 50; static int aggressive_window = 250; static int gc_auto_threshold = 6700; @@ -152,6 +153,7 @@ static void gc_config(void) git_config_get_int("gc.auto", &gc_auto_threshold); git_config_get_int("gc.autopacklimit", &gc_auto_pack_limit); git_config_get_bool("gc.autodetach", &detach_auto); + git_config_get_bool("gc.cruftpacks", &cruft_packs); git_config_get_expiry("gc.pruneexpire", &prune_expire); git_config_get_expiry("gc.worktreepruneexpire", &prune_worktrees_expire); git_config_get_expiry("gc.logexpiry", &gc_log_expire); @@ -331,7 +333,11 @@ static void add_repack_all_option(struct string_list *keep_pack) { if (prune_expire && !strcmp(prune_expire, "now")) strvec_push(&repack, "-a"); - else { + else if (cruft_packs) { + strvec_push(&repack, "--cruft"); + if (prune_expire) + strvec_pushf(&repack, "--cruft-expiration=%s", prune_expire); + } else { strvec_push(&repack, "-A"); if (prune_expire) strvec_pushf(&repack, "--unpack-unreachable=%s", prune_expire); @@ -394,7 +400,7 @@ static int need_to_gc(void) else return 0; - if (run_hook_le(NULL, "pre-auto-gc", NULL)) + if (run_hooks("pre-auto-gc")) return 0; return 1; } @@ -446,7 +452,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) fscanf(fp, scan_fmt, &pid, locking_host) == 2 && /* be gentle to concurrent "gc" on remote hosts */ (strcmp(locking_host, my_host) || !kill(pid, 0) || errno == EPERM); - if (fp != NULL) + if (fp) fclose(fp); if (should_exit) { if (fd >= 0) @@ -470,7 +476,8 @@ 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 occurred while reading gc.log + * message and returns with a non-[01] status code if an error occurred + * while reading gc.log */ static int report_last_gc_error(void) { @@ -484,7 +491,7 @@ static int report_last_gc_error(void) if (errno == ENOENT) goto done; - ret = error_errno(_("cannot stat '%s'"), gc_log_path); + ret = die_message_errno(_("cannot stat '%s'"), gc_log_path); goto done; } @@ -493,7 +500,7 @@ static int report_last_gc_error(void) len = strbuf_read_file(&sb, gc_log_path, 0); if (len < 0) - ret = error_errno(_("cannot read '%s'"), gc_log_path); + ret = die_message_errno(_("cannot read '%s'"), gc_log_path); else if (len > 0) { /* * A previous gc failed. Report the error, and don't @@ -550,6 +557,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) { OPTION_STRING, 0, "prune", &prune_expire, N_("date"), N_("prune unreferenced objects"), PARSE_OPT_OPTARG, NULL, (intptr_t)prune_expire }, + OPT_BOOL(0, "cruft", &cruft_packs, N_("pack unreferenced objects separately")), OPT_BOOL(0, "aggressive", &aggressive, N_("be more thorough (increased runtime)")), OPT_BOOL_F(0, "auto", &auto_gc, N_("enable auto-gc mode"), PARSE_OPT_NOCOMPLETE), @@ -611,12 +619,13 @@ 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 occurred, already reported */ - exit(128); + if (ret == 1) /* Last gc --auto failed. Skip this one. */ return 0; + else if (ret) + /* an I/O error occurred, already reported */ + return ret; if (lock_repo_for_gc(force, &pid)) return 0; @@ -668,6 +677,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) die(FAILED_RUN, repack.v[0]); if (prune_expire) { + /* run `git prune` even if using cruft packs */ strvec_push(&prune, prune_expire); if (quiet) strvec_push(&prune, "--no-progress"); @@ -2236,7 +2246,7 @@ static int systemd_timer_write_unit_templates(const char *exec_path) goto error; } file = fopen_or_warn(filename, "w"); - if (file == NULL) + if (!file) goto error; unit = "# This file was created and is maintained by Git.\n" @@ -2265,7 +2275,7 @@ static int systemd_timer_write_unit_templates(const char *exec_path) filename = xdg_config_home_systemd("git-maintenance@.service"); file = fopen_or_warn(filename, "w"); - if (file == NULL) + if (!file) goto error; unit = "# This file was created and is maintained by Git.\n" diff --git a/builtin/grep.c b/builtin/grep.c index 9e34a820ad..bcb07ea7f7 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -26,6 +26,8 @@ #include "object-store.h" #include "packfile.h" +static const char *grep_prefix; + static char const * const grep_usage[] = { N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"), NULL @@ -284,7 +286,7 @@ static int wait_all(void) static int grep_cmd_config(const char *var, const char *value, void *cb) { int st = grep_config(var, value, cb); - if (git_color_default_config(var, value, cb) < 0) + if (git_color_default_config(var, value, NULL) < 0) st = -1; if (!strcmp(var, "grep.threads")) { @@ -315,11 +317,11 @@ static void grep_source_name(struct grep_opt *opt, const char *filename, strbuf_reset(out); if (opt->null_following_name) { - if (opt->relative && opt->prefix_length) { + if (opt->relative && grep_prefix) { struct strbuf rel_buf = STRBUF_INIT; const char *rel_name = relative_path(filename + tree_name_len, - opt->prefix, &rel_buf); + grep_prefix, &rel_buf); if (tree_name_len) strbuf_add(out, filename, tree_name_len); @@ -332,8 +334,8 @@ static void grep_source_name(struct grep_opt *opt, const char *filename, return; } - if (opt->relative && opt->prefix_length) - quote_path(filename + tree_name_len, opt->prefix, out, 0); + if (opt->relative && grep_prefix) + quote_path(filename + tree_name_len, grep_prefix, out, 0); else quote_c_style(filename + tree_name_len, out, NULL, 0); @@ -482,7 +484,7 @@ static int grep_submodule(struct grep_opt *opt, object_type = oid_object_info(subrepo, oid, NULL); obj_read_unlock(); data = read_object_with_reference(subrepo, - oid, tree_type, + oid, OBJ_TREE, &size, NULL); if (!data) die(_("unable to read tree (%s)"), oid_to_hex(oid)); @@ -651,7 +653,7 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, int hit, len; data = read_object_with_reference(opt->repo, - &obj->oid, tree_type, + &obj->oid, OBJ_TREE, &size, NULL); if (!data) die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid)); @@ -843,7 +845,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) int i; int dummy; int use_index = 1; - int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED; int allow_revs; struct option options[] = { @@ -877,16 +878,16 @@ int cmd_grep(int argc, const char **argv, const char *prefix) N_("descend at most <depth> levels"), PARSE_OPT_NONEG, NULL, 1 }, OPT_GROUP(""), - OPT_SET_INT('E', "extended-regexp", &pattern_type_arg, + OPT_SET_INT('E', "extended-regexp", &opt.pattern_type_option, N_("use extended POSIX regular expressions"), GREP_PATTERN_TYPE_ERE), - OPT_SET_INT('G', "basic-regexp", &pattern_type_arg, + OPT_SET_INT('G', "basic-regexp", &opt.pattern_type_option, N_("use basic POSIX regular expressions (default)"), GREP_PATTERN_TYPE_BRE), - OPT_SET_INT('F', "fixed-strings", &pattern_type_arg, + OPT_SET_INT('F', "fixed-strings", &opt.pattern_type_option, N_("interpret patterns as fixed strings"), GREP_PATTERN_TYPE_FIXED), - OPT_SET_INT('P', "perl-regexp", &pattern_type_arg, + OPT_SET_INT('P', "perl-regexp", &opt.pattern_type_option, N_("use Perl-compatible regular expressions"), GREP_PATTERN_TYPE_PCRE), OPT_GROUP(""), @@ -962,9 +963,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) PARSE_OPT_NOCOMPLETE), OPT_END() }; + grep_prefix = prefix; - git_config(grep_cmd_config, NULL); - grep_init(&opt, the_repository, prefix); + grep_init(&opt, the_repository); + git_config(grep_cmd_config, &opt); /* * If there is no -- then the paths must exist in the working @@ -979,7 +981,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, grep_usage, PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_STOP_AT_NON_OPTION); - grep_commit_pattern_type(pattern_type_arg, &opt); if (use_index && !startup_info->have_repository) { int fallback = 0; @@ -1167,11 +1168,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!show_in_pager && !opt.status_only) setup_pager(); - if (!use_index && (untracked || cached)) - die(_("--cached or --untracked cannot be used with --no-index")); - - if (untracked && cached) - die(_("--untracked cannot be used with --cached")); + die_for_incompatible_opt3(!use_index, "--no-index", + untracked, "--untracked", + cached, "--cached"); if (!use_index || untracked) { int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude; diff --git a/builtin/hash-object.c b/builtin/hash-object.c index c7b3ad74c6..fbae878c2b 100644 --- a/builtin/hash-object.c +++ b/builtin/hash-object.c @@ -25,7 +25,7 @@ static int hash_literally(struct object_id *oid, int fd, const char *type, unsig if (strbuf_read(&buf, fd, 4096) < 0) ret = -1; else - ret = hash_object_file_literally(buf.buf, buf.len, type, oid, + ret = write_object_file_literally(buf.buf, buf.len, type, oid, flags); strbuf_release(&buf); return ret; @@ -81,7 +81,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) { static const char * const hash_object_usage[] = { N_("git hash-object [-t <type>] [-w] [--path=<file> | --no-filters] [--stdin] [--] <file>..."), - N_("git hash-object --stdin-paths"), + "git hash-object --stdin-paths", NULL }; const char *type = blob_type; @@ -92,6 +92,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) int nongit = 0; unsigned flags = HASH_FORMAT_CHECK; const char *vpath = NULL; + char *vpath_free = NULL; const struct option hash_object_options[] = { OPT_STRING('t', NULL, &type, N_("type"), N_("object type")), OPT_BIT('w', NULL, &flags, N_("write the object into the object database"), @@ -114,8 +115,10 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) else prefix = setup_git_directory_gently(&nongit); - if (vpath && prefix) - vpath = prefix_filename(prefix, vpath); + if (vpath && prefix) { + vpath_free = prefix_filename(prefix, vpath); + vpath = vpath_free; + } git_config(git_default_config, NULL); @@ -156,5 +159,7 @@ int cmd_hash_object(int argc, const char **argv, const char *prefix) if (stdin_paths) hash_stdin_paths(type, no_filters, flags, literally); + free(vpath_free); + return 0; } diff --git a/builtin/help.c b/builtin/help.c index 75cd2fb407..222f994f86 100644 --- a/builtin/help.c +++ b/builtin/help.c @@ -51,9 +51,14 @@ static const char *html_path; static int verbose = 1; static enum help_format help_format = HELP_FORMAT_NONE; static int exclude_guides; +static int show_external_commands = -1; +static int show_aliases = -1; static struct option builtin_help_options[] = { OPT_CMDMODE('a', "all", &cmd_mode, N_("print all available commands"), HELP_ACTION_ALL), + OPT_BOOL(0, "external-commands", &show_external_commands, + N_("show external commands in --all")), + OPT_BOOL(0, "aliases", &show_aliases, N_("show aliases in --all")), OPT_HIDDEN_BOOL(0, "exclude-guides", &exclude_guides, N_("exclude guides")), OPT_SET_INT('m', "man", &help_format, N_("show man page"), HELP_FORMAT_MAN), OPT_SET_INT('w', "web", &help_format, N_("show manual in web browser"), @@ -75,10 +80,10 @@ static struct option builtin_help_options[] = { }; static const char * const builtin_help_usage[] = { - N_("git help [-a|--all] [--[no-]verbose]]\n" - " [[-i|--info] [-m|--man] [-w|--web]] [<command>]"), - N_("git help [-g|--guides]"), - N_("git help [-c|--config]"), + "git help [-a|--all] [--[no-]verbose]] [--[no-]external-commands] [--[no-]aliases]", + N_("git help [[-i|--info] [-m|--man] [-w|--web]] [<command>]"), + "git help [-g|--guides]", + "git help [-c|--config]", NULL }; @@ -212,11 +217,10 @@ static int check_emacsclient_version(void) { struct strbuf buffer = STRBUF_INIT; struct child_process ec_process = CHILD_PROCESS_INIT; - const char *argv_ec[] = { "emacsclient", "--version", NULL }; int version; /* emacsclient prints its version number on stderr */ - ec_process.argv = argv_ec; + strvec_pushl(&ec_process.args, "emacsclient", "--version", NULL); ec_process.err = -1; ec_process.stdout_to_stderr = 1; if (start_command(&ec_process)) @@ -575,11 +579,40 @@ static const char *check_git_cmd(const char* cmd) return cmd; } -static void no_extra_argc(int argc) +static void no_help_format(const char *opt_mode, enum help_format fmt) +{ + const char *opt_fmt; + + switch (fmt) { + case HELP_FORMAT_NONE: + return; + case HELP_FORMAT_MAN: + opt_fmt = "--man"; + break; + case HELP_FORMAT_INFO: + opt_fmt = "--info"; + break; + case HELP_FORMAT_WEB: + opt_fmt = "--web"; + break; + default: + BUG("unreachable"); + } + + usage_msg_optf(_("options '%s' and '%s' cannot be used together"), + builtin_help_usage, builtin_help_options, opt_mode, + opt_fmt); +} + +static void opt_mode_usage(int argc, const char *opt_mode, + enum help_format fmt) { if (argc) - usage_msg_opt(_("this option doesn't take any other arguments"), - builtin_help_usage, builtin_help_options); + usage_msg_optf(_("the '%s' option doesn't take any non-option arguments"), + builtin_help_usage, builtin_help_options, + opt_mode); + + no_help_format(opt_mode, fmt); } int cmd_help(int argc, const char **argv, const char *prefix) @@ -592,11 +625,19 @@ int cmd_help(int argc, const char **argv, const char *prefix) builtin_help_usage, 0); parsed_help_format = help_format; + if (cmd_mode != HELP_ACTION_ALL && + (show_external_commands >= 0 || + show_aliases >= 0)) + usage_msg_opt(_("the '--no-[external-commands|aliases]' options can only be used with '--all'"), + builtin_help_usage, builtin_help_options); + switch (cmd_mode) { case HELP_ACTION_ALL: + opt_mode_usage(argc, "--all", help_format); if (verbose) { setup_pager(); - list_all_cmds_help(); + list_all_cmds_help(show_external_commands, + show_aliases); return 0; } printf(_("usage: %s%s"), _(git_usage_string), "\n\n"); @@ -605,20 +646,21 @@ int cmd_help(int argc, const char **argv, const char *prefix) printf("%s\n", _(git_more_info_string)); break; case HELP_ACTION_GUIDES: - no_extra_argc(argc); + opt_mode_usage(argc, "--guides", help_format); list_guides_help(); printf("%s\n", _(git_more_info_string)); return 0; case HELP_ACTION_CONFIG_FOR_COMPLETION: - no_extra_argc(argc); + opt_mode_usage(argc, "--config-for-completion", help_format); list_config_help(SHOW_CONFIG_VARS); return 0; case HELP_ACTION_CONFIG_SECTIONS_FOR_COMPLETION: - no_extra_argc(argc); + opt_mode_usage(argc, "--config-sections-for-completion", + help_format); list_config_help(SHOW_CONFIG_SECTIONS); return 0; case HELP_ACTION_CONFIG: - no_extra_argc(argc); + opt_mode_usage(argc, "--config", help_format); setup_pager(); list_config_help(SHOW_CONFIG_HUMAN); printf("\n%s\n", _("'git help config' for more information")); diff --git a/builtin/hook.c b/builtin/hook.c new file mode 100644 index 0000000000..54e5c6ec93 --- /dev/null +++ b/builtin/hook.c @@ -0,0 +1,84 @@ +#include "cache.h" +#include "builtin.h" +#include "config.h" +#include "hook.h" +#include "parse-options.h" +#include "strbuf.h" +#include "strvec.h" + +#define BUILTIN_HOOK_RUN_USAGE \ + N_("git hook run [--ignore-missing] <hook-name> [-- <hook-args>]") + +static const char * const builtin_hook_usage[] = { + BUILTIN_HOOK_RUN_USAGE, + NULL +}; + +static const char * const builtin_hook_run_usage[] = { + BUILTIN_HOOK_RUN_USAGE, + NULL +}; + +static int run(int argc, const char **argv, const char *prefix) +{ + int i; + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + int ignore_missing = 0; + const char *hook_name; + struct option run_options[] = { + OPT_BOOL(0, "ignore-missing", &ignore_missing, + N_("silently ignore missing requested <hook-name>")), + OPT_END(), + }; + int ret; + + argc = parse_options(argc, argv, prefix, run_options, + builtin_hook_run_usage, + PARSE_OPT_KEEP_DASHDASH); + + if (!argc) + goto usage; + + /* + * Having a -- for "run" when providing <hook-args> is + * mandatory. + */ + if (argc > 1 && strcmp(argv[1], "--") && + strcmp(argv[1], "--end-of-options")) + goto usage; + + /* Add our arguments, start after -- */ + for (i = 2 ; i < argc; i++) + strvec_push(&opt.args, argv[i]); + + /* Need to take into account core.hooksPath */ + git_config(git_default_config, NULL); + + hook_name = argv[0]; + if (!ignore_missing) + opt.error_if_missing = 1; + ret = run_hooks_opt(hook_name, &opt); + if (ret < 0) /* error() return */ + ret = 1; + return ret; +usage: + usage_with_options(builtin_hook_run_usage, run_options); +} + +int cmd_hook(int argc, const char **argv, const char *prefix) +{ + struct option builtin_hook_options[] = { + OPT_END(), + }; + + argc = parse_options(argc, argv, NULL, builtin_hook_options, + builtin_hook_usage, PARSE_OPT_STOP_AT_NON_OPTION); + if (!argc) + goto usage; + + if (!strcmp(argv[0], "run")) + return run(argc, argv, prefix); + +usage: + usage_with_options(builtin_hook_usage, builtin_hook_options); +} diff --git a/builtin/index-pack.c b/builtin/index-pack.c index c23d01de7d..3e385b4800 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -323,8 +323,12 @@ static void use(int bytes) if (signed_add_overflows(consumed_bytes, bytes)) die(_("pack too large for current definition of off_t")); consumed_bytes += bytes; - if (max_input_size && consumed_bytes > max_input_size) - die(_("pack exceeds maximum allowed size")); + if (max_input_size && consumed_bytes > max_input_size) { + struct strbuf size_limit = STRBUF_INIT; + strbuf_humanise_bytes(&size_limit, max_input_size); + die(_("pack exceeds maximum allowed size (%s)"), + size_limit.buf); + } } static const char *open_pack_file(const char *pack_name) @@ -449,8 +453,7 @@ static void *unpack_entry_data(off_t offset, unsigned long size, int hdrlen; if (!is_delta_type(type)) { - hdrlen = xsnprintf(hdr, sizeof(hdr), "%s %"PRIuMAX, - type_name(type),(uintmax_t)size) + 1; + hdrlen = format_object_header(hdr, sizeof(hdr), type, size); the_hash_algo->init_fn(&c); the_hash_algo->update_fn(&c, hdr, hdrlen); } else @@ -579,7 +582,7 @@ static void *unpack_data(struct object_entry *obj, if (!n) die(Q_("premature end of pack file, %"PRIuMAX" byte missing", "premature end of pack file, %"PRIuMAX" bytes missing", - (unsigned int)len), + len), (uintmax_t)len); from += n; len -= n; @@ -971,7 +974,7 @@ static struct base_data *resolve_delta(struct object_entry *delta_obj, if (!result_data) bad_object(delta_obj->idx.offset, _("failed to apply delta")); hash_object_file(the_hash_algo, result_data, result_size, - type_name(delta_obj->real_type), &delta_obj->idx.oid); + delta_obj->real_type, &delta_obj->idx.oid); sha1_object(result_data, NULL, result_size, delta_obj->real_type, &delta_obj->idx.oid); @@ -1109,6 +1112,7 @@ static void *threaded_second_pass(void *data) list_add(&child->list, &work_head); base_cache_used += child->size; prune_base_data(NULL); + free_base_data(child); } else { /* * This child does not have its own children. It may be @@ -1131,6 +1135,7 @@ static void *threaded_second_pass(void *data) p = next_p; } + FREE_AND_NULL(child); } work_unlock(); } @@ -1286,7 +1291,7 @@ static void conclude_pack(int fix_thin_pack, const char *curr_pack, unsigned cha nr_objects - nr_objects_initial); stop_progress_msg(&progress, msg.buf); strbuf_release(&msg); - finalize_hashfile(f, tail_hash, 0); + finalize_hashfile(f, tail_hash, FSYNC_COMPONENT_PACK, 0); hashcpy(read_hash, pack_hash); fixup_pack_header_footer(output_fd, pack_hash, curr_pack, nr_objects, @@ -1413,9 +1418,8 @@ static void fix_unresolved_deltas(struct hashfile *f) if (!data) continue; - if (check_object_signature(the_repository, &d->oid, - data, size, - type_name(type), NULL)) + if (check_object_signature(the_repository, &d->oid, data, size, + type) < 0) die(_("local object %s is corrupt"), oid_to_hex(&d->oid)); /* @@ -1424,6 +1428,7 @@ static void fix_unresolved_deltas(struct hashfile *f) * object). */ append_obj_to_pack(f, d->oid.hash, data, size, type); + free(data); threaded_second_pass(NULL); display_progress(progress, nr_resolved_deltas); @@ -1508,7 +1513,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, if (!from_stdin) { close(input_fd); } else { - fsync_or_die(output_fd, curr_pack_name); + fsync_component_or_die(FSYNC_COMPONENT_PACK, output_fd, curr_pack_name); err = close(output_fd); if (err) die_errno(_("error while closing pack file")); @@ -1703,6 +1708,7 @@ static void show_pack_info(int stat_only) i + 1, chain_histogram[i]); } + free(chain_histogram); } int cmd_index_pack(int argc, const char **argv, const char *prefix) @@ -1845,11 +1851,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (!pack_name && !from_stdin) usage(index_pack_usage); if (fix_thin_pack && !from_stdin) - die(_("--fix-thin cannot be used without --stdin")); + die(_("the option '%s' requires '%s'"), "--fix-thin", "--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")); + die(_("options '%s' and '%s' cannot be used together"), "--object-format", "--stdin"); if (!index_name && pack_name) index_name = derive_filename(pack_name, "pack", "idx", &index_name_buf); @@ -1932,14 +1938,15 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (do_fsck_object && fsck_finish(&fsck_options)) die(_("fsck error in pack objects")); + free(opts.anomaly); free(objects); strbuf_release(&index_name_buf); strbuf_release(&rev_index_name_buf); - if (pack_name == NULL) + if (!pack_name) free((void *) curr_pack); - if (index_name == NULL) + if (!index_name) free((void *) curr_index); - if (rev_index_name == NULL) + if (!rev_index_name) free((void *) curr_rev_index); /* diff --git a/builtin/init-db.c b/builtin/init-db.c index 2167796ff2..546f9c595e 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -557,7 +557,7 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) 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")); + die(_("options '%s' and '%s' cannot be used together"), "--separate-git-dir", "--bare"); if (real_git_dir && !is_absolute_path(real_git_dir)) real_git_dir = real_pathdup(real_git_dir, 1); diff --git a/builtin/log.c b/builtin/log.c index f75d87e8d7..88a5e98875 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -35,6 +35,7 @@ #include "repository.h" #include "commit-reach.h" #include "range-diff.h" +#include "tmp-objdir.h" #define MAIL_DEFAULT_WRAP 72 #define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100 @@ -230,7 +231,8 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, } if (mailmap) { - rev->mailmap = xcalloc(1, sizeof(struct string_list)); + rev->mailmap = xmalloc(sizeof(struct string_list)); + string_list_init_nodup(rev->mailmap); read_mailmap(rev->mailmap); } @@ -245,10 +247,24 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, rev->abbrev_commit = 0; } - if (rev->commit_format == CMIT_FMT_USERFORMAT && !w.decorate) - decoration_style = 0; + if (rev->commit_format == CMIT_FMT_USERFORMAT) { + if (!w.decorate) { + /* + * Disable decoration loading if the format will not + * show them anyway. + */ + decoration_style = 0; + } else if (!decoration_style) { + /* + * If we are going to show them, make sure we do load + * them here, but taking care not to override a + * specific style set by config or --decorate. + */ + decoration_style = DECORATE_SHORT_REFS; + } + } - if (decoration_style) { + if (decoration_style || rev->simplify_by_decoration) { const struct string_list *config_exclude = repo_config_get_value_multi(the_repository, "log.excludeDecoration"); @@ -260,7 +276,8 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, item->string); } - rev->show_decorations = 1; + if (decoration_style) + rev->show_decorations = 1; load_ref_decorations(&decoration_filter, decoration_style); } @@ -278,6 +295,12 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, cmd_log_init_finish(argc, argv, prefix, rev, opt); } +static int cmd_log_deinit(int ret, struct rev_info *rev) +{ + release_revisions(rev); + return ret; +} + /* * This gives a rough estimate for how many commits we * will print out in the list. @@ -401,12 +424,19 @@ static void finish_early_output(struct rev_info *rev) show_early_header(rev, "done", n); } -static int cmd_log_walk(struct rev_info *rev) +static int cmd_log_walk_no_free(struct rev_info *rev) { struct commit *commit; int saved_nrl = 0; int saved_dcctc = 0; + if (rev->remerge_diff) { + rev->remerge_objdir = tmp_objdir_create("remerge-diff"); + if (!rev->remerge_objdir) + die(_("unable to create temporary object directory")); + tmp_objdir_replace_primary_odb(rev->remerge_objdir, 1); + } + if (rev->early_output) setup_early_output(); @@ -421,7 +451,6 @@ static int cmd_log_walk(struct rev_info *rev) * and HAS_CHANGES being accumulated in rev->diffopt, so be careful to * retain that state information if replacing rev->diffopt in this loop */ - rev->diffopt.no_free = 1; while ((commit = get_revision(rev)) != NULL) { if (!log_tree_commit(rev, commit) && rev->max_count >= 0) /* @@ -446,8 +475,11 @@ static int cmd_log_walk(struct rev_info *rev) } rev->diffopt.degraded_cc_to_c = saved_dcctc; rev->diffopt.needed_rename_limit = saved_nrl; - rev->diffopt.no_free = 0; - diff_free(&rev->diffopt); + + if (rev->remerge_diff) { + tmp_objdir_destroy(rev->remerge_objdir); + rev->remerge_objdir = NULL; + } if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF && rev->diffopt.flags.check_failed) { @@ -456,6 +488,17 @@ static int cmd_log_walk(struct rev_info *rev) return diff_result_code(&rev->diffopt, 0); } +static int cmd_log_walk(struct rev_info *rev) +{ + int retval; + + rev->diffopt.no_free = 1; + retval = cmd_log_walk_no_free(rev); + rev->diffopt.no_free = 0; + diff_free(&rev->diffopt); + return retval; +} + static int git_log_config(const char *var, const char *value, void *cb) { const char *slot_name; @@ -505,8 +548,6 @@ static int git_log_config(const char *var, const char *value, void *cb) return 0; } - if (grep_config(var, value, cb) < 0) - return -1; if (git_gpg_config(var, value, cb) < 0) return -1; return git_diff_ui_config(var, value, cb); @@ -521,6 +562,8 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) git_config(git_log_config, NULL); repo_init_revisions(the_repository, &rev, prefix); + git_config(grep_config, &rev.grep_filter); + rev.diff = 1; rev.simplify_history = 0; memset(&opt, 0, sizeof(opt)); @@ -529,7 +572,7 @@ int cmd_whatchanged(int argc, const char **argv, const char *prefix) cmd_log_init(argc, argv, prefix, &rev, &opt); if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; - return cmd_log_walk(&rev); + return cmd_log_deinit(cmd_log_walk(&rev), &rev); } static void show_tagger(const char *buf, struct rev_info *rev) @@ -633,8 +676,15 @@ int cmd_show(int argc, const char **argv, const char *prefix) init_log_defaults(); git_config(git_log_config, NULL); + if (the_repository->gitdir) { + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + } + memset(&match_all, 0, sizeof(match_all)); repo_init_revisions(the_repository, &rev, prefix); + git_config(grep_config, &rev.grep_filter); + rev.diff = 1; rev.always_show_header = 1; rev.no_walk = 1; @@ -646,10 +696,11 @@ int cmd_show(int argc, const char **argv, const char *prefix) cmd_log_init(argc, argv, prefix, &rev, &opt); if (!rev.no_walk) - return cmd_log_walk(&rev); + return cmd_log_deinit(cmd_log_walk(&rev), &rev); count = rev.pending.nr; objects = rev.pending.objects; + rev.diffopt.no_free = 1; for (i = 0; i < count && !ret; i++) { struct object *o = objects[i].item; const char *name = objects[i].name; @@ -695,14 +746,17 @@ int cmd_show(int argc, const char **argv, const char *prefix) rev.pending.nr = rev.pending.alloc = 0; rev.pending.objects = NULL; add_object_array(o, name, &rev.pending); - ret = cmd_log_walk(&rev); + ret = cmd_log_walk_no_free(&rev); break; default: ret = error(_("unknown type: %d"), o->type); } } - free(objects); - return ret; + + rev.diffopt.no_free = 0; + diff_free(&rev.diffopt); + + return cmd_log_deinit(ret, &rev); } /* @@ -718,6 +772,8 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) repo_init_revisions(the_repository, &rev, prefix); init_reflog_walk(&rev.reflog_info); + git_config(grep_config, &rev.grep_filter); + rev.verbose_header = 1; memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; @@ -728,7 +784,7 @@ int cmd_log_reflog(int argc, const char **argv, const char *prefix) rev.always_show_header = 1; cmd_log_init_finish(argc, argv, prefix, &rev, &opt); - return cmd_log_walk(&rev); + return cmd_log_deinit(cmd_log_walk(&rev), &rev); } static void log_setup_revisions_tweak(struct rev_info *rev, @@ -751,13 +807,15 @@ int cmd_log(int argc, const char **argv, const char *prefix) git_config(git_log_config, NULL); repo_init_revisions(the_repository, &rev, prefix); + git_config(grep_config, &rev.grep_filter); + rev.always_show_header = 1; memset(&opt, 0, sizeof(opt)); opt.def = "HEAD"; opt.revarg_opt = REVARG_COMMITTISH; opt.tweak = log_setup_revisions_tweak; cmd_log_init(argc, argv, prefix, &rev, &opt); - return cmd_log_walk(&rev); + return cmd_log_deinit(cmd_log_walk(&rev), &rev); } /* format-patch */ @@ -978,7 +1036,7 @@ static int open_next_file(struct commit *commit, const char *subject, if (!quiet) printf("%s\n", filename.buf + outdir_offset); - if ((rev->diffopt.file = fopen(filename.buf, "w")) == NULL) { + if (!(rev->diffopt.file = fopen(filename.buf, "w"))) { error_errno(_("cannot open patch file %s"), filename.buf); strbuf_release(&filename); return -1; @@ -1712,6 +1770,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) struct commit *commit; struct commit **list = NULL; struct rev_info rev; + char *to_free = NULL; struct setup_revision_opt s_r_opt; int nr = 0, total, i; int use_stdout = 0; @@ -1833,10 +1892,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) extra_hdr.strdup_strings = 1; 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(grep_config, &rev.grep_filter); + rev.show_notes = show_notes; memcpy(&rev.notes_opt, ¬es_opt, sizeof(notes_opt)); rev.commit_format = CMIT_FMT_EMAIL; @@ -1846,6 +1908,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.diff = 1; rev.max_parents = 1; rev.diffopt.flags.recursive = 1; + rev.diffopt.no_free = 1; rev.subject_prefix = fmt_patch_subject_prefix; memset(&s_r_opt, 0, sizeof(s_r_opt)); s_r_opt.def = "HEAD"; @@ -1909,7 +1972,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) strbuf_addch(&buf, '\n'); } - rev.extra_headers = strbuf_detach(&buf, NULL); + rev.extra_headers = to_free = strbuf_detach(&buf, NULL); if (from) { if (split_ident_line(&rev.from_ident, from, strlen(from))) @@ -1928,9 +1991,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) numbered = 0; if (numbered && keep_subject) - die(_("-n and -k are mutually exclusive")); + die(_("options '%s' and '%s' cannot be used together"), "-n", "-k"); if (keep_subject && subject_prefix) - die(_("--subject-prefix/--rfc and -k are mutually exclusive")); + die(_("options '%s' and '%s' cannot be used together"), "--subject-prefix/--rfc", "-k"); rev.preserve_subject = keep_subject; argc = setup_revisions(argc, argv, &rev, &s_r_opt); @@ -1943,6 +2006,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) die(_("--name-status does not make sense")); if (rev.diffopt.output_format & DIFF_FORMAT_CHECKDIFF) die(_("--check does not make sense")); + if (rev.remerge_diff) + die(_("--remerge-diff does not make sense")); if (!use_patch_format && (!rev.diffopt.output_format || @@ -1963,18 +2028,13 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (rev.show_notes) load_display_notes(&rev.notes_opt); - if (use_stdout + rev.diffopt.close_file + !!output_directory > 1) - die(_("--stdout, --output, and --output-directory are mutually exclusive")); + die_for_incompatible_opt3(use_stdout, "--stdout", + rev.diffopt.close_file, "--output", + !!output_directory, "--output-directory"); if (use_stdout) { setup_pager(); - } else if (rev.diffopt.close_file) { - /* - * The diff code parsed --output; it has already opened the - * file, but we must instruct it not to close after each diff. - */ - rev.diffopt.no_free = 1; - } else { + } else if (!rev.diffopt.close_file) { int saved; if (!output_directory) @@ -2097,7 +2157,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (creation_factor < 0) creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT; else if (!rdiff_prev) - die(_("--creation-factor requires --range-diff")); + die(_("the option '%s' requires '%s'"), "--creation-factor", "--range-diff"); if (rdiff_prev) { if (!cover_letter && total != 1) @@ -2133,8 +2193,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) prepare_bases(&bases, base, list, nr); } - if (in_reply_to || thread || cover_letter) - rev.ref_message_ids = xcalloc(1, sizeof(struct string_list)); + if (in_reply_to || thread || cover_letter) { + rev.ref_message_ids = xmalloc(sizeof(*rev.ref_message_ids)); + string_list_init_nodup(rev.ref_message_ids); + } if (in_reply_to) { const char *msgid = clean_message_id(in_reply_to); string_list_append(rev.ref_message_ids, msgid); @@ -2241,7 +2303,11 @@ done: strbuf_release(&rdiff1); strbuf_release(&rdiff2); strbuf_release(&rdiff_title); - return 0; + free(to_free); + if (rev.ref_message_ids) + string_list_clear(rev.ref_message_ids, 0); + free(rev.ref_message_ids); + return cmd_log_deinit(0, &rev); } static int add_pending_commit(const char *arg, struct rev_info *revs, int flags) diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 031fef1bca..e791b65e7e 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -37,6 +37,7 @@ static int debug_mode; static int show_eol; static int recurse_submodules; static int skipping_duplicates; +static int show_sparse_dirs; static const char *prefix; static int max_prefix_len; @@ -243,7 +244,7 @@ static void show_ce(struct repository *repo, struct dir_struct *dir, printf("%s%06o %s %d\t", tag, ce->ce_mode, - find_unique_abbrev(&ce->oid, abbrev), + repo_find_unique_abbrev(repo, &ce->oid, abbrev), ce_stage(ce)); } write_eolinfo(repo->index, ce, fullname); @@ -315,8 +316,10 @@ static void show_files(struct repository *repo, struct dir_struct *dir) if (!(show_cached || show_stage || show_deleted || show_modified)) return; - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(repo->index); + + if (!show_sparse_dirs) + ensure_full_index(repo->index); + for (i = 0; i < repo->index->cache_nr; i++) { const struct cache_entry *ce = repo->index->cache[i]; struct stat st; @@ -670,6 +673,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) OPT_BOOL(0, "debug", &debug_mode, N_("show debugging data")), OPT_BOOL(0, "deduplicate", &skipping_duplicates, N_("suppress duplicate entries")), + OPT_BOOL(0, "sparse", &show_sparse_dirs, + N_("show sparse directories in the presence of a sparse index")), OPT_END() }; int ret = 0; @@ -677,6 +682,9 @@ 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); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + prefix = cmd_prefix; if (prefix) prefix_len = strlen(prefix); @@ -718,7 +726,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) setup_work_tree(); if (recurse_submodules && - (show_stage || show_deleted || show_others || show_unmerged || + (show_deleted || show_others || show_unmerged || show_killed || show_modified || show_resolve_undo || with_tree)) die("ls-files --recurse-submodules unsupported mode"); @@ -767,7 +775,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) * would not make any sense with this option. */ if (show_stage || show_unmerged) - die("ls-files --with-tree is incompatible with -s or -u"); + die(_("options '%s' and '%s' cannot be used together"), "ls-files --with-tree", "-s/-u"); overlay_tree_on_index(the_repository->index, with_tree, max_prefix); } diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 318949c3d7..df44e5cc0d 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -54,7 +54,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) struct transport *transport; const struct ref *ref; struct ref_array ref_array; - static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; + struct string_list sorting_options = STRING_LIST_INIT_DUP; struct option options[] = { OPT__QUIET(&quiet, N_("do not print remote URL")), @@ -68,7 +68,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) OPT_BIT(0, "refs", &flags, N_("do not show peeled tags"), REF_NORMAL), OPT_BOOL(0, "get-url", &get_url, N_("take url.<base>.insteadOf into account")), - OPT_REF_SORT(sorting_tail), + OPT_REF_SORT(&sorting_options), OPT_SET_INT_F(0, "exit-code", &status, N_("exit with exit code 2 if no matching refs are found"), 2, PARSE_OPT_NOCOMPLETE), @@ -86,8 +86,6 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) packet_trace_identity("ls-remote"); - UNLEAK(sorting); - if (argc > 1) { int i; CALLOC_ARRAY(pattern, argc); @@ -116,7 +114,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) } transport = transport_get(remote, NULL); - if (uploadpack != NULL) + if (uploadpack) transport_set_option(transport, TRANS_OPT_UPLOADPACK, uploadpack); if (server_options.nr) transport->server_options = &server_options; @@ -139,8 +137,13 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) item->symref = xstrdup_or_null(ref->symref); } - if (sorting) + if (sorting_options.nr) { + struct ref_sorting *sorting; + + sorting = ref_sorting_options(&sorting_options); ref_array_sort(sorting, &ref_array); + ref_sorting_release(sorting); + } for (i = 0; i < ref_array.nr; i++) { const struct ref_array_item *ref = ref_array.items[i]; @@ -152,6 +155,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) ref_array_clear(&ref_array); if (transport_disconnect(transport)) - return 1; + status = 1; + transport_ls_refs_options_release(&transport_options); return status; } diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 696d94b8b8..e279be8bb6 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -364,7 +364,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); ls_tree_prefix = prefix; - if (prefix && *prefix) + if (prefix) chomp_prefix = strlen(prefix); argc = parse_options(argc, argv, prefix, ls_tree_options, diff --git a/builtin/mailsplit.c b/builtin/mailsplit.c index 7baef30569..73509f651b 100644 --- a/builtin/mailsplit.c +++ b/builtin/mailsplit.c @@ -120,7 +120,7 @@ static int populate_maildir_list(struct string_list *list, const char *path) for (sub = subs; *sub; ++sub) { free(name); name = xstrfmt("%s/%s", path, *sub); - if ((dir = opendir(name)) == NULL) { + if (!(dir = opendir(name))) { if (errno == ENOENT) continue; error_errno("cannot opendir %s", name); @@ -223,6 +223,9 @@ static int split_mbox(const char *file, const char *dir, int allow_bare, FILE *f = !strcmp(file, "-") ? stdin : fopen(file, "r"); int file_done = 0; + if (isatty(fileno(f))) + warning(_("reading patches from stdin/tty...")); + if (!f) { error_errno("cannot open mbox %s", file); goto out; diff --git a/builtin/merge-base.c b/builtin/merge-base.c index 6719ac198d..a11f8c6e4b 100644 --- a/builtin/merge-base.c +++ b/builtin/merge-base.c @@ -138,6 +138,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix) int rev_nr = 0; int show_all = 0; int cmdmode = 0; + int ret; struct option options[] = { OPT_BOOL('a', "all", &show_all, N_("output all common ancestors")), @@ -159,12 +160,14 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix) if (argc < 2) usage_with_options(merge_base_usage, options); if (show_all) - die("--is-ancestor cannot be used with --all"); + die(_("options '%s' and '%s' cannot be used together"), + "--is-ancestor", "--all"); return handle_is_ancestor(argc, argv); } if (cmdmode == 'r' && show_all) - die("--independent cannot be used with --all"); + die(_("options '%s' and '%s' cannot be used together"), + "--independent", "--all"); if (cmdmode == 'o') return handle_octopus(argc, argv, show_all); @@ -184,5 +187,7 @@ int cmd_merge_base(int argc, const char **argv, const char *prefix) ALLOC_ARRAY(rev, argc); while (argc-- > 0) rev[rev_nr++] = get_commit_reference(*argv++); - return show_merge_base(rev, rev_nr, show_all); + ret = show_merge_base(rev, rev_nr, show_all); + free(rev); + return ret; } diff --git a/builtin/merge-file.c b/builtin/merge-file.c index 06a2f90c48..e695867ee5 100644 --- a/builtin/merge-file.c +++ b/builtin/merge-file.c @@ -34,6 +34,8 @@ int cmd_merge_file(int argc, const char **argv, const char *prefix) struct option options[] = { OPT_BOOL('p', "stdout", &to_stdout, N_("send results to standard output")), OPT_SET_INT(0, "diff3", &xmp.style, N_("use a diff3 based merge"), XDL_MERGE_DIFF3), + OPT_SET_INT(0, "zdiff3", &xmp.style, N_("use a zealous diff3 based merge"), + XDL_MERGE_ZEALOUS_DIFF3), OPT_SET_INT(0, "ours", &xmp.favor, N_("for conflicts, use our version"), XDL_MERGE_FAVOR_OURS), OPT_SET_INT(0, "theirs", &xmp.favor, N_("for conflicts, use their version"), diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index a4bfd8fc51..b9acbf5d34 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -58,7 +58,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) "Ignoring %s.", "cannot handle more than %d bases. " "Ignoring %s.", - (int)ARRAY_SIZE(bases)-1), + ARRAY_SIZE(bases)-1), (int)ARRAY_SIZE(bases)-1, argv[i]); } if (argc - i != 3) /* "--" "<head>" "<remote>" */ diff --git a/builtin/merge.c b/builtin/merge.c index ea3112e0c0..d9784d4891 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -87,6 +87,7 @@ static int signoff; static const char *sign_commit; static int autostash; static int no_verify; +static char *into_name; static struct strategy all_strategy[] = { { "recursive", NO_TRIVIAL }, @@ -286,6 +287,8 @@ static struct option builtin_merge_options[] = { { OPTION_LOWLEVEL_CALLBACK, 'F', "file", &merge_msg, N_("path"), N_("read message from file"), PARSE_OPT_NONEG, NULL, 0, option_read_message }, + OPT_STRING(0, "into-name", &into_name, N_("name"), + N_("use <name> instead of the real target")), OPT__VERBOSITY(&verbosity), OPT_BOOL(0, "abort", &abort_current_merge, N_("abort the current in-progress merge")), @@ -310,10 +313,9 @@ static int save_state(struct object_id *stash) int len; struct child_process cp = CHILD_PROCESS_INIT; struct strbuf buffer = STRBUF_INIT; - const char *argv[] = {"stash", "create", NULL}; int rc = -1; - cp.argv = argv; + strvec_pushl(&cp.args, "stash", "create", NULL); cp.out = -1; cp.git_cmd = 1; @@ -441,6 +443,7 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead } write_file_buf(git_path_squash_msg(the_repository), out.buf, out.len); strbuf_release(&out); + release_revisions(&rev); } static void finish(struct commit *head_commit, @@ -488,7 +491,7 @@ static void finish(struct commit *head_commit, } /* Run a post-merge hook */ - run_hook_le(NULL, "post-merge", squash ? "1" : "0", NULL); + run_hooks_l("post-merge", squash ? "1" : "0", NULL); apply_autostash(git_path_merge_autostash(the_repository)); strbuf_release(&reflog_message); @@ -843,15 +846,20 @@ 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 (hook_exists("pre-merge-commit")) - discard_cache(); + if (!no_verify) { + int invoked_hook; + + if (run_commit_hook(0 < option_edit, index_file, &invoked_hook, + "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 (invoked_hook) + discard_cache(); + } read_cache_from(index_file); strbuf_addbuf(&msg, &merge_msg); if (squash) @@ -873,7 +881,8 @@ static void prepare_to_commit(struct commit_list *remoteheads) append_signoff(&msg, ignore_non_trailer(msg.buf, msg.len), 0); write_merge_heads(remoteheads); write_file_buf(git_path_merge_msg(the_repository), msg.buf, msg.len); - if (run_commit_hook(0 < option_edit, get_index_file(), "prepare-commit-msg", + if (run_commit_hook(0 < option_edit, get_index_file(), NULL, + "prepare-commit-msg", git_path_merge_msg(the_repository), "merge", NULL)) abort_commit(remoteheads, NULL); if (0 < option_edit) { @@ -882,7 +891,7 @@ static void prepare_to_commit(struct commit_list *remoteheads) } if (!no_verify && run_commit_hook(0 < option_edit, get_index_file(), - "commit-msg", + NULL, "commit-msg", git_path_merge_msg(the_repository), NULL)) abort_commit(remoteheads, NULL); @@ -990,6 +999,7 @@ static int evaluate_result(void) */ cnt += count_unmerged_entries(); + release_revisions(&rev); return cnt; } @@ -1122,6 +1132,7 @@ static void prepare_merge_message(struct strbuf *merge_names, struct strbuf *mer opts.add_title = !have_message; opts.shortlog_len = shortlog_len; opts.credit_people = (0 < option_edit); + opts.into_name = into_name; fmt_merge_msg(merge_names, merge_msg, &opts); if (merge_msg->len) @@ -1270,7 +1281,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) int best_cnt = -1, merge_was_ok = 0, automerge_was_ok = 0; struct commit_list *common = NULL; const char *best_strategy = NULL, *wt_strategy = NULL; - struct commit_list *remoteheads, *p; + struct commit_list *remoteheads = NULL, *p; void *branch_to_free; int orig_argc = argc; @@ -1397,9 +1408,9 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (squash) { if (fast_forward == FF_NO) - die(_("You cannot combine --squash with --no-ff.")); + die(_("options '%s' and '%s' cannot be used together"), "--squash", "--no-ff."); if (option_commit > 0) - die(_("You cannot combine --squash with --commit.")); + die(_("options '%s' and '%s' cannot be used together"), "--squash", "--commit."); /* * squash can now silently disable option_commit - this is not * a problem as it is only overriding the default, not a user @@ -1565,8 +1576,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (autostash) create_autostash(the_repository, - git_path_merge_autostash(the_repository), - "merge"); + git_path_merge_autostash(the_repository)); if (checkout_fast_forward(the_repository, &head_commit->object.oid, &commit->object.oid, @@ -1637,8 +1647,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (autostash) create_autostash(the_repository, - git_path_merge_autostash(the_repository), - "merge"); + git_path_merge_autostash(the_repository)); /* We are going to make a new commit. */ git_committer_info(IDENT_STRICT); @@ -1749,6 +1758,10 @@ int cmd_merge(int argc, const char **argv, const char *prefix) ret = suggest_conflicts(); done: + if (!automerge_was_ok) { + free_commit_list(common); + free_commit_list(remoteheads); + } strbuf_release(&buf); free(branch_to_free); return ret; diff --git a/builtin/mktag.c b/builtin/mktag.c index 3b2dbbb37e..5d22909122 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -7,7 +7,7 @@ #include "config.h" static char const * const builtin_mktag_usage[] = { - N_("git mktag"), + "git mktag", NULL }; static int option_strict = 1; @@ -61,9 +61,8 @@ static int verify_object_in_tag(struct object_id *tagged_oid, int *tagged_type) type_name(*tagged_type), type_name(type)); repl = lookup_replace_object(the_repository, tagged_oid); - ret = check_object_signature(the_repository, repl, - buffer, size, type_name(*tagged_type), - NULL); + ret = check_object_signature(the_repository, repl, buffer, size, + *tagged_type); free(buffer); return ret; @@ -97,10 +96,10 @@ int cmd_mktag(int argc, const char **argv, const char *prefix) &tagged_oid, &tagged_type)) die(_("tag on stdin did not pass our strict fsck check")); - if (verify_object_in_tag(&tagged_oid, &tagged_type)) + if (verify_object_in_tag(&tagged_oid, &tagged_type) < 0) die(_("tag on stdin did not refer to a valid object")); - if (write_object_file(buf.buf, buf.len, tag_type, &result) < 0) + if (write_object_file(buf.buf, buf.len, OBJ_TAG, &result) < 0) die(_("unable to write tag file")); strbuf_release(&buf); diff --git a/builtin/mktree.c b/builtin/mktree.c index ae78ca1c02..902edba6d2 100644 --- a/builtin/mktree.c +++ b/builtin/mktree.c @@ -58,12 +58,12 @@ static void write_tree(struct object_id *oid) strbuf_add(&buf, ent->oid.hash, the_hash_algo->rawsz); } - write_object_file(buf.buf, buf.len, tree_type, oid); + write_object_file(buf.buf, buf.len, OBJ_TREE, oid); strbuf_release(&buf); } static const char *mktree_usage[] = { - N_("git mktree [-z] [--missing] [--batch]"), + "git mktree [-z] [--missing] [--batch]", NULL }; diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index 075d15d706..5edbb7fe86 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -44,7 +44,7 @@ static char const * const builtin_multi_pack_index_usage[] = { }; static struct opts_multi_pack_index { - const char *object_dir; + char *object_dir; const char *preferred_pack; const char *refs_snapshot; unsigned long batch_size; @@ -52,9 +52,23 @@ static struct opts_multi_pack_index { int stdin_packs; } opts; + +static int parse_object_dir(const struct option *opt, const char *arg, + int unset) +{ + free(opts.object_dir); + if (unset) + opts.object_dir = xstrdup(get_object_directory()); + else + opts.object_dir = real_pathdup(arg, 1); + return 0; +} + static struct option common_opts[] = { - OPT_FILENAME(0, "object-dir", &opts.object_dir, - N_("object directory containing set of packfile and pack-index pairs")), + OPT_CALLBACK(0, "object-dir", &opts.object_dir, + N_("directory"), + N_("object directory containing set of packfile and pack-index pairs"), + parse_object_dir), OPT_END(), }; @@ -167,6 +181,8 @@ static int cmd_multi_pack_index_verify(int argc, const char **argv) usage_with_options(builtin_multi_pack_index_verify_usage, options); + FREE_AND_NULL(options); + return verify_midx_file(the_repository, opts.object_dir, opts.flags); } @@ -191,6 +207,8 @@ static int cmd_multi_pack_index_expire(int argc, const char **argv) usage_with_options(builtin_multi_pack_index_expire_usage, options); + FREE_AND_NULL(options); + return expire_midx_packs(the_repository, opts.object_dir, opts.flags); } @@ -228,31 +246,40 @@ static int cmd_multi_pack_index_repack(int argc, const char **argv) int cmd_multi_pack_index(int argc, const char **argv, const char *prefix) { + int res; struct option *builtin_multi_pack_index_options = common_opts; git_config(git_default_config, NULL); + if (the_repository && + the_repository->objects && + the_repository->objects->odb) + opts.object_dir = xstrdup(the_repository->objects->odb->path); + argc = parse_options(argc, argv, prefix, builtin_multi_pack_index_options, builtin_multi_pack_index_usage, PARSE_OPT_STOP_AT_NON_OPTION); - if (!opts.object_dir) - opts.object_dir = get_object_directory(); - if (!argc) goto usage; if (!strcmp(argv[0], "repack")) - return cmd_multi_pack_index_repack(argc, argv); + res = cmd_multi_pack_index_repack(argc, argv); else if (!strcmp(argv[0], "write")) - return cmd_multi_pack_index_write(argc, argv); + res = cmd_multi_pack_index_write(argc, argv); else if (!strcmp(argv[0], "verify")) - return cmd_multi_pack_index_verify(argc, argv); + res = cmd_multi_pack_index_verify(argc, argv); else if (!strcmp(argv[0], "expire")) - return cmd_multi_pack_index_expire(argc, argv); + res = cmd_multi_pack_index_expire(argc, argv); + else { + error(_("unrecognized subcommand: %s"), argv[0]); + goto usage; + } + + free(opts.object_dir); + return res; - error(_("unrecognized subcommand: %s"), argv[0]); usage: usage_with_options(builtin_multi_pack_index_usage, builtin_multi_pack_index_options); diff --git a/builtin/name-rev.c b/builtin/name-rev.c index b221d30014..02ea9d1633 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -9,6 +9,7 @@ #include "prio-queue.h" #include "hash-lookup.h" #include "commit-slab.h" +#include "commit-graph.h" /* * One day. See the 'name a rev shortly after epoch' test in t6120 when @@ -17,7 +18,7 @@ #define CUTOFF_DATE_SLOP 86400 struct rev_name { - char *tip_name; + const char *tip_name; timestamp_t taggerdate; int generation; int distance; @@ -26,15 +27,64 @@ struct rev_name { define_commit_slab(commit_rev_name, struct rev_name); +static timestamp_t generation_cutoff = GENERATION_NUMBER_INFINITY; static timestamp_t cutoff = TIME_MAX; static struct commit_rev_name rev_names; +/* Disable the cutoff checks entirely */ +static void disable_cutoff(void) +{ + generation_cutoff = 0; + cutoff = 0; +} + +/* Cutoff searching any commits older than this one */ +static void set_commit_cutoff(struct commit *commit) +{ + + if (cutoff > commit->date) + cutoff = commit->date; + + if (generation_cutoff) { + timestamp_t generation = commit_graph_generation(commit); + + if (generation_cutoff > generation) + generation_cutoff = generation; + } +} + +/* adjust the commit date cutoff with a slop to allow for slightly incorrect + * commit timestamps in case of clock skew. + */ +static void adjust_cutoff_timestamp_for_slop(void) +{ + if (cutoff) { + /* check for undeflow */ + if (cutoff > TIME_MIN + CUTOFF_DATE_SLOP) + cutoff = cutoff - CUTOFF_DATE_SLOP; + else + cutoff = TIME_MIN; + } +} + +/* Check if a commit is before the cutoff. Prioritize generation numbers + * first, but use the commit timestamp if we lack generation data. + */ +static int commit_is_before_cutoff(struct commit *commit) +{ + if (generation_cutoff < GENERATION_NUMBER_INFINITY) + return generation_cutoff && + commit_graph_generation(commit) < generation_cutoff; + + return commit->date < cutoff; +} + /* How many generations are maximally preferred over _one_ merge traversal? */ #define MERGE_TRAVERSAL_WEIGHT 65535 static int is_valid_rev_name(const struct rev_name *name) { - return name && (name->generation || name->tip_name); + return name && name->tip_name; } static struct rev_name *get_commit_rev_name(const struct commit *commit) @@ -44,11 +94,20 @@ static struct rev_name *get_commit_rev_name(const struct commit *commit) return is_valid_rev_name(name) ? name : NULL; } +static int effective_distance(int distance, int generation) +{ + return distance + (generation > 0 ? MERGE_TRAVERSAL_WEIGHT : 0); +} + static int is_better_name(struct rev_name *name, timestamp_t taggerdate, + int generation, int distance, int from_tag) { + int name_distance = effective_distance(name->distance, name->generation); + int new_distance = effective_distance(distance, generation); + /* * When comparing names based on tags, prefer names * based on the older tag, even if it is farther away. @@ -56,7 +115,7 @@ static int is_better_name(struct rev_name *name, if (from_tag && name->from_tag) return (name->taggerdate > taggerdate || (name->taggerdate == taggerdate && - name->distance > distance)); + name_distance > new_distance)); /* * We know that at least one of them is a non-tag at this point. @@ -69,8 +128,8 @@ static int is_better_name(struct rev_name *name, * We are now looking at two non-tags. Tiebreak to favor * shorter hops. */ - if (name->distance != distance) - return name->distance > distance; + if (name_distance != new_distance) + return name_distance > new_distance; /* ... or tiebreak to favor older date */ if (name->taggerdate != taggerdate) @@ -87,20 +146,9 @@ static struct rev_name *create_or_update_name(struct 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 (is_valid_rev_name(name) && + !is_better_name(name, taggerdate, generation, distance, from_tag)) + return NULL; name->taggerdate = taggerdate; name->generation = generation; @@ -142,7 +190,7 @@ static void name_rev(struct commit *start_commit, struct rev_name *start_name; parse_commit(start_commit); - if (start_commit->date < cutoff) + if (commit_is_before_cutoff(start_commit)) return; start_name = create_or_update_name(start_commit, taggerdate, 0, 0, @@ -172,7 +220,7 @@ static void name_rev(struct commit *start_commit, int generation, distance; parse_commit(parent); - if (parent->date < cutoff) + if (commit_is_before_cutoff(parent)) continue; if (parent_number > 1) { @@ -464,7 +512,7 @@ static void show_name(const struct object *obj, static char const * const name_rev_usage[] = { N_("git name-rev [<options>] <commit>..."), N_("git name-rev [<options>] --all"), - N_("git name-rev [<options>] --stdin"), + N_("git name-rev [<options>] --annotate-stdin"), NULL }; @@ -518,7 +566,7 @@ static void name_rev_line(char *p, struct name_ref_data *data) int cmd_name_rev(int argc, const char **argv, const char *prefix) { struct object_array revs = OBJECT_ARRAY_INIT; - int all = 0, transform_stdin = 0, allow_undefined = 1, always = 0, peel_tag = 0; + int all = 0, annotate_stdin = 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 ref-based names (no object names)")), @@ -529,7 +577,8 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) N_("ignore refs matching <pattern>")), OPT_GROUP(""), OPT_BOOL(0, "all", &all, N_("list all commits reachable from all refs")), - OPT_BOOL(0, "stdin", &transform_stdin, N_("read from stdin")), + OPT_BOOL(0, "stdin", &transform_stdin, N_("deprecated: use annotate-stdin instead")), + OPT_BOOL(0, "annotate-stdin", &annotate_stdin, N_("annotate text from stdin")), OPT_BOOL(0, "undefined", &allow_undefined, N_("allow to print `undefined` names (default)")), OPT_BOOL(0, "always", &always, N_("show abbreviated commit object as fallback")), @@ -545,12 +594,20 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) init_commit_rev_name(&rev_names); git_config(git_default_config, NULL); argc = parse_options(argc, argv, prefix, opts, name_rev_usage, 0); - if (all + transform_stdin + !!argc > 1) { + + if (transform_stdin) { + warning("--stdin is deprecated. Please use --annotate-stdin instead, " + "which is functionally equivalent.\n" + "This option will be removed in a future release."); + annotate_stdin = 1; + } + + if (all + annotate_stdin + !!argc > 1) { error("Specify either a list, or --all, not both!"); usage_with_options(name_rev_usage, opts); } - if (all || transform_stdin) - cutoff = 0; + if (all || annotate_stdin) + disable_cutoff(); for (; argc; argc--, argv++) { struct object_id oid; @@ -578,10 +635,8 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) continue; } - if (commit) { - if (cutoff > commit->date) - cutoff = commit->date; - } + if (commit) + set_commit_cutoff(commit); if (peel_tag) { if (!commit) { @@ -594,25 +649,19 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) add_object_array(object, *argv, &revs); } - if (cutoff) { - /* check for undeflow */ - if (cutoff > TIME_MIN + CUTOFF_DATE_SLOP) - cutoff = cutoff - CUTOFF_DATE_SLOP; - else - cutoff = TIME_MIN; - } + adjust_cutoff_timestamp_for_slop(); + for_each_ref(name_ref, &data); name_tips(); - if (transform_stdin) { - char buffer[2048]; + if (annotate_stdin) { + struct strbuf sb = STRBUF_INIT; - while (!feof(stdin)) { - char *p = fgets(buffer, sizeof(buffer), stdin); - if (!p) - break; - name_rev_line(p, &data); + while (strbuf_getline(&sb, stdin) != EOF) { + strbuf_addch(&sb, '\n'); + name_rev_line(sb.buf, &data); } + strbuf_release(&sb); } else if (all) { int i, max; diff --git a/builtin/notes.c b/builtin/notes.c index 71c59583a1..a3d0d15a22 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -32,8 +32,8 @@ static const char * const git_notes_usage[] = { N_("git notes [--ref <notes-ref>] edit [--allow-empty] [<object>]"), N_("git notes [--ref <notes-ref>] show [<object>]"), N_("git notes [--ref <notes-ref>] merge [-v | -q] [-s <strategy>] <notes-ref>"), - N_("git notes merge --commit [-v | -q]"), - N_("git notes merge --abort [-v | -q]"), + "git notes merge --commit [-v | -q]", + "git notes merge --abort [-v | -q]", N_("git notes [--ref <notes-ref>] remove [<object>...]"), N_("git notes [--ref <notes-ref>] prune [-n] [-v]"), N_("git notes [--ref <notes-ref>] get-ref"), @@ -89,7 +89,7 @@ static const char * const git_notes_prune_usage[] = { }; static const char * const git_notes_get_ref_usage[] = { - N_("git notes get-ref"), + "git notes get-ref", NULL }; @@ -134,14 +134,13 @@ static void copy_obj_to_fd(int fd, const struct object_id *oid) static void write_commented_object(int fd, const struct object_id *object) { - const char *show_args[5] = - {"show", "--stat", "--no-notes", oid_to_hex(object), NULL}; struct child_process show = CHILD_PROCESS_INIT; struct strbuf buf = STRBUF_INIT; struct strbuf cbuf = STRBUF_INIT; /* Invoke "git show --stat --no-notes $object" */ - show.argv = show_args; + strvec_pushl(&show.args, "show", "--stat", "--no-notes", + oid_to_hex(object), NULL); show.no_stdin = 1; show.out = -1; show.err = 0; @@ -200,12 +199,13 @@ static void prepare_note_data(const struct object_id *object, struct note_data * static void write_note_data(struct note_data *d, struct object_id *oid) { - if (write_object_file(d->buf.buf, d->buf.len, blob_type, oid)) { - error(_("unable to write note object")); + if (write_object_file(d->buf.buf, d->buf.len, OBJ_BLOB, oid)) { + int status = die_message(_("unable to write note object")); + if (d->edit_path) - error(_("the note contents have been left in %s"), - d->edit_path); - exit(128); + die_message(_("the note contents have been left in %s"), + d->edit_path); + exit(status); } } @@ -861,15 +861,19 @@ static int merge(int argc, const char **argv, const char *prefix) update_ref(msg.buf, default_notes_ref(), &result_oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR); else { /* Merge has unresolved conflicts */ + struct worktree **worktrees; const struct worktree *wt; /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */ update_ref(msg.buf, "NOTES_MERGE_PARTIAL", &result_oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR); /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */ - wt = find_shared_symref("NOTES_MERGE_REF", default_notes_ref()); + worktrees = get_worktrees(); + wt = find_shared_symref(worktrees, "NOTES_MERGE_REF", + default_notes_ref()); if (wt) die(_("a notes merge into %s is already in-progress at %s"), default_notes_ref(), wt->path); + free_worktrees(worktrees); if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL)) die(_("failed to store link to current notes ref (%s)"), default_notes_ref()); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 1a3dd445f8..cc5f41086d 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -36,6 +36,7 @@ #include "trace2.h" #include "shallow.h" #include "promisor-remote.h" +#include "pack-mtimes.h" /* * Objects we are going to pack are collected in the `to_pack` structure. @@ -194,6 +195,8 @@ static int reuse_delta = 1, reuse_object = 1; static int keep_unreachable, unpack_unreachable, include_tag; static timestamp_t unpack_unreachable_expiration; static int pack_loose_unreachable; +static int cruft; +static timestamp_t cruft_expiration; static int local; static int have_non_local_packs; static int incremental; @@ -237,8 +240,6 @@ static unsigned long cache_max_small_delta_size = 1000; 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 { @@ -1199,16 +1200,26 @@ static void write_pack_file(void) display_progress(progress_state, written); } - /* - * Did we write the wrong # entries in the header? - * If so, rewrite it like in fast-import - */ if (pack_to_stdout) { - finalize_hashfile(f, hash, CSUM_HASH_IN_STREAM | CSUM_CLOSE); + /* + * We never fsync when writing to stdout since we may + * not be writing to an actual pack file. For instance, + * the upload-pack code passes a pipe here. Calling + * fsync on a pipe results in unnecessary + * synchronization with the reader on some platforms. + */ + finalize_hashfile(f, hash, FSYNC_COMPONENT_NONE, + CSUM_HASH_IN_STREAM | CSUM_CLOSE); } else if (nr_written == nr_remaining) { - finalize_hashfile(f, hash, CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE); + finalize_hashfile(f, hash, FSYNC_COMPONENT_PACK, + CSUM_HASH_IN_STREAM | CSUM_FSYNC | CSUM_CLOSE); } else { - int fd = finalize_hashfile(f, hash, 0); + /* + * If we wrote the wrong number of entries in the + * header, rewrite it like in fast-import. + */ + + int fd = finalize_hashfile(f, hash, FSYNC_COMPONENT_PACK, 0); fixup_pack_header_footer(fd, hash, pack_tmp_name, nr_written, hash, offset); close(fd); @@ -1252,9 +1263,13 @@ static void write_pack_file(void) &to_pack, written_list, nr_written); } + if (cruft) + pack_idx_opts.flags |= WRITE_MTIMES; + stage_tmp_packfiles(&tmpname, pack_tmp_name, written_list, nr_written, - &pack_idx_opts, hash, &idx_tmp_name); + &to_pack, &pack_idx_opts, hash, + &idx_tmp_name); if (write_bitmap_index) { size_t tmpname_len = tmpname.len; @@ -1349,6 +1364,9 @@ static int want_found_object(const struct object_id *oid, int exclude, if (incremental) return 0; + if (!is_pack_valid(p)) + return -1; + /* * When asked to do --local (do not include an object that appears in a * pack we borrow from elsewhere) or --honor-pack-keep (do not include @@ -1464,6 +1482,9 @@ static int want_object_in_pack(const struct object_id *oid, want = want_found_object(oid, exclude, *found_pack); if (want != -1) return want; + + *found_pack = NULL; + *found_offset = 0; } for (m = get_multi_pack_index(the_repository); m; m = m->next) { @@ -1507,13 +1528,13 @@ static int want_object_in_pack(const struct object_id *oid, return 1; } -static void create_object_entry(const struct object_id *oid, - enum object_type type, - uint32_t hash, - int exclude, - int no_try_delta, - struct packed_git *found_pack, - off_t found_offset) +static struct object_entry *create_object_entry(const struct object_id *oid, + enum object_type type, + uint32_t hash, + int exclude, + int no_try_delta, + struct packed_git *found_pack, + off_t found_offset) { struct object_entry *entry; @@ -1530,6 +1551,8 @@ static void create_object_entry(const struct object_id *oid, } entry->no_try_delta = no_try_delta; + + return entry; } static const char no_closure_warning[] = N_( @@ -1802,7 +1825,7 @@ static void add_preferred_base(struct object_id *oid) return; data = read_object_with_reference(the_repository, oid, - tree_type, &size, &tree_oid); + OBJ_TREE, &size, &tree_oid); if (!data) return; @@ -3193,10 +3216,8 @@ static int add_object_entry_from_pack(const struct object_id *oid, uint32_t pos, void *_data) { - struct rev_info *revs = _data; - struct object_info oi = OBJECT_INFO_INIT; off_t ofs; - enum object_type type; + enum object_type type = OBJ_NONE; display_progress(progress_state, ++nr_seen); @@ -3207,19 +3228,24 @@ static int add_object_entry_from_pack(const struct object_id *oid, if (!want_object_in_pack(oid, 0, &p, &ofs)) return 0; - oi.typep = &type; - if (packed_object_info(the_repository, p, ofs, &oi) < 0) - die(_("could not get type of object %s in pack %s"), - oid_to_hex(oid), p->pack_name); - else if (type == OBJ_COMMIT) { - /* - * commits in included packs are used as starting points for the - * subsequent revision walk - */ - add_pending_oid(revs, NULL, oid, 0); - } + if (p) { + struct rev_info *revs = _data; + struct object_info oi = OBJECT_INFO_INIT; - stdin_packs_found_nr++; + oi.typep = &type; + if (packed_object_info(the_repository, p, ofs, &oi) < 0) { + die(_("could not get type of object %s in pack %s"), + oid_to_hex(oid), p->pack_name); + } else if (type == OBJ_COMMIT) { + /* + * commits in included packs are used as starting points for the + * subsequent revision walk + */ + add_pending_oid(revs, NULL, oid, 0); + } + + stdin_packs_found_nr++; + } create_object_entry(oid, type, 0, 0, 0, p, ofs); @@ -3338,6 +3364,8 @@ static void read_packs_list_from_stdin(void) struct packed_git *p = item->util; if (!p) die(_("could not find pack '%s'"), item->string); + if (!is_pack_valid(p)) + die(_("packfile %s cannot be accessed"), p->pack_name); } /* @@ -3361,8 +3389,6 @@ static void read_packs_list_from_stdin(void) for_each_string_list_item(item, &include_packs) { struct packed_git *p = item->util; - if (!p) - die(_("could not find pack '%s'"), item->string); for_each_object_in_pack(p, add_object_entry_from_pack, &revs, @@ -3386,6 +3412,217 @@ static void read_packs_list_from_stdin(void) string_list_clear(&exclude_packs, 0); } +static void add_cruft_object_entry(const struct object_id *oid, enum object_type type, + struct packed_git *pack, off_t offset, + const char *name, uint32_t mtime) +{ + struct object_entry *entry; + + display_progress(progress_state, ++nr_seen); + + entry = packlist_find(&to_pack, oid); + if (entry) { + if (name) { + entry->hash = pack_name_hash(name); + entry->no_try_delta = no_try_delta(name); + } + } else { + if (!want_object_in_pack(oid, 0, &pack, &offset)) + return; + if (!pack && type == OBJ_BLOB && !has_loose_object(oid)) { + /* + * If a traversed tree has a missing blob then we want + * to avoid adding that missing object to our pack. + * + * This only applies to missing blobs, not trees, + * because the traversal needs to parse sub-trees but + * not blobs. + * + * Note we only perform this check when we couldn't + * already find the object in a pack, so we're really + * limited to "ensure non-tip blobs which don't exist in + * packs do exist via loose objects". Confused? + */ + return; + } + + entry = create_object_entry(oid, type, pack_name_hash(name), + 0, name && no_try_delta(name), + pack, offset); + } + + if (mtime > oe_cruft_mtime(&to_pack, entry)) + oe_set_cruft_mtime(&to_pack, entry, mtime); + return; +} + +static void show_cruft_object(struct object *obj, const char *name, void *data) +{ + /* + * if we did not record it earlier, it's at least as old as our + * expiration value. Rather than find it exactly, just use that + * value. This may bump it forward from its real mtime, but it + * will still be "too old" next time we run with the same + * expiration. + * + * if obj does appear in the packing list, this call is a noop (or may + * set the namehash). + */ + add_cruft_object_entry(&obj->oid, obj->type, NULL, 0, name, cruft_expiration); +} + +static void show_cruft_commit(struct commit *commit, void *data) +{ + show_cruft_object((struct object*)commit, NULL, data); +} + +static int cruft_include_check_obj(struct object *obj, void *data) +{ + return !has_object_kept_pack(&obj->oid, IN_CORE_KEEP_PACKS); +} + +static int cruft_include_check(struct commit *commit, void *data) +{ + return cruft_include_check_obj((struct object*)commit, data); +} + +static void set_cruft_mtime(const struct object *object, + struct packed_git *pack, + off_t offset, time_t mtime) +{ + add_cruft_object_entry(&object->oid, object->type, pack, offset, NULL, + mtime); +} + +static void mark_pack_kept_in_core(struct string_list *packs, unsigned keep) +{ + struct string_list_item *item = NULL; + for_each_string_list_item(item, packs) { + struct packed_git *p = item->util; + if (!p) + die(_("could not find pack '%s'"), item->string); + p->pack_keep_in_core = keep; + } +} + +static void add_unreachable_loose_objects(void); +static void add_objects_in_unpacked_packs(void); + +static void enumerate_cruft_objects(void) +{ + if (progress) + progress_state = start_progress(_("Enumerating cruft objects"), 0); + + add_objects_in_unpacked_packs(); + add_unreachable_loose_objects(); + + stop_progress(&progress_state); +} + +static void enumerate_and_traverse_cruft_objects(struct string_list *fresh_packs) +{ + struct packed_git *p; + struct rev_info revs; + int ret; + + repo_init_revisions(the_repository, &revs, NULL); + + revs.tag_objects = 1; + revs.tree_objects = 1; + revs.blob_objects = 1; + + revs.include_check = cruft_include_check; + revs.include_check_obj = cruft_include_check_obj; + + revs.ignore_missing_links = 1; + + if (progress) + progress_state = start_progress(_("Enumerating cruft objects"), 0); + ret = add_unseen_recent_objects_to_traversal(&revs, cruft_expiration, + set_cruft_mtime, 1); + stop_progress(&progress_state); + + if (ret) + die(_("unable to add cruft objects")); + + /* + * Re-mark only the fresh packs as kept so that objects in + * unknown packs do not halt the reachability traversal early. + */ + for (p = get_all_packs(the_repository); p; p = p->next) + p->pack_keep_in_core = 0; + mark_pack_kept_in_core(fresh_packs, 1); + + if (prepare_revision_walk(&revs)) + die(_("revision walk setup failed")); + if (progress) + progress_state = start_progress(_("Traversing cruft objects"), 0); + nr_seen = 0; + traverse_commit_list(&revs, show_cruft_commit, show_cruft_object, NULL); + + stop_progress(&progress_state); +} + +static void read_cruft_objects(void) +{ + struct strbuf buf = STRBUF_INIT; + struct string_list discard_packs = STRING_LIST_INIT_DUP; + struct string_list fresh_packs = STRING_LIST_INIT_DUP; + struct packed_git *p; + + ignore_packed_keep_in_core = 1; + + while (strbuf_getline(&buf, stdin) != EOF) { + if (!buf.len) + continue; + + if (*buf.buf == '-') + string_list_append(&discard_packs, buf.buf + 1); + else + string_list_append(&fresh_packs, buf.buf); + strbuf_reset(&buf); + } + + string_list_sort(&discard_packs); + string_list_sort(&fresh_packs); + + for (p = get_all_packs(the_repository); p; p = p->next) { + const char *pack_name = pack_basename(p); + struct string_list_item *item; + + item = string_list_lookup(&fresh_packs, pack_name); + if (!item) + item = string_list_lookup(&discard_packs, pack_name); + + if (item) { + item->util = p; + } else { + /* + * This pack wasn't mentioned in either the "fresh" or + * "discard" list, so the caller didn't know about it. + * + * Mark it as kept so that its objects are ignored by + * add_unseen_recent_objects_to_traversal(). We'll + * unmark it before starting the traversal so it doesn't + * halt the traversal early. + */ + p->pack_keep_in_core = 1; + } + } + + mark_pack_kept_in_core(&fresh_packs, 1); + mark_pack_kept_in_core(&discard_packs, 0); + + if (cruft_expiration) + enumerate_and_traverse_cruft_objects(&fresh_packs); + else + enumerate_cruft_objects(); + + strbuf_release(&buf); + string_list_clear(&discard_packs, 0); + string_list_clear(&fresh_packs, 0); +} + static void read_object_list_from_stdin(void) { char line[GIT_MAX_HEXSZ + 1 + PATH_MAX + 2]; @@ -3397,7 +3634,7 @@ static void read_object_list_from_stdin(void) if (feof(stdin)) break; if (!ferror(stdin)) - die("BUG: fgets returned NULL, not EOF, not error!"); + BUG("fgets returned NULL, not EOF, not error!"); if (errno != EINTR) die_errno("fgets"); clearerr(stdin); @@ -3504,7 +3741,7 @@ static int option_parse_missing_action(const struct option *opt, return 0; } - die(_("invalid value for --missing")); + die(_("invalid value for '%s': '%s'"), "--missing", arg); return 0; } @@ -3518,7 +3755,24 @@ static int add_object_in_unpacked_pack(const struct object_id *oid, uint32_t pos, void *_data) { - add_object_entry(oid, OBJ_NONE, "", 0); + if (cruft) { + off_t offset; + time_t mtime; + + if (pack->is_cruft) { + if (load_pack_mtimes(pack) < 0) + die(_("could not load cruft pack .mtimes")); + mtime = nth_packed_mtime(pack, pos); + } else { + mtime = pack->mtime; + } + offset = nth_packed_object_offset(pack, pos); + + add_cruft_object_entry(oid, OBJ_NONE, pack, offset, + NULL, mtime); + } else { + add_object_entry(oid, OBJ_NONE, "", 0); + } return 0; } @@ -3542,7 +3796,19 @@ static int add_loose_object(const struct object_id *oid, const char *path, return 0; } - add_object_entry(oid, type, "", 0); + if (cruft) { + struct stat st; + if (stat(path, &st) < 0) { + if (errno == ENOENT) + return 0; + return error_errno("unable to stat %s", oid_to_hex(oid)); + } + + add_cruft_object_entry(oid, type, NULL, 0, NULL, + st.st_mtime); + } else { + add_object_entry(oid, type, "", 0); + } return 0; } @@ -3651,7 +3917,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, &filter_options, 0))) + if (!(bitmap_git = prepare_bitmap_walk(revs, 0))) return -1; if (pack_options_allow_reuse() && @@ -3714,9 +3980,8 @@ static void mark_bitmap_preferred_tips(void) } } -static void get_object_list(int ac, const char **av) +static void get_object_list(struct rev_info *revs, int ac, const char **av) { - struct rev_info revs; struct setup_revision_opt s_r_opt = { .allow_exclude_promisor_objects = 1, }; @@ -3724,9 +3989,8 @@ static void get_object_list(int ac, const char **av) int flags = 0; int save_warning; - repo_init_revisions(the_repository, &revs, NULL); save_commit_buffer = 0; - setup_revisions(ac, av, &revs, &s_r_opt); + setup_revisions(ac, av, revs, &s_r_opt); /* make sure shallows are read */ is_repository_shallow(the_repository); @@ -3756,13 +4020,13 @@ static void get_object_list(int ac, const char **av) } die(_("not a rev '%s'"), line); } - if (handle_revision_arg(line, &revs, flags, REVARG_CANNOT_BE_FILENAME)) + if (handle_revision_arg(line, revs, flags, REVARG_CANNOT_BE_FILENAME)) die(_("bad revision '%s'"), line); } warn_on_object_refname_ambiguity = save_warning; - if (use_bitmap_index && !get_object_list_from_bitmap(&revs)) + if (use_bitmap_index && !get_object_list_from_bitmap(revs)) return; if (use_delta_islands) @@ -3771,24 +4035,24 @@ static void get_object_list(int ac, const char **av) if (write_bitmap_index) mark_bitmap_preferred_tips(); - if (prepare_revision_walk(&revs)) + if (prepare_revision_walk(revs)) die(_("revision walk setup failed")); - mark_edges_uninteresting(&revs, show_edge, sparse); + mark_edges_uninteresting(revs, show_edge, sparse); if (!fn_show_object) fn_show_object = show_object; - traverse_commit_list_filtered(&filter_options, &revs, - show_commit, fn_show_object, NULL, - NULL); + traverse_commit_list(revs, + show_commit, fn_show_object, + NULL); if (unpack_unreachable_expiration) { - revs.ignore_missing_links = 1; - if (add_unseen_recent_objects_to_traversal(&revs, - unpack_unreachable_expiration)) + revs->ignore_missing_links = 1; + if (add_unseen_recent_objects_to_traversal(revs, + unpack_unreachable_expiration, NULL, 0)) die(_("unable to add recent objects")); - if (prepare_revision_walk(&revs)) + if (prepare_revision_walk(revs)) die(_("revision walk setup failed")); - traverse_commit_list(&revs, record_recent_commit, + traverse_commit_list(revs, record_recent_commit, record_recent_object, NULL); } @@ -3861,6 +4125,35 @@ static int option_parse_unpack_unreachable(const struct option *opt, return 0; } +static int option_parse_cruft_expiration(const struct option *opt, + const char *arg, int unset) +{ + if (unset) { + cruft = 0; + cruft_expiration = 0; + } else { + cruft = 1; + if (arg) + cruft_expiration = approxidate(arg); + } + return 0; +} + +struct po_filter_data { + unsigned have_revs:1; + struct rev_info revs; +}; + +static struct list_objects_filter_options *po_filter_revs_init(void *value) +{ + struct po_filter_data *data = value; + + repo_init_revisions(the_repository, &data->revs, NULL); + data->have_revs = 1; + + return &data->revs.filter; +} + int cmd_pack_objects(int argc, const char **argv, const char *prefix) { int use_internal_rev_list = 0; @@ -3871,6 +4164,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) int rev_list_index = 0; int stdin_packs = 0; struct string_list keep_pack_list = STRING_LIST_INIT_NODUP; + struct po_filter_data pfd = { .have_revs = 0 }; + struct option pack_objects_options[] = { OPT_SET_INT('q', "quiet", &progress, N_("do not show progress meter"), 0), @@ -3933,6 +4228,10 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) OPT_CALLBACK_F(0, "unpack-unreachable", NULL, N_("time"), N_("unpack unreachable objects newer than <time>"), PARSE_OPT_OPTARG, option_parse_unpack_unreachable), + OPT_BOOL(0, "cruft", &cruft, N_("create a cruft pack")), + OPT_CALLBACK_F(0, "cruft-expiration", NULL, N_("time"), + N_("expire cruft objects older than <time>"), + PARSE_OPT_OPTARG, option_parse_cruft_expiration), OPT_BOOL(0, "sparse", &sparse, N_("use the sparse reachability algorithm")), OPT_BOOL(0, "thin", &thin, @@ -3956,7 +4255,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) &write_bitmap_index, N_("write a bitmap index if possible"), WRITE_BITMAP_QUIET, PARSE_OPT_HIDDEN), - OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), + OPT_PARSE_LIST_OBJECTS_FILTER_INIT(&pfd, po_filter_revs_init), OPT_CALLBACK_F(0, "missing", NULL, N_("action"), N_("handling for missing objects"), PARSE_OPT_NONEG, option_parse_missing_action), @@ -3976,9 +4275,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", -1); - prepare_repo_settings(the_repository); - if (sparse < 0) - sparse = the_repository->settings.pack_use_sparse; + if (the_repository->gitdir) { + 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); @@ -4057,7 +4358,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (!HAVE_THREADS && delta_search_threads != 1) warning(_("no threads support, ignoring --threads")); - if (!pack_to_stdout && !pack_size_limit) + if (!pack_to_stdout && !pack_size_limit && !cruft) pack_size_limit = pack_size_limit_cfg; if (pack_to_stdout && pack_size_limit) die(_("--max-pack-size cannot be used to build a pack for transfer")); @@ -4070,11 +4371,11 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) die(_("--thin cannot be used to build an indexable pack")); if (keep_unreachable && unpack_unreachable) - die(_("--keep-unreachable and --unpack-unreachable are incompatible")); + die(_("options '%s' and '%s' cannot be used together"), "--keep-unreachable", "--unpack-unreachable"); if (!rev_list_all || !rev_list_reflog || !rev_list_index) unpack_unreachable_expiration = 0; - if (filter_options.choice) { + if (pfd.have_revs && pfd.revs.filter.choice) { if (!pack_to_stdout) die(_("cannot use --filter without --stdout")); if (stdin_packs) @@ -4084,6 +4385,15 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (stdin_packs && use_internal_rev_list) die(_("cannot use internal rev list with --stdin-packs")); + if (cruft) { + if (use_internal_rev_list) + die(_("cannot use internal rev list with --cruft")); + if (stdin_packs) + die(_("cannot use --stdin-packs with --cruft")); + if (pack_size_limit) + die(_("cannot use --max-pack-size with --cruft")); + } + /* * "soft" reasons not to use bitmaps - for on-disk repack by default we want * @@ -4140,7 +4450,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) the_repository); prepare_packing_data(the_repository, &to_pack); - if (progress) + if (progress && !cruft) progress_state = start_progress(_("Enumerating objects"), 0); if (stdin_packs) { /* avoids adding objects in excluded packs */ @@ -4148,11 +4458,19 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) read_packs_list_from_stdin(); if (rev_list_unpacked) add_unreachable_loose_objects(); - } else if (!use_internal_rev_list) + } else if (cruft) { + read_cruft_objects(); + } else if (!use_internal_rev_list) { read_object_list_from_stdin(); - else { - get_object_list(rp.nr, rp.v); - strvec_clear(&rp); + } else if (pfd.have_revs) { + get_object_list(&pfd.revs, rp.nr, rp.v); + release_revisions(&pfd.revs); + } else { + struct rev_info revs; + + repo_init_revisions(the_repository, &revs, NULL); + get_object_list(&revs, rp.nr, rp.v); + release_revisions(&revs); } cleanup_preferred_base(); if (include_tag && nr_result) @@ -4162,7 +4480,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) the_repository); if (non_empty && !nr_result) - return 0; + goto cleanup; if (nr_result) { trace2_region_enter("pack-objects", "prepare-pack", the_repository); @@ -4183,5 +4501,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) " pack-reused %"PRIu32), written, written_delta, reused, reused_delta, reuse_packfile_objects); + +cleanup: + strvec_clear(&rp); + return 0; } diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index 8bf5c0acad..ed9b9013a5 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -101,7 +101,7 @@ static inline struct llist_item *llist_insert(struct llist *list, oidread(&new_item->oid, oid); new_item->next = NULL; - if (after != NULL) { + if (after) { new_item->next = after->next; after->next = new_item; if (after == list->back) @@ -157,7 +157,7 @@ redo_from_start: if (cmp > 0) /* not in list, since sorted */ return prev; if (!cmp) { /* found */ - if (prev == NULL) { + if (!prev) { if (hint != NULL && hint != list->front) { /* we don't know the previous element */ hint = NULL; @@ -219,7 +219,7 @@ static struct pack_list * pack_list_difference(const struct pack_list *A, struct pack_list *ret; const struct pack_list *pl; - if (A == NULL) + if (!A) return NULL; pl = B; @@ -317,7 +317,7 @@ static size_t get_pack_redundancy(struct pack_list *pl) struct pack_list *subset; size_t ret = 0; - if (pl == NULL) + if (!pl) return 0; while ((subset = pl->next)) { @@ -611,7 +611,7 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix) while (*(argv + i) != NULL) add_pack_file(*(argv + i++)); - if (local_packs == NULL) + if (!local_packs) die("Zero packs found!"); load_all_objects(); diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 822ffff51f..881fcf3273 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -32,8 +32,12 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) n = strspn(q, digits); if (q[n] == ',') { q += n + 1; + *p_before = atoi(q); n = strspn(q, digits); + } else { + *p_before = 1; } + if (n == 0 || q[n] != ' ' || q[n+1] != '+') return 0; @@ -41,13 +45,14 @@ static int scan_hunk_header(const char *p, int *p_before, int *p_after) n = strspn(r, digits); if (r[n] == ',') { r += n + 1; + *p_after = atoi(r); n = strspn(r, digits); + } else { + *p_after = 1; } if (n == 0) return 0; - *p_before = atoi(q); - *p_after = atoi(r); return 1; } diff --git a/builtin/prune-packed.c b/builtin/prune-packed.c index b7b9281a8c..da3273a268 100644 --- a/builtin/prune-packed.c +++ b/builtin/prune-packed.c @@ -3,7 +3,7 @@ #include "prune-packed.h" static const char * const prune_packed_usage[] = { - N_("git prune-packed [-n | --dry-run] [-q | --quiet]"), + "git prune-packed [-n | --dry-run] [-q | --quiet]", NULL }; diff --git a/builtin/prune.c b/builtin/prune.c index 485c9a3c56..df376b2ed1 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -26,10 +26,22 @@ static int prune_tmp_file(const char *fullpath) return error("Could not stat '%s'", fullpath); if (st.st_mtime > expire) return 0; - if (show_only || verbose) - printf("Removing stale temporary file %s\n", fullpath); - if (!show_only) - unlink_or_warn(fullpath); + if (S_ISDIR(st.st_mode)) { + if (show_only || verbose) + printf("Removing stale temporary directory %s\n", fullpath); + if (!show_only) { + struct strbuf remove_dir_buf = STRBUF_INIT; + + strbuf_addstr(&remove_dir_buf, fullpath); + remove_dir_recursively(&remove_dir_buf, 0); + strbuf_release(&remove_dir_buf); + } + } else { + if (show_only || verbose) + printf("Removing stale temporary file %s\n", fullpath); + if (!show_only) + unlink_or_warn(fullpath); + } return 0; } @@ -184,5 +196,6 @@ int cmd_prune(int argc, const char **argv, const char *prefix) prune_shallow(show_only ? PRUNE_SHOW_ONLY : 0); } + release_revisions(&revs); return 0; } diff --git a/builtin/pull.c b/builtin/pull.c index 127798ba84..01155ba67b 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -42,9 +42,9 @@ static enum rebase_type parse_config_rebase(const char *key, const char *value, return v; if (fatal) - die(_("Invalid value for %s: %s"), key, value); + die(_("invalid value for '%s': '%s'"), key, value); else - error(_("Invalid value for %s: %s"), key, value); + error(_("invalid value for '%s': '%s'"), key, value); return REBASE_INVALID; } @@ -72,6 +72,7 @@ static const char * const pull_usage[] = { static int opt_verbosity; static char *opt_progress; static int recurse_submodules = RECURSE_SUBMODULES_DEFAULT; +static int recurse_submodules_cli = RECURSE_SUBMODULES_DEFAULT; /* Options passed to git-merge or git-rebase */ static enum rebase_type opt_rebase = -1; @@ -120,7 +121,7 @@ static struct option pull_options[] = { N_("force progress reporting"), PARSE_OPT_NOARG), OPT_CALLBACK_F(0, "recurse-submodules", - &recurse_submodules, N_("on-demand"), + &recurse_submodules_cli, N_("on-demand"), N_("control for recursive fetching of submodules"), PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules), @@ -318,7 +319,7 @@ static const char *config_get_ff(void) if (!strcmp(value, "only")) return "--ff-only"; - die(_("Invalid value for pull.ff: %s"), value); + die(_("invalid value for '%s': '%s'"), "pull.ff", value); } /** @@ -536,8 +537,8 @@ static int run_fetch(const char *repo, const char **refspecs) strvec_push(&args, opt_tags); if (opt_prune) strvec_push(&args, opt_prune); - if (recurse_submodules != RECURSE_SUBMODULES_DEFAULT) - switch (recurse_submodules) { + if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT) + switch (recurse_submodules_cli) { case RECURSE_SUBMODULES_ON: strvec_push(&args, "--recurse-submodules=on"); break; @@ -970,7 +971,7 @@ static void show_advice_pull_non_ff(void) "You can do so by running one of the following commands sometime before\n" "your next pull:\n" "\n" - " git config pull.rebase false # merge (the default strategy)\n" + " git config pull.rebase false # merge\n" " git config pull.rebase true # rebase\n" " git config pull.ff only # fast-forward only\n" "\n" @@ -988,14 +989,22 @@ int cmd_pull(int argc, const char **argv, const char *prefix) struct object_id rebase_fork_point; int rebase_unspecified = 0; int can_ff; + int divergent; if (!getenv("GIT_REFLOG_ACTION")) set_reflog_message(argc, argv); git_config(git_pull_config, NULL); + if (the_repository->gitdir) { + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + } argc = parse_options(argc, argv, prefix, pull_options, pull_usage, 0); + if (recurse_submodules_cli != RECURSE_SUBMODULES_DEFAULT) + recurse_submodules = recurse_submodules_cli; + if (cleanup_arg) /* * this only checks the validity of cleanup_arg; we don't need @@ -1035,14 +1044,13 @@ int cmd_pull(int argc, const char **argv, const char *prefix) oidclr(&orig_head); if (opt_rebase) { - int autostash = config_autostash; - if (opt_autostash != -1) - autostash = opt_autostash; + if (opt_autostash == -1) + opt_autostash = config_autostash; if (is_null_oid(&orig_head) && !is_cache_unborn()) die(_("Updating an unborn branch with changes added to the index.")); - if (!autostash) + if (!opt_autostash) require_clean_work_tree(the_repository, N_("pull with rebase"), _("please commit or stash them."), 1, 0); @@ -1102,15 +1110,16 @@ int cmd_pull(int argc, const char **argv, const char *prefix) } can_ff = get_can_ff(&orig_head, &merge_heads); + divergent = !can_ff && !already_up_to_date(&orig_head, &merge_heads); /* ff-only takes precedence over rebase */ if (opt_ff && !strcmp(opt_ff, "--ff-only")) { - if (!can_ff && !already_up_to_date(&orig_head, &merge_heads)) + if (divergent) die_ff_impossible(); opt_rebase = REBASE_FALSE; } /* If no action specified and we can't fast forward, then warn. */ - if (!opt_ff && rebase_unspecified && !can_ff) { + if (!opt_ff && rebase_unspecified && divergent) { show_advice_pull_non_ff(); die(_("Need to specify how to reconcile divergent branches.")); } diff --git a/builtin/push.c b/builtin/push.c index 4b026ce6c6..86b44f8aa7 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -2,6 +2,7 @@ * "git push" */ #include "cache.h" +#include "branch.h" #include "config.h" #include "refs.h" #include "refspec.h" @@ -151,7 +152,8 @@ static NORETURN void die_push_simple(struct branch *branch, * upstream to a non-branch, we should probably be showing * them the big ugly fully qualified ref. */ - const char *advice_maybe = ""; + const char *advice_pushdefault_maybe = ""; + const char *advice_automergesimple_maybe = ""; const char *short_upstream = branch->merge[0]->src; skip_prefix(short_upstream, "refs/heads/", &short_upstream); @@ -161,9 +163,16 @@ static NORETURN void die_push_simple(struct branch *branch, * push.default. */ if (push_default == PUSH_DEFAULT_UNSPECIFIED) - advice_maybe = _("\n" + advice_pushdefault_maybe = _("\n" "To choose either option permanently, " - "see push.default in 'git help config'."); + "see push.default in 'git help config'.\n"); + if (git_branch_track != BRANCH_TRACK_SIMPLE) + advice_automergesimple_maybe = _("\n" + "To avoid automatically configuring " + "upstream branches when their name\n" + "doesn't match the local branch, see option " + "'simple' of branch.autosetupmerge\n" + "in 'git help config'.\n"); die(_("The upstream branch of your current branch does not match\n" "the name of your current branch. To push to the upstream branch\n" "on the remote, use\n" @@ -173,9 +182,10 @@ static NORETURN void die_push_simple(struct branch *branch, "To push to the branch of the same name on the remote, use\n" "\n" " git push %s HEAD\n" - "%s"), + "%s%s"), remote->name, short_upstream, - remote->name, advice_maybe); + remote->name, advice_pushdefault_maybe, + advice_automergesimple_maybe); } static const char message_detached_head_die[] = @@ -185,16 +195,32 @@ static const char message_detached_head_die[] = "\n" " git push %s HEAD:<name-of-remote-branch>\n"); -static const char *get_upstream_ref(struct branch *branch, const char *remote_name) +static const char *get_upstream_ref(int flags, struct branch *branch, const char *remote_name) { - if (!branch->merge_nr || !branch->merge || !branch->remote_name) + if (branch->merge_nr == 0 && (flags & TRANSPORT_PUSH_AUTO_UPSTREAM)) { + /* if missing, assume same; set_upstream will be defined later */ + return branch->refname; + } + + if (!branch->merge_nr || !branch->merge || !branch->remote_name) { + const char *advice_autosetup_maybe = ""; + if (!(flags & TRANSPORT_PUSH_AUTO_UPSTREAM)) { + advice_autosetup_maybe = _("\n" + "To have this happen automatically for " + "branches without a tracking\n" + "upstream, see 'push.autoSetupRemote' " + "in 'git help config'.\n"); + } die(_("The current branch %s has no upstream branch.\n" "To push the current branch and set the remote as upstream, use\n" "\n" - " git push --set-upstream %s %s\n"), + " git push --set-upstream %s %s\n" + "%s"), branch->name, remote_name, - branch->name); + branch->name, + advice_autosetup_maybe); + } if (branch->merge_nr != 1) die(_("The current branch %s has multiple upstream branches, " "refusing to push."), branch->name); @@ -202,7 +228,7 @@ static const char *get_upstream_ref(struct branch *branch, const char *remote_na return branch->merge[0]->src; } -static void setup_default_push_refspecs(struct remote *remote) +static void setup_default_push_refspecs(int *flags, struct remote *remote) { struct branch *branch; const char *dst; @@ -234,7 +260,7 @@ static void setup_default_push_refspecs(struct remote *remote) case PUSH_DEFAULT_SIMPLE: if (!same_remote) break; - if (strcmp(branch->refname, get_upstream_ref(branch, remote->name))) + if (strcmp(branch->refname, get_upstream_ref(*flags, branch, remote->name))) die_push_simple(branch, remote); break; @@ -244,13 +270,21 @@ static void setup_default_push_refspecs(struct remote *remote) "your current branch '%s', without telling me what to push\n" "to update which remote branch."), remote->name, branch->name); - dst = get_upstream_ref(branch, remote->name); + dst = get_upstream_ref(*flags, branch, remote->name); break; case PUSH_DEFAULT_CURRENT: break; } + /* + * this is a default push - if auto-upstream is enabled and there is + * no upstream defined, then set it (with options 'simple', 'upstream', + * and 'current'). + */ + if ((*flags & TRANSPORT_PUSH_AUTO_UPSTREAM) && branch->merge_nr == 0) + *flags |= TRANSPORT_PUSH_SET_UPSTREAM; + refspec_appendf(&rs, "%s:%s", branch->refname, dst); } @@ -401,7 +435,7 @@ static int do_push(int flags, if (remote->push.nr) { push_refspec = &remote->push; } else if (!(flags & TRANSPORT_PUSH_MIRROR)) - setup_default_push_refspecs(remote); + setup_default_push_refspecs(&flags, remote); } errs = 0; url_nr = push_url_of_remote(remote, &url); @@ -472,6 +506,10 @@ static int git_push_config(const char *k, const char *v, void *cb) else *flags &= ~TRANSPORT_PUSH_FOLLOW_TAGS; return 0; + } else if (!strcmp(k, "push.autosetupremote")) { + if (git_config_bool(k, v)) + *flags |= TRANSPORT_PUSH_AUTO_UPSTREAM; + return 0; } else if (!strcmp(k, "push.gpgsign")) { const char *value; if (!git_config_get_value("push.gpgsign", &value)) { @@ -486,7 +524,7 @@ static int git_push_config(const char *k, const char *v, void *cb) if (value && !strcasecmp(value, "if-asked")) set_push_cert_flags(flags, SEND_PACK_PUSH_CERT_IF_ASKED); else - return error("Invalid value for '%s'", k); + return error(_("invalid value for '%s'"), k); } } } else if (!strcmp(k, "push.recursesubmodules")) { @@ -589,7 +627,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) set_push_cert_flags(&flags, push_cert); if (deleterefs && (tags || (flags & (TRANSPORT_PUSH_ALL | TRANSPORT_PUSH_MIRROR)))) - die(_("--delete is incompatible with --all, --mirror and --tags")); + die(_("options '%s' and '%s' cannot be used together"), "--delete", "--all/--mirror/--tags"); if (deleterefs && argc < 2) die(_("--delete doesn't make sense without any refs")); @@ -627,18 +665,18 @@ int cmd_push(int argc, const char **argv, const char *prefix) if (flags & TRANSPORT_PUSH_ALL) { if (tags) - die(_("--all and --tags are incompatible")); + die(_("options '%s' and '%s' cannot be used together"), "--all", "--tags"); if (argc >= 2) die(_("--all can't be combined with refspecs")); } if (flags & TRANSPORT_PUSH_MIRROR) { if (tags) - die(_("--mirror and --tags are incompatible")); + die(_("options '%s' and '%s' cannot be used together"), "--mirror", "--tags"); 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")); + die(_("options '%s' and '%s' cannot be used together"), "--all", "--mirror"); if (!is_empty_cas(&cas) && (flags & TRANSPORT_PUSH_FORCE_IF_INCLUDES)) cas.use_force_if_includes = 1; diff --git a/builtin/read-tree.c b/builtin/read-tree.c index 2109c4c9e5..9f1f33e954 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -160,15 +160,22 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) argc = parse_options(argc, argv, cmd_prefix, read_tree_options, read_tree_usage, 0); - hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); - prefix_set = opts.prefix ? 1 : 0; if (1 < opts.merge + opts.reset + prefix_set) die("Which one? -m, --reset, or --prefix?"); + /* Prefix should not start with a directory separator */ + if (opts.prefix && opts.prefix[0] == '/') + die("Invalid prefix, prefix cannot start with '/'"); + if (opts.reset) opts.reset = UNPACK_RESET_OVERWRITE_UNTRACKED; + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + + hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); + /* * NEEDSWORK * @@ -210,6 +217,9 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) if (opts.merge && !opts.index_only) setup_work_tree(); + if (opts.skip_sparse_checkout) + ensure_full_index(&the_index); + if (opts.merge) { switch (stage - 1) { case 0: diff --git a/builtin/rebase.c b/builtin/rebase.c index 34b4744e5f..7ab50cda2a 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -28,6 +28,7 @@ #include "sequencer.h" #include "rebase-interactive.h" #include "reset.h" +#include "hook.h" #define DEFAULT_REFLOG_ACTION "rebase" @@ -36,7 +37,7 @@ static char const * const builtin_rebase_usage[] = { "[--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"), + "git rebase --continue | --abort | --skip | --edit-todo", NULL }; @@ -570,7 +571,8 @@ static int finish_rebase(struct rebase_options *opts) static int move_to_original_branch(struct rebase_options *opts) { - struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT; + struct strbuf branch_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT; + struct reset_head_opts ropts = { 0 }; int ret; if (!opts->head_name) @@ -579,16 +581,17 @@ static int move_to_original_branch(struct rebase_options *opts) if (!opts->onto) BUG("move_to_original_branch without onto"); - strbuf_addf(&orig_head_reflog, "rebase finished: %s onto %s", + strbuf_addf(&branch_reflog, "rebase finished: %s onto %s", 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(the_repository, NULL, "", opts->head_name, - RESET_HEAD_REFS_ONLY, - orig_head_reflog.buf, head_reflog.buf, - DEFAULT_REFLOG_ACTION); + ropts.branch = opts->head_name; + ropts.flags = RESET_HEAD_REFS_ONLY; + ropts.branch_msg = branch_reflog.buf; + ropts.head_msg = head_reflog.buf; + ret = reset_head(the_repository, &ropts); - strbuf_release(&orig_head_reflog); + strbuf_release(&branch_reflog); strbuf_release(&head_reflog); return ret; } @@ -670,13 +673,15 @@ static int run_am(struct rebase_options *opts) status = run_command(&format_patch); if (status) { + struct reset_head_opts ropts = { 0 }; unlink(rebased_patches); free(rebased_patches); strvec_clear(&am.args); - reset_head(the_repository, &opts->orig_head, "checkout", - opts->head_name, 0, - "HEAD", NULL, DEFAULT_REFLOG_ACTION); + ropts.oid = &opts->orig_head; + ropts.branch = opts->head_name; + ropts.default_reflog_action = DEFAULT_REFLOG_ACTION; + reset_head(the_repository, &ropts); error(_("\ngit encountered an error while preparing the " "patches to replay\n" "these revisions:\n" @@ -812,6 +817,28 @@ static int rebase_config(const char *var, const char *value, void *data) return git_default_config(var, value, data); } +static int checkout_up_to_date(struct rebase_options *options) +{ + struct strbuf buf = STRBUF_INIT; + struct reset_head_opts ropts = { 0 }; + int ret = 0; + + strbuf_addf(&buf, "%s: checkout %s", + getenv(GIT_REFLOG_ACTION_ENVIRONMENT), + options->switch_to); + ropts.oid = &options->orig_head; + ropts.branch = options->head_name; + ropts.flags = RESET_HEAD_RUN_POST_CHECKOUT_HOOK; + if (!ropts.branch) + ropts.flags |= RESET_HEAD_DETACH; + ropts.head_msg = buf.buf; + if (reset_head(the_repository, &ropts) < 0) + ret = error(_("could not switch to %s"), options->switch_to); + strbuf_release(&buf); + + return ret; +} + /* * Determines whether the commits in from..to are linear, i.e. contain * no merge commits. This function *expects* `from` to be an ancestor of @@ -1017,6 +1044,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) int reschedule_failed_exec = -1; int allow_preemptive_ff = 1; int preserve_merges_selected = 0; + struct reset_head_opts ropts = { 0 }; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -1159,11 +1187,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } else { strbuf_reset(&buf); strbuf_addf(&buf, "%s/interactive", merge_dir()); - if(file_exists(buf.buf)) { - options.type = REBASE_MERGE; + options.type = REBASE_MERGE; + if (file_exists(buf.buf)) options.flags |= REBASE_INTERACTIVE_EXPLICIT; - } else - options.type = REBASE_MERGE; } options.state_dir = merge_dir(); } @@ -1190,13 +1216,13 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (keep_base) { if (options.onto_name) - die(_("cannot combine '--keep-base' with '--onto'")); + die(_("options '%s' and '%s' cannot be used together"), "--keep-base", "--onto"); if (options.root) - die(_("cannot combine '--keep-base' with '--root'")); + die(_("options '%s' and '%s' cannot be used together"), "--keep-base", "--root"); } if (options.root && options.fork_point > 0) - die(_("cannot combine '--root' with '--fork-point'")); + die(_("options '%s' and '%s' cannot be used together"), "--root", "--fork-point"); if (action != ACTION_NONE && !in_progress) die(_("No rebase in progress?")); @@ -1254,9 +1280,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(the_repository, NULL, "reset", NULL, RESET_HEAD_HARD, - NULL, NULL, DEFAULT_REFLOG_ACTION) < 0) + ropts.flags = RESET_HEAD_HARD; + if (reset_head(the_repository, &ropts) < 0) die(_("could not discard worktree changes")); remove_branch_state(the_repository, 0); if (read_basic_state(&options)) @@ -1273,9 +1298,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (read_basic_state(&options)) exit(1); - if (reset_head(the_repository, &options.orig_head, "reset", - options.head_name, RESET_HEAD_HARD, - NULL, NULL, DEFAULT_REFLOG_ACTION) < 0) + ropts.oid = &options.orig_head; + ropts.branch = options.head_name; + ropts.flags = RESET_HEAD_HARD; + ropts.default_reflog_action = DEFAULT_REFLOG_ACTION; + if (reset_head(the_repository, &ropts) < 0) die(_("could not move back to %s"), oid_to_hex(&options.orig_head)); remove_branch_state(the_repository, 0); @@ -1460,8 +1487,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (i >= 0) { if (is_merge(&options)) - die(_("cannot combine apply options with " - "merge options")); + die(_("apply options and merge options " + "cannot be used together")); else options.type = REBASE_APPLY; } @@ -1554,33 +1581,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.upstream_arg = "--root"; } - /* Make sure the branch to rebase onto is valid. */ - 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) { - 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 { - options.onto = - lookup_commit_reference_by_name(options.onto_name); - if (!options.onto) - die(_("Does not point to a valid commit '%s'"), - options.onto_name); - } - /* * If the branch to rebase is given, that is the branch we will rebase * branch_name -- branch/commit being rebased, or @@ -1630,6 +1630,34 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } else BUG("unexpected number of arguments left to parse"); + /* Make sure the branch to rebase onto is valid. */ + if (keep_base) { + strbuf_reset(&buf); + strbuf_addstr(&buf, options.upstream_name); + strbuf_addstr(&buf, "..."); + strbuf_addstr(&buf, branch_name); + 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) { + 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 { + options.onto = + lookup_commit_reference_by_name(options.onto_name); + if (!options.onto) + die(_("Does not point to a valid commit '%s'"), + options.onto_name); + } + if (options.fork_point > 0) { struct commit *head = lookup_commit_reference(the_repository, @@ -1641,10 +1669,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (repo_read_index(the_repository) < 0) die(_("could not read index")); - if (options.autostash) { - create_autostash(the_repository, state_dir_path("autostash", &options), - DEFAULT_REFLOG_ACTION); - } + if (options.autostash) + create_autostash(the_repository, + state_dir_path("autostash", &options)); + if (require_clean_work_tree(the_repository, "rebase", _("Please commit or stash them."), 1, 1)) { @@ -1673,21 +1701,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (!(options.flags & REBASE_FORCE)) { /* Lazily switch to the target branch if needed... */ if (options.switch_to) { - strbuf_reset(&buf); - strbuf_addf(&buf, "%s: checkout %s", - getenv(GIT_REFLOG_ACTION_ENVIRONMENT), - options.switch_to); - if (reset_head(the_repository, - &options.orig_head, "checkout", - options.head_name, - RESET_HEAD_RUN_POST_CHECKOUT_HOOK, - NULL, buf.buf, - DEFAULT_REFLOG_ACTION) < 0) { - ret = error(_("could not switch to " - "%s"), - options.switch_to); + ret = checkout_up_to_date(&options); + if (ret) goto cleanup; - } } if (!(options.flags & REBASE_NO_QUIET)) @@ -1712,7 +1728,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* If a hook exists, give it a chance to interrupt*/ if (!ok_to_skip_pre_rebase && - run_hook_le(NULL, "pre-rebase", options.upstream_arg, + run_hooks_l("pre-rebase", options.upstream_arg, argc ? argv[0] : NULL, NULL)) die(_("The pre-rebase hook refused to rebase.")); @@ -1754,10 +1770,13 @@ 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(the_repository, &options.onto->object.oid, "checkout", NULL, - RESET_HEAD_DETACH | RESET_ORIG_HEAD | - RESET_HEAD_RUN_POST_CHECKOUT_HOOK, - NULL, msg.buf, DEFAULT_REFLOG_ACTION)) + ropts.oid = &options.onto->object.oid; + ropts.orig_head = &options.orig_head, + ropts.flags = RESET_HEAD_DETACH | RESET_ORIG_HEAD | + RESET_HEAD_RUN_POST_CHECKOUT_HOOK; + ropts.head_msg = msg.buf; + ropts.default_reflog_action = DEFAULT_REFLOG_ACTION; + if (reset_head(the_repository, &ropts)) die(_("Could not detach HEAD")); strbuf_release(&msg); @@ -1772,9 +1791,11 @@ 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(the_repository, NULL, "Fast-forwarded", options.head_name, - RESET_HEAD_REFS_ONLY, "HEAD", msg.buf, - DEFAULT_REFLOG_ACTION); + memset(&ropts, 0, sizeof(ropts)); + ropts.branch = options.head_name; + ropts.flags = RESET_HEAD_REFS_ONLY; + ropts.head_msg = msg.buf; + reset_head(the_repository, &ropts); strbuf_release(&msg); ret = finish_rebase(&options); goto cleanup; diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 49b846d960..31b48e728b 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -175,7 +175,7 @@ static int receive_pack_config(const char *var, const char *value, void *cb) strbuf_addf(&fsck_msg_types, "%c%s=%s", fsck_msg_types.len ? ',' : '=', var, value); else - warning("Skipping unknown msg id '%s'", var); + warning("skipping unknown msg id '%s'", var); return 0; } @@ -581,32 +581,19 @@ static char *prepare_push_cert_nonce(const char *path, timestamp_t stamp) return strbuf_detach(&buf, NULL); } -/* - * NEEDSWORK: reuse find_commit_header() from jk/commit-author-parsing - * after dropping "_commit" from its name and possibly moving it out - * of commit.c - */ static char *find_header(const char *msg, size_t len, const char *key, const char **next_line) { - int key_len = strlen(key); - const char *line = msg; - - while (line && line < msg + len) { - const char *eol = strchrnul(line, '\n'); - - if ((msg + len <= eol) || line == eol) - return NULL; - if (line + key_len < eol && - !memcmp(line, key, key_len) && line[key_len] == ' ') { - int offset = key_len + 1; - if (next_line) - *next_line = *eol ? eol + 1 : eol; - return xmemdupz(line + offset, (eol - line) - offset); - } - line = *eol ? eol + 1 : NULL; - } - return NULL; + size_t out_len; + const char *val = find_header_mem(msg, len, key, &out_len); + + if (!val) + return NULL; + + if (next_line) + *next_line = val + out_len + 1; + + return xmemdupz(val, out_len); } /* @@ -762,36 +749,38 @@ static void prepare_push_cert_sha1(struct child_process *proc) int bogs /* beginning_of_gpg_sig */; already_done = 1; - if (write_object_file(push_cert.buf, push_cert.len, "blob", + if (write_object_file(push_cert.buf, push_cert.len, OBJ_BLOB, &push_cert_oid)) oidclr(&push_cert_oid); memset(&sigcheck, '\0', sizeof(sigcheck)); bogs = parse_signed_buffer(push_cert.buf, push_cert.len); - check_signature(push_cert.buf, bogs, push_cert.buf + bogs, - push_cert.len - bogs, &sigcheck); + sigcheck.payload = xmemdupz(push_cert.buf, bogs); + sigcheck.payload_len = bogs; + check_signature(&sigcheck, push_cert.buf + bogs, + push_cert.len - bogs); nonce_status = check_nonce(push_cert.buf, bogs); } if (!is_null_oid(&push_cert_oid)) { - strvec_pushf(&proc->env_array, "GIT_PUSH_CERT=%s", + strvec_pushf(&proc->env, "GIT_PUSH_CERT=%s", oid_to_hex(&push_cert_oid)); - strvec_pushf(&proc->env_array, "GIT_PUSH_CERT_SIGNER=%s", + strvec_pushf(&proc->env, "GIT_PUSH_CERT_SIGNER=%s", sigcheck.signer ? sigcheck.signer : ""); - strvec_pushf(&proc->env_array, "GIT_PUSH_CERT_KEY=%s", + strvec_pushf(&proc->env, "GIT_PUSH_CERT_KEY=%s", sigcheck.key ? sigcheck.key : ""); - strvec_pushf(&proc->env_array, "GIT_PUSH_CERT_STATUS=%c", + strvec_pushf(&proc->env, "GIT_PUSH_CERT_STATUS=%c", sigcheck.result); if (push_cert_nonce) { - strvec_pushf(&proc->env_array, + strvec_pushf(&proc->env, "GIT_PUSH_CERT_NONCE=%s", push_cert_nonce); - strvec_pushf(&proc->env_array, + strvec_pushf(&proc->env, "GIT_PUSH_CERT_NONCE_STATUS=%s", nonce_status); if (nonce_status == NONCE_SLOP) - strvec_pushf(&proc->env_array, + strvec_pushf(&proc->env, "GIT_PUSH_CERT_NONCE_SLOP=%ld", nonce_stamp_slop); } @@ -812,33 +801,31 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, { struct child_process proc = CHILD_PROCESS_INIT; struct async muxer; - const char *argv[2]; int code; + const char *hook_path = find_hook(hook_name); - argv[0] = find_hook(hook_name); - if (!argv[0]) + if (!hook_path) return 0; - argv[1] = NULL; - - proc.argv = argv; + strvec_push(&proc.args, hook_path); proc.in = -1; proc.stdout_to_stderr = 1; proc.trace2_hook_name = hook_name; if (feed_state->push_options) { - int i; + size_t i; for (i = 0; i < feed_state->push_options->nr; i++) - strvec_pushf(&proc.env_array, - "GIT_PUSH_OPTION_%d=%s", i, + strvec_pushf(&proc.env, + "GIT_PUSH_OPTION_%"PRIuMAX"=%s", + (uintmax_t)i, feed_state->push_options->items[i].string); - strvec_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d", - feed_state->push_options->nr); + strvec_pushf(&proc.env, "GIT_PUSH_OPTION_COUNT=%"PRIuMAX"", + (uintmax_t)feed_state->push_options->nr); } else - strvec_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT"); + strvec_pushf(&proc.env, "GIT_PUSH_OPTION_COUNT"); if (tmp_objdir) - strvec_pushv(&proc.env_array, tmp_objdir_env(tmp_objdir)); + strvec_pushv(&proc.env, tmp_objdir_env(tmp_objdir)); if (use_sideband) { memset(&muxer, 0, sizeof(muxer)); @@ -943,23 +930,21 @@ static int run_receive_hook(struct command *commands, static int run_update_hook(struct command *cmd) { - const char *argv[5]; struct child_process proc = CHILD_PROCESS_INIT; int code; + const char *hook_path = find_hook("update"); - argv[0] = find_hook("update"); - if (!argv[0]) + if (!hook_path) return 0; - argv[1] = cmd->ref_name; - argv[2] = oid_to_hex(&cmd->old_oid); - argv[3] = oid_to_hex(&cmd->new_oid); - argv[4] = NULL; + strvec_push(&proc.args, hook_path); + strvec_push(&proc.args, cmd->ref_name); + strvec_push(&proc.args, oid_to_hex(&cmd->old_oid)); + strvec_push(&proc.args, oid_to_hex(&cmd->new_oid)); proc.no_stdin = 1; proc.stdout_to_stderr = 1; proc.err = use_sideband ? -1 : 0; - proc.argv = argv; proc.trace2_hook_name = "update"; code = start_command(&proc); @@ -1117,22 +1102,20 @@ static int run_proc_receive_hook(struct command *commands, 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; + const char *hook_path = find_hook("proc-receive"); - argv[0] = find_hook("proc-receive"); - if (!argv[0]) { + if (!hook_path) { rp_error("cannot find hook 'proc-receive'"); return -1; } - argv[1] = NULL; - proc.argv = argv; + strvec_push(&proc.args, hook_path); proc.in = -1; proc.out = -1; proc.trace2_hook_name = "proc-receive"; @@ -1370,23 +1353,11 @@ static const char *push_to_deploy(unsigned char *sha1, struct strvec *env, const char *work_tree) { - const char *update_refresh[] = { - "update-index", "-q", "--ignore-submodules", "--refresh", NULL - }; - const char *diff_files[] = { - "diff-files", "--quiet", "--ignore-submodules", "--", NULL - }; - const char *diff_index[] = { - "diff-index", "--quiet", "--cached", "--ignore-submodules", - NULL, "--", NULL - }; - const char *read_tree[] = { - "read-tree", "-u", "-m", NULL, NULL - }; struct child_process child = CHILD_PROCESS_INIT; - child.argv = update_refresh; - child.env = env->v; + strvec_pushl(&child.args, "update-index", "-q", "--ignore-submodules", + "--refresh", NULL); + strvec_pushv(&child.env, env->v); child.dir = work_tree; child.no_stdin = 1; child.stdout_to_stderr = 1; @@ -1396,8 +1367,9 @@ 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->v; + strvec_pushl(&child.args, "diff-files", "--quiet", + "--ignore-submodules", "--", NULL); + strvec_pushv(&child.env, env->v); child.dir = work_tree; child.no_stdin = 1; child.stdout_to_stderr = 1; @@ -1405,12 +1377,13 @@ static const char *push_to_deploy(unsigned char *sha1, if (run_command(&child)) return "Working directory has unstaged changes"; - /* diff-index with either HEAD or an empty tree */ - diff_index[4] = head_has_history() ? "HEAD" : empty_tree_oid_hex(); - child_process_init(&child); - child.argv = diff_index; - child.env = env->v; + strvec_pushl(&child.args, "diff-index", "--quiet", "--cached", + "--ignore-submodules", + /* diff-index with either HEAD or an empty tree */ + head_has_history() ? "HEAD" : empty_tree_oid_hex(), + "--", NULL); + strvec_pushv(&child.env, env->v); child.no_stdin = 1; child.no_stdout = 1; child.stdout_to_stderr = 0; @@ -1418,10 +1391,10 @@ static const char *push_to_deploy(unsigned char *sha1, if (run_command(&child)) return "Working directory has staged changes"; - read_tree[3] = hash_to_hex(sha1); child_process_init(&child); - child.argv = read_tree; - child.env = env->v; + strvec_pushl(&child.args, "read-tree", "-u", "-m", hash_to_hex(sha1), + NULL); + strvec_pushv(&child.env, env->v); child.dir = work_tree; child.no_stdin = 1; child.no_stdout = 1; @@ -1436,12 +1409,17 @@ 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 *hash, + int *invoked_hook, struct strvec *env, const char *work_tree) { + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + opt.invoked_hook = invoked_hook; + 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)) + strvec_pushv(&opt.env, env->v); + strvec_push(&opt.args, hash_to_hex(hash)); + if (run_hooks_opt(push_to_checkout_hook, &opt)) return "push-to-checkout hook declined"; else return NULL; @@ -1449,29 +1427,22 @@ static const char *push_to_checkout(unsigned char *hash, static const char *update_worktree(unsigned char *sha1, const struct worktree *worktree) { - const char *retval, *work_tree, *git_dir = NULL; + const char *retval, *git_dir; struct strvec env = STRVEC_INIT; + int invoked_hook; - if (worktree && worktree->path) - work_tree = worktree->path; - else if (git_work_tree_cfg) - work_tree = git_work_tree_cfg; - else - work_tree = ".."; + if (!worktree || !worktree->path) + BUG("worktree->path must be non-NULL"); - if (is_bare_repository()) + if (worktree->is_bare) return "denyCurrentBranch = updateInstead needs a worktree"; - if (worktree) - git_dir = get_worktree_git_dir(worktree); - if (!git_dir) - git_dir = get_git_dir(); + git_dir = get_worktree_git_dir(worktree); strvec_pushf(&env, "GIT_DIR=%s", absolute_path(git_dir)); - if (!hook_exists(push_to_checkout_hook)) - retval = push_to_deploy(sha1, &env, work_tree); - else - retval = push_to_checkout(sha1, &env, work_tree); + retval = push_to_checkout(sha1, &invoked_hook, &env, worktree->path); + if (!invoked_hook) + retval = push_to_deploy(sha1, &env, worktree->path); strvec_clear(&env); return retval; @@ -1486,19 +1457,22 @@ 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); + struct worktree **worktrees = get_worktrees(); + const struct worktree *worktree = + find_shared_symref(worktrees, "HEAD", name); /* only refs/... are allowed */ if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) { rp_error("refusing to create funny ref '%s' remotely", name); - return "funny refname"; + ret = "funny refname"; + goto out; } strbuf_addf(&namespaced_name_buf, "%s%s", get_git_namespace(), name); free(namespaced_name); namespaced_name = strbuf_detach(&namespaced_name_buf, NULL); - if (worktree) { + if (worktree && !worktree->is_bare) { switch (deny_current_branch) { case DENY_IGNORE: break; @@ -1510,7 +1484,8 @@ static const char *update(struct command *cmd, struct shallow_info *si) rp_error("refusing to update checked out branch: %s", name); if (deny_current_branch == DENY_UNCONFIGURED) refuse_unconfigured_deny(); - return "branch is currently checked out"; + ret = "branch is currently checked out"; + goto out; case DENY_UPDATE_INSTEAD: /* pass -- let other checks intervene first */ do_update_worktree = 1; @@ -1521,13 +1496,15 @@ static const char *update(struct command *cmd, struct shallow_info *si) if (!is_null_oid(new_oid) && !has_object_file(new_oid)) { error("unpack should have generated %s, " "but I can't find it!", oid_to_hex(new_oid)); - return "bad pack"; + ret = "bad pack"; + goto out; } if (!is_null_oid(old_oid) && is_null_oid(new_oid)) { if (deny_deletes && starts_with(name, "refs/heads/")) { rp_error("denying ref deletion for %s", name); - return "deletion prohibited"; + ret = "deletion prohibited"; + goto out; } if (worktree || (head_name && !strcmp(namespaced_name, head_name))) { @@ -1543,9 +1520,11 @@ static const char *update(struct command *cmd, struct shallow_info *si) if (deny_delete_current == DENY_UNCONFIGURED) refuse_unconfigured_deny_delete_current(); rp_error("refusing to delete the current branch: %s", name); - return "deletion of the current branch prohibited"; + ret = "deletion of the current branch prohibited"; + goto out; default: - return "Invalid denyDeleteCurrent setting"; + ret = "Invalid denyDeleteCurrent setting"; + goto out; } } } @@ -1563,25 +1542,28 @@ static const char *update(struct command *cmd, struct shallow_info *si) old_object->type != OBJ_COMMIT || new_object->type != OBJ_COMMIT) { error("bad sha1 objects for %s", name); - return "bad ref"; + ret = "bad ref"; + goto out; } old_commit = (struct commit *)old_object; new_commit = (struct commit *)new_object; if (!in_merge_bases(old_commit, new_commit)) { rp_error("denying non-fast-forward %s" " (you should pull first)", name); - return "non-fast-forward"; + ret = "non-fast-forward"; + goto out; } } if (run_update_hook(cmd)) { rp_error("hook declined to update %s", name); - return "hook declined"; + ret = "hook declined"; + goto out; } if (do_update_worktree) { - ret = update_worktree(new_oid->hash, find_shared_symref("HEAD", name)); + ret = update_worktree(new_oid->hash, worktree); if (ret) - return ret; + goto out; } if (is_null_oid(new_oid)) { @@ -1589,9 +1571,9 @@ static const char *update(struct command *cmd, struct shallow_info *si) if (!parse_object(the_repository, old_oid)) { old_oid = NULL; if (ref_exists(name)) { - rp_warning("Allowing deletion of corrupt ref."); + rp_warning("allowing deletion of corrupt ref"); } else { - rp_warning("Deleting a non-existent ref."); + rp_warning("deleting a non-existent ref"); cmd->did_not_exist = 1; } } @@ -1600,17 +1582,19 @@ static const char *update(struct command *cmd, struct shallow_info *si) old_oid, 0, "push", &err)) { rp_error("%s", err.buf); - strbuf_release(&err); - return "failed to delete"; + ret = "failed to delete"; + } else { + ret = NULL; /* good */ } strbuf_release(&err); - return NULL; /* good */ } else { struct strbuf err = STRBUF_INIT; if (shallow_update && si->shallow_ref[cmd->index] && - update_shallow_ref(cmd, si)) - return "shallow error"; + update_shallow_ref(cmd, si)) { + ret = "shallow error"; + goto out; + } if (ref_transaction_update(transaction, namespaced_name, @@ -1618,14 +1602,16 @@ static const char *update(struct command *cmd, struct shallow_info *si) 0, "push", &err)) { rp_error("%s", err.buf); - strbuf_release(&err); - - return "failed to update ref"; + ret = "failed to update ref"; + } else { + ret = NULL; /* good */ } strbuf_release(&err); - - return NULL; /* good */ } + +out: + free_worktrees(worktrees); + return ret; } static void run_update_post_hook(struct command *commands) @@ -1678,7 +1664,7 @@ static void check_aliased_update_internal(struct command *cmd, } dst_name = strip_namespace(dst_name); - if ((item = string_list_lookup(list, dst_name)) == NULL) + if (!(item = string_list_lookup(list, dst_name))) return; cmd->skip_update = 1; @@ -1824,21 +1810,17 @@ static int should_process_cmd(struct command *cmd) return !cmd->error_string && !cmd->skip_update; } -static void warn_if_skipped_connectivity_check(struct command *commands, +static void BUG_if_skipped_connectivity_check(struct command *commands, struct shallow_info *si) { struct command *cmd; - int checked_connectivity = 1; for (cmd = commands; cmd; cmd = cmd->next) { - if (should_process_cmd(cmd) && si->shallow_ref[cmd->index]) { - error("BUG: connectivity check has not been run on ref %s", - cmd->ref_name); - checked_connectivity = 0; - } + if (should_process_cmd(cmd) && si->shallow_ref[cmd->index]) + bug("connectivity check has not been run on ref %s", + cmd->ref_name); } - if (!checked_connectivity) - BUG("connectivity check skipped???"); + BUG_if_bug("connectivity check skipped???"); } static void execute_commands_non_atomic(struct command *commands, @@ -1979,6 +1961,15 @@ static void execute_commands(struct command *commands, } /* + * If there is no command ready to run, should return directly to destroy + * temporary data in the quarantine area. + */ + for (cmd = commands; cmd && cmd->error_string; cmd = cmd->next) + ; /* nothing */ + if (!cmd) + return; + + /* * Now we'll start writing out refs, which means the objects need * to be in their final positions so that other processes can see them. */ @@ -2010,7 +2001,7 @@ static void execute_commands(struct command *commands, execute_commands_non_atomic(commands, si); if (shallow_update) - warn_if_skipped_connectivity_check(commands, si); + BUG_if_skipped_connectivity_check(commands, si); } static struct command **queue_command(struct command **tail, @@ -2213,13 +2204,13 @@ static const char *unpack(int err_fd, struct shallow_info *si) strvec_push(&child.args, alt_shallow_file); } - tmp_objdir = tmp_objdir_create(); + tmp_objdir = tmp_objdir_create("incoming"); if (!tmp_objdir) { if (err_fd > 0) close(err_fd); return "unable to create temporary object directory"; } - child.env = tmp_objdir_env(tmp_objdir); + strvec_pushv(&child.env, tmp_objdir_env(tmp_objdir)); /* * Normally we just pass the tmp_objdir environment to the child @@ -2490,9 +2481,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, receive_pack_usage, 0); if (argc > 1) - usage_msg_opt(_("Too many arguments."), receive_pack_usage, options); + usage_msg_opt(_("too many arguments"), receive_pack_usage, options); if (argc == 0) - usage_msg_opt(_("You must specify a directory."), receive_pack_usage, options); + usage_msg_opt(_("you must specify a directory"), receive_pack_usage, options); service_dir = argv[0]; @@ -2542,7 +2533,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) PACKET_READ_CHOMP_NEWLINE | PACKET_READ_DIE_ON_ERR_PACKET); - if ((commands = read_head_info(&reader, &shallow)) != NULL) { + if ((commands = read_head_info(&reader, &shallow))) { const char *unpack_status = NULL; struct string_list push_options = STRING_LIST_INIT_DUP; @@ -2566,25 +2557,25 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) &push_options); if (pack_lockfile) unlink_or_warn(pack_lockfile); + sigchain_push(SIGPIPE, SIG_IGN); if (report_status_v2) report_v2(commands, unpack_status); else if (report_status) report(commands, unpack_status); + sigchain_pop(SIGPIPE); run_receive_hook(commands, "post-receive", 1, &push_options); run_update_post_hook(commands); string_list_clear(&push_options, 0); if (auto_gc) { - const char *argv_gc_auto[] = { - "gc", "--auto", "--quiet", NULL, - }; struct child_process proc = CHILD_PROCESS_INIT; proc.no_stdin = 1; proc.stdout_to_stderr = 1; proc.err = use_sideband ? -1 : 0; proc.git_cmd = proc.close_object_store = 1; - proc.argv = argv_gc_auto; + strvec_pushl(&proc.args, "gc", "--auto", "--quiet", + NULL); if (!start_command(&proc)) { if (use_sideband) diff --git a/builtin/reflog.c b/builtin/reflog.c index 175c83e7cc..4dd297dce8 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -1,429 +1,77 @@ #include "builtin.h" #include "config.h" -#include "lockfile.h" -#include "object-store.h" -#include "repository.h" -#include "commit.h" -#include "refs.h" -#include "dir.h" -#include "tree-walk.h" -#include "diff.h" #include "revision.h" #include "reachable.h" #include "worktree.h" +#include "reflog.h" -/* NEEDSWORK: switch to using parse_options */ -static const char reflog_expire_usage[] = -N_("git reflog expire [--expire=<time>] " - "[--expire-unreachable=<time>] " - "[--rewrite] [--updateref] [--stale-fix] [--dry-run | -n] " - "[--verbose] [--all] <refs>..."); -static const char reflog_delete_usage[] = -N_("git reflog delete [--rewrite] [--updateref] " - "[--dry-run | -n] [--verbose] <refs>..."); -static const char reflog_exists_usage[] = -N_("git reflog exists <ref>"); +#define BUILTIN_REFLOG_SHOW_USAGE \ + N_("git reflog [show] [<log-options>] [<ref>]") -static timestamp_t default_reflog_expire; -static timestamp_t default_reflog_expire_unreachable; +#define BUILTIN_REFLOG_EXPIRE_USAGE \ + N_("git reflog expire [--expire=<time>] [--expire-unreachable=<time>]\n" \ + " [--rewrite] [--updateref] [--stale-fix]\n" \ + " [--dry-run | -n] [--verbose] [--all [--single-worktree] | <refs>...]") -struct cmd_reflog_expire_cb { - struct rev_info revs; - int stalefix; - timestamp_t expire_total; - timestamp_t expire_unreachable; - int recno; -}; +#define BUILTIN_REFLOG_DELETE_USAGE \ + N_("git reflog delete [--rewrite] [--updateref]\n" \ + " [--dry-run | -n] [--verbose] <ref>@{<specifier>}...") -struct expire_reflog_policy_cb { - enum { - UE_NORMAL, - UE_ALWAYS, - UE_HEAD - } unreachable_expire_kind; - struct commit_list *mark_list; - unsigned long mark_limit; - struct cmd_reflog_expire_cb cmd; - struct commit *tip_commit; - struct commit_list *tips; -}; +#define BUILTIN_REFLOG_EXISTS_USAGE \ + N_("git reflog exists <ref>") -struct collected_reflog { - struct object_id oid; - char reflog[FLEX_ARRAY]; +static const char *const reflog_show_usage[] = { + BUILTIN_REFLOG_SHOW_USAGE, + NULL, }; -struct collect_reflog_cb { - struct collected_reflog **e; - int alloc; - int nr; - struct worktree *wt; +static const char *const reflog_expire_usage[] = { + BUILTIN_REFLOG_EXPIRE_USAGE, + NULL }; -/* Remember to update object flag allocation in object.h */ -#define INCOMPLETE (1u<<10) -#define STUDYING (1u<<11) -#define REACHABLE (1u<<12) - -static int tree_is_complete(const struct object_id *oid) -{ - struct tree_desc desc; - struct name_entry entry; - int complete; - struct tree *tree; - - tree = lookup_tree(the_repository, oid); - if (!tree) - return 0; - if (tree->object.flags & SEEN) - return 1; - if (tree->object.flags & INCOMPLETE) - return 0; - - if (!tree->buffer) { - enum object_type type; - unsigned long size; - void *data = read_object_file(oid, &type, &size); - if (!data) { - tree->object.flags |= INCOMPLETE; - return 0; - } - tree->buffer = data; - tree->size = size; - } - init_tree_desc(&desc, tree->buffer, tree->size); - complete = 1; - while (tree_entry(&desc, &entry)) { - if (!has_object_file(&entry.oid) || - (S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) { - tree->object.flags |= INCOMPLETE; - complete = 0; - } - } - free_tree_buffer(tree); - - if (complete) - tree->object.flags |= SEEN; - return complete; -} - -static int commit_is_complete(struct commit *commit) -{ - struct object_array study; - struct object_array found; - int is_incomplete = 0; - int i; - - /* early return */ - if (commit->object.flags & SEEN) - return 1; - if (commit->object.flags & INCOMPLETE) - return 0; - /* - * Find all commits that are reachable and are not marked as - * SEEN. Then make sure the trees and blobs contained are - * complete. After that, mark these commits also as SEEN. - * If some of the objects that are needed to complete this - * commit are missing, mark this commit as INCOMPLETE. - */ - memset(&study, 0, sizeof(study)); - memset(&found, 0, sizeof(found)); - add_object_array(&commit->object, NULL, &study); - add_object_array(&commit->object, NULL, &found); - commit->object.flags |= STUDYING; - while (study.nr) { - struct commit *c; - struct commit_list *parent; - - c = (struct commit *)object_array_pop(&study); - if (!c->object.parsed && !parse_object(the_repository, &c->object.oid)) - c->object.flags |= INCOMPLETE; - - if (c->object.flags & INCOMPLETE) { - is_incomplete = 1; - break; - } - else if (c->object.flags & SEEN) - continue; - for (parent = c->parents; parent; parent = parent->next) { - struct commit *p = parent->item; - if (p->object.flags & STUDYING) - continue; - p->object.flags |= STUDYING; - add_object_array(&p->object, NULL, &study); - add_object_array(&p->object, NULL, &found); - } - } - if (!is_incomplete) { - /* - * make sure all commits in "found" array have all the - * necessary objects. - */ - for (i = 0; i < found.nr; i++) { - struct commit *c = - (struct commit *)found.objects[i].item; - if (!tree_is_complete(get_commit_tree_oid(c))) { - is_incomplete = 1; - c->object.flags |= INCOMPLETE; - } - } - if (!is_incomplete) { - /* mark all found commits as complete, iow SEEN */ - for (i = 0; i < found.nr; i++) - found.objects[i].item->flags |= SEEN; - } - } - /* clear flags from the objects we traversed */ - for (i = 0; i < found.nr; i++) - found.objects[i].item->flags &= ~STUDYING; - if (is_incomplete) - commit->object.flags |= INCOMPLETE; - else { - /* - * If we come here, we have (1) traversed the ancestry chain - * from the "commit" until we reach SEEN commits (which are - * known to be complete), and (2) made sure that the commits - * encountered during the above traversal refer to trees that - * are complete. Which means that we know *all* the commits - * we have seen during this process are complete. - */ - for (i = 0; i < found.nr; i++) - found.objects[i].item->flags |= SEEN; - } - /* free object arrays */ - object_array_clear(&study); - object_array_clear(&found); - return !is_incomplete; -} - -static int keep_entry(struct commit **it, struct object_id *oid) -{ - struct commit *commit; - - if (is_null_oid(oid)) - return 1; - commit = lookup_commit_reference_gently(the_repository, oid, 1); - if (!commit) - return 0; - - /* - * Make sure everything in this commit exists. - * - * We have walked all the objects reachable from the refs - * and cache earlier. The commits reachable by this commit - * must meet SEEN commits -- and then we should mark them as - * SEEN as well. - */ - if (!commit_is_complete(commit)) - return 0; - *it = commit; - return 1; -} - -/* - * Starting from commits in the cb->mark_list, mark commits that are - * reachable from them. Stop the traversal at commits older than - * the expire_limit and queue them back, so that the caller can call - * us again to restart the traversal with longer expire_limit. - */ -static void mark_reachable(struct expire_reflog_policy_cb *cb) -{ - struct commit_list *pending; - timestamp_t expire_limit = cb->mark_limit; - struct commit_list *leftover = NULL; - - for (pending = cb->mark_list; pending; pending = pending->next) - pending->item->object.flags &= ~REACHABLE; - - pending = cb->mark_list; - while (pending) { - struct commit_list *parent; - struct commit *commit = pop_commit(&pending); - if (commit->object.flags & REACHABLE) - continue; - if (parse_commit(commit)) - continue; - commit->object.flags |= REACHABLE; - if (commit->date < expire_limit) { - commit_list_insert(commit, &leftover); - continue; - } - commit->object.flags |= REACHABLE; - parent = commit->parents; - while (parent) { - commit = parent->item; - parent = parent->next; - if (commit->object.flags & REACHABLE) - continue; - commit_list_insert(commit, &pending); - } - } - cb->mark_list = leftover; -} - -static int unreachable(struct expire_reflog_policy_cb *cb, struct commit *commit, struct object_id *oid) -{ - /* - * We may or may not have the commit yet - if not, look it - * up using the supplied sha1. - */ - if (!commit) { - if (is_null_oid(oid)) - return 0; - - commit = lookup_commit_reference_gently(the_repository, oid, - 1); - - /* Not a commit -- keep it */ - if (!commit) - return 0; - } - - /* Reachable from the current ref? Don't prune. */ - if (commit->object.flags & REACHABLE) - return 0; - - if (cb->mark_list && cb->mark_limit) { - cb->mark_limit = 0; /* dig down to the root */ - mark_reachable(cb); - } - - return !(commit->object.flags & REACHABLE); -} - -/* - * Return true iff the specified reflog entry should be expired. - */ -static int should_expire_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *email, timestamp_t timestamp, int tz, - const char *message, void *cb_data) -{ - struct expire_reflog_policy_cb *cb = cb_data; - struct commit *old_commit, *new_commit; - - if (timestamp < cb->cmd.expire_total) - return 1; - - old_commit = new_commit = NULL; - if (cb->cmd.stalefix && - (!keep_entry(&old_commit, ooid) || !keep_entry(&new_commit, noid))) - return 1; - - if (timestamp < cb->cmd.expire_unreachable) { - if (cb->unreachable_expire_kind == UE_ALWAYS) - return 1; - if (unreachable(cb, old_commit, ooid) || unreachable(cb, new_commit, noid)) - return 1; - } - - if (cb->cmd.recno && --(cb->cmd.recno) == 0) - return 1; - - return 0; -} - -static int push_tip_to_list(const char *refname, const struct object_id *oid, - int flags, void *cb_data) -{ - struct commit_list **list = cb_data; - struct commit *tip_commit; - if (flags & REF_ISSYMREF) - return 0; - tip_commit = lookup_commit_reference_gently(the_repository, oid, 1); - if (!tip_commit) - return 0; - commit_list_insert(tip_commit, list); - return 0; -} - -static int is_head(const char *refname) -{ - switch (ref_type(refname)) { - case REF_TYPE_OTHER_PSEUDOREF: - case REF_TYPE_MAIN_PSEUDOREF: - if (parse_worktree_ref(refname, NULL, NULL, &refname)) - BUG("not a worktree ref: %s", refname); - break; - default: - break; - } - return !strcmp(refname, "HEAD"); -} - -static void reflog_expiry_prepare(const char *refname, - const struct object_id *oid, - void *cb_data) -{ - struct expire_reflog_policy_cb *cb = cb_data; - - if (!cb->cmd.expire_unreachable || is_head(refname)) { - cb->tip_commit = NULL; - cb->unreachable_expire_kind = UE_HEAD; - } else { - cb->tip_commit = lookup_commit_reference_gently(the_repository, - oid, 1); - if (!cb->tip_commit) - cb->unreachable_expire_kind = UE_ALWAYS; - else - cb->unreachable_expire_kind = UE_NORMAL; - } +static const char *const reflog_delete_usage[] = { + BUILTIN_REFLOG_DELETE_USAGE, + NULL +}; - if (cb->cmd.expire_unreachable <= cb->cmd.expire_total) - cb->unreachable_expire_kind = UE_ALWAYS; +static const char *const reflog_exists_usage[] = { + BUILTIN_REFLOG_EXISTS_USAGE, + NULL, +}; - cb->mark_list = NULL; - cb->tips = NULL; - if (cb->unreachable_expire_kind != UE_ALWAYS) { - if (cb->unreachable_expire_kind == UE_HEAD) { - struct commit_list *elem; +static const char *const reflog_usage[] = { + BUILTIN_REFLOG_SHOW_USAGE, + BUILTIN_REFLOG_EXPIRE_USAGE, + BUILTIN_REFLOG_DELETE_USAGE, + BUILTIN_REFLOG_EXISTS_USAGE, + NULL +}; - for_each_ref(push_tip_to_list, &cb->tips); - for (elem = cb->tips; elem; elem = elem->next) - commit_list_insert(elem->item, &cb->mark_list); - } else { - commit_list_insert(cb->tip_commit, &cb->mark_list); - } - cb->mark_limit = cb->cmd.expire_total; - mark_reachable(cb); - } -} +static timestamp_t default_reflog_expire; +static timestamp_t default_reflog_expire_unreachable; -static void reflog_expiry_cleanup(void *cb_data) -{ - struct expire_reflog_policy_cb *cb = cb_data; - - if (cb->unreachable_expire_kind != UE_ALWAYS) { - if (cb->unreachable_expire_kind == UE_HEAD) { - struct commit_list *elem; - for (elem = cb->tips; elem; elem = elem->next) - clear_commit_marks(elem->item, REACHABLE); - free_commit_list(cb->tips); - } else { - clear_commit_marks(cb->tip_commit, REACHABLE); - } - } -} +struct worktree_reflogs { + struct worktree *worktree; + struct string_list reflogs; +}; static int collect_reflog(const char *ref, const struct object_id *oid, int unused, void *cb_data) { - struct collected_reflog *e; - struct collect_reflog_cb *cb = cb_data; + struct worktree_reflogs *cb = cb_data; + struct worktree *worktree = cb->worktree; struct strbuf newref = STRBUF_INIT; /* * Avoid collecting the same shared ref multiple times because * they are available via all worktrees. */ - if (!cb->wt->is_current && ref_type(ref) == REF_TYPE_NORMAL) + if (!worktree->is_current && ref_type(ref) == REF_TYPE_NORMAL) return 0; - strbuf_worktree_ref(cb->wt, &newref, ref); - FLEX_ALLOC_STR(e, reflog, newref.buf); - strbuf_release(&newref); + strbuf_worktree_ref(worktree, &newref, ref); + string_list_append_nodup(&cb->reflogs, strbuf_detach(&newref, NULL)); - oidcpy(&e->oid, oid); - ALLOC_GROW(cb->e, cb->nr + 1, cb->alloc); - cb->e[cb->nr++] = e; return 0; } @@ -504,18 +152,18 @@ static int reflog_expire_config(const char *var, const char *value, void *cb) return 0; } -static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, const char *ref) +static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, const char *ref) { struct reflog_expire_cfg *ent; - if (slot == (EXPIRE_TOTAL|EXPIRE_UNREACH)) + if (cb->explicit_expiry == (EXPIRE_TOTAL|EXPIRE_UNREACH)) return; /* both given explicitly -- nothing to tweak */ for (ent = reflog_expire_cfg; ent; ent = ent->next) { if (!wildmatch(ent->pattern, ref, 0)) { - if (!(slot & EXPIRE_TOTAL)) + if (!(cb->explicit_expiry & EXPIRE_TOTAL)) cb->expire_total = ent->expire_total; - if (!(slot & EXPIRE_UNREACH)) + if (!(cb->explicit_expiry & EXPIRE_UNREACH)) cb->expire_unreachable = ent->expire_unreachable; return; } @@ -525,27 +173,94 @@ static void set_reflog_expiry_param(struct cmd_reflog_expire_cb *cb, int slot, c * If unconfigured, make stash never expire */ if (!strcmp(ref, "refs/stash")) { - if (!(slot & EXPIRE_TOTAL)) + if (!(cb->explicit_expiry & EXPIRE_TOTAL)) cb->expire_total = 0; - if (!(slot & EXPIRE_UNREACH)) + if (!(cb->explicit_expiry & EXPIRE_UNREACH)) cb->expire_unreachable = 0; return; } /* Nothing matched -- use the default value */ - if (!(slot & EXPIRE_TOTAL)) + if (!(cb->explicit_expiry & EXPIRE_TOTAL)) cb->expire_total = default_reflog_expire; - if (!(slot & EXPIRE_UNREACH)) + if (!(cb->explicit_expiry & EXPIRE_UNREACH)) cb->expire_unreachable = default_reflog_expire_unreachable; } +static int expire_unreachable_callback(const struct option *opt, + const char *arg, + int unset) +{ + struct cmd_reflog_expire_cb *cmd = opt->value; + + if (parse_expiry_date(arg, &cmd->expire_unreachable)) + die(_("invalid timestamp '%s' given to '--%s'"), + arg, opt->long_name); + + cmd->explicit_expiry |= EXPIRE_UNREACH; + return 0; +} + +static int expire_total_callback(const struct option *opt, + const char *arg, + int unset) +{ + struct cmd_reflog_expire_cb *cmd = opt->value; + + if (parse_expiry_date(arg, &cmd->expire_total)) + die(_("invalid timestamp '%s' given to '--%s'"), + arg, opt->long_name); + + cmd->explicit_expiry |= EXPIRE_TOTAL; + return 0; +} + +static int cmd_reflog_show(int argc, const char **argv, const char *prefix) +{ + struct option options[] = { + OPT_END() + }; + + parse_options(argc, argv, prefix, options, reflog_show_usage, + PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0 | + PARSE_OPT_KEEP_UNKNOWN); + + return cmd_log_reflog(argc, argv, prefix); +} + static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) { - struct expire_reflog_policy_cb cb; + struct cmd_reflog_expire_cb cmd = { 0 }; timestamp_t now = time(NULL); int i, status, do_all, all_worktrees = 1; - int explicit_expiry = 0; unsigned int flags = 0; + int verbose = 0; + reflog_expiry_should_prune_fn *should_prune_fn = should_expire_reflog_ent; + const struct option options[] = { + OPT_BIT(0, "dry-run", &flags, N_("do not actually prune any entries"), + EXPIRE_REFLOGS_DRY_RUN), + OPT_BIT(0, "rewrite", &flags, + N_("rewrite the old SHA1 with the new SHA1 of the entry that now precedes it"), + EXPIRE_REFLOGS_REWRITE), + OPT_BIT(0, "updateref", &flags, + N_("update the reference to the value of the top reflog entry"), + EXPIRE_REFLOGS_UPDATE_REF), + OPT_BOOL(0, "verbose", &verbose, N_("print extra information on screen")), + OPT_CALLBACK_F(0, "expire", &cmd, N_("timestamp"), + N_("prune entries older than the specified time"), + PARSE_OPT_NONEG, + expire_total_callback), + OPT_CALLBACK_F(0, "expire-unreachable", &cmd, N_("timestamp"), + N_("prune entries older than <time> that are not reachable from the current tip of the branch"), + PARSE_OPT_NONEG, + expire_unreachable_callback), + OPT_BOOL(0, "stale-fix", &cmd.stalefix, + N_("prune any reflog entries that point to broken commits")), + OPT_BOOL(0, "all", &do_all, N_("process the reflogs of all references")), + OPT_BOOL(1, "single-worktree", &all_worktrees, + N_("limits processing to reflogs from the current worktree only")), + OPT_END() + }; default_reflog_expire_unreachable = now - 30 * 24 * 3600; default_reflog_expire = now - 90 * 24 * 3600; @@ -553,104 +268,81 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; do_all = status = 0; - memset(&cb, 0, sizeof(cb)); - cb.cmd.expire_total = default_reflog_expire; - cb.cmd.expire_unreachable = default_reflog_expire_unreachable; + cmd.explicit_expiry = 0; + cmd.expire_total = default_reflog_expire; + cmd.expire_unreachable = default_reflog_expire_unreachable; - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; + argc = parse_options(argc, argv, prefix, options, reflog_expire_usage, 0); - if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) - flags |= EXPIRE_REFLOGS_DRY_RUN; - 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 (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; - } - else if (!strcmp(arg, "--stale-fix")) - cb.cmd.stalefix = 1; - else if (!strcmp(arg, "--rewrite")) - flags |= EXPIRE_REFLOGS_REWRITE; - else if (!strcmp(arg, "--updateref")) - flags |= EXPIRE_REFLOGS_UPDATE_REF; - else if (!strcmp(arg, "--all")) - do_all = 1; - else if (!strcmp(arg, "--single-worktree")) - all_worktrees = 0; - else if (!strcmp(arg, "--verbose")) - flags |= EXPIRE_REFLOGS_VERBOSE; - else if (!strcmp(arg, "--")) { - i++; - break; - } - else if (arg[0] == '-') - usage(_(reflog_expire_usage)); - else - break; - } + if (verbose) + should_prune_fn = should_expire_reflog_ent_verbose; /* * We can trust the commits and objects reachable from refs * even in older repository. We cannot trust what's reachable * from reflog if the repository was pruned with older git. */ - if (cb.cmd.stalefix) { - repo_init_revisions(the_repository, &cb.cmd.revs, prefix); - cb.cmd.revs.do_not_die_on_missing_tree = 1; - cb.cmd.revs.ignore_missing = 1; - cb.cmd.revs.ignore_missing_links = 1; - if (flags & EXPIRE_REFLOGS_VERBOSE) + if (cmd.stalefix) { + struct rev_info revs; + + repo_init_revisions(the_repository, &revs, prefix); + revs.do_not_die_on_missing_tree = 1; + revs.ignore_missing = 1; + revs.ignore_missing_links = 1; + if (verbose) printf(_("Marking reachable objects...")); - mark_reachable_objects(&cb.cmd.revs, 0, 0, NULL); - if (flags & EXPIRE_REFLOGS_VERBOSE) + mark_reachable_objects(&revs, 0, 0, NULL); + release_revisions(&revs); + if (verbose) putchar('\n'); } if (do_all) { - struct collect_reflog_cb collected; + struct worktree_reflogs collected = { + .reflogs = STRING_LIST_INIT_DUP, + }; + struct string_list_item *item; struct worktree **worktrees, **p; - int i; - memset(&collected, 0, sizeof(collected)); worktrees = get_worktrees(); for (p = worktrees; *p; p++) { if (!all_worktrees && !(*p)->is_current) continue; - collected.wt = *p; + collected.worktree = *p; refs_for_each_reflog(get_worktree_ref_store(*p), collect_reflog, &collected); } free_worktrees(worktrees); - for (i = 0; i < collected.nr; i++) { - struct collected_reflog *e = collected.e[i]; - set_reflog_expiry_param(&cb.cmd, explicit_expiry, e->reflog); - status |= reflog_expire(e->reflog, flags, + for_each_string_list_item(item, &collected.reflogs) { + struct expire_reflog_policy_cb cb = { + .cmd = cmd, + .dry_run = !!(flags & EXPIRE_REFLOGS_DRY_RUN), + }; + + set_reflog_expiry_param(&cb.cmd, item->string); + status |= reflog_expire(item->string, flags, reflog_expiry_prepare, - should_expire_reflog_ent, + should_prune_fn, reflog_expiry_cleanup, &cb); - free(e); } - free(collected.e); + string_list_clear(&collected.reflogs, 0); } - for (; i < argc; i++) { + for (i = 0; i < argc; i++) { char *ref; + struct expire_reflog_policy_cb cb = { .cmd = cmd }; + if (!dwim_log(argv[i], strlen(argv[i]), NULL, &ref)) { status |= error(_("%s points nowhere!"), argv[i]); continue; } - set_reflog_expiry_param(&cb.cmd, explicit_expiry, ref); + set_reflog_expiry_param(&cb.cmd, ref); status |= reflog_expire(ref, flags, reflog_expiry_prepare, - should_expire_reflog_ent, + should_prune_fn, reflog_expiry_cleanup, &cb); free(ref); @@ -658,135 +350,94 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) return status; } -static int count_reflog_ent(struct object_id *ooid, struct object_id *noid, - const char *email, timestamp_t timestamp, int tz, - const char *message, void *cb_data) -{ - struct expire_reflog_policy_cb *cb = cb_data; - if (!cb->cmd.expire_total || timestamp < cb->cmd.expire_total) - cb->cmd.recno++; - return 0; -} - static int cmd_reflog_delete(int argc, const char **argv, const char *prefix) { - struct expire_reflog_policy_cb cb; int i, status = 0; unsigned int flags = 0; - - memset(&cb, 0, sizeof(cb)); - - 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 (!strcmp(arg, "--rewrite")) - flags |= EXPIRE_REFLOGS_REWRITE; - else if (!strcmp(arg, "--updateref")) - flags |= EXPIRE_REFLOGS_UPDATE_REF; - else if (!strcmp(arg, "--verbose")) - flags |= EXPIRE_REFLOGS_VERBOSE; - else if (!strcmp(arg, "--")) { - i++; - break; - } - else if (arg[0] == '-') - usage(_(reflog_delete_usage)); - else - break; - } - - if (argc - i < 1) + int verbose = 0; + + const struct option options[] = { + OPT_BIT(0, "dry-run", &flags, N_("do not actually prune any entries"), + EXPIRE_REFLOGS_DRY_RUN), + OPT_BIT(0, "rewrite", &flags, + N_("rewrite the old SHA1 with the new SHA1 of the entry that now precedes it"), + EXPIRE_REFLOGS_REWRITE), + OPT_BIT(0, "updateref", &flags, + N_("update the reference to the value of the top reflog entry"), + EXPIRE_REFLOGS_UPDATE_REF), + OPT_BOOL(0, "verbose", &verbose, N_("print extra information on screen")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, reflog_delete_usage, 0); + + if (argc < 1) return error(_("no reflog specified to delete")); - for ( ; i < argc; i++) { - const char *spec = strstr(argv[i], "@{"); - char *ep, *ref; - int recno; + for (i = 0; i < argc; i++) + status |= reflog_delete(argv[i], flags, verbose); - if (!spec) { - status |= error(_("not a reflog: %s"), argv[i]); - continue; - } - - if (!dwim_log(argv[i], spec - argv[i], NULL, &ref)) { - status |= error(_("no reflog for '%s'"), argv[i]); - continue; - } - - recno = strtoul(spec + 2, &ep, 10); - if (*ep == '}') { - cb.cmd.recno = -recno; - for_each_reflog_ent(ref, count_reflog_ent, &cb); - } else { - cb.cmd.expire_total = approxidate(spec + 2); - for_each_reflog_ent(ref, count_reflog_ent, &cb); - cb.cmd.expire_total = 0; - } - - status |= reflog_expire(ref, flags, - reflog_expiry_prepare, - should_expire_reflog_ent, - reflog_expiry_cleanup, - &cb); - free(ref); - } return status; } static int cmd_reflog_exists(int argc, const char **argv, const char *prefix) { - int i, start = 0; - - for (i = 1; i < argc; i++) { - const char *arg = argv[i]; - if (!strcmp(arg, "--")) { - i++; - break; - } - else if (arg[0] == '-') - usage(_(reflog_exists_usage)); - else - break; - } - - start = i; - - if (argc - start != 1) - usage(_(reflog_exists_usage)); - - if (check_refname_format(argv[start], REFNAME_ALLOW_ONELEVEL)) - die(_("invalid ref format: %s"), argv[start]); - return !reflog_exists(argv[start]); + struct option options[] = { + OPT_END() + }; + const char *refname; + + argc = parse_options(argc, argv, prefix, options, reflog_exists_usage, + 0); + if (!argc) + usage_with_options(reflog_exists_usage, options); + + refname = argv[0]; + if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) + die(_("invalid ref format: %s"), refname); + return !reflog_exists(refname); } /* * main "reflog" */ -static const char reflog_usage[] = -N_("git reflog [ show | expire | delete | exists ]"); - int cmd_reflog(int argc, const char **argv, const char *prefix) { - if (argc > 1 && !strcmp(argv[1], "-h")) - usage(_(reflog_usage)); + struct option options[] = { + OPT_END() + }; - /* With no command, we default to showing it. */ - if (argc < 2 || *argv[1] == '-') - return cmd_log_reflog(argc, argv, prefix); + argc = parse_options(argc, argv, prefix, options, reflog_usage, + PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_ARGV0 | + PARSE_OPT_KEEP_UNKNOWN | + PARSE_OPT_NO_INTERNAL_HELP); - if (!strcmp(argv[1], "show")) - return cmd_log_reflog(argc - 1, argv + 1, prefix); + /* + * With "git reflog" we default to showing it. !argc is + * impossible with PARSE_OPT_KEEP_ARGV0. + */ + if (argc == 1) + goto log_reflog; - if (!strcmp(argv[1], "expire")) - return cmd_reflog_expire(argc - 1, argv + 1, prefix); + if (!strcmp(argv[1], "-h")) + usage_with_options(reflog_usage, options); + else if (*argv[1] == '-') + goto log_reflog; - if (!strcmp(argv[1], "delete")) + if (!strcmp(argv[1], "show")) + return cmd_reflog_show(argc - 1, argv + 1, prefix); + else if (!strcmp(argv[1], "expire")) + return cmd_reflog_expire(argc - 1, argv + 1, prefix); + else if (!strcmp(argv[1], "delete")) return cmd_reflog_delete(argc - 1, argv + 1, prefix); - - if (!strcmp(argv[1], "exists")) + else if (!strcmp(argv[1], "exists")) return cmd_reflog_exists(argc - 1, argv + 1, prefix); + /* + * Fall-through for e.g. "git reflog -1", "git reflog master", + * as well as the plain "git reflog" above goto above. + */ +log_reflog: return cmd_log_reflog(argc, argv, prefix); } diff --git a/builtin/remote.c b/builtin/remote.c index 299c466116..d4b69fe778 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -12,11 +12,12 @@ #include "object-store.h" #include "strvec.h" #include "commit-reach.h" +#include "progress.h" static const char * const builtin_remote_usage[] = { - N_("git remote [-v | --verbose]"), + "git remote [-v | --verbose]", N_("git remote add [-t <branch>] [-m <master>] [-f] [--tags | --no-tags] [--mirror=<fetch|push>] <name> <url>"), - N_("git remote rename <old> <new>"), + N_("git remote rename [--[no-]progress] <old> <new>"), N_("git remote remove <name>"), N_("git remote set-head <name> (-a | --auto | -d | --delete | <branch>)"), N_("git remote [-v | --verbose] show [-n] <name>"), @@ -36,7 +37,7 @@ static const char * const builtin_remote_add_usage[] = { }; static const char * const builtin_remote_rename_usage[] = { - N_("git remote rename <old> <new>"), + N_("git remote rename [--[no-]progress] <old> <new>"), NULL }; @@ -571,6 +572,7 @@ struct rename_info { const char *old_name; const char *new_name; struct string_list *remote_branches; + uint32_t symrefs_nr; }; static int read_remote_branches(const char *refname, @@ -587,10 +589,12 @@ static int read_remote_branches(const char *refname, item = string_list_append(rename->remote_branches, refname); symref = resolve_ref_unsafe(refname, RESOLVE_REF_READING, NULL, &flag); - if (symref && (flag & REF_ISSYMREF)) + if (symref && (flag & REF_ISSYMREF)) { item->util = xstrdup(symref); - else + rename->symrefs_nr++; + } else { item->util = NULL; + } } strbuf_release(&buf); @@ -674,7 +678,9 @@ static void handle_push_default(const char* old_name, const char* new_name) static int mv(int argc, const char **argv) { + int show_progress = isatty(2); struct option options[] = { + OPT_BOOL(0, "progress", &show_progress, N_("force progress reporting")), OPT_END() }; struct remote *oldremote, *newremote; @@ -682,14 +688,19 @@ static int mv(int argc, const char **argv) old_remote_context = STRBUF_INIT; struct string_list remote_branches = STRING_LIST_INIT_DUP; struct rename_info rename; - int i, refspec_updated = 0; + int i, refs_renamed_nr = 0, refspec_updated = 0; + struct progress *progress = NULL; - if (argc != 3) + argc = parse_options(argc, argv, NULL, options, + builtin_remote_rename_usage, 0); + + if (argc != 2) usage_with_options(builtin_remote_rename_usage, options); - rename.old_name = argv[1]; - rename.new_name = argv[2]; + rename.old_name = argv[0]; + rename.new_name = argv[1]; rename.remote_branches = &remote_branches; + rename.symrefs_nr = 0; oldremote = remote_get(rename.old_name); if (!remote_is_configured(oldremote, 1)) { @@ -764,15 +775,26 @@ static int mv(int argc, const char **argv) * the new symrefs. */ for_each_ref(read_remote_branches, &rename); + if (show_progress) { + /* + * Count symrefs twice, since "renaming" them is done by + * deleting and recreating them in two separate passes. + */ + progress = start_progress(_("Renaming remote references"), + rename.remote_branches->nr + rename.symrefs_nr); + } for (i = 0; i < remote_branches.nr; i++) { struct string_list_item *item = remote_branches.items + i; - int flag = 0; + struct strbuf referent = STRBUF_INIT; - read_ref_full(item->string, RESOLVE_REF_READING, NULL, &flag); - if (!(flag & REF_ISSYMREF)) + if (refs_read_symbolic_ref(get_main_ref_store(the_repository), item->string, + &referent)) continue; if (delete_ref(NULL, item->string, NULL, REF_NO_DEREF)) die(_("deleting '%s' failed"), item->string); + + strbuf_release(&referent); + display_progress(progress, ++refs_renamed_nr); } for (i = 0; i < remote_branches.nr; i++) { struct string_list_item *item = remote_branches.items + i; @@ -788,6 +810,7 @@ static int mv(int argc, const char **argv) item->string, buf.buf); if (rename_ref(item->string, buf.buf, buf2.buf)) die(_("renaming '%s' failed"), item->string); + display_progress(progress, ++refs_renamed_nr); } for (i = 0; i < remote_branches.nr; i++) { struct string_list_item *item = remote_branches.items + i; @@ -807,7 +830,9 @@ static int mv(int argc, const char **argv) item->string, buf.buf); if (create_symref(buf.buf, buf2.buf, buf3.buf)) die(_("creating '%s' failed"), buf.buf); + display_progress(progress, ++refs_renamed_nr); } + stop_progress(&progress); string_list_clear(&remote_branches, 1); handle_push_default(rename.old_name, rename.new_name); @@ -1160,14 +1185,22 @@ static int show_push_info_item(struct string_list_item *item, void *cb_data) static int get_one_entry(struct remote *remote, void *priv) { struct string_list *list = priv; - struct strbuf url_buf = STRBUF_INIT; + struct strbuf remote_info_buf = STRBUF_INIT; const char **url; int i, url_nr; if (remote->url_nr > 0) { - strbuf_addf(&url_buf, "%s (fetch)", remote->url[0]); + struct strbuf promisor_config = STRBUF_INIT; + const char *partial_clone_filter = NULL; + + strbuf_addf(&promisor_config, "remote.%s.partialclonefilter", remote->name); + strbuf_addf(&remote_info_buf, "%s (fetch)", remote->url[0]); + if (!git_config_get_string_tmp(promisor_config.buf, &partial_clone_filter)) + strbuf_addf(&remote_info_buf, " [%s]", partial_clone_filter); + + strbuf_release(&promisor_config); string_list_append(list, remote->name)->util = - strbuf_detach(&url_buf, NULL); + strbuf_detach(&remote_info_buf, NULL); } else string_list_append(list, remote->name)->util = NULL; if (remote->pushurl_nr) { @@ -1179,9 +1212,9 @@ static int get_one_entry(struct remote *remote, void *priv) } for (i = 0; i < url_nr; i++) { - strbuf_addf(&url_buf, "%s (push)", url[i]); + strbuf_addf(&remote_info_buf, "%s (push)", url[i]); string_list_append(list, remote->name)->util = - strbuf_detach(&url_buf, NULL); + strbuf_detach(&remote_info_buf, NULL); } return 0; diff --git a/builtin/repack.c b/builtin/repack.c index 0b2d1e5d82..c957b2959f 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -18,11 +18,21 @@ #include "pack-bitmap.h" #include "refs.h" +#define ALL_INTO_ONE 1 +#define LOOSEN_UNREACHABLE 2 +#define PACK_CRUFT 4 + +#define DELETE_PACK 1 +#define CRUFT_PACK 2 + +static int pack_everything; static int delta_base_offset = 1; static int pack_kept_objects = -1; static int write_bitmaps = -1; static int use_delta_islands; +static int run_update_server_info = 1; static char *packdir, *packtmp_name, *packtmp; +static char *cruft_expiration; static const char *const git_repack_usage[] = { N_("git repack [<options>]"), @@ -34,9 +44,21 @@ static const char incremental_bitmap_conflict_error[] = N_( "--no-write-bitmap-index or disable the pack.writebitmaps configuration." ); +struct pack_objects_args { + const char *window; + const char *window_memory; + const char *depth; + const char *threads; + const char *max_pack_size; + int no_reuse_delta; + int no_reuse_object; + int quiet; + int local; +}; static int repack_config(const char *var, const char *value, void *cb) { + struct pack_objects_args *cruft_po_args = cb; if (!strcmp(var, "repack.usedeltabaseoffset")) { delta_base_offset = git_config_bool(var, value); return 0; @@ -54,6 +76,18 @@ static int repack_config(const char *var, const char *value, void *cb) use_delta_islands = git_config_bool(var, value); return 0; } + if (strcmp(var, "repack.updateserverinfo") == 0) { + run_update_server_info = git_config_bool(var, value); + return 0; + } + if (!strcmp(var, "repack.cruftwindow")) + return git_config_string(&cruft_po_args->window, var, value); + if (!strcmp(var, "repack.cruftwindowmemory")) + return git_config_string(&cruft_po_args->window_memory, var, value); + if (!strcmp(var, "repack.cruftdepth")) + return git_config_string(&cruft_po_args->depth, var, value); + if (!strcmp(var, "repack.cruftthreads")) + return git_config_string(&cruft_po_args->threads, var, value); return git_default_config(var, value, cb); } @@ -126,12 +160,19 @@ static void collect_pack_filenames(struct string_list *fname_nonkept_list, fname = xmemdupz(e->d_name, len); if ((extra_keep->nr > 0 && i < extra_keep->nr) || - (file_exists(mkpath("%s/%s.keep", packdir, fname)))) + (file_exists(mkpath("%s/%s.keep", packdir, fname)))) { string_list_append_nodup(fname_kept_list, fname); - else - string_list_append_nodup(fname_nonkept_list, fname); + } else { + struct string_list_item *item; + item = string_list_append_nodup(fname_nonkept_list, + fname); + if (file_exists(mkpath("%s/%s.mtimes", packdir, fname))) + item->util = (void*)(uintptr_t)CRUFT_PACK; + } } closedir(dir); + + string_list_sort(fname_kept_list); } static void remove_redundant_pack(const char *dir_name, const char *base_name) @@ -146,18 +187,6 @@ static void remove_redundant_pack(const char *dir_name, const char *base_name) strbuf_release(&buf); } -struct pack_objects_args { - const char *window; - const char *window_memory; - const char *depth; - const char *threads; - const char *max_pack_size; - int no_reuse_delta; - int no_reuse_object; - int quiet; - int local; -}; - static void prepare_pack_objects(struct child_process *cmd, const struct pack_objects_args *args) { @@ -212,6 +241,7 @@ static struct { } exts[] = { {".pack"}, {".rev", 1}, + {".mtimes", 1}, {".bitmap", 1}, {".promisor", 1}, {".idx"}, @@ -258,9 +288,11 @@ static void repack_promisor_objects(const struct pack_objects_args *args, for_each_packed_object(write_oid, &cmd, FOR_EACH_OBJECT_PROMISOR_ONLY); - if (cmd.in == -1) + if (cmd.in == -1) { /* No packed objects; cmd was never started */ + child_process_clear(&cmd); return; + } close(cmd.in); @@ -297,9 +329,6 @@ static void repack_promisor_objects(const struct pack_objects_args *args, die(_("could not finish pack-objects to repack promisor objects")); } -#define ALL_INTO_ONE 1 -#define LOOSEN_UNREACHABLE 2 - struct pack_geometry { struct packed_git **pack; uint32_t pack_nr, pack_alloc; @@ -325,16 +354,39 @@ static int geometry_cmp(const void *va, const void *vb) return 0; } -static void init_pack_geometry(struct pack_geometry **geometry_p) +static void init_pack_geometry(struct pack_geometry **geometry_p, + struct string_list *existing_kept_packs) { struct packed_git *p; struct pack_geometry *geometry; + struct strbuf buf = STRBUF_INIT; *geometry_p = xcalloc(1, sizeof(struct pack_geometry)); geometry = *geometry_p; for (p = get_all_packs(the_repository); p; p = p->next) { - if (!pack_kept_objects && p->pack_keep) + if (!pack_kept_objects) { + /* + * Any pack that has its pack_keep bit set will appear + * in existing_kept_packs below, but this saves us from + * doing a more expensive check. + */ + if (p->pack_keep) + continue; + + /* + * The pack may be kept via the --keep-pack option; + * check 'existing_kept_packs' to determine whether to + * ignore it. + */ + strbuf_reset(&buf); + strbuf_addstr(&buf, pack_basename(p)); + strbuf_strip_suffix(&buf, ".pack"); + + if (string_list_has_string(existing_kept_packs, buf.buf)) + continue; + } + if (p->is_cruft) continue; ALLOC_GROW(geometry->pack, @@ -346,6 +398,7 @@ static void init_pack_geometry(struct pack_geometry **geometry_p) } QSORT(geometry->pack, geometry->pack_nr, geometry_cmp); + strbuf_release(&buf); } static void split_pack_geometry(struct pack_geometry *geometry, int factor) @@ -541,9 +594,20 @@ static void midx_included_packs(struct string_list *include, string_list_insert(include, strbuf_detach(&buf, NULL)); } + + for_each_string_list_item(item, existing_nonkept_packs) { + if (!((uintptr_t)item->util & CRUFT_PACK)) { + /* + * no need to check DELETE_PACK, since we're not + * doing an ALL_INTO_ONE repack + */ + continue; + } + string_list_insert(include, xstrfmt("%s.idx", item->string)); + } } else { for_each_string_list_item(item, existing_nonkept_packs) { - if (item->util) + if ((uintptr_t)item->util & DELETE_PACK) continue; string_list_insert(include, xstrfmt("%s.idx", item->string)); } @@ -597,6 +661,67 @@ static int write_midx_included_packs(struct string_list *include, return finish_command(&cmd); } +static int write_cruft_pack(const struct pack_objects_args *args, + const char *pack_prefix, + struct string_list *names, + struct string_list *existing_packs, + struct string_list *existing_kept_packs) +{ + struct child_process cmd = CHILD_PROCESS_INIT; + struct strbuf line = STRBUF_INIT; + struct string_list_item *item; + FILE *in, *out; + int ret; + + prepare_pack_objects(&cmd, args); + + strvec_push(&cmd.args, "--cruft"); + if (cruft_expiration) + strvec_pushf(&cmd.args, "--cruft-expiration=%s", + cruft_expiration); + + strvec_push(&cmd.args, "--honor-pack-keep"); + strvec_push(&cmd.args, "--non-empty"); + strvec_push(&cmd.args, "--max-pack-size=0"); + + cmd.in = -1; + + ret = start_command(&cmd); + if (ret) + return ret; + + /* + * names has a confusing double use: it both provides the list + * of just-written new packs, and accepts the name of the cruft + * pack we are writing. + * + * By the time it is read here, it contains only the pack(s) + * that were just written, which is exactly the set of packs we + * want to consider kept. + */ + in = xfdopen(cmd.in, "w"); + for_each_string_list_item(item, names) + fprintf(in, "%s-%s.pack\n", pack_prefix, item->string); + for_each_string_list_item(item, existing_packs) + fprintf(in, "-%s.pack\n", item->string); + for_each_string_list_item(item, existing_kept_packs) + fprintf(in, "%s.pack\n", item->string); + fclose(in); + + out = xfdopen(cmd.out, "r"); + while (strbuf_getline_lf(&line, out) != EOF) { + if (line.len != the_hash_algo->hexsz) + die(_("repack: Expecting full hex object ID lines only " + "from pack-objects.")); + string_list_append(names, line.buf); + } + fclose(out); + + strbuf_release(&line); + + return finish_command(&cmd); +} + int cmd_repack(int argc, const char **argv, const char *prefix) { struct child_process cmd = CHILD_PROCESS_INIT; @@ -610,16 +735,15 @@ int cmd_repack(int argc, const char **argv, const char *prefix) struct tempfile *refs_snapshot = NULL; int i, ext, ret; FILE *out; - int show_progress = isatty(2); + int show_progress; /* variables to be filled by option parsing */ - int pack_everything = 0; int delete_redundant = 0; const char *unpack_unreachable = NULL; int keep_unreachable = 0; struct string_list keep_pack_list = STRING_LIST_INIT_NODUP; - int no_update_server_info = 0; struct pack_objects_args po_args = {NULL}; + struct pack_objects_args cruft_po_args = {NULL}; int geometric_factor = 0; int write_midx = 0; @@ -629,14 +753,19 @@ int cmd_repack(int argc, const char **argv, const char *prefix) OPT_BIT('A', NULL, &pack_everything, N_("same as -a, and turn unreachable objects loose"), LOOSEN_UNREACHABLE | ALL_INTO_ONE), + OPT_BIT(0, "cruft", &pack_everything, + N_("same as -a, pack unreachable cruft objects separately"), + PACK_CRUFT), + OPT_STRING(0, "cruft-expiration", &cruft_expiration, N_("approxidate"), + N_("with -C, expire objects older than this")), OPT_BOOL('d', NULL, &delete_redundant, N_("remove redundant packs, and run git-prune-packed")), OPT_BOOL('f', NULL, &po_args.no_reuse_delta, N_("pass --no-reuse-delta to git-pack-objects")), OPT_BOOL('F', NULL, &po_args.no_reuse_object, N_("pass --no-reuse-object to git-pack-objects")), - OPT_BOOL('n', NULL, &no_update_server_info, - N_("do not run git-update-server-info")), + OPT_NEGBIT('n', NULL, &run_update_server_info, + N_("do not run git-update-server-info"), 1), OPT__QUIET(&po_args.quiet, N_("be quiet")), OPT_BOOL('l', "local", &po_args.local, N_("pass --local to git-pack-objects")), @@ -669,7 +798,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) OPT_END() }; - git_config(repack_config, NULL); + git_config(repack_config, &cruft_po_args); argc = parse_options(argc, argv, prefix, builtin_repack_options, git_repack_usage, 0); @@ -679,7 +808,16 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (keep_unreachable && (unpack_unreachable || (pack_everything & LOOSEN_UNREACHABLE))) - die(_("--keep-unreachable and -A are incompatible")); + die(_("options '%s' and '%s' cannot be used together"), "--keep-unreachable", "-A"); + + if (pack_everything & PACK_CRUFT) { + pack_everything |= ALL_INTO_ONE; + + if (unpack_unreachable || (pack_everything & LOOSEN_UNREACHABLE)) + die(_("options '%s' and '%s' cannot be used together"), "--cruft", "-A"); + if (keep_unreachable) + die(_("options '%s' and '%s' cannot be used together"), "--cruft", "-k"); + } if (write_bitmaps < 0) { if (!write_midx && @@ -691,7 +829,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) write_bitmaps = 0; } if (pack_kept_objects < 0) - pack_kept_objects = write_bitmaps > 0; + pack_kept_objects = write_bitmaps > 0 && !write_midx; if (write_bitmaps && !(pack_everything & ALL_INTO_ONE) && !write_midx) die(_(incremental_bitmap_conflict_error)); @@ -708,21 +846,26 @@ int cmd_repack(int argc, const char **argv, const char *prefix) strbuf_release(&path); } + packdir = mkpathdup("%s/pack", get_object_directory()); + packtmp_name = xstrfmt(".tmp-%d-pack", (int)getpid()); + packtmp = mkpathdup("%s/%s", packdir, packtmp_name); + + collect_pack_filenames(&existing_nonkept_packs, &existing_kept_packs, + &keep_pack_list); + if (geometric_factor) { if (pack_everything) - die(_("--geometric is incompatible with -A, -a")); - init_pack_geometry(&geometry); + die(_("options '%s' and '%s' cannot be used together"), "--geometric", "-A/-a"); + init_pack_geometry(&geometry, &existing_kept_packs); split_pack_geometry(geometry, geometric_factor); } - packdir = mkpathdup("%s/pack", get_object_directory()); - packtmp_name = xstrfmt(".tmp-%d-pack", (int)getpid()); - packtmp = mkpathdup("%s/%s", packdir, packtmp_name); - sigchain_push_common(remove_pack_on_signal); prepare_pack_objects(&cmd, &po_args); + show_progress = !po_args.quiet && isatty(2); + strvec_push(&cmd.args, "--keep-true-parents"); if (!pack_kept_objects) strvec_push(&cmd.args, "--honor-pack-keep"); @@ -756,13 +899,11 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (use_delta_islands) strvec_push(&cmd.args, "--delta-islands"); - collect_pack_filenames(&existing_nonkept_packs, &existing_kept_packs, - &keep_pack_list); - if (pack_everything & ALL_INTO_ONE) { repack_promisor_objects(&po_args, &names); - if (existing_nonkept_packs.nr && delete_redundant) { + if (existing_nonkept_packs.nr && delete_redundant && + !(pack_everything & PACK_CRUFT)) { for_each_string_list_item(item, &names) { strvec_pushf(&cmd.args, "--keep-pack=%s-%s.pack", packtmp_name, item->string); @@ -824,6 +965,35 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (!names.nr && !po_args.quiet) printf_ln(_("Nothing new to pack.")); + if (pack_everything & PACK_CRUFT) { + const char *pack_prefix; + if (!skip_prefix(packtmp, packdir, &pack_prefix)) + die(_("pack prefix %s does not begin with objdir %s"), + packtmp, packdir); + if (*pack_prefix == '/') + pack_prefix++; + + if (!cruft_po_args.window) + cruft_po_args.window = po_args.window; + if (!cruft_po_args.window_memory) + cruft_po_args.window_memory = po_args.window_memory; + if (!cruft_po_args.depth) + cruft_po_args.depth = po_args.depth; + if (!cruft_po_args.threads) + cruft_po_args.threads = po_args.threads; + + cruft_po_args.local = po_args.local; + cruft_po_args.quiet = po_args.quiet; + + ret = write_cruft_pack(&cruft_po_args, pack_prefix, &names, + &existing_nonkept_packs, + &existing_kept_packs); + if (ret) + return ret; + } + + string_list_sort(&names); + for_each_string_list_item(item, &names) { item->util = (void *)(uintptr_t)populate_pack_exts(item->string); } @@ -842,7 +1012,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) fname_old = mkpathdup("%s-%s%s", packtmp, item->string, exts[ext].name); - if (((uintptr_t)item->util) & (1 << ext)) { + if (((uintptr_t)item->util) & ((uintptr_t)1 << ext)) { struct stat statbuffer; if (!stat(fname_old, &statbuffer)) { statbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); @@ -864,7 +1034,6 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (delete_redundant && pack_everything & ALL_INTO_ONE) { const int hexsz = the_hash_algo->hexsz; - string_list_sort(&names); for_each_string_list_item(item, &existing_nonkept_packs) { char *sha1; size_t len = strlen(item->string); @@ -877,7 +1046,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) * was given) and that we will actually delete this pack * (if `-d` was given). */ - item->util = (void*)(intptr_t)!string_list_has_string(&names, sha1); + if (!string_list_has_string(&names, sha1)) + item->util = (void*)(uintptr_t)((size_t)item->util | DELETE_PACK); } } @@ -901,7 +1071,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (delete_redundant) { int opts = 0; for_each_string_list_item(item, &existing_nonkept_packs) { - if (!item->util) + if (!((uintptr_t)item->util & DELETE_PACK)) continue; remove_redundant_pack(packdir, item->string); } @@ -924,7 +1094,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) } strbuf_release(&buf); } - if (!po_args.quiet && show_progress) + if (show_progress) opts |= PRUNE_PACKED_VERBOSE; prune_packed_objects(opts); @@ -935,7 +1105,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) prune_shallow(PRUNE_QUICK); } - if (!no_update_server_info) + if (run_update_server_info) update_server_info(0); remove_temporary_files(); diff --git a/builtin/replace.c b/builtin/replace.c index 946938d011..583702a098 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -22,7 +22,7 @@ static const char * const git_replace_usage[] = { N_("git replace [-f] <object> <replacement>"), N_("git replace [-f] --edit <object>"), N_("git replace [-f] --graft <commit> [<parent>...]"), - N_("git replace [-f] --convert-graft-file"), + "git replace [-f] --convert-graft-file", N_("git replace -d <object>..."), N_("git replace [--format=<format>] [-l [<pattern>]]"), NULL @@ -72,7 +72,7 @@ static int list_replace_refs(const char *pattern, const char *format) { struct show_data data; - if (pattern == NULL) + if (!pattern) pattern = "*"; data.pattern = pattern; @@ -258,11 +258,10 @@ static int import_object(struct object_id *oid, enum object_type type, return error_errno(_("unable to open %s for reading"), filename); if (!raw && type == OBJ_TREE) { - const char *argv[] = { "mktree", NULL }; struct child_process cmd = CHILD_PROCESS_INIT; struct strbuf result = STRBUF_INIT; - cmd.argv = argv; + strvec_push(&cmd.args, "mktree"); cmd.git_cmd = 1; cmd.in = fd; cmd.out = -1; @@ -410,7 +409,7 @@ static int check_one_mergetag(struct commit *commit, int i; hash_object_file(the_hash_algo, extra->value, extra->len, - type_name(OBJ_TAG), &tag_oid); + OBJ_TAG, &tag_oid); tag = lookup_tag(the_repository, &tag_oid); if (!tag) return error(_("bad mergetag in commit '%s'"), ref); @@ -475,7 +474,7 @@ static int create_graft(int argc, const char **argv, int force, int gentle) return -1; } - if (write_object_file(buf.buf, buf.len, commit_type, &new_oid)) { + if (write_object_file(buf.buf, buf.len, OBJ_COMMIT, &new_oid)) { strbuf_release(&buf); return error(_("could not write replacement commit for: '%s'"), old_ref); diff --git a/builtin/reset.c b/builtin/reset.c index 7393595349..344fff8f3a 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -25,6 +25,7 @@ #include "cache-tree.h" #include "submodule.h" #include "submodule-config.h" +#include "dir.h" #define REFRESH_INDEX_DELAY_WARNING_IN_MS (2 * 1000) @@ -136,21 +137,36 @@ static void update_index_from_diff(struct diff_queue_struct *q, int intent_to_add = *(int *)data; for (i = 0; i < q->nr; i++) { + int pos; struct diff_filespec *one = q->queue[i]->one; - int is_missing = !(one->mode && !is_null_oid(&one->oid)); + int is_in_reset_tree = one->mode && !is_null_oid(&one->oid); struct cache_entry *ce; - if (is_missing && !intent_to_add) { + if (!is_in_reset_tree && !intent_to_add) { remove_file_from_cache(one->path); continue; } ce = make_cache_entry(&the_index, one->mode, &one->oid, one->path, 0, 0); + + /* + * If the file 1) corresponds to an existing index entry with + * skip-worktree set, or 2) does not exist in the index but is + * outside the sparse checkout definition, add a skip-worktree bit + * to the new index entry. Note that a sparse index will be expanded + * if this entry is outside the sparse cone - this is necessary + * to properly construct the reset sparse directory. + */ + pos = cache_name_pos(one->path, strlen(one->path)); + if ((pos >= 0 && ce_skip_worktree(active_cache[pos])) || + (pos < 0 && !path_in_sparse_checkout(one->path, &the_index))) + ce->ce_flags |= CE_SKIP_WORKTREE; + if (!ce) die(_("make_cache_entry failed for path '%s'"), one->path); - if (is_missing) { + if (!is_in_reset_tree) { ce->ce_flags |= CE_INTENT_TO_ADD; set_object_name_for_intent_to_add_entry(ce); } @@ -158,6 +174,88 @@ static void update_index_from_diff(struct diff_queue_struct *q, } } +static int pathspec_needs_expanded_index(const struct pathspec *pathspec) +{ + unsigned int i, pos; + int res = 0; + char *skip_worktree_seen = NULL; + + /* + * When using a magic pathspec, assume for the sake of simplicity that + * the index needs to be expanded to match all matchable files. + */ + if (pathspec->magic) + return 1; + + for (i = 0; i < pathspec->nr; i++) { + struct pathspec_item item = pathspec->items[i]; + + /* + * If the pathspec item has a wildcard, the index should be expanded + * if the pathspec has the possibility of matching a subset of entries inside + * of a sparse directory (but not the entire directory). + * + * If the pathspec item is a literal path, the index only needs to be expanded + * if a) the pathspec isn't in the sparse checkout cone (to make sure we don't + * expand for in-cone files) and b) it doesn't match any sparse directories + * (since we can reset whole sparse directories without expanding them). + */ + if (item.nowildcard_len < item.len) { + /* + * Special case: if the pattern is a path inside the cone + * followed by only wildcards, the pattern cannot match + * partial sparse directories, so we know we don't need to + * expand the index. + * + * Examples: + * - in-cone/foo***: doesn't need expanded index + * - not-in-cone/bar*: may need expanded index + * - **.c: may need expanded index + */ + if (strspn(item.original + item.nowildcard_len, "*") == item.len - item.nowildcard_len && + path_in_cone_mode_sparse_checkout(item.original, &the_index)) + continue; + + for (pos = 0; pos < active_nr; pos++) { + struct cache_entry *ce = active_cache[pos]; + + if (!S_ISSPARSEDIR(ce->ce_mode)) + continue; + + /* + * If the pre-wildcard length is longer than the sparse + * directory name and the sparse directory is the first + * component of the pathspec, need to expand the index. + */ + if (item.nowildcard_len > ce_namelen(ce) && + !strncmp(item.original, ce->name, ce_namelen(ce))) { + res = 1; + break; + } + + /* + * If the pre-wildcard length is shorter than the sparse + * directory and the pathspec does not match the whole + * directory, need to expand the index. + */ + if (!strncmp(item.original, ce->name, item.nowildcard_len) && + wildmatch(item.original, ce->name, 0)) { + res = 1; + break; + } + } + } else if (!path_in_cone_mode_sparse_checkout(item.original, &the_index) && + !matches_skip_worktree(pathspec, i, &skip_worktree_seen)) + res = 1; + + if (res > 0) + break; + } + + free(skip_worktree_seen); + return res; +} + static int read_from_tree(const struct pathspec *pathspec, struct object_id *tree_oid, int intent_to_add) @@ -170,13 +268,18 @@ static int read_from_tree(const struct pathspec *pathspec, opt.format_callback = update_index_from_diff; opt.format_callback_data = &intent_to_add; opt.flags.override_submodule_config = 1; + opt.flags.recursive = 1; opt.repo = the_repository; + opt.change = diff_change; + opt.add_remove = diff_addremove; + + if (pathspec->nr && the_index.sparse_index && pathspec_needs_expanded_index(pathspec)) + ensure_full_index(&the_index); if (do_diff_cache(tree_oid, &opt)) return 1; diffcore_std(&opt); diff_flush(&opt); - clear_pathspec(&opt.pathspec); return 0; } @@ -249,9 +352,6 @@ static void parse_args(struct pathspec *pathspec, } *rev_ret = rev; - if (read_cache() < 0) - die(_("index file corrupt")); - parse_pathspec(pathspec, 0, PATHSPEC_PREFER_FULL | (patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0), @@ -292,6 +392,7 @@ 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 no_refresh = 0; int patch_mode = 0, pathspec_file_nul = 0, unborn; const char *rev, *pathspec_from_file = NULL; struct object_id oid; @@ -299,6 +400,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix) int intent_to_add = 0; const struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), + OPT_BOOL(0, "no-refresh", &no_refresh, + N_("skip refreshing the index after reset")), OPT_SET_INT(0, "mixed", &reset_type, N_("reset HEAD and index"), MIXED), OPT_SET_INT(0, "soft", &reset_type, N_("reset only HEAD"), SOFT), @@ -320,7 +423,6 @@ int cmd_reset(int argc, const char **argv, const char *prefix) }; git_config(git_reset_config, NULL); - git_config_get_bool("reset.quiet", &quiet); argc = parse_options(argc, argv, prefix, options, git_reset_usage, PARSE_OPT_KEEP_DASHDASH); @@ -328,16 +430,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (pathspec_from_file) { if (patch_mode) - die(_("--pathspec-from-file is incompatible with --patch")); + die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--patch"); if (pathspec.nr) - die(_("--pathspec-from-file is incompatible with pathspec arguments")); + die(_("'%s' and pathspec arguments cannot be used together"), "--pathspec-from-file"); 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")); + die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file"); } unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid); @@ -364,7 +466,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (patch_mode) { if (reset_type != NONE) - die(_("--patch is incompatible with --{hard,mixed,soft}")); + die(_("options '%s' and '%s' cannot be used together"), "--patch", "--{hard,mixed,soft}"); trace2_cmd_mode("patch-interactive"); return run_add_interactive(rev, "--patch=reset", &pathspec); } @@ -395,7 +497,13 @@ int cmd_reset(int argc, const char **argv, const char *prefix) _(reset_type_names[reset_type])); if (intent_to_add && reset_type != MIXED) - die(_("-N can only be used with --mixed")); + die(_("the option '%s' requires '%s'"), "-N", "--mixed"); + + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + + if (read_cache() < 0) + die(_("index file corrupt")); /* Soft reset does not touch the index file nor the working tree * at all, but requires them in a good order. Other resets reset @@ -411,17 +519,16 @@ int cmd_reset(int argc, const char **argv, const char *prefix) if (read_from_tree(&pathspec, &oid, intent_to_add)) return 1; the_index.updated_skipworktree = 1; - if (!quiet && get_git_work_tree()) { + if (!no_refresh && get_git_work_tree()) { uint64_t t_begin, t_delta_in_ms; t_begin = getnanotime(); refresh_index(&the_index, flags, NULL, NULL, _("Unstaged changes after reset:")); t_delta_in_ms = (getnanotime() - t_begin) / 1000000; - if (advice_enabled(ADVICE_RESET_QUIET_WARNING) && t_delta_in_ms > REFRESH_INDEX_DELAY_WARNING_IN_MS) { - printf(_("\nIt took %.2f seconds to enumerate unstaged changes after reset. You can\n" - "use '--quiet' to avoid this. Set the config setting reset.quiet to true\n" - "to make this the default.\n"), t_delta_in_ms / 1000.0); + if (!quiet && advice_enabled(ADVICE_RESET_NO_REFRESH_WARNING) && t_delta_in_ms > REFRESH_INDEX_DELAY_WARNING_IN_MS) { + advise(_("It took %.2f seconds to refresh the index after reset. You can use\n" + "'--no-refresh' to avoid this."), t_delta_in_ms / 1000.0); } } } else { diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 36cb909eba..30fd8e83ea 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -20,7 +20,7 @@ #include "packfile.h" static const char rev_list_usage[] = -"git rev-list [OPTION] <commit-id>... [ -- paths... ]\n" +"git rev-list [<options>] <commit-id>... [-- <path>...]\n" " limiting output:\n" " --max-count=<n>\n" " --max-age=<epoch>\n" @@ -62,7 +62,6 @@ static const char rev_list_usage[] = static struct progress *progress; static unsigned progress_counter; -static struct list_objects_filter_options filter_options; static struct oidset omitted_objects; static int arg_print_omitted; /* print objects omitted by filter */ @@ -214,10 +213,8 @@ static void show_commit(struct commit *commit, void *data) static void finish_commit(struct commit *commit) { - if (commit->parents) { - free_commit_list(commit->parents); - commit->parents = NULL; - } + free_commit_list(commit->parents); + commit->parents = NULL; free_commit_buffer(the_repository->parsed_objects, commit); } @@ -400,7 +397,6 @@ static inline int parse_missing_action_value(const char *value) } static int try_bitmap_count(struct rev_info *revs, - struct list_objects_filter_options *filter, int filter_provided_objects) { uint32_t commit_count = 0, @@ -436,7 +432,7 @@ static int try_bitmap_count(struct rev_info *revs, */ max_count = revs->max_count; - bitmap_git = prepare_bitmap_walk(revs, filter, filter_provided_objects); + bitmap_git = prepare_bitmap_walk(revs, filter_provided_objects); if (!bitmap_git) return -1; @@ -453,7 +449,6 @@ static int try_bitmap_count(struct rev_info *revs, } static int try_bitmap_traversal(struct rev_info *revs, - struct list_objects_filter_options *filter, int filter_provided_objects) { struct bitmap_index *bitmap_git; @@ -465,7 +460,7 @@ static int try_bitmap_traversal(struct rev_info *revs, if (revs->max_count >= 0) return -1; - bitmap_git = prepare_bitmap_walk(revs, filter, filter_provided_objects); + bitmap_git = prepare_bitmap_walk(revs, filter_provided_objects); if (!bitmap_git) return -1; @@ -475,7 +470,6 @@ static int try_bitmap_traversal(struct rev_info *revs, } static int try_bitmap_disk_usage(struct rev_info *revs, - struct list_objects_filter_options *filter, int filter_provided_objects) { struct bitmap_index *bitmap_git; @@ -483,7 +477,7 @@ static int try_bitmap_disk_usage(struct rev_info *revs, if (!show_disk_usage) return -1; - bitmap_git = prepare_bitmap_walk(revs, filter, filter_provided_objects); + bitmap_git = prepare_bitmap_walk(revs, filter_provided_objects); if (!bitmap_git) return -1; @@ -506,6 +500,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) int use_bitmap_index = 0; int filter_provided_objects = 0; const char *show_progress = NULL; + int ret = 0; if (argc == 2 && !strcmp(argv[1], "-h")) usage(rev_list_usage); @@ -538,7 +533,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) const char *arg = argv[i]; if (skip_prefix(arg, "--missing=", &arg)) { if (revs.exclude_promisor_objects) - die(_("cannot combine --exclude-promisor-objects and --missing")); + die(_("options '%s' and '%s' cannot be used together"), "--exclude-promisor-objects", "--missing"); if (parse_missing_action_value(arg)) break; } @@ -589,23 +584,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) } if (!strcmp(arg, "--test-bitmap")) { test_bitmap_walk(&revs); - return 0; + goto cleanup; } if (skip_prefix(arg, "--progress=", &arg)) { show_progress = arg; continue; } - - if (skip_prefix(arg, ("--" CL_ARG__FILTER "="), &arg)) { - parse_list_objects_filter(&filter_options, arg); - if (filter_options.choice && !revs.blob_objects) - die(_("object filtering requires --objects")); - continue; - } - if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) { - list_objects_filter_set_no_filter(&filter_options); - continue; - } if (!strcmp(arg, "--filter-provided-objects")) { filter_provided_objects = 1; continue; @@ -676,7 +660,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) 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")); + die(_("marked counting and '%s' cannot be used together"), "--objects"); save_commit_buffer = (revs.verbose_header || revs.grep_filter.pattern_list || @@ -688,12 +672,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) progress = start_delayed_progress(show_progress, 0); if (use_bitmap_index) { - if (!try_bitmap_count(&revs, &filter_options, filter_provided_objects)) - return 0; - if (!try_bitmap_disk_usage(&revs, &filter_options, filter_provided_objects)) - return 0; - if (!try_bitmap_traversal(&revs, &filter_options, filter_provided_objects)) - return 0; + if (!try_bitmap_count(&revs, filter_provided_objects)) + goto cleanup; + if (!try_bitmap_disk_usage(&revs, filter_provided_objects)) + goto cleanup; + if (!try_bitmap_traversal(&revs, filter_provided_objects)) + goto cleanup; } if (prepare_revision_walk(&revs)) @@ -713,8 +697,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) find_bisection(&revs.commits, &reaches, &all, bisect_flags); - if (bisect_show_vars) - return show_bisect_vars(&info, reaches, all); + if (bisect_show_vars) { + ret = show_bisect_vars(&info, reaches, all); + goto cleanup; + } } if (filter_provided_objects) { @@ -733,7 +719,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) oidset_init(&missing_objects, DEFAULT_OIDSET_SIZE); traverse_commit_list_filtered( - &filter_options, &revs, show_commit, show_object, &info, + &revs, show_commit, show_object, &info, (arg_print_omitted ? &omitted_objects : NULL)); if (arg_print_omitted) { @@ -769,5 +755,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (show_disk_usage) printf("%"PRIuMAX"\n", (uintmax_t)total_disk_usage); - return 0; +cleanup: + release_revisions(&revs); + return ret; } diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 8480a59f57..b259d8990a 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -476,7 +476,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) /* name(s) */ s = strpbrk(sb.buf, flag_chars); - if (s == NULL) + if (!s) s = help; if (s - sb.buf == 1) /* short option only */ @@ -723,6 +723,9 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) prefix = setup_git_directory(); git_config(git_default_config, NULL); did_repo_setup = 1; + + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; } if (!strcmp(arg, "--")) { diff --git a/builtin/rm.c b/builtin/rm.c index 3d0967cdc1..84a935a16e 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -272,13 +272,13 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (pathspec_from_file) { if (pathspec.nr) - die(_("--pathspec-from-file is incompatible with pathspec arguments")); + die(_("'%s' and pathspec arguments cannot be used together"), "--pathspec-from-file"); 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")); + die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file"); } if (!pathspec.nr) @@ -399,12 +399,13 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (!index_only) { int removed = 0, gitmodules_modified = 0; struct strbuf buf = STRBUF_INIT; + int flag = force ? REMOVE_DIR_PURGE_ORIGINAL_CWD : 0; for (i = 0; i < list.nr; i++) { const char *path = list.entry[i].name; if (list.entry[i].is_submodule) { strbuf_reset(&buf); strbuf_addstr(&buf, path); - if (remove_dir_recursively(&buf, 0)) + if (remove_dir_recursively(&buf, flag)) die(_("could not remove '%s'"), path); removed = 1; diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 69c432ef1a..64962be016 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -145,7 +145,7 @@ static int send_pack_config(const char *k, const char *v, void *cb) if (value && !strcasecmp(value, "if-asked")) args.push_cert = SEND_PACK_PUSH_CERT_IF_ASKED; else - return error("Invalid value for '%s'", k); + return error(_("invalid value for '%s'"), k); } } } diff --git a/builtin/shortlog.c b/builtin/shortlog.c index e7f7af5de3..35825f075e 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -81,8 +81,10 @@ static void insert_one_record(struct shortlog *log, format_subject(&subject, oneline, " "); buffer = strbuf_detach(&subject, NULL); - if (item->util == NULL) - item->util = xcalloc(1, sizeof(struct string_list)); + if (!item->util) { + item->util = xmalloc(sizeof(struct string_list)); + string_list_init_nodup(item->util); + } string_list_append(item->util, buffer); } } @@ -388,6 +390,7 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) parse_revision_opt(&rev, &ctx, options, shortlog_usage); } parse_done: + revision_opts_finish(&rev); argc = parse_options_end(&ctx); if (nongit && argc > 1) { @@ -419,6 +422,8 @@ parse_done: else get_from_rev(&rev, &log); + release_revisions(&rev); + shortlog_output(&log); if (log.file != stdout) fclose(log.file); @@ -434,7 +439,7 @@ static void add_wrapped_shortlog_msg(struct strbuf *sb, const char *s, void shortlog_output(struct shortlog *log) { - int i, j; + size_t i, j; struct strbuf sb = STRBUF_INIT; if (log->sort_by_number) @@ -447,10 +452,10 @@ void shortlog_output(struct shortlog *log) (int)UTIL_TO_INT(item), item->string); } else { struct string_list *onelines = item->util; - fprintf(log->file, "%s (%d):\n", - item->string, onelines->nr); - for (j = onelines->nr - 1; j >= 0; j--) { - const char *msg = onelines->items[j].string; + fprintf(log->file, "%s (%"PRIuMAX"):\n", + item->string, (uintmax_t)onelines->nr); + for (j = onelines->nr; j >= 1; j--) { + const char *msg = onelines->items[j - 1].string; if (log->wrap_lines) { strbuf_reset(&sb); diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 082449293b..64c649c6a2 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -8,6 +8,7 @@ #include "parse-options.h" #include "dir.h" #include "commit-slab.h" +#include "date.h" static const char* show_branch_usage[] = { N_("git show-branch [-a | --all] [-r | --remotes] [--topo-order | --date-order]\n" @@ -707,10 +708,14 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) * * Also --all and --remotes do not make sense either. */ - die(_("--reflog is incompatible with --all, --remotes, " - "--independent or --merge-base")); + die(_("options '%s' and '%s' cannot be used together"), "--reflog", + "--all/--remotes/--independent/--merge-base"); } + if (with_current_branch && reflog) + die(_("options '%s' and '%s' cannot be used together"), + "--reflog", "--current"); + /* If nothing is specified, show all branches by default */ if (ac <= topics && all_heads + all_remotes == 0) all_heads = 1; @@ -761,6 +766,7 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) char *logmsg; char *nth_desc; const char *msg; + char *end; timestamp_t timestamp; int tz; @@ -770,11 +776,12 @@ int cmd_show_branch(int ac, const char **av, const char *prefix) reflog = i; break; } - msg = strchr(logmsg, '\t'); - if (!msg) - msg = "(none)"; - else - msg++; + + end = strchr(logmsg, '\n'); + if (end) + *end = '\0'; + + msg = (*logmsg == '\0') ? "(none)" : logmsg; reflog_msg[i] = xstrfmt("(%s) %s", show_date(timestamp, tz, DATE_MODE(RELATIVE)), diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index d0f5c4702b..f91e29b56a 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "cache.h" #include "config.h" #include "dir.h" #include "parse-options.h" @@ -7,7 +8,6 @@ #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" @@ -15,6 +15,7 @@ #include "wt-status.h" #include "quote.h" #include "sparse-index.h" +#include "worktree.h" static const char *empty_base = ""; @@ -43,7 +44,7 @@ static void write_patterns_to_file(FILE *fp, struct pattern_list *pl) } static char const * const builtin_sparse_checkout_list_usage[] = { - N_("git sparse-checkout list"), + "git sparse-checkout list", NULL }; @@ -56,6 +57,9 @@ static int sparse_checkout_list(int argc, const char **argv) char *sparse_filename; int res; + if (!core_apply_sparse_checkout) + die(_("this worktree is not sparse")); + argc = parse_options(argc, argv, NULL, builtin_sparse_checkout_list_options, builtin_sparse_checkout_list_usage, 0); @@ -124,7 +128,7 @@ static void clean_tracked_sparse_directories(struct repository *r) * sparse index will not delete directories that contain * conflicted entries or submodules. */ - if (!r->index->sparse_index) { + if (r->index->sparse_index == INDEX_EXPANDED) { /* * If something, such as a merge conflict or other concern, * prevents us from converting to a sparse index, then do @@ -182,6 +186,8 @@ static void clean_tracked_sparse_directories(struct repository *r) item->string); } + strvec_clear(&s); + clear_pathspec(&p); dir_clear(&dir); } @@ -322,11 +328,11 @@ static int write_patterns_and_update(struct pattern_list *pl) fd = hold_lock_file_for_update(&lk, sparse_filename, LOCK_DIE_ON_ERROR); + free(sparse_filename); result = update_working_directory(pl); if (result) { rollback_lock_file(&lk); - free(sparse_filename); clear_pattern_list(pl); update_working_directory(NULL); return result; @@ -342,7 +348,6 @@ static int write_patterns_and_update(struct pattern_list *pl) fflush(fp); commit_lock_file(&lk); - free(sparse_filename); clear_pattern_list(pl); return 0; @@ -356,32 +361,68 @@ enum sparse_checkout_mode { static int set_config(enum sparse_checkout_mode mode) { - const char *config_path; + /* Update to use worktree config, if not already. */ + if (init_worktree_config(the_repository)) { + error(_("failed to initialize worktree config")); + return 1; + } - 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")); + if (repo_config_set_worktree_gently(the_repository, + "core.sparseCheckout", + mode ? "true" : "false") || + repo_config_set_worktree_gently(the_repository, + "core.sparseCheckoutCone", + mode == MODE_CONE_PATTERNS ? + "true" : "false")) return 1; + + if (mode == MODE_NO_PATTERNS) + return set_sparse_index_config(the_repository, 0); + + return 0; +} + +static int update_modes(int *cone_mode, int *sparse_index) +{ + int mode, record_mode; + + /* Determine if we need to record the mode; ensure sparse checkout on */ + record_mode = (*cone_mode != -1) || !core_apply_sparse_checkout; + + /* If not specified, use previous definition of cone mode */ + if (*cone_mode == -1 && core_apply_sparse_checkout) + *cone_mode = core_sparse_checkout_cone; + + /* Set cone/non-cone mode appropriately */ + core_apply_sparse_checkout = 1; + if (*cone_mode == 1 || *cone_mode == -1) { + mode = MODE_CONE_PATTERNS; + core_sparse_checkout_cone = 1; + } else { + mode = MODE_ALL_PATTERNS; + core_sparse_checkout_cone = 0; } + if (record_mode && set_config(mode)) + return 1; - config_path = git_path("config.worktree"); - git_config_set_in_file_gently(config_path, - "core.sparseCheckout", - mode ? "true" : NULL); + /* Set sparse-index/non-sparse-index mode if specified */ + if (*sparse_index >= 0) { + if (set_sparse_index_config(the_repository, *sparse_index) < 0) + die(_("failed to modify sparse-index config")); - git_config_set_in_file_gently(config_path, - "core.sparseCheckoutCone", - mode == MODE_CONE_PATTERNS ? "true" : NULL); + /* force an index rewrite */ + repo_read_index(the_repository); + the_repository->index->updated_workdir = 1; - if (mode == MODE_NO_PATTERNS) - set_sparse_index_config(the_repository, 0); + if (!*sparse_index) + ensure_full_index(the_repository->index); + } return 0; } static char const * const builtin_sparse_checkout_init_usage[] = { - N_("git sparse-checkout init [--cone] [--[no-]sparse-index]"), + "git sparse-checkout init [--cone] [--[no-]sparse-index]", NULL }; @@ -396,7 +437,6 @@ static int sparse_checkout_init(int argc, const char **argv) char *sparse_filename; int res; struct object_id oid; - int mode; struct strbuf pattern = STRBUF_INIT; static struct option builtin_sparse_checkout_init_options[] = { @@ -409,19 +449,14 @@ static int sparse_checkout_init(int argc, const char **argv) repo_read_index(the_repository); + init_opts.cone_mode = -1; init_opts.sparse_index = -1; 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)) + if (update_modes(&init_opts.cone_mode, &init_opts.sparse_index)) return 1; memset(&pl, 0, sizeof(pl)); @@ -429,17 +464,6 @@ static int sparse_checkout_init(int argc, const char **argv) sparse_filename = get_sparse_checkout_filename(); res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL, 0); - if (init_opts.sparse_index >= 0) { - if (set_sparse_index_config(the_repository, init_opts.sparse_index) < 0) - die(_("failed to modify sparse-index config")); - - /* force an index rewrite */ - repo_read_index(the_repository); - the_repository->index->updated_workdir = 1; - } - - core_apply_sparse_checkout = 1; - /* If we already have a sparse-checkout file, use it. */ if (res >= 0) { free(sparse_filename); @@ -450,6 +474,9 @@ static int sparse_checkout_init(int argc, const char **argv) FILE *fp; /* assume we are in a fresh repo, but update the sparse-checkout file */ + if (safe_create_leading_directories(sparse_filename)) + die(_("unable to create leading directories of %s"), + sparse_filename); fp = xfopen(sparse_filename, "w"); if (!fp) die(_("failed to open '%s'"), sparse_filename); @@ -483,7 +510,7 @@ static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *pat char *oldpattern = e->pattern; size_t newlen; - if (slash == e->pattern) + if (!slash || slash == e->pattern) break; newlen = slash - e->pattern; @@ -515,17 +542,9 @@ static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl) 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 argc, const char **argv, + int use_stdin) { int i; if (core_sparse_checkout_cone) { @@ -535,7 +554,7 @@ static void add_patterns_from_input(struct pattern_list *pl, hashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0); pl->use_cone_patterns = 1; - if (set_opts.use_stdin) { + if (use_stdin) { struct strbuf unquoted = STRBUF_INIT; while (!strbuf_getline(&line, stdin)) { if (line.buf[0] == '"') { @@ -559,7 +578,7 @@ static void add_patterns_from_input(struct pattern_list *pl, } } } else { - if (set_opts.use_stdin) { + if (use_stdin) { struct strbuf line = STRBUF_INIT; while (!strbuf_getline(&line, stdin)) { @@ -580,7 +599,8 @@ enum modify_type { }; static void add_patterns_cone_mode(int argc, const char **argv, - struct pattern_list *pl) + struct pattern_list *pl, + int use_stdin) { struct strbuf buffer = STRBUF_INIT; struct pattern_entry *pe; @@ -588,7 +608,7 @@ static void add_patterns_cone_mode(int argc, const char **argv, struct pattern_list existing; char *sparse_filename = get_sparse_checkout_filename(); - add_patterns_from_input(pl, argc, argv); + add_patterns_from_input(pl, argc, argv, use_stdin); memset(&existing, 0, sizeof(existing)); existing.use_cone_patterns = core_sparse_checkout_cone; @@ -598,6 +618,9 @@ static void add_patterns_cone_mode(int argc, const char **argv, die(_("unable to load existing sparse-checkout patterns")); free(sparse_filename); + if (!existing.use_cone_patterns) + die(_("existing sparse-checkout patterns do not use cone mode")); + hashmap_for_each_entry(&existing.recursive_hashmap, &iter, pe, ent) { if (!hashmap_contains_parent(&pl->recursive_hashmap, pe->pattern, &buffer) || @@ -614,17 +637,19 @@ static void add_patterns_cone_mode(int argc, const char **argv, } static void add_patterns_literal(int argc, const char **argv, - struct pattern_list *pl) + struct pattern_list *pl, + int use_stdin) { char *sparse_filename = get_sparse_checkout_filename(); if (add_patterns_from_file_to_list(sparse_filename, "", 0, pl, NULL, 0)) die(_("unable to load existing sparse-checkout patterns")); free(sparse_filename); - add_patterns_from_input(pl, argc, argv); + add_patterns_from_input(pl, argc, argv, use_stdin); } -static int modify_pattern_list(int argc, const char **argv, enum modify_type m) +static int modify_pattern_list(int argc, const char **argv, int use_stdin, + enum modify_type m) { int result; int changed_config = 0; @@ -633,13 +658,13 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m) switch (m) { case ADD: if (core_sparse_checkout_cone) - add_patterns_cone_mode(argc, argv, pl); + add_patterns_cone_mode(argc, argv, pl, use_stdin); else - add_patterns_literal(argc, argv, pl); + add_patterns_literal(argc, argv, pl, use_stdin); break; case REPLACE: - add_patterns_from_input(pl, argc, argv); + add_patterns_from_input(pl, argc, argv, use_stdin); break; } @@ -659,46 +684,195 @@ static int modify_pattern_list(int argc, const char **argv, enum modify_type m) return result; } -static int sparse_checkout_set(int argc, const char **argv, const char *prefix, - enum modify_type m) +static void sanitize_paths(int argc, const char **argv, + const char *prefix, int skip_checks) { - static struct option builtin_sparse_checkout_set_options[] = { - OPT_BOOL(0, "stdin", &set_opts.use_stdin, + int i; + + if (!argc) + return; + + if (prefix && *prefix && core_sparse_checkout_cone) { + /* + * The args are not pathspecs, so unfortunately we + * cannot imitate how cmd_add() uses parse_pathspec(). + */ + int prefix_len = strlen(prefix); + + for (i = 0; i < argc; i++) + argv[i] = prefix_path(prefix, prefix_len, argv[i]); + } + + if (skip_checks) + return; + + if (prefix && *prefix && !core_sparse_checkout_cone) + die(_("please run from the toplevel directory in non-cone mode")); + + if (core_sparse_checkout_cone) { + for (i = 0; i < argc; i++) { + if (argv[i][0] == '/') + die(_("specify directories rather than patterns (no leading slash)")); + if (argv[i][0] == '!') + die(_("specify directories rather than patterns. If your directory starts with a '!', pass --skip-checks")); + if (strpbrk(argv[i], "*?[]")) + die(_("specify directories rather than patterns. If your directory really has any of '*?[]\\' in it, pass --skip-checks")); + } + } + + for (i = 0; i < argc; i++) { + struct cache_entry *ce; + struct index_state *index = the_repository->index; + int pos = index_name_pos(index, argv[i], strlen(argv[i])); + + if (pos < 0) + continue; + ce = index->cache[pos]; + if (S_ISSPARSEDIR(ce->ce_mode)) + continue; + + if (core_sparse_checkout_cone) + die(_("'%s' is not a directory; to treat it as a directory anyway, rerun with --skip-checks"), argv[i]); + else + warning(_("pass a leading slash before paths such as '%s' if you want a single file (see NON-CONE PROBLEMS in the git-sparse-checkout manual)."), argv[i]); + } +} + +static char const * const builtin_sparse_checkout_add_usage[] = { + N_("git sparse-checkout add [--skip-checks] (--stdin | <patterns>)"), + NULL +}; + +static struct sparse_checkout_add_opts { + int skip_checks; + int use_stdin; +} add_opts; + +static int sparse_checkout_add(int argc, const char **argv, const char *prefix) +{ + static struct option builtin_sparse_checkout_add_options[] = { + OPT_BOOL_F(0, "skip-checks", &add_opts.skip_checks, + N_("skip some sanity checks on the given paths that might give false positives"), + PARSE_OPT_NONEG), + OPT_BOOL(0, "stdin", &add_opts.use_stdin, N_("read patterns from standard in")), OPT_END(), }; + if (!core_apply_sparse_checkout) + die(_("no sparse-checkout to add to")); + + repo_read_index(the_repository); + + argc = parse_options(argc, argv, prefix, + builtin_sparse_checkout_add_options, + builtin_sparse_checkout_add_usage, + PARSE_OPT_KEEP_UNKNOWN); + + sanitize_paths(argc, argv, prefix, add_opts.skip_checks); + + return modify_pattern_list(argc, argv, add_opts.use_stdin, ADD); +} + +static char const * const builtin_sparse_checkout_set_usage[] = { + N_("git sparse-checkout set [--[no-]cone] [--[no-]sparse-index] [--skip-checks] (--stdin | <patterns>)"), + NULL +}; + +static struct sparse_checkout_set_opts { + int cone_mode; + int sparse_index; + int skip_checks; + int use_stdin; +} set_opts; + +static int sparse_checkout_set(int argc, const char **argv, const char *prefix) +{ + int default_patterns_nr = 2; + const char *default_patterns[] = {"/*", "!/*/", NULL}; + + static struct option builtin_sparse_checkout_set_options[] = { + OPT_BOOL(0, "cone", &set_opts.cone_mode, + N_("initialize the sparse-checkout in cone mode")), + OPT_BOOL(0, "sparse-index", &set_opts.sparse_index, + N_("toggle the use of a sparse index")), + OPT_BOOL_F(0, "skip-checks", &set_opts.skip_checks, + N_("skip some sanity checks on the given paths that might give false positives"), + PARSE_OPT_NONEG), + OPT_BOOL_F(0, "stdin", &set_opts.use_stdin, + N_("read patterns from standard in"), + PARSE_OPT_NONEG), + OPT_END(), + }; + repo_read_index(the_repository); + set_opts.cone_mode = -1; + set_opts.sparse_index = -1; + 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); + if (update_modes(&set_opts.cone_mode, &set_opts.sparse_index)) + return 1; + + /* + * Cone mode automatically specifies the toplevel directory. For + * non-cone mode, if nothing is specified, manually select just the + * top-level directory (much as 'init' would do). + */ + if (!core_sparse_checkout_cone && argc == 0) { + argv = default_patterns; + argc = default_patterns_nr; + } else { + sanitize_paths(argc, argv, prefix, set_opts.skip_checks); + } + + return modify_pattern_list(argc, argv, set_opts.use_stdin, REPLACE); } static char const * const builtin_sparse_checkout_reapply_usage[] = { - N_("git sparse-checkout reapply"), + "git sparse-checkout reapply [--[no-]cone] [--[no-]sparse-index]", NULL }; +static struct sparse_checkout_reapply_opts { + int cone_mode; + int sparse_index; +} reapply_opts; + static int sparse_checkout_reapply(int argc, const char **argv) { static struct option builtin_sparse_checkout_reapply_options[] = { + OPT_BOOL(0, "cone", &reapply_opts.cone_mode, + N_("initialize the sparse-checkout in cone mode")), + OPT_BOOL(0, "sparse-index", &reapply_opts.sparse_index, + N_("toggle the use of a sparse index")), OPT_END(), }; + if (!core_apply_sparse_checkout) + die(_("must be in a sparse-checkout to reapply sparsity patterns")); + + reapply_opts.cone_mode = -1; + reapply_opts.sparse_index = -1; + argc = parse_options(argc, argv, NULL, builtin_sparse_checkout_reapply_options, builtin_sparse_checkout_reapply_usage, 0); repo_read_index(the_repository); + + if (update_modes(&reapply_opts.cone_mode, &reapply_opts.sparse_index)) + return 1; + return update_working_directory(NULL); } static char const * const builtin_sparse_checkout_disable_usage[] = { - N_("git sparse-checkout disable"), + "git sparse-checkout disable", NULL }; @@ -710,6 +884,17 @@ static int sparse_checkout_disable(int argc, const char **argv) struct pattern_list pl; struct strbuf match_all = STRBUF_INIT; + /* + * We do not exit early if !core_apply_sparse_checkout; due to the + * ability for users to manually muck things up between + * direct editing of .git/info/sparse-checkout + * running read-tree -m u HEAD or update-index --skip-worktree + * direct toggling of config options + * users might end up with an index with SKIP_WORKTREE bit set on + * some files and not know how to undo it. So, here we just + * forcibly return to a dense checkout regardless of initial state. + */ + argc = parse_options(argc, argv, NULL, builtin_sparse_checkout_disable_options, builtin_sparse_checkout_disable_usage, 0); @@ -752,15 +937,18 @@ int cmd_sparse_checkout(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + 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); + return sparse_checkout_set(argc, argv, prefix); if (!strcmp(argv[0], "add")) - return sparse_checkout_set(argc, argv, prefix, ADD); + return sparse_checkout_add(argc, argv, prefix); if (!strcmp(argv[0], "reapply")) return sparse_checkout_reapply(argc, argv); if (!strcmp(argv[0], "disable")) diff --git a/builtin/stash.c b/builtin/stash.c index a0ccc8654d..30fa101460 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -7,6 +7,7 @@ #include "cache-tree.h" #include "unpack-trees.h" #include "merge-recursive.h" +#include "merge-ort-wrappers.h" #include "strvec.h" #include "run-command.h" #include "dir.h" @@ -16,7 +17,7 @@ #include "log-tree.h" #include "diffcore.h" #include "exec-cmd.h" -#include "entry.h" +#include "reflog.h" #define INCLUDE_ALL_FILES 2 @@ -27,11 +28,11 @@ static const char * const git_stash_usage[] = { N_("git stash ( pop | apply ) [--index] [-q|--quiet] [<stash>]"), N_("git stash branch <branchname> [<stash>]"), "git stash clear", - N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" + N_("git stash [push [-p|--patch] [-S|--staged] [-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" + N_("git stash save [-p|--patch] [-S|--staged] [-k|--[no-]keep-index] [-q|--quiet]\n" " [-u|--include-untracked] [-a|--all] [<message>]"), NULL }; @@ -116,6 +117,10 @@ struct stash_info { int has_u; }; +#define STASH_INFO_INIT { \ + .revision = STRBUF_INIT, \ +} + static void free_stash_info(struct stash_info *info) { strbuf_release(&info->revision); @@ -157,10 +162,8 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv) if (argc == 1) commit = argv[0]; - strbuf_init(&info->revision, 0); if (!commit) { if (!ref_exists(ref_stash)) { - free_stash_info(info); fprintf_ln(stderr, _("No stash entries found.")); return -1; } @@ -174,11 +177,8 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv) revision = info->revision.buf; - if (get_oid(revision, &info->w_commit)) { - error(_("%s is not a valid reference"), revision); - free_stash_info(info); - return -1; - } + if (get_oid(revision, &info->w_commit)) + return error(_("%s is not a valid reference"), revision); assert_stash_like(info, revision); @@ -197,7 +197,7 @@ static int get_stash_info(struct stash_info *info, int argc, const char **argv) info->is_stash_ref = !strcmp(expanded_ref, ref_stash); break; default: /* Invalid or ambiguous */ - free_stash_info(info); + break; } free(expanded_ref); @@ -310,7 +310,7 @@ static int reset_head(void) * API for resetting. */ cp.git_cmd = 1; - strvec_push(&cp.args, "reset"); + strvec_pushl(&cp.args, "reset", "--quiet", "--refresh", NULL); return run_command(&cp); } @@ -356,7 +356,7 @@ static int restore_untracked(struct object_id *u_tree) cp.git_cmd = 1; strvec_push(&cp.args, "read-tree"); strvec_push(&cp.args, oid_to_hex(u_tree)); - strvec_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", + strvec_pushf(&cp.env, "GIT_INDEX_FILE=%s", stash_index_path.buf); if (run_command(&cp)) { remove_path(stash_index_path.buf); @@ -366,7 +366,7 @@ static int restore_untracked(struct object_id *u_tree) child_process_init(&cp); cp.git_cmd = 1; strvec_pushl(&cp.args, "checkout-index", "--all", NULL); - strvec_pushf(&cp.env_array, "GIT_INDEX_FILE=%s", + strvec_pushf(&cp.env, "GIT_INDEX_FILE=%s", stash_index_path.buf); res = run_command(&cp); @@ -492,13 +492,13 @@ static void unstage_changes_unless_new(struct object_id *orig_tree) static int do_apply_stash(const char *prefix, struct stash_info *info, int index, int quiet) { - int ret; + int clean, ret; int has_index = index; struct merge_options o; struct object_id c_tree; struct object_id index_tree; - struct commit *result; - const struct object_id *bases[1]; + struct tree *head, *merge, *merge_base; + struct lock_file lock = LOCK_INIT; read_cache_preload(NULL); if (refresh_and_write_cache(REFRESH_QUIET, 0, 0)) @@ -541,6 +541,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, o.branch1 = "Updated upstream"; o.branch2 = "Stashed changes"; + o.ancestor = "Stash base"; if (oideq(&info->b_tree, &c_tree)) o.branch1 = "Version stash was based on"; @@ -551,28 +552,45 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, if (o.verbosity >= 3) printf_ln(_("Merging %s with %s"), o.branch1, o.branch2); - bases[0] = &info->b_tree; + head = lookup_tree(o.repo, &c_tree); + merge = lookup_tree(o.repo, &info->w_tree); + merge_base = lookup_tree(o.repo, &info->b_tree); + + repo_hold_locked_index(o.repo, &lock, LOCK_DIE_ON_ERROR); + clean = merge_ort_nonrecursive(&o, head, merge, merge_base); + + /* + * If 'clean' >= 0, reverse the value for 'ret' so 'ret' is 0 when the + * merge was clean, and nonzero if the merge was unclean or encountered + * an error. + */ + ret = clean >= 0 ? !clean : clean; + + if (ret < 0) + rollback_lock_file(&lock); + else if (write_locked_index(o.repo->index, &lock, + COMMIT_LOCK | SKIP_IF_UNCHANGED)) + ret = error(_("could not write index")); - ret = merge_recursive_generic(&o, &c_tree, &info->w_tree, 1, bases, - &result); if (ret) { rerere(0); if (index) fprintf_ln(stderr, _("Index was not unstashed.")); - return ret; + goto restore_untracked; } if (has_index) { if (reset_tree(&index_tree, 0, 0)) - return -1; + ret = -1; } else { unstage_changes_unless_new(&c_tree); } +restore_untracked: if (info->has_u && restore_untracked(&info->u_tree)) - return error(_("could not restore untracked files from stash")); + ret = error(_("could not restore untracked files from stash")); if (!quiet) { struct child_process cp = CHILD_PROCESS_INIT; @@ -584,23 +602,23 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, */ cp.git_cmd = 1; cp.dir = prefix; - strvec_pushf(&cp.env_array, GIT_WORK_TREE_ENVIRONMENT"=%s", + strvec_pushf(&cp.env, GIT_WORK_TREE_ENVIRONMENT"=%s", absolute_path(get_git_work_tree())); - strvec_pushf(&cp.env_array, GIT_DIR_ENVIRONMENT"=%s", + strvec_pushf(&cp.env, GIT_DIR_ENVIRONMENT"=%s", absolute_path(get_git_dir())); strvec_push(&cp.args, "status"); run_command(&cp); } - return 0; + return ret; } static int apply_stash(int argc, const char **argv, const char *prefix) { - int ret; + int ret = -1; int quiet = 0; int index = 0; - struct stash_info info; + struct stash_info info = STASH_INFO_INIT; struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_BOOL(0, "index", &index, @@ -612,9 +630,10 @@ static int apply_stash(int argc, const char **argv, const char *prefix) git_stash_apply_usage, 0); if (get_stash_info(&info, argc, argv)) - return -1; + goto cleanup; ret = do_apply_stash(prefix, &info, index, quiet); +cleanup: free_stash_info(&info); return ret; } @@ -633,20 +652,9 @@ static int reflog_is_empty(const char *refname) static int do_drop_stash(struct stash_info *info, int quiet) { - int ret; - struct child_process cp_reflog = CHILD_PROCESS_INIT; - - /* - * reflog does not provide a simple function for deleting refs. One will - * need to be added to avoid implementing too much reflog code here - */ - - cp_reflog.git_cmd = 1; - 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 (!reflog_delete(info->revision.buf, + EXPIRE_REFLOGS_REWRITE | EXPIRE_REFLOGS_UPDATE_REF, + 0)) { if (!quiet) printf_ln(_("Dropped %s (%s)"), info->revision.buf, oid_to_hex(&info->w_commit)); @@ -661,20 +669,25 @@ static int do_drop_stash(struct stash_info *info, int quiet) return 0; } -static void assert_stash_ref(struct stash_info *info) +static int get_stash_info_assert(struct stash_info *info, int argc, + const char **argv) { - if (!info->is_stash_ref) { - error(_("'%s' is not a stash reference"), info->revision.buf); - free_stash_info(info); - exit(1); - } + int ret = get_stash_info(info, argc, argv); + + if (ret < 0) + return ret; + + if (!info->is_stash_ref) + return error(_("'%s' is not a stash reference"), info->revision.buf); + + return 0; } static int drop_stash(int argc, const char **argv, const char *prefix) { - int ret; + int ret = -1; int quiet = 0; - struct stash_info info; + struct stash_info info = STASH_INFO_INIT; struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_END() @@ -683,22 +696,21 @@ static int drop_stash(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_stash_drop_usage, 0); - if (get_stash_info(&info, argc, argv)) - return -1; - - assert_stash_ref(&info); + if (get_stash_info_assert(&info, argc, argv)) + goto cleanup; ret = do_drop_stash(&info, quiet); +cleanup: free_stash_info(&info); return ret; } static int pop_stash(int argc, const char **argv, const char *prefix) { - int ret; + int ret = -1; int index = 0; int quiet = 0; - struct stash_info info; + struct stash_info info = STASH_INFO_INIT; struct option options[] = { OPT__QUIET(&quiet, N_("be quiet, only report errors")), OPT_BOOL(0, "index", &index, @@ -709,25 +721,25 @@ static int pop_stash(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, git_stash_pop_usage, 0); - if (get_stash_info(&info, argc, argv)) - return -1; + if (get_stash_info_assert(&info, argc, argv)) + goto cleanup; - assert_stash_ref(&info); if ((ret = do_apply_stash(prefix, &info, index, quiet))) printf_ln(_("The stash entry is kept in case " "you need it again.")); else ret = do_drop_stash(&info, quiet); +cleanup: free_stash_info(&info); return ret; } static int branch_stash(int argc, const char **argv, const char *prefix) { - int ret; + int ret = -1; const char *branch = NULL; - struct stash_info info; + struct stash_info info = STASH_INFO_INIT; struct child_process cp = CHILD_PROCESS_INIT; struct option options[] = { OPT_END() @@ -744,7 +756,7 @@ static int branch_stash(int argc, const char **argv, const char *prefix) branch = argv[0]; if (get_stash_info(&info, argc - 1, argv + 1)) - return -1; + goto cleanup; cp.git_cmd = 1; strvec_pushl(&cp.args, "checkout", "-b", NULL); @@ -756,8 +768,8 @@ static int branch_stash(int argc, const char **argv, const char *prefix) if (!ret && info.is_stash_ref) ret = do_drop_stash(&info, 0); +cleanup: free_stash_info(&info); - return ret; } @@ -787,7 +799,6 @@ static int list_stash(int argc, const char **argv, const char *prefix) static int show_stat = 1; static int show_patch; static int show_include_untracked; -static int use_legacy_stash; static int git_stash_config(const char *var, const char *value, void *cb) { @@ -803,10 +814,6 @@ static int git_stash_config(const char *var, const char *value, void *cb) show_include_untracked = git_config_bool(var, value); return 0; } - if (!strcmp(var, "stash.usebuiltin")) { - use_legacy_stash = !git_config_bool(var, value); - return 0; - } return git_diff_basic_config(var, value, cb); } @@ -840,8 +847,8 @@ static void diff_include_untracked(const struct stash_info *info, struct diff_op static int show_stash(int argc, const char **argv, const char *prefix) { int i; - int ret = 0; - struct stash_info info; + int ret = -1; + struct stash_info info = STASH_INFO_INIT; struct rev_info rev; struct strvec stash_args = STRVEC_INIT; struct strvec revision_args = STRVEC_INIT; @@ -859,6 +866,7 @@ static int show_stash(int argc, const char **argv, const char *prefix) UNTRACKED_ONLY, PARSE_OPT_NONEG), OPT_END() }; + int do_usage = 0; init_diff_ui_defaults(); git_config(git_diff_ui_config, NULL); @@ -876,10 +884,8 @@ static int show_stash(int argc, const char **argv, const char *prefix) strvec_push(&revision_args, argv[i]); } - ret = get_stash_info(&info, stash_args.nr, stash_args.v); - strvec_clear(&stash_args); - if (ret) - return -1; + if (get_stash_info(&info, stash_args.nr, stash_args.v)) + goto cleanup; /* * The config settings are applied only if there are not passed @@ -893,16 +899,14 @@ static int show_stash(int argc, const char **argv, const char *prefix) rev.diffopt.output_format |= DIFF_FORMAT_PATCH; if (!show_stat && !show_patch) { - free_stash_info(&info); - return 0; + ret = 0; + goto cleanup; } } 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); - } + if (argc > 1) + goto usage; if (!rev.diffopt.output_format) { rev.diffopt.output_format = DIFF_FORMAT_PATCH; diff_setup_done(&rev.diffopt); @@ -927,8 +931,17 @@ static int show_stash(int argc, const char **argv, const char *prefix) } log_tree_diff_flush(&rev); + ret = diff_result_code(&rev.diffopt, 0); +cleanup: + strvec_clear(&stash_args); free_stash_info(&info); - return diff_result_code(&rev.diffopt, 0); + release_revisions(&rev); + if (do_usage) + usage_with_options(git_stash_show_usage, options); + return ret; +usage: + do_usage = 1; + goto cleanup; } static int do_store_stash(const struct object_id *w_commit, const char *stash_msg, @@ -1062,7 +1075,6 @@ static int check_changes_tracked_files(const struct pathspec *ps) goto done; } - object_array_clear(&rev.pending); result = run_diff_files(&rev, 0); if (diff_result_code(&rev.diffopt, result)) { ret = 1; @@ -1070,7 +1082,7 @@ static int check_changes_tracked_files(const struct pathspec *ps) } done: - clear_pathspec(&rev.prune_data); + release_revisions(&rev); return ret; } @@ -1103,7 +1115,7 @@ static int save_untracked_files(struct stash_info *info, struct strbuf *msg, cp_upd_index.git_cmd = 1; strvec_pushl(&cp_upd_index.args, "update-index", "-z", "--add", "--remove", "--stdin", NULL); - strvec_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", + strvec_pushf(&cp_upd_index.env, "GIT_INDEX_FILE=%s", stash_index_path.buf); strbuf_addf(&untracked_msg, "untracked files on %s\n", msg->buf); @@ -1132,6 +1144,38 @@ done: return ret; } +static int stash_staged(struct stash_info *info, struct strbuf *out_patch, + int quiet) +{ + int ret = 0; + struct child_process cp_diff_tree = CHILD_PROCESS_INIT; + struct index_state istate = { NULL }; + + if (write_index_as_tree(&info->w_tree, &istate, the_repository->index_file, + 0, NULL)) { + ret = -1; + goto done; + } + + cp_diff_tree.git_cmd = 1; + 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; + } + + if (!out_patch->len) { + if (!quiet) + fprintf_ln(stderr, _("No staged changes")); + ret = 1; + } + +done: + discard_index(&istate); + return ret; +} + static int stash_patch(struct stash_info *info, const struct pathspec *ps, struct strbuf *out_patch, int quiet) { @@ -1145,7 +1189,7 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, cp_read_tree.git_cmd = 1; strvec_pushl(&cp_read_tree.args, "read-tree", "HEAD", NULL); - strvec_pushf(&cp_read_tree.env_array, "GIT_INDEX_FILE=%s", + strvec_pushf(&cp_read_tree.env, "GIT_INDEX_FILE=%s", stash_index_path.buf); if (run_command(&cp_read_tree)) { ret = -1; @@ -1232,7 +1276,7 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps 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", + strvec_pushf(&cp_upd_index.env, "GIT_INDEX_FILE=%s", stash_index_path.buf); if (pipe_command(&cp_upd_index, diff_output.buf, diff_output.len, @@ -1249,16 +1293,14 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps done: discard_index(&istate); - UNLEAK(rev); - object_array_clear(&rev.pending); - clear_pathspec(&rev.prune_data); + release_revisions(&rev); strbuf_release(&diff_output); remove_path(stash_index_path.buf); return ret; } static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_buf, - int include_untracked, int patch_mode, + int include_untracked, int patch_mode, int only_staged, struct stash_info *info, struct strbuf *patch, int quiet) { @@ -1299,7 +1341,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b branch_ref = resolve_ref_unsafe("HEAD", 0, NULL, &flags); if (flags & REF_ISSYMREF) - branch_name = strrchr(branch_ref, '/') + 1; + skip_prefix(branch_ref, "refs/heads/", &branch_name); head_short_sha1 = find_unique_abbrev(&head_commit->object.oid, DEFAULT_ABBREV); strbuf_addf(&msg, "%s: %s ", branch_name, head_short_sha1); @@ -1337,6 +1379,16 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b } else if (ret > 0) { goto done; } + } else if (only_staged) { + ret = stash_staged(info, patch, quiet); + if (ret < 0) { + if (!quiet) + fprintf_ln(stderr, _("Cannot save the current " + "staged state")); + goto done; + } else if (ret > 0) { + goto done; + } } else { if (stash_working_tree(info, ps)) { if (!quiet) @@ -1383,9 +1435,9 @@ done: static int create_stash(int argc, const char **argv, const char *prefix) { - int ret = 0; + int ret; struct strbuf stash_msg_buf = STRBUF_INIT; - struct stash_info info; + struct stash_info info = STASH_INFO_INIT; struct pathspec ps; /* Starting with argv[1], since argv[0] is "create" */ @@ -1395,20 +1447,21 @@ static int create_stash(int argc, const char **argv, const char *prefix) if (!check_changes_tracked_files(&ps)) return 0; - ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, &info, + ret = do_create_stash(&ps, &stash_msg_buf, 0, 0, 0, &info, NULL, 0); if (!ret) printf_ln("%s", oid_to_hex(&info.w_commit)); + free_stash_info(&info); strbuf_release(&stash_msg_buf); return ret; } static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int quiet, - int keep_index, int patch_mode, int include_untracked) + int keep_index, int patch_mode, int include_untracked, int only_staged) { int ret = 0; - struct stash_info info; + struct stash_info info = STASH_INFO_INIT; struct strbuf patch = STRBUF_INIT; struct strbuf stash_msg_buf = STRBUF_INIT; struct strbuf untracked_files = STRBUF_INIT; @@ -1423,6 +1476,17 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q goto done; } + /* --patch overrides --staged */ + if (patch_mode) + only_staged = 0; + + if (only_staged && include_untracked) { + fprintf_ln(stderr, _("Can't use --staged and --include-untracked" + " or --all at the same time")); + ret = -1; + goto done; + } + read_cache_preload(NULL); if (!include_untracked && ps->nr) { int i; @@ -1463,7 +1527,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q if (stash_msg) strbuf_addstr(&stash_msg_buf, stash_msg); - if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, + if (do_create_stash(ps, &stash_msg_buf, include_untracked, patch_mode, only_staged, &info, &patch, quiet)) { ret = -1; goto done; @@ -1480,13 +1544,19 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q printf_ln(_("Saved working directory and index state %s"), stash_msg_buf.buf); - if (!patch_mode) { + if (!(patch_mode || only_staged)) { if (include_untracked && !ps->nr) { struct child_process cp = CHILD_PROCESS_INIT; cp.git_cmd = 1; + if (startup_info->original_cwd) { + cp.dir = startup_info->original_cwd; + strvec_pushf(&cp.env, "%s=%s", + GIT_WORK_TREE_ENVIRONMENT, + the_repository->worktree); + } strvec_pushl(&cp.args, "clean", "--force", - "--quiet", "-d", NULL); + "--quiet", "-d", ":/", NULL); if (include_untracked == INCLUDE_ALL_FILES) strvec_push(&cp.args, "-x"); if (run_command(&cp)) { @@ -1578,7 +1648,8 @@ 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; - strvec_pushl(&cp.args, "reset", "-q", "--", NULL); + strvec_pushl(&cp.args, "reset", "-q", "--refresh", "--", + NULL); add_pathspecs(&cp.args, ps); if (run_command(&cp)) { ret = -1; @@ -1589,6 +1660,7 @@ static int do_push_stash(const struct pathspec *ps, const char *stash_msg, int q } done: + free_stash_info(&info); strbuf_release(&stash_msg_buf); return ret; } @@ -1598,6 +1670,7 @@ static int push_stash(int argc, const char **argv, const char *prefix, { int force_assume = 0; int keep_index = -1; + int only_staged = 0; int patch_mode = 0; int include_untracked = 0; int quiet = 0; @@ -1608,6 +1681,8 @@ static int push_stash(int argc, const char **argv, const char *prefix, struct option options[] = { OPT_BOOL('k', "keep-index", &keep_index, N_("keep index")), + OPT_BOOL('S', "staged", &only_staged, + N_("stash staged changes only")), OPT_BOOL('p', "patch", &patch_mode, N_("stash in patch mode")), OPT__QUIET(&quiet, N_("quiet mode")), @@ -1625,6 +1700,7 @@ static int push_stash(int argc, const char **argv, const char *prefix, if (argc) { force_assume = !strcmp(argv[0], "-p"); argc = parse_options(argc, argv, prefix, options, + push_assumed ? git_stash_usage : git_stash_push_usage, PARSE_OPT_KEEP_DASHDASH); } @@ -1644,25 +1720,29 @@ static int push_stash(int argc, const char **argv, const char *prefix, if (pathspec_from_file) { if (patch_mode) - die(_("--pathspec-from-file is incompatible with --patch")); + die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--patch"); + + if (only_staged) + die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--staged"); if (ps.nr) - die(_("--pathspec-from-file is incompatible with pathspec arguments")); + die(_("'%s' and pathspec arguments cannot be used together"), "--pathspec-from-file"); 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")); + die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file"); } return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode, - include_untracked); + include_untracked, only_staged); } static int save_stash(int argc, const char **argv, const char *prefix) { int keep_index = -1; + int only_staged = 0; int patch_mode = 0; int include_untracked = 0; int quiet = 0; @@ -1673,6 +1753,8 @@ static int save_stash(int argc, const char **argv, const char *prefix) struct option options[] = { OPT_BOOL('k', "keep-index", &keep_index, N_("keep index")), + OPT_BOOL('S', "staged", &only_staged, + N_("stash staged changes only")), OPT_BOOL('p', "patch", &patch_mode, N_("stash in patch mode")), OPT__QUIET(&quiet, N_("quiet mode")), @@ -1694,7 +1776,7 @@ static int save_stash(int argc, const char **argv, const char *prefix) memset(&ps, 0, sizeof(ps)); ret = do_push_stash(&ps, stash_msg, quiet, keep_index, - patch_mode, include_untracked); + patch_mode, include_untracked, only_staged); strbuf_release(&stash_msg_buf); return ret; @@ -1712,14 +1794,12 @@ int cmd_stash(int argc, const char **argv, const char *prefix) git_config(git_stash_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); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + index_file = get_index_file(); strbuf_addf(&stash_index_path, "%s.stash.%" PRIuMAX, index_file, (uintmax_t)pid); @@ -1749,8 +1829,8 @@ int cmd_stash(int argc, const char **argv, const char *prefix) 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); + usage_msg_optf(_("unknown subcommand: %s"), + git_stash_usage, options, argv[0]); /* Assume 'stash push' */ strvec_push(&args, "push"); diff --git a/builtin/stripspace.c b/builtin/stripspace.c index be33eb83c1..1e34cf2beb 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c @@ -15,8 +15,8 @@ static void comment_lines(struct strbuf *buf) } static const char * const stripspace_usage[] = { - N_("git stripspace [-s | --strip-comments]"), - N_("git stripspace [-c | --comment-lines]"), + "git stripspace [-s | --strip-comments]", + "git stripspace [-c | --comment-lines]", NULL }; diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index e630f0c730..5c77dfcffe 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -20,6 +20,8 @@ #include "diff.h" #include "object-store.h" #include "advice.h" +#include "branch.h" +#include "list-objects-filter-options.h" #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -29,11 +31,13 @@ typedef void (*each_submodule_fn)(const struct cache_entry *list_item, void *cb_data); -static char *get_default_remote(void) +static char *repo_get_default_remote(struct repository *repo) { char *dest = NULL, *ret; struct strbuf sb = STRBUF_INIT; - const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL); + struct ref_store *store = get_main_ref_store(repo); + const char *refname = refs_resolve_ref_unsafe(store, "HEAD", 0, NULL, + NULL); if (!refname) die(_("No such ref: %s"), "HEAD"); @@ -46,7 +50,7 @@ static char *get_default_remote(void) die(_("Expecting a full ref name, got %s"), refname); strbuf_addf(&sb, "branch.%s.remote", refname); - if (git_config_get_string(sb.buf, &dest)) + if (repo_config_get_string(repo, sb.buf, &dest)) ret = xstrdup("origin"); else ret = dest; @@ -55,148 +59,17 @@ static char *get_default_remote(void) return ret; } -static int print_default_remote(int argc, const char **argv, const char *prefix) +static char *get_default_remote_submodule(const char *module_path) { - char *remote; - - if (argc != 1) - die(_("submodule--helper print-default-remote takes no arguments")); - - remote = get_default_remote(); - if (remote) - printf("%s\n", remote); - - free(remote); - return 0; -} - -static int starts_with_dot_slash(const char *str) -{ - return str[0] == '.' && is_dir_sep(str[1]); -} + struct repository subrepo; -static int starts_with_dot_dot_slash(const char *str) -{ - return str[0] == '.' && str[1] == '.' && is_dir_sep(str[2]); + repo_submodule_init(&subrepo, the_repository, module_path, null_oid()); + return repo_get_default_remote(&subrepo); } -/* - * Returns 1 if it was the last chop before ':'. - */ -static int chop_last_dir(char **remoteurl, int is_relative) +static char *get_default_remote(void) { - char *rfind = find_last_dir_sep(*remoteurl); - if (rfind) { - *rfind = '\0'; - return 0; - } - - rfind = strrchr(*remoteurl, ':'); - if (rfind) { - *rfind = '\0'; - return 1; - } - - if (is_relative || !strcmp(".", *remoteurl)) - die(_("cannot strip one component off url '%s'"), - *remoteurl); - - free(*remoteurl); - *remoteurl = xstrdup("."); - return 0; -} - -/* - * The `url` argument is the URL that navigates to the submodule origin - * repo. When relative, this URL is relative to the superproject origin - * URL repo. The `up_path` argument, if specified, is the relative - * path that navigates from the submodule working tree to the superproject - * working tree. Returns the origin URL of the submodule. - * - * Return either an absolute URL or filesystem path (if the superproject - * origin URL is an absolute URL or filesystem path, respectively) or a - * relative file system path (if the superproject origin URL is a relative - * file system path). - * - * When the output is a relative file system path, the path is either - * relative to the submodule working tree, if up_path is specified, or to - * the superproject working tree otherwise. - * - * NEEDSWORK: This works incorrectly on the domain and protocol part. - * remote_url url outcome expectation - * http://a.com/b ../c http://a.com/c as is - * http://a.com/b/ ../c http://a.com/c same as previous line, but - * ignore trailing slash in url - * http://a.com/b ../../c http://c error out - * http://a.com/b ../../../c http:/c error out - * http://a.com/b ../../../../c http:c error out - * http://a.com/b ../../../../../c .:c error out - * NEEDSWORK: Given how chop_last_dir() works, this function is broken - * when a local part has a colon in its path component, too. - */ -static char *relative_url(const char *remote_url, - const char *url, - const char *up_path) -{ - int is_relative = 0; - int colonsep = 0; - char *out; - char *remoteurl = xstrdup(remote_url); - struct strbuf sb = STRBUF_INIT; - size_t len = strlen(remoteurl); - - if (is_dir_sep(remoteurl[len-1])) - remoteurl[len-1] = '\0'; - - if (!url_is_local_not_ssh(remoteurl) || is_absolute_path(remoteurl)) - is_relative = 0; - else { - is_relative = 1; - /* - * Prepend a './' to ensure all relative - * remoteurls start with './' or '../' - */ - if (!starts_with_dot_slash(remoteurl) && - !starts_with_dot_dot_slash(remoteurl)) { - strbuf_reset(&sb); - strbuf_addf(&sb, "./%s", remoteurl); - free(remoteurl); - remoteurl = strbuf_detach(&sb, NULL); - } - } - /* - * When the url starts with '../', remove that and the - * last directory in remoteurl. - */ - while (url) { - if (starts_with_dot_dot_slash(url)) { - url += 3; - colonsep |= chop_last_dir(&remoteurl, is_relative); - } else if (starts_with_dot_slash(url)) - url += 2; - else - break; - } - strbuf_reset(&sb); - strbuf_addf(&sb, "%s%s%s", remoteurl, colonsep ? ":" : "/", url); - if (ends_with(url, "/")) - strbuf_setlen(&sb, sb.len - 1); - free(remoteurl); - - if (starts_with_dot_slash(sb.buf)) - out = xstrdup(sb.buf + 2); - else - out = xstrdup(sb.buf); - - if (!up_path || !is_relative) { - strbuf_release(&sb); - return out; - } - - strbuf_reset(&sb); - strbuf_addf(&sb, "%s%s", up_path, out); - free(out); - return strbuf_detach(&sb, NULL); + return repo_get_default_remote(the_repository); } static char *resolve_relative_url(const char *rel_url, const char *up_path, int quiet) @@ -245,11 +118,10 @@ static int resolve_relative_url_test(int argc, const char **argv, const char *pr return 0; } -/* the result should be freed by the caller. */ -static char *get_submodule_displaypath(const char *path, const char *prefix) +static char *do_get_submodule_displaypath(const char *path, + const char *prefix, + const char *super_prefix) { - const char *super_prefix = get_super_prefix(); - if (prefix && super_prefix) { BUG("cannot have prefix '%s' and superprefix '%s'", prefix, super_prefix); @@ -265,6 +137,13 @@ static char *get_submodule_displaypath(const char *path, const char *prefix) } } +/* the result should be freed by the caller. */ +static char *get_submodule_displaypath(const char *path, const char *prefix) +{ + const char *super_prefix = get_super_prefix(); + return do_get_submodule_displaypath(path, prefix, super_prefix); +} + static char *compute_rev_name(const char *sub_path, const char* object_id) { struct strbuf sb = STRBUF_INIT; @@ -284,7 +163,7 @@ static char *compute_rev_name(const char *sub_path, const char* object_id) for (d = describe_argv; *d; d++) { struct child_process cp = CHILD_PROCESS_INIT; - prepare_submodule_repo_env(&cp.env_array); + prepare_submodule_repo_env(&cp.env); cp.dir = sub_path; cp.git_cmd = 1; cp.no_stderr = 1; @@ -471,7 +350,7 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item, if (!is_submodule_populated_gently(path, NULL)) goto cleanup; - prepare_submodule_repo_env(&cp.env_array); + prepare_submodule_repo_env(&cp.env); /* * For the purpose of executing <command> in the submodule, @@ -491,12 +370,12 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item, char *toplevel = xgetcwd(); struct strbuf sb = STRBUF_INIT; - 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", + strvec_pushf(&cp.env, "name=%s", sub->name); + strvec_pushf(&cp.env, "sm_path=%s", path); + strvec_pushf(&cp.env, "displaypath=%s", displaypath); + strvec_pushf(&cp.env, "sha1=%s", oid_to_hex(ce_oid)); - strvec_pushf(&cp.env_array, "toplevel=%s", toplevel); + strvec_pushf(&cp.env, "toplevel=%s", toplevel); /* * Since the path variable was accessible from the script @@ -505,7 +384,7 @@ 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 strvec and not via env_array. + * path via the args strvec and not via env. */ sq_quote_buf(&sb, path); strvec_pushf(&cp.args, "path=%s; %s", @@ -528,7 +407,7 @@ static void runcommand_in_submodule_cb(const struct cache_entry *list_item, cpr.git_cmd = 1; cpr.dir = path; - prepare_submodule_repo_env(&cpr.env_array); + prepare_submodule_repo_env(&cpr.env); strvec_pushl(&cpr.args, "--super-prefix", NULL); strvec_pushf(&cpr.args, "%s/", displaypath); @@ -584,20 +463,36 @@ static int module_foreach(int argc, const char **argv, const char *prefix) return 0; } +static int starts_with_dot_slash(const char *const path) +{ + return path_match_flags(path, PATH_MATCH_STARTS_WITH_DOT_SLASH | + PATH_MATCH_XPLATFORM); +} + +static int starts_with_dot_dot_slash(const char *const path) +{ + return path_match_flags(path, PATH_MATCH_STARTS_WITH_DOT_DOT_SLASH | + PATH_MATCH_XPLATFORM); +} + struct init_cb { const char *prefix; + const char *superprefix; unsigned int flags; }; #define INIT_CB_INIT { 0 } static void init_submodule(const char *path, const char *prefix, - unsigned int flags) + const char *superprefix, unsigned int flags) { const struct submodule *sub; struct strbuf sb = STRBUF_INIT; char *upd = NULL, *url = NULL, *displaypath; - displaypath = get_submodule_displaypath(path, prefix); + /* try superprefix from the environment, if it is not passed explicitly */ + if (!superprefix) + superprefix = get_super_prefix(); + displaypath = do_get_submodule_displaypath(path, prefix, superprefix); sub = submodule_from_path(the_repository, null_oid(), path); @@ -671,7 +566,7 @@ static void init_submodule(const char *path, const char *prefix, static void init_submodule_cb(const struct cache_entry *list_item, void *cb_data) { struct init_cb *info = cb_data; - init_submodule(list_item->name, info->prefix, info->flags); + init_submodule(list_item->name, info->prefix, info->superprefix, info->flags); } static int module_init(int argc, const char **argv, const char *prefix) @@ -754,7 +649,7 @@ static void status_submodule(const char *path, const struct object_id *ce_oid, { char *displaypath; struct strvec diff_files_args = STRVEC_INIT; - struct rev_info rev; + struct rev_info rev = REV_INFO_INIT; int diff_files_result; struct strbuf buf = STRBUF_INIT; const char *git_dir; @@ -821,7 +716,7 @@ static void status_submodule(const char *path, const struct object_id *ce_oid, cpr.git_cmd = 1; cpr.dir = path; - prepare_submodule_repo_env(&cpr.env_array); + prepare_submodule_repo_env(&cpr.env); strvec_push(&cpr.args, "--super-prefix"); strvec_pushf(&cpr.args, "%s/", displaypath); @@ -841,6 +736,7 @@ static void status_submodule(const char *path, const struct object_id *ce_oid, cleanup: strvec_clear(&diff_files_args); free(displaypath); + release_revisions(&rev); } static void status_submodule_cb(const struct cache_entry *list_item, @@ -943,7 +839,7 @@ static char *verify_submodule_committish(const char *sm_path, cp_rev_parse.git_cmd = 1; cp_rev_parse.dir = sm_path; - prepare_submodule_repo_env(&cp_rev_parse.env_array); + prepare_submodule_repo_env(&cp_rev_parse.env); 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, "--"); @@ -984,7 +880,7 @@ static void print_submodule_summary(struct summary_cb *info, char *errmsg, cp_log.git_cmd = 1; cp_log.dir = p->sm_path; - prepare_submodule_repo_env(&cp_log.env_array); + prepare_submodule_repo_env(&cp_log.env); strvec_pushl(&cp_log.args, "log", NULL); if (S_ISGITLINK(p->mod_src) && S_ISGITLINK(p->mod_dst)) { @@ -1101,7 +997,7 @@ static void generate_submodule_summary(struct summary_cb *info, cp_rev_list.git_cmd = 1; cp_rev_list.dir = p->sm_path; - prepare_submodule_repo_env(&cp_rev_list.env_array); + prepare_submodule_repo_env(&cp_rev_list.env); if (!capture_command(&cp_rev_list, &sb_rev_list, 0)) total_commits = atoi(sb_rev_list.buf); @@ -1219,6 +1115,7 @@ static int compute_summary_module_list(struct object_id *head_oid, struct strvec diff_args = STRVEC_INIT; struct rev_info rev; struct module_cb_list list = MODULE_CB_LIST_INIT; + int ret = 0; strvec_push(&diff_args, get_diff_cmd(diff_cmd)); if (info->cached) @@ -1244,11 +1141,13 @@ static int compute_summary_module_list(struct object_id *head_oid, setup_work_tree(); if (read_cache_preload(&rev.diffopt.pathspec) < 0) { perror("read_cache_preload"); - return -1; + ret = -1; + goto cleanup; } } else if (read_cache() < 0) { perror("read_cache"); - return -1; + ret = -1; + goto cleanup; } if (diff_cmd == DIFF_INDEX) @@ -1256,8 +1155,10 @@ static int compute_summary_module_list(struct object_id *head_oid, else run_diff_files(&rev, 0); prepare_submodule_summary(info, &list); +cleanup: strvec_clear(&diff_args); - return 0; + release_revisions(&rev); + return ret; } static int module_summary(int argc, const char **argv, const char *prefix) @@ -1313,7 +1214,7 @@ static int module_summary(int argc, const char **argv, const char *prefix) if (files) { if (cached) - die(_("--cached and --files are mutually exclusive")); + die(_("options '%s' and '%s' cannot be used together"), "--cached", "--files"); diff_cmd = DIFF_FILES; } @@ -1341,9 +1242,8 @@ static void sync_submodule(const char *path, const char *prefix, { const struct submodule *sub; char *remote_key = NULL; - char *sub_origin_url, *super_config_url, *displaypath; + char *sub_origin_url, *super_config_url, *displaypath, *default_remote; struct strbuf sb = STRBUF_INIT; - struct child_process cp = CHILD_PROCESS_INIT; char *sub_config_path = NULL; if (!is_submodule_active(the_repository, path)) @@ -1382,21 +1282,15 @@ static void sync_submodule(const char *path, const char *prefix, if (!is_submodule_populated_gently(path, NULL)) goto cleanup; - prepare_submodule_repo_env(&cp.env_array); - cp.git_cmd = 1; - cp.dir = path; - strvec_pushl(&cp.args, "submodule--helper", - "print-default-remote", NULL); - strbuf_reset(&sb); - if (capture_command(&cp, &sb, 0)) + default_remote = get_default_remote_submodule(path); + if (!default_remote) die(_("failed to get the default remote for submodule '%s'"), path); - strbuf_strip_suffix(&sb, "\n"); - remote_key = xstrfmt("remote.%s.url", sb.buf); + remote_key = xstrfmt("remote.%s.url", default_remote); + free(default_remote); - strbuf_reset(&sb); submodule_to_gitdir(&sb, path); strbuf_addstr(&sb, "/config"); @@ -1409,7 +1303,7 @@ static void sync_submodule(const char *path, const char *prefix, cpr.git_cmd = 1; cpr.dir = path; - prepare_submodule_repo_env(&cpr.env_array); + prepare_submodule_repo_env(&cpr.env); strvec_push(&cpr.args, "--super-prefix"); strvec_pushf(&cpr.args, "%s/", displaypath); @@ -1503,16 +1397,17 @@ static void deinit_submodule(const char *path, const char *prefix, struct strbuf sb_rm = STRBUF_INIT; const char *format; - /* - * protect submodules containing a .git directory - * NEEDSWORK: instead of dying, automatically call - * absorbgitdirs and (possibly) warn. - */ - if (is_directory(sub_git_dir)) - die(_("Submodule work tree '%s' contains a .git " - "directory (use 'rm -rf' if you really want " - "to remove it including all of its history)"), - displaypath); + if (is_directory(sub_git_dir)) { + if (!(flags & OPT_QUIET)) + warning(_("Submodule work tree '%s' contains a .git " + "directory. This will be replaced with a " + ".git file by using absorbgitdirs."), + displaypath); + + absorb_git_dir_into_superproject(path, + ABSORB_GITDIR_RECURSE_SUBMODULES); + + } if (!(flags & OPT_FORCE)) { struct child_process cp_rm = CHILD_PROCESS_INIT; @@ -1629,6 +1524,7 @@ struct module_clone_data { const char *name; const char *url; const char *depth; + struct list_objects_filter_options *filter_options; struct string_list reference; unsigned int quiet: 1; unsigned int progress: 1; @@ -1636,7 +1532,10 @@ struct module_clone_data { unsigned int require_init: 1; int single_branch; }; -#define MODULE_CLONE_DATA_INIT { .reference = STRING_LIST_INIT_NODUP, .single_branch = -1 } +#define MODULE_CLONE_DATA_INIT { \ + .reference = STRING_LIST_INIT_NODUP, \ + .single_branch = -1, \ +} struct submodule_alternate_setup { const char *submodule_name; @@ -1795,6 +1694,10 @@ static int clone_submodule(struct module_clone_data *clone_data) strvec_push(&cp.args, "--dissociate"); if (sm_gitdir && *sm_gitdir) strvec_pushl(&cp.args, "--separate-git-dir", sm_gitdir, NULL); + if (clone_data->filter_options && clone_data->filter_options->choice) + strvec_pushf(&cp.args, "--filter=%s", + expand_list_objects_filter_spec( + clone_data->filter_options)); if (clone_data->single_branch >= 0) strvec_push(&cp.args, clone_data->single_branch ? "--single-branch" : @@ -1805,7 +1708,7 @@ static int clone_submodule(struct module_clone_data *clone_data) strvec_push(&cp.args, clone_data->path); cp.git_cmd = 1; - prepare_submodule_repo_env(&cp.env_array); + prepare_submodule_repo_env(&cp.env); cp.no_stdin = 1; if(run_command(&cp)) @@ -1851,6 +1754,7 @@ static int module_clone(int argc, const char **argv, const char *prefix) { int dissociate = 0, quiet = 0, progress = 0, require_init = 0; struct module_clone_data clone_data = MODULE_CLONE_DATA_INIT; + struct list_objects_filter_options filter_options; struct option module_clone_options[] = { OPT_STRING(0, "prefix", &clone_data.prefix, @@ -1873,24 +1777,26 @@ static int module_clone(int argc, const char **argv, const char *prefix) OPT_STRING(0, "depth", &clone_data.depth, N_("string"), N_("depth for shallow clones")), - OPT__QUIET(&quiet, "Suppress output for cloning a submodule"), + 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", &clone_data.single_branch, N_("clone only one branch, HEAD or --branch")), + OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), 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] " + "[--single-branch] [--filter <filter-spec>] " "--url <url> --path <path>"), NULL }; + memset(&filter_options, 0, sizeof(filter_options)); argc = parse_options(argc, argv, prefix, module_clone_options, git_submodule_helper_usage, 0); @@ -1898,12 +1804,14 @@ static int module_clone(int argc, const char **argv, const char *prefix) clone_data.quiet = !!quiet; clone_data.progress = !!progress; clone_data.require_init = !!require_init; + clone_data.filter_options = &filter_options; if (argc || !clone_data.url || !clone_data.path || !*(clone_data.path)) usage_with_options(git_submodule_helper_usage, module_clone_options); clone_submodule(&clone_data); + list_objects_filter_release(&filter_options); return 0; } @@ -1944,29 +1852,6 @@ static void determine_submodule_update_strategy(struct repository *r, free(key); } -static int module_update_module_mode(int argc, const char **argv, const char *prefix) -{ - const char *path, *update = NULL; - int just_cloned; - struct submodule_update_strategy update_strategy = { .type = SM_UPDATE_CHECKOUT }; - - if (argc < 3 || argc > 4) - die("submodule--helper update-module-clone expects <just-cloned> <path> [<update>]"); - - just_cloned = git_config_int("just_cloned", argv[1]); - path = argv[2]; - - if (argc == 4) - update = argv[3]; - - determine_submodule_update_strategy(the_repository, - just_cloned, path, update, - &update_strategy); - fputs(submodule_strategy_to_string(&update_strategy), stdout); - - return 0; -} - struct update_clone_data { const struct submodule *sub; struct object_id oid; @@ -1974,27 +1859,13 @@ struct update_clone_data { }; struct submodule_update_clone { - /* index into 'list', the list of submodules to look into for cloning */ + /* index into 'update_data.list', the list of submodules to look into for cloning */ int current; - struct module_list list; - unsigned warn_if_uninitialized : 1; - - /* update parameter passed via commandline */ - struct submodule_update_strategy update; /* configuration parameters which are passed on to the children */ - int progress; - int quiet; - 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; + struct update_data *update_data; - /* to be consumed by git-submodule.sh */ + /* to be consumed by update_submodule() */ struct update_clone_data *update_clone; int update_clone_nr; int update_clone_alloc; @@ -2004,32 +1875,47 @@ struct submodule_update_clone { /* failed clones to be retried again */ const struct cache_entry **failed_clones; int failed_clones_nr, failed_clones_alloc; - - int max_jobs; }; -#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, \ -} +#define SUBMODULE_UPDATE_CLONE_INIT { 0 } struct update_data { + const char *prefix; const char *recursive_prefix; - const char *sm_path; const char *displaypath; - struct object_id oid; + const char *update_default; struct object_id suboid; + struct string_list references; struct submodule_update_strategy update_strategy; + struct list_objects_filter_options *filter_options; + struct module_list list; int depth; - unsigned int force: 1; - unsigned int quiet: 1; - unsigned int nofetch: 1; - unsigned int just_cloned: 1; + int max_jobs; + int single_branch; + int recommend_shallow; + unsigned int require_init; + unsigned int force; + unsigned int quiet; + unsigned int nofetch; + unsigned int remote; + unsigned int progress; + unsigned int dissociate; + unsigned int init; + unsigned int warn_if_uninitialized; + unsigned int recursive; + + /* copied over from update_clone_data */ + struct object_id oid; + unsigned int just_cloned; + const char *sm_path; }; -#define UPDATE_DATA_INIT { .update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT } +#define UPDATE_DATA_INIT { \ + .update_strategy = SUBMODULE_UPDATE_STRATEGY_INIT, \ + .list = MODULE_LIST_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, struct strbuf *out, const char *displaypath) @@ -2038,7 +1924,7 @@ static void next_submodule_warn_missing(struct submodule_update_clone *suc, * Only mention uninitialized submodules when their * paths have been specified. */ - if (suc->warn_if_uninitialized) { + if (suc->update_data->warn_if_uninitialized) { strbuf_addf(out, _("Submodule path '%s' not initialized"), displaypath); @@ -2070,8 +1956,8 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, int need_free_url = 0; if (ce_stage(ce)) { - if (suc->recursive_prefix) - strbuf_addf(&sb, "%s/%s", suc->recursive_prefix, ce->name); + if (suc->update_data->recursive_prefix) + strbuf_addf(&sb, "%s/%s", suc->update_data->recursive_prefix, ce->name); else strbuf_addstr(&sb, ce->name); strbuf_addf(out, _("Skipping unmerged submodule %s"), sb.buf); @@ -2081,8 +1967,8 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, sub = submodule_from_path(the_repository, null_oid(), ce->name); - if (suc->recursive_prefix) - displaypath = relative_path(suc->recursive_prefix, + if (suc->update_data->recursive_prefix) + displaypath = relative_path(suc->update_data->recursive_prefix, ce->name, &displaypath_sb); else displaypath = ce->name; @@ -2100,8 +1986,8 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, } free(key); - if (suc->update.type == SM_UPDATE_NONE - || (suc->update.type == SM_UPDATE_UNSPECIFIED + if (suc->update_data->update_strategy.type == SM_UPDATE_NONE + || (suc->update_data->update_strategy.type == SM_UPDATE_UNSPECIFIED && update_type == SM_UPDATE_NONE)) { strbuf_addf(out, _("Skipping submodule '%s'"), displaypath); strbuf_addch(out, '\n'); @@ -2145,30 +2031,33 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, child->err = -1; strvec_push(&child->args, "submodule--helper"); strvec_push(&child->args, "clone"); - if (suc->progress) + if (suc->update_data->progress) strvec_push(&child->args, "--progress"); - if (suc->quiet) + if (suc->update_data->quiet) strvec_push(&child->args, "--quiet"); - if (suc->prefix) - strvec_pushl(&child->args, "--prefix", suc->prefix, NULL); - if (suc->recommend_shallow && sub->recommend_shallow == 1) + if (suc->update_data->prefix) + strvec_pushl(&child->args, "--prefix", suc->update_data->prefix, NULL); + if (suc->update_data->recommend_shallow && sub->recommend_shallow == 1) strvec_push(&child->args, "--depth=1"); - if (suc->require_init) + else if (suc->update_data->depth) + strvec_pushf(&child->args, "--depth=%d", suc->update_data->depth); + if (suc->update_data->filter_options && suc->update_data->filter_options->choice) + strvec_pushf(&child->args, "--filter=%s", + expand_list_objects_filter_spec(suc->update_data->filter_options)); + if (suc->update_data->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) { + if (suc->update_data->references.nr) { struct string_list_item *item; - for_each_string_list_item(item, &suc->references) + for_each_string_list_item(item, &suc->update_data->references) strvec_pushl(&child->args, "--reference", item->string, NULL); } - if (suc->dissociate) + if (suc->update_data->dissociate) strvec_push(&child->args, "--dissociate"); - if (suc->depth) - strvec_push(&child->args, suc->depth); - if (suc->single_branch >= 0) - strvec_push(&child->args, suc->single_branch ? + if (suc->update_data->single_branch >= 0) + strvec_push(&child->args, suc->update_data->single_branch ? "--single-branch" : "--no-single-branch"); @@ -2190,8 +2079,8 @@ static int update_clone_get_next_task(struct child_process *child, const struct cache_entry *ce; int index; - for (; suc->current < suc->list.nr; suc->current++) { - ce = suc->list.entries[suc->current]; + for (; suc->current < suc->update_data->list.nr; suc->current++) { + ce = suc->update_data->list.entries[suc->current]; if (prepare_to_clone_next_submodule(ce, child, suc, err)) { int *p = xmalloc(sizeof(*p)); *p = suc->current; @@ -2206,7 +2095,7 @@ static int update_clone_get_next_task(struct child_process *child, * stragglers again, which we can imagine as an extension of the * entry list. */ - index = suc->current - suc->list.nr; + index = suc->current - suc->update_data->list.nr; if (index < suc->failed_clones_nr) { int *p; ce = suc->failed_clones[index]; @@ -2251,8 +2140,8 @@ static int update_clone_task_finished(int result, if (!result) return 0; - if (idx < suc->list.nr) { - ce = suc->list.entries[idx]; + if (idx < suc->update_data->list.nr) { + ce = suc->update_data->list.entries[idx]; strbuf_addf(err, _("Failed to clone '%s'. Retry scheduled"), ce->name); strbuf_addch(err, '\n'); @@ -2262,7 +2151,7 @@ static int update_clone_task_finished(int result, suc->failed_clones[suc->failed_clones_nr++] = ce; return 0; } else { - idx -= suc->list.nr; + idx -= suc->update_data->list.nr; ce = suc->failed_clones[idx]; strbuf_addf(err, _("Failed to clone '%s' a second time, aborting"), ce->name); @@ -2294,7 +2183,7 @@ static int is_tip_reachable(const char *path, struct object_id *oid) cp.no_stderr = 1; strvec_pushl(&cp.args, "rev-list", "-n", "1", hex, "--not", "--all", NULL); - prepare_submodule_repo_env(&cp.env_array); + prepare_submodule_repo_env(&cp.env); if (capture_command(&cp, &rev, GIT_MAX_HEXSZ + 1) || rev.len) return 0; @@ -2306,7 +2195,7 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, str { struct child_process cp = CHILD_PROCESS_INIT; - prepare_submodule_repo_env(&cp.env_array); + prepare_submodule_repo_env(&cp.env); cp.git_cmd = 1; cp.dir = xstrdup(module_path); @@ -2326,83 +2215,76 @@ static int fetch_in_submodule(const char *module_path, int depth, int quiet, str static int run_update_command(struct update_data *ud, int subforce) { - struct strvec args = STRVEC_INIT; - struct strvec child_env = STRVEC_INIT; + struct child_process cp = CHILD_PROCESS_INIT; char *oid = oid_to_hex(&ud->oid); int must_die_on_failure = 0; - int git_cmd; switch (ud->update_strategy.type) { case SM_UPDATE_CHECKOUT: - git_cmd = 1; - strvec_pushl(&args, "checkout", "-q", NULL); + cp.git_cmd = 1; + strvec_pushl(&cp.args, "checkout", "-q", NULL); if (subforce) - strvec_push(&args, "-f"); + strvec_push(&cp.args, "-f"); break; case SM_UPDATE_REBASE: - git_cmd = 1; - strvec_push(&args, "rebase"); + cp.git_cmd = 1; + strvec_push(&cp.args, "rebase"); if (ud->quiet) - strvec_push(&args, "--quiet"); + strvec_push(&cp.args, "--quiet"); must_die_on_failure = 1; break; case SM_UPDATE_MERGE: - git_cmd = 1; - strvec_push(&args, "merge"); + cp.git_cmd = 1; + strvec_push(&cp.args, "merge"); if (ud->quiet) - strvec_push(&args, "--quiet"); + strvec_push(&cp.args, "--quiet"); must_die_on_failure = 1; break; case SM_UPDATE_COMMAND: - git_cmd = 0; - strvec_push(&args, ud->update_strategy.command); + cp.use_shell = 1; + strvec_push(&cp.args, ud->update_strategy.command); must_die_on_failure = 1; break; default: BUG("unexpected update strategy type: %s", submodule_strategy_to_string(&ud->update_strategy)); } - strvec_push(&args, oid); + strvec_push(&cp.args, oid); - prepare_submodule_repo_env(&child_env); - if (run_command_v_opt_cd_env(args.v, git_cmd ? RUN_GIT_CMD : RUN_USING_SHELL, - ud->sm_path, child_env.v)) { + cp.dir = xstrdup(ud->sm_path); + prepare_submodule_repo_env(&cp.env); + if (run_command(&cp)) { switch (ud->update_strategy.type) { case SM_UPDATE_CHECKOUT: - printf(_("Unable to checkout '%s' in submodule path '%s'"), - oid, ud->displaypath); + die_message(_("Unable to checkout '%s' in submodule path '%s'"), + oid, ud->displaypath); break; case SM_UPDATE_REBASE: - printf(_("Unable to rebase '%s' in submodule path '%s'"), - oid, ud->displaypath); + die_message(_("Unable to rebase '%s' in submodule path '%s'"), + oid, ud->displaypath); break; case SM_UPDATE_MERGE: - printf(_("Unable to merge '%s' in submodule path '%s'"), - oid, ud->displaypath); + die_message(_("Unable to merge '%s' in submodule path '%s'"), + oid, ud->displaypath); break; case SM_UPDATE_COMMAND: - printf(_("Execution of '%s %s' failed in submodule path '%s'"), - ud->update_strategy.command, oid, ud->displaypath); + die_message(_("Execution of '%s %s' failed in submodule path '%s'"), + ud->update_strategy.command, oid, ud->displaypath); break; default: BUG("unexpected update strategy type: %s", submodule_strategy_to_string(&ud->update_strategy)); } - /* - * NEEDSWORK: We are currently printing to stdout with error - * return so that the shell caller handles the error output - * properly. Once we start handling the error messages within - * C, we should use die() instead. - */ if (must_die_on_failure) - return 2; - /* - * This signifies to the caller in shell that the command - * failed without dying - */ + exit(128); + + /* the command failed, but update must continue */ return 1; } + if (ud->quiet) + return 0; + switch (ud->update_strategy.type) { case SM_UPDATE_CHECKOUT: printf(_("Submodule path '%s': checked out '%s'\n"), @@ -2428,7 +2310,7 @@ static int run_update_command(struct update_data *ud, int subforce) return 0; } -static int do_run_update_procedure(struct update_data *ud) +static int run_update_procedure(struct update_data *ud) { int subforce = is_null_oid(&ud->suboid) || ud->force; @@ -2458,21 +2340,211 @@ static int do_run_update_procedure(struct update_data *ud) return run_update_command(ud, subforce); } -static void update_submodule(struct update_clone_data *ucd) +static const char *remote_submodule_branch(const char *path) { - fprintf(stdout, "dummy %s %d\t%s\n", - oid_to_hex(&ucd->oid), - ucd->just_cloned, - ucd->sub->path); + const struct submodule *sub; + const char *branch = NULL; + char *key; + + sub = submodule_from_path(the_repository, null_oid(), path); + if (!sub) + return NULL; + + key = xstrfmt("submodule.%s.branch", sub->name); + if (repo_config_get_string_tmp(the_repository, key, &branch)) + branch = sub->branch; + free(key); + + if (!branch) + return "HEAD"; + + if (!strcmp(branch, ".")) { + const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL); + + if (!refname) + die(_("No such ref: %s"), "HEAD"); + + /* detached HEAD */ + if (!strcmp(refname, "HEAD")) + die(_("Submodule (%s) branch configured to inherit " + "branch from superproject, but the superproject " + "is not on any branch"), sub->name); + + if (!skip_prefix(refname, "refs/heads/", &refname)) + die(_("Expecting a full ref name, got %s"), refname); + return refname; + } + + return branch; } -static int update_submodules(struct submodule_update_clone *suc) +static void ensure_core_worktree(const char *path) { - int i; + const char *cw; + struct repository subrepo; + + if (repo_submodule_init(&subrepo, the_repository, path, null_oid())) + die(_("could not get a repository handle for submodule '%s'"), path); + + if (!repo_config_get_string_tmp(&subrepo, "core.worktree", &cw)) { + char *cfg_file, *abs_path; + const char *rel_path; + struct strbuf sb = STRBUF_INIT; - run_processes_parallel_tr2(suc->max_jobs, update_clone_get_next_task, + cfg_file = repo_git_path(&subrepo, "config"); + + abs_path = absolute_pathdup(path); + rel_path = relative_path(abs_path, subrepo.gitdir, &sb); + + git_config_set_in_file(cfg_file, "core.worktree", rel_path); + + free(cfg_file); + free(abs_path); + strbuf_release(&sb); + } +} + +static void update_data_to_args(struct update_data *update_data, struct strvec *args) +{ + strvec_pushl(args, "submodule--helper", "update", "--recursive", NULL); + strvec_pushf(args, "--jobs=%d", update_data->max_jobs); + if (update_data->recursive_prefix) + strvec_pushl(args, "--recursive-prefix", + update_data->recursive_prefix, NULL); + if (update_data->quiet) + strvec_push(args, "--quiet"); + if (update_data->force) + strvec_push(args, "--force"); + if (update_data->init) + strvec_push(args, "--init"); + if (update_data->remote) + strvec_push(args, "--remote"); + if (update_data->nofetch) + strvec_push(args, "--no-fetch"); + if (update_data->dissociate) + strvec_push(args, "--dissociate"); + if (update_data->progress) + strvec_push(args, "--progress"); + if (update_data->require_init) + strvec_push(args, "--require-init"); + if (update_data->depth) + strvec_pushf(args, "--depth=%d", update_data->depth); + if (update_data->update_default) + strvec_pushl(args, "--update", update_data->update_default, NULL); + if (update_data->references.nr) { + struct string_list_item *item; + for_each_string_list_item(item, &update_data->references) + strvec_pushl(args, "--reference", item->string, NULL); + } + if (update_data->filter_options && update_data->filter_options->choice) + strvec_pushf(args, "--filter=%s", + expand_list_objects_filter_spec( + update_data->filter_options)); + if (update_data->recommend_shallow == 0) + strvec_push(args, "--no-recommend-shallow"); + else if (update_data->recommend_shallow == 1) + strvec_push(args, "--recommend-shallow"); + if (update_data->single_branch >= 0) + strvec_push(args, update_data->single_branch ? + "--single-branch" : + "--no-single-branch"); +} + +static int update_submodule(struct update_data *update_data) +{ + char *prefixed_path; + + ensure_core_worktree(update_data->sm_path); + + if (update_data->recursive_prefix) + prefixed_path = xstrfmt("%s%s", update_data->recursive_prefix, + update_data->sm_path); + else + prefixed_path = xstrdup(update_data->sm_path); + + update_data->displaypath = get_submodule_displaypath(prefixed_path, + update_data->prefix); + free(prefixed_path); + + determine_submodule_update_strategy(the_repository, update_data->just_cloned, + update_data->sm_path, update_data->update_default, + &update_data->update_strategy); + + if (update_data->just_cloned) + oidcpy(&update_data->suboid, null_oid()); + else if (resolve_gitlink_ref(update_data->sm_path, "HEAD", &update_data->suboid)) + die(_("Unable to find current revision in submodule path '%s'"), + update_data->displaypath); + + if (update_data->remote) { + char *remote_name = get_default_remote_submodule(update_data->sm_path); + const char *branch = remote_submodule_branch(update_data->sm_path); + char *remote_ref = xstrfmt("refs/remotes/%s/%s", remote_name, branch); + + if (!update_data->nofetch) { + if (fetch_in_submodule(update_data->sm_path, update_data->depth, + 0, NULL)) + die(_("Unable to fetch in submodule path '%s'"), + update_data->sm_path); + } + + if (resolve_gitlink_ref(update_data->sm_path, remote_ref, &update_data->oid)) + die(_("Unable to find %s revision in submodule path '%s'"), + remote_ref, update_data->sm_path); + + free(remote_ref); + } + + if (!oideq(&update_data->oid, &update_data->suboid) || update_data->force) + if (run_update_procedure(update_data)) + return 1; + + if (update_data->recursive) { + struct child_process cp = CHILD_PROCESS_INIT; + struct update_data next = *update_data; + int res; + + if (update_data->recursive_prefix) + prefixed_path = xstrfmt("%s%s/", update_data->recursive_prefix, + update_data->sm_path); + else + prefixed_path = xstrfmt("%s/", update_data->sm_path); + + next.recursive_prefix = get_submodule_displaypath(prefixed_path, + update_data->prefix); + next.prefix = NULL; + oidcpy(&next.oid, null_oid()); + oidcpy(&next.suboid, null_oid()); + + cp.dir = update_data->sm_path; + cp.git_cmd = 1; + prepare_submodule_repo_env(&cp.env); + update_data_to_args(&next, &cp.args); + + /* die() if child process die()'d */ + res = run_command(&cp); + if (!res) + return 0; + die_message(_("Failed to recurse into submodule path '%s'"), + update_data->displaypath); + if (res == 128) + exit(res); + else if (res) + return 1; + } + + return 0; +} + +static int update_submodules(struct update_data *update_data) +{ + int i, res = 0; + struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT; + + suc.update_data = update_data; + run_processes_parallel_tr2(suc.update_data->max_jobs, update_clone_get_next_task, update_clone_start_failure, - update_clone_task_finished, suc, "submodule", + update_clone_task_finished, &suc, "submodule", "parallel/update"); /* @@ -2483,209 +2555,139 @@ static int update_submodules(struct submodule_update_clone *suc) * checkout involve more straightforward sequential I/O. * - the listener can avoid doing any work if fetching failed. */ - if (suc->quickstop) - return 1; + if (suc.quickstop) { + res = 1; + goto cleanup; + } - for (i = 0; i < suc->update_clone_nr; i++) - update_submodule(&suc->update_clone[i]); + for (i = 0; i < suc.update_clone_nr; i++) { + struct update_clone_data ucd = suc.update_clone[i]; - return 0; + oidcpy(&update_data->oid, &ucd.oid); + update_data->just_cloned = ucd.just_cloned; + update_data->sm_path = ucd.sub->path; + + if (update_submodule(update_data)) + res = 1; + } + +cleanup: + string_list_clear(&update_data->references, 0); + return res; } -static int update_clone(int argc, const char **argv, const char *prefix) +static int module_update(int argc, const char **argv, const char *prefix) { - const char *update = NULL; struct pathspec pathspec; - struct submodule_update_clone suc = SUBMODULE_UPDATE_CLONE_INIT; + struct update_data opt = UPDATE_DATA_INIT; + struct list_objects_filter_options filter_options; + int ret; - struct option module_update_clone_options[] = { - OPT_STRING(0, "prefix", &prefix, + struct option module_update_options[] = { + OPT__FORCE(&opt.force, N_("force checkout updates"), 0), + OPT_BOOL(0, "init", &opt.init, + N_("initialize uninitialized submodules before update")), + OPT_BOOL(0, "remote", &opt.remote, + N_("use SHA-1 of submodule's remote tracking branch")), + OPT_BOOL(0, "recursive", &opt.recursive, + N_("traverse submodules recursively")), + OPT_BOOL('N', "no-fetch", &opt.nofetch, + N_("don't fetch new objects from the remote site")), + OPT_STRING(0, "prefix", &opt.prefix, N_("path"), N_("path into the working tree")), - OPT_STRING(0, "recursive-prefix", &suc.recursive_prefix, + OPT_STRING(0, "recursive-prefix", &opt.recursive_prefix, N_("path"), N_("path into the working tree, across nested " "submodule boundaries")), - OPT_STRING(0, "update", &update, + OPT_STRING(0, "update", &opt.update_default, N_("string"), N_("rebase, merge, checkout or none")), - OPT_STRING_LIST(0, "reference", &suc.references, N_("repo"), + OPT_STRING_LIST(0, "reference", &opt.references, N_("repo"), N_("reference repository")), - OPT_BOOL(0, "dissociate", &suc.dissociate, + OPT_BOOL(0, "dissociate", &opt.dissociate, N_("use --reference only while cloning")), - OPT_STRING(0, "depth", &suc.depth, "<depth>", + OPT_INTEGER(0, "depth", &opt.depth, N_("create a shallow clone truncated to the " "specified number of revisions")), - OPT_INTEGER('j', "jobs", &suc.max_jobs, + OPT_INTEGER('j', "jobs", &opt.max_jobs, N_("parallel jobs")), - OPT_BOOL(0, "recommend-shallow", &suc.recommend_shallow, + OPT_BOOL(0, "recommend-shallow", &opt.recommend_shallow, N_("whether the initial clone should follow the shallow recommendation")), - OPT__QUIET(&suc.quiet, N_("don't print cloning progress")), - OPT_BOOL(0, "progress", &suc.progress, + OPT__QUIET(&opt.quiet, N_("don't print cloning progress")), + OPT_BOOL(0, "progress", &opt.progress, N_("force cloning progress")), - OPT_BOOL(0, "require-init", &suc.require_init, + OPT_BOOL(0, "require-init", &opt.require_init, N_("disallow cloning into non-empty directory")), - OPT_BOOL(0, "single-branch", &suc.single_branch, + OPT_BOOL(0, "single-branch", &opt.single_branch, N_("clone only one branch, HEAD or --branch")), + OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), OPT_END() }; const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper update-clone [--prefix=<path>] [<path>...]"), + N_("git submodule [--quiet] update" + " [--init [--filter=<filter-spec>]] [--remote]" + " [-N|--no-fetch] [-f|--force]" + " [--checkout|--merge|--rebase]" + " [--[no-]recommend-shallow] [--reference <repository>]" + " [--recursive] [--[no-]single-branch] [--] [<path>...]"), NULL }; - suc.prefix = prefix; - update_clone_config_from_gitmodules(&suc.max_jobs); - git_config(git_update_clone_config, &suc.max_jobs); + update_clone_config_from_gitmodules(&opt.max_jobs); + git_config(git_update_clone_config, &opt.max_jobs); - argc = parse_options(argc, argv, prefix, module_update_clone_options, + memset(&filter_options, 0, sizeof(filter_options)); + argc = parse_options(argc, argv, prefix, module_update_options, git_submodule_helper_usage, 0); - if (update) - if (parse_submodule_update_strategy(update, &suc.update) < 0) + if (filter_options.choice && !opt.init) { + usage_with_options(git_submodule_helper_usage, + module_update_options); + } + + opt.filter_options = &filter_options; + + if (opt.update_default) + if (parse_submodule_update_strategy(opt.update_default, + &opt.update_strategy) < 0) die(_("bad value for update parameter")); - if (module_list_compute(argc, argv, prefix, &pathspec, &suc.list) < 0) + if (module_list_compute(argc, argv, prefix, &pathspec, &opt.list) < 0) { + list_objects_filter_release(&filter_options); return 1; + } if (pathspec.nr) - suc.warn_if_uninitialized = 1; + opt.warn_if_uninitialized = 1; - return update_submodules(&suc); -} + if (opt.init) { + struct module_list list = MODULE_LIST_INIT; + struct init_cb info = INIT_CB_INIT; -static int run_update_procedure(int argc, const char **argv, const char *prefix) -{ - int force = 0, quiet = 0, nofetch = 0, just_cloned = 0; - char *prefixed_path, *update = NULL; - struct update_data update_data = UPDATE_DATA_INIT; - - struct option options[] = { - OPT__QUIET(&quiet, N_("suppress output for update by rebase or merge")), - OPT__FORCE(&force, N_("force checkout updates"), 0), - OPT_BOOL('N', "no-fetch", &nofetch, - N_("don't fetch new objects from the remote site")), - OPT_BOOL(0, "just-cloned", &just_cloned, - N_("overrides update mode in case the repository is a fresh clone")), - OPT_INTEGER(0, "depth", &update_data.depth, N_("depth for shallow fetch")), - OPT_STRING(0, "prefix", &prefix, - N_("path"), - N_("path into the working tree")), - OPT_STRING(0, "update", &update, - N_("string"), - N_("rebase, merge, checkout or none")), - OPT_STRING(0, "recursive-prefix", &update_data.recursive_prefix, N_("path"), - N_("path into the working tree, across nested " - "submodule boundaries")), - OPT_CALLBACK_F(0, "oid", &update_data.oid, N_("sha1"), - N_("SHA1 expected by superproject"), PARSE_OPT_NONEG, - parse_opt_object_id), - OPT_CALLBACK_F(0, "suboid", &update_data.suboid, N_("subsha1"), - N_("SHA1 of submodule's HEAD"), PARSE_OPT_NONEG, - parse_opt_object_id), - OPT_END() - }; - - const char *const usage[] = { - N_("git submodule--helper run-update-procedure [<options>] <path>"), - NULL - }; - - argc = parse_options(argc, argv, prefix, options, usage, 0); - - if (argc != 1) - usage_with_options(usage, options); - - update_data.force = !!force; - update_data.quiet = !!quiet; - update_data.nofetch = !!nofetch; - update_data.just_cloned = !!just_cloned; - update_data.sm_path = argv[0]; - - if (update_data.recursive_prefix) - prefixed_path = xstrfmt("%s%s", update_data.recursive_prefix, update_data.sm_path); - else - prefixed_path = xstrdup(update_data.sm_path); - - update_data.displaypath = get_submodule_displaypath(prefixed_path, prefix); - - determine_submodule_update_strategy(the_repository, update_data.just_cloned, - update_data.sm_path, update, - &update_data.update_strategy); - - free(prefixed_path); - - if (!oideq(&update_data.oid, &update_data.suboid) || update_data.force) - return do_run_update_procedure(&update_data); - - return 3; -} - -static int resolve_relative_path(int argc, const char **argv, const char *prefix) -{ - struct strbuf sb = STRBUF_INIT; - if (argc != 3) - die("submodule--helper relative-path takes exactly 2 arguments, got %d", argc); - - printf("%s", relative_path(argv[1], argv[2], &sb)); - strbuf_release(&sb); - return 0; -} - -static const char *remote_submodule_branch(const char *path) -{ - const struct submodule *sub; - const char *branch = NULL; - char *key; - - sub = submodule_from_path(the_repository, null_oid(), path); - if (!sub) - return NULL; - - key = xstrfmt("submodule.%s.branch", sub->name); - if (repo_config_get_string_tmp(the_repository, key, &branch)) - branch = sub->branch; - free(key); - - if (!branch) - return "HEAD"; - - if (!strcmp(branch, ".")) { - const char *refname = resolve_ref_unsafe("HEAD", 0, NULL, NULL); + if (module_list_compute(argc, argv, opt.prefix, + &pathspec, &list) < 0) + return 1; - if (!refname) - die(_("No such ref: %s"), "HEAD"); + /* + * If there are no path args and submodule.active is set then, + * by default, only initialize 'active' modules. + */ + if (!argc && git_config_get_value_multi("submodule.active")) + module_list_active(&list); - /* detached HEAD */ - if (!strcmp(refname, "HEAD")) - die(_("Submodule (%s) branch configured to inherit " - "branch from superproject, but the superproject " - "is not on any branch"), sub->name); + info.prefix = opt.prefix; + info.superprefix = opt.recursive_prefix; + if (opt.quiet) + info.flags |= OPT_QUIET; - if (!skip_prefix(refname, "refs/heads/", &refname)) - die(_("Expecting a full ref name, got %s"), refname); - return refname; + for_each_listed_submodule(&list, init_submodule_cb, &info); } - return branch; -} - -static int resolve_remote_submodule_branch(int argc, const char **argv, - const char *prefix) -{ - const char *ret; - struct strbuf sb = STRBUF_INIT; - if (argc != 2) - die("submodule--helper remote-branch takes exactly one arguments, got %d", argc); - - ret = remote_submodule_branch(argv[1]); - if (!ret) - die("submodule %s doesn't exist", argv[1]); - - printf("%s", ret); - strbuf_release(&sb); - return 0; + ret = update_submodules(&opt); + list_objects_filter_release(&filter_options); + return ret; } static int push_check(int argc, const char **argv, const char *prefix) @@ -2765,40 +2767,6 @@ static int push_check(int argc, const char **argv, const char *prefix) return 0; } -static int ensure_core_worktree(int argc, const char **argv, const char *prefix) -{ - const char *path; - const char *cw; - struct repository subrepo; - - if (argc != 2) - BUG("submodule--helper ensure-core-worktree <path>"); - - path = argv[1]; - - if (repo_submodule_init(&subrepo, the_repository, path, null_oid())) - die(_("could not get a repository handle for submodule '%s'"), path); - - if (!repo_config_get_string_tmp(&subrepo, "core.worktree", &cw)) { - char *cfg_file, *abs_path; - const char *rel_path; - struct strbuf sb = STRBUF_INIT; - - cfg_file = repo_git_path(&subrepo, "config"); - - abs_path = absolute_pathdup(path); - rel_path = relative_path(abs_path, subrepo.gitdir, &sb); - - git_config_set_in_file(cfg_file, "core.worktree", rel_path); - - free(cfg_file); - free(abs_path); - strbuf_release(&sb); - } - - return 0; -} - static int absorb_git_dirs(int argc, const char **argv, const char *prefix) { int i; @@ -2882,7 +2850,7 @@ static int module_config(int argc, const char **argv, const char *prefix) const char *const git_submodule_helper_usage[] = { N_("git submodule--helper config <name> [<value>]"), N_("git submodule--helper config --unset <name>"), - N_("git submodule--helper config --check-writeable"), + "git submodule--helper config --check-writeable", NULL }; @@ -2971,7 +2939,7 @@ static int module_set_branch(int argc, const char **argv, const char *prefix) die(_("--branch or --default required")); if (opt_branch && opt_default) - die(_("--branch and --default are mutually exclusive")); + die(_("options '%s' and '%s' cannot be used together"), "--branch", "--default"); if (argc != 1 || !(path = argv[0])) usage_with_options(usage, options); @@ -2983,6 +2951,44 @@ static int module_set_branch(int argc, const char **argv, const char *prefix) return !!ret; } +static int module_create_branch(int argc, const char **argv, const char *prefix) +{ + enum branch_track track; + int quiet = 0, force = 0, reflog = 0, dry_run = 0; + + struct option options[] = { + OPT__QUIET(&quiet, N_("print only error messages")), + OPT__FORCE(&force, N_("force creation"), 0), + OPT_BOOL(0, "create-reflog", &reflog, + N_("create the branch's reflog")), + OPT_CALLBACK_F('t', "track", &track, "(direct|inherit)", + N_("set branch tracking configuration"), + PARSE_OPT_OPTARG, + parse_opt_tracking_mode), + OPT__DRY_RUN(&dry_run, + N_("show whether the branch would be created")), + OPT_END() + }; + const char *const usage[] = { + N_("git submodule--helper create-branch [-f|--force] [--create-reflog] [-q|--quiet] [-t|--track] [-n|--dry-run] <name> <start-oid> <start-name>"), + NULL + }; + + git_config(git_default_config, NULL); + track = git_branch_track; + argc = parse_options(argc, argv, prefix, options, usage, 0); + + if (argc != 3) + usage_with_options(usage, options); + + if (!quiet && !dry_run) + printf_ln(_("creating branch '%s'"), argv[0]); + + create_branches_recursively(the_repository, argv[0], argv[1], argv[2], + force, reflog, quiet, track, dry_run); + return 0; +} + struct add_data { const char *prefix; const char *branch; @@ -3005,9 +3011,9 @@ static void append_fetch_remotes(struct strbuf *msg, const char *git_dir_path) struct strbuf sb_remote_out = STRBUF_INIT; cp_remote.git_cmd = 1; - strvec_pushf(&cp_remote.env_array, + strvec_pushf(&cp_remote.env, "GIT_DIR=%s", git_dir_path); - strvec_push(&cp_remote.env_array, "GIT_WORK_TREE=."); + strvec_push(&cp_remote.env, "GIT_WORK_TREE=."); strvec_pushl(&cp_remote.args, "remote", "-v", NULL); if (!capture_command(&cp_remote, &sb_remote_out, 0)) { char *next_line; @@ -3091,7 +3097,7 @@ static int add_submodule(const struct add_data *add_data) if (clone_submodule(&clone_data)) return -1; - prepare_submodule_repo_env(&cp.env_array); + prepare_submodule_repo_env(&cp.env); cp.git_cmd = 1; cp.dir = add_data->sm_path; /* @@ -3247,6 +3253,7 @@ static int module_add(int argc, const char **argv, const char *prefix) { int force = 0, quiet = 0, progress = 0, dissociate = 0; struct add_data add_data = ADD_DATA_INIT; + char *to_free = NULL; struct option options[] = { OPT_STRING('b', "branch", &add_data.branch, N_("branch"), @@ -3259,7 +3266,7 @@ static int module_add(int argc, const char **argv, const char *prefix) N_("reference repository")), OPT_BOOL(0, "dissociate", &dissociate, N_("borrow the objects from reference repositories")), OPT_STRING(0, "name", &add_data.sm_name, N_("name"), - N_("sets the submodule’s name to the given string " + N_("sets the submodule's name to the given string " "instead of defaulting to its path")), OPT_INTEGER(0, "depth", &add_data.depth, N_("depth for shallow clones")), OPT_END() @@ -3298,7 +3305,8 @@ static int module_add(int argc, const char **argv, const char *prefix) "of the working tree")); /* dereference source url relative to parent's url */ - add_data.realrepo = resolve_relative_url(add_data.repo, NULL, 1); + to_free = resolve_relative_url(add_data.repo, NULL, 1); + add_data.realrepo = to_free; } else if (is_dir_sep(add_data.repo[0]) || strchr(add_data.repo, ':')) { add_data.realrepo = add_data.repo; } else { @@ -3351,6 +3359,7 @@ static int module_add(int argc, const char **argv, const char *prefix) } configure_added_submodule(&add_data); free(add_data.sm_path); + free(to_free); return 0; } @@ -3368,20 +3377,14 @@ static struct cmd_struct commands[] = { {"name", module_name, 0}, {"clone", module_clone, 0}, {"add", module_add, SUPPORT_SUPER_PREFIX}, - {"update-module-mode", module_update_module_mode, 0}, - {"update-clone", update_clone, 0}, - {"run-update-procedure", run_update_procedure, 0}, - {"ensure-core-worktree", ensure_core_worktree, 0}, - {"relative-path", resolve_relative_path, 0}, + {"update", module_update, 0}, {"resolve-relative-url-test", resolve_relative_url_test, 0}, {"foreach", module_foreach, SUPPORT_SUPER_PREFIX}, {"init", module_init, SUPPORT_SUPER_PREFIX}, {"status", module_status, SUPPORT_SUPER_PREFIX}, - {"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}, @@ -3389,6 +3392,7 @@ static struct cmd_struct commands[] = { {"config", module_config, 0}, {"set-url", module_set_url, 0}, {"set-branch", module_set_branch, 0}, + {"create-branch", module_create_branch, 0}, }; int cmd_submodule__helper(int argc, const char **argv, const char *prefix) diff --git a/builtin/tag.c b/builtin/tag.c index 6fe646710d..75dece0e4f 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -20,6 +20,7 @@ #include "oid-array.h" #include "column.h" #include "ref-filter.h" +#include "date.h" static const char * const git_tag_usage[] = { N_("git tag [-a | -s | -u <key-id>] [-f] [-m <msg> | -F <file>]\n" @@ -178,7 +179,6 @@ static const char tag_template_nocleanup[] = static int git_tag_config(const char *var, const char *value, void *cb) { int status; - struct ref_sorting **sorting_tail = (struct ref_sorting **)cb; if (!strcmp(var, "tag.gpgsign")) { config_sign_tag = git_config_bool(var, value); @@ -188,7 +188,7 @@ static int git_tag_config(const char *var, const char *value, void *cb) if (!strcmp(var, "tag.sort")) { if (!value) return config_error_nonbool(var); - parse_ref_sorting(sorting_tail, value); + string_list_append(cb, value); return 0; } @@ -239,7 +239,7 @@ static int build_tag_object(struct strbuf *buf, int sign, struct object_id *resu { if (sign && do_sign(buf) < 0) return error(_("unable to sign the tag")); - if (write_object_file(buf->buf, buf->len, tag_type, result) < 0) + if (write_object_file(buf->buf, buf->len, OBJ_TAG, result) < 0) return error(_("unable to write tag file")); return 0; } @@ -364,7 +364,7 @@ static void create_reflog_msg(const struct object_id *oid, struct strbuf *sb) strbuf_addstr(sb, "object of unknown type"); break; case OBJ_COMMIT: - if ((buf = read_object_file(oid, &type, &size)) != NULL) { + if ((buf = read_object_file(oid, &type, &size))) { subject_len = find_commit_subject(buf, &subject_start); strbuf_insert(sb, sb->len, subject_start, subject_len); } else { @@ -372,7 +372,7 @@ static void create_reflog_msg(const struct object_id *oid, struct strbuf *sb) } free(buf); - if ((c = lookup_commit_reference(the_repository, oid)) != NULL) + if ((c = lookup_commit_reference(the_repository, oid))) strbuf_addf(sb, ", %s", show_date(c->date, 0, DATE_MODE(SHORT))); break; case OBJ_TREE: @@ -436,7 +436,8 @@ int cmd_tag(int argc, const char **argv, const char *prefix) struct ref_transaction *transaction; struct strbuf err = STRBUF_INIT; struct ref_filter filter; - static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting; + struct ref_sorting *sorting; + struct string_list sorting_options = STRING_LIST_INIT_DUP; struct ref_format format = REF_FORMAT_INIT; int icase = 0; int edit_flag = 0; @@ -470,7 +471,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_WITHOUT(&filter.no_commit, N_("print only tags that don't contain the commit")), OPT_MERGED(&filter, N_("print only tags that are merged")), OPT_NO_MERGED(&filter, N_("print only tags that are not merged")), - OPT_REF_SORT(sorting_tail), + OPT_REF_SORT(&sorting_options), { OPTION_CALLBACK, 0, "points-at", &filter.points_at, N_("object"), N_("print only tags of the object"), PARSE_OPT_LASTARG_DEFAULT, @@ -483,10 +484,11 @@ int cmd_tag(int argc, const char **argv, const char *prefix) OPT_END() }; int ret = 0; + const char *only_in_list = NULL; setup_ref_filter_porcelain_msg(); - git_config(git_tag_config, sorting_tail); + git_config(git_tag_config, &sorting_options); memset(&opt, 0, sizeof(opt)); memset(&filter, 0, sizeof(filter)); @@ -522,11 +524,10 @@ int cmd_tag(int argc, const char **argv, const char *prefix) finalize_colopts(&colopts, -1); if (cmdmode == 'l' && filter.lines != -1) { if (explicitly_enable_column(colopts)) - die(_("--column and -n are incompatible")); + die(_("options '%s' and '%s' cannot be used together"), "--column", "-n"); colopts = 0; } - if (!sorting) - sorting = ref_default_sorting(); + sorting = ref_sorting_options(&sorting_options); ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase); filter.ignore_case = icase; if (cmdmode == 'l') { @@ -543,15 +544,19 @@ int cmd_tag(int argc, const char **argv, const char *prefix) goto cleanup; } if (filter.lines != -1) - die(_("-n option is only allowed in list mode")); - if (filter.with_commit) - die(_("--contains option is only allowed in list mode")); - if (filter.no_commit) - 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.reachable_from || filter.unreachable_from) - die(_("--merged and --no-merged options are only allowed in list mode")); + only_in_list = "-n"; + else if (filter.with_commit) + only_in_list = "--contains"; + else if (filter.no_commit) + only_in_list = "--no-contains"; + else if (filter.points_at.nr) + only_in_list = "--points-at"; + else if (filter.reachable_from) + only_in_list = "--merged"; + else if (filter.unreachable_from) + only_in_list = "--no-merged"; + if (only_in_list) + die(_("the '%s' option is only allowed in list mode"), only_in_list); if (cmdmode == 'd') { ret = delete_tags(argv); goto cleanup; @@ -565,7 +570,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (msg.given || msgfile) { if (msg.given && msgfile) - die(_("only one -F or -m option is allowed.")); + die(_("options '%s' and '%s' cannot be used together"), "-F", "-m"); if (msg.given) strbuf_addbuf(&buf, &(msg.buf)); else { diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index 4a9466295b..56d05e2725 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -1,5 +1,6 @@ #include "builtin.h" #include "cache.h" +#include "bulk-checkin.h" #include "config.h" #include "object-store.h" #include "object.h" @@ -177,7 +178,7 @@ static void write_cached_object(struct object *obj, struct obj_buffer *obj_buf) struct object_id oid; if (write_object_file(obj_buf->buffer, obj_buf->size, - type_name(obj->type), &oid) < 0) + obj->type, &oid) < 0) die("failed to write object %s", oid_to_hex(&obj->oid)); obj->flags |= FLAG_WRITTEN; } @@ -243,7 +244,7 @@ static void write_object(unsigned nr, enum object_type type, void *buf, unsigned long size) { if (!strict) { - if (write_object_file(buf, size, type_name(type), + if (write_object_file(buf, size, type, &obj_list[nr].oid) < 0) die("failed to write object"); added_object(nr, type, buf, size); @@ -251,7 +252,7 @@ static void write_object(unsigned nr, enum object_type type, obj_list[nr].obj = NULL; } else if (type == OBJ_BLOB) { struct blob *blob; - if (write_object_file(buf, size, type_name(type), + if (write_object_file(buf, size, type, &obj_list[nr].oid) < 0) die("failed to write object"); added_object(nr, type, buf, size); @@ -266,7 +267,7 @@ static void write_object(unsigned nr, enum object_type type, } else { struct object *obj; int eaten; - hash_object_file(the_hash_algo, buf, size, type_name(type), + hash_object_file(the_hash_algo, buf, size, type, &obj_list[nr].oid); added_object(nr, type, buf, size); obj = parse_object_buffer(the_repository, &obj_list[nr].oid, @@ -503,10 +504,12 @@ static void unpack_all(void) if (!quiet) progress = start_progress(_("Unpacking objects"), nr_objects); CALLOC_ARRAY(obj_list, nr_objects); + begin_odb_transaction(); for (i = 0; i < nr_objects; i++) { unpack_one(i); display_progress(progress, i + 1); } + end_odb_transaction(); stop_progress(&progress); if (delta_list) diff --git a/builtin/update-index.c b/builtin/update-index.c index 187203e8bb..b62249905f 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -5,6 +5,7 @@ */ #define USE_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "bulk-checkin.h" #include "config.h" #include "lockfile.h" #include "quote.h" @@ -57,6 +58,14 @@ static void report(const char *fmt, ...) if (!verbose) return; + /* + * It is possible, though unlikely, that a caller could use the verbose + * output to synchronize with addition of objects to the object + * database. The current implementation of ODB transactions leaves + * objects invisible while a transaction is active, so flush the + * transaction here before reporting a change made by update-index. + */ + flush_odb_transaction(); va_start(vp, fmt); vprintf(fmt, vp); putchar('\n'); @@ -606,7 +615,7 @@ static struct cache_entry *read_one_ent(const char *which, error("%s: not in %s branch.", path, which); return NULL; } - if (mode == S_IFDIR) { + if (!the_index.sparse_index && mode == S_IFDIR) { if (which) error("%s: not a blob in %s branch.", path, which); return NULL; @@ -743,8 +752,6 @@ static int do_reupdate(int ac, const char **av, */ has_head = 0; redo: - /* TODO: audit for interaction with sparse-index. */ - ensure_full_index(&the_index); for (pos = 0; pos < active_nr; pos++) { const struct cache_entry *ce = active_cache[pos]; struct cache_entry *old = NULL; @@ -761,6 +768,16 @@ static int do_reupdate(int ac, const char **av, discard_cache_entry(old); continue; /* unchanged */ } + + /* At this point, we know the contents of the sparse directory are + * modified with respect to HEAD, so we expand the index and restart + * to process each path individually + */ + if (S_ISSPARSEDIR(ce->ce_mode)) { + ensure_full_index(&the_index); + goto redo; + } + /* Be careful. The working tree may not have the * path anymore, in which case, under 'allow_remove', * or worse yet 'allow_replace', active_nr may decrease. @@ -787,6 +804,17 @@ static int refresh(struct refresh_params *o, unsigned int flag) setup_work_tree(); read_cache(); *o->has_errors |= refresh_cache(o->flags | flag); + if (has_racy_timestamp(&the_index)) { + /* + * Even if nothing else has changed, updating the file + * increases the chance that racy timestamps become + * non-racy, helping future run-time performance. + * We do that even in case of "errors" returned by + * refresh_cache() as these are no actual errors. + * cmd_status() does the same. + */ + active_cache_changed |= SOMETHING_CHANGED; + } return 0; } @@ -1077,6 +1105,9 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); + prepare_repo_settings(r); + the_repository->settings.command_requires_full_index = 0; + /* we will diagnose later if it turns out that we need to update it */ newfd = hold_locked_index(&lock_file, 0); if (newfd < 0) @@ -1094,6 +1125,12 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) */ parse_options_start(&ctx, argc, argv, prefix, options, PARSE_OPT_STOP_AT_NON_OPTION); + + /* + * Allow the object layer to optimize adding multiple objects in + * a batch. + */ + begin_odb_transaction(); while (ctx.argc) { if (parseopt_state != PARSE_OPT_DONE) parseopt_state = parse_options_step(&ctx, options, @@ -1168,6 +1205,11 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) strbuf_release(&buf); } + /* + * By now we have added all of the new objects + */ + end_odb_transaction(); + if (split_index > 0) { if (git_config_get_split_index() == 0) warning(_("core.splitIndex is set to false; " @@ -1214,14 +1256,33 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) } if (fsmonitor > 0) { - if (git_config_get_fsmonitor() == 0) + enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(r); + enum fsmonitor_reason reason = fsm_settings__get_reason(r); + + /* + * The user wants to turn on FSMonitor using the command + * line argument. (We don't know (or care) whether that + * is the IPC or HOOK version.) + * + * Use one of the __get routines to force load the FSMonitor + * config settings into the repo-settings. That will detect + * whether the file system is compatible so that we can stop + * here with a nice error message. + */ + if (reason > FSMONITOR_REASON_OK) + die("%s", + fsm_settings__get_incompatible_msg(r, reason)); + + if (fsm_mode == FSMONITOR_MODE_DISABLED) { warning(_("core.fsmonitor is unset; " "set it if you really want to " "enable fsmonitor")); + } add_fsmonitor(&the_index); report(_("fsmonitor enabled")); } else if (!fsmonitor) { - if (git_config_get_fsmonitor() == 1) + enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(r); + if (fsm_mode > FSMONITOR_MODE_DISABLED) warning(_("core.fsmonitor is set; " "remove it if you really want to " "disable fsmonitor")); diff --git a/builtin/update-server-info.c b/builtin/update-server-info.c index 4321a34456..880fffec58 100644 --- a/builtin/update-server-info.c +++ b/builtin/update-server-info.c @@ -4,7 +4,7 @@ #include "parse-options.h" static const char * const update_server_info_usage[] = { - N_("git update-server-info [--force]"), + "git update-server-info [--force]", NULL }; diff --git a/builtin/upload-archive.c b/builtin/upload-archive.c index 24654b4c9b..98d028dae6 100644 --- a/builtin/upload-archive.c +++ b/builtin/upload-archive.c @@ -77,7 +77,7 @@ static ssize_t process_input(int child_fd, int band) int cmd_upload_archive(int argc, const char **argv, const char *prefix) { - struct child_process writer = { argv }; + struct child_process writer = CHILD_PROCESS_INIT; if (argc == 2 && !strcmp(argv[1], "-h")) usage(upload_archive_usage); @@ -89,9 +89,10 @@ int cmd_upload_archive(int argc, const char **argv, const char *prefix) * multiplexed out to our fd#1. If the child dies, we tell the other * end over channel #3. */ - argv[0] = "upload-archive--writer"; writer.out = writer.err = -1; writer.git_cmd = 1; + strvec_push(&writer.args, "upload-archive--writer"); + strvec_pushv(&writer.args, argv + 1); if (start_command(&writer)) { int err = errno; packet_write_fmt(1, "NACK unable to spawn subprocess\n"); diff --git a/builtin/var.c b/builtin/var.c index 6c6f46b4ae..491db27429 100644 --- a/builtin/var.c +++ b/builtin/var.c @@ -5,6 +5,7 @@ */ #include "builtin.h" #include "config.h" +#include "refs.h" static const char var_usage[] = "git var (-l | <variable>)"; @@ -27,6 +28,11 @@ static const char *pager(int flag) return pgm; } +static const char *default_branch(int flag) +{ + return git_default_branch_name(1); +} + struct git_var { const char *name; const char *(*read)(int); @@ -36,6 +42,7 @@ static struct git_var git_vars[] = { { "GIT_AUTHOR_IDENT", git_author_info }, { "GIT_EDITOR", editor }, { "GIT_PAGER", pager }, + { "GIT_DEFAULT_BRANCH", default_branch }, { "", NULL }, }; diff --git a/builtin/worktree.c b/builtin/worktree.c index d22ece93e1..cd62eef240 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -22,6 +22,7 @@ static const char * const worktree_usage[] = { N_("git worktree move <worktree> <new-path>"), N_("git worktree prune [<options>]"), N_("git worktree remove [<options>] <worktree>"), + N_("git worktree repair [<path>...]"), N_("git worktree unlock <path>"), NULL }; @@ -72,7 +73,7 @@ static void delete_worktrees_dir_if_empty(void) static void prune_worktree(const char *id, const char *reason) { if (show_only || verbose) - printf_ln(_("Removing %s/%s: %s"), "worktrees", id, reason); + fprintf_ln(stderr, _("Removing %s/%s: %s"), "worktrees", id, reason); if (!show_only) delete_git_dir(id); } @@ -236,6 +237,74 @@ static void check_candidate_path(const char *path, die(_("'%s' is a missing but already registered worktree;\nuse '%s -f' to override, or 'prune' or 'remove' to clear"), path, cmd); } +static void copy_sparse_checkout(const char *worktree_git_dir) +{ + char *from_file = git_pathdup("info/sparse-checkout"); + char *to_file = xstrfmt("%s/info/sparse-checkout", worktree_git_dir); + + if (file_exists(from_file)) { + if (safe_create_leading_directories(to_file) || + copy_file(to_file, from_file, 0666)) + error(_("failed to copy '%s' to '%s'; sparse-checkout may not work correctly"), + from_file, to_file); + } + + free(from_file); + free(to_file); +} + +static void copy_filtered_worktree_config(const char *worktree_git_dir) +{ + char *from_file = git_pathdup("config.worktree"); + char *to_file = xstrfmt("%s/config.worktree", worktree_git_dir); + + if (file_exists(from_file)) { + struct config_set cs = { { 0 } }; + const char *core_worktree; + int bare; + + if (safe_create_leading_directories(to_file) || + copy_file(to_file, from_file, 0666)) { + error(_("failed to copy worktree config from '%s' to '%s'"), + from_file, to_file); + goto worktree_copy_cleanup; + } + + git_configset_init(&cs); + git_configset_add_file(&cs, from_file); + + if (!git_configset_get_bool(&cs, "core.bare", &bare) && + bare && + git_config_set_multivar_in_file_gently( + to_file, "core.bare", NULL, "true", 0)) + error(_("failed to unset '%s' in '%s'"), + "core.bare", to_file); + if (!git_configset_get_value(&cs, "core.worktree", &core_worktree) && + git_config_set_in_file_gently(to_file, + "core.worktree", NULL)) + error(_("failed to unset '%s' in '%s'"), + "core.worktree", to_file); + + git_configset_clear(&cs); + } + +worktree_copy_cleanup: + free(from_file); + free(to_file); +} + +static int checkout_worktree(const struct add_opts *opts, + struct strvec *child_env) +{ + struct child_process cp = CHILD_PROCESS_INIT; + cp.git_cmd = 1; + strvec_pushl(&cp.args, "reset", "--hard", "--no-recurse-submodules", NULL); + if (opts->quiet) + strvec_push(&cp.args, "--quiet"); + strvec_pushv(&cp.env, child_env->v); + return run_command(&cp); +} + static int add_worktree(const char *path, const char *refname, const struct add_opts *opts) { @@ -335,6 +404,21 @@ static int add_worktree(const char *path, const char *refname, strbuf_addf(&sb, "%s/commondir", sb_repo.buf); write_file(sb.buf, "../.."); + /* + * If the current worktree has sparse-checkout enabled, then copy + * the sparse-checkout patterns from the current worktree. + */ + if (core_apply_sparse_checkout) + copy_sparse_checkout(sb_repo.buf); + + /* + * If we are using worktree config, then copy all current config + * values from the current worktree into the new one, that way the + * new worktree behaves the same as this one. + */ + if (repository_format_worktree_config) + copy_filtered_worktree_config(sb_repo.buf); + 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; @@ -349,22 +433,14 @@ static int add_worktree(const char *path, const char *refname, strvec_push(&cp.args, "--quiet"); } - cp.env = child_env.v; + strvec_pushv(&cp.env, child_env.v); ret = run_command(&cp); if (ret) goto done; - if (opts->checkout) { - cp.argv = NULL; - strvec_clear(&cp.args); - strvec_pushl(&cp.args, "reset", "--hard", "--no-recurse-submodules", NULL); - if (opts->quiet) - strvec_push(&cp.args, "--quiet"); - cp.env = child_env.v; - ret = run_command(&cp); - if (ret) - goto done; - } + if (opts->checkout && + (ret = checkout_worktree(opts, &child_env))) + goto done; is_junk = 0; FREE_AND_NULL(junk_work_tree); @@ -382,22 +458,17 @@ done: * is_junk is cleared, but do return appropriate code when hook fails. */ if (!ret && opts->checkout) { - const char *hook = find_hook("post-checkout"); - if (hook) { - const char *env[] = { "GIT_DIR", "GIT_WORK_TREE", NULL }; - cp.git_cmd = 0; - cp.no_stdin = 1; - cp.stdout_to_stderr = 1; - cp.dir = path; - cp.env = env; - cp.argv = NULL; - cp.trace2_hook_name = "post-checkout"; - strvec_pushl(&cp.args, absolute_path(hook), - oid_to_hex(null_oid()), - oid_to_hex(&commit->object.oid), - "1", NULL); - ret = run_command(&cp); - } + struct run_hooks_opt opt = RUN_HOOKS_OPT_INIT; + + strvec_pushl(&opt.env, "GIT_DIR", "GIT_WORK_TREE", NULL); + strvec_pushl(&opt.args, + oid_to_hex(null_oid()), + oid_to_hex(&commit->object.oid), + "1", + NULL); + opt.dir = path; + + ret = run_hooks_opt("post-checkout", &opt); } strvec_clear(&child_env); @@ -418,24 +489,24 @@ static void print_preparing_worktree_line(int detach, if (force_new_branch) { struct commit *commit = lookup_commit_reference_by_name(new_branch); if (!commit) - printf_ln(_("Preparing worktree (new branch '%s')"), new_branch); + fprintf_ln(stderr, _("Preparing worktree (new branch '%s')"), new_branch); else - printf_ln(_("Preparing worktree (resetting branch '%s'; was at %s)"), + fprintf_ln(stderr, _("Preparing worktree (resetting branch '%s'; was at %s)"), new_branch, find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV)); } else if (new_branch) { - printf_ln(_("Preparing worktree (new branch '%s')"), new_branch); + fprintf_ln(stderr, _("Preparing worktree (new branch '%s')"), new_branch); } else { struct strbuf s = STRBUF_INIT; if (!detach && !strbuf_check_branch_ref(&s, branch) && ref_exists(s.buf)) - printf_ln(_("Preparing worktree (checking out '%s')"), + fprintf_ln(stderr, _("Preparing worktree (checking out '%s')"), branch); else { struct commit *commit = lookup_commit_reference_by_name(branch); if (!commit) die(_("invalid reference: %s"), branch); - printf_ln(_("Preparing worktree (detached HEAD %s)"), + fprintf_ln(stderr, _("Preparing worktree (detached HEAD %s)"), find_unique_abbrev(&commit->object.oid, DEFAULT_ABBREV)); } strbuf_release(&s); @@ -504,9 +575,9 @@ static int add(int ac, const char **av, const char *prefix) opts.checkout = 1; ac = parse_options(ac, av, prefix, options, worktree_usage, 0); if (!!opts.detach + !!new_branch + !!new_branch_force > 1) - die(_("-b, -B, and --detach are mutually exclusive")); + die(_("options '%s', '%s', and '%s' cannot be used together"), "-b", "-B", "--detach"); if (lock_reason && !keep_locked) - die(_("--reason requires --lock")); + die(_("the option '%s' requires '%s'"), "--reason", "--lock"); if (lock_reason) opts.keep_locked = lock_reason; else if (keep_locked) @@ -580,35 +651,37 @@ static int add(int ac, const char **av, const char *prefix) return add_worktree(path, branch, &opts); } -static void show_worktree_porcelain(struct worktree *wt) +static void show_worktree_porcelain(struct worktree *wt, int line_terminator) { const char *reason; - printf("worktree %s\n", wt->path); + printf("worktree %s%c", wt->path, line_terminator); if (wt->is_bare) - printf("bare\n"); + printf("bare%c", line_terminator); else { - printf("HEAD %s\n", oid_to_hex(&wt->head_oid)); + printf("HEAD %s%c", oid_to_hex(&wt->head_oid), line_terminator); if (wt->is_detached) - printf("detached\n"); + printf("detached%c", line_terminator); else if (wt->head_ref) - printf("branch %s\n", wt->head_ref); + printf("branch %s%c", wt->head_ref, line_terminator); } reason = worktree_lock_reason(wt); - if (reason && *reason) { - struct strbuf sb = STRBUF_INIT; - quote_c_style(reason, &sb, NULL, 0); - printf("locked %s\n", sb.buf); - strbuf_release(&sb); - } else if (reason) - printf("locked\n"); + if (reason) { + fputs("locked", stdout); + if (*reason) { + fputc(' ', stdout); + write_name_quoted(reason, stdout, line_terminator); + } else { + fputc(line_terminator, stdout); + } + } reason = worktree_prune_reason(wt, expire); if (reason) - printf("prunable %s\n", reason); + printf("prunable %s%c", reason, line_terminator); - printf("\n"); + fputc(line_terminator, stdout); } static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len) @@ -686,12 +759,15 @@ static void pathsort(struct worktree **wt) static int list(int ac, const char **av, const char *prefix) { int porcelain = 0; + int line_terminator = '\n'; struct option options[] = { OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")), OPT__VERBOSE(&verbose, N_("show extended annotations and reasons, if available")), OPT_EXPIRY_DATE(0, "expire", &expire, N_("add 'prunable' annotation to worktrees older than <time>")), + OPT_SET_INT('z', NULL, &line_terminator, + N_("terminate records with a NUL character"), '\0'), OPT_END() }; @@ -700,7 +776,9 @@ static int list(int ac, const char **av, const char *prefix) if (ac) usage_with_options(worktree_usage, options); else if (verbose && porcelain) - die(_("--verbose and --porcelain are mutually exclusive")); + die(_("options '%s' and '%s' cannot be used together"), "--verbose", "--porcelain"); + else if (!line_terminator && !porcelain) + die(_("the option '%s' requires '%s'"), "-z", "--porcelain"); else { struct worktree **worktrees = get_worktrees(); int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i; @@ -713,7 +791,8 @@ static int list(int ac, const char **av, const char *prefix) for (i = 0; worktrees[i]; i++) { if (porcelain) - show_worktree_porcelain(worktrees[i]); + show_worktree_porcelain(worktrees[i], + line_terminator); else show_worktree(worktrees[i], path_maxlen, abbrev); } @@ -910,9 +989,9 @@ static void check_clean_worktree(struct worktree *wt, validate_no_submodules(wt); child_process_init(&cp); - strvec_pushf(&cp.env_array, "%s=%s/.git", + strvec_pushf(&cp.env, "%s=%s/.git", GIT_DIR_ENVIRONMENT, wt->path); - strvec_pushf(&cp.env_array, "%s=%s", + strvec_pushf(&cp.env, "%s=%s", GIT_WORK_TREE_ENVIRONMENT, wt->path); strvec_pushl(&cp.args, "status", "--porcelain", "--ignore-submodules=none", @@ -1006,7 +1085,7 @@ static int remove_worktree(int ac, const char **av, const char *prefix) static void report_repair(int iserr, const char *path, const char *msg, void *cb_data) { if (!iserr) { - printf_ln(_("repair: %s: %s"), msg, path); + fprintf_ln(stderr, _("repair: %s: %s"), msg, path); } else { int *exit_status = (int *)cb_data; fprintf_ln(stderr, _("error: %s: %s"), msg, path); |
