diff options
Diffstat (limited to 'builtin')
52 files changed, 2899 insertions, 1124 deletions
diff --git a/builtin/add.c b/builtin/add.c index dd18e5c9b6..18a0881ecf 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -20,6 +20,7 @@ #include "bulk-checkin.h" #include "argv-array.h" #include "submodule.h" +#include "add-interactive.h" static const char * const builtin_add_usage[] = { N_("git add [<options>] [--] <pathspec>..."), @@ -28,6 +29,9 @@ static const char * const builtin_add_usage[] = { static int patch_interactive, add_interactive, edit_interactive; static int take_worktree_changes; static int add_renormalize; +static int pathspec_file_nul; +static const char *pathspec_from_file; +static int legacy_stash_p; /* support for the scripted `git stash` */ struct update_callback_data { int flags; @@ -185,6 +189,34 @@ int run_add_interactive(const char *revision, const char *patch_mode, { int status, i; struct argv_array argv = ARGV_ARRAY_INIT; + int use_builtin_add_i = + git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1); + + if (use_builtin_add_i < 0) + git_config_get_bool("add.interactive.usebuiltin", + &use_builtin_add_i); + + if (use_builtin_add_i == 1) { + enum add_p_mode mode; + + if (!patch_mode) + return !!run_add_i(the_repository, pathspec); + + if (!strcmp(patch_mode, "--patch")) + mode = ADD_P_ADD; + else if (!strcmp(patch_mode, "--patch=stash")) + mode = ADD_P_STASH; + else if (!strcmp(patch_mode, "--patch=reset")) + mode = ADD_P_RESET; + else if (!strcmp(patch_mode, "--patch=checkout")) + mode = ADD_P_CHECKOUT; + else if (!strcmp(patch_mode, "--patch=worktree")) + mode = ADD_P_WORKTREE; + else + die("'%s' not supported", patch_mode); + + return !!run_add_p(the_repository, mode, revision, pathspec); + } argv_array_push(&argv, "add--interactive"); if (patch_mode) @@ -309,6 +341,10 @@ static struct option builtin_add_options[] = { N_("override the executable bit of the listed files")), OPT_HIDDEN_BOOL(0, "warn-embedded-repo", &warn_on_embedded_repo, N_("warn when adding an embedded repository")), + OPT_HIDDEN_BOOL(0, "legacy-stash-p", &legacy_stash_p, + N_("backend for `git stash -p`")), + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END(), }; @@ -319,6 +355,7 @@ static int add_config(const char *var, const char *value, void *cb) ignore_add_errors = git_config_bool(var, value); return 0; } + return git_default_config(var, value, cb); } @@ -369,7 +406,10 @@ static int add_files(struct dir_struct *dir, int flags) fprintf(stderr, _(ignore_error)); for (i = 0; i < dir->ignored_nr; i++) fprintf(stderr, "%s\n", dir->ignored[i]->name); - fprintf(stderr, _("Use -f if you really want to add them.\n")); + if (advice_add_ignored_file) + advise(_("Use -f if you really want to add them.\n" + "Turn this message off by running\n" + "\"git config advice.addIgnoredFile false\"")); exit_status = 1; } @@ -402,11 +442,28 @@ int cmd_add(int argc, const char **argv, const char *prefix) builtin_add_usage, PARSE_OPT_KEEP_ARGV0); if (patch_interactive) add_interactive = 1; - if (add_interactive) + if (add_interactive) { + if (pathspec_from_file) + die(_("--pathspec-from-file is incompatible with --interactive/--patch")); exit(interactive_add(argc - 1, 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 (edit_interactive) { + if (pathspec_from_file) + die(_("--pathspec-from-file is incompatible with --edit")); return(edit_patch(argc, argv, prefix)); + } argc--; argv++; @@ -418,10 +475,6 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (addremove && take_worktree_changes) die(_("-A and -u are mutually incompatible")); - if (!take_worktree_changes && addremove_explicit < 0 && argc) - /* Turn "git add pathspec..." to "git add -A pathspec..." */ - addremove = 1; - if (!show_only && ignore_missing) die(_("Option --ignore-missing can only be used together with --dry-run")); @@ -434,19 +487,6 @@ int cmd_add(int argc, const char **argv, const char *prefix) hold_locked_index(&lock_file, LOCK_DIE_ON_ERROR); - flags = ((verbose ? ADD_CACHE_VERBOSE : 0) | - (show_only ? ADD_CACHE_PRETEND : 0) | - (intent_to_add ? ADD_CACHE_INTENT : 0) | - (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) | - (!(addremove || take_worktree_changes) - ? ADD_CACHE_IGNORE_REMOVAL : 0)); - - if (require_pathspec && argc == 0) { - fprintf(stderr, _("Nothing specified, nothing added.\n")); - fprintf(stderr, _("Maybe you wanted to say 'git add .'?\n")); - return 0; - } - /* * Check the "pathspec '%s' did not match any files" block * below before enabling new magic. @@ -456,6 +496,38 @@ int cmd_add(int argc, const char **argv, const char *prefix) PATHSPEC_SYMLINK_LEADING_PATH, prefix, argv); + if (pathspec_from_file) { + if (pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&pathspec, PATHSPEC_ATTR, + PATHSPEC_PREFER_FULL | + PATHSPEC_SYMLINK_LEADING_PATH, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + + if (require_pathspec && pathspec.nr == 0) { + fprintf(stderr, _("Nothing specified, nothing added.\n")); + if (advice_add_empty_pathspec) + advise( _("Maybe you wanted to say 'git add .'?\n" + "Turn this message off by running\n" + "\"git config advice.addEmptyPathspec false\"")); + return 0; + } + + if (!take_worktree_changes && addremove_explicit < 0 && pathspec.nr) + /* Turn "git add pathspec..." to "git add -A pathspec..." */ + addremove = 1; + + flags = ((verbose ? ADD_CACHE_VERBOSE : 0) | + (show_only ? ADD_CACHE_PRETEND : 0) | + (intent_to_add ? ADD_CACHE_INTENT : 0) | + (ignore_add_errors ? ADD_CACHE_IGNORE_ERRORS : 0) | + (!(addremove || take_worktree_changes) + ? ADD_CACHE_IGNORE_REMOVAL : 0)); + if (read_cache_preload(&pathspec) < 0) die(_("index file corrupt")); diff --git a/builtin/am.c b/builtin/am.c index 8181c2aef3..e3dfd93c25 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -81,6 +81,11 @@ enum signoff_type { SIGNOFF_EXPLICIT /* --signoff was set on the command-line */ }; +enum show_patch_type { + SHOW_PATCH_RAW = 0, + SHOW_PATCH_DIFF = 1, +}; + struct am_state { /* state directory path */ char *dir; @@ -1763,7 +1768,7 @@ static void am_run(struct am_state *state, int resume) linelen(state->msg), state->msg); if (advice_amworkdir) - advise(_("Use 'git am --show-current-patch' to see the failed patch")); + advise(_("Use 'git am --show-current-patch=diff' to see the failed patch")); die_user_resolve(state); } @@ -2061,7 +2066,7 @@ static void am_abort(struct am_state *state) am_destroy(state); } -static int show_patch(struct am_state *state) +static int show_patch(struct am_state *state, enum show_patch_type sub_mode) { struct strbuf sb = STRBUF_INIT; const char *patch_path; @@ -2078,7 +2083,17 @@ static int show_patch(struct am_state *state) return ret; } - patch_path = am_path(state, msgnum(state)); + switch (sub_mode) { + case SHOW_PATCH_RAW: + patch_path = am_path(state, msgnum(state)); + break; + case SHOW_PATCH_DIFF: + patch_path = am_path(state, "patch"); + break; + default: + BUG("invalid mode for --show-current-patch"); + } + len = strbuf_read_file(&sb, patch_path, 0); if (len < 0) die_errno(_("failed to read '%s'"), patch_path); @@ -2118,7 +2133,7 @@ static int parse_opt_patchformat(const struct option *opt, const char *arg, int return 0; } -enum resume_mode { +enum resume_type { RESUME_FALSE = 0, RESUME_APPLY, RESUME_RESOLVED, @@ -2128,6 +2143,45 @@ enum resume_mode { RESUME_SHOW_PATCH }; +struct resume_mode { + enum resume_type mode; + enum show_patch_type sub_mode; +}; + +static int parse_opt_show_current_patch(const struct option *opt, const char *arg, int unset) +{ + int *opt_value = opt->value; + struct resume_mode *resume = container_of(opt_value, struct resume_mode, mode); + + /* + * Please update $__git_showcurrentpatch in git-completion.bash + * when you add new options + */ + const char *valid_modes[] = { + [SHOW_PATCH_DIFF] = "diff", + [SHOW_PATCH_RAW] = "raw" + }; + int new_value = SHOW_PATCH_RAW; + + if (arg) { + for (new_value = 0; new_value < ARRAY_SIZE(valid_modes); new_value++) { + if (!strcmp(arg, valid_modes[new_value])) + break; + } + if (new_value >= ARRAY_SIZE(valid_modes)) + return error(_("Invalid value for --show-current-patch: %s"), arg); + } + + if (resume->mode == RESUME_SHOW_PATCH && new_value != resume->sub_mode) + return error(_("--show-current-patch=%s is incompatible with " + "--show-current-patch=%s"), + arg, valid_modes[resume->sub_mode]); + + resume->mode = RESUME_SHOW_PATCH; + resume->sub_mode = new_value; + return 0; +} + static int git_am_config(const char *k, const char *v, void *cb) { int status; @@ -2145,7 +2199,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) int binary = -1; int keep_cr = -1; int patch_format = PATCH_FORMAT_UNKNOWN; - enum resume_mode resume = RESUME_FALSE; + struct resume_mode resume = { .mode = RESUME_FALSE }; int in_progress; int ret = 0; @@ -2214,24 +2268,26 @@ int cmd_am(int argc, const char **argv, const char *prefix) PARSE_OPT_NOARG), OPT_STRING(0, "resolvemsg", &state.resolvemsg, NULL, N_("override error message when patch failure occurs")), - OPT_CMDMODE(0, "continue", &resume, + OPT_CMDMODE(0, "continue", &resume.mode, N_("continue applying patches after resolving a conflict"), RESUME_RESOLVED), - OPT_CMDMODE('r', "resolved", &resume, + OPT_CMDMODE('r', "resolved", &resume.mode, N_("synonyms for --continue"), RESUME_RESOLVED), - OPT_CMDMODE(0, "skip", &resume, + OPT_CMDMODE(0, "skip", &resume.mode, N_("skip the current patch"), RESUME_SKIP), - OPT_CMDMODE(0, "abort", &resume, + OPT_CMDMODE(0, "abort", &resume.mode, N_("restore the original branch and abort the patching operation."), RESUME_ABORT), - OPT_CMDMODE(0, "quit", &resume, + OPT_CMDMODE(0, "quit", &resume.mode, N_("abort the patching operation but keep HEAD where it is."), RESUME_QUIT), - OPT_CMDMODE(0, "show-current-patch", &resume, - N_("show the patch being applied."), - RESUME_SHOW_PATCH), + { OPTION_CALLBACK, 0, "show-current-patch", &resume.mode, + "(diff|raw)", + N_("show the patch being applied"), + PARSE_OPT_CMDMODE | PARSE_OPT_OPTARG | PARSE_OPT_NONEG | PARSE_OPT_LITERAL_ARGHELP, + parse_opt_show_current_patch, RESUME_SHOW_PATCH }, OPT_BOOL(0, "committer-date-is-author-date", &state.committer_date_is_author_date, N_("lie about committer date")), @@ -2281,12 +2337,12 @@ int cmd_am(int argc, const char **argv, const char *prefix) * intend to feed us a patch but wanted to continue * unattended. */ - if (argc || (resume == RESUME_FALSE && !isatty(0))) + if (argc || (resume.mode == RESUME_FALSE && !isatty(0))) die(_("previous rebase directory %s still exists but mbox given."), state.dir); - if (resume == RESUME_FALSE) - resume = RESUME_APPLY; + if (resume.mode == RESUME_FALSE) + resume.mode = RESUME_APPLY; if (state.signoff == SIGNOFF_EXPLICIT) am_append_signoff(&state); @@ -2300,7 +2356,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) * stray directories. */ if (file_exists(state.dir) && !state.rebasing) { - if (resume == RESUME_ABORT || resume == RESUME_QUIT) { + if (resume.mode == RESUME_ABORT || resume.mode == RESUME_QUIT) { am_destroy(&state); am_state_release(&state); return 0; @@ -2311,7 +2367,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) state.dir); } - if (resume) + if (resume.mode) die(_("Resolve operation not in progress, we are not resuming.")); for (i = 0; i < argc; i++) { @@ -2329,7 +2385,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) argv_array_clear(&paths); } - switch (resume) { + switch (resume.mode) { case RESUME_FALSE: am_run(&state, 0); break; @@ -2350,7 +2406,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) am_destroy(&state); break; case RESUME_SHOW_PATCH: - ret = show_patch(&state); + ret = show_patch(&state, resume.sub_mode); break; default: BUG("invalid resume value"); diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 1fbe156e67..c1c40b516d 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -52,8 +52,8 @@ static void set_terms(struct bisect_terms *terms, const char *bad, terms->term_bad = xstrdup(bad); } -static const char *vocab_bad = "bad|new"; -static const char *vocab_good = "good|old"; +static const char vocab_bad[] = "bad|new"; +static const char vocab_good[] = "good|old"; /* * Check whether the string `term` belongs to the set of strings @@ -169,11 +169,12 @@ static int bisect_reset(const char *commit) argv_array_pushl(&argv, "checkout", branch.buf, "--", NULL); if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { + error(_("could not check out original" + " HEAD '%s'. Try 'git bisect" + " reset <commit>'."), branch.buf); strbuf_release(&branch); argv_array_clear(&argv); - return error(_("could not check out original" - " HEAD '%s'. Try 'git bisect" - " reset <commit>'."), branch.buf); + return -1; } argv_array_clear(&argv); } @@ -205,31 +206,31 @@ static int bisect_write(const char *state, const char *rev, struct object_id oid; struct commit *commit; FILE *fp = NULL; - int retval = 0; + int res = 0; if (!strcmp(state, terms->term_bad)) { strbuf_addf(&tag, "refs/bisect/%s", state); } else if (one_of(state, terms->term_good, "skip", NULL)) { strbuf_addf(&tag, "refs/bisect/%s-%s", state, rev); } else { - retval = error(_("Bad bisect_write argument: %s"), state); + res = error(_("Bad bisect_write argument: %s"), state); goto finish; } if (get_oid(rev, &oid)) { - retval = error(_("couldn't get the oid of the rev '%s'"), rev); + res = error(_("couldn't get the oid of the rev '%s'"), rev); goto finish; } if (update_ref(NULL, tag.buf, &oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR)) { - retval = -1; + res = -1; goto finish; } fp = fopen(git_path_bisect_log(), "a"); if (!fp) { - retval = error_errno(_("couldn't open the file '%s'"), git_path_bisect_log()); + res = error_errno(_("couldn't open the file '%s'"), git_path_bisect_log()); goto finish; } @@ -243,7 +244,7 @@ finish: if (fp) fclose(fp); strbuf_release(&tag); - return retval; + return res; } static int check_and_set_terms(struct bisect_terms *terms, const char *cmd) @@ -281,35 +282,23 @@ static int mark_good(const char *refname, const struct object_id *oid, return 1; } -static const char *need_bad_and_good_revision_warning = +static const char need_bad_and_good_revision_warning[] = N_("You need to give me at least one %s and %s revision.\n" "You can use \"git bisect %s\" and \"git bisect %s\" for that."); -static const char *need_bisect_start_warning = +static const char need_bisect_start_warning[] = N_("You need to start by \"git bisect start\".\n" "You then need to give me at least one %s and %s revision.\n" "You can use \"git bisect %s\" and \"git bisect %s\" for that."); -static int bisect_next_check(const struct bisect_terms *terms, - const char *current_term) +static int decide_next(const struct bisect_terms *terms, + const char *current_term, int missing_good, + int missing_bad) { - int missing_good = 1, missing_bad = 1, retval = 0; - const char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad); - const char *good_glob = xstrfmt("%s-*", terms->term_good); - - if (ref_exists(bad_ref)) - missing_bad = 0; - - for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/", - (void *) &missing_good); - if (!missing_good && !missing_bad) - goto finish; - - if (!current_term) { - retval = -1; - goto finish; - } + return 0; + if (!current_term) + return -1; if (missing_good && !missing_bad && !strcmp(current_term, terms->term_good)) { @@ -320,7 +309,7 @@ static int bisect_next_check(const struct bisect_terms *terms, */ warning(_("bisecting only with a %s commit"), terms->term_bad); if (!isatty(0)) - goto finish; + return 0; /* * TRANSLATORS: Make sure to include [Y] and [n] in your * translation. The program will only accept English input @@ -328,21 +317,35 @@ static int bisect_next_check(const struct bisect_terms *terms, */ yesno = git_prompt(_("Are you sure [Y/n]? "), PROMPT_ECHO); if (starts_with(yesno, "N") || starts_with(yesno, "n")) - retval = -1; - goto finish; - } - if (!is_empty_or_missing_file(git_path_bisect_start())) { - retval = error(_(need_bad_and_good_revision_warning), - vocab_bad, vocab_good, vocab_bad, vocab_good); - } else { - retval = error(_(need_bisect_start_warning), - vocab_good, vocab_bad, vocab_good, vocab_bad); + return -1; + return 0; } -finish: - free((void *) good_glob); - free((void *) bad_ref); - return retval; + if (!is_empty_or_missing_file(git_path_bisect_start())) + return error(_(need_bad_and_good_revision_warning), + vocab_bad, vocab_good, vocab_bad, vocab_good); + else + return error(_(need_bisect_start_warning), + vocab_good, vocab_bad, vocab_good, vocab_bad); +} + +static int bisect_next_check(const struct bisect_terms *terms, + const char *current_term) +{ + int missing_good = 1, missing_bad = 1; + char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad); + char *good_glob = xstrfmt("%s-*", terms->term_good); + + if (ref_exists(bad_ref)) + missing_bad = 0; + + for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/", + (void *) &missing_good); + + free(good_glob); + free(bad_ref); + + return decide_next(terms, current_term, missing_good, missing_bad); } static int get_terms(struct bisect_terms *terms) @@ -396,7 +399,7 @@ static int bisect_terms(struct bisect_terms *terms, const char *option) static int bisect_append_log_quoted(const char **argv) { - int retval = 0; + int res = 0; FILE *fp = fopen(git_path_bisect_log(), "a"); struct strbuf orig_args = STRBUF_INIT; @@ -404,25 +407,25 @@ static int bisect_append_log_quoted(const char **argv) return -1; if (fprintf(fp, "git bisect start") < 1) { - retval = -1; + res = -1; goto finish; } sq_quote_argv(&orig_args, argv); if (fprintf(fp, "%s\n", orig_args.buf) < 1) - retval = -1; + res = -1; finish: fclose(fp); strbuf_release(&orig_args); - return retval; + return res; } static int bisect_start(struct bisect_terms *terms, int no_checkout, const char **argv, int argc) { int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0; - int flags, pathspec_pos, retval = 0; + int flags, pathspec_pos, res = 0; struct string_list revs = STRING_LIST_INIT_DUP; struct string_list states = STRING_LIST_INIT_DUP; struct strbuf start_head = STRBUF_INIT; @@ -523,7 +526,7 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout, argv_array_pushl(&argv, "checkout", start_head.buf, "--", NULL); if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { - retval = error(_("checking out '%s' failed." + res = error(_("checking out '%s' failed." " Try 'git bisect start " "<valid-branch>'."), start_head.buf); @@ -571,12 +574,12 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout, if (no_checkout) { if (get_oid(start_head.buf, &oid) < 0) { - retval = error(_("invalid ref: '%s'"), start_head.buf); + res = error(_("invalid ref: '%s'"), start_head.buf); goto finish; } if (update_ref(NULL, "BISECT_HEAD", &oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR)) { - retval = -1; + res = -1; goto finish; } } @@ -588,26 +591,26 @@ static int bisect_start(struct bisect_terms *terms, int no_checkout, for (i = 0; i < states.nr; i++) if (bisect_write(states.items[i].string, revs.items[i].string, terms, 1)) { - retval = -1; + res = -1; goto finish; } if (must_write_terms && write_terms(terms->term_bad, terms->term_good)) { - retval = -1; + res = -1; goto finish; } - retval = bisect_append_log_quoted(argv); - if (retval) - retval = -1; + res = bisect_append_log_quoted(argv); + if (res) + res = -1; finish: string_list_clear(&revs, 0); string_list_clear(&states, 0); strbuf_release(&start_head); strbuf_release(&bisect_names); - return retval; + return res; } int cmd_bisect__helper(int argc, const char **argv, const char *prefix) @@ -663,7 +666,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) switch (cmdmode) { case NEXT_ALL: - return bisect_next_all(the_repository, prefix, no_checkout); + res = bisect_next_all(the_repository, prefix, no_checkout); + break; case WRITE_TERMS: if (argc != 2) return error(_("--write-terms requires two arguments")); @@ -710,5 +714,13 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) return error("BUG: unknown subcommand '%d'", cmdmode); } free_terms(&terms); - return !!res; + + /* + * Handle early success + * From check_merge_bases > check_good_are_ancestors_of_bad > bisect_next_all + */ + if (res == BISECT_INTERNAL_SUCCESS_MERGE_BASE) + res = BISECT_OK; + + return abs(res); } diff --git a/builtin/blame.c b/builtin/blame.c index e946ba6cd9..bf1cecdf3f 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -319,18 +319,18 @@ static const char *format_time(timestamp_t time, const char *tz_str, return time_buf.buf; } -#define OUTPUT_ANNOTATE_COMPAT 001 -#define OUTPUT_LONG_OBJECT_NAME 002 -#define OUTPUT_RAW_TIMESTAMP 004 -#define OUTPUT_PORCELAIN 010 -#define OUTPUT_SHOW_NAME 020 -#define OUTPUT_SHOW_NUMBER 040 -#define OUTPUT_SHOW_SCORE 0100 -#define OUTPUT_NO_AUTHOR 0200 -#define OUTPUT_SHOW_EMAIL 0400 -#define OUTPUT_LINE_PORCELAIN 01000 -#define OUTPUT_COLOR_LINE 02000 -#define OUTPUT_SHOW_AGE_WITH_COLOR 04000 +#define OUTPUT_ANNOTATE_COMPAT (1U<<0) +#define OUTPUT_LONG_OBJECT_NAME (1U<<1) +#define OUTPUT_RAW_TIMESTAMP (1U<<2) +#define OUTPUT_PORCELAIN (1U<<3) +#define OUTPUT_SHOW_NAME (1U<<4) +#define OUTPUT_SHOW_NUMBER (1U<<5) +#define OUTPUT_SHOW_SCORE (1U<<6) +#define OUTPUT_NO_AUTHOR (1U<<7) +#define OUTPUT_SHOW_EMAIL (1U<<8) +#define OUTPUT_LINE_PORCELAIN (1U<<9) +#define OUTPUT_COLOR_LINE (1U<<10) +#define OUTPUT_SHOW_AGE_WITH_COLOR (1U<<11) static void emit_porcelain_details(struct blame_origin *suspect, int repeat) { @@ -861,14 +861,6 @@ int cmd_blame(int argc, const char **argv, const char *prefix) OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("Ignore revisions from <file>")), OPT_BIT(0, "color-lines", &output_option, N_("color redundant metadata from previous line differently"), OUTPUT_COLOR_LINE), OPT_BIT(0, "color-by-age", &output_option, N_("color lines by age"), OUTPUT_SHOW_AGE_WITH_COLOR), - - /* - * The following two options are parsed by parse_revision_opt() - * and are only included here to get included in the "-h" - * output: - */ - { OPTION_LOWLEVEL_CALLBACK, 0, "indent-heuristic", NULL, NULL, N_("Use an experimental heuristic to improve diffs"), PARSE_OPT_NOARG, NULL, 0, parse_opt_unknown_cb }, - OPT_BIT(0, "minimal", &xdl_opts, N_("Spend extra cycles to find better match"), XDF_NEED_MINIMAL), OPT_STRING('S', NULL, &revs_file, N_("file"), N_("Use revisions from <file> instead of calling git-rev-list")), OPT_STRING(0, "contents", &contents_from, N_("file"), N_("Use <file>'s contents as the final image")), diff --git a/builtin/branch.c b/builtin/branch.c index 2ef214632f..d8297f80ff 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -624,7 +624,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_SET_INT_F(0, "set-upstream", &track, N_("do not use"), BRANCH_TRACK_OVERRIDE, PARSE_OPT_HIDDEN), OPT_STRING('u', "set-upstream-to", &new_upstream, N_("upstream"), N_("change the upstream info")), - OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("Unset the upstream info")), + OPT_BOOL(0, "unset-upstream", &unset_upstream, N_("unset the upstream info")), OPT__COLOR(&branch_use_color, N_("use colored output")), OPT_SET_INT('r', "remotes", &filter.kind, N_("act on remote-tracking branches"), FILTER_REFS_REMOTES), diff --git a/builtin/bundle.c b/builtin/bundle.c index 1ea4bfdfc1..f049d27a14 100644 --- a/builtin/bundle.c +++ b/builtin/bundle.c @@ -1,4 +1,6 @@ #include "builtin.h" +#include "argv-array.h" +#include "parse-options.h" #include "cache.h" #include "bundle.h" @@ -9,59 +11,184 @@ * bundle supporting "fetch", "pull", and "ls-remote". */ -static const char builtin_bundle_usage[] = - "git bundle create <file> <git-rev-list args>\n" - " or: git bundle verify <file>\n" - " or: git bundle list-heads <file> [<refname>...]\n" - " or: git bundle unbundle <file> [<refname>...]"; +static const char * const builtin_bundle_usage[] = { + N_("git bundle create [<options>] <file> <git-rev-list args>"), + N_("git bundle verify [<options>] <file>"), + N_("git bundle list-heads <file> [<refname>...]"), + N_("git bundle unbundle <file> [<refname>...]"), + NULL +}; -int cmd_bundle(int argc, const char **argv, const char *prefix) -{ +static const char * const builtin_bundle_create_usage[] = { + N_("git bundle create [<options>] <file> <git-rev-list args>"), + NULL +}; + +static const char * const builtin_bundle_verify_usage[] = { + N_("git bundle verify [<options>] <file>"), + NULL +}; + +static const char * const builtin_bundle_list_heads_usage[] = { + N_("git bundle list-heads <file> [<refname>...]"), + NULL +}; + +static const char * const builtin_bundle_unbundle_usage[] = { + N_("git bundle unbundle <file> [<refname>...]"), + NULL +}; + +static int verbose; + +static int parse_options_cmd_bundle(int argc, + const char **argv, + const char* prefix, + const char * const usagestr[], + const struct option options[], + const char **bundle_file) { + int newargc; + newargc = parse_options(argc, argv, NULL, options, usagestr, + PARSE_OPT_STOP_AT_NON_OPTION); + if (argc < 1) + usage_with_options(usagestr, options); + *bundle_file = prefix_filename(prefix, argv[0]); + return newargc; +} + +static int cmd_bundle_create(int argc, const char **argv, const char *prefix) { + int all_progress_implied = 0; + int progress = isatty(STDERR_FILENO); + struct argv_array pack_opts; + + struct option options[] = { + OPT_SET_INT('q', "quiet", &progress, + N_("do not show progress meter"), 0), + OPT_SET_INT(0, "progress", &progress, + N_("show progress meter"), 1), + OPT_SET_INT(0, "all-progress", &progress, + N_("show progress meter during object writing phase"), 2), + OPT_BOOL(0, "all-progress-implied", + &all_progress_implied, + N_("similar to --all-progress when progress meter is shown")), + OPT_END() + }; + const char* bundle_file; + + argc = parse_options_cmd_bundle(argc, argv, prefix, + builtin_bundle_create_usage, options, &bundle_file); + /* bundle internals use argv[1] as further parameters */ + + argv_array_init(&pack_opts); + if (progress == 0) + argv_array_push(&pack_opts, "--quiet"); + else if (progress == 1) + argv_array_push(&pack_opts, "--progress"); + else if (progress == 2) + argv_array_push(&pack_opts, "--all-progress"); + if (progress && all_progress_implied) + argv_array_push(&pack_opts, "--all-progress-implied"); + + if (!startup_info->have_repository) + die(_("Need a repository to create a bundle.")); + return !!create_bundle(the_repository, bundle_file, argc, argv, &pack_opts); +} + +static int cmd_bundle_verify(int argc, const char **argv, const char *prefix) { struct bundle_header header; - const char *cmd, *bundle_file; int bundle_fd = -1; + int quiet = 0; - if (argc < 3) - usage(builtin_bundle_usage); + struct option options[] = { + OPT_BOOL('q', "quiet", &quiet, + N_("do not show bundle details")), + OPT_END() + }; + const char* bundle_file; - cmd = argv[1]; - bundle_file = prefix_filename(prefix, argv[2]); - argc -= 2; - argv += 2; + argc = parse_options_cmd_bundle(argc, argv, prefix, + builtin_bundle_verify_usage, options, &bundle_file); + /* bundle internals use argv[1] as further parameters */ memset(&header, 0, sizeof(header)); - if (strcmp(cmd, "create") && (bundle_fd = - read_bundle_header(bundle_file, &header)) < 0) + if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) + return 1; + close(bundle_fd); + if (verify_bundle(the_repository, &header, !quiet)) return 1; + fprintf(stderr, _("%s is okay\n"), bundle_file); + return 0; +} - if (!strcmp(cmd, "verify")) { - close(bundle_fd); - if (argc != 1) { - usage(builtin_bundle_usage); - return 1; - } - if (verify_bundle(the_repository, &header, 1)) - return 1; - fprintf(stderr, _("%s is okay\n"), bundle_file); - return 0; - } - if (!strcmp(cmd, "list-heads")) { - close(bundle_fd); - return !!list_bundle_refs(&header, argc, argv); +static int cmd_bundle_list_heads(int argc, const char **argv, const char *prefix) { + struct bundle_header header; + int bundle_fd = -1; + + struct option options[] = { + OPT_END() + }; + const char* bundle_file; + + argc = parse_options_cmd_bundle(argc, argv, prefix, + builtin_bundle_list_heads_usage, options, &bundle_file); + /* bundle internals use argv[1] as further parameters */ + + memset(&header, 0, sizeof(header)); + if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) + return 1; + close(bundle_fd); + return !!list_bundle_refs(&header, argc, argv); +} + +static int cmd_bundle_unbundle(int argc, const char **argv, const char *prefix) { + struct bundle_header header; + int bundle_fd = -1; + + struct option options[] = { + OPT_END() + }; + const char* bundle_file; + + argc = parse_options_cmd_bundle(argc, argv, prefix, + builtin_bundle_unbundle_usage, options, &bundle_file); + /* bundle internals use argv[1] as further parameters */ + + memset(&header, 0, sizeof(header)); + if ((bundle_fd = read_bundle_header(bundle_file, &header)) < 0) + return 1; + if (!startup_info->have_repository) + die(_("Need a repository to unbundle.")); + return !!unbundle(the_repository, &header, bundle_fd, 0) || + list_bundle_refs(&header, argc, argv); +} + +int cmd_bundle(int argc, const char **argv, const char *prefix) +{ + struct option options[] = { + OPT__VERBOSE(&verbose, N_("be verbose; must be placed before a subcommand")), + OPT_END() + }; + int result; + + argc = parse_options(argc, argv, prefix, options, builtin_bundle_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + packet_trace_identity("bundle"); + + if (argc < 2) + usage_with_options(builtin_bundle_usage, options); + + else if (!strcmp(argv[0], "create")) + result = cmd_bundle_create(argc, argv, prefix); + else if (!strcmp(argv[0], "verify")) + result = cmd_bundle_verify(argc, argv, prefix); + else if (!strcmp(argv[0], "list-heads")) + result = cmd_bundle_list_heads(argc, argv, prefix); + else if (!strcmp(argv[0], "unbundle")) + result = cmd_bundle_unbundle(argc, argv, prefix); + else { + error(_("Unknown subcommand: %s"), argv[0]); + usage_with_options(builtin_bundle_usage, options); } - if (!strcmp(cmd, "create")) { - if (argc < 2) { - usage(builtin_bundle_usage); - return 1; - } - if (!startup_info->have_repository) - die(_("Need a repository to create a bundle.")); - return !!create_bundle(the_repository, bundle_file, argc, argv); - } else if (!strcmp(cmd, "unbundle")) { - if (!startup_info->have_repository) - die(_("Need a repository to unbundle.")); - return !!unbundle(the_repository, &header, bundle_fd, 0) || - list_bundle_refs(&header, argc, argv); - } else - usage(builtin_bundle_usage); + return result ? 1 : 0; } diff --git a/builtin/cat-file.c b/builtin/cat-file.c index d6a1aa74cd..6ecc8ee6dc 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -42,7 +42,10 @@ static int filter_object(const char *path, unsigned mode, oid_to_hex(oid), path); if ((type == OBJ_BLOB) && S_ISREG(mode)) { struct strbuf strbuf = STRBUF_INIT; - if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf)) { + struct checkout_metadata meta; + + init_checkout_metadata(&meta, NULL, NULL, oid); + if (convert_to_working_tree(&the_index, path, *buf, *size, &strbuf, &meta)) { free(*buf); *size = strbuf.len; *buf = strbuf_detach(&strbuf, NULL); @@ -262,7 +265,7 @@ static void expand_atom(struct strbuf *sb, const char *atom, int len, strbuf_addstr(sb, data->rest); } else if (is_atom("deltabase", atom, len)) { if (data->mark_query) - data->info.delta_base_sha1 = data->delta_base_oid.hash; + data->info.delta_base_oid = &data->delta_base_oid; else strbuf_addstr(sb, oid_to_hex(&data->delta_base_oid)); diff --git a/builtin/check-ignore.c b/builtin/check-ignore.c index 5a4f92395f..ea5d0ae3a6 100644 --- a/builtin/check-ignore.c +++ b/builtin/check-ignore.c @@ -108,6 +108,9 @@ static int check_ignore(struct dir_struct *dir, int dtype = DT_UNKNOWN; pattern = last_matching_pattern(dir, &the_index, full_path, &dtype); + if (!verbose && pattern && + pattern->flags & PATTERN_FLAG_NEGATIVE) + pattern = NULL; } if (!quiet && (pattern || show_non_matching)) output_pattern(pathspec.items[i].original, pattern); diff --git a/builtin/checkout.c b/builtin/checkout.c index 3634a3dac1..8bc94d392b 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -70,6 +70,8 @@ struct checkout_opts { int checkout_worktree; const char *ignore_unmerged_opt; int ignore_unmerged; + int pathspec_file_nul; + const char *pathspec_from_file; const char *new_branch; const char *new_branch_force; @@ -86,6 +88,19 @@ struct checkout_opts { struct tree *source_tree; }; +struct branch_info { + const char *name; /* The short name used */ + const char *path; /* The full name of a real branch */ + struct commit *commit; /* The named commit */ + char *refname; /* The full name of the ref being checked out. */ + struct object_id oid; /* The object ID of the commit being checked out. */ + /* + * if not null the branch is detached because it's already + * checked out in this checkout + */ + char *checkout; +}; + static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit, int changed) { @@ -335,7 +350,8 @@ static void mark_ce_for_checkout_no_overlay(struct cache_entry *ce, } } -static int checkout_worktree(const struct checkout_opts *opts) +static int checkout_worktree(const struct checkout_opts *opts, + const struct branch_info *info) { struct checkout state = CHECKOUT_INIT; int nr_checkouts = 0, nr_unmerged = 0; @@ -346,6 +362,10 @@ static int checkout_worktree(const struct checkout_opts *opts) state.refresh_cache = 1; state.istate = &the_index; + init_checkout_metadata(&state.meta, info->refname, + info->commit ? &info->commit->object.oid : &info->oid, + NULL); + enable_delayed_checkout(&state); for (pos = 0; pos < active_nr; pos++) { struct cache_entry *ce = active_cache[pos]; @@ -394,7 +414,7 @@ static int checkout_worktree(const struct checkout_opts *opts) } static int checkout_paths(const struct checkout_opts *opts, - const char *revision) + const struct branch_info *new_branch_info) { int pos; static char *ps_matched; @@ -460,7 +480,7 @@ static int checkout_paths(const struct checkout_opts *opts, else BUG("either flag must have been set, worktree=%d, index=%d", opts->checkout_worktree, opts->checkout_index); - return run_add_interactive(revision, patch_mode, &opts->pathspec); + return run_add_interactive(new_branch_info->name, patch_mode, &opts->pathspec); } repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); @@ -521,7 +541,9 @@ static int checkout_paths(const struct checkout_opts *opts, /* Now we are committed to check them out */ if (opts->checkout_worktree) - errs |= checkout_worktree(opts); + errs |= checkout_worktree(opts, new_branch_info); + else + remove_marked_cache_entries(&the_index, 1); /* * Allow updating the index when checking out from the index. @@ -582,7 +604,8 @@ static void describe_detached_head(const char *msg, struct commit *commit) } static int reset_tree(struct tree *tree, const struct checkout_opts *o, - int worktree, int *writeout_error) + int worktree, int *writeout_error, + struct branch_info *info) { struct unpack_trees_options opts; struct tree_desc tree_desc; @@ -597,6 +620,11 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o, opts.verbose_update = o->show_progress; opts.src_index = &the_index; opts.dst_index = &the_index; + init_checkout_metadata(&opts.meta, info->refname, + info->commit ? &info->commit->object.oid : + is_null_oid(&info->oid) ? &tree->object.oid : + &info->oid, + NULL); parse_tree(tree); init_tree_desc(&tree_desc, tree->buffer, tree->size); switch (unpack_trees(1, &tree_desc, &opts)) { @@ -616,21 +644,17 @@ static int reset_tree(struct tree *tree, const struct checkout_opts *o, } } -struct branch_info { - const char *name; /* The short name used */ - const char *path; /* The full name of a real branch */ - struct commit *commit; /* The named commit */ - /* - * if not null the branch is detached because it's already - * checked out in this checkout - */ - char *checkout; -}; - static void setup_branch_path(struct branch_info *branch) { struct strbuf buf = STRBUF_INIT; + /* + * If this is a ref, resolve it; otherwise, look up the OID for our + * expression. Failure here is okay. + */ + if (!dwim_ref(branch->name, strlen(branch->name), &branch->oid, &branch->refname)) + repo_get_oid_committish(the_repository, branch->name, &branch->oid); + strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL); if (strcmp(buf.buf, branch->name)) branch->name = xstrdup(buf.buf); @@ -659,7 +683,7 @@ static int merge_working_tree(const struct checkout_opts *opts, } else new_tree = get_commit_tree(new_branch_info->commit); if (opts->discard_changes) { - ret = reset_tree(new_tree, opts, 1, writeout_error); + ret = reset_tree(new_tree, opts, 1, writeout_error, new_branch_info); if (ret) return ret; } else { @@ -688,6 +712,10 @@ static int merge_working_tree(const struct checkout_opts *opts, topts.quiet = opts->merge && old_branch_info->commit; topts.verbose_update = opts->show_progress; topts.fn = twoway_merge; + init_checkout_metadata(&topts.meta, new_branch_info->refname, + new_branch_info->commit ? + &new_branch_info->commit->object.oid : + &new_branch_info->oid, NULL); if (opts->overwrite_ignore) { topts.dir = xcalloc(1, sizeof(*topts.dir)); topts.dir->flags |= DIR_SHOW_IGNORED; @@ -758,7 +786,7 @@ static int merge_working_tree(const struct checkout_opts *opts, ret = reset_tree(new_tree, opts, 1, - writeout_error); + writeout_error, new_branch_info); if (ret) return ret; o.ancestor = old_branch_info->name; @@ -778,7 +806,7 @@ static int merge_working_tree(const struct checkout_opts *opts, exit(128); ret = reset_tree(new_tree, opts, 0, - writeout_error); + writeout_error, new_branch_info); strbuf_release(&o.obuf); strbuf_release(&old_commit_shortname); if (ret) @@ -861,7 +889,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts, strbuf_addf(&msg, "checkout: moving from %s to %s", old_desc ? old_desc : "(invalid)", new_branch_info->name); else - strbuf_insert(&msg, 0, reflog_msg, strlen(reflog_msg)); + strbuf_insertstr(&msg, 0, reflog_msg); if (!strcmp(new_branch_info->name, "HEAD") && !new_branch_info->path && !opts->force_detach) { /* Nothing to do. */ @@ -1113,12 +1141,43 @@ static void setup_new_branch_info_and_source_tree( } } +static const char *parse_remote_branch(const char *arg, + struct object_id *rev, + int could_be_checkout_paths) +{ + int num_matches = 0; + const char *remote = unique_tracking_name(arg, rev, &num_matches); + + if (remote && could_be_checkout_paths) { + die(_("'%s' could be both a local file and a tracking branch.\n" + "Please use -- (and optionally --no-guess) to disambiguate"), + arg); + } + + if (!remote && num_matches > 1) { + if (advice_checkout_ambiguous_remote_branch_name) { + advise(_("If you meant to check out a remote tracking branch on, e.g. 'origin',\n" + "you can do so by fully qualifying the name with the --track option:\n" + "\n" + " git checkout --track origin/<name>\n" + "\n" + "If you'd like to always have checkouts of an ambiguous <name> prefer\n" + "one remote, e.g. the 'origin' remote, consider setting\n" + "checkout.defaultRemote=origin in your config.")); + } + + die(_("'%s' matched multiple (%d) remote tracking branches"), + arg, num_matches); + } + + return remote; +} + static int parse_branchname_arg(int argc, const char **argv, int dwim_new_local_branch_ok, struct branch_info *new_branch_info, struct checkout_opts *opts, - struct object_id *rev, - int *dwim_remotes_matched) + struct object_id *rev) { const char **new_branch = &opts->new_branch; int argcount = 0; @@ -1223,13 +1282,9 @@ static int parse_branchname_arg(int argc, const char **argv, recover_with_dwim = 0; if (recover_with_dwim) { - const char *remote = unique_tracking_name(arg, rev, - dwim_remotes_matched); + const char *remote = parse_remote_branch(arg, rev, + could_be_checkout_paths); if (remote) { - if (could_be_checkout_paths) - die(_("'%s' could be both a local file and a tracking branch.\n" - "Please use -- (and optionally --no-guess) to disambiguate"), - arg); *new_branch = arg; arg = remote; /* DWIMmed to create local branch, case (3).(b) */ @@ -1480,6 +1535,8 @@ static struct option *add_checkout_path_options(struct checkout_opts *opts, OPT_BOOL('p', "patch", &opts->patch_mode, N_("select hunks interactively")), OPT_BOOL(0, "ignore-skip-worktree-bits", &opts->ignore_skipworktree, N_("do not limit pathspecs to sparse entries only")), + OPT_PATHSPEC_FROM_FILE(&opts->pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&opts->pathspec_file_nul), OPT_END() }; struct option *newopts = parse_options_concat(prevopts, options); @@ -1492,7 +1549,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix, const char * const usagestr[]) { struct branch_info new_branch_info; - int dwim_remotes_matched = 0; int parseopt_flags = 0; memset(&new_branch_info, 0, sizeof(new_branch_info)); @@ -1600,8 +1656,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix, opts->track == BRANCH_TRACK_UNSPECIFIED && !opts->new_branch; int n = parse_branchname_arg(argc, argv, dwim_ok, - &new_branch_info, opts, &rev, - &dwim_remotes_matched); + &new_branch_info, opts, &rev); argv += n; argc -= n; } else if (!opts->accept_ref && opts->from_treeish) { @@ -1618,10 +1673,6 @@ static int checkout_main(int argc, const char **argv, const char *prefix, die(_("reference is not a tree: %s"), opts->from_treeish); } - if (opts->accept_pathspec && !opts->empty_pathspec_ok && !argc && - !opts->patch_mode) /* patch mode is special */ - die(_("you must specify path(s) to restore")); - if (argc) { parse_pathspec(&opts->pathspec, 0, opts->patch_mode ? PATHSPEC_PREFIX_ORIGIN : 0, @@ -1641,10 +1692,33 @@ static int checkout_main(int argc, const char **argv, const char *prefix, if (opts->force_detach) die(_("git checkout: --detach does not take a path argument '%s'"), argv[0]); + } + + if (opts->pathspec_from_file) { + if (opts->pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + if (opts->force_detach) + die(_("--pathspec-from-file is incompatible with --detach")); + + if (opts->patch_mode) + die(_("--pathspec-from-file is incompatible with --patch")); + parse_pathspec_file(&opts->pathspec, 0, + 0, + prefix, opts->pathspec_from_file, opts->pathspec_file_nul); + } else if (opts->pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + + if (opts->pathspec.nr) { if (1 < !!opts->writeout_stage + !!opts->force + !!opts->merge) die(_("git checkout: --ours/--theirs, --force and --merge are incompatible when\n" "checking out of the index.")); + } else { + if (opts->accept_pathspec && !opts->empty_pathspec_ok && + !opts->patch_mode) /* patch mode is special */ + die(_("you must specify path(s) to restore")); } if (opts->new_branch) { @@ -1659,28 +1733,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix, } UNLEAK(opts); - if (opts->patch_mode || opts->pathspec.nr) { - int ret = checkout_paths(opts, new_branch_info.name); - if (ret && dwim_remotes_matched > 1 && - advice_checkout_ambiguous_remote_branch_name) - advise(_("'%s' matched more than one remote tracking branch.\n" - "We found %d remotes with a reference that matched. So we fell back\n" - "on trying to resolve the argument as a path, but failed there too!\n" - "\n" - "If you meant to check out a remote tracking branch on, e.g. 'origin',\n" - "you can do so by fully qualifying the name with the --track option:\n" - "\n" - " git checkout --track origin/<name>\n" - "\n" - "If you'd like to always have checkouts of an ambiguous <name> prefer\n" - "one remote, e.g. the 'origin' remote, consider setting\n" - "checkout.defaultRemote=origin in your config."), - argv[0], - dwim_remotes_matched); - return ret; - } else { + if (opts->patch_mode || opts->pathspec.nr) + return checkout_paths(opts, &new_branch_info); + else return checkout_branch(opts, &new_branch_info); - } } int cmd_checkout(int argc, const char **argv, const char *prefix) diff --git a/builtin/clone.c b/builtin/clone.c index c46ee29f0a..d8b1f413aa 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -59,6 +59,7 @@ static const char *real_git_dir; static char *option_upload_pack = "git-upload-pack"; static int option_verbosity; static int option_progress = -1; +static int option_sparse_checkout; static enum transport_family family; static struct string_list option_config = STRING_LIST_INIT_NODUP; static struct string_list option_required_reference = STRING_LIST_INIT_NODUP; @@ -146,6 +147,8 @@ static struct option builtin_clone_options[] = { OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), OPT_BOOL(0, "remote-submodules", &option_remote_submodules, N_("any cloned submodules will use their remote-tracking branch")), + OPT_BOOL(0, "sparse", &option_sparse_checkout, + N_("initialize sparse-checkout file to include only files at root")), OPT_END() }; @@ -417,6 +420,7 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, struct dir_iterator *iter; int iter_status; unsigned int flags; + struct strbuf realpath = STRBUF_INIT; mkdir_if_missing(dest->buf, 0777); @@ -451,7 +455,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, if (unlink(dest->buf) && errno != ENOENT) die_errno(_("failed to unlink '%s'"), dest->buf); if (!option_no_hardlinks) { - if (!link(real_path(src->buf), dest->buf)) + strbuf_realpath(&realpath, src->buf, 1); + if (!link(realpath.buf, dest->buf)) continue; if (option_local > 0) die_errno(_("failed to create link '%s'"), dest->buf); @@ -465,6 +470,8 @@ static void copy_or_link_directory(struct strbuf *src, struct strbuf *dest, strbuf_setlen(src, src_len); die(_("failed to iterate over '%s'"), src->buf); } + + strbuf_release(&realpath); } static void clone_local(const char *src_repo, const char *dest_repo) @@ -670,7 +677,7 @@ static void update_remote_refs(const struct ref *refs, const char *msg, struct transport *transport, int check_connectivity, - int check_refs_only) + int check_refs_are_promisor_objects_only) { const struct ref *rm = mapped_refs; @@ -679,7 +686,8 @@ static void update_remote_refs(const struct ref *refs, opt.transport = transport; opt.progress = transport->progress; - opt.check_refs_only = !!check_refs_only; + opt.check_refs_are_promisor_objects_only = + !!check_refs_are_promisor_objects_only; if (check_connected(iterate_ref_map, &rm, &opt)) die(_("remote did not send all necessary objects")); @@ -733,6 +741,27 @@ static void update_head(const struct ref *our, const struct ref *remote, } } +static int git_sparse_checkout_init(const char *repo) +{ + struct argv_array argv = ARGV_ARRAY_INIT; + int result = 0; + argv_array_pushl(&argv, "-C", repo, "sparse-checkout", "init", NULL); + + /* + * We must apply the setting in the current process + * for the later checkout to use the sparse-checkout file. + */ + core_apply_sparse_checkout = 1; + + if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { + error(_("failed to initialize sparse-checkout")); + result = 1; + } + + argv_array_clear(&argv); + return result; +} + static int checkout(int submodule_progress) { struct object_id oid; @@ -755,11 +784,11 @@ static int checkout(int submodule_progress) if (!strcmp(head, "HEAD")) { if (advice_detached_head) detach_advice(oid_to_hex(&oid)); + FREE_AND_NULL(head); } else { if (!starts_with(head, "refs/heads/")) die(_("HEAD not found below refs/heads!")); } - free(head); /* We need to be in the new work tree for the checkout */ setup_work_tree(); @@ -774,6 +803,7 @@ static int checkout(int submodule_progress) opts.verbose_update = (option_verbosity >= 0); opts.src_index = &the_index; opts.dst_index = &the_index; + init_checkout_metadata(&opts.meta, head, &oid, NULL); tree = parse_tree_indirect(&oid); parse_tree(tree); @@ -781,6 +811,8 @@ static int checkout(int submodule_progress) if (unpack_trees(1, &t, &opts) < 0) die(_("unable to checkout working tree")); + free(head); + if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); @@ -789,7 +821,7 @@ static int checkout(int submodule_progress) if (!err && (option_recurse_submodules.nr > 0)) { struct argv_array args = ARGV_ARRAY_INIT; - argv_array_pushl(&args, "submodule", "update", "--init", "--recursive", NULL); + argv_array_pushl(&args, "submodule", "update", "--require-init", "--recursive", NULL); if (option_shallow_submodules == 1) argv_array_push(&args, "--depth=1"); @@ -808,6 +840,11 @@ static int checkout(int submodule_progress) argv_array_push(&args, "--no-fetch"); } + if (option_single_branch >= 0) + argv_array_push(&args, option_single_branch ? + "--single-branch" : + "--no-single-branch"); + err = run_command_v_opt(args.argv, RUN_GIT_CMD); argv_array_clear(&args); } @@ -899,7 +936,7 @@ static void dissociate_from_references(void) free(alternates); } -static int dir_exists(const char *path) +static int path_exists(const char *path) { struct stat sb; return !stat(path, &sb); @@ -927,8 +964,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) struct argv_array ref_prefixes = ARGV_ARRAY_INIT; - fetch_if_missing = 0; - packet_trace_identity("clone"); argc = parse_options(argc, argv, prefix, builtin_clone_options, builtin_clone_usage, 0); @@ -981,7 +1016,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) dir = guess_dir_name(repo_name, is_bundle, option_bare); strip_trailing_slashes(dir); - dest_exists = dir_exists(dir); + dest_exists = path_exists(dir); if (dest_exists && !is_empty_dir(dir)) die(_("destination path '%s' already exists and is not " "an empty directory."), dir); @@ -992,7 +1027,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) work_tree = NULL; else { work_tree = getenv("GIT_WORK_TREE"); - if (work_tree && dir_exists(work_tree)) + if (work_tree && path_exists(work_tree)) die(_("working tree '%s' already exists."), work_tree); } @@ -1020,7 +1055,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } if (real_git_dir) { - if (dir_exists(real_git_dir)) + if (path_exists(real_git_dir)) junk_git_dir_flags |= REMOVE_DIR_KEEP_TOPLEVEL; junk_git_dir = real_git_dir; } else { @@ -1074,7 +1109,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } } - init_db(git_dir, real_git_dir, option_template, INIT_DB_QUIET); + init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, INIT_DB_QUIET); if (real_git_dir) git_dir = real_git_dir; @@ -1106,6 +1141,9 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_required_reference.nr || option_optional_reference.nr) setup_reference(); + if (option_sparse_checkout && git_sparse_checkout_init(dir)) + return 1; + remote = remote_get(option_origin); strbuf_addf(&default_refspec, "+%s*:%s*", src_ref_prefix, @@ -1265,7 +1303,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) } junk_mode = JUNK_LEAVE_REPO; - fetch_if_missing = 1; err = checkout(submodule_progress); strbuf_release(&reflog_msg); diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index addc8d4cc0..d1ab6625f6 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -8,8 +8,6 @@ #include "object-store.h" static char const * const builtin_commit_graph_usage[] = { - N_("git commit-graph [--object-dir <objdir>]"), - N_("git commit-graph read [--object-dir <objdir>]"), N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"), N_("git commit-graph write [--object-dir <objdir>] [--append|--split] [--reachable|--stdin-packs|--stdin-commits] [--[no-]progress] <split options>"), NULL @@ -20,11 +18,6 @@ static const char * const builtin_commit_graph_verify_usage[] = { NULL }; -static const char * const builtin_commit_graph_read_usage[] = { - N_("git commit-graph read [--object-dir <objdir>]"), - NULL -}; - static const char * const builtin_commit_graph_write_usage[] = { N_("git commit-graph write [--object-dir <objdir>] [--append|--split] [--reachable|--stdin-packs|--stdin-commits] [--[no-]progress] <split options>"), NULL @@ -41,9 +34,32 @@ static struct opts_commit_graph { int progress; } opts; +static struct object_directory *find_odb(struct repository *r, + const char *obj_dir) +{ + struct object_directory *odb; + char *obj_dir_real = real_pathdup(obj_dir, 1); + struct strbuf odb_path_real = STRBUF_INIT; + + prepare_alt_odb(r); + for (odb = r->objects->odb; odb; odb = odb->next) { + strbuf_realpath(&odb_path_real, odb->path, 1); + if (!strcmp(obj_dir_real, odb_path_real.buf)) + break; + } + + free(obj_dir_real); + strbuf_release(&odb_path_real); + + if (!odb) + die(_("could not find object directory matching %s"), obj_dir); + return odb; +} + static int graph_verify(int argc, const char **argv) { struct commit_graph *graph = NULL; + struct object_directory *odb = NULL; char *graph_name; int open_ok; int fd; @@ -74,7 +90,8 @@ static int graph_verify(int argc, const char **argv) if (opts.progress) flags |= COMMIT_GRAPH_WRITE_PROGRESS; - graph_name = get_commit_graph_filename(opts.obj_dir); + odb = find_odb(the_repository, opts.obj_dir); + graph_name = get_commit_graph_filename(odb); open_ok = open_commit_graph(graph_name, &fd, &st); if (!open_ok && errno != ENOENT) die_errno(_("Could not open commit-graph '%s'"), graph_name); @@ -82,9 +99,9 @@ static int graph_verify(int argc, const char **argv) FREE_AND_NULL(graph_name); if (open_ok) - graph = load_commit_graph_one_fd_st(fd, &st); - else - graph = read_commit_graph_one(the_repository, opts.obj_dir); + graph = load_commit_graph_one_fd_st(fd, &st, odb); + else + graph = read_commit_graph_one(the_repository, odb); /* Return failure if open_ok predicted success */ if (!graph) @@ -94,66 +111,6 @@ static int graph_verify(int argc, const char **argv) return verify_commit_graph(the_repository, graph, flags); } -static int graph_read(int argc, const char **argv) -{ - struct commit_graph *graph = NULL; - char *graph_name; - int open_ok; - int fd; - struct stat st; - - static struct option builtin_commit_graph_read_options[] = { - OPT_STRING(0, "object-dir", &opts.obj_dir, - N_("dir"), - N_("The object directory to store the graph")), - OPT_END(), - }; - - trace2_cmd_mode("read"); - - argc = parse_options(argc, argv, NULL, - builtin_commit_graph_read_options, - builtin_commit_graph_read_usage, 0); - - if (!opts.obj_dir) - opts.obj_dir = get_object_directory(); - - graph_name = get_commit_graph_filename(opts.obj_dir); - - open_ok = open_commit_graph(graph_name, &fd, &st); - if (!open_ok) - die_errno(_("Could not open commit-graph '%s'"), graph_name); - - graph = load_commit_graph_one_fd_st(fd, &st); - if (!graph) - return 1; - - FREE_AND_NULL(graph_name); - - printf("header: %08x %d %d %d %d\n", - ntohl(*(uint32_t*)graph->data), - *(unsigned char*)(graph->data + 4), - *(unsigned char*)(graph->data + 5), - *(unsigned char*)(graph->data + 6), - *(unsigned char*)(graph->data + 7)); - printf("num_commits: %u\n", graph->num_commits); - printf("chunks:"); - - if (graph->chunk_oid_fanout) - printf(" oid_fanout"); - if (graph->chunk_oid_lookup) - printf(" oid_lookup"); - if (graph->chunk_commit_data) - printf(" commit_metadata"); - if (graph->chunk_extra_edges) - printf(" extra_edges"); - printf("\n"); - - UNLEAK(graph); - - return 0; -} - extern int read_replace_refs; static struct split_commit_graph_opts split_opts; @@ -161,6 +118,7 @@ static int graph_write(int argc, const char **argv) { struct string_list *pack_indexes = NULL; struct string_list *commit_hex = NULL; + struct object_directory *odb = NULL; struct string_list lines; int result = 0; enum commit_graph_write_flags flags = 0; @@ -212,9 +170,10 @@ static int graph_write(int argc, const char **argv) flags |= COMMIT_GRAPH_WRITE_PROGRESS; read_replace_refs = 0; + odb = find_odb(the_repository, opts.obj_dir); if (opts.reachable) { - if (write_commit_graph_reachable(opts.obj_dir, flags, &split_opts)) + if (write_commit_graph_reachable(odb, flags, &split_opts)) return 1; return 0; } @@ -236,7 +195,7 @@ static int graph_write(int argc, const char **argv) UNLEAK(buf); } - if (write_commit_graph(opts.obj_dir, + if (write_commit_graph(odb, pack_indexes, commit_hex, flags, @@ -269,8 +228,6 @@ int cmd_commit_graph(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; if (argc > 0) { - if (!strcmp(argv[0], "read")) - return graph_read(argc, argv); if (!strcmp(argv[0], "verify")) return graph_verify(argc, argv); if (!strcmp(argv[0], "write")) diff --git a/builtin/commit.c b/builtin/commit.c index e588bc6ad3..d3e7781e65 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -59,6 +59,9 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\ " git commit --allow-empty\n" "\n"); +static const char empty_rebase_pick_advice[] = +N_("Otherwise, please use 'git rebase --skip'\n"); + static const char empty_cherry_pick_advice_single[] = N_("Otherwise, please use 'git cherry-pick --skip'\n"); @@ -107,9 +110,9 @@ static int all, also, interactive, patch_interactive, only, amend, signoff; static int edit_flag = -1; /* unspecified */ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static int config_commit_verbose = -1; /* unspecified */ -static int no_post_rewrite, allow_empty_message; +static int no_post_rewrite, allow_empty_message, pathspec_file_nul; static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg; -static char *sign_commit; +static char *sign_commit, *pathspec_from_file; /* * The default commit message cleanup mode will remove the lines @@ -122,7 +125,6 @@ static enum commit_msg_cleanup_mode cleanup_mode; static const char *cleanup_arg; static enum commit_whence whence; -static int sequencer_in_use; static int use_editor = 1, include_status = 1; static int have_option_m; static struct strbuf message = STRBUF_INIT; @@ -179,12 +181,7 @@ static void determine_whence(struct wt_status *s) { if (file_exists(git_path_merge_head(the_repository))) whence = FROM_MERGE; - else if (file_exists(git_path_cherry_pick_head(the_repository))) { - whence = FROM_CHERRY_PICK; - if (file_exists(git_path_seq_dir())) - sequencer_in_use = 1; - } - else + else if (!sequencer_determine_whence(the_repository, &whence)) whence = FROM_COMMIT; if (s) s->whence = whence; @@ -343,11 +340,31 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix PATHSPEC_PREFER_FULL, prefix, argv); + if (pathspec_from_file) { + if (interactive) + die(_("--pathspec-from-file is incompatible with --interactive/--patch")); + + if (all) + die(_("--pathspec-from-file with -a does not make sense")); + + if (pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&pathspec, 0, + PATHSPEC_PREFER_FULL, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + + if (!pathspec.nr && (also || (only && !amend && !allow_empty))) + die(_("No paths with --include/--only does not make sense.")); + if (read_cache_preload(&pathspec) < 0) die(_("index file corrupt")); if (interactive) { - char *old_index_env = NULL; + char *old_index_env = NULL, *old_repo_index_file; hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); refresh_cache_or_die(refresh_flags); @@ -355,12 +372,16 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (write_locked_index(&the_index, &index_lock, 0)) die(_("unable to create temporary index")); + old_repo_index_file = the_repository->index_file; + the_repository->index_file = + (char *)get_lock_file_path(&index_lock); old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT)); - setenv(INDEX_ENVIRONMENT, get_lock_file_path(&index_lock), 1); + setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1); if (interactive_add(argc, argv, prefix, patch_interactive) != 0) die(_("interactive add failed")); + the_repository->index_file = old_repo_index_file; if (old_index_env && *old_index_env) setenv(INDEX_ENVIRONMENT, old_index_env, 1); else @@ -453,8 +474,10 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (whence != FROM_COMMIT) { if (whence == FROM_MERGE) die(_("cannot do a partial commit during a merge.")); - else if (whence == FROM_CHERRY_PICK) + else if (is_from_cherry_pick(whence)) die(_("cannot do a partial commit during a cherry-pick.")); + else if (is_from_rebase(whence)) + die(_("cannot do a partial commit during a rebase.")); } if (list_paths(&partial, !current_head ? NULL : "HEAD", &pathspec)) @@ -537,7 +560,7 @@ static void export_one(const char *var, const char *s, const char *e, int hack) struct strbuf buf = STRBUF_INIT; if (hack) strbuf_addch(&buf, hack); - strbuf_addf(&buf, "%.*s", (int)(e - s), s); + strbuf_add(&buf, s, e - s); setenv(var, buf.buf, 1); strbuf_release(&buf); } @@ -771,7 +794,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, */ else if (whence == FROM_MERGE) hook_arg1 = "merge"; - else if (whence == FROM_CHERRY_PICK) { + else if (is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) { hook_arg1 = "commit"; hook_arg2 = "CHERRY_PICK_HEAD"; } @@ -944,16 +967,20 @@ static int prepare_to_commit(const char *index_file, const char *prefix, */ if (!committable && whence != FROM_MERGE && !allow_empty && !(amend && is_a_merge(current_head))) { + s->hints = advice_status_hints; s->display_comment_prefix = old_display_comment_prefix; run_status(stdout, index_file, prefix, 0, s); if (amend) fputs(_(empty_amend_advice), stderr); - else if (whence == FROM_CHERRY_PICK) { + else if (is_from_cherry_pick(whence) || + whence == FROM_REBASE_PICK) { fputs(_(empty_cherry_pick_advice), stderr); - if (!sequencer_in_use) + if (whence == FROM_CHERRY_PICK_SINGLE) fputs(_(empty_cherry_pick_advice_single), stderr); - else + else if (whence == FROM_CHERRY_PICK_MULTI) fputs(_(empty_cherry_pick_advice_multi), stderr); + else + fputs(_(empty_rebase_pick_advice), stderr); } return 0; } @@ -1156,8 +1183,10 @@ static int parse_and_validate_options(int argc, const char *argv[], if (amend && whence != FROM_COMMIT) { if (whence == FROM_MERGE) die(_("You are in the middle of a merge -- cannot amend.")); - else if (whence == FROM_CHERRY_PICK) + else if (is_from_cherry_pick(whence)) die(_("You are in the middle of a cherry-pick -- cannot amend.")); + else if (whence == FROM_REBASE_PICK) + die(_("You are in the middle of a rebase -- cannot amend.")); } if (fixup_message && squash_message) die(_("Options --squash and --fixup cannot be used together")); @@ -1179,7 +1208,8 @@ static int parse_and_validate_options(int argc, const char *argv[], use_message = edit_message; if (amend && !use_message && !fixup_message) use_message = "HEAD"; - if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship) + if (!use_message && !is_from_cherry_pick(whence) && + !is_from_rebase(whence) && renew_authorship) die(_("--reset-author can be used only with -C, -c or --amend.")); if (use_message) { use_message_buffer = read_commit_message(use_message); @@ -1188,7 +1218,8 @@ static int parse_and_validate_options(int argc, const char *argv[], author_message_buffer = use_message_buffer; } } - if (whence == FROM_CHERRY_PICK && !renew_authorship) { + if ((is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) && + !renew_authorship) { author_message = "CHERRY_PICK_HEAD"; author_message_buffer = read_commit_message(author_message); } @@ -1198,8 +1229,6 @@ static int parse_and_validate_options(int argc, const char *argv[], if (also + only + all + interactive > 1) die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); - if (argc == 0 && (also || (only && !amend && !allow_empty))) - die(_("No paths with --include/--only does not make sense.")); cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor); handle_untracked_files_arg(s); @@ -1463,28 +1492,6 @@ static int git_commit_config(const char *k, const char *v, void *cb) return git_status_config(k, v, s); } -int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...) -{ - struct argv_array hook_env = ARGV_ARRAY_INIT; - va_list args; - int ret; - - argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file); - - /* - * Let the hook know that no editor will be launched. - */ - if (!editor_is_used) - argv_array_push(&hook_env, "GIT_EDITOR=:"); - - va_start(args, name); - ret = run_hook_ve(hook_env.argv,name, args); - va_end(args); - argv_array_clear(&hook_env); - - return ret; -} - int cmd_commit(int argc, const char **argv, const char *prefix) { const char *argv_gc_auto[] = {"gc", "--auto", NULL}; @@ -1535,6 +1542,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "amend", &amend, N_("amend previous commit")), OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), /* end commit contents options */ OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty, @@ -1628,8 +1637,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix) reduce_heads_replace(&parents); } else { if (!reflog_msg) - reflog_msg = (whence == FROM_CHERRY_PICK) + reflog_msg = is_from_cherry_pick(whence) ? "commit (cherry-pick)" + : is_from_rebase(whence) + ? "commit (rebase)" : "commit"; commit_list_insert(current_head, &parents); } @@ -1656,7 +1667,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) } if (amend) { - const char *exclude_gpgsig[2] = { "gpgsig", NULL }; + const char *exclude_gpgsig[3] = { "gpgsig", "gpgsig-sha256", NULL }; extra = read_commit_extra_headers(current_head, exclude_gpgsig); } else { struct commit_extra_header **tail = &extra; @@ -1690,7 +1701,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) "not exceeded, and then \"git restore --staged :/\" to recover.")); if (git_env_bool(GIT_TEST_COMMIT_GRAPH, 0) && - write_commit_graph_reachable(get_object_directory(), 0, NULL)) + write_commit_graph_reachable(the_repository->objects->odb, 0, NULL)) return 1; repo_rerere(the_repository, 0); diff --git a/builtin/config.c b/builtin/config.c index 98d65bc0ad..ee4aef6a35 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -29,10 +29,11 @@ static int use_worktree_config; static struct git_config_source given_config_source; static int actions, type; static char *default_value; -static int end_null; +static int end_nul; static int respect_includes_opt = -1; static struct config_options config_options; static int show_origin; +static int show_scope; #define ACTION_GET (1<<0) #define ACTION_GET_ALL (1<<1) @@ -151,10 +152,11 @@ static struct option builtin_config_options[] = { OPT_CALLBACK_VALUE(0, "path", &type, N_("value is a path (file or directory name)"), TYPE_PATH), OPT_CALLBACK_VALUE(0, "expiry-date", &type, N_("value is an expiry date"), TYPE_EXPIRY_DATE), OPT_GROUP(N_("Other")), - OPT_BOOL('z', "null", &end_null, N_("terminate values with NUL byte")), + OPT_BOOL('z', "null", &end_nul, N_("terminate values with NUL byte")), OPT_BOOL(0, "name-only", &omit_values, N_("show variable names only")), OPT_BOOL(0, "includes", &respect_includes_opt, N_("respect include directives on lookup")), OPT_BOOL(0, "show-origin", &show_origin, N_("show origin of config (file, standard input, blob, command line)")), + OPT_BOOL(0, "show-scope", &show_scope, N_("show scope of config (worktree, local, global, system, command)")), OPT_STRING(0, "default", &default_value, N_("value"), N_("with --get, use default value when missing entry")), OPT_END(), }; @@ -178,22 +180,34 @@ static void check_argc(int argc, int min, int max) static void show_config_origin(struct strbuf *buf) { - const char term = end_null ? '\0' : '\t'; + const char term = end_nul ? '\0' : '\t'; strbuf_addstr(buf, current_config_origin_type()); strbuf_addch(buf, ':'); - if (end_null) + if (end_nul) strbuf_addstr(buf, current_config_name()); else quote_c_style(current_config_name(), buf, NULL, 0); strbuf_addch(buf, term); } +static void show_config_scope(struct strbuf *buf) +{ + const char term = end_nul ? '\0' : '\t'; + const char *scope = config_scope_name(current_config_scope()); + + strbuf_addstr(buf, N_(scope)); + strbuf_addch(buf, term); +} + static int show_all_config(const char *key_, const char *value_, void *cb) { - if (show_origin) { + if (show_origin || show_scope) { struct strbuf buf = STRBUF_INIT; - show_config_origin(&buf); + if (show_scope) + show_config_scope(&buf); + if (show_origin) + show_config_origin(&buf); /* Use fwrite as "buf" can contain \0's if "end_null" is set. */ fwrite(buf.buf, 1, buf.len, stdout); strbuf_release(&buf); @@ -213,6 +227,8 @@ struct strbuf_list { static int format_config(struct strbuf *buf, const char *key_, const char *value_) { + if (show_scope) + show_config_scope(buf); if (show_origin) show_config_origin(buf); if (show_keys) @@ -622,6 +638,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) !strcmp(given_config_source.file, "-")) { given_config_source.file = NULL; given_config_source.use_stdin = 1; + given_config_source.scope = CONFIG_SCOPE_COMMAND; } if (use_global_config) { @@ -637,6 +654,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) */ die(_("$HOME not set")); + given_config_source.scope = CONFIG_SCOPE_GLOBAL; + if (access_or_warn(user_config, R_OK, 0) && xdg_config && !access_or_warn(xdg_config, R_OK, 0)) { given_config_source.file = xdg_config; @@ -646,11 +665,13 @@ int cmd_config(int argc, const char **argv, const char *prefix) free(xdg_config); } } - else if (use_system_config) + else if (use_system_config) { given_config_source.file = git_etc_gitconfig(); - else if (use_local_config) + given_config_source.scope = CONFIG_SCOPE_SYSTEM; + } else if (use_local_config) { given_config_source.file = git_pathdup("config"); - else if (use_worktree_config) { + given_config_source.scope = CONFIG_SCOPE_LOCAL; + } else if (use_worktree_config) { struct worktree **worktrees = get_worktrees(0); if (repository_format_worktree_config) given_config_source.file = git_pathdup("config.worktree"); @@ -662,13 +683,18 @@ int cmd_config(int argc, const char **argv, const char *prefix) "section in \"git help worktree\" for details")); else given_config_source.file = git_pathdup("config"); + given_config_source.scope = CONFIG_SCOPE_LOCAL; free_worktrees(worktrees); } else if (given_config_source.file) { if (!is_absolute_path(given_config_source.file) && prefix) given_config_source.file = prefix_filename(prefix, given_config_source.file); + given_config_source.scope = CONFIG_SCOPE_COMMAND; + } else if (given_config_source.blob) { + given_config_source.scope = CONFIG_SCOPE_COMMAND; } + if (respect_includes_opt == -1) config_options.respect_includes = !given_config_source.file; else @@ -678,7 +704,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) config_options.git_dir = get_git_dir(); } - if (end_null) { + if (end_nul) { term = '\0'; delim = '\n'; key_delim = '\n'; diff --git a/builtin/describe.c b/builtin/describe.c index b6df81d8d0..420f4c6401 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -376,11 +376,25 @@ static void describe_commit(struct object_id *oid, struct strbuf *dst) if (!(c->object.flags & t->flag_within)) t->depth++; } + /* Stop if last remaining path already covered by best candidate(s) */ if (annotated_cnt && !list) { - if (debug) - fprintf(stderr, _("finished search at %s\n"), - oid_to_hex(&c->object.oid)); - break; + int best_depth = INT_MAX; + unsigned best_within = 0; + for (cur_match = 0; cur_match < match_cnt; cur_match++) { + struct possible_tag *t = &all_matches[cur_match]; + if (t->depth < best_depth) { + best_depth = t->depth; + best_within = t->flag_within; + } else if (t->depth == best_depth) { + best_within |= t->flag_within; + } + } + if ((c->object.flags & best_within) == best_within) { + if (debug) + fprintf(stderr, _("finished search at %s\n"), + oid_to_hex(&c->object.oid)); + break; + } } while (parents) { struct commit *p = parents->item; diff --git a/builtin/fast-export.c b/builtin/fast-export.c index dbec4df92b..85868162ee 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -293,7 +293,8 @@ static void export_blob(const struct object_id *oid) buf = read_object_file(oid, &type, &size); if (!buf) die("could not read blob %s", oid_to_hex(oid)); - if (check_object_signature(oid, buf, size, type_name(type)) < 0) + if (check_object_signature(the_repository, oid, buf, size, + type_name(type)) < 0) die("oid mismatch in blob %s", oid_to_hex(oid)); object = parse_object_buffer(the_repository, oid, type, size, buf, &eaten); @@ -870,8 +871,7 @@ static void handle_tag(const char *name, struct tag *tag) printf("reset %s\nfrom %s\n\n", name, oid_to_hex(&null_oid)); } - if (starts_with(name, "refs/tags/")) - name += 10; + skip_prefix(name, "refs/tags/", &name); printf("tag %s\n", name); if (mark_tags) { mark_next_object(&tag->object); diff --git a/builtin/fetch.c b/builtin/fetch.c index 863c858fde..bf6bab80fa 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -77,6 +77,7 @@ static struct refspec refmap = REFSPEC_INIT_FETCH; static struct list_objects_filter_options filter_options; static struct string_list server_options = STRING_LIST_INIT_DUP; static struct string_list negotiation_tip = STRING_LIST_INIT_NODUP; +static int fetch_write_commit_graph = -1; static int git_fetch_config(const char *k, const char *v, void *cb) { @@ -198,6 +199,8 @@ static struct option builtin_fetch_options[] = { N_("run 'gc --auto' after fetching")), OPT_BOOL(0, "show-forced-updates", &fetch_show_forced_updates, N_("check for forced-updates on all updated branches")), + OPT_BOOL(0, "write-commit-graph", &fetch_write_commit_graph, + N_("write the commit-graph after fetching")), OPT_END() }; @@ -332,6 +335,7 @@ static void find_non_local_tags(const struct ref *refs, struct string_list_item *remote_ref_item; const struct ref *ref; struct refname_hash_entry *item = NULL; + const int quick_flags = OBJECT_INFO_QUICK | OBJECT_INFO_SKIP_FETCH_OBJECT; refname_hash_init(&existing_refs); refname_hash_init(&remote_refs); @@ -350,10 +354,9 @@ static void find_non_local_tags(const struct ref *refs, */ if (ends_with(ref->name, "^{}")) { if (item && - !has_object_file_with_flags(&ref->old_oid, - OBJECT_INFO_QUICK) && + !has_object_file_with_flags(&ref->old_oid, quick_flags) && !oidset_contains(&fetch_oids, &ref->old_oid) && - !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) && + !has_object_file_with_flags(&item->oid, quick_flags) && !oidset_contains(&fetch_oids, &item->oid)) clear_item(item); item = NULL; @@ -367,7 +370,7 @@ static void find_non_local_tags(const struct ref *refs, * fetch. */ if (item && - !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) && + !has_object_file_with_flags(&item->oid, quick_flags) && !oidset_contains(&fetch_oids, &item->oid)) clear_item(item); @@ -388,7 +391,7 @@ static void find_non_local_tags(const struct ref *refs, * checked to see if it needs fetching. */ if (item && - !has_object_file_with_flags(&item->oid, OBJECT_INFO_QUICK) && + !has_object_file_with_flags(&item->oid, quick_flags) && !oidset_contains(&fetch_oids, &item->oid)) clear_item(item); @@ -903,8 +906,17 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, url = xstrdup("foreign"); if (!connectivity_checked) { + struct check_connected_options opt = CHECK_CONNECTED_INIT; + + if (filter_options.choice) + /* + * Since a filter is specified, objects indirectly + * referenced by refs are allowed to be absent. + */ + opt.check_refs_are_promisor_objects_only = 1; + rm = ref_map; - if (check_connected(iterate_ref_map, &rm, NULL)) { + if (check_connected(iterate_ref_map, &rm, &opt)) { rc = error(_("%s did not send all necessary objects\n"), url); goto abort; } @@ -954,18 +966,12 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, kind = ""; what = ""; } - else if (starts_with(rm->name, "refs/heads/")) { + else if (skip_prefix(rm->name, "refs/heads/", &what)) kind = "branch"; - what = rm->name + 11; - } - else if (starts_with(rm->name, "refs/tags/")) { + else if (skip_prefix(rm->name, "refs/tags/", &what)) kind = "tag"; - what = rm->name + 10; - } - else if (starts_with(rm->name, "refs/remotes/")) { + else if (skip_prefix(rm->name, "refs/remotes/", &what)) kind = "remote-tracking branch"; - what = rm->name + 13; - } else { kind = ""; what = rm->name; @@ -1074,7 +1080,8 @@ static int check_exist_and_connected(struct ref *ref_map) * we need all direct targets to exist. */ for (r = rm; r; r = r->next) { - if (!has_object_file(&r->old_oid)) + if (!has_object_file_with_flags(&r->old_oid, + OBJECT_INFO_SKIP_FETCH_OBJECT)) return -1; } @@ -1400,7 +1407,7 @@ static int do_fetch(struct transport *transport, /* * We're setting the upstream configuration for the - * current branch. The relevent upstream is the + * current branch. The relevant upstream is the * fetched branch that is meant to be merged with the * current one, i.e. the one fetched to FETCH_HEAD. * @@ -1411,7 +1418,7 @@ static int do_fetch(struct transport *transport, for (rm = ref_map; rm; rm = rm->next) { if (!rm->peer_ref) { if (source_ref) { - warning(_("multiple branch detected, incompatible with --set-upstream")); + warning(_("multiple branches detected, incompatible with --set-upstream")); goto skip; } else { source_ref = rm; @@ -1599,7 +1606,8 @@ static int fetch_multiple(struct string_list *list, int max_children) return errcode; } - argv_array_pushl(&argv, "fetch", "--append", "--no-auto-gc", NULL); + argv_array_pushl(&argv, "fetch", "--append", "--no-auto-gc", + "--no-write-commit-graph", NULL); add_options_to_argv(&argv); if (max_children != 1 && list->nr != 1) { @@ -1822,8 +1830,6 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) } } - fetch_if_missing = 0; - if (remote) { if (filter_options.choice || has_promisor_remote()) fetch_one_setup_partial(remote); @@ -1865,17 +1871,17 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) string_list_clear(&list, 0); prepare_repo_settings(the_repository); - if (the_repository->settings.fetch_write_commit_graph) { + if (fetch_write_commit_graph > 0 || + (fetch_write_commit_graph < 0 && + the_repository->settings.fetch_write_commit_graph)) { int commit_graph_flags = COMMIT_GRAPH_WRITE_SPLIT; - struct split_commit_graph_opts split_opts; - memset(&split_opts, 0, sizeof(struct split_commit_graph_opts)); if (progress) commit_graph_flags |= COMMIT_GRAPH_WRITE_PROGRESS; - write_commit_graph_reachable(get_object_directory(), + write_commit_graph_reachable(the_repository->objects->odb, commit_graph_flags, - &split_opts); + NULL); } close_object_store(the_repository->objects); diff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c index a4615587fd..172dfbd852 100644 --- a/builtin/fmt-merge-msg.c +++ b/builtin/fmt-merge-msg.c @@ -106,7 +106,7 @@ static int handle_line(char *line, struct merge_parents *merge_parents) int i, len = strlen(line); struct origin_data *origin_data; char *src; - const char *origin; + const char *origin, *tag_name; struct src_data *src_data; struct string_list_item *item; int pulling_head = 0; @@ -162,14 +162,13 @@ static int handle_line(char *line, struct merge_parents *merge_parents) if (pulling_head) { origin = src; src_data->head_status |= 1; - } else if (starts_with(line, "branch ")) { + } else if (skip_prefix(line, "branch ", &origin)) { origin_data->is_local_branch = 1; - origin = line + 7; string_list_append(&src_data->branch, origin); src_data->head_status |= 2; - } else if (starts_with(line, "tag ")) { + } else if (skip_prefix(line, "tag ", &tag_name)) { origin = line; - string_list_append(&src_data->tag, origin + 4); + string_list_append(&src_data->tag, tag_name); src_data->head_status |= 2; } else if (skip_prefix(line, "remote-tracking branch ", &origin)) { string_list_append(&src_data->r_branch, origin); @@ -495,6 +494,7 @@ static void fmt_merge_msg_sigs(struct strbuf *out) enum object_type type; unsigned long size, len; char *buf = read_object_file(oid, &type, &size); + struct signature_check sigc = { 0 }; struct strbuf sig = STRBUF_INIT; if (!buf || type != OBJ_TAG) @@ -503,10 +503,12 @@ static void fmt_merge_msg_sigs(struct strbuf *out) if (size == len) ; /* merely annotated */ - else if (verify_signed_buffer(buf, len, buf + len, size - len, &sig, NULL)) { - if (!sig.len) - strbuf_addstr(&sig, "gpg verification failed.\n"); - } + else if (check_signature(buf, len, buf + len, size - len, &sigc) && + !sigc.gpg_output) + strbuf_addstr(&sig, "gpg verification failed.\n"); + else + strbuf_addstr(&sig, sigc.gpg_output); + signature_check_clear(&sigc); if (!tag_number++) { fmt_tag_signature(&tagbuf, &sig, buf, len); diff --git a/builtin/fsck.c b/builtin/fsck.c index 18403a94fa..8d13794b14 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -50,40 +50,20 @@ static int name_objects; #define ERROR_REFS 010 #define ERROR_COMMIT_GRAPH 020 -static const char *describe_object(struct object *obj) +static const char *describe_object(const struct object_id *oid) { - static struct strbuf bufs[] = { - STRBUF_INIT, STRBUF_INIT, STRBUF_INIT, STRBUF_INIT - }; - static int b = 0; - struct strbuf *buf; - char *name = NULL; - - if (name_objects) - name = lookup_decoration(fsck_walk_options.object_names, obj); - - buf = bufs + b; - b = (b + 1) % ARRAY_SIZE(bufs); - strbuf_reset(buf); - strbuf_addstr(buf, oid_to_hex(&obj->oid)); - if (name) - strbuf_addf(buf, " (%s)", name); - - return buf->buf; + return fsck_describe_object(&fsck_walk_options, oid); } -static const char *printable_type(struct object *obj) +static const char *printable_type(const struct object_id *oid, + enum object_type type) { const char *ret; - if (obj->type == OBJ_NONE) { - enum object_type type = oid_object_info(the_repository, - &obj->oid, NULL); - if (type > 0) - object_as_type(the_repository, obj, type, 0); - } + if (type == OBJ_NONE) + type = oid_object_info(the_repository, oid, NULL); - ret = type_name(obj->type); + ret = type_name(type); if (!ret) ret = _("unknown"); @@ -118,26 +98,32 @@ static int objerror(struct object *obj, const char *err) errors_found |= ERROR_OBJECT; /* TRANSLATORS: e.g. error in tree 01bfda: <more explanation> */ fprintf_ln(stderr, _("error in %s %s: %s"), - printable_type(obj), describe_object(obj), err); + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid), err); return -1; } static int fsck_error_func(struct fsck_options *o, - struct object *obj, int type, const char *message) + const struct object_id *oid, + enum object_type object_type, + int msg_type, const char *message) { - switch (type) { + switch (msg_type) { case FSCK_WARN: /* TRANSLATORS: e.g. warning in tree 01bfda: <more explanation> */ fprintf_ln(stderr, _("warning in %s %s: %s"), - printable_type(obj), describe_object(obj), message); + printable_type(oid, object_type), + describe_object(oid), message); return 0; case FSCK_ERROR: /* TRANSLATORS: e.g. error in tree 01bfda: <more explanation> */ fprintf_ln(stderr, _("error in %s %s: %s"), - printable_type(obj), describe_object(obj), message); + printable_type(oid, object_type), + describe_object(oid), message); return 1; default: - BUG("%d (FSCK_IGNORE?) should never trigger this callback", type); + BUG("%d (FSCK_IGNORE?) should never trigger this callback", + msg_type); } } @@ -155,7 +141,8 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt if (!obj) { /* ... these references to parent->fld are safe here */ printf_ln(_("broken link from %7s %s"), - printable_type(parent), describe_object(parent)); + printable_type(&parent->oid, parent->type), + describe_object(&parent->oid)); printf_ln(_("broken link from %7s %s"), (type == OBJ_ANY ? _("unknown") : type_name(type)), _("unknown")); @@ -183,10 +170,10 @@ static int mark_object(struct object *obj, int type, void *data, struct fsck_opt if (parent && !has_object_file(&obj->oid)) { printf_ln(_("broken link from %7s %s\n" " to %7s %s"), - printable_type(parent), - describe_object(parent), - printable_type(obj), - describe_object(obj)); + printable_type(&parent->oid, parent->type), + describe_object(&parent->oid), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); errors_found |= ERROR_REACHABLE; } return 1; @@ -292,8 +279,9 @@ static void check_reachable_object(struct object *obj) return; if (has_object_pack(&obj->oid)) return; /* it is in pack - forget about it */ - printf_ln(_("missing %s %s"), printable_type(obj), - describe_object(obj)); + printf_ln(_("missing %s %s"), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); errors_found |= ERROR_REACHABLE; return; } @@ -318,8 +306,9 @@ static void check_unreachable_object(struct object *obj) * since this is something that is prunable. */ if (show_unreachable) { - printf_ln(_("unreachable %s %s"), printable_type(obj), - describe_object(obj)); + printf_ln(_("unreachable %s %s"), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); return; } @@ -337,12 +326,13 @@ static void check_unreachable_object(struct object *obj) */ if (!(obj->flags & USED)) { if (show_dangling) - printf_ln(_("dangling %s %s"), printable_type(obj), - describe_object(obj)); + printf_ln(_("dangling %s %s"), + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); if (write_lost_and_found) { char *filename = git_pathdup("lost-found/%s/%s", obj->type == OBJ_COMMIT ? "commit" : "other", - describe_object(obj)); + describe_object(&obj->oid)); FILE *f; if (safe_create_leading_directories_const(filename)) { @@ -355,7 +345,7 @@ static void check_unreachable_object(struct object *obj) if (stream_blob_to_fd(fileno(f), &obj->oid, NULL, 1)) die_errno(_("could not write '%s'"), filename); } else - fprintf(f, "%s\n", describe_object(obj)); + fprintf(f, "%s\n", describe_object(&obj->oid)); if (fclose(f)) die_errno(_("could not finish '%s'"), filename); @@ -374,7 +364,7 @@ static void check_unreachable_object(struct object *obj) static void check_object(struct object *obj) { if (verbose) - fprintf_ln(stderr, _("Checking %s"), describe_object(obj)); + fprintf_ln(stderr, _("Checking %s"), describe_object(&obj->oid)); if (obj->flags & REACHABLE) check_reachable_object(obj); @@ -432,7 +422,8 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size) if (verbose) fprintf_ln(stderr, _("Checking %s %s"), - printable_type(obj), describe_object(obj)); + printable_type(&obj->oid, obj->type), + describe_object(&obj->oid)); if (fsck_walk(obj, NULL, &fsck_obj_options)) objerror(obj, _("broken links")); @@ -445,7 +436,7 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size) if (!commit->parents && show_root) printf_ln(_("root %s"), - describe_object(&commit->object)); + describe_object(&commit->object.oid)); } if (obj->type == OBJ_TAG) { @@ -453,10 +444,10 @@ static int fsck_obj(struct object *obj, void *buffer, unsigned long size) if (show_tags && tag->tagged) { printf_ln(_("tagged %s %s (%s) in %s"), - printable_type(tag->tagged), - describe_object(tag->tagged), + printable_type(&tag->tagged->oid, tag->tagged->type), + describe_object(&tag->tagged->oid), tag->tag, - describe_object(&tag->object)); + describe_object(&tag->object.oid)); } } @@ -499,10 +490,10 @@ static void fsck_handle_reflog_oid(const char *refname, struct object_id *oid, if (!is_null_oid(oid)) { obj = lookup_object(the_repository, oid); if (obj && (obj->flags & HAS_OBJ)) { - if (timestamp && name_objects) - add_decoration(fsck_walk_options.object_names, - obj, - xstrfmt("%s@{%"PRItime"}", refname, timestamp)); + if (timestamp) + fsck_put_object_name(&fsck_walk_options, oid, + "%s@{%"PRItime"}", + refname, timestamp); obj->flags |= USED; mark_object_reachable(obj); } else if (!is_promisor_object(oid)) { @@ -566,9 +557,8 @@ static int fsck_handle_ref(const char *refname, const struct object_id *oid, } default_refs++; obj->flags |= USED; - if (name_objects) - add_decoration(fsck_walk_options.object_names, - obj, xstrdup(refname)); + fsck_put_object_name(&fsck_walk_options, + oid, "%s", refname); mark_object_reachable(obj); return 0; @@ -742,9 +732,7 @@ static int fsck_cache_tree(struct cache_tree *it) return 1; } obj->flags |= USED; - if (name_objects) - add_decoration(fsck_walk_options.object_names, - obj, xstrdup(":")); + fsck_put_object_name(&fsck_walk_options, &it->oid, ":"); mark_object_reachable(obj); if (obj->type != OBJ_TREE) err |= objerror(obj, _("non-tree in cache-tree")); @@ -830,8 +818,7 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) } if (name_objects) - fsck_walk_options.object_names = - xcalloc(1, sizeof(struct decoration)); + fsck_enable_object_names(&fsck_walk_options); git_config(fsck_config, NULL); @@ -890,9 +877,8 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) } obj->flags |= USED; - if (name_objects) - add_decoration(fsck_walk_options.object_names, - obj, xstrdup(arg)); + fsck_put_object_name(&fsck_walk_options, &oid, + "%s", arg); mark_object_reachable(obj); continue; } @@ -928,10 +914,8 @@ int cmd_fsck(int argc, const char **argv, const char *prefix) continue; obj = &blob->object; obj->flags |= USED; - if (name_objects) - add_decoration(fsck_walk_options.object_names, - obj, - xstrfmt(":%s", active_cache[i]->name)); + fsck_put_object_name(&fsck_walk_options, &obj->oid, + ":%s", active_cache[i]->name); mark_object_reachable(obj); } if (active_cache_tree) diff --git a/builtin/gc.c b/builtin/gc.c index fadb45489f..8e0b9cf41b 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -458,7 +458,7 @@ static const char *lock_repo_for_gc(int force, pid_t* ret_pid) /* * Returns 0 if there was no previous error and gc can proceed, 1 if * gc should not proceed due to an error in the last run. Prints a - * message and returns -1 if an error occured while reading gc.log + * message and returns -1 if an error occurred while reading gc.log */ static int report_last_gc_error(void) { @@ -601,7 +601,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (detach_auto) { int ret = report_last_gc_error(); if (ret < 0) - /* an I/O error occured, already reported */ + /* an I/O error occurred, already reported */ exit(128); if (ret == 1) /* Last gc --auto failed. Skip this one. */ @@ -686,7 +686,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) prepare_repo_settings(the_repository); if (the_repository->settings.gc_write_commit_graph == 1) - write_commit_graph_reachable(get_object_directory(), + write_commit_graph_reachable(the_repository->objects->odb, !quiet && !daemonized ? COMMIT_GRAPH_WRITE_PROGRESS : 0, NULL); diff --git a/builtin/grep.c b/builtin/grep.c index 50ce8d9461..99e2685090 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -24,6 +24,7 @@ #include "submodule.h" #include "submodule-config.h" #include "object-store.h" +#include "packfile.h" static char const * const grep_usage[] = { N_("git grep [<options>] [-e] <pattern> [<rev>...] [[--] <path>...]"), @@ -32,7 +33,6 @@ static char const * const grep_usage[] = { static int recurse_submodules; -#define GREP_NUM_THREADS_DEFAULT 8 static int num_threads; static pthread_t *threads; @@ -91,8 +91,11 @@ static pthread_cond_t cond_result; static int skip_first_line; -static void add_work(struct grep_opt *opt, const struct grep_source *gs) +static void add_work(struct grep_opt *opt, struct grep_source *gs) { + if (opt->binary != GREP_BINARY_TEXT) + grep_source_load_driver(gs, opt->repo->index); + grep_lock(); while ((todo_end+1) % ARRAY_SIZE(todo) == todo_done) { @@ -100,9 +103,6 @@ static void add_work(struct grep_opt *opt, const struct grep_source *gs) } todo[todo_end].source = *gs; - if (opt->binary != GREP_BINARY_TEXT) - grep_source_load_driver(&todo[todo_end].source, - opt->repo->index); todo[todo_end].done = 0; strbuf_reset(&todo[todo_end].out); todo_end = (todo_end + 1) % ARRAY_SIZE(todo); @@ -200,12 +200,12 @@ static void start_threads(struct grep_opt *opt) int i; pthread_mutex_init(&grep_mutex, NULL); - pthread_mutex_init(&grep_read_mutex, NULL); pthread_mutex_init(&grep_attr_mutex, NULL); pthread_cond_init(&cond_add, NULL); pthread_cond_init(&cond_write, NULL); pthread_cond_init(&cond_result, NULL); grep_use_locks = 1; + enable_obj_read_lock(); for (i = 0; i < ARRAY_SIZE(todo); i++) { strbuf_init(&todo[i].out, 0); @@ -257,12 +257,12 @@ static int wait_all(void) free(threads); pthread_mutex_destroy(&grep_mutex); - pthread_mutex_destroy(&grep_read_mutex); pthread_mutex_destroy(&grep_attr_mutex); pthread_cond_destroy(&cond_add); pthread_cond_destroy(&cond_write); pthread_cond_destroy(&cond_result); grep_use_locks = 0; + disable_obj_read_lock(); return hit; } @@ -295,16 +295,6 @@ static int grep_cmd_config(const char *var, const char *value, void *cb) return st; } -static void *lock_and_read_oid_file(const struct object_id *oid, enum object_type *type, unsigned long *size) -{ - void *data; - - grep_read_lock(); - data = read_object_file(oid, type, size); - grep_read_unlock(); - return data; -} - static int grep_oid(struct grep_opt *opt, const struct object_id *oid, const char *filename, int tree_name_len, const char *path) @@ -407,30 +397,28 @@ static int grep_submodule(struct grep_opt *opt, { struct repository subrepo; struct repository *superproject = opt->repo; - const struct submodule *sub = submodule_from_path(superproject, - &null_oid, path); + const struct submodule *sub; struct grep_opt subopt; int hit; - /* - * NEEDSWORK: submodules functions need to be protected because they - * access the object store via config_from_gitmodules(): the latter - * uses get_oid() which, for now, relies on the global the_repository - * object. - */ - grep_read_lock(); + sub = submodule_from_path(superproject, &null_oid, path); - if (!is_submodule_active(superproject, path)) { - grep_read_unlock(); + if (!is_submodule_active(superproject, path)) return 0; - } - if (repo_submodule_init(&subrepo, superproject, sub)) { - grep_read_unlock(); + if (repo_submodule_init(&subrepo, superproject, sub)) return 0; - } - repo_read_gitmodules(&subrepo); + /* + * NEEDSWORK: repo_read_gitmodules() might call + * add_to_alternates_memory() via config_from_gitmodules(). This + * operation causes a race condition with concurrent object readings + * performed by the worker threads. That's why we need obj_read_lock() + * here. It should be removed once it's no longer necessary to add the + * subrepo's odbs to the in-memory alternates list. + */ + obj_read_lock(); + repo_read_gitmodules(&subrepo, 0); /* * NEEDSWORK: This adds the submodule's object directory to the list of @@ -443,7 +431,7 @@ static int grep_submodule(struct grep_opt *opt, * object. */ add_to_alternates_memory(subrepo.objects->odb->path); - grep_read_unlock(); + obj_read_unlock(); memcpy(&subopt, opt, sizeof(subopt)); subopt.repo = &subrepo; @@ -455,14 +443,12 @@ static int grep_submodule(struct grep_opt *opt, unsigned long size; struct strbuf base = STRBUF_INIT; + obj_read_lock(); object = parse_object_or_die(oid, oid_to_hex(oid)); - - grep_read_lock(); + obj_read_unlock(); data = read_object_with_reference(&subrepo, &object->oid, tree_type, &size, NULL); - grep_read_unlock(); - if (!data) die(_("unable to read tree (%s)"), oid_to_hex(&object->oid)); @@ -587,7 +573,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, void *data; unsigned long size; - data = lock_and_read_oid_file(&entry.oid, &type, &size); + data = read_object_file(&entry.oid, &type, &size); if (!data) die(_("unable to read tree (%s)"), oid_to_hex(&entry.oid)); @@ -625,12 +611,9 @@ static int grep_object(struct grep_opt *opt, const struct pathspec *pathspec, struct strbuf base; int hit, len; - grep_read_lock(); data = read_object_with_reference(opt->repo, &obj->oid, tree_type, &size, NULL); - grep_read_unlock(); - if (!data) die(_("unable to read tree (%s)"), oid_to_hex(&obj->oid)); @@ -659,13 +642,18 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, for (i = 0; i < nr; i++) { struct object *real_obj; + + obj_read_lock(); real_obj = deref_tag(opt->repo, list->objects[i].item, NULL, 0); + obj_read_unlock(); /* load the gitmodules file for this rev */ if (recurse_submodules) { submodule_free(opt->repo); + obj_read_lock(); gitmodules_config_oid(&real_obj->oid); + obj_read_unlock(); } if (grep_object(opt, pathspec, real_obj, list->objects[i].name, list->objects[i].path)) { @@ -958,6 +946,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) /* die the same way as if we did it at the beginning */ setup_git_directory(); } + /* Ignore --recurse-submodules if --no-index is given or implied */ + if (!use_index) + recurse_submodules = 0; /* * skip a -- separator; we know it cannot be @@ -1062,7 +1053,10 @@ int cmd_grep(int argc, const char **argv, const char *prefix) pathspec.recursive = 1; pathspec.recurse_submodules = !!recurse_submodules; - if (list.nr || cached || show_in_pager) { + if (recurse_submodules && untracked) + die(_("--untracked not supported with --recurse-submodules")); + + if (show_in_pager) { if (num_threads > 1) warning(_("invalid option combination, ignoring --threads")); num_threads = 1; @@ -1072,7 +1066,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } else if (num_threads < 0) die(_("invalid number of threads specified (%d)"), num_threads); else if (num_threads == 0) - num_threads = HAVE_THREADS ? GREP_NUM_THREADS_DEFAULT : 1; + num_threads = HAVE_THREADS ? online_cpus() : 1; if (num_threads > 1) { if (!HAVE_THREADS) @@ -1081,6 +1075,17 @@ int cmd_grep(int argc, const char **argv, const char *prefix) && (opt.pre_context || opt.post_context || opt.file_break || opt.funcbody)) skip_first_line = 1; + + /* + * Pre-read gitmodules (if not read already) and force eager + * initialization of packed_git to prevent racy lazy + * reading/initialization once worker threads are started. + */ + if (recurse_submodules) + repo_read_gitmodules(the_repository, 1); + if (startup_info->have_repository) + (void)get_packed_git(the_repository); + start_threads(&opt); } else { /* @@ -1115,9 +1120,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) } } - if (recurse_submodules && (!use_index || untracked)) - die(_("option not supported with --recurse-submodules")); - if (!show_in_pager && !opt.status_only) setup_pager(); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 60a5591039..d967d188a3 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -757,7 +757,8 @@ static int check_collison(struct object_entry *entry) memset(&data, 0, sizeof(data)); data.entry = entry; - data.st = open_istream(&entry->idx.oid, &type, &size, NULL); + data.st = open_istream(the_repository, &entry->idx.oid, &type, &size, + NULL); if (!data.st) return -1; if (size != entry->size || type != entry->type) @@ -948,7 +949,7 @@ static void resolve_delta(struct object_entry *delta_obj, free(delta_data); if (!result->data) bad_object(delta_obj->idx.offset, _("failed to apply delta")); - hash_object_file(result->data, result->size, + hash_object_file(the_hash_algo, result->data, result->size, type_name(delta_obj->real_type), &delta_obj->idx.oid); sha1_object(result->data, NULL, result->size, delta_obj->real_type, &delta_obj->idx.oid); @@ -1003,7 +1004,9 @@ static struct base_data *find_unresolved_deltas_1(struct base_data *base, if (!compare_and_swap_type(&child->real_type, OBJ_REF_DELTA, base->obj->real_type)) - BUG("child->real_type != OBJ_REF_DELTA"); + die("REF_DELTA at offset %"PRIuMAX" already resolved (duplicate base %s?)", + (uintmax_t)child->idx.offset, + oid_to_hex(&base->obj->idx.oid)); resolve_delta(child, base, result); if (base->ref_first == base->ref_last && base->ofs_last == -1) @@ -1383,8 +1386,9 @@ static void fix_unresolved_deltas(struct hashfile *f) if (!base_obj->data) continue; - if (check_object_signature(&d->oid, base_obj->data, - base_obj->size, type_name(type))) + if (check_object_signature(the_repository, &d->oid, + base_obj->data, base_obj->size, + type_name(type))) die(_("local object %s is corrupt"), oid_to_hex(&d->oid)); base_obj->obj = append_obj_to_pack(f, d->oid.hash, base_obj->data, base_obj->size, type); diff --git a/builtin/init-db.c b/builtin/init-db.c index 944ec77fe1..0b7222e718 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -20,6 +20,8 @@ #define TEST_FILEMODE 1 #endif +#define GIT_DEFAULT_HASH_ENVIRONMENT "GIT_DEFAULT_HASH" + static int init_is_bare_repository = 0; static int init_shared_repository = -1; static const char *init_db_template_dir; @@ -176,13 +178,36 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree) return 1; } +void initialize_repository_version(int hash_algo) +{ + char repo_version_string[10]; + int repo_version = GIT_REPO_VERSION; + +#ifndef ENABLE_SHA256 + if (hash_algo != GIT_HASH_SHA1) + die(_("The hash algorithm %s is not supported in this build."), hash_algos[hash_algo].name); +#endif + + if (hash_algo != GIT_HASH_SHA1) + repo_version = GIT_REPO_VERSION_READ; + + /* This forces creation of new config file */ + xsnprintf(repo_version_string, sizeof(repo_version_string), + "%d", repo_version); + git_config_set("core.repositoryformatversion", repo_version_string); + + if (hash_algo != GIT_HASH_SHA1) + git_config_set("extensions.objectformat", + hash_algos[hash_algo].name); +} + static int create_default_files(const char *template_path, - const char *original_git_dir) + const char *original_git_dir, + const struct repository_format *fmt) { struct stat st1; struct strbuf buf = STRBUF_INIT; char *path; - char repo_version_string[10]; char junk[2]; int reinit; int filemode; @@ -244,10 +269,7 @@ static int create_default_files(const char *template_path, exit(1); } - /* This forces creation of new config file */ - xsnprintf(repo_version_string, sizeof(repo_version_string), - "%d", GIT_REPO_VERSION); - git_config_set("core.repositoryformatversion", repo_version_string); + initialize_repository_version(fmt->hash_algo); /* Check filemode trustability */ path = git_path_buf(&buf, "config"); @@ -340,12 +362,33 @@ static void separate_git_dir(const char *git_dir, const char *git_link) write_file(git_link, "gitdir: %s", git_dir); } +static void validate_hash_algorithm(struct repository_format *repo_fmt, int hash) +{ + const char *env = getenv(GIT_DEFAULT_HASH_ENVIRONMENT); + /* + * If we already have an initialized repo, don't allow the user to + * specify a different algorithm, as that could cause corruption. + * Otherwise, if the user has specified one on the command line, use it. + */ + if (repo_fmt->version >= 0 && hash != GIT_HASH_UNKNOWN && hash != repo_fmt->hash_algo) + die(_("attempt to reinitialize repository with different hash")); + else if (hash != GIT_HASH_UNKNOWN) + repo_fmt->hash_algo = hash; + else if (env) { + int env_algo = hash_algo_by_name(env); + if (env_algo == GIT_HASH_UNKNOWN) + die(_("unknown hash algorithm '%s'"), env); + repo_fmt->hash_algo = env_algo; + } +} + int init_db(const char *git_dir, const char *real_git_dir, - const char *template_dir, unsigned int flags) + const char *template_dir, int hash, unsigned int flags) { int reinit; int exist_ok = flags & INIT_DB_EXIST_OK; char *original_git_dir = real_pathdup(git_dir, 1); + struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; if (real_git_dir) { struct stat st; @@ -356,12 +399,12 @@ int init_db(const char *git_dir, const char *real_git_dir, if (!exist_ok && !stat(real_git_dir, &st)) die(_("%s already exists"), real_git_dir); - set_git_dir(real_path(real_git_dir)); + set_git_dir(real_git_dir, 1); git_dir = get_git_dir(); separate_git_dir(git_dir, original_git_dir); } else { - set_git_dir(real_path(git_dir)); + set_git_dir(git_dir, 1); git_dir = get_git_dir(); } startup_info->have_repository = 1; @@ -378,9 +421,11 @@ int init_db(const char *git_dir, const char *real_git_dir, * config file, so this will not fail. What we are catching * is an attempt to reinitialize new repository with an old tool. */ - check_repository_format(); + check_repository_format(&repo_fmt); - reinit = create_default_files(template_dir, original_git_dir); + validate_hash_algorithm(&repo_fmt, hash); + + reinit = create_default_files(template_dir, original_git_dir, &repo_fmt); create_object_directory(); @@ -482,6 +527,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) const char *work_tree; const char *template_dir = NULL; unsigned int flags = 0; + const char *object_format = NULL; + int hash_algo = GIT_HASH_UNKNOWN; const struct option init_db_options[] = { OPT_STRING(0, "template", &template_dir, N_("template-directory"), N_("directory from which templates will be used")), @@ -494,6 +541,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET), OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"), N_("separate git dir from working tree")), + OPT_STRING(0, "object-format", &object_format, N_("hash"), + N_("specify the hash algorithm to use")), OPT_END() }; @@ -546,6 +595,12 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) free(cwd); } + if (object_format) { + hash_algo = hash_algo_by_name(object_format); + if (hash_algo == GIT_HASH_UNKNOWN) + die(_("unknown hash algorithm '%s'"), object_format); + } + if (init_shared_repository != -1) set_shared_repository(init_shared_repository); @@ -597,5 +652,5 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) UNLEAK(work_tree); flags |= INIT_DB_EXIST_OK; - return init_db(git_dir, real_git_dir, template_dir, flags); + return init_db(git_dir, real_git_dir, template_dir, hash_algo, flags); } diff --git a/builtin/log.c b/builtin/log.c index 89873d2dc2..83a4a6188e 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -37,6 +37,7 @@ #include "range-diff.h" #define MAIL_DEFAULT_WRAP 72 +#define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100 /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -207,7 +208,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, if (!rev->show_notes_given && (!rev->pretty_given || w.notes)) rev->show_notes = 1; if (rev->show_notes) - init_display_notes(&rev->notes_opt); + load_display_notes(&rev->notes_opt); if ((rev->diffopt.pickaxe_opts & DIFF_PICKAXE_KINDS_MASK) || rev->diffopt.filter || rev->diffopt.flags.follow_renames) @@ -765,28 +766,56 @@ static void add_header(const char *value) item->string[len] = '\0'; } -#define THREAD_SHALLOW 1 -#define THREAD_DEEP 2 -static int thread; +enum cover_setting { + COVER_UNSET, + COVER_OFF, + COVER_ON, + COVER_AUTO +}; + +enum thread_level { + THREAD_UNSET, + THREAD_SHALLOW, + THREAD_DEEP +}; + +enum cover_from_description { + COVER_FROM_NONE, + COVER_FROM_MESSAGE, + COVER_FROM_SUBJECT, + COVER_FROM_AUTO +}; + +static enum thread_level thread; static int do_signoff; static int base_auto; static char *from; static const char *signature = git_version_string; static const char *signature_file; -static int config_cover_letter; +static enum cover_setting config_cover_letter; static const char *config_output_directory; +static enum cover_from_description cover_from_description_mode = COVER_FROM_MESSAGE; +static int show_notes; +static struct display_notes_opt notes_opt; -enum { - COVER_UNSET, - COVER_OFF, - COVER_ON, - COVER_AUTO -}; +static enum cover_from_description parse_cover_from_description(const char *arg) +{ + if (!arg || !strcmp(arg, "default")) + return COVER_FROM_MESSAGE; + else if (!strcmp(arg, "none")) + return COVER_FROM_NONE; + else if (!strcmp(arg, "message")) + return COVER_FROM_MESSAGE; + else if (!strcmp(arg, "subject")) + return COVER_FROM_SUBJECT; + else if (!strcmp(arg, "auto")) + return COVER_FROM_AUTO; + else + die(_("%s: invalid cover from description mode"), arg); +} static int git_format_config(const char *var, const char *value, void *cb) { - struct rev_info *rev = cb; - if (!strcmp(var, "format.headers")) { if (!value) die(_("format.headers without value")); @@ -836,7 +865,7 @@ static int git_format_config(const char *var, const char *value, void *cb) thread = THREAD_SHALLOW; return 0; } - thread = git_config_bool(var, value) && THREAD_SHALLOW; + thread = git_config_bool(var, value) ? THREAD_SHALLOW : THREAD_UNSET; return 0; } if (!strcmp(var, "format.signoff")) { @@ -873,19 +902,17 @@ static int git_format_config(const char *var, const char *value, void *cb) return 0; } if (!strcmp(var, "format.notes")) { - struct strbuf buf = STRBUF_INIT; int b = git_parse_maybe_bool(value); - if (!b) - return 0; - rev->show_notes = 1; - if (b < 0) { - strbuf_addstr(&buf, value); - expand_notes_ref(&buf); - string_list_append(&rev->notes_opt.extra_notes_refs, - strbuf_detach(&buf, NULL)); - } else { - rev->notes_opt.use_default_notes = 1; - } + if (b < 0) + enable_ref_display_notes(¬es_opt, &show_notes, value); + else if (b) + enable_default_display_notes(¬es_opt, &show_notes); + else + disable_display_notes(¬es_opt, &show_notes); + return 0; + } + if (!strcmp(var, "format.coverfromdescription")) { + cover_from_description_mode = parse_cover_from_description(value); return 0; } @@ -994,20 +1021,6 @@ static void print_signature(FILE *file) putc('\n', file); } -static void add_branch_description(struct strbuf *buf, const char *branch_name) -{ - struct strbuf desc = STRBUF_INIT; - if (!branch_name || !*branch_name) - return; - read_branch_desc(&desc, branch_name); - if (desc.len) { - strbuf_addch(buf, '\n'); - strbuf_addbuf(buf, &desc); - strbuf_addch(buf, '\n'); - } - strbuf_release(&desc); -} - static char *find_branch_name(struct rev_info *rev) { int i, positive = -1; @@ -1054,6 +1067,63 @@ static void show_diffstat(struct rev_info *rev, fprintf(rev->diffopt.file, "\n"); } +static void prepare_cover_text(struct pretty_print_context *pp, + const char *branch_name, + struct strbuf *sb, + const char *encoding, + int need_8bit_cte) +{ + const char *subject = "*** SUBJECT HERE ***"; + const char *body = "*** BLURB HERE ***"; + struct strbuf description_sb = STRBUF_INIT; + struct strbuf subject_sb = STRBUF_INIT; + + if (cover_from_description_mode == COVER_FROM_NONE) + goto do_pp; + + if (branch_name && *branch_name) + read_branch_desc(&description_sb, branch_name); + if (!description_sb.len) + goto do_pp; + + if (cover_from_description_mode == COVER_FROM_SUBJECT || + cover_from_description_mode == COVER_FROM_AUTO) + body = format_subject(&subject_sb, description_sb.buf, " "); + + if (cover_from_description_mode == COVER_FROM_MESSAGE || + (cover_from_description_mode == COVER_FROM_AUTO && + subject_sb.len > COVER_FROM_AUTO_MAX_SUBJECT_LEN)) + body = description_sb.buf; + else + subject = subject_sb.buf; + +do_pp: + pp_title_line(pp, &subject, sb, encoding, need_8bit_cte); + pp_remainder(pp, &body, sb, 0); + + strbuf_release(&description_sb); + strbuf_release(&subject_sb); +} + +static int get_notes_refs(struct string_list_item *item, void *arg) +{ + argv_array_pushf(arg, "--notes=%s", item->string); + return 0; +} + +static void get_notes_args(struct argv_array *arg, struct rev_info *rev) +{ + if (!rev->show_notes) { + argv_array_push(arg, "--no-notes"); + } else if (rev->notes_opt.use_default_notes > 0 || + (rev->notes_opt.use_default_notes == -1 && + !rev->notes_opt.extra_notes_refs.nr)) { + argv_array_push(arg, "--notes"); + } else { + for_each_string_list(&rev->notes_opt.extra_notes_refs, get_notes_refs, arg); + } +} + static void make_cover_letter(struct rev_info *rev, int use_stdout, struct commit *origin, int nr, struct commit **list, @@ -1061,8 +1131,6 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, int quiet) { const char *committer; - const char *body = "*** SUBJECT HERE ***\n\n*** BLURB HERE ***\n"; - const char *msg; struct shortlog log; struct strbuf sb = STRBUF_INIT; int i; @@ -1092,15 +1160,12 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, if (!branch_name) branch_name = find_branch_name(rev); - msg = body; pp.fmt = CMIT_FMT_EMAIL; pp.date_mode.type = DATE_RFC2822; pp.rev = rev; pp.print_email_subject = 1; pp_user_info(&pp, NULL, &sb, committer, encoding); - pp_title_line(&pp, &msg, &sb, encoding, need_8bit_cte); - pp_remainder(&pp, &msg, &sb, 0); - add_branch_description(&sb, branch_name); + prepare_cover_text(&pp, branch_name, &sb, encoding, need_8bit_cte); fprintf(rev->diffopt.file, "%s\n", sb.buf); strbuf_release(&sb); @@ -1131,13 +1196,16 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, * can be added later if deemed desirable. */ struct diff_options opts; + struct argv_array other_arg = ARGV_ARRAY_INIT; diff_setup(&opts); opts.file = rev->diffopt.file; opts.use_color = rev->diffopt.use_color; diff_setup_done(&opts); fprintf_ln(rev->diffopt.file, "%s", rev->rdiff_title); + get_notes_args(&other_arg, rev); show_range_diff(rev->rdiff1, rev->rdiff2, - rev->creation_factor, 1, &opts); + rev->creation_factor, 1, &opts, &other_arg); + argv_array_clear(&other_arg); } } @@ -1249,9 +1317,9 @@ static int output_directory_callback(const struct option *opt, const char *arg, static int thread_callback(const struct option *opt, const char *arg, int unset) { - int *thread = (int *)opt->value; + enum thread_level *thread = (enum thread_level *)opt->value; if (unset) - *thread = 0; + *thread = THREAD_UNSET; else if (!arg || !strcmp(arg, "shallow")) *thread = THREAD_SHALLOW; else if (!strcmp(arg, "deep")) @@ -1298,7 +1366,7 @@ static int header_callback(const struct option *opt, const char *arg, int unset) string_list_clear(&extra_to, 0); string_list_clear(&extra_cc, 0); } else { - add_header(arg); + add_header(arg); } return 0; } @@ -1354,7 +1422,7 @@ static struct commit *get_base_commit(const char *base_commit, base = lookup_commit_reference_by_name(base_commit); if (!base) die(_("unknown commit %s"), base_commit); - } else if ((base_commit && !strcmp(base_commit, "auto")) || base_auto) { + } else if ((base_commit && !strcmp(base_commit, "auto"))) { struct branch *curr_branch = branch_get(NULL); const char *upstream = branch_get_upstream(curr_branch, NULL); if (upstream) { @@ -1542,6 +1610,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) int use_patch_format = 0; int quiet = 0; int reroll_count = -1; + char *cover_from_description_arg = NULL; char *branch_name = NULL; char *base_commit = NULL; struct base_tree_info bases; @@ -1578,6 +1647,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) { OPTION_CALLBACK, 0, "rfc", &rev, NULL, N_("Use [RFC PATCH] instead of [PATCH]"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, rfc_callback }, + OPT_STRING(0, "cover-from-description", &cover_from_description_arg, + N_("cover-from-description-mode"), + N_("generate parts of a cover letter based on a branch's description")), { OPTION_CALLBACK, 0, "subject-prefix", &rev, N_("prefix"), N_("Use [<prefix>] instead of [PATCH]"), PARSE_OPT_NONEG, subject_prefix_callback }, @@ -1641,8 +1713,11 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) extra_to.strdup_strings = 1; extra_cc.strdup_strings = 1; init_log_defaults(); + init_display_notes(¬es_opt); + git_config(git_format_config, NULL); repo_init_revisions(the_repository, &rev, prefix); - git_config(git_format_config, &rev); + rev.show_notes = show_notes; + memcpy(&rev.notes_opt, ¬es_opt, sizeof(notes_opt)); rev.commit_format = CMIT_FMT_EMAIL; rev.expand_tabs_in_log_default = 0; rev.verbose_header = 1; @@ -1654,6 +1729,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) s_r_opt.def = "HEAD"; s_r_opt.revarg_opt = REVARG_COMMITTISH; + if (base_auto) + base_commit = "auto"; + if (default_attach) { rev.mime_boundary = default_attach; rev.no_inline = 1; @@ -1669,6 +1747,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); + if (cover_from_description_arg) + cover_from_description_mode = parse_cover_from_description(cover_from_description_arg); + if (0 < reroll_count) { struct strbuf sprefix = STRBUF_INIT; strbuf_addf(&sprefix, "%s v%d", @@ -1755,7 +1836,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.diffopt.flags.binary = 1; if (rev.show_notes) - init_display_notes(&rev.notes_opt); + load_display_notes(&rev.notes_opt); if (!output_directory && !use_stdout) output_directory = config_output_directory; @@ -1914,7 +1995,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } memset(&bases, 0, sizeof(bases)); - if (base_commit || base_auto) { + if (base_commit) { struct commit *base = get_base_commit(base_commit, list, nr); reset_revision_walk(); clear_object_flags(UNINTERESTING); diff --git a/builtin/merge.c b/builtin/merge.c index 062e911441..d127d2225f 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -62,6 +62,7 @@ static int show_diffstat = 1, shortlog_len = -1, squash; static int option_commit = -1; static int option_edit = -1; static int allow_trivial = 1, have_message, verify_signatures; +static int check_trust_level = 1; static int overwrite_ignore = 1; static struct strbuf merge_msg = STRBUF_INIT; static struct strategy **use_strategies; @@ -631,6 +632,8 @@ static int git_merge_config(const char *k, const char *v, void *cb) } else if (!strcmp(k, "commit.gpgsign")) { sign_commit = git_config_bool(k, v) ? "" : NULL; return 0; + } else if (!strcmp(k, "gpg.mintrustlevel")) { + check_trust_level = 0; } status = fmt_merge_msg_config(k, v, cb); @@ -1397,7 +1400,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) die(_("Can merge only exactly one commit into empty head")); if (verify_signatures) - verify_merge_signature(remoteheads->item, verbosity); + verify_merge_signature(remoteheads->item, verbosity, + check_trust_level); remote_head_oid = &remoteheads->item->object.oid; read_empty(remote_head_oid, 0); @@ -1420,7 +1424,8 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (verify_signatures) { for (p = remoteheads; p; p = p->next) { - verify_merge_signature(p->item, verbosity); + verify_merge_signature(p->item, verbosity, + check_trust_level); } } diff --git a/builtin/mktag.c b/builtin/mktag.c index 6fb7dc8578..4982d3a93e 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -29,8 +29,11 @@ static int verify_object(const struct object_id *oid, const char *expected_type) const struct object_id *repl = lookup_replace_object(the_repository, oid); if (buffer) { - if (type == type_from_string(expected_type)) - ret = check_object_signature(repl, buffer, size, expected_type); + if (type == type_from_string(expected_type)) { + ret = check_object_signature(the_repository, repl, + buffer, size, + expected_type); + } free(buffer); } return ret; diff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c index b1ea1a6aa1..5bf88cd2a8 100644 --- a/builtin/multi-pack-index.c +++ b/builtin/multi-pack-index.c @@ -6,21 +6,25 @@ #include "trace2.h" static char const * const builtin_multi_pack_index_usage[] = { - N_("git multi-pack-index [--object-dir=<dir>] (write|verify|expire|repack --batch-size=<size>)"), + N_("git multi-pack-index [<options>] (write|verify|expire|repack --batch-size=<size>)"), NULL }; static struct opts_multi_pack_index { const char *object_dir; unsigned long batch_size; + int progress; } opts; int cmd_multi_pack_index(int argc, const char **argv, const char *prefix) { + unsigned flags = 0; + static struct option builtin_multi_pack_index_options[] = { OPT_FILENAME(0, "object-dir", &opts.object_dir, N_("object directory containing set of packfile and pack-index pairs")), + OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")), OPT_MAGNITUDE(0, "batch-size", &opts.batch_size, N_("during repack, collect pack-files of smaller size into a batch that is larger than this size")), OPT_END(), @@ -28,12 +32,15 @@ int cmd_multi_pack_index(int argc, const char **argv, git_config(git_default_config, NULL); + opts.progress = isatty(2); argc = parse_options(argc, argv, prefix, builtin_multi_pack_index_options, builtin_multi_pack_index_usage, 0); if (!opts.object_dir) opts.object_dir = get_object_directory(); + if (opts.progress) + flags |= MIDX_PROGRESS; if (argc == 0) usage_with_options(builtin_multi_pack_index_usage, @@ -47,16 +54,17 @@ int cmd_multi_pack_index(int argc, const char **argv, trace2_cmd_mode(argv[0]); if (!strcmp(argv[0], "repack")) - return midx_repack(the_repository, opts.object_dir, (size_t)opts.batch_size); + return midx_repack(the_repository, opts.object_dir, + (size_t)opts.batch_size, flags); if (opts.batch_size) die(_("--batch-size option is only for 'repack' subcommand")); if (!strcmp(argv[0], "write")) - return write_midx_file(opts.object_dir); + return write_midx_file(opts.object_dir, flags); if (!strcmp(argv[0], "verify")) - return verify_midx_file(the_repository, opts.object_dir); + return verify_midx_file(the_repository, opts.object_dir, flags); if (!strcmp(argv[0], "expire")) - return expire_midx_packs(the_repository, opts.object_dir); + return expire_midx_packs(the_repository, opts.object_dir, flags); die(_("unrecognized subcommand: %s"), argv[0]); } diff --git a/builtin/name-rev.c b/builtin/name-rev.c index b0f0776947..a9dcd25e46 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -6,6 +6,7 @@ #include "tag.h" #include "refs.h" #include "parse-options.h" +#include "prio-queue.h" #include "sha1-lookup.h" #include "commit-slab.h" @@ -15,15 +16,15 @@ */ #define CUTOFF_DATE_SLOP 86400 -typedef struct rev_name { - const char *tip_name; +struct rev_name { + char *tip_name; timestamp_t taggerdate; int generation; int distance; int from_tag; -} rev_name; +}; -define_commit_slab(commit_rev_name, struct rev_name *); +define_commit_slab(commit_rev_name, struct rev_name); static timestamp_t cutoff = TIME_MAX; static struct commit_rev_name rev_names; @@ -31,16 +32,16 @@ static struct commit_rev_name rev_names; /* How many generations are maximally preferred over _one_ merge traversal? */ #define MERGE_TRAVERSAL_WEIGHT 65535 -static struct rev_name *get_commit_rev_name(struct commit *commit) +static int is_valid_rev_name(const struct rev_name *name) { - struct rev_name **slot = commit_rev_name_peek(&rev_names, commit); - - return slot ? *slot : NULL; + return name && (name->generation || name->tip_name); } -static void set_commit_rev_name(struct commit *commit, struct rev_name *name) +static struct rev_name *get_commit_rev_name(const struct commit *commit) { - *commit_rev_name_at(&rev_names, commit) = name; + struct rev_name *name = commit_rev_name_peek(&rev_names, commit); + + return is_valid_rev_name(name) ? name : NULL; } static int is_better_name(struct rev_name *name, @@ -79,68 +80,135 @@ static int is_better_name(struct rev_name *name, return 0; } -static void name_rev(struct commit *commit, - const char *tip_name, timestamp_t taggerdate, - int generation, int distance, int from_tag, - int deref) +static struct rev_name *create_or_update_name(struct commit *commit, + timestamp_t taggerdate, + int generation, int distance, + int from_tag) { - struct rev_name *name = get_commit_rev_name(commit); - struct commit_list *parents; - int parent_number = 1; - char *to_free = NULL; - - parse_commit(commit); + struct rev_name *name = commit_rev_name_at(&rev_names, commit); + + if (is_valid_rev_name(name)) { + if (!is_better_name(name, taggerdate, distance, from_tag)) + return NULL; + + /* + * This string might still be shared with ancestors + * (generation > 0). We can release it here regardless, + * because the new name that has just won will be better + * for them as well, so name_rev() will replace these + * stale pointers when it processes the parents. + */ + if (!name->generation) + free(name->tip_name); + } - if (commit->date < cutoff) - return; + name->taggerdate = taggerdate; + name->generation = generation; + name->distance = distance; + name->from_tag = from_tag; - if (deref) { - tip_name = to_free = xstrfmt("%s^0", tip_name); + return name; +} - if (generation) - die("generation: %d, but deref?", generation); +static char *get_parent_name(const struct rev_name *name, int parent_number) +{ + struct strbuf sb = STRBUF_INIT; + size_t len; + + strip_suffix(name->tip_name, "^0", &len); + if (name->generation > 0) { + strbuf_grow(&sb, len + + 1 + decimal_width(name->generation) + + 1 + decimal_width(parent_number)); + strbuf_addf(&sb, "%.*s~%d^%d", (int)len, name->tip_name, + name->generation, parent_number); + } else { + strbuf_grow(&sb, len + + 1 + decimal_width(parent_number)); + strbuf_addf(&sb, "%.*s^%d", (int)len, name->tip_name, + parent_number); } + return strbuf_detach(&sb, NULL); +} - if (name == NULL) { - name = xmalloc(sizeof(rev_name)); - set_commit_rev_name(commit, name); - goto copy_data; - } else if (is_better_name(name, taggerdate, distance, from_tag)) { -copy_data: - name->tip_name = tip_name; - name->taggerdate = taggerdate; - name->generation = generation; - name->distance = distance; - name->from_tag = from_tag; - } else { - free(to_free); +static void name_rev(struct commit *start_commit, + const char *tip_name, timestamp_t taggerdate, + int from_tag, int deref) +{ + struct prio_queue queue; + struct commit *commit; + struct commit **parents_to_queue = NULL; + size_t parents_to_queue_nr, parents_to_queue_alloc = 0; + struct rev_name *start_name; + + parse_commit(start_commit); + if (start_commit->date < cutoff) return; - } - for (parents = commit->parents; - parents; - parents = parents->next, parent_number++) { - if (parent_number > 1) { - size_t len; - char *new_name; - - strip_suffix(tip_name, "^0", &len); - if (generation > 0) - new_name = xstrfmt("%.*s~%d^%d", (int)len, tip_name, - generation, parent_number); - else - new_name = xstrfmt("%.*s^%d", (int)len, tip_name, - parent_number); - - name_rev(parents->item, new_name, taggerdate, 0, - distance + MERGE_TRAVERSAL_WEIGHT, - from_tag, 0); - } else { - name_rev(parents->item, tip_name, taggerdate, - generation + 1, distance + 1, - from_tag, 0); + start_name = create_or_update_name(start_commit, taggerdate, 0, 0, + from_tag); + if (!start_name) + return; + if (deref) + start_name->tip_name = xstrfmt("%s^0", tip_name); + else + start_name->tip_name = xstrdup(tip_name); + + memset(&queue, 0, sizeof(queue)); /* Use the prio_queue as LIFO */ + prio_queue_put(&queue, start_commit); + + while ((commit = prio_queue_get(&queue))) { + struct rev_name *name = get_commit_rev_name(commit); + struct commit_list *parents; + int parent_number = 1; + + parents_to_queue_nr = 0; + + for (parents = commit->parents; + parents; + parents = parents->next, parent_number++) { + struct commit *parent = parents->item; + struct rev_name *parent_name; + int generation, distance; + + parse_commit(parent); + if (parent->date < cutoff) + continue; + + if (parent_number > 1) { + generation = 0; + distance = name->distance + MERGE_TRAVERSAL_WEIGHT; + } else { + generation = name->generation + 1; + distance = name->distance + 1; + } + + parent_name = create_or_update_name(parent, taggerdate, + generation, + distance, from_tag); + if (parent_name) { + if (parent_number > 1) + parent_name->tip_name = + get_parent_name(name, + parent_number); + else + parent_name->tip_name = name->tip_name; + ALLOC_GROW(parents_to_queue, + parents_to_queue_nr + 1, + parents_to_queue_alloc); + parents_to_queue[parents_to_queue_nr] = parent; + parents_to_queue_nr++; + } } + + /* The first parent must come out first from the prio_queue */ + while (parents_to_queue_nr) + prio_queue_put(&queue, + parents_to_queue[--parents_to_queue_nr]); } + + clear_prio_queue(&queue); + free(parents_to_queue); } static int subpath_matches(const char *path, const char *filter) @@ -161,10 +229,10 @@ static const char *name_ref_abbrev(const char *refname, int shorten_unambiguous) { if (shorten_unambiguous) refname = shorten_unambiguous_ref(refname, 0); - else if (starts_with(refname, "refs/heads/")) - refname = refname + 11; - else if (starts_with(refname, "refs/")) - refname = refname + 5; + else if (skip_prefix(refname, "refs/heads/", &refname)) + ; /* refname already advanced */ + else + skip_prefix(refname, "refs/", &refname); return refname; } @@ -179,6 +247,10 @@ static struct tip_table { struct tip_table_entry { struct object_id oid; const char *refname; + struct commit *commit; + timestamp_t taggerdate; + unsigned int from_tag:1; + unsigned int deref:1; } *table; int nr; int alloc; @@ -186,13 +258,18 @@ static struct tip_table { } tip_table; static void add_to_tip_table(const struct object_id *oid, const char *refname, - int shorten_unambiguous) + int shorten_unambiguous, struct commit *commit, + timestamp_t taggerdate, int from_tag, int deref) { refname = name_ref_abbrev(refname, shorten_unambiguous); ALLOC_GROW(tip_table.table, tip_table.nr + 1, tip_table.alloc); oidcpy(&tip_table.table[tip_table.nr].oid, oid); tip_table.table[tip_table.nr].refname = xstrdup(refname); + tip_table.table[tip_table.nr].commit = commit; + tip_table.table[tip_table.nr].taggerdate = taggerdate; + tip_table.table[tip_table.nr].from_tag = from_tag; + tip_table.table[tip_table.nr].deref = deref; tip_table.nr++; tip_table.sorted = 0; } @@ -203,12 +280,30 @@ static int tipcmp(const void *a_, const void *b_) return oidcmp(&a->oid, &b->oid); } +static int cmp_by_tag_and_age(const void *a_, const void *b_) +{ + const struct tip_table_entry *a = a_, *b = b_; + int cmp; + + /* Prefer tags. */ + cmp = b->from_tag - a->from_tag; + if (cmp) + return cmp; + + /* Older is better. */ + if (a->taggerdate < b->taggerdate) + return -1; + return a->taggerdate != b->taggerdate; +} + static int name_ref(const char *path, const struct object_id *oid, int flags, void *cb_data) { struct object *o = parse_object(the_repository, oid); struct name_ref_data *data = cb_data; int can_abbreviate_output = data->tags_only && data->name_only; int deref = 0; + int from_tag = 0; + struct commit *commit = NULL; timestamp_t taggerdate = TIME_MAX; if (data->tags_only && !starts_with(path, "refs/tags/")) @@ -257,8 +352,6 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo return 0; } - add_to_tip_table(oid, path, can_abbreviate_output); - while (o && o->type == OBJ_TAG) { struct tag *t = (struct tag *) o; if (!t->tagged) @@ -268,18 +361,35 @@ static int name_ref(const char *path, const struct object_id *oid, int flags, vo taggerdate = t->date; } if (o && o->type == OBJ_COMMIT) { - struct commit *commit = (struct commit *)o; - int from_tag = starts_with(path, "refs/tags/"); - + commit = (struct commit *)o; + from_tag = starts_with(path, "refs/tags/"); if (taggerdate == TIME_MAX) - taggerdate = ((struct commit *)o)->date; - path = name_ref_abbrev(path, can_abbreviate_output); - name_rev(commit, xstrdup(path), taggerdate, 0, 0, - from_tag, deref); + taggerdate = commit->date; } + + add_to_tip_table(oid, path, can_abbreviate_output, commit, taggerdate, + from_tag, deref); return 0; } +static void name_tips(void) +{ + int i; + + /* + * Try to set better names first, so that worse ones spread + * less. + */ + QSORT(tip_table.table, tip_table.nr, cmp_by_tag_and_age); + for (i = 0; i < tip_table.nr; i++) { + struct tip_table_entry *e = &tip_table.table[i]; + if (e->commit) { + name_rev(e->commit, e->refname, e->taggerdate, + e->from_tag, e->deref); + } + } +} + static const unsigned char *nth_tip_table_ent(size_t ix, void *table_) { struct tip_table_entry *table = table_; @@ -309,11 +419,11 @@ static const char *get_exact_ref_match(const struct object *o) static const char *get_rev_name(const struct object *o, struct strbuf *buf) { struct rev_name *n; - struct commit *c; + const struct commit *c; if (o->type != OBJ_COMMIT) return get_exact_ref_match(o); - c = (struct commit *) o; + c = (const struct commit *) o; n = get_commit_rev_name(c); if (!n) return NULL; @@ -321,11 +431,10 @@ static const char *get_rev_name(const struct object *o, struct strbuf *buf) if (!n->generation) return n->tip_name; else { - int len = strlen(n->tip_name); - if (len > 2 && !strcmp(n->tip_name + len - 2, "^0")) - len -= 2; strbuf_reset(buf); - strbuf_addf(buf, "%.*s~%d", len, n->tip_name, n->generation); + strbuf_addstr(buf, n->tip_name); + strbuf_strip_suffix(buf, "^0"); + strbuf_addf(buf, "~%d", n->generation); return buf->buf; } } @@ -493,6 +602,7 @@ int cmd_name_rev(int argc, const char **argv, const char *prefix) cutoff = TIME_MIN; } for_each_ref(name_ref, &data); + name_tips(); if (transform_stdin) { char buffer[2048]; diff --git a/builtin/notes.c b/builtin/notes.c index 02e97f55c5..35e468ea2d 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -513,7 +513,7 @@ static int copy(int argc, const char **argv, const char *prefix) } } - if (argc < 2) { + if (argc < 1) { error(_("too few parameters")); usage_with_options(git_notes_copy_usage, options); } @@ -622,7 +622,7 @@ static int append_edit(int argc, const char **argv, const char *prefix) strbuf_grow(&d.buf, size + 1); if (d.buf.len && prev_buf && size) - strbuf_insert(&d.buf, 0, "\n", 1); + strbuf_insertstr(&d.buf, 0, "\n"); if (prev_buf && size) strbuf_insert(&d.buf, 0, prev_buf, size); free(prev_buf); @@ -745,7 +745,7 @@ static int merge_commit(struct notes_merge_options *o) memset(&pretty_ctx, 0, sizeof(pretty_ctx)); format_commit_message(partial, "%s", &msg, &pretty_ctx); strbuf_trim(&msg); - strbuf_insert(&msg, 0, "notes: ", 7); + strbuf_insertstr(&msg, 0, "notes: "); update_ref(msg.buf, o->local_ref, &oid, is_null_oid(&parent_oid) ? NULL : &parent_oid, 0, UPDATE_REFS_DIE_ON_ERR); diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 5876583220..4c2bb170c6 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -92,10 +92,11 @@ static struct progress *progress_state; static struct packed_git *reuse_packfile; static uint32_t reuse_packfile_objects; -static off_t reuse_packfile_offset; +static struct bitmap *reuse_packfile_bitmap; static int use_bitmap_index_default = 1; static int use_bitmap_index = -1; +static int allow_pack_reuse = 1; static enum { WRITE_BITMAP_FALSE = 0, WRITE_BITMAP_QUIET, @@ -163,7 +164,7 @@ static void *get_delta(struct object_entry *entry) delta_buf = diff_delta(base_buf, base_size, buf, size, &delta_size, 0); /* - * We succesfully computed this delta once but dropped it for + * We successfully computed this delta once but dropped it for * memory reasons. Something is very wrong if this time we * recompute and create a different delta. */ @@ -303,7 +304,8 @@ static unsigned long write_no_reuse_object(struct hashfile *f, struct object_ent if (!usable_delta) { if (oe_type(entry) == OBJ_BLOB && oe_size_greater_than(&to_pack, entry, big_file_threshold) && - (st = open_istream(&entry->idx.oid, &type, &size, NULL)) != NULL) + (st = open_istream(the_repository, &entry->idx.oid, &type, + &size, NULL)) != NULL) buf = NULL; else { buf = read_object_file(&entry->idx.oid, &type, &size); @@ -784,57 +786,186 @@ static struct object_entry **compute_write_order(void) return wo; } -static off_t write_reused_pack(struct hashfile *f) + +/* + * A reused set of objects. All objects in a chunk have the same + * relative position in the original packfile and the generated + * packfile. + */ + +static struct reused_chunk { + /* The offset of the first object of this chunk in the original + * packfile. */ + off_t original; + /* The offset of the first object of this chunk in the generated + * packfile minus "original". */ + off_t difference; +} *reused_chunks; +static int reused_chunks_nr; +static int reused_chunks_alloc; + +static void record_reused_object(off_t where, off_t offset) { - unsigned char buffer[8192]; - off_t to_write, total; - int fd; + if (reused_chunks_nr && reused_chunks[reused_chunks_nr-1].difference == offset) + return; - if (!is_pack_valid(reuse_packfile)) - die(_("packfile is invalid: %s"), reuse_packfile->pack_name); + ALLOC_GROW(reused_chunks, reused_chunks_nr + 1, + reused_chunks_alloc); + reused_chunks[reused_chunks_nr].original = where; + reused_chunks[reused_chunks_nr].difference = offset; + reused_chunks_nr++; +} - fd = git_open(reuse_packfile->pack_name); - if (fd < 0) - die_errno(_("unable to open packfile for reuse: %s"), - reuse_packfile->pack_name); +/* + * Binary search to find the chunk that "where" is in. Note + * that we're not looking for an exact match, just the first + * chunk that contains it (which implicitly ends at the start + * of the next chunk. + */ +static off_t find_reused_offset(off_t where) +{ + int lo = 0, hi = reused_chunks_nr; + while (lo < hi) { + int mi = lo + ((hi - lo) / 2); + if (where == reused_chunks[mi].original) + return reused_chunks[mi].difference; + if (where < reused_chunks[mi].original) + hi = mi; + else + lo = mi + 1; + } - if (lseek(fd, sizeof(struct pack_header), SEEK_SET) == -1) - die_errno(_("unable to seek in reused packfile")); + /* + * The first chunk starts at zero, so we can't have gone below + * there. + */ + assert(lo); + return reused_chunks[lo-1].difference; +} - if (reuse_packfile_offset < 0) - reuse_packfile_offset = reuse_packfile->pack_size - the_hash_algo->rawsz; +static void write_reused_pack_one(size_t pos, struct hashfile *out, + struct pack_window **w_curs) +{ + off_t offset, next, cur; + enum object_type type; + unsigned long size; - total = to_write = reuse_packfile_offset - sizeof(struct pack_header); + offset = reuse_packfile->revindex[pos].offset; + next = reuse_packfile->revindex[pos + 1].offset; - while (to_write) { - int read_pack = xread(fd, buffer, sizeof(buffer)); + record_reused_object(offset, offset - hashfile_total(out)); - if (read_pack <= 0) - die_errno(_("unable to read from reused packfile")); + cur = offset; + type = unpack_object_header(reuse_packfile, w_curs, &cur, &size); + assert(type >= 0); - if (read_pack > to_write) - read_pack = to_write; + if (type == OBJ_OFS_DELTA) { + off_t base_offset; + off_t fixup; - hashwrite(f, buffer, read_pack); - to_write -= read_pack; + unsigned char header[MAX_PACK_OBJECT_HEADER]; + unsigned len; + + base_offset = get_delta_base(reuse_packfile, w_curs, &cur, type, offset); + assert(base_offset != 0); + + /* Convert to REF_DELTA if we must... */ + if (!allow_ofs_delta) { + int base_pos = find_revindex_position(reuse_packfile, base_offset); + struct object_id base_oid; + + nth_packed_object_id(&base_oid, reuse_packfile, + reuse_packfile->revindex[base_pos].nr); + + len = encode_in_pack_object_header(header, sizeof(header), + OBJ_REF_DELTA, size); + hashwrite(out, header, len); + hashwrite(out, base_oid.hash, the_hash_algo->rawsz); + copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur); + return; + } + + /* Otherwise see if we need to rewrite the offset... */ + fixup = find_reused_offset(offset) - + find_reused_offset(base_offset); + if (fixup) { + unsigned char ofs_header[10]; + unsigned i, ofs_len; + off_t ofs = offset - base_offset - fixup; + + len = encode_in_pack_object_header(header, sizeof(header), + OBJ_OFS_DELTA, size); + + i = sizeof(ofs_header) - 1; + ofs_header[i] = ofs & 127; + while (ofs >>= 7) + ofs_header[--i] = 128 | (--ofs & 127); + + ofs_len = sizeof(ofs_header) - i; + + hashwrite(out, header, len); + hashwrite(out, ofs_header + sizeof(ofs_header) - ofs_len, ofs_len); + copy_pack_data(out, reuse_packfile, w_curs, cur, next - cur); + return; + } + + /* ...otherwise we have no fixup, and can write it verbatim */ + } + + copy_pack_data(out, reuse_packfile, w_curs, offset, next - offset); +} + +static size_t write_reused_pack_verbatim(struct hashfile *out, + struct pack_window **w_curs) +{ + size_t pos = 0; + + while (pos < reuse_packfile_bitmap->word_alloc && + reuse_packfile_bitmap->words[pos] == (eword_t)~0) + pos++; + + if (pos) { + off_t to_write; + + written = (pos * BITS_IN_EWORD); + to_write = reuse_packfile->revindex[written].offset + - sizeof(struct pack_header); + + /* We're recording one chunk, not one object. */ + record_reused_object(sizeof(struct pack_header), 0); + hashflush(out); + copy_pack_data(out, reuse_packfile, w_curs, + sizeof(struct pack_header), to_write); - /* - * We don't know the actual number of objects written, - * only how many bytes written, how many bytes total, and - * how many objects total. So we can fake it by pretending all - * objects we are writing are the same size. This gives us a - * smooth progress meter, and at the end it matches the true - * answer. - */ - written = reuse_packfile_objects * - (((double)(total - to_write)) / total); display_progress(progress_state, written); } + return pos; +} + +static void write_reused_pack(struct hashfile *f) +{ + size_t i = 0; + uint32_t offset; + struct pack_window *w_curs = NULL; + + if (allow_ofs_delta) + i = write_reused_pack_verbatim(f, &w_curs); - close(fd); - written = reuse_packfile_objects; - display_progress(progress_state, written); - return reuse_packfile_offset - sizeof(struct pack_header); + for (; i < reuse_packfile_bitmap->word_alloc; ++i) { + eword_t word = reuse_packfile_bitmap->words[i]; + size_t pos = (i * BITS_IN_EWORD); + + for (offset = 0; offset < BITS_IN_EWORD; ++offset) { + if ((word >> offset) == 0) + break; + + offset += ewah_bit_ctz64(word >> offset); + write_reused_pack_one(pos + offset, f, &w_curs); + display_progress(progress_state, ++written); + } + } + + unuse_pack(&w_curs); } static const char no_split_warning[] = N_( @@ -867,11 +998,9 @@ static void write_pack_file(void) offset = write_pack_header(f, nr_remaining); if (reuse_packfile) { - off_t packfile_size; assert(pack_to_stdout); - - packfile_size = write_reused_pack(f); - offset += packfile_size; + write_reused_pack(f); + offset = hashfile_total(f); } nr_written = 0; @@ -1000,6 +1129,10 @@ static int have_duplicate_entry(const struct object_id *oid, { struct object_entry *entry; + if (reuse_packfile_bitmap && + bitmap_walk_contains(bitmap_git, reuse_packfile_bitmap, oid)) + return 1; + entry = packlist_find(&to_pack, oid); if (!entry) return 0; @@ -1486,23 +1619,17 @@ static void cleanup_preferred_base(void) * deltify other objects against, in order to avoid * circular deltas. */ -static int can_reuse_delta(const unsigned char *base_sha1, +static int can_reuse_delta(const struct object_id *base_oid, struct object_entry *delta, struct object_entry **base_out) { struct object_entry *base; - struct object_id base_oid; - - if (!base_sha1) - return 0; - - oidread(&base_oid, base_sha1); /* * First see if we're already sending the base (or it's explicitly in * our "excluded" list). */ - base = packlist_find(&to_pack, &base_oid); + base = packlist_find(&to_pack, base_oid); if (base) { if (!in_same_island(&delta->idx.oid, &base->idx.oid)) return 0; @@ -1515,9 +1642,9 @@ static int can_reuse_delta(const unsigned char *base_sha1, * even if it was buried too deep in history to make it into the * packing list. */ - if (thin && bitmap_has_oid_in_uninteresting(bitmap_git, &base_oid)) { + if (thin && bitmap_has_oid_in_uninteresting(bitmap_git, base_oid)) { if (use_delta_islands) { - if (!in_same_island(&delta->idx.oid, &base_oid)) + if (!in_same_island(&delta->idx.oid, base_oid)) return 0; } *base_out = NULL; @@ -1534,7 +1661,8 @@ static void check_object(struct object_entry *entry) if (IN_PACK(entry)) { struct packed_git *p = IN_PACK(entry); struct pack_window *w_curs = NULL; - const unsigned char *base_ref = NULL; + int have_base = 0; + struct object_id base_ref; struct object_entry *base_entry; unsigned long used, used_0; unsigned long avail; @@ -1575,9 +1703,13 @@ static void check_object(struct object_entry *entry) unuse_pack(&w_curs); return; case OBJ_REF_DELTA: - if (reuse_delta && !entry->preferred_base) - base_ref = use_pack(p, &w_curs, - entry->in_pack_offset + used, NULL); + if (reuse_delta && !entry->preferred_base) { + oidread(&base_ref, + use_pack(p, &w_curs, + entry->in_pack_offset + used, + NULL)); + have_base = 1; + } entry->in_pack_header_size = used + the_hash_algo->rawsz; break; case OBJ_OFS_DELTA: @@ -1607,13 +1739,15 @@ static void check_object(struct object_entry *entry) revidx = find_pack_revindex(p, ofs); if (!revidx) goto give_up; - base_ref = nth_packed_object_sha1(p, revidx->nr); + if (!nth_packed_object_id(&base_ref, p, revidx->nr)) + have_base = 1; } entry->in_pack_header_size = used + used_0; break; } - if (can_reuse_delta(base_ref, entry, &base_entry)) { + if (have_base && + can_reuse_delta(&base_ref, entry, &base_entry)) { oe_set_type(entry, entry->in_pack_type); SET_SIZE(entry, in_pack_size); /* delta size */ SET_DELTA_SIZE(entry, in_pack_size); @@ -1623,7 +1757,7 @@ static void check_object(struct object_entry *entry) entry->delta_sibling_idx = base_entry->delta_child_idx; SET_DELTA_CHILD(base_entry, entry); } else { - SET_DELTA_EXT(entry, base_ref); + SET_DELTA_EXT(entry, &base_ref); } unuse_pack(&w_curs); @@ -2552,6 +2686,13 @@ static void ll_find_deltas(struct object_entry **list, unsigned list_size, free(p); } +static int obj_is_packed(const struct object_id *oid) +{ + return packlist_find(&to_pack, oid) || + (reuse_packfile_bitmap && + bitmap_walk_contains(bitmap_git, reuse_packfile_bitmap, oid)); +} + static void add_tag_chain(const struct object_id *oid) { struct tag *tag; @@ -2563,7 +2704,7 @@ static void add_tag_chain(const struct object_id *oid) * it was included via bitmaps, we would not have parsed it * previously). */ - if (packlist_find(&to_pack, oid)) + if (obj_is_packed(oid)) return; tag = lookup_tag(the_repository, oid); @@ -2587,7 +2728,7 @@ static int add_ref_tag(const char *path, const struct object_id *oid, int flag, if (starts_with(path, "refs/tags/") && /* is a tag? */ !peel_ref(path, &peeled) && /* peelable? */ - packlist_find(&to_pack, &peeled)) /* object packed? */ + obj_is_packed(&peeled)) /* object packed? */ add_tag_chain(oid); return 0; } @@ -2655,6 +2796,7 @@ static void prepare_pack(int window, int depth) if (nr_deltas && n > 1) { unsigned nr_done = 0; + if (progress) progress_state = start_progress(_("Compressing objects"), nr_deltas); @@ -2699,6 +2841,10 @@ static int git_pack_config(const char *k, const char *v, void *cb) use_bitmap_index_default = git_config_bool(k, v); return 0; } + if (!strcmp(k, "pack.allowpackreuse")) { + allow_pack_reuse = git_config_bool(k, v); + return 0; + } if (!strcmp(k, "pack.threads")) { delta_search_threads = git_config_int(k, v); if (delta_search_threads < 0) @@ -2909,7 +3055,7 @@ static void add_objects_in_unpacked_packs(void) in_pack.alloc); for (i = 0; i < p->num_objects; i++) { - nth_packed_object_oid(&oid, p, i); + nth_packed_object_id(&oid, p, i); o = lookup_unknown_object(&oid); if (!(o->flags & OBJECT_ADDED)) mark_in_pack_object(o, p, &in_pack); @@ -3013,7 +3159,7 @@ static void loosen_unused_packed_objects(void) die(_("cannot open pack index")); for (i = 0; i < p->num_objects; i++) { - nth_packed_object_oid(&oid, p, i); + nth_packed_object_id(&oid, p, i); if (!packlist_find(&to_pack, &oid) && !has_sha1_pack_kept_or_nonlocal(&oid) && !loosened_object_can_be_discarded(&oid, p->mtime)) @@ -3030,8 +3176,8 @@ static void loosen_unused_packed_objects(void) */ static int pack_options_allow_reuse(void) { - return pack_to_stdout && - allow_ofs_delta && + return allow_pack_reuse && + pack_to_stdout && !ignore_packed_keep_on_disk && !ignore_packed_keep_in_core && (!local || !have_non_local_packs) && @@ -3040,7 +3186,7 @@ static int pack_options_allow_reuse(void) static int get_object_list_from_bitmap(struct rev_info *revs) { - if (!(bitmap_git = prepare_bitmap_walk(revs))) + if (!(bitmap_git = prepare_bitmap_walk(revs, &filter_options))) return -1; if (pack_options_allow_reuse() && @@ -3048,13 +3194,14 @@ static int get_object_list_from_bitmap(struct rev_info *revs) bitmap_git, &reuse_packfile, &reuse_packfile_objects, - &reuse_packfile_offset)) { + &reuse_packfile_bitmap)) { assert(reuse_packfile_objects); nr_result += reuse_packfile_objects; display_progress(progress_state, nr_result); } - traverse_bitmap_commit_list(bitmap_git, &add_object_entry_from_bitmap); + traverse_bitmap_commit_list(bitmap_git, revs, + &add_object_entry_from_bitmap); return 0; } @@ -3418,7 +3565,6 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (filter_options.choice) { if (!pack_to_stdout) die(_("cannot use --filter without --stdout")); - use_bitmap_index = 0; } /* @@ -3509,7 +3655,9 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) if (progress) fprintf_ln(stderr, _("Total %"PRIu32" (delta %"PRIu32")," - " reused %"PRIu32" (delta %"PRIu32")"), - written, written_delta, reused, reused_delta); + " reused %"PRIu32" (delta %"PRIu32")," + " pack-reused %"PRIu32), + written, written_delta, reused, reused_delta, + reuse_packfile_objects); return 0; } diff --git a/builtin/patch-id.c b/builtin/patch-id.c index 3059e525b8..822ffff51f 100644 --- a/builtin/patch-id.c +++ b/builtin/patch-id.c @@ -5,13 +5,8 @@ static void flush_current_id(int patchlen, struct object_id *id, struct object_id *result) { - char name[GIT_MAX_HEXSZ + 1]; - - if (!patchlen) - return; - - memcpy(name, oid_to_hex(id), the_hash_algo->hexsz + 1); - printf("%s %s\n", oid_to_hex(result), name); + if (patchlen) + printf("%s %s\n", oid_to_hex(result), oid_to_hex(id)); } static int remove_space(char *line) diff --git a/builtin/pull.c b/builtin/pull.c index d25ff13a60..3e624d1e00 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -15,6 +15,7 @@ #include "sha1-array.h" #include "remote.h" #include "dir.h" +#include "rebase.h" #include "refs.h" #include "refspec.h" #include "revision.h" @@ -26,15 +27,6 @@ #include "commit-reach.h" #include "sequencer.h" -enum rebase_type { - REBASE_INVALID = -1, - REBASE_FALSE = 0, - REBASE_TRUE, - REBASE_PRESERVE, - REBASE_MERGES, - REBASE_INTERACTIVE -}; - /** * Parses the value of --rebase. If value is a false value, returns * REBASE_FALSE. If value is a true value, returns REBASE_TRUE. If value is @@ -45,22 +37,9 @@ enum rebase_type { static enum rebase_type parse_config_rebase(const char *key, const char *value, int fatal) { - int v = git_parse_maybe_bool(value); - - if (!v) - return REBASE_FALSE; - else if (v > 0) - return REBASE_TRUE; - else if (!strcmp(value, "preserve") || !strcmp(value, "p")) - return REBASE_PRESERVE; - else if (!strcmp(value, "merges") || !strcmp(value, "m")) - return REBASE_MERGES; - else if (!strcmp(value, "interactive") || !strcmp(value, "i")) - return REBASE_INTERACTIVE; - /* - * Please update _git_config() in git-completion.bash when you - * add new rebase modes. - */ + enum rebase_type v = rebase_parse_value(value); + if (v != REBASE_INVALID) + return v; if (fatal) die(_("Invalid value for %s: %s"), key, value); @@ -107,6 +86,7 @@ static char *opt_ff; static char *opt_verify_signatures; static int opt_autostash = -1; static int config_autostash; +static int check_trust_level = 1; static struct argv_array opt_strategies = ARGV_ARRAY_INIT; static struct argv_array opt_strategy_opts = ARGV_ARRAY_INIT; static char *opt_gpg_sign; @@ -355,6 +335,8 @@ static enum rebase_type config_get_rebase(void) */ static int git_pull_config(const char *var, const char *value, void *cb) { + int status; + if (!strcmp(var, "rebase.autostash")) { config_autostash = git_config_bool(var, value); return 0; @@ -362,7 +344,14 @@ static int git_pull_config(const char *var, const char *value, void *cb) recurse_submodules = git_config_bool(var, value) ? RECURSE_SUBMODULES_ON : RECURSE_SUBMODULES_OFF; return 0; + } else if (!strcmp(var, "gpg.mintrustlevel")) { + check_trust_level = 0; } + + status = git_gpg_config(var, value, cb); + if (status) + return status; + return git_default_config(var, value, cb); } @@ -587,7 +576,8 @@ static int pull_into_void(const struct object_id *merge_head, die(_("unable to access commit %s"), oid_to_hex(merge_head)); - verify_merge_signature(commit, opt_verbosity); + verify_merge_signature(commit, opt_verbosity, + check_trust_level); } /* diff --git a/builtin/push.c b/builtin/push.c index 843f5b22a2..6dbf0f0bb7 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -64,6 +64,7 @@ static struct string_list push_options_config = STRING_LIST_INIT_DUP; static const char *map_refspec(const char *ref, struct remote *remote, struct ref *local_refs) { + const char *branch_name; struct ref *matched = NULL; /* Does "ref" uniquely name our ref? */ @@ -84,8 +85,8 @@ static const char *map_refspec(const char *ref, } if (push_default == PUSH_DEFAULT_UPSTREAM && - starts_with(matched->name, "refs/heads/")) { - struct branch *branch = branch_get(matched->name + 11); + skip_prefix(matched->name, "refs/heads/", &branch_name)) { + struct branch *branch = branch_get(branch_name); if (branch->merge_nr == 1 && branch->merge[0]->src) { struct strbuf buf = STRBUF_INIT; strbuf_addf(&buf, "%s:%s", diff --git a/builtin/range-diff.c b/builtin/range-diff.c index 9202e75544..d8a4670629 100644 --- a/builtin/range-diff.c +++ b/builtin/range-diff.c @@ -15,12 +15,16 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) { int creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT; struct diff_options diffopt = { NULL }; + struct argv_array other_arg = ARGV_ARRAY_INIT; int simple_color = -1; struct option range_diff_options[] = { OPT_INTEGER(0, "creation-factor", &creation_factor, N_("Percentage by which creation is weighted")), OPT_BOOL(0, "no-dual-color", &simple_color, N_("use simple diff colors")), + OPT_PASSTHRU_ARGV(0, "notes", &other_arg, + N_("notes"), N_("passed to 'git log'"), + PARSE_OPT_OPTARG), OPT_END() }; struct option *options; @@ -78,8 +82,9 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) FREE_AND_NULL(options); res = show_range_diff(range1.buf, range2.buf, creation_factor, - simple_color < 1, &diffopt); + simple_color < 1, &diffopt, &other_arg); + argv_array_clear(&other_arg); strbuf_release(&range1); strbuf_release(&range2); diff --git a/builtin/read-tree.c b/builtin/read-tree.c index ca5e655d2f..af7424b94c 100644 --- a/builtin/read-tree.c +++ b/builtin/read-tree.c @@ -185,7 +185,7 @@ int cmd_read_tree(int argc, const char **argv, const char *cmd_prefix) if (opts.reset || opts.merge || opts.prefix) { if (read_cache_unmerged() && (opts.prefix || opts.merge)) - die("You need to resolve your current index first"); + die(_("You need to resolve your current index first")); stage = opts.merge = 1; } resolve_undo_clear(); diff --git a/builtin/rebase.c b/builtin/rebase.c index 4a20582e72..27a07d4e78 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -44,14 +44,22 @@ static GIT_PATH_FUNC(merge_dir, "rebase-merge") enum rebase_type { REBASE_UNSPECIFIED = -1, - REBASE_AM, + REBASE_APPLY, REBASE_MERGE, - REBASE_INTERACTIVE, REBASE_PRESERVE_MERGES }; +enum empty_type { + EMPTY_UNSPECIFIED = -1, + EMPTY_DROP, + EMPTY_KEEP, + EMPTY_ASK +}; + struct rebase_options { enum rebase_type type; + enum empty_type empty; + const char *default_backend; const char *state_dir; struct commit *upstream; const char *upstream_name; @@ -77,7 +85,6 @@ struct rebase_options { const char *action; int signoff; int allow_rerere_autoupdate; - int keep_empty; int autosquash; char *gpg_sign_opt; int autostash; @@ -92,6 +99,8 @@ struct rebase_options { #define REBASE_OPTIONS_INIT { \ .type = REBASE_UNSPECIFIED, \ + .empty = EMPTY_UNSPECIFIED, \ + .default_backend = "merge", \ .flags = REBASE_NO_QUIET, \ .git_am_opts = ARGV_ARRAY_INIT, \ .git_format_patch_opt = STRBUF_INIT \ @@ -110,6 +119,9 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) replay.allow_rerere_auto = opts->allow_rerere_autoupdate; replay.allow_empty = 1; replay.allow_empty_message = opts->allow_empty_message; + replay.drop_redundant_commits = (opts->empty == EMPTY_DROP); + replay.keep_redundant_commits = (opts->empty == EMPTY_KEEP); + replay.quiet = !(opts->flags & REBASE_NO_QUIET); replay.verbose = opts->flags & REBASE_VERBOSE; replay.reschedule_failed_exec = opts->reschedule_failed_exec; replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt); @@ -117,6 +129,11 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) if (opts->strategy_opts) parse_strategy_opts(&replay, opts->strategy_opts); + if (opts->squash_onto) { + oidcpy(&replay.squash_onto, opts->squash_onto); + replay.have_squash_onto = 1; + } + return replay; } @@ -241,21 +258,17 @@ static int edit_todo_file(unsigned flags) } static int get_revision_ranges(struct commit *upstream, struct commit *onto, - const char **head_hash, + struct object_id *orig_head, const char **head_hash, char **revisions, char **shortrevisions) { struct commit *base_rev = upstream ? upstream : onto; const char *shorthead; - struct object_id orig_head; - if (get_oid("HEAD", &orig_head)) - return error(_("no HEAD?")); - - *head_hash = find_unique_abbrev(&orig_head, GIT_MAX_HEXSZ); + *head_hash = find_unique_abbrev(orig_head, GIT_MAX_HEXSZ); *revisions = xstrfmt("%s...%s", oid_to_hex(&base_rev->object.oid), *head_hash); - shorthead = find_unique_abbrev(&orig_head, DEFAULT_ABBREV); + shorthead = find_unique_abbrev(orig_head, DEFAULT_ABBREV); if (upstream) { const char *shortrev; @@ -309,12 +322,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) struct replay_opts replay = get_replay_opts(opts); struct string_list commands = STRING_LIST_INIT_DUP; - if (prepare_branch_to_be_rebased(the_repository, &replay, - opts->switch_to)) - return -1; - - if (get_revision_ranges(opts->upstream, opts->onto, &head_hash, - &revisions, &shortrevisions)) + if (get_revision_ranges(opts->upstream, opts->onto, &opts->orig_head, + &head_hash, &revisions, &shortrevisions)) return -1; if (init_basic_state(&replay, @@ -332,8 +341,8 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) argv_array_pushl(&make_script_args, "", revisions, NULL); if (opts->restrict_revision) - argv_array_push(&make_script_args, - oid_to_hex(&opts->restrict_revision->object.oid)); + argv_array_pushf(&make_script_args, "^%s", + oid_to_hex(&opts->restrict_revision->object.oid)); ret = sequencer_make_script(the_repository, &todo_list.buf, make_script_args.argc, make_script_args.argv, @@ -362,7 +371,7 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) return ret; } -static int run_rebase_interactive(struct rebase_options *opts, +static int run_sequencer_rebase(struct rebase_options *opts, enum action command) { unsigned flags = 0; @@ -370,7 +379,6 @@ static int run_rebase_interactive(struct rebase_options *opts, git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands); - flags |= opts->keep_empty ? TODO_LIST_KEEP_EMPTY : 0; flags |= abbreviate_commands ? TODO_LIST_ABBREVIATE_CMDS : 0; flags |= opts->rebase_merges ? TODO_LIST_REBASE_MERGES : 0; flags |= opts->rebase_cousins > 0 ? TODO_LIST_REBASE_COUSINS : 0; @@ -434,6 +442,21 @@ static int run_rebase_interactive(struct rebase_options *opts, return ret; } +static int parse_opt_keep_empty(const struct option *opt, const char *arg, + int unset) +{ + struct rebase_options *opts = opt->value; + + BUG_ON_OPT_ARG(arg); + + /* + * If we ever want to remap --keep-empty to --empty=keep, insert: + * opts->empty = unset ? EMPTY_UNSPECIFIED : EMPTY_KEEP; + */ + opts->type = REBASE_MERGE; + return 0; +} + static const char * const builtin_rebase_interactive_usage[] = { N_("git rebase--interactive [<options>]"), NULL @@ -447,9 +470,13 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) struct option options[] = { OPT_NEGBIT(0, "ff", &opts.flags, N_("allow fast-forward"), REBASE_FORCE), - OPT_BOOL(0, "keep-empty", &opts.keep_empty, N_("keep empty commits")), - OPT_BOOL(0, "allow-empty-message", &opts.allow_empty_message, - N_("allow commits with empty messages")), + { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL, + N_("(DEPRECATED) keep empty commits"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, + parse_opt_keep_empty }, + OPT_BOOL_F(0, "allow-empty-message", &opts.allow_empty_message, + N_("allow commits with empty messages"), + PARSE_OPT_HIDDEN), OPT_BOOL(0, "rebase-merges", &opts.rebase_merges, N_("rebase merge commits")), OPT_BOOL(0, "rebase-cousins", &opts.rebase_cousins, N_("keep original branch points of cousins")), @@ -519,28 +546,26 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) warning(_("--[no-]rebase-cousins has no effect without " "--rebase-merges")); - return !!run_rebase_interactive(&opts, command); + return !!run_sequencer_rebase(&opts, command); } -static int is_interactive(struct rebase_options *opts) +static int is_merge(struct rebase_options *opts) { - return opts->type == REBASE_INTERACTIVE || + return opts->type == REBASE_MERGE || opts->type == REBASE_PRESERVE_MERGES; } -static void imply_interactive(struct rebase_options *opts, const char *option) +static void imply_merge(struct rebase_options *opts, const char *option) { switch (opts->type) { - case REBASE_AM: + case REBASE_APPLY: die(_("%s requires an interactive rebase"), option); break; - case REBASE_INTERACTIVE: + case REBASE_MERGE: case REBASE_PRESERVE_MERGES: break; - case REBASE_MERGE: - /* we now implement --merge via --interactive */ default: - opts->type = REBASE_INTERACTIVE; /* implied */ + opts->type = REBASE_MERGE; /* implied */ break; } } @@ -666,8 +691,8 @@ static int rebase_write_basic_state(struct rebase_options *opts) opts->onto ? oid_to_hex(&opts->onto->object.oid) : ""); write_file(state_dir_path("orig-head", opts), "%s", oid_to_hex(&opts->orig_head)); - write_file(state_dir_path("quiet", opts), "%s", - opts->flags & REBASE_NO_QUIET ? "" : "t"); + if (!(opts->flags & REBASE_NO_QUIET)) + write_file(state_dir_path("quiet", opts), "%s", ""); if (opts->flags & REBASE_VERBOSE) write_file(state_dir_path("verbose", opts), "%s", ""); if (opts->strategy) @@ -685,7 +710,7 @@ static int rebase_write_basic_state(struct rebase_options *opts) write_file(state_dir_path("gpg_sign_opt", opts), "%s", opts->gpg_sign_opt); if (opts->signoff) - write_file(state_dir_path("strategy", opts), "--signoff"); + write_file(state_dir_path("signoff", opts), "--signoff"); return 0; } @@ -749,7 +774,7 @@ static int finish_rebase(struct rebase_options *opts) * user should see them. */ run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); - if (opts->type == REBASE_INTERACTIVE) { + if (opts->type == REBASE_MERGE) { struct replay_opts replay = REPLAY_OPTS_INIT; replay.action = REPLAY_INTERACTIVE_REBASE; @@ -843,6 +868,7 @@ static int reset_head(struct object_id *oid, const char *action, unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge; unpack_tree_opts.update = 1; unpack_tree_opts.merge = 1; + init_checkout_metadata(&unpack_tree_opts.meta, switch_to_branch, oid, NULL); if (!detach_head) unpack_tree_opts.reset = 1; @@ -1012,7 +1038,8 @@ static int run_am(struct rebase_options *opts) argv_array_pushl(&format_patch.args, "format-patch", "-k", "--stdout", "--full-index", "--cherry-pick", "--right-only", "--src-prefix=a/", "--dst-prefix=b/", "--no-renames", - "--no-cover-letter", "--pretty=mboxrd", "--topo-order", NULL); + "--no-cover-letter", "--pretty=mboxrd", "--topo-order", + "--no-base", NULL); if (opts->git_format_patch_opt.len) argv_array_split(&format_patch.args, opts->git_format_patch_opt.buf); @@ -1081,8 +1108,8 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action) int status; const char *backend, *backend_func; - if (opts->type == REBASE_INTERACTIVE) { - /* Run builtin interactive rebase */ + if (opts->type == REBASE_MERGE) { + /* Run sequencer-based rebase */ setenv("GIT_CHERRY_PICK_HELP", resolvemsg, 1); if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) { setenv("GIT_SEQUENCE_EDITOR", ":", 1); @@ -1095,11 +1122,11 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action) opts->gpg_sign_opt = tmp; } - status = run_rebase_interactive(opts, action); + status = run_sequencer_rebase(opts, action); goto finished_rebase; } - if (opts->type == REBASE_AM) { + if (opts->type == REBASE_APPLY) { status = run_am(opts); goto finished_rebase; } @@ -1119,8 +1146,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action) add_var(&script_snippet, "revisions", opts->revisions); add_var(&script_snippet, "restrict_revision", opts->restrict_revision ? oid_to_hex(&opts->restrict_revision->object.oid) : NULL); - add_var(&script_snippet, "GIT_QUIET", - opts->flags & REBASE_NO_QUIET ? "" : "t"); sq_quote_argv_pretty(&buf, opts->git_am_opts.argv); add_var(&script_snippet, "git_am_opt", buf.buf); strbuf_release(&buf); @@ -1138,7 +1163,6 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action) opts->allow_rerere_autoupdate ? opts->allow_rerere_autoupdate == RERERE_AUTOUPDATE ? "--rerere-autoupdate" : "--no-rerere-autoupdate" : ""); - add_var(&script_snippet, "keep_empty", opts->keep_empty ? "yes" : ""); add_var(&script_snippet, "autosquash", opts->autosquash ? "t" : ""); add_var(&script_snippet, "gpg_sign_opt", opts->gpg_sign_opt); add_var(&script_snippet, "cmd", opts->cmd); @@ -1156,7 +1180,7 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action) add_var(&script_snippet, "git_format_patch_opt", opts->git_format_patch_opt.buf); - if (is_interactive(opts) && + if (is_merge(opts) && !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) { strbuf_addstr(&script_snippet, "GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; "); @@ -1181,8 +1205,8 @@ static int run_specific_rebase(struct rebase_options *opts, enum action action) finished_rebase: if (opts->dont_finish_rebase) ; /* do nothing */ - else if (opts->type == REBASE_INTERACTIVE) - ; /* interactive rebase cleans up after itself */ + else if (opts->type == REBASE_MERGE) + ; /* merge backend cleans up after itself */ else if (status == 0) { if (!file_exists(state_dir_path("stopped-sha", opts))) finish_rebase(opts); @@ -1240,6 +1264,10 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "rebase.backend")) { + return git_config_string(&opts->default_backend, var, value); + } + return git_default_config(var, value, data); } @@ -1303,6 +1331,18 @@ done: return res && is_linear_history(onto, head); } +static int parse_opt_am(const struct option *opt, const char *arg, int unset) +{ + struct rebase_options *opts = opt->value; + + BUG_ON_OPT_NEG(unset); + BUG_ON_OPT_ARG(arg); + + opts->type = REBASE_APPLY; + + return 0; +} + /* -i followed by -m is still -i */ static int parse_opt_merge(const struct option *opt, const char *arg, int unset) { @@ -1311,7 +1351,7 @@ static int parse_opt_merge(const struct option *opt, const char *arg, int unset) BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(arg); - if (!is_interactive(opts)) + if (!is_merge(opts)) opts->type = REBASE_MERGE; return 0; @@ -1326,12 +1366,35 @@ static int parse_opt_interactive(const struct option *opt, const char *arg, BUG_ON_OPT_NEG(unset); BUG_ON_OPT_ARG(arg); - opts->type = REBASE_INTERACTIVE; + opts->type = REBASE_MERGE; opts->flags |= REBASE_INTERACTIVE_EXPLICIT; return 0; } +static enum empty_type parse_empty_value(const char *value) +{ + if (!strcasecmp(value, "drop")) + return EMPTY_DROP; + else if (!strcasecmp(value, "keep")) + return EMPTY_KEEP; + else if (!strcasecmp(value, "ask")) + return EMPTY_ASK; + + die(_("unrecognized empty type '%s'; valid values are \"drop\", \"keep\", and \"ask\"."), value); +} + +static int parse_opt_empty(const struct option *opt, const char *arg, int unset) +{ + struct rebase_options *options = opt->value; + enum empty_type value = parse_empty_value(arg); + + BUG_ON_OPT_NEG(unset); + + options->empty = value; + return 0; +} + static void NORETURN error_on_missing_default_upstream(void) { struct branch *current_branch = branch_get(NULL); @@ -1367,14 +1430,14 @@ static void set_reflog_action(struct rebase_options *options) const char *env; struct strbuf buf = STRBUF_INIT; - if (!is_interactive(options)) + if (!is_merge(options)) return; env = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); if (env && strcmp("rebase", env)) return; /* only override it if it is "rebase" */ - strbuf_addf(&buf, "rebase -i (%s)", options->action); + strbuf_addf(&buf, "rebase (%s)", options->action); setenv(GIT_REFLOG_ACTION_ENVIRONMENT, buf.buf, 1); strbuf_release(&buf); } @@ -1412,6 +1475,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct object_id squash_onto; char *squash_onto_name = NULL; int reschedule_failed_exec = -1; + int allow_preemptive_ff = 1; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -1422,7 +1486,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("allow pre-rebase hook to run")), OPT_NEGBIT('q', "quiet", &options.flags, N_("be quiet. implies --no-stat"), - REBASE_NO_QUIET| REBASE_VERBOSE | REBASE_DIFFSTAT), + REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT), OPT_BIT('v', "verbose", &options.flags, N_("display a diffstat of what changed upstream"), REBASE_NO_QUIET | REBASE_VERBOSE | REBASE_DIFFSTAT), @@ -1463,6 +1527,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_CMDMODE(0, "show-current-patch", &action, N_("show the patch file being applied or merged"), ACTION_SHOW_CURRENT_PATCH), + { OPTION_CALLBACK, 0, "apply", &options, NULL, + N_("use apply strategies to rebase"), + PARSE_OPT_NOARG | PARSE_OPT_NONEG, + parse_opt_am }, { OPTION_CALLBACK, 'm', "merge", &options, NULL, N_("use merging strategies to rebase"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, @@ -1471,12 +1539,18 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("let the user edit the list of commits to rebase"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, parse_opt_interactive }, - OPT_SET_INT('p', "preserve-merges", &options.type, - N_("(DEPRECATED) try to recreate merges instead of " - "ignoring them"), REBASE_PRESERVE_MERGES), + OPT_SET_INT_F('p', "preserve-merges", &options.type, + N_("(DEPRECATED) try to recreate merges instead of " + "ignoring them"), + REBASE_PRESERVE_MERGES, PARSE_OPT_HIDDEN), OPT_RERERE_AUTOUPDATE(&options.allow_rerere_autoupdate), - OPT_BOOL('k', "keep-empty", &options.keep_empty, - N_("preserve empty commits during rebase")), + OPT_CALLBACK_F(0, "empty", &options, "{drop,keep,ask}", + N_("how to handle commits that become empty"), + PARSE_OPT_NONEG, parse_opt_empty), + { OPTION_CALLBACK, 'k', "keep-empty", &options, NULL, + N_("(DEPRECATED) keep empty commits"), + PARSE_OPT_NOARG | PARSE_OPT_HIDDEN, + parse_opt_keep_empty }, OPT_BOOL(0, "autosquash", &options.autosquash, N_("move commits that begin with " "squash!/fixup! under -i")), @@ -1488,9 +1562,10 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING_LIST('x', "exec", &exec, N_("exec"), N_("add exec lines after each commit of the " "editable list")), - OPT_BOOL(0, "allow-empty-message", - &options.allow_empty_message, - N_("allow rebasing commits with empty messages")), + OPT_BOOL_F(0, "allow-empty-message", + &options.allow_empty_message, + N_("allow rebasing commits with empty messages"), + PARSE_OPT_HIDDEN), {OPTION_STRING, 'r', "rebase-merges", &rebase_merges, N_("mode"), N_("try to rebase merges instead of skipping them"), @@ -1530,7 +1605,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("It looks like 'git am' is in progress. Cannot rebase.")); if (is_directory(apply_dir())) { - options.type = REBASE_AM; + options.type = REBASE_APPLY; options.state_dir = apply_dir(); } else if (is_directory(merge_dir())) { strbuf_reset(&buf); @@ -1542,7 +1617,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_reset(&buf); strbuf_addf(&buf, "%s/interactive", merge_dir()); if(file_exists(buf.buf)) { - options.type = REBASE_INTERACTIVE; + options.type = REBASE_MERGE; options.flags |= REBASE_INTERACTIVE_EXPLICIT; } else options.type = REBASE_MERGE; @@ -1582,12 +1657,12 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("No rebase in progress?")); setenv(GIT_REFLOG_ACTION_ENVIRONMENT, "rebase", 0); - if (action == ACTION_EDIT_TODO && !is_interactive(&options)) + if (action == ACTION_EDIT_TODO && !is_merge(&options)) die(_("The --edit-todo action can only be used during " "interactive rebase.")); if (trace2_is_enabled()) { - if (is_interactive(&options)) + if (is_merge(&options)) trace2_cmd_mode("interactive"); else if (exec.nr) trace2_cmd_mode("interactive-exec"); @@ -1663,7 +1738,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) goto cleanup; } case ACTION_QUIT: { - if (options.type == REBASE_INTERACTIVE) { + if (options.type == REBASE_MERGE) { struct replay_opts replay = REPLAY_OPTS_INIT; replay.action = REPLAY_INTERACTIVE_REBASE; @@ -1712,13 +1787,20 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) state_dir_base, cmd_live_rebase, buf.buf); } + if ((options.flags & REBASE_INTERACTIVE_EXPLICIT) || + (action != ACTION_NONE) || + (exec.nr > 0) || + options.autosquash) { + allow_preemptive_ff = 0; + } + for (i = 0; i < options.git_am_opts.argc; i++) { const char *option = options.git_am_opts.argv[i], *p; if (!strcmp(option, "--committer-date-is-author-date") || !strcmp(option, "--ignore-date") || !strcmp(option, "--whitespace=fix") || !strcmp(option, "--whitespace=strip")) - options.flags |= REBASE_FORCE; + allow_preemptive_ff = 0; else if (skip_prefix(option, "-C", &p)) { while (*p) if (!isdigit(*(p++))) @@ -1738,8 +1820,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (!(options.flags & REBASE_NO_QUIET)) argv_array_push(&options.git_am_opts, "-q"); - if (options.keep_empty) - imply_interactive(&options, "--keep-empty"); + if (options.empty != EMPTY_UNSPECIFIED) + imply_merge(&options, "--empty"); if (gpg_sign) { free(options.gpg_sign_opt); @@ -1749,7 +1831,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (exec.nr) { int i; - imply_interactive(&options, "--exec"); + imply_merge(&options, "--exec"); strbuf_reset(&buf); for (i = 0; i < exec.nr; i++) @@ -1765,7 +1847,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) else if (strcmp("no-rebase-cousins", rebase_merges)) die(_("Unknown mode: %s"), rebase_merges); options.rebase_merges = 1; - imply_interactive(&options, "--rebase-merges"); + imply_merge(&options, "--rebase-merges"); } if (strategy_options.nr) { @@ -1784,10 +1866,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) if (options.strategy) { options.strategy = xstrdup(options.strategy); switch (options.type) { - case REBASE_AM: + case REBASE_APPLY: die(_("--strategy requires --merge or --interactive")); case REBASE_MERGE: - case REBASE_INTERACTIVE: case REBASE_PRESERVE_MERGES: /* compatible */ break; @@ -1800,47 +1881,65 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } if (options.type == REBASE_MERGE) - imply_interactive(&options, "--merge"); + imply_merge(&options, "--merge"); if (options.root && !options.onto_name) - imply_interactive(&options, "--root without --onto"); + imply_merge(&options, "--root without --onto"); if (isatty(2) && options.flags & REBASE_NO_QUIET) strbuf_addstr(&options.git_format_patch_opt, " --progress"); + if (options.git_am_opts.argc || options.type == REBASE_APPLY) { + /* all am options except -q are compatible only with --apply */ + for (i = options.git_am_opts.argc - 1; i >= 0; i--) + if (strcmp(options.git_am_opts.argv[i], "-q")) + break; + + if (i >= 0) { + if (is_merge(&options)) + die(_("cannot combine apply options with " + "merge options")); + else + options.type = REBASE_APPLY; + } + } + + if (options.type == REBASE_UNSPECIFIED) { + if (!strcmp(options.default_backend, "merge")) + imply_merge(&options, "--merge"); + else if (!strcmp(options.default_backend, "apply")) + options.type = REBASE_APPLY; + else + die(_("Unknown rebase backend: %s"), + options.default_backend); + } + switch (options.type) { case REBASE_MERGE: - case REBASE_INTERACTIVE: case REBASE_PRESERVE_MERGES: options.state_dir = merge_dir(); break; - case REBASE_AM: + case REBASE_APPLY: options.state_dir = apply_dir(); break; default: - /* the default rebase backend is `--am` */ - options.type = REBASE_AM; - options.state_dir = apply_dir(); - break; + BUG("options.type was just set above; should be unreachable."); } - if (reschedule_failed_exec > 0 && !is_interactive(&options)) + if (options.empty == EMPTY_UNSPECIFIED) { + if (options.flags & REBASE_INTERACTIVE_EXPLICIT) + options.empty = EMPTY_ASK; + else if (exec.nr > 0) + options.empty = EMPTY_KEEP; + else + options.empty = EMPTY_DROP; + } + if (reschedule_failed_exec > 0 && !is_merge(&options)) die(_("--reschedule-failed-exec requires " "--exec or --interactive")); if (reschedule_failed_exec >= 0) options.reschedule_failed_exec = reschedule_failed_exec; - if (options.git_am_opts.argc) { - /* all am options except -q are compatible only with --am */ - for (i = options.git_am_opts.argc - 1; i >= 0; i--) - if (strcmp(options.git_am_opts.argv[i], "-q")) - break; - - if (is_interactive(&options) && i >= 0) - die(_("cannot combine am options with either " - "interactive or merge options")); - } - if (options.signoff) { if (options.type == REBASE_PRESERVE_MERGES) die("cannot combine '--signoff' with " @@ -1946,10 +2045,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* Is it a local branch? */ strbuf_reset(&buf); strbuf_addf(&buf, "refs/heads/%s", branch_name); - if (!read_ref(buf.buf, &options.orig_head)) + if (!read_ref(buf.buf, &options.orig_head)) { + die_if_checked_out(buf.buf, 1); options.head_name = xstrdup(buf.buf); /* If not is it a valid ref (branch or commit)? */ - else if (!get_oid(branch_name, &options.orig_head)) + } else if (!get_oid(branch_name, &options.orig_head)) options.head_name = NULL; else die(_("fatal: no such branch/commit '%s'"), @@ -2046,30 +2146,24 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) /* * Check if we are already based on onto with linear history, * in which case we could fast-forward without replacing the commits - * with new commits recreated by replaying their changes. This - * optimization must not be done if this is an interactive rebase. + * with new commits recreated by replaying their changes. + * + * Note that can_fast_forward() initializes merge_base, so we have to + * call it before checking allow_preemptive_ff. */ if (can_fast_forward(options.onto, options.upstream, options.restrict_revision, &options.orig_head, &merge_base) && - !is_interactive(&options)) { + allow_preemptive_ff) { int flag; if (!(options.flags & REBASE_FORCE)) { /* Lazily switch to the target branch if needed... */ if (options.switch_to) { - struct object_id oid; - - if (get_oid(options.switch_to, &oid) < 0) { - ret = !!error(_("could not parse '%s'"), - options.switch_to); - goto cleanup; - } - strbuf_reset(&buf); strbuf_addf(&buf, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.switch_to); - if (reset_head(&oid, "checkout", + if (reset_head(&options.orig_head, "checkout", options.head_name, RESET_HEAD_RUN_POST_CHECKOUT_HOOK, NULL, buf.buf) < 0) { @@ -2134,7 +2228,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) diff_flush(&opts); } - if (is_interactive(&options)) + if (is_merge(&options)) goto run_rebase; /* Detach HEAD and reset the tree */ diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 411e0b4d99..2cc18bbffd 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -27,6 +27,7 @@ #include "object-store.h" #include "protocol.h" #include "commit-reach.h" +#include "worktree.h" static const char * const receive_pack_usage[] = { N_("git receive-pack <git-dir>"), @@ -816,16 +817,6 @@ static int run_update_hook(struct command *cmd) return finish_command(&proc); } -static int is_ref_checked_out(const char *ref) -{ - if (is_bare_repository()) - return 0; - - if (!head_name) - return 0; - return !strcmp(head_name, ref); -} - static char *refuse_unconfigured_deny_msg = N_("By default, updating the current branch in a non-bare repository\n" "is denied, because it will make the index and work tree inconsistent\n" @@ -997,16 +988,26 @@ static const char *push_to_checkout(unsigned char *hash, return NULL; } -static const char *update_worktree(unsigned char *sha1) +static const char *update_worktree(unsigned char *sha1, const struct worktree *worktree) { - const char *retval; - const char *work_tree = git_work_tree_cfg ? git_work_tree_cfg : ".."; + const char *retval, *work_tree, *git_dir = NULL; struct argv_array env = ARGV_ARRAY_INIT; + if (worktree && worktree->path) + work_tree = worktree->path; + else if (git_work_tree_cfg) + work_tree = git_work_tree_cfg; + else + work_tree = ".."; + if (is_bare_repository()) return "denyCurrentBranch = updateInstead needs a worktree"; + if (worktree) + git_dir = get_worktree_git_dir(worktree); + if (!git_dir) + git_dir = get_git_dir(); - argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(get_git_dir())); + argv_array_pushf(&env, "GIT_DIR=%s", absolute_path(git_dir)); if (!find_hook(push_to_checkout_hook)) retval = push_to_deploy(sha1, &env, work_tree); @@ -1026,6 +1027,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) struct object_id *old_oid = &cmd->old_oid; struct object_id *new_oid = &cmd->new_oid; int do_update_worktree = 0; + const struct worktree *worktree = is_bare_repository() ? NULL : find_shared_symref("HEAD", name); /* only refs/... are allowed */ if (!starts_with(name, "refs/") || check_refname_format(name + 5, 0)) { @@ -1037,7 +1039,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) free(namespaced_name); namespaced_name = strbuf_detach(&namespaced_name_buf, NULL); - if (is_ref_checked_out(namespaced_name)) { + if (worktree) { switch (deny_current_branch) { case DENY_IGNORE: break; @@ -1069,7 +1071,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) return "deletion prohibited"; } - if (head_name && !strcmp(namespaced_name, head_name)) { + if (worktree || (head_name && !strcmp(namespaced_name, head_name))) { switch (deny_delete_current) { case DENY_IGNORE: break; @@ -1118,7 +1120,7 @@ static const char *update(struct command *cmd, struct shallow_info *si) } if (do_update_worktree) { - ret = update_worktree(new_oid->hash); + ret = update_worktree(new_oid->hash, find_shared_symref("HEAD", name)); if (ret) return ret; } diff --git a/builtin/reflog.c b/builtin/reflog.c index 4d3430900d..81dfd563c0 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -560,15 +560,16 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) for (i = 1; i < argc; i++) { const char *arg = argv[i]; + if (!strcmp(arg, "--dry-run") || !strcmp(arg, "-n")) flags |= EXPIRE_REFLOGS_DRY_RUN; - else if (starts_with(arg, "--expire=")) { - if (parse_expiry_date(arg + 9, &cb.cmd.expire_total)) + else if (skip_prefix(arg, "--expire=", &arg)) { + if (parse_expiry_date(arg, &cb.cmd.expire_total)) die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_TOTAL; } - else if (starts_with(arg, "--expire-unreachable=")) { - if (parse_expiry_date(arg + 21, &cb.cmd.expire_unreachable)) + else if (skip_prefix(arg, "--expire-unreachable=", &arg)) { + if (parse_expiry_date(arg, &cb.cmd.expire_unreachable)) die(_("'%s' is not a valid timestamp"), arg); explicit_expiry |= EXPIRE_UNREACH; } diff --git a/builtin/remote.c b/builtin/remote.c index 5591cef775..555d4c896c 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -6,6 +6,7 @@ #include "string-list.h" #include "strbuf.h" #include "run-command.h" +#include "rebase.h" #include "refs.h" #include "refspec.h" #include "object-store.h" @@ -248,9 +249,8 @@ static int add(int argc, const char **argv) struct branch_info { char *remote_name; struct string_list merge; - enum { - NO_REBASE, NORMAL_REBASE, INTERACTIVE_REBASE, REBASE_MERGES - } rebase; + enum rebase_type rebase; + char *push_remote_name; }; static struct string_list branch_list = STRING_LIST_INIT_NODUP; @@ -264,59 +264,69 @@ static const char *abbrev_ref(const char *name, const char *prefix) static int config_read_branches(const char *key, const char *value, void *cb) { - if (starts_with(key, "branch.")) { - const char *orig_key = key; - char *name; - struct string_list_item *item; - struct branch_info *info; - enum { REMOTE, MERGE, REBASE } type; - size_t key_len; - - key += 7; - if (strip_suffix(key, ".remote", &key_len)) { - name = xmemdupz(key, key_len); - type = REMOTE; - } else if (strip_suffix(key, ".merge", &key_len)) { - name = xmemdupz(key, key_len); - type = MERGE; - } else if (strip_suffix(key, ".rebase", &key_len)) { - name = xmemdupz(key, key_len); - type = REBASE; - } else - return 0; + const char *orig_key = key; + char *name; + struct string_list_item *item; + struct branch_info *info; + enum { REMOTE, MERGE, REBASE, PUSH_REMOTE } type; + size_t key_len; - item = string_list_insert(&branch_list, name); + if (!starts_with(key, "branch.")) + return 0; - if (!item->util) - item->util = xcalloc(1, sizeof(struct branch_info)); - info = item->util; - if (type == REMOTE) { - if (info->remote_name) - warning(_("more than one %s"), orig_key); - info->remote_name = xstrdup(value); - } else if (type == MERGE) { - char *space = strchr(value, ' '); - value = abbrev_branch(value); - while (space) { - char *merge; - merge = xstrndup(value, space - value); - string_list_append(&info->merge, merge); - value = abbrev_branch(space + 1); - space = strchr(value, ' '); - } - string_list_append(&info->merge, xstrdup(value)); - } else { - int v = git_parse_maybe_bool(value); - if (v >= 0) - info->rebase = v; - else if (!strcmp(value, "preserve")) - info->rebase = NORMAL_REBASE; - else if (!strcmp(value, "merges")) - info->rebase = REBASE_MERGES; - else if (!strcmp(value, "interactive")) - info->rebase = INTERACTIVE_REBASE; + key += strlen("branch."); + if (strip_suffix(key, ".remote", &key_len)) + type = REMOTE; + else if (strip_suffix(key, ".merge", &key_len)) + type = MERGE; + else if (strip_suffix(key, ".rebase", &key_len)) + type = REBASE; + else if (strip_suffix(key, ".pushremote", &key_len)) + type = PUSH_REMOTE; + else + return 0; + name = xmemdupz(key, key_len); + + item = string_list_insert(&branch_list, name); + + if (!item->util) + item->util = xcalloc(1, sizeof(struct branch_info)); + info = item->util; + switch (type) { + case REMOTE: + if (info->remote_name) + warning(_("more than one %s"), orig_key); + info->remote_name = xstrdup(value); + break; + case MERGE: { + char *space = strchr(value, ' '); + value = abbrev_branch(value); + while (space) { + char *merge; + merge = xstrndup(value, space - value); + string_list_append(&info->merge, merge); + value = abbrev_branch(space + 1); + space = strchr(value, ' '); } + string_list_append(&info->merge, xstrdup(value)); + break; + } + case REBASE: + /* + * Consider invalid values as false and check the + * truth value with >= REBASE_TRUE. + */ + info->rebase = rebase_parse_value(value); + break; + case PUSH_REMOTE: + if (info->push_remote_name) + warning(_("more than one %s"), orig_key); + info->push_remote_name = xstrdup(value); + break; + default: + BUG("unexpected type=%d", type); } + return 0; } @@ -605,6 +615,56 @@ static int migrate_file(struct remote *remote) return 0; } +struct push_default_info +{ + const char *old_name; + enum config_scope scope; + struct strbuf origin; + int linenr; +}; + +static int config_read_push_default(const char *key, const char *value, + void *cb) +{ + struct push_default_info* info = cb; + if (strcmp(key, "remote.pushdefault") || + !value || strcmp(value, info->old_name)) + return 0; + + info->scope = current_config_scope(); + strbuf_reset(&info->origin); + strbuf_addstr(&info->origin, current_config_name()); + info->linenr = current_config_line(); + + return 0; +} + +static void handle_push_default(const char* old_name, const char* new_name) +{ + struct push_default_info push_default = { + old_name, CONFIG_SCOPE_UNKNOWN, STRBUF_INIT, -1 }; + git_config(config_read_push_default, &push_default); + if (push_default.scope >= CONFIG_SCOPE_COMMAND) + ; /* pass */ + else if (push_default.scope >= CONFIG_SCOPE_LOCAL) { + int result = git_config_set_gently("remote.pushDefault", + new_name); + if (new_name && result && result != CONFIG_NOTHING_SET) + die(_("could not set '%s'"), "remote.pushDefault"); + else if (!new_name && result && result != CONFIG_NOTHING_SET) + die(_("could not unset '%s'"), "remote.pushDefault"); + } else if (push_default.scope >= CONFIG_SCOPE_SYSTEM) { + /* warn */ + warning(_("The %s configuration remote.pushDefault in:\n" + "\t%s:%d\n" + "now names the non-existent remote '%s'"), + config_scope_name(push_default.scope), + push_default.origin.buf, push_default.linenr, + old_name); + } +} + + static int mv(int argc, const char **argv) { struct option options[] = { @@ -680,6 +740,11 @@ static int mv(int argc, const char **argv) strbuf_addf(&buf, "branch.%s.remote", item->string); git_config_set(buf.buf, rename.new_name); } + if (info->push_remote_name && !strcmp(info->push_remote_name, rename.old_name)) { + strbuf_reset(&buf); + strbuf_addf(&buf, "branch.%s.pushremote", item->string); + git_config_set(buf.buf, rename.new_name); + } } if (!refspec_updated) @@ -693,9 +758,8 @@ static int mv(int argc, const char **argv) for (i = 0; i < remote_branches.nr; i++) { struct string_list_item *item = remote_branches.items + i; int flag = 0; - struct object_id oid; - read_ref_full(item->string, RESOLVE_REF_READING, &oid, &flag); + read_ref_full(item->string, RESOLVE_REF_READING, NULL, &flag); if (!(flag & REF_ISSYMREF)) continue; if (delete_ref(NULL, item->string, NULL, REF_NO_DEREF)) @@ -736,6 +800,9 @@ static int mv(int argc, const char **argv) die(_("creating '%s' failed"), buf.buf); } string_list_clear(&remote_branches, 1); + + handle_push_default(rename.old_name, rename.new_name); + return 0; } @@ -782,6 +849,13 @@ static int rm(int argc, const char **argv) die(_("could not unset '%s'"), buf.buf); } } + if (info->push_remote_name && !strcmp(info->push_remote_name, remote->name)) { + strbuf_reset(&buf); + strbuf_addf(&buf, "branch.%s.pushremote", item->string); + result = git_config_set_gently(buf.buf, NULL); + if (result && result != CONFIG_NOTHING_SET) + die(_("could not unset '%s'"), buf.buf); + } } /* @@ -814,6 +888,8 @@ static int rm(int argc, const char **argv) strbuf_addf(&buf, "remote.%s", remote->name); if (git_config_rename_section(buf.buf, NULL) < 1) return error(_("Could not remove config section '%s'"), buf.buf); + + handle_push_default(remote->name, NULL); } return result; @@ -944,7 +1020,7 @@ static int add_local_to_show_info(struct string_list_item *branch_item, void *cb return 0; if ((n = strlen(branch_item->string)) > show_info->width) show_info->width = n; - if (branch_info->rebase) + if (branch_info->rebase >= REBASE_TRUE) show_info->any_rebase = 1; item = string_list_insert(show_info->list, branch_item->string); @@ -961,16 +1037,16 @@ static int show_local_info_item(struct string_list_item *item, void *cb_data) int width = show_info->width + 4; int i; - if (branch_info->rebase && branch_info->merge.nr > 1) { + if (branch_info->rebase >= REBASE_TRUE && branch_info->merge.nr > 1) { error(_("invalid branch.%s.merge; cannot rebase onto > 1 branch"), item->string); return 0; } printf(" %-*s ", show_info->width, item->string); - if (branch_info->rebase) { + if (branch_info->rebase >= REBASE_TRUE) { const char *msg; - if (branch_info->rebase == INTERACTIVE_REBASE) + if (branch_info->rebase == REBASE_INTERACTIVE) msg = _("rebases interactively onto remote %s"); else if (branch_info->rebase == REBASE_MERGES) msg = _("rebases interactively (with merges) onto " diff --git a/builtin/repack.c b/builtin/repack.c index 094c2f8ea4..0781763b06 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -233,6 +233,13 @@ static void repack_promisor_objects(const struct pack_objects_args *args, /* * pack-objects creates the .pack and .idx files, but not the * .promisor file. Create the .promisor file, which is empty. + * + * NEEDSWORK: fetch-pack sometimes generates non-empty + * .promisor files containing the ref names and associated + * hashes at the point of generation of the corresponding + * packfile, but this would not preserve their contents. Maybe + * concatenate the contents of all .promisor files instead of + * just creating a new empty file. */ promisor_name = mkpathdup("%s-%s.promisor", packtmp, line.buf); @@ -562,7 +569,7 @@ int cmd_repack(int argc, const char **argv, const char *prefix) remove_temporary_files(); if (git_env_bool(GIT_TEST_MULTI_PACK_INDEX, 0)) - write_midx_file(get_object_directory()); + write_midx_file(get_object_directory(), 0); string_list_clear(&names, 0); string_list_clear(&rollback, 0); diff --git a/builtin/replace.c b/builtin/replace.c index bd92dc63b9..b36d17a657 100644 --- a/builtin/replace.c +++ b/builtin/replace.c @@ -409,7 +409,8 @@ static int check_one_mergetag(struct commit *commit, struct tag *tag; int i; - hash_object_file(extra->value, extra->len, type_name(OBJ_TAG), &tag_oid); + hash_object_file(the_hash_algo, extra->value, extra->len, + type_name(OBJ_TAG), &tag_oid); tag = lookup_tag(the_repository, &tag_oid); if (!tag) return error(_("bad mergetag in commit '%s'"), ref); diff --git a/builtin/reset.c b/builtin/reset.c index fdd572168b..4c634111bd 100644 --- a/builtin/reset.c +++ b/builtin/reset.c @@ -30,8 +30,9 @@ static const char * const git_reset_usage[] = { N_("git reset [--mixed | --soft | --hard | --merge | --keep] [-q] [<commit>]"), - N_("git reset [-q] [<tree-ish>] [--] <paths>..."), - N_("git reset --patch [<tree-ish>] [--] [<paths>...]"), + N_("git reset [-q] [<tree-ish>] [--] <pathspec>..."), + N_("git reset [-q] [--pathspec-from-file [--pathspec-file-nul]] [<tree-ish>]"), + N_("git reset --patch [<tree-ish>] [--] [<pathspec>...]"), NULL }; @@ -45,7 +46,7 @@ static inline int is_merge(void) return !access(git_path_merge_head(the_repository), F_OK); } -static int reset_index(const struct object_id *oid, int reset_type, int quiet) +static int reset_index(const char *ref, const struct object_id *oid, int reset_type, int quiet) { int i, nr = 0; struct tree_desc desc[2]; @@ -59,6 +60,7 @@ static int reset_index(const struct object_id *oid, int reset_type, int quiet) opts.dst_index = &the_index; opts.fn = oneway_merge; opts.merge = 1; + init_checkout_metadata(&opts.meta, ref, oid, NULL); if (!quiet) opts.verbose_update = 1; switch (reset_type) { @@ -284,8 +286,8 @@ static int git_reset_config(const char *var, const char *value, void *cb) int cmd_reset(int argc, const char **argv, const char *prefix) { int reset_type = NONE, update_ref_status = 0, quiet = 0; - int patch_mode = 0, unborn; - const char *rev; + int patch_mode = 0, pathspec_file_nul = 0, unborn; + const char *rev, *pathspec_from_file = NULL; struct object_id oid; struct pathspec pathspec; int intent_to_add = 0; @@ -306,6 +308,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix) OPT_BOOL('p', "patch", &patch_mode, N_("select hunks interactively")), OPT_BOOL('N', "intent-to-add", &intent_to_add, N_("record only the fact that removed paths will be added later")), + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END() }; @@ -316,11 +320,25 @@ int cmd_reset(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_DASHDASH); parse_args(&pathspec, argv, prefix, patch_mode, &rev); + if (pathspec_from_file) { + if (patch_mode) + die(_("--pathspec-from-file is incompatible with --patch")); + + if (pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&pathspec, 0, + PATHSPEC_PREFER_FULL, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + unborn = !strcmp(rev, "HEAD") && get_oid("HEAD", &oid); if (unborn) { /* reset on unborn branch: treat as reset to empty tree */ oidcpy(&oid, the_hash_algo->empty_tree); - } else if (!pathspec.nr) { + } else if (!pathspec.nr && !patch_mode) { struct commit *commit; if (get_oid_committish(rev, &oid)) die(_("Failed to resolve '%s' as a valid revision."), rev); @@ -401,11 +419,20 @@ int cmd_reset(int argc, const char **argv, const char *prefix) } } } else { - int err = reset_index(&oid, reset_type, quiet); + struct object_id dummy; + char *ref = NULL; + int err; + + dwim_ref(rev, strlen(rev), &dummy, &ref); + if (ref && !starts_with(ref, "refs/")) + ref = NULL; + + err = reset_index(ref, &oid, reset_type, quiet); if (reset_type == KEEP && !err) - err = reset_index(&oid, MIXED, quiet); + err = reset_index(ref, &oid, MIXED, quiet); if (err) die(_("Could not reset index file to revision '%s'."), rev); + free(ref); } if (write_locked_index(&the_index, &lock, COMMIT_LOCK)) diff --git a/builtin/rev-list.c b/builtin/rev-list.c index e28d62ec64..f520111eda 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -253,11 +253,26 @@ static int finish_object(struct object *obj, const char *name, void *cb_data) static void show_object(struct object *obj, const char *name, void *cb_data) { struct rev_list_info *info = cb_data; + struct rev_info *revs = info->revs; + if (finish_object(obj, name, cb_data)) return; display_progress(progress, ++progress_counter); if (info->flags & REV_LIST_QUIET) return; + + if (revs->count) { + /* + * The object count is always accumulated in the .count_right + * field for traversal that is not a left-right traversal, + * and cmd_rev_list() made sure that a .count request that + * wants to count non-commit objects, which is handled by + * the show_object() callback, does not ask for .left_right. + */ + revs->count_right++; + return; + } + if (arg_show_object_names) show_object_with_name(stdout, obj, name); else @@ -364,6 +379,79 @@ static inline int parse_missing_action_value(const char *value) return 0; } +static int try_bitmap_count(struct rev_info *revs, + struct list_objects_filter_options *filter) +{ + uint32_t commit_count = 0, + tag_count = 0, + tree_count = 0, + blob_count = 0; + int max_count; + struct bitmap_index *bitmap_git; + + /* This function only handles counting, not general traversal. */ + if (!revs->count) + return -1; + + /* + * A bitmap result can't know left/right, etc, because we don't + * actually traverse. + */ + if (revs->left_right || revs->cherry_mark) + return -1; + + /* + * If we're counting reachable objects, we can't handle a max count of + * commits to traverse, since we don't know which objects go with which + * commit. + */ + if (revs->max_count >= 0 && + (revs->tag_objects || revs->tree_objects || revs->blob_objects)) + return -1; + + /* + * This must be saved before doing any walking, since the revision + * machinery will count it down to zero while traversing. + */ + max_count = revs->max_count; + + bitmap_git = prepare_bitmap_walk(revs, filter); + if (!bitmap_git) + return -1; + + count_bitmap_commit_list(bitmap_git, &commit_count, + revs->tree_objects ? &tree_count : NULL, + revs->blob_objects ? &blob_count : NULL, + revs->tag_objects ? &tag_count : NULL); + if (max_count >= 0 && max_count < commit_count) + commit_count = max_count; + + printf("%d\n", commit_count + tree_count + blob_count + tag_count); + free_bitmap_index(bitmap_git); + return 0; +} + +static int try_bitmap_traversal(struct rev_info *revs, + struct list_objects_filter_options *filter) +{ + struct bitmap_index *bitmap_git; + + /* + * We can't use a bitmap result with a traversal limit, since the set + * of commits we'd get would be essentially random. + */ + if (revs->max_count >= 0) + return -1; + + bitmap_git = prepare_bitmap_walk(revs, filter); + if (!bitmap_git) + return -1; + + traverse_bitmap_commit_list(bitmap_git, revs, &show_object_fast); + free_bitmap_index(bitmap_git); + return 0; +} + int cmd_rev_list(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -521,8 +609,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (revs.show_notes) die(_("rev-list does not support display of notes")); - if (filter_options.choice && use_bitmap_index) - die(_("cannot combine --use-bitmap-index with object filtering")); + if (revs.count && + (revs.tag_objects || revs.tree_objects || revs.blob_objects) && + (revs.left_right || revs.cherry_mark)) + die(_("marked counting is incompatible with --objects")); save_commit_buffer = (revs.verbose_header || revs.grep_filter.pattern_list || @@ -533,28 +623,11 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (show_progress) progress = start_delayed_progress(show_progress, 0); - if (use_bitmap_index && !revs.prune) { - if (revs.count && !revs.left_right && !revs.cherry_mark) { - uint32_t commit_count; - int max_count = revs.max_count; - struct bitmap_index *bitmap_git; - if ((bitmap_git = prepare_bitmap_walk(&revs))) { - count_bitmap_commit_list(bitmap_git, &commit_count, NULL, NULL, NULL); - if (max_count >= 0 && max_count < commit_count) - commit_count = max_count; - printf("%d\n", commit_count); - free_bitmap_index(bitmap_git); - return 0; - } - } else if (revs.max_count < 0 && - revs.tag_objects && revs.tree_objects && revs.blob_objects) { - struct bitmap_index *bitmap_git; - if ((bitmap_git = prepare_bitmap_walk(&revs))) { - traverse_bitmap_commit_list(bitmap_git, &show_object_fast); - free_bitmap_index(bitmap_git); - return 0; - } - } + if (use_bitmap_index) { + if (!try_bitmap_count(&revs, &filter_options)) + return 0; + if (!try_bitmap_traversal(&revs, &filter_options)) + return 0; } if (prepare_revision_walk(&revs)) diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 308c67e4fc..06056434ed 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -803,12 +803,15 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) const char *work_tree = get_git_work_tree(); if (work_tree) puts(work_tree); + else + die("this operation must be run in a work tree"); continue; } if (!strcmp(arg, "--show-superproject-working-tree")) { - const char *superproject = get_superproject_working_tree(); - if (superproject) - puts(superproject); + struct strbuf superproject = STRBUF_INIT; + if (get_superproject_working_tree(&superproject)) + puts(superproject.buf); + strbuf_release(&superproject); continue; } if (!strcmp(arg, "--show-prefix")) { @@ -855,7 +858,10 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (!gitdir && !prefix) gitdir = ".git"; if (gitdir) { - puts(real_path(gitdir)); + struct strbuf realpath = STRBUF_INIT; + strbuf_realpath(&realpath, gitdir, 1); + puts(realpath.buf); + strbuf_release(&realpath); continue; } } @@ -919,6 +925,17 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) show_datestring("--min-age=", arg); continue; } + if (opt_with_value(arg, "--show-object-format", &arg)) { + const char *val = arg ? arg : "storage"; + + if (strcmp(val, "storage") && + strcmp(val, "input") && + strcmp(val, "output")) + die("unknown mode for --show-object-format: %s", + arg); + puts(the_hash_algo->name); + continue; + } if (show_flag(arg) && verify) die_no_single_rev(quiet); continue; diff --git a/builtin/rm.c b/builtin/rm.c index 19ce95a901..4858631e0f 100644 --- a/builtin/rm.c +++ b/builtin/rm.c @@ -235,7 +235,8 @@ static int check_local_mod(struct object_id *head, int index_only) } static int show_only = 0, force = 0, index_only = 0, recursive = 0, quiet = 0; -static int ignore_unmatch = 0; +static int ignore_unmatch = 0, pathspec_file_nul; +static char *pathspec_from_file; static struct option builtin_rm_options[] = { OPT__DRY_RUN(&show_only, N_("dry run")), @@ -245,6 +246,8 @@ static struct option builtin_rm_options[] = { OPT_BOOL('r', NULL, &recursive, N_("allow recursive removal")), OPT_BOOL( 0 , "ignore-unmatch", &ignore_unmatch, N_("exit with a zero status even if nothing matched")), + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END(), }; @@ -259,8 +262,24 @@ int cmd_rm(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, builtin_rm_options, builtin_rm_usage, 0); - if (!argc) - usage_with_options(builtin_rm_usage, builtin_rm_options); + + parse_pathspec(&pathspec, 0, + PATHSPEC_PREFER_CWD, + prefix, argv); + + if (pathspec_from_file) { + if (pathspec.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&pathspec, 0, + PATHSPEC_PREFER_CWD, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + + if (!pathspec.nr) + die(_("No pathspec was given. Which files should I remove?")); if (!index_only) setup_work_tree(); @@ -270,9 +289,6 @@ int cmd_rm(int argc, const char **argv, const char *prefix) if (read_cache() < 0) die(_("index file corrupt")); - parse_pathspec(&pathspec, 0, - PATHSPEC_PREFER_CWD, - prefix, argv); refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &pathspec, NULL, NULL); seen = xcalloc(pathspec.nr, 1); diff --git a/builtin/show-branch.c b/builtin/show-branch.c index 35d7f51c23..8c90cbb18f 100644 --- a/builtin/show-branch.c +++ b/builtin/show-branch.c @@ -536,7 +536,7 @@ static void append_one_rev(const char *av) append_ref(av, &revkey, 0); return; } - if (strchr(av, '*') || strchr(av, '?') || strchr(av, '[')) { + if (strpbrk(av, "*?[")) { /* glob style match */ int saved_matches = ref_name_cnt; diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c new file mode 100644 index 0000000000..740da4b6d5 --- /dev/null +++ b/builtin/sparse-checkout.c @@ -0,0 +1,631 @@ +#include "builtin.h" +#include "config.h" +#include "dir.h" +#include "parse-options.h" +#include "pathspec.h" +#include "repository.h" +#include "run-command.h" +#include "strbuf.h" +#include "string-list.h" +#include "cache.h" +#include "cache-tree.h" +#include "lockfile.h" +#include "resolve-undo.h" +#include "unpack-trees.h" +#include "wt-status.h" +#include "quote.h" + +static const char *empty_base = ""; + +static char const * const builtin_sparse_checkout_usage[] = { + N_("git sparse-checkout (init|list|set|add|disable) <options>"), + NULL +}; + +static char *get_sparse_checkout_filename(void) +{ + return git_pathdup("info/sparse-checkout"); +} + +static void write_patterns_to_file(FILE *fp, struct pattern_list *pl) +{ + int i; + + for (i = 0; i < pl->nr; i++) { + struct path_pattern *p = pl->patterns[i]; + + if (p->flags & PATTERN_FLAG_NEGATIVE) + fprintf(fp, "!"); + + fprintf(fp, "%s", p->pattern); + + if (p->flags & PATTERN_FLAG_MUSTBEDIR) + fprintf(fp, "/"); + + fprintf(fp, "\n"); + } +} + +static int sparse_checkout_list(int argc, const char **argv) +{ + struct pattern_list pl; + char *sparse_filename; + int res; + + memset(&pl, 0, sizeof(pl)); + + pl.use_cone_patterns = core_sparse_checkout_cone; + + sparse_filename = get_sparse_checkout_filename(); + res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL); + free(sparse_filename); + + if (res < 0) { + warning(_("this worktree is not sparse (sparse-checkout file may not exist)")); + return 0; + } + + if (pl.use_cone_patterns) { + int i; + struct pattern_entry *pe; + struct hashmap_iter iter; + struct string_list sl = STRING_LIST_INIT_DUP; + + hashmap_for_each_entry(&pl.recursive_hashmap, &iter, pe, ent) { + /* pe->pattern starts with "/", skip it */ + string_list_insert(&sl, pe->pattern + 1); + } + + string_list_sort(&sl); + + for (i = 0; i < sl.nr; i++) { + quote_c_style(sl.items[i].string, NULL, stdout, 0); + printf("\n"); + } + + return 0; + } + + write_patterns_to_file(stdout, &pl); + clear_pattern_list(&pl); + + return 0; +} + +static int update_working_directory(struct pattern_list *pl) +{ + int result = 0; + struct unpack_trees_options o; + struct lock_file lock_file = LOCK_INIT; + struct object_id oid; + struct tree *tree; + struct tree_desc t; + struct repository *r = the_repository; + + if (repo_read_index_unmerged(r)) + die(_("you need to resolve your current index first")); + + if (get_oid("HEAD", &oid)) + return 0; + + tree = parse_tree_indirect(&oid); + parse_tree(tree); + init_tree_desc(&t, tree->buffer, tree->size); + + memset(&o, 0, sizeof(o)); + o.verbose_update = isatty(2); + o.merge = 1; + o.update = 1; + o.fn = oneway_merge; + o.head_idx = -1; + o.src_index = r->index; + o.dst_index = r->index; + o.skip_sparse_checkout = 0; + o.pl = pl; + o.keep_pattern_list = !!pl; + + resolve_undo_clear_index(r->index); + setup_work_tree(); + + cache_tree_free(&r->index->cache_tree); + + repo_hold_locked_index(r, &lock_file, LOCK_DIE_ON_ERROR); + + core_apply_sparse_checkout = 1; + result = unpack_trees(1, &t, &o); + + if (!result) { + prime_cache_tree(r, r->index, tree); + write_locked_index(r->index, &lock_file, COMMIT_LOCK); + } else + rollback_lock_file(&lock_file); + + return result; +} + +static char *escaped_pattern(char *pattern) +{ + char *p = pattern; + struct strbuf final = STRBUF_INIT; + + while (*p) { + if (is_glob_special(*p)) + strbuf_addch(&final, '\\'); + + strbuf_addch(&final, *p); + p++; + } + + return strbuf_detach(&final, NULL); +} + +static void write_cone_to_file(FILE *fp, struct pattern_list *pl) +{ + int i; + struct pattern_entry *pe; + struct hashmap_iter iter; + struct string_list sl = STRING_LIST_INIT_DUP; + struct strbuf parent_pattern = STRBUF_INIT; + + hashmap_for_each_entry(&pl->parent_hashmap, &iter, pe, ent) { + if (hashmap_get_entry(&pl->recursive_hashmap, pe, ent, NULL)) + continue; + + if (!hashmap_contains_parent(&pl->recursive_hashmap, + pe->pattern, + &parent_pattern)) + string_list_insert(&sl, pe->pattern); + } + + string_list_sort(&sl); + string_list_remove_duplicates(&sl, 0); + + fprintf(fp, "/*\n!/*/\n"); + + for (i = 0; i < sl.nr; i++) { + char *pattern = escaped_pattern(sl.items[i].string); + + if (strlen(pattern)) + fprintf(fp, "%s/\n!%s/*/\n", pattern, pattern); + free(pattern); + } + + string_list_clear(&sl, 0); + + hashmap_for_each_entry(&pl->recursive_hashmap, &iter, pe, ent) { + if (!hashmap_contains_parent(&pl->recursive_hashmap, + pe->pattern, + &parent_pattern)) + string_list_insert(&sl, pe->pattern); + } + + strbuf_release(&parent_pattern); + + string_list_sort(&sl); + string_list_remove_duplicates(&sl, 0); + + for (i = 0; i < sl.nr; i++) { + char *pattern = escaped_pattern(sl.items[i].string); + fprintf(fp, "%s/\n", pattern); + free(pattern); + } +} + +static int write_patterns_and_update(struct pattern_list *pl) +{ + char *sparse_filename; + FILE *fp; + int fd; + struct lock_file lk = LOCK_INIT; + int result; + + sparse_filename = get_sparse_checkout_filename(); + + if (safe_create_leading_directories(sparse_filename)) + die(_("failed to create directory for sparse-checkout file")); + + fd = hold_lock_file_for_update(&lk, sparse_filename, + LOCK_DIE_ON_ERROR); + + result = update_working_directory(pl); + if (result) { + rollback_lock_file(&lk); + free(sparse_filename); + clear_pattern_list(pl); + update_working_directory(NULL); + return result; + } + + fp = xfdopen(fd, "w"); + + if (core_sparse_checkout_cone) + write_cone_to_file(fp, pl); + else + write_patterns_to_file(fp, pl); + + fflush(fp); + commit_lock_file(&lk); + + free(sparse_filename); + clear_pattern_list(pl); + + return 0; +} + +enum sparse_checkout_mode { + MODE_NO_PATTERNS = 0, + MODE_ALL_PATTERNS = 1, + MODE_CONE_PATTERNS = 2, +}; + +static int set_config(enum sparse_checkout_mode mode) +{ + const char *config_path; + + if (git_config_set_gently("extensions.worktreeConfig", "true")) { + error(_("failed to set extensions.worktreeConfig setting")); + return 1; + } + + config_path = git_path("config.worktree"); + git_config_set_in_file_gently(config_path, + "core.sparseCheckout", + mode ? "true" : NULL); + + git_config_set_in_file_gently(config_path, + "core.sparseCheckoutCone", + mode == MODE_CONE_PATTERNS ? "true" : NULL); + + return 0; +} + +static char const * const builtin_sparse_checkout_init_usage[] = { + N_("git sparse-checkout init [--cone]"), + NULL +}; + +static struct sparse_checkout_init_opts { + int cone_mode; +} init_opts; + +static int sparse_checkout_init(int argc, const char **argv) +{ + struct pattern_list pl; + char *sparse_filename; + int res; + struct object_id oid; + int mode; + struct strbuf pattern = STRBUF_INIT; + + static struct option builtin_sparse_checkout_init_options[] = { + OPT_BOOL(0, "cone", &init_opts.cone_mode, + N_("initialize the sparse-checkout in cone mode")), + OPT_END(), + }; + + repo_read_index(the_repository); + require_clean_work_tree(the_repository, + N_("initialize sparse-checkout"), NULL, 1, 0); + + argc = parse_options(argc, argv, NULL, + builtin_sparse_checkout_init_options, + builtin_sparse_checkout_init_usage, 0); + + if (init_opts.cone_mode) { + mode = MODE_CONE_PATTERNS; + core_sparse_checkout_cone = 1; + } else + mode = MODE_ALL_PATTERNS; + + if (set_config(mode)) + return 1; + + memset(&pl, 0, sizeof(pl)); + + sparse_filename = get_sparse_checkout_filename(); + res = add_patterns_from_file_to_list(sparse_filename, "", 0, &pl, NULL); + + /* If we already have a sparse-checkout file, use it. */ + if (res >= 0) { + free(sparse_filename); + core_apply_sparse_checkout = 1; + return update_working_directory(NULL); + } + + if (get_oid("HEAD", &oid)) { + FILE *fp; + + /* assume we are in a fresh repo, but update the sparse-checkout file */ + fp = xfopen(sparse_filename, "w"); + if (!fp) + die(_("failed to open '%s'"), sparse_filename); + + free(sparse_filename); + fprintf(fp, "/*\n!/*/\n"); + fclose(fp); + return 0; + } + + strbuf_addstr(&pattern, "/*"); + add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0); + strbuf_addstr(&pattern, "!/*/"); + add_pattern(strbuf_detach(&pattern, NULL), empty_base, 0, &pl, 0); + + return write_patterns_and_update(&pl); +} + +static void insert_recursive_pattern(struct pattern_list *pl, struct strbuf *path) +{ + struct pattern_entry *e = xmalloc(sizeof(*e)); + e->patternlen = path->len; + e->pattern = strbuf_detach(path, NULL); + hashmap_entry_init(&e->ent, + ignore_case ? + strihash(e->pattern) : + strhash(e->pattern)); + + hashmap_add(&pl->recursive_hashmap, &e->ent); + + while (e->patternlen) { + char *slash = strrchr(e->pattern, '/'); + char *oldpattern = e->pattern; + size_t newlen; + + if (slash == e->pattern) + break; + + newlen = slash - e->pattern; + e = xmalloc(sizeof(struct pattern_entry)); + e->patternlen = newlen; + e->pattern = xstrndup(oldpattern, newlen); + hashmap_entry_init(&e->ent, + ignore_case ? + strihash(e->pattern) : + strhash(e->pattern)); + + if (!hashmap_get_entry(&pl->parent_hashmap, e, ent, NULL)) + hashmap_add(&pl->parent_hashmap, &e->ent); + } +} + +static void strbuf_to_cone_pattern(struct strbuf *line, struct pattern_list *pl) +{ + strbuf_trim(line); + + strbuf_trim_trailing_dir_sep(line); + + if (strbuf_normalize_path(line)) + die(_("could not normalize path %s"), line->buf); + + if (!line->len) + return; + + if (line->buf[0] != '/') + strbuf_insertstr(line, 0, "/"); + + insert_recursive_pattern(pl, line); +} + +static char const * const builtin_sparse_checkout_set_usage[] = { + N_("git sparse-checkout (set|add) (--stdin | <patterns>)"), + NULL +}; + +static struct sparse_checkout_set_opts { + int use_stdin; +} set_opts; + +static void add_patterns_from_input(struct pattern_list *pl, + int argc, const char **argv) +{ + int i; + if (core_sparse_checkout_cone) { + struct strbuf line = STRBUF_INIT; + + hashmap_init(&pl->recursive_hashmap, pl_hashmap_cmp, NULL, 0); + hashmap_init(&pl->parent_hashmap, pl_hashmap_cmp, NULL, 0); + pl->use_cone_patterns = 1; + + if (set_opts.use_stdin) { + struct strbuf unquoted = STRBUF_INIT; + while (!strbuf_getline(&line, stdin)) { + if (line.buf[0] == '"') { + strbuf_reset(&unquoted); + if (unquote_c_style(&unquoted, line.buf, NULL)) + die(_("unable to unquote C-style string '%s'"), + line.buf); + + strbuf_swap(&unquoted, &line); + } + + strbuf_to_cone_pattern(&line, pl); + } + + strbuf_release(&unquoted); + } else { + for (i = 0; i < argc; i++) { + strbuf_setlen(&line, 0); + strbuf_addstr(&line, argv[i]); + strbuf_to_cone_pattern(&line, pl); + } + } + } else { + if (set_opts.use_stdin) { + struct strbuf line = STRBUF_INIT; + + while (!strbuf_getline(&line, stdin)) { + size_t len; + char *buf = strbuf_detach(&line, &len); + add_pattern(buf, empty_base, 0, pl, 0); + } + } else { + for (i = 0; i < argc; i++) + add_pattern(argv[i], empty_base, 0, pl, 0); + } + } +} + +enum modify_type { + REPLACE, + ADD, +}; + +static void add_patterns_cone_mode(int argc, const char **argv, + struct pattern_list *pl) +{ + struct strbuf buffer = STRBUF_INIT; + struct pattern_entry *pe; + struct hashmap_iter iter; + struct pattern_list existing; + char *sparse_filename = get_sparse_checkout_filename(); + + add_patterns_from_input(pl, argc, argv); + + memset(&existing, 0, sizeof(existing)); + existing.use_cone_patterns = core_sparse_checkout_cone; + + if (add_patterns_from_file_to_list(sparse_filename, "", 0, + &existing, NULL)) + die(_("unable to load existing sparse-checkout patterns")); + free(sparse_filename); + + hashmap_for_each_entry(&existing.recursive_hashmap, &iter, pe, ent) { + if (!hashmap_contains_parent(&pl->recursive_hashmap, + pe->pattern, &buffer) || + !hashmap_contains_parent(&pl->parent_hashmap, + pe->pattern, &buffer)) { + strbuf_reset(&buffer); + strbuf_addstr(&buffer, pe->pattern); + insert_recursive_pattern(pl, &buffer); + } + } + + clear_pattern_list(&existing); + strbuf_release(&buffer); +} + +static void add_patterns_literal(int argc, const char **argv, + struct pattern_list *pl) +{ + char *sparse_filename = get_sparse_checkout_filename(); + if (add_patterns_from_file_to_list(sparse_filename, "", 0, + pl, NULL)) + die(_("unable to load existing sparse-checkout patterns")); + free(sparse_filename); + add_patterns_from_input(pl, argc, argv); +} + +static int modify_pattern_list(int argc, const char **argv, enum modify_type m) +{ + int result; + int changed_config = 0; + struct pattern_list pl; + memset(&pl, 0, sizeof(pl)); + + switch (m) { + case ADD: + if (core_sparse_checkout_cone) + add_patterns_cone_mode(argc, argv, &pl); + else + add_patterns_literal(argc, argv, &pl); + break; + + case REPLACE: + add_patterns_from_input(&pl, argc, argv); + break; + } + + if (!core_apply_sparse_checkout) { + set_config(MODE_ALL_PATTERNS); + core_apply_sparse_checkout = 1; + changed_config = 1; + } + + result = write_patterns_and_update(&pl); + + if (result && changed_config) + set_config(MODE_NO_PATTERNS); + + clear_pattern_list(&pl); + return result; +} + +static int sparse_checkout_set(int argc, const char **argv, const char *prefix, + enum modify_type m) +{ + static struct option builtin_sparse_checkout_set_options[] = { + OPT_BOOL(0, "stdin", &set_opts.use_stdin, + N_("read patterns from standard in")), + OPT_END(), + }; + + repo_read_index(the_repository); + require_clean_work_tree(the_repository, + N_("set sparse-checkout patterns"), NULL, 1, 0); + + argc = parse_options(argc, argv, prefix, + builtin_sparse_checkout_set_options, + builtin_sparse_checkout_set_usage, + PARSE_OPT_KEEP_UNKNOWN); + + return modify_pattern_list(argc, argv, m); +} + +static int sparse_checkout_disable(int argc, const char **argv) +{ + struct pattern_list pl; + struct strbuf match_all = STRBUF_INIT; + + repo_read_index(the_repository); + require_clean_work_tree(the_repository, + N_("disable sparse-checkout"), NULL, 1, 0); + + memset(&pl, 0, sizeof(pl)); + hashmap_init(&pl.recursive_hashmap, pl_hashmap_cmp, NULL, 0); + hashmap_init(&pl.parent_hashmap, pl_hashmap_cmp, NULL, 0); + pl.use_cone_patterns = 0; + core_apply_sparse_checkout = 1; + + strbuf_addstr(&match_all, "/*"); + add_pattern(strbuf_detach(&match_all, NULL), empty_base, 0, &pl, 0); + + if (update_working_directory(&pl)) + die(_("error while refreshing working directory")); + + clear_pattern_list(&pl); + return set_config(MODE_NO_PATTERNS); +} + +int cmd_sparse_checkout(int argc, const char **argv, const char *prefix) +{ + static struct option builtin_sparse_checkout_options[] = { + OPT_END(), + }; + + if (argc == 2 && !strcmp(argv[1], "-h")) + usage_with_options(builtin_sparse_checkout_usage, + builtin_sparse_checkout_options); + + argc = parse_options(argc, argv, prefix, + builtin_sparse_checkout_options, + builtin_sparse_checkout_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + git_config(git_default_config, NULL); + + if (argc > 0) { + if (!strcmp(argv[0], "list")) + return sparse_checkout_list(argc, argv); + if (!strcmp(argv[0], "init")) + return sparse_checkout_init(argc, argv); + if (!strcmp(argv[0], "set")) + return sparse_checkout_set(argc, argv, prefix, REPLACE); + if (!strcmp(argv[0], "add")) + return sparse_checkout_set(argc, argv, prefix, ADD); + if (!strcmp(argv[0], "disable")) + return sparse_checkout_disable(argc, argv); + } + + usage_with_options(builtin_sparse_checkout_usage, + builtin_sparse_checkout_options); +} diff --git a/builtin/stash.c b/builtin/stash.c index 4e806176b0..78af6ce564 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -27,6 +27,7 @@ static const char * const git_stash_usage[] = { N_("git stash clear"), N_("git stash [push [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" " [-u|--include-untracked] [-a|--all] [-m|--message <message>]\n" + " [--pathspec-from-file=<file> [--pathspec-file-nul]]\n" " [--] [<pathspec>...]]"), N_("git stash save [-p|--patch] [-k|--[no-]keep-index] [-q|--quiet]\n" " [-u|--include-untracked] [-a|--all] [<message>]"), @@ -483,13 +484,12 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, if (ret) return -1; + /* read back the result of update_index() back from the disk */ discard_cache(); + read_cache(); } - if (quiet) { - if (refresh_and_write_cache(REFRESH_QUIET, 0, 0)) - warning("could not refresh index"); - } else { + if (!quiet) { struct child_process cp = CHILD_PROCESS_INIT; /* @@ -999,9 +999,9 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, { int ret = 0; struct child_process cp_read_tree = CHILD_PROCESS_INIT; - struct child_process cp_add_i = CHILD_PROCESS_INIT; struct child_process cp_diff_tree = CHILD_PROCESS_INIT; struct index_state istate = { NULL }; + char *old_index_env = NULL, *old_repo_index_file; remove_path(stash_index_path.buf); @@ -1015,16 +1015,19 @@ static int stash_patch(struct stash_info *info, const struct pathspec *ps, } /* Find out what the user wants. */ - cp_add_i.git_cmd = 1; - argv_array_pushl(&cp_add_i.args, "add--interactive", "--patch=stash", - "--", NULL); - add_pathspecs(&cp_add_i.args, ps); - argv_array_pushf(&cp_add_i.env_array, "GIT_INDEX_FILE=%s", - stash_index_path.buf); - if (run_command(&cp_add_i)) { - ret = -1; - goto done; - } + old_repo_index_file = the_repository->index_file; + the_repository->index_file = stash_index_path.buf; + old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT)); + setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1); + + ret = run_add_interactive(NULL, "--patch=stash", ps); + + the_repository->index_file = old_repo_index_file; + if (old_index_env && *old_index_env) + setenv(INDEX_ENVIRONMENT, old_index_env, 1); + else + unsetenv(INDEX_ENVIRONMENT); + FREE_AND_NULL(old_index_env); /* State of the working tree. */ if (write_index_as_tree(&info->w_tree, &istate, stash_index_path.buf, 0, @@ -1088,8 +1091,9 @@ static int stash_working_tree(struct stash_info *info, const struct pathspec *ps } cp_upd_index.git_cmd = 1; - argv_array_pushl(&cp_upd_index.args, "update-index", "-z", "--add", - "--remove", "--stdin", NULL); + argv_array_pushl(&cp_upd_index.args, "update-index", + "--ignore-skip-worktree-entries", + "-z", "--add", "--remove", "--stdin", NULL); argv_array_pushf(&cp_upd_index.env_array, "GIT_INDEX_FILE=%s", stash_index_path.buf); @@ -1448,13 +1452,17 @@ done: return ret; } -static int push_stash(int argc, const char **argv, const char *prefix) +static int push_stash(int argc, const char **argv, const char *prefix, + int push_assumed) { + int force_assume = 0; int keep_index = -1; int patch_mode = 0; int include_untracked = 0; int quiet = 0; + int pathspec_file_nul = 0; const char *stash_msg = NULL; + const char *pathspec_from_file = NULL; struct pathspec ps; struct option options[] = { OPT_BOOL('k', "keep-index", &keep_index, @@ -1468,16 +1476,45 @@ static int push_stash(int argc, const char **argv, const char *prefix) N_("include ignore files"), 2), OPT_STRING('m', "message", &stash_msg, N_("message"), N_("stash message")), + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), OPT_END() }; - if (argc) + if (argc) { + force_assume = !strcmp(argv[0], "-p"); argc = parse_options(argc, argv, prefix, options, git_stash_push_usage, - 0); + PARSE_OPT_KEEP_DASHDASH); + } + + if (argc) { + if (!strcmp(argv[0], "--")) { + argc--; + argv++; + } else if (push_assumed && !force_assume) { + die("subcommand wasn't specified; 'push' can't be assumed due to unexpected token '%s'", + argv[0]); + } + } parse_pathspec(&ps, 0, PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN, prefix, argv); + + if (pathspec_from_file) { + if (patch_mode) + die(_("--pathspec-from-file is incompatible with --patch")); + + if (ps.nr) + die(_("--pathspec-from-file is incompatible with pathspec arguments")); + + parse_pathspec_file(&ps, 0, + PATHSPEC_PREFER_FULL | PATHSPEC_PREFIX_ORIGIN, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("--pathspec-file-nul requires --pathspec-from-file")); + } + return do_push_stash(&ps, stash_msg, quiet, keep_index, patch_mode, include_untracked); } @@ -1547,7 +1584,6 @@ static int use_builtin_stash(void) int cmd_stash(int argc, const char **argv, const char *prefix) { - int i = -1; pid_t pid = getpid(); const char *index_file; struct argv_array args = ARGV_ARRAY_INIT; @@ -1580,7 +1616,7 @@ int cmd_stash(int argc, const char **argv, const char *prefix) (uintmax_t)pid); if (!argc) - return !!push_stash(0, NULL, prefix); + return !!push_stash(0, NULL, prefix, 0); else if (!strcmp(argv[0], "apply")) return !!apply_stash(argc, argv, prefix); else if (!strcmp(argv[0], "clear")) @@ -1600,45 +1636,15 @@ int cmd_stash(int argc, const char **argv, const char *prefix) else if (!strcmp(argv[0], "create")) return !!create_stash(argc, argv, prefix); else if (!strcmp(argv[0], "push")) - return !!push_stash(argc, argv, prefix); + return !!push_stash(argc, argv, prefix, 0); else if (!strcmp(argv[0], "save")) return !!save_stash(argc, argv, prefix); else if (*argv[0] != '-') usage_msg_opt(xstrfmt(_("unknown subcommand: %s"), argv[0]), git_stash_usage, options); - if (strcmp(argv[0], "-p")) { - while (++i < argc && strcmp(argv[i], "--")) { - /* - * `akpqu` is a string which contains all short options, - * except `-m` which is verified separately. - */ - if ((strlen(argv[i]) == 2) && *argv[i] == '-' && - strchr("akpqu", argv[i][1])) - continue; - - if (!strcmp(argv[i], "--all") || - !strcmp(argv[i], "--keep-index") || - !strcmp(argv[i], "--no-keep-index") || - !strcmp(argv[i], "--patch") || - !strcmp(argv[i], "--quiet") || - !strcmp(argv[i], "--include-untracked")) - continue; - - /* - * `-m` and `--message=` are verified separately because - * they need to be immediately followed by a string - * (i.e.`-m"foobar"` or `--message="foobar"`). - */ - if (starts_with(argv[i], "-m") || - starts_with(argv[i], "--message=")) - continue; - - usage_with_options(git_stash_usage, options); - } - } - + /* Assume 'stash push' */ argv_array_push(&args, "push"); argv_array_pushv(&args, argv); - return !!push_stash(args.argc, args.argv, prefix); + return !!push_stash(args.argc, args.argv, prefix, 1); } diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 2c2395a620..1a4b391c88 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -19,6 +19,8 @@ #include "diffcore.h" #include "diff.h" #include "object-store.h" +#include "dir.h" +#include "advice.h" #define OPT_QUIET (1 << 0) #define OPT_CACHED (1 << 1) @@ -442,19 +444,19 @@ static void for_each_listed_submodule(const struct module_list *list, fn(list->entries[i], cb_data); } -struct cb_foreach { +struct foreach_cb { int argc; const char **argv; const char *prefix; int quiet; int recursive; }; -#define CB_FOREACH_INIT { 0 } +#define FOREACH_CB_INIT { 0 } static void runcommand_in_submodule_cb(const struct cache_entry *list_item, void *cb_data) { - struct cb_foreach *info = cb_data; + struct foreach_cb *info = cb_data; const char *path = list_item->name; const struct object_id *ce_oid = &list_item->oid; @@ -555,7 +557,7 @@ cleanup: static int module_foreach(int argc, const char **argv, const char *prefix) { - struct cb_foreach info = CB_FOREACH_INIT; + struct foreach_cb info = FOREACH_CB_INIT; struct pathspec pathspec; struct module_list list = MODULE_LIST_INIT; @@ -780,6 +782,8 @@ static void status_submodule(const char *path, const struct object_id *ce_oid, struct argv_array diff_files_args = ARGV_ARRAY_INIT; struct rev_info rev; int diff_files_result; + struct strbuf buf = STRBUF_INIT; + const char *git_dir; if (!submodule_from_path(the_repository, &null_oid, path)) die(_("no submodule mapping found in .gitmodules for path '%s'"), @@ -792,17 +796,26 @@ static void status_submodule(const char *path, const struct object_id *ce_oid, goto cleanup; } - if (!is_submodule_active(the_repository, path)) { + strbuf_addf(&buf, "%s/.git", path); + git_dir = read_gitfile(buf.buf); + if (!git_dir) + git_dir = buf.buf; + + if (!is_submodule_active(the_repository, path) || + !is_git_directory(git_dir)) { print_status(flags, '-', path, ce_oid, displaypath); + strbuf_release(&buf); goto cleanup; } + strbuf_release(&buf); argv_array_pushl(&diff_files_args, "diff-files", "--ignore-submodules=dirty", "--quiet", "--", path, NULL); git_config(git_diff_basic_config, NULL); - repo_init_revisions(the_repository, &rev, prefix); + + repo_init_revisions(the_repository, &rev, NULL); rev.abbrev = 0; diff_files_args.argc = setup_revisions(diff_files_args.argc, diff_files_args.argv, @@ -1222,7 +1235,7 @@ static int module_deinit(int argc, const char **argv, const char *prefix) static int clone_submodule(const char *path, const char *gitdir, const char *url, const char *depth, struct string_list *reference, int dissociate, - int quiet, int progress) + int quiet, int progress, int single_branch) { struct child_process cp = CHILD_PROCESS_INIT; @@ -1244,6 +1257,10 @@ static int clone_submodule(const char *path, const char *gitdir, const char *url argv_array_push(&cp.args, "--dissociate"); if (gitdir && *gitdir) argv_array_pushl(&cp.args, "--separate-git-dir", gitdir, NULL); + if (single_branch >= 0) + argv_array_push(&cp.args, single_branch ? + "--single-branch" : + "--no-single-branch"); argv_array_push(&cp.args, "--"); argv_array_push(&cp.args, url); @@ -1268,6 +1285,13 @@ struct submodule_alternate_setup { #define SUBMODULE_ALTERNATE_SETUP_INIT { NULL, \ SUBMODULE_ALTERNATE_ERROR_IGNORE, NULL } +static const char alternate_error_advice[] = N_( +"An alternate computed from a superproject's alternate is invalid.\n" +"To allow Git to clone without an alternate in such a case, set\n" +"submodule.alternateErrorStrategy to 'info' or, equivalently, clone with\n" +"'--reference-if-able' instead of '--reference'." +); + static int add_possible_reference_from_superproject( struct object_directory *odb, void *sas_cb) { @@ -1299,6 +1323,8 @@ static int add_possible_reference_from_superproject( } else { switch (sas->error_mode) { case SUBMODULE_ALTERNATE_ERROR_DIE: + if (advice_submodule_alternate_error_strategy_die) + advise(_(alternate_error_advice)); die(_("submodule '%s' cannot add alternate: %s"), sas->submodule_name, err.buf); case SUBMODULE_ALTERNATE_ERROR_INFO: @@ -1359,8 +1385,9 @@ static int module_clone(int argc, const char **argv, const char *prefix) char *p, *path = NULL, *sm_gitdir; struct strbuf sb = STRBUF_INIT; struct string_list reference = STRING_LIST_INIT_NODUP; - int dissociate = 0; + int dissociate = 0, require_init = 0; char *sm_alternate = NULL, *error_strategy = NULL; + int single_branch = -1; struct option module_clone_options[] = { OPT_STRING(0, "prefix", &prefix, @@ -1386,12 +1413,17 @@ static int module_clone(int argc, const char **argv, const char *prefix) OPT__QUIET(&quiet, "Suppress output for cloning a submodule"), OPT_BOOL(0, "progress", &progress, N_("force cloning progress")), + OPT_BOOL(0, "require-init", &require_init, + N_("disallow cloning into non-empty directory")), + OPT_BOOL(0, "single-branch", &single_branch, + N_("clone only one branch, HEAD or --branch")), OPT_END() }; const char *const git_submodule_helper_usage[] = { N_("git submodule--helper clone [--prefix=<path>] [--quiet] " "[--reference <repository>] [--name <name>] [--depth <depth>] " + "[--single-branch] " "--url <url> --path <path>"), NULL }; @@ -1413,6 +1445,10 @@ static int module_clone(int argc, const char **argv, const char *prefix) } else path = xstrdup(path); + if (validate_submodule_git_dir(sm_gitdir, name) < 0) + die(_("refusing to create/use '%s' in another submodule's " + "git dir"), sm_gitdir); + if (!file_exists(sm_gitdir)) { if (safe_create_leading_directories_const(sm_gitdir) < 0) die(_("could not create directory '%s'"), sm_gitdir); @@ -1420,10 +1456,12 @@ static int module_clone(int argc, const char **argv, const char *prefix) prepare_possible_alternates(name, &reference); if (clone_submodule(path, sm_gitdir, url, depth, &reference, dissociate, - quiet, progress)) + quiet, progress, single_branch)) die(_("clone of '%s' into submodule path '%s' failed"), url, path); } else { + if (require_init && !access(path, X_OK) && !is_empty_dir(path)) + die(_("directory not empty: '%s'"), path); if (safe_create_leading_directories_const(path) < 0) die(_("could not create directory '%s'"), path); strbuf_addf(&sb, "%s/index", sm_gitdir); @@ -1478,6 +1516,8 @@ static void determine_submodule_update_strategy(struct repository *r, die(_("Invalid update mode '%s' configured for submodule path '%s'"), val, path); } else if (sub->update_strategy.type != SM_UPDATE_UNSPECIFIED) { + if (sub->update_strategy.type == SM_UPDATE_COMMAND) + BUG("how did we read update = !command from .gitmodules?"); out->type = sub->update_strategy.type; out->command = sub->update_strategy.command; } else @@ -1536,9 +1576,11 @@ struct submodule_update_clone { int recommend_shallow; struct string_list references; int dissociate; + unsigned require_init; const char *depth; const char *recursive_prefix; const char *prefix; + int single_branch; /* to be consumed by git-submodule.sh */ struct update_clone_data *update_clone; @@ -1553,10 +1595,14 @@ struct submodule_update_clone { int max_jobs; }; -#define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \ - SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \ - NULL, NULL, NULL, \ - NULL, 0, 0, 0, NULL, 0, 0, 1} +#define SUBMODULE_UPDATE_CLONE_INIT { \ + .list = MODULE_LIST_INIT, \ + .update = SUBMODULE_UPDATE_STRATEGY_INIT, \ + .recommend_shallow = -1, \ + .references = STRING_LIST_INIT_DUP, \ + .single_branch = -1, \ + .max_jobs = 1, \ +} static void next_submodule_warn_missing(struct submodule_update_clone *suc, @@ -1681,6 +1727,8 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, argv_array_pushl(&child->args, "--prefix", suc->prefix, NULL); if (suc->recommend_shallow && sub->recommend_shallow == 1) argv_array_push(&child->args, "--depth=1"); + if (suc->require_init) + argv_array_push(&child->args, "--require-init"); argv_array_pushl(&child->args, "--path", sub->path, NULL); argv_array_pushl(&child->args, "--name", sub->name, NULL); argv_array_pushl(&child->args, "--url", url, NULL); @@ -1693,6 +1741,10 @@ static int prepare_to_clone_next_submodule(const struct cache_entry *ce, argv_array_push(&child->args, "--dissociate"); if (suc->depth) argv_array_push(&child->args, suc->depth); + if (suc->single_branch >= 0) + argv_array_push(&child->args, suc->single_branch ? + "--single-branch" : + "--no-single-branch"); cleanup: strbuf_reset(&displaypath_sb); @@ -1870,6 +1922,10 @@ static int update_clone(int argc, const char **argv, const char *prefix) OPT__QUIET(&suc.quiet, N_("don't print cloning progress")), OPT_BOOL(0, "progress", &suc.progress, N_("force cloning progress")), + OPT_BOOL(0, "require-init", &suc.require_init, + N_("disallow cloning into non-empty directory")), + OPT_BOOL(0, "single-branch", &suc.single_branch, + N_("clone only one branch, HEAD or --branch")), OPT_END() }; diff --git a/builtin/tag.c b/builtin/tag.c index e0a4c25382..cc30d346f5 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -231,8 +231,9 @@ static void create_tag(const struct object_id *object, const char *object_ref, if (type <= OBJ_NONE) die(_("bad object type.")); - if (type == OBJ_TAG && advice_nested_tag) - advise(_(message_advice_nested_tag), tag, object_ref); + if (type == OBJ_TAG) + advise_if_enabled(ADVICE_NESTED_TAG, _(message_advice_nested_tag), + tag, object_ref); strbuf_addf(&header, "object %s\n" diff --git a/builtin/unpack-objects.c b/builtin/unpack-objects.c index a87a4bfd2c..dd4a75e030 100644 --- a/builtin/unpack-objects.c +++ b/builtin/unpack-objects.c @@ -24,6 +24,7 @@ static off_t consumed_bytes; static off_t max_input_size; static git_hash_ctx ctx; static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; +static struct progress *progress; /* * When running under --strict mode, objects whose reachability are @@ -92,6 +93,7 @@ static void use(int bytes) consumed_bytes += bytes; if (max_input_size && consumed_bytes > max_input_size) die(_("pack exceeds maximum allowed size")); + display_throughput(progress, consumed_bytes); } static void *get_data(unsigned long size) @@ -263,7 +265,8 @@ static void write_object(unsigned nr, enum object_type type, } else { struct object *obj; int eaten; - hash_object_file(buf, size, type_name(type), &obj_list[nr].oid); + hash_object_file(the_hash_algo, buf, size, type_name(type), + &obj_list[nr].oid); added_object(nr, type, buf, size); obj = parse_object_buffer(the_repository, &obj_list[nr].oid, type, size, buf, @@ -484,7 +487,6 @@ static void unpack_one(unsigned nr) static void unpack_all(void) { int i; - struct progress *progress = NULL; struct pack_header *hdr = fill(sizeof(struct pack_header)); nr_objects = ntohl(hdr->hdr_entries); diff --git a/builtin/update-index.c b/builtin/update-index.c index 49302d98c5..d527b8f106 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -35,6 +35,7 @@ static int verbose; static int mark_valid_only; static int mark_skip_worktree_only; static int mark_fsmonitor_only; +static int ignore_skip_worktree_entries; #define MARK_FLAG 1 #define UNMARK_FLAG 2 static struct strbuf mtime_dir = STRBUF_INIT; @@ -381,7 +382,8 @@ static int process_path(const char *path, struct stat *st, int stat_errno) * so updating it does not make sense. * On the other hand, removing it from index should work */ - if (allow_remove && remove_file_from_cache(path)) + if (!ignore_skip_worktree_entries && allow_remove && + remove_file_from_cache(path)) return error("%s: cannot remove from the index", path); return 0; } @@ -1014,6 +1016,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) {OPTION_SET_INT, 0, "no-skip-worktree", &mark_skip_worktree_only, NULL, N_("clear skip-worktree bit"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG}, + OPT_BOOL(0, "ignore-skip-worktree-entries", &ignore_skip_worktree_entries, + N_("do not touch index-only entries")), OPT_SET_INT(0, "info-only", &info_only, N_("add to index only; do not add content to object database"), 1), OPT_SET_INT(0, "force-remove", &force_remove, diff --git a/builtin/worktree.c b/builtin/worktree.c index 4de44f579a..d99db35668 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -234,14 +234,7 @@ static void validate_worktree_add(const char *path, const struct add_opts *opts) die(_("'%s' already exists"), path); worktrees = get_worktrees(0); - /* - * find_worktree()'s suffix matching may undesirably find the main - * rather than a linked worktree (for instance, when the basenames - * of the main worktree and the one being created are the same). - * We're only interested in linked worktrees, so skip the main - * worktree with +1. - */ - wt = find_worktree(worktrees + 1, NULL, path); + wt = find_worktree_by_path(worktrees, path); if (!wt) goto done; @@ -265,7 +258,7 @@ static int add_worktree(const char *path, const char *refname, const struct add_opts *opts) { struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; - struct strbuf sb = STRBUF_INIT; + struct strbuf sb = STRBUF_INIT, realpath = STRBUF_INIT; const char *name; struct child_process cp = CHILD_PROCESS_INIT; struct argv_array child_env = ARGV_ARRAY_INIT; @@ -337,9 +330,11 @@ static int add_worktree(const char *path, const char *refname, strbuf_reset(&sb); strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); - write_file(sb.buf, "%s", real_path(sb_git.buf)); + strbuf_realpath(&realpath, sb_git.buf, 1); + write_file(sb.buf, "%s", realpath.buf); + strbuf_realpath(&realpath, get_git_common_dir(), 1); write_file(sb_git.buf, "gitdir: %s/worktrees/%s", - real_path(get_git_common_dir()), name); + realpath.buf, name); /* * This is to keep resolve_ref() happy. We need a valid HEAD * or is_git_directory() will reject the directory. Any value which @@ -376,7 +371,7 @@ static int add_worktree(const char *path, const char *refname, if (opts->checkout) { cp.argv = NULL; argv_array_clear(&cp.args); - argv_array_pushl(&cp.args, "reset", "--hard", NULL); + argv_array_pushl(&cp.args, "reset", "--hard", "--no-recurse-submodules", NULL); if (opts->quiet) argv_array_push(&cp.args, "--quiet"); cp.env = child_env.argv; @@ -425,6 +420,7 @@ done: strbuf_release(&sb_repo); strbuf_release(&sb_git); strbuf_release(&sb_name); + strbuf_release(&realpath); return ret; } |