diff options
Diffstat (limited to 'builtin')
63 files changed, 4650 insertions, 1314 deletions
diff --git a/builtin/add.c b/builtin/add.c index 26b6ced09e..ea762a41e3 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -38,19 +38,27 @@ struct update_callback_data { int add_errors; }; -static void chmod_pathspec(struct pathspec *pathspec, char flip) +static int chmod_pathspec(struct pathspec *pathspec, char flip, int show_only) { - int i; + int i, ret = 0; for (i = 0; i < active_nr; i++) { struct cache_entry *ce = active_cache[i]; + int err; if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL)) continue; - if (chmod_cache_entry(ce, flip) < 0) - fprintf(stderr, "cannot chmod %cx '%s'\n", flip, ce->name); + if (!show_only) + err = chmod_cache_entry(ce, flip); + else + err = S_ISREG(ce->ce_mode) ? 0 : -1; + + if (err < 0) + ret = error(_("cannot chmod %cx '%s'"), flip, ce->name); } + + return ret; } static int fix_unmerged_status(struct diff_filepair *p, @@ -239,7 +247,7 @@ int run_add_interactive(const char *revision, const char *patch_mode, return status; } -int interactive_add(int argc, const char **argv, const char *prefix, int patch) +int interactive_add(const char **argv, const char *prefix, int patch) { struct pathspec pathspec; @@ -451,7 +459,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) 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)); + exit(interactive_add(argv + 1, prefix, patch_interactive)); } if (legacy_stash_p) { struct pathspec pathspec; @@ -609,7 +617,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) exit_status |= add_files(&dir, flags); if (chmod_arg && pathspec.nr) - chmod_pathspec(&pathspec, chmod_arg[0]); + exit_status |= chmod_pathspec(&pathspec, chmod_arg[0], show_only); unplug_bulk_checkin(); finish: diff --git a/builtin/am.c b/builtin/am.c index b5c63ddf1d..8355e3566f 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -98,8 +98,6 @@ struct am_state { char *author_name; char *author_email; char *author_date; - char *committer_name; - char *committer_email; char *msg; size_t msg_len; @@ -132,8 +130,6 @@ struct am_state { */ static void am_state_init(struct am_state *state) { - const char *committer; - struct ident_split id; int gpgsign; memset(state, 0, sizeof(*state)); @@ -154,14 +150,6 @@ static void am_state_init(struct am_state *state) if (!git_config_get_bool("commit.gpgsign", &gpgsign)) state->sign_commit = gpgsign ? "" : NULL; - - committer = git_committer_info(IDENT_STRICT); - if (split_ident_line(&id, committer, strlen(committer)) < 0) - die(_("invalid committer: %s"), committer); - state->committer_name = - xmemdupz(id.name_begin, id.name_end - id.name_begin); - state->committer_email = - xmemdupz(id.mail_begin, id.mail_end - id.mail_end); } /** @@ -173,8 +161,6 @@ static void am_state_release(struct am_state *state) free(state->author_name); free(state->author_email); free(state->author_date); - free(state->committer_name); - free(state->committer_email); free(state->msg); strvec_clear(&state->git_apply_opts); } @@ -1594,8 +1580,9 @@ static void do_commit(const struct am_state *state) IDENT_STRICT); if (state->committer_date_is_author_date) - committer = fmt_ident(state->committer_name, - state->author_email, WANT_COMMITTER_IDENT, + committer = fmt_ident(getenv("GIT_COMMITTER_NAME"), + getenv("GIT_COMMITTER_EMAIL"), + WANT_COMMITTER_IDENT, state->ignore_date ? NULL : state->author_date, IDENT_STRICT); @@ -1813,7 +1800,7 @@ next: if (!state->rebasing) { am_destroy(state); close_object_store(the_repository->objects); - run_auto_gc(state->quiet); + run_auto_maintenance(state->quiet); } } @@ -2180,6 +2167,8 @@ static int parse_opt_show_current_patch(const struct option *opt, const char *ar }; int new_value = SHOW_PATCH_RAW; + BUG_ON_OPT_NEG(unset); + if (arg) { for (new_value = 0; new_value < ARRAY_SIZE(valid_modes); new_value++) { if (!strcmp(arg, valid_modes[new_value])) @@ -2235,7 +2224,7 @@ int cmd_am(int argc, const char **argv, const char *prefix) N_("allow fall back on 3way merging if needed")), OPT__QUIET(&state.quiet, N_("be quiet")), OPT_SET_INT('s', "signoff", &state.signoff, - N_("add a Signed-off-by line to the commit message"), + N_("add a Signed-off-by trailer to the commit message"), SIGNOFF_EXPLICIT), OPT_BOOL('u', "utf8", &state.utf8, N_("recode into utf8 (default)")), @@ -2295,10 +2284,10 @@ int cmd_am(int argc, const char **argv, const char *prefix) N_("skip the current patch"), RESUME_SKIP), OPT_CMDMODE(0, "abort", &resume.mode, - N_("restore the original branch and abort the patching operation."), + N_("restore the original branch and abort the patching operation"), RESUME_ABORT), OPT_CMDMODE(0, "quit", &resume.mode, - N_("abort the patching operation but keep HEAD where it is."), + N_("abort the patching operation but keep HEAD where it is"), RESUME_QUIT), { OPTION_CALLBACK, 0, "show-current-patch", &resume.mode, "(diff|raw)", diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 7dcc1b5188..d69e13335d 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -8,6 +8,7 @@ #include "run-command.h" #include "prompt.h" #include "quote.h" +#include "revision.h" static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS") static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV") @@ -19,19 +20,24 @@ static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES") static GIT_PATH_FUNC(git_path_bisect_first_parent, "BISECT_FIRST_PARENT") static const char * const git_bisect_helper_usage[] = { - N_("git bisect--helper --next-all"), - N_("git bisect--helper --write-terms <bad_term> <good_term>"), - N_("git bisect--helper --bisect-clean-state"), N_("git bisect--helper --bisect-reset [<commit>]"), - N_("git bisect--helper --bisect-write [--no-log] <state> <revision> <good_term> <bad_term>"), - N_("git bisect--helper --bisect-check-and-set-terms <command> <good_term> <bad_term>"), N_("git bisect--helper --bisect-next-check <good_term> <bad_term> [<term>]"), N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"), N_("git bisect--helper --bisect-start [--term-{new,bad}=<term> --term-{old,good}=<term>]" " [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]"), + N_("git bisect--helper --bisect-next"), + N_("git bisect--helper --bisect-state (bad|new) [<rev>]"), + N_("git bisect--helper --bisect-state (good|old) [<rev>...]"), + N_("git bisect--helper --bisect-replay <filename>"), + N_("git bisect--helper --bisect-skip [(<rev>|<range>)...]"), NULL }; +struct add_bisect_ref_data { + struct rev_info *revs; + unsigned int object_flags; +}; + struct bisect_terms { char *term_good; char *term_bad; @@ -55,6 +61,8 @@ static void set_terms(struct bisect_terms *terms, const char *bad, static const char vocab_bad[] = "bad|new"; static const char vocab_good[] = "good|old"; +static int bisect_autostart(struct bisect_terms *terms); + /* * Check whether the string `term` belongs to the set of strings * included in the variable arguments. @@ -74,6 +82,65 @@ static int one_of(const char *term, ...) return res; } +/* + * return code BISECT_INTERNAL_SUCCESS_MERGE_BASE + * and BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND are codes + * that indicate special success. + */ + +static int is_bisect_success(enum bisect_error res) +{ + return !res || + res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND || + res == BISECT_INTERNAL_SUCCESS_MERGE_BASE; +} + +static int write_in_file(const char *path, const char *mode, const char *format, va_list args) +{ + FILE *fp = NULL; + int res = 0; + + if (strcmp(mode, "w") && strcmp(mode, "a")) + BUG("write-in-file does not support '%s' mode", mode); + fp = fopen(path, mode); + if (!fp) + return error_errno(_("cannot open file '%s' in mode '%s'"), path, mode); + res = vfprintf(fp, format, args); + + if (res < 0) { + int saved_errno = errno; + fclose(fp); + errno = saved_errno; + return error_errno(_("could not write to file '%s'"), path); + } + + return fclose(fp); +} + +static int write_to_file(const char *path, const char *format, ...) +{ + int res; + va_list args; + + va_start(args, format); + res = write_in_file(path, "w", format, args); + va_end(args); + + return res; +} + +static int append_to_file(const char *path, const char *format, ...) +{ + int res; + va_list args; + + va_start(args, format); + res = write_in_file(path, "a", format, args); + va_end(args); + + return res; +} + static int check_term_format(const char *term, const char *orig_term) { int res; @@ -104,7 +171,6 @@ static int check_term_format(const char *term, const char *orig_term) static int write_terms(const char *bad, const char *good) { - FILE *fp = NULL; int res; if (!strcmp(bad, good)) @@ -113,39 +179,11 @@ static int write_terms(const char *bad, const char *good) if (check_term_format(bad, "bad") || check_term_format(good, "good")) return -1; - fp = fopen(git_path_bisect_terms(), "w"); - if (!fp) - return error_errno(_("could not open the file BISECT_TERMS")); - - res = fprintf(fp, "%s\n%s\n", bad, good); - res |= fclose(fp); - return (res < 0) ? -1 : 0; -} + res = write_to_file(git_path_bisect_terms(), "%s\n%s\n", bad, good); -static int is_expected_rev(const char *expected_hex) -{ - struct strbuf actual_hex = STRBUF_INIT; - int res = 0; - if (strbuf_read_file(&actual_hex, git_path_bisect_expected_rev(), 0) >= 40) { - strbuf_trim(&actual_hex); - res = !strcmp(actual_hex.buf, expected_hex); - } - strbuf_release(&actual_hex); return res; } -static void check_expected_revs(const char **revs, int rev_nr) -{ - int i; - - for (i = 0; i < rev_nr; i++) { - if (!is_expected_rev(revs[i])) { - unlink_or_warn(git_path_bisect_ancestors_ok()); - unlink_or_warn(git_path_bisect_expected_rev()); - } - } -} - static int bisect_reset(const char *commit) { struct strbuf branch = STRBUF_INIT; @@ -421,12 +459,149 @@ finish: return res; } -static int bisect_start(struct bisect_terms *terms, const char **argv, int argc) +static int add_bisect_ref(const char *refname, const struct object_id *oid, + int flags, void *cb) +{ + struct add_bisect_ref_data *data = cb; + + add_pending_oid(data->revs, refname, oid, data->object_flags); + + return 0; +} + +static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs) +{ + int res = 0; + struct add_bisect_ref_data cb = { revs }; + char *good = xstrfmt("%s-*", terms->term_good); + + /* + * We cannot use terms->term_bad directly in + * for_each_glob_ref_in() and we have to append a '*' to it, + * otherwise for_each_glob_ref_in() will append '/' and '*'. + */ + char *bad = xstrfmt("%s*", terms->term_bad); + + /* + * It is important to reset the flags used by revision walks + * as the previous call to bisect_next_all() in turn + * sets up a revision walk. + */ + reset_revision_walk(); + init_revisions(revs, NULL); + setup_revisions(0, NULL, revs, NULL); + for_each_glob_ref_in(add_bisect_ref, bad, "refs/bisect/", &cb); + cb.object_flags = UNINTERESTING; + for_each_glob_ref_in(add_bisect_ref, good, "refs/bisect/", &cb); + if (prepare_revision_walk(revs)) + res = error(_("revision walk setup failed\n")); + + free(good); + free(bad); + return res; +} + +static int bisect_skipped_commits(struct bisect_terms *terms) +{ + int res; + FILE *fp = NULL; + struct rev_info revs; + struct commit *commit; + struct pretty_print_context pp = {0}; + struct strbuf commit_name = STRBUF_INIT; + + res = prepare_revs(terms, &revs); + if (res) + return res; + + fp = fopen(git_path_bisect_log(), "a"); + if (!fp) + return error_errno(_("could not open '%s' for appending"), + git_path_bisect_log()); + + if (fprintf(fp, "# only skipped commits left to test\n") < 0) + return error_errno(_("failed to write to '%s'"), git_path_bisect_log()); + + while ((commit = get_revision(&revs)) != NULL) { + strbuf_reset(&commit_name); + format_commit_message(commit, "%s", + &commit_name, &pp); + fprintf(fp, "# possible first %s commit: [%s] %s\n", + terms->term_bad, oid_to_hex(&commit->object.oid), + commit_name.buf); + } + + /* + * Reset the flags used by revision walks in case + * there is another revision walk after this one. + */ + reset_revision_walk(); + + strbuf_release(&commit_name); + fclose(fp); + return 0; +} + +static int bisect_successful(struct bisect_terms *terms) +{ + struct object_id oid; + struct commit *commit; + struct pretty_print_context pp = {0}; + struct strbuf commit_name = STRBUF_INIT; + char *bad_ref = xstrfmt("refs/bisect/%s",terms->term_bad); + int res; + + read_ref(bad_ref, &oid); + commit = lookup_commit_reference_by_name(bad_ref); + format_commit_message(commit, "%s", &commit_name, &pp); + + res = append_to_file(git_path_bisect_log(), "# first %s commit: [%s] %s\n", + terms->term_bad, oid_to_hex(&commit->object.oid), + commit_name.buf); + + strbuf_release(&commit_name); + free(bad_ref); + return res; +} + +static enum bisect_error bisect_next(struct bisect_terms *terms, const char *prefix) +{ + enum bisect_error res; + + if (bisect_autostart(terms)) + return BISECT_FAILED; + + if (bisect_next_check(terms, terms->term_good)) + return BISECT_FAILED; + + /* Perform all bisection computation */ + res = bisect_next_all(the_repository, prefix); + + if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) { + res = bisect_successful(terms); + return res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND; + } else if (res == BISECT_ONLY_SKIPPED_LEFT) { + res = bisect_skipped_commits(terms); + return res ? res : BISECT_ONLY_SKIPPED_LEFT; + } + return res; +} + +static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix) +{ + if (bisect_next_check(terms, NULL)) + return BISECT_OK; + + return bisect_next(terms, prefix); +} + +static enum bisect_error bisect_start(struct bisect_terms *terms, const char **argv, int argc) { int no_checkout = 0; int first_parent_only = 0; int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0; - int flags, pathspec_pos, res = 0; + int flags, pathspec_pos; + enum bisect_error res = BISECT_OK; struct string_list revs = STRING_LIST_INIT_DUP; struct string_list states = STRING_LIST_INIT_DUP; struct strbuf start_head = STRBUF_INIT; @@ -484,14 +659,13 @@ static int bisect_start(struct bisect_terms *terms, const char **argv, int argc) terms->term_bad = xstrdup(arg); } else if (starts_with(arg, "--")) { return error(_("unrecognized option: '%s'"), arg); - } else { - char *commit_id = xstrfmt("%s^{commit}", arg); - if (get_oid(commit_id, &oid) && has_double_dash) - die(_("'%s' does not appear to be a valid " - "revision"), arg); - + } else if (!get_oidf(&oid, "%s^{commit}", arg)) { string_list_append(&revs, oid_to_hex(&oid)); - free(commit_id); + } else if (has_double_dash) { + die(_("'%s' does not appear to be a valid " + "revision"), arg); + } else { + break; } } pathspec_pos = i; @@ -566,14 +740,7 @@ static int bisect_start(struct bisect_terms *terms, const char **argv, int argc) * Get rid of any old bisect state. */ if (bisect_clean_state()) - return -1; - - /* - * In case of mistaken revs or checkout error, or signals received, - * "bisect_auto_next" below may exit or misbehave. - * We have to trap this to be able to clean up using - * "bisect_clean_state". - */ + return BISECT_FAILED; /* * Write new start state @@ -590,7 +757,7 @@ static int bisect_start(struct bisect_terms *terms, const char **argv, int argc) } if (update_ref(NULL, "BISECT_HEAD", &oid, NULL, 0, UPDATE_REFS_MSG_ON_ERR)) { - res = -1; + res = BISECT_FAILED; goto finish; } } @@ -602,64 +769,298 @@ static int bisect_start(struct bisect_terms *terms, const char **argv, int argc) for (i = 0; i < states.nr; i++) if (bisect_write(states.items[i].string, revs.items[i].string, terms, 1)) { - res = -1; + res = BISECT_FAILED; goto finish; } if (must_write_terms && write_terms(terms->term_bad, terms->term_good)) { - res = -1; + res = BISECT_FAILED; goto finish; } res = bisect_append_log_quoted(argv); if (res) - res = -1; + res = BISECT_FAILED; finish: string_list_clear(&revs, 0); string_list_clear(&states, 0); strbuf_release(&start_head); strbuf_release(&bisect_names); + if (res) + return res; + + res = bisect_auto_next(terms, NULL); + if (!is_bisect_success(res)) + bisect_clean_state(); + return res; +} + +static inline int file_is_not_empty(const char *path) +{ + return !is_empty_or_missing_file(path); +} + +static int bisect_autostart(struct bisect_terms *terms) +{ + int res; + const char *yesno; + + if (file_is_not_empty(git_path_bisect_start())) + return 0; + + fprintf_ln(stderr, _("You need to start by \"git bisect " + "start\"\n")); + + if (!isatty(STDIN_FILENO)) + return -1; + + /* + * TRANSLATORS: Make sure to include [Y] and [n] in your + * translation. The program will only accept English input + * at this point. + */ + yesno = git_prompt(_("Do you want me to do it for you " + "[Y/n]? "), PROMPT_ECHO); + res = tolower(*yesno) == 'n' ? + -1 : bisect_start(terms, empty_strvec, 0); + + return res; +} + +static enum bisect_error bisect_state(struct bisect_terms *terms, const char **argv, + int argc) +{ + const char *state; + int i, verify_expected = 1; + struct object_id oid, expected; + struct strbuf buf = STRBUF_INIT; + struct oid_array revs = OID_ARRAY_INIT; + + if (!argc) + return error(_("Please call `--bisect-state` with at least one argument")); + + if (bisect_autostart(terms)) + return BISECT_FAILED; + + state = argv[0]; + if (check_and_set_terms(terms, state) || + !one_of(state, terms->term_good, terms->term_bad, "skip", NULL)) + return BISECT_FAILED; + + argv++; + argc--; + if (argc > 1 && !strcmp(state, terms->term_bad)) + return error(_("'git bisect %s' can take only one argument."), terms->term_bad); + + if (argc == 0) { + const char *head = "BISECT_HEAD"; + enum get_oid_result res_head = get_oid(head, &oid); + + if (res_head == MISSING_OBJECT) { + head = "HEAD"; + res_head = get_oid(head, &oid); + } + + if (res_head) + error(_("Bad rev input: %s"), head); + oid_array_append(&revs, &oid); + } + + /* + * All input revs must be checked before executing bisect_write() + * to discard junk revs. + */ + + for (; argc; argc--, argv++) { + if (get_oid(*argv, &oid)){ + error(_("Bad rev input: %s"), *argv); + oid_array_clear(&revs); + return BISECT_FAILED; + } + oid_array_append(&revs, &oid); + } + + if (strbuf_read_file(&buf, git_path_bisect_expected_rev(), 0) < the_hash_algo->hexsz || + get_oid_hex(buf.buf, &expected) < 0) + verify_expected = 0; /* Ignore invalid file contents */ + strbuf_release(&buf); + + for (i = 0; i < revs.nr; i++) { + if (bisect_write(state, oid_to_hex(&revs.oid[i]), terms, 0)) { + oid_array_clear(&revs); + return BISECT_FAILED; + } + if (verify_expected && !oideq(&revs.oid[i], &expected)) { + unlink_or_warn(git_path_bisect_ancestors_ok()); + unlink_or_warn(git_path_bisect_expected_rev()); + verify_expected = 0; + } + } + + oid_array_clear(&revs); + return bisect_auto_next(terms, NULL); +} + +static enum bisect_error bisect_log(void) +{ + int fd, status; + const char* filename = git_path_bisect_log(); + + if (is_empty_or_missing_file(filename)) + return error(_("We are not bisecting.")); + + fd = open(filename, O_RDONLY); + if (fd < 0) + return BISECT_FAILED; + + status = copy_fd(fd, STDOUT_FILENO); + close(fd); + return status ? BISECT_FAILED : BISECT_OK; +} + +static int process_replay_line(struct bisect_terms *terms, struct strbuf *line) +{ + const char *p = line->buf + strspn(line->buf, " \t"); + char *word_end, *rev; + + if ((!skip_prefix(p, "git bisect", &p) && + !skip_prefix(p, "git-bisect", &p)) || !isspace(*p)) + return 0; + p += strspn(p, " \t"); + + word_end = (char *)p + strcspn(p, " \t"); + rev = word_end + strspn(word_end, " \t"); + *word_end = '\0'; /* NUL-terminate the word */ + + get_terms(terms); + if (check_and_set_terms(terms, p)) + return -1; + + if (!strcmp(p, "start")) { + struct strvec argv = STRVEC_INIT; + int res; + sq_dequote_to_strvec(rev, &argv); + res = bisect_start(terms, argv.v, argv.nr); + strvec_clear(&argv); + return res; + } + + if (one_of(p, terms->term_good, + terms->term_bad, "skip", NULL)) + return bisect_write(p, rev, terms, 0); + + if (!strcmp(p, "terms")) { + struct strvec argv = STRVEC_INIT; + int res; + sq_dequote_to_strvec(rev, &argv); + res = bisect_terms(terms, argv.nr == 1 ? argv.v[0] : NULL); + strvec_clear(&argv); + return res; + } + error(_("'%s'?? what are you talking about?"), p); + + return -1; +} + +static enum bisect_error bisect_replay(struct bisect_terms *terms, const char *filename) +{ + FILE *fp = NULL; + enum bisect_error res = BISECT_OK; + struct strbuf line = STRBUF_INIT; + + if (is_empty_or_missing_file(filename)) + return error(_("cannot read file '%s' for replaying"), filename); + + if (bisect_reset(NULL)) + return BISECT_FAILED; + + fp = fopen(filename, "r"); + if (!fp) + return BISECT_FAILED; + + while ((strbuf_getline(&line, fp) != EOF) && !res) + res = process_replay_line(terms, &line); + + strbuf_release(&line); + fclose(fp); + + if (res) + return BISECT_FAILED; + + return bisect_auto_next(terms, NULL); +} + +static enum bisect_error bisect_skip(struct bisect_terms *terms, const char **argv, int argc) +{ + int i; + enum bisect_error res; + struct strvec argv_state = STRVEC_INIT; + + strvec_push(&argv_state, "skip"); + + for (i = 0; i < argc; i++) { + const char *dotdot = strstr(argv[i], ".."); + + if (dotdot) { + struct rev_info revs; + struct commit *commit; + + init_revisions(&revs, NULL); + setup_revisions(2, argv + i - 1, &revs, NULL); + + if (prepare_revision_walk(&revs)) + die(_("revision walk setup failed\n")); + while ((commit = get_revision(&revs)) != NULL) + strvec_push(&argv_state, + oid_to_hex(&commit->object.oid)); + + reset_revision_walk(); + } else { + strvec_push(&argv_state, argv[i]); + } + } + res = bisect_state(terms, argv_state.v, argv_state.nr); + + strvec_clear(&argv_state); return res; } int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { enum { - NEXT_ALL = 1, - WRITE_TERMS, - BISECT_CLEAN_STATE, - CHECK_EXPECTED_REVS, - BISECT_RESET, - BISECT_WRITE, - CHECK_AND_SET_TERMS, + BISECT_RESET = 1, BISECT_NEXT_CHECK, BISECT_TERMS, - BISECT_START + BISECT_START, + BISECT_AUTOSTART, + BISECT_NEXT, + BISECT_STATE, + BISECT_LOG, + BISECT_REPLAY, + BISECT_SKIP } cmdmode = 0; int res = 0, nolog = 0; struct option options[] = { - OPT_CMDMODE(0, "next-all", &cmdmode, - N_("perform 'git bisect next'"), NEXT_ALL), - OPT_CMDMODE(0, "write-terms", &cmdmode, - N_("write the terms to .git/BISECT_TERMS"), WRITE_TERMS), - OPT_CMDMODE(0, "bisect-clean-state", &cmdmode, - N_("cleanup the bisection state"), BISECT_CLEAN_STATE), - OPT_CMDMODE(0, "check-expected-revs", &cmdmode, - N_("check for expected revs"), CHECK_EXPECTED_REVS), OPT_CMDMODE(0, "bisect-reset", &cmdmode, N_("reset the bisection state"), BISECT_RESET), - OPT_CMDMODE(0, "bisect-write", &cmdmode, - N_("write out the bisection state in BISECT_LOG"), BISECT_WRITE), - OPT_CMDMODE(0, "check-and-set-terms", &cmdmode, - N_("check and set terms in a bisection state"), CHECK_AND_SET_TERMS), OPT_CMDMODE(0, "bisect-next-check", &cmdmode, N_("check whether bad or good terms exist"), BISECT_NEXT_CHECK), OPT_CMDMODE(0, "bisect-terms", &cmdmode, N_("print out the bisect terms"), BISECT_TERMS), OPT_CMDMODE(0, "bisect-start", &cmdmode, N_("start the bisect session"), BISECT_START), + OPT_CMDMODE(0, "bisect-next", &cmdmode, + N_("find the next bisection commit"), BISECT_NEXT), + OPT_CMDMODE(0, "bisect-state", &cmdmode, + N_("mark the state of ref (or refs)"), BISECT_STATE), + OPT_CMDMODE(0, "bisect-log", &cmdmode, + N_("list the bisection steps so far"), BISECT_LOG), + OPT_CMDMODE(0, "bisect-replay", &cmdmode, + N_("replay the bisection process from the given file"), BISECT_REPLAY), + OPT_CMDMODE(0, "bisect-skip", &cmdmode, + N_("skip some commits for checkout"), BISECT_SKIP), OPT_BOOL(0, "no-log", &nolog, N_("no log for BISECT_WRITE")), OPT_END() @@ -674,35 +1075,10 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) usage_with_options(git_bisect_helper_usage, options); switch (cmdmode) { - case NEXT_ALL: - res = bisect_next_all(the_repository, prefix); - break; - case WRITE_TERMS: - if (argc != 2) - return error(_("--write-terms requires two arguments")); - return write_terms(argv[0], argv[1]); - case BISECT_CLEAN_STATE: - if (argc != 0) - return error(_("--bisect-clean-state requires no arguments")); - return bisect_clean_state(); - case CHECK_EXPECTED_REVS: - check_expected_revs(argv, argc); - return 0; case BISECT_RESET: if (argc > 1) return error(_("--bisect-reset requires either no argument or a commit")); - return !!bisect_reset(argc ? argv[0] : NULL); - case BISECT_WRITE: - if (argc != 4 && argc != 5) - return error(_("--bisect-write requires either 4 or 5 arguments")); - set_terms(&terms, argv[3], argv[2]); - res = bisect_write(argv[0], argv[1], &terms, nolog); - break; - case CHECK_AND_SET_TERMS: - if (argc != 3) - return error(_("--check-and-set-terms requires 3 arguments")); - set_terms(&terms, argv[2], argv[1]); - res = check_and_set_terms(&terms, argv[0]); + res = bisect_reset(argc ? argv[0] : NULL); break; case BISECT_NEXT_CHECK: if (argc != 2 && argc != 3) @@ -719,8 +1095,34 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) set_terms(&terms, "bad", "good"); res = bisect_start(&terms, argv, argc); break; + case BISECT_NEXT: + if (argc) + return error(_("--bisect-next requires 0 arguments")); + get_terms(&terms); + res = bisect_next(&terms, prefix); + break; + case BISECT_STATE: + set_terms(&terms, "bad", "good"); + get_terms(&terms); + res = bisect_state(&terms, argv, argc); + break; + case BISECT_LOG: + if (argc) + return error(_("--bisect-log requires 0 arguments")); + res = bisect_log(); + break; + case BISECT_REPLAY: + if (argc != 1) + return error(_("no logfile given")); + set_terms(&terms, "bad", "good"); + res = bisect_replay(&terms, argv[0]); + break; + case BISECT_SKIP: + set_terms(&terms, "bad", "good"); + res = bisect_skip(&terms, argv, argc); + break; default: - return error("BUG: unknown subcommand '%d'", cmdmode); + BUG("unknown subcommand %d", cmdmode); } free_terms(&terms); @@ -728,8 +1130,8 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) * Handle early success * From check_merge_bases > check_good_are_ancestors_of_bad > bisect_next_all */ - if (res == BISECT_INTERNAL_SUCCESS_MERGE_BASE) + if ((res == BISECT_INTERNAL_SUCCESS_MERGE_BASE) || (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND)) res = BISECT_OK; - return abs(res); + return -res; } diff --git a/builtin/blame.c b/builtin/blame.c index eb513fbe60..641523ff9a 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -27,6 +27,7 @@ #include "object-store.h" #include "blame.h" #include "refs.h" +#include "tag.h" static char blame_usage[] = N_("git blame [<options>] [<rev-opts>] [<rev>] [--] <file>"); @@ -424,13 +425,11 @@ static void setup_default_color_by_age(void) parse_color_fields("blue,12 month ago,white,1 month ago,red"); } -static void determine_line_heat(struct blame_entry *ent, const char **dest_color) +static void determine_line_heat(struct commit_info *ci, const char **dest_color) { int i = 0; - struct commit_info ci; - get_commit_info(ent->suspect->commit, &ci, 1); - while (i < colorfield_nr && ci.author_time > colorfield[i].hop) + while (i < colorfield_nr && ci->author_time > colorfield[i].hop) i++; *dest_color = colorfield[i].col; @@ -452,7 +451,7 @@ static void emit_other(struct blame_scoreboard *sb, struct blame_entry *ent, int cp = blame_nth_line(sb, ent->lno); if (opt & OUTPUT_SHOW_AGE_WITH_COLOR) { - determine_line_heat(ent, &default_color); + determine_line_heat(&ci, &default_color); color = default_color; reset = GIT_COLOR_RESET; } @@ -803,6 +802,28 @@ static int is_a_rev(const char *name) return OBJ_NONE < oid_object_info(the_repository, &oid, NULL); } +static int peel_to_commit_oid(struct object_id *oid_ret, void *cbdata) +{ + struct repository *r = ((struct blame_scoreboard *)cbdata)->repo; + struct object_id oid; + + oidcpy(&oid, oid_ret); + while (1) { + struct object *obj; + int kind = oid_object_info(r, &oid, NULL); + if (kind == OBJ_COMMIT) { + oidcpy(oid_ret, &oid); + return 0; + } + if (kind != OBJ_TAG) + return -1; + obj = deref_tag(r, parse_object(r, &oid), NULL, 0); + if (!obj) + return -1; + oidcpy(&oid, &obj->oid); + } +} + static void build_ignorelist(struct blame_scoreboard *sb, struct string_list *ignore_revs_file_list, struct string_list *ignore_rev_list) @@ -815,10 +836,12 @@ static void build_ignorelist(struct blame_scoreboard *sb, if (!strcmp(i->string, "")) oidset_clear(&sb->ignore_list); else - oidset_parse_file(&sb->ignore_list, i->string); + oidset_parse_file_carefully(&sb->ignore_list, i->string, + peel_to_commit_oid, sb); } for_each_string_list_item(i, ignore_rev_list) { - if (get_oid_committish(i->string, &oid)) + if (get_oid_committish(i->string, &oid) || + peel_to_commit_oid(&oid, sb)) die(_("cannot find revision %s to ignore"), i->string); oidset_insert(&sb->ignore_list, &oid); } @@ -841,32 +864,33 @@ int cmd_blame(int argc, const char **argv, const char *prefix) const char *revs_file = NULL; const char *contents_from = NULL; const struct option options[] = { - OPT_BOOL(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")), - OPT_BOOL('b', NULL, &blank_boundary, N_("Do not show object names of boundary commits (Default: off)")), - OPT_BOOL(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")), - OPT_BOOL(0, "show-stats", &show_stats, N_("Show work cost statistics")), - OPT_BOOL(0, "progress", &show_progress, N_("Force progress reporting")), - OPT_BIT(0, "score-debug", &output_option, N_("Show output score for blame entries"), OUTPUT_SHOW_SCORE), - OPT_BIT('f', "show-name", &output_option, N_("Show original filename (Default: auto)"), OUTPUT_SHOW_NAME), - OPT_BIT('n', "show-number", &output_option, N_("Show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER), - OPT_BIT('p', "porcelain", &output_option, N_("Show in a format designed for machine consumption"), OUTPUT_PORCELAIN), - OPT_BIT(0, "line-porcelain", &output_option, N_("Show porcelain format with per-line commit information"), OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN), - OPT_BIT('c', NULL, &output_option, N_("Use the same output mode as git-annotate (Default: off)"), OUTPUT_ANNOTATE_COMPAT), - OPT_BIT('t', NULL, &output_option, N_("Show raw timestamp (Default: off)"), OUTPUT_RAW_TIMESTAMP), - OPT_BIT('l', NULL, &output_option, N_("Show long commit SHA1 (Default: off)"), OUTPUT_LONG_OBJECT_NAME), - OPT_BIT('s', NULL, &output_option, N_("Suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR), - OPT_BIT('e', "show-email", &output_option, N_("Show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL), - OPT_BIT('w', NULL, &xdl_opts, N_("Ignore whitespace differences"), XDF_IGNORE_WHITESPACE), - OPT_STRING_LIST(0, "ignore-rev", &ignore_rev_list, N_("rev"), N_("Ignore <rev> when blaming")), - OPT_STRING_LIST(0, "ignore-revs-file", &ignore_revs_file_list, N_("file"), N_("Ignore revisions from <file>")), + OPT_BOOL(0, "incremental", &incremental, N_("show blame entries as we find them, incrementally")), + OPT_BOOL('b', NULL, &blank_boundary, N_("do not show object names of boundary commits (Default: off)")), + OPT_BOOL(0, "root", &show_root, N_("do not treat root commits as boundaries (Default: off)")), + OPT_BOOL(0, "show-stats", &show_stats, N_("show work cost statistics")), + OPT_BOOL(0, "progress", &show_progress, N_("force progress reporting")), + OPT_BIT(0, "score-debug", &output_option, N_("show output score for blame entries"), OUTPUT_SHOW_SCORE), + OPT_BIT('f', "show-name", &output_option, N_("show original filename (Default: auto)"), OUTPUT_SHOW_NAME), + OPT_BIT('n', "show-number", &output_option, N_("show original linenumber (Default: off)"), OUTPUT_SHOW_NUMBER), + OPT_BIT('p', "porcelain", &output_option, N_("show in a format designed for machine consumption"), OUTPUT_PORCELAIN), + OPT_BIT(0, "line-porcelain", &output_option, N_("show porcelain format with per-line commit information"), OUTPUT_PORCELAIN|OUTPUT_LINE_PORCELAIN), + OPT_BIT('c', NULL, &output_option, N_("use the same output mode as git-annotate (Default: off)"), OUTPUT_ANNOTATE_COMPAT), + OPT_BIT('t', NULL, &output_option, N_("show raw timestamp (Default: off)"), OUTPUT_RAW_TIMESTAMP), + OPT_BIT('l', NULL, &output_option, N_("show long commit SHA1 (Default: off)"), OUTPUT_LONG_OBJECT_NAME), + OPT_BIT('s', NULL, &output_option, N_("suppress author name and timestamp (Default: off)"), OUTPUT_NO_AUTHOR), + OPT_BIT('e', "show-email", &output_option, N_("show author email instead of name (Default: off)"), OUTPUT_SHOW_EMAIL), + OPT_BIT('w', NULL, &xdl_opts, N_("ignore whitespace differences"), XDF_IGNORE_WHITESPACE), + OPT_STRING_LIST(0, "ignore-rev", &ignore_rev_list, N_("rev"), N_("ignore <rev> when blaming")), + 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), - 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")), - OPT_CALLBACK_F('C', NULL, &opt, N_("score"), N_("Find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback), - OPT_CALLBACK_F('M', NULL, &opt, N_("score"), N_("Find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback), - OPT_STRING_LIST('L', NULL, &range_list, N_("n,m"), N_("Process only line range n,m, counting from 1")), + OPT_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")), + OPT_CALLBACK_F('C', NULL, &opt, N_("score"), N_("find line copies within and across files"), PARSE_OPT_OPTARG, blame_copy_callback), + OPT_CALLBACK_F('M', NULL, &opt, N_("score"), N_("find line movements within and across files"), PARSE_OPT_OPTARG, blame_move_callback), + OPT_STRING_LIST('L', NULL, &range_list, N_("range"), + N_("process only line range <start>,<end> or function :<funcname>")), OPT__ABBREV(&abbrev), OPT_END() }; @@ -1057,17 +1081,18 @@ parse_done: sb.contents_from = contents_from; sb.reverse = reverse; sb.repo = the_repository; + sb.path = path; build_ignorelist(&sb, &ignore_revs_file_list, &ignore_rev_list); string_list_clear(&ignore_revs_file_list, 0); string_list_clear(&ignore_rev_list, 0); - setup_scoreboard(&sb, path, &o); + setup_scoreboard(&sb, &o); /* * Changed-path Bloom filters are disabled when looking * for copies. */ if (!(opt & PICKAXE_BLAME_COPY)) - setup_blame_bloom_data(&sb, path); + setup_blame_bloom_data(&sb); lno = sb.num_lines; @@ -1086,7 +1111,7 @@ parse_done: if ((!lno && (top || bottom)) || lno < bottom) die(Q_("file %s has only %lu line", "file %s has only %lu lines", - lno), path, lno); + lno), sb.path, lno); if (bottom < 1) bottom = 1; if (top < 1 || lno < top) @@ -1111,7 +1136,6 @@ parse_done: string_list_clear(&range_list, 0); sb.ent = NULL; - sb.path = path; if (blame_move_score) sb.move_score = blame_move_score; @@ -1125,7 +1149,7 @@ parse_done: sb.xdl_opts = xdl_opts; sb.no_whole_file_rename = no_whole_file_rename; - read_mailmap(&mailmap, NULL); + read_mailmap(&mailmap); sb.found_guilty_entry = &found_guilty_entry; sb.found_guilty_entry_data = π diff --git a/builtin/branch.c b/builtin/branch.c index efb30b8820..bcc00bcf18 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -202,6 +202,9 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, int remote_branch = 0; struct strbuf bname = STRBUF_INIT; unsigned allowed_interpret; + struct string_list refs_to_delete = STRING_LIST_INIT_DUP; + struct string_list_item *item; + int branch_name_pos; switch (kinds) { case FILTER_REFS_REMOTES: @@ -219,6 +222,7 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, default: die(_("cannot use -a with -d")); } + branch_name_pos = strcspn(fmt, "%"); if (!force) { head_rev = lookup_commit_reference(the_repository, &head_oid); @@ -265,30 +269,35 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, goto next; } - if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : &oid, - REF_NO_DEREF)) { - error(remote_branch - ? _("Error deleting remote-tracking branch '%s'") - : _("Error deleting branch '%s'"), - bname.buf); - ret = 1; - goto next; - } - if (!quiet) { - printf(remote_branch - ? _("Deleted remote-tracking branch %s (was %s).\n") - : _("Deleted branch %s (was %s).\n"), - bname.buf, - (flags & REF_ISBROKEN) ? "broken" - : (flags & REF_ISSYMREF) ? target - : find_unique_abbrev(&oid, DEFAULT_ABBREV)); - } - delete_branch_config(bname.buf); + item = string_list_append(&refs_to_delete, name); + item->util = xstrdup((flags & REF_ISBROKEN) ? "broken" + : (flags & REF_ISSYMREF) ? target + : find_unique_abbrev(&oid, DEFAULT_ABBREV)); next: free(target); } + if (delete_refs(NULL, &refs_to_delete, REF_NO_DEREF)) + ret = 1; + + for_each_string_list_item(item, &refs_to_delete) { + char *describe_ref = item->util; + char *name = item->string; + if (!ref_exists(name)) { + char *refname = name + branch_name_pos; + if (!quiet) + printf(remote_branch + ? _("Deleted remote-tracking branch %s (was %s).\n") + : _("Deleted branch %s (was %s).\n"), + name + branch_name_pos, describe_ref); + + delete_branch_config(refname); + } + free(describe_ref); + } + string_list_clear(&refs_to_delete, 0); + free(name); strbuf_release(&bname); @@ -538,7 +547,9 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int strbuf_addf(&logmsg, "Branch: renamed %s to %s", oldref.buf, newref.buf); - if (!copy && rename_ref(oldref.buf, newref.buf, logmsg.buf)) + if (!copy && + (!head || strcmp(oldname, head) || !is_null_oid(&head_oid)) && + rename_ref(oldref.buf, newref.buf, logmsg.buf)) die(_("Branch rename failed")); if (copy && copy_existing_ref(oldref.buf, newref.buf, logmsg.buf)) die(_("Branch copy failed")); @@ -724,7 +735,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix) print_current_branch_name(); return 0; } else if (list) { - /* git branch --local also shows HEAD when it is detached */ + /* git branch --list also shows HEAD when it is detached */ if ((filter.kind & FILTER_REFS_BRANCHES) && filter.detached) filter.kind |= FILTER_REFS_DETACHED_HEAD; filter.name_patterns = argv; @@ -737,7 +748,9 @@ int cmd_branch(int argc, const char **argv, const char *prefix) */ if (!sorting) sorting = ref_default_sorting(); - ref_sorting_icase_all(sorting, icase); + ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase); + ref_sorting_set_sort_flags_all( + sorting, REF_SORTING_DETACHED_HEAD_FIRST, 1); print_ref_list(&filter, sorting, &format); print_columns(&output, colopts, NULL); string_list_clear(&output, 0); @@ -829,10 +842,10 @@ int cmd_branch(int argc, const char **argv, const char *prefix) die(_("Branch '%s' has no upstream information"), branch->name); strbuf_addf(&buf, "branch.%s.remote", branch->name); - git_config_set_multivar(buf.buf, NULL, NULL, 1); + git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE); strbuf_reset(&buf); strbuf_addf(&buf, "branch.%s.merge", branch->name); - git_config_set_multivar(buf.buf, NULL, NULL, 1); + git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE); strbuf_release(&buf); } else if (argc > 0 && argc <= 2) { if (filter.kind != FILTER_REFS_BRANCHES) diff --git a/builtin/bugreport.c b/builtin/bugreport.c index 3ad4b9b62e..ad3cc9c02f 100644 --- a/builtin/bugreport.c +++ b/builtin/bugreport.c @@ -125,6 +125,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix) struct strbuf report_path = STRBUF_INIT; int report = -1; time_t now = time(NULL); + struct tm tm; char *option_output = NULL; char *option_suffix = "%Y-%m-%d-%H%M"; const char *user_relative_path = NULL; @@ -147,7 +148,7 @@ int cmd_bugreport(int argc, const char **argv, const char *prefix) strbuf_complete(&report_path, '/'); strbuf_addstr(&report_path, "git-bugreport-"); - strbuf_addftime(&report_path, option_suffix, localtime(&now), 0, 0); + strbuf_addftime(&report_path, option_suffix, localtime_r(&now, &tm), 0, 0); strbuf_addstr(&report_path, ".txt"); switch (safe_create_leading_directories(report_path.buf)) { diff --git a/builtin/check-mailmap.c b/builtin/check-mailmap.c index cdce144f3b..7dc47e4793 100644 --- a/builtin/check-mailmap.c +++ b/builtin/check-mailmap.c @@ -47,7 +47,7 @@ int cmd_check_mailmap(int argc, const char **argv, const char *prefix) if (argc == 0 && !use_stdin) die(_("no contacts specified")); - read_mailmap(&mailmap, NULL); + read_mailmap(&mailmap); for (i = 0; i < argc; ++i) check_mailmap(&mailmap, argv[i]); diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index a854fd16e7..023e49e271 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -23,22 +23,35 @@ static struct checkout state = CHECKOUT_INIT; static void write_tempfile_record(const char *name, const char *prefix) { int i; + int have_tempname = 0; if (CHECKOUT_ALL == checkout_stage) { - for (i = 1; i < 4; i++) { - if (i > 1) - putchar(' '); - if (topath[i][0]) - fputs(topath[i], stdout); - else - putchar('.'); + for (i = 1; i < 4; i++) + if (topath[i][0]) { + have_tempname = 1; + break; + } + + if (have_tempname) { + for (i = 1; i < 4; i++) { + if (i > 1) + putchar(' '); + if (topath[i][0]) + fputs(topath[i], stdout); + else + putchar('.'); + } } - } else + } else if (topath[checkout_stage][0]) { + have_tempname = 1; fputs(topath[checkout_stage], stdout); + } - putchar('\t'); - write_name_quoted_relative(name, prefix, stdout, - nul_term_line ? '\0' : '\n'); + if (have_tempname) { + putchar('\t'); + write_name_quoted_relative(name, prefix, stdout, + nul_term_line ? '\0' : '\n'); + } for (i = 0; i < 4; i++) { topath[i][0] = 0; @@ -79,6 +92,14 @@ static int checkout_file(const char *name, const char *prefix) return errs > 0 ? -1 : 0; } + /* + * At this point we know we didn't try to check anything out. If it was + * because we did find an entry but it was stage 0, that's not an + * error. + */ + if (has_same_name && checkout_stage == CHECKOUT_ALL) + return 0; + if (!state.quiet) { fprintf(stderr, "git checkout-index: %s ", name); if (!has_same_name) @@ -159,6 +180,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) int prefix_length; int force = 0, quiet = 0, not_new = 0; int index_opt = 0; + int err = 0; struct option builtin_checkout_index_options[] = { OPT_BOOL('a', "all", &all, N_("check out all files in the index")), @@ -223,7 +245,7 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) if (read_from_stdin) die("git checkout-index: don't mix '--stdin' and explicit filenames"); p = prefix_path(prefix, prefix_length, arg); - checkout_file(p, prefix); + err |= checkout_file(p, prefix); free(p); } @@ -245,13 +267,16 @@ int cmd_checkout_index(int argc, const char **argv, const char *prefix) strbuf_swap(&buf, &unquoted); } p = prefix_path(prefix, prefix_length, buf.buf); - checkout_file(p, prefix); + err |= checkout_file(p, prefix); free(p); } strbuf_release(&unquoted); strbuf_release(&buf); } + if (err) + return 1; + if (all) checkout_all(prefix, prefix_length); diff --git a/builtin/checkout.c b/builtin/checkout.c index 0951f8fee5..2d6550bc3c 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -471,6 +471,21 @@ static int checkout_paths(const struct checkout_opts *opts, if (opts->patch_mode) { const char *patch_mode; + const char *rev = new_branch_info->name; + char rev_oid[GIT_MAX_HEXSZ + 1]; + + /* + * Since rev can be in the form of `<a>...<b>` (which is not + * recognized by diff-index), we will always replace the name + * with the hex of the commit (whether it's in `...` form or + * not) for the run_add_interactive() machinery to work + * properly. However, there is special logic for the HEAD case + * so we mustn't replace that. Also, when we were given a + * tree-object, new_branch_info->commit would be NULL, but we + * do not have to do any replacement, either. + */ + if (rev && new_branch_info->commit && strcmp(rev, "HEAD")) + rev = oid_to_hex_r(rev_oid, &new_branch_info->commit->object.oid); if (opts->checkout_index && opts->checkout_worktree) patch_mode = "--patch=checkout"; @@ -481,7 +496,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(new_branch_info->name, patch_mode, &opts->pathspec); + return run_add_interactive(rev, patch_mode, &opts->pathspec); } repo_hold_locked_index(the_repository, &lock_file, LOCK_DIE_ON_ERROR); @@ -806,9 +821,6 @@ static int merge_working_tree(const struct checkout_opts *opts, } } - if (!active_cache_tree) - active_cache_tree = cache_tree(); - if (!cache_tree_fully_valid(active_cache_tree)) cache_tree_update(&the_index, WRITE_TREE_SILENT | WRITE_TREE_REPAIR); @@ -1029,7 +1041,7 @@ static void orphaned_commit_warning(struct commit *old_commit, struct commit *ne describe_detached_head(_("Previous HEAD position was"), old_commit); /* Clean up objects used, as they will be reused. */ - clear_commit_marks_all(ALL_REV_FLAGS); + repo_clear_commit_marks(the_repository, ALL_REV_FLAGS); } static int switch_branches(const struct checkout_opts *opts, @@ -1093,11 +1105,16 @@ static int switch_branches(const struct checkout_opts *opts, static int git_checkout_config(const char *var, const char *value, void *cb) { + struct checkout_opts *opts = cb; + if (!strcmp(var, "diff.ignoresubmodules")) { - struct checkout_opts *opts = cb; handle_ignore_submodules_arg(&opts->diff_options, value); return 0; } + if (!strcmp(var, "checkout.guess")) { + opts->dwim_new_local_branch = git_config_bool(var, value); + return 0; + } if (starts_with(var, "submodule.")) return git_default_submodule_config(var, value, NULL); diff --git a/builtin/clone.c b/builtin/clone.c index fbfd6568cd..51e844a2de 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -53,6 +53,7 @@ static int option_shallow_submodules; static int deepen; static char *option_template, *option_depth, *option_since; static char *option_origin = NULL; +static char *remote_name = NULL; static char *option_branch = NULL; static struct string_list option_not = STRING_LIST_INIT_NODUP; static const char *real_git_dir; @@ -721,7 +722,7 @@ static void update_head(const struct ref *our, const struct ref *remote, if (!option_bare) { update_ref(msg, "HEAD", &our->old_oid, NULL, 0, UPDATE_REFS_DIE_ON_ERR); - install_branch_config(0, head, option_origin, our->name); + install_branch_config(0, head, remote_name, our->name); } } else if (our) { struct commit *c = lookup_commit_reference(the_repository, @@ -851,8 +852,26 @@ static int checkout(int submodule_progress) return err; } +static int git_clone_config(const char *k, const char *v, void *cb) +{ + if (!strcmp(k, "clone.defaultremotename")) { + free(remote_name); + remote_name = xstrdup(v); + } + return git_default_config(k, v, cb); +} + static int write_one_config(const char *key, const char *value, void *data) { + /* + * give git_clone_config a chance to write config values back to the + * environment, since git_config_set_multivar_gently only deals with + * config-file writes + */ + int apply_failed = git_clone_config(key, value, data); + if (apply_failed) + return apply_failed; + return git_config_set_multivar_gently(key, value ? value : "true", CONFIG_REGEX_NONE, 0); @@ -905,12 +924,12 @@ static void write_refspec_config(const char *src_ref_prefix, } /* Configure the remote */ if (value.len) { - strbuf_addf(&key, "remote.%s.fetch", option_origin); + strbuf_addf(&key, "remote.%s.fetch", remote_name); git_config_set_multivar(key.buf, value.buf, "^$", 0); strbuf_reset(&key); if (option_mirror) { - strbuf_addf(&key, "remote.%s.mirror", option_origin); + strbuf_addf(&key, "remote.%s.mirror", remote_name); git_config_set(key.buf, "true"); strbuf_reset(&key); } @@ -960,9 +979,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix) int err = 0, complete_refs_before_fetch = 1; int submodule_progress; - struct strvec ref_prefixes = STRVEC_INIT; + struct transport_ls_refs_options transport_ls_refs_options = + TRANSPORT_LS_REFS_OPTIONS_INIT; packet_trace_identity("clone"); + + git_config(git_clone_config, NULL); + argc = parse_options(argc, argv, prefix, builtin_clone_options, builtin_clone_usage, 0); @@ -991,9 +1014,6 @@ int cmd_clone(int argc, const char **argv, const char *prefix) option_no_checkout = 1; } - if (!option_origin) - option_origin = "origin"; - repo_name = argv[0]; path = get_repo_path(repo_name, &is_bundle); @@ -1124,9 +1144,30 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (real_git_dir) git_dir = real_git_dir; + /* + * additional config can be injected with -c, make sure it's included + * after init_db, which clears the entire config environment. + */ write_config(&option_config); - git_config(git_default_config, NULL); + /* + * re-read config after init_db and write_config to pick up any config + * injected by --template and --config, respectively. + */ + git_config(git_clone_config, NULL); + + /* + * apply the remote name provided by --origin only after this second + * call to git_config, to ensure it overrides all config-based values. + */ + if (option_origin != NULL) + remote_name = xstrdup(option_origin); + + if (remote_name == NULL) + remote_name = xstrdup("origin"); + + if (!valid_remote_name(remote_name)) + die(_("'%s' is not a valid remote name"), remote_name); if (option_bare) { if (option_mirror) @@ -1135,15 +1176,15 @@ int cmd_clone(int argc, const char **argv, const char *prefix) git_config_set("core.bare", "true"); } else { - strbuf_addf(&branch_top, "refs/remotes/%s/", option_origin); + strbuf_addf(&branch_top, "refs/remotes/%s/", remote_name); } - strbuf_addf(&key, "remote.%s.url", option_origin); + strbuf_addf(&key, "remote.%s.url", remote_name); git_config_set(key.buf, repo); strbuf_reset(&key); if (option_no_tags) { - strbuf_addf(&key, "remote.%s.tagOpt", option_origin); + strbuf_addf(&key, "remote.%s.tagOpt", remote_name); git_config_set(key.buf, "--no-tags"); strbuf_reset(&key); } @@ -1154,7 +1195,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (option_sparse_checkout && git_sparse_checkout_init(dir)) return 1; - remote = remote_get(option_origin); + remote = remote_get(remote_name); refspec_appendf(&remote->fetch, "+%s*:%s*", src_ref_prefix, branch_top.buf); @@ -1217,14 +1258,17 @@ int cmd_clone(int argc, const char **argv, const char *prefix) transport->smart_options->check_self_contained_and_connected = 1; - strvec_push(&ref_prefixes, "HEAD"); - refspec_ref_prefixes(&remote->fetch, &ref_prefixes); + strvec_push(&transport_ls_refs_options.ref_prefixes, "HEAD"); + refspec_ref_prefixes(&remote->fetch, + &transport_ls_refs_options.ref_prefixes); if (option_branch) - expand_ref_prefix(&ref_prefixes, option_branch); + expand_ref_prefix(&transport_ls_refs_options.ref_prefixes, + option_branch); if (!option_no_tags) - strvec_push(&ref_prefixes, "refs/tags/"); + strvec_push(&transport_ls_refs_options.ref_prefixes, + "refs/tags/"); - refs = transport_get_remote_refs(transport, &ref_prefixes); + refs = transport_get_remote_refs(transport, &transport_ls_refs_options); if (refs) { int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); @@ -1233,7 +1277,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) * Now that we know what algorithm the remote side is using, * let's set ours to the same thing. */ - initialize_repository_version(hash_algo); + initialize_repository_version(hash_algo, 1); repo_set_hash_algo(the_repository, hash_algo); mapped_refs = wanted_peer_refs(refs, &remote->fetch); @@ -1253,8 +1297,11 @@ int cmd_clone(int argc, const char **argv, const char *prefix) break; } - if (!is_local && !complete_refs_before_fetch) - transport_fetch_refs(transport, mapped_refs); + if (!is_local && !complete_refs_before_fetch) { + err = transport_fetch_refs(transport, mapped_refs); + if (err) + goto cleanup; + } remote_head = find_ref_by_name(refs, "HEAD"); remote_head_points_at = @@ -1266,7 +1313,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) if (!our_head_points_at) die(_("Remote branch %s not found in upstream %s"), - option_branch, option_origin); + option_branch, remote_name); } else our_head_points_at = remote_head_points_at; @@ -1274,7 +1321,7 @@ int cmd_clone(int argc, const char **argv, const char *prefix) else { if (option_branch) die(_("Remote branch %s not found in upstream %s"), - option_branch, option_origin); + option_branch, remote_name); warning(_("You appear to have cloned an empty repository.")); mapped_refs = NULL; @@ -1283,10 +1330,21 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote_head = NULL; option_no_checkout = 1; if (!option_bare) { - const char *branch = git_default_branch_name(); - char *ref = xstrfmt("refs/heads/%s", branch); + const char *branch; + char *ref; + + if (transport_ls_refs_options.unborn_head_target && + skip_prefix(transport_ls_refs_options.unborn_head_target, + "refs/heads/", &branch)) { + ref = transport_ls_refs_options.unborn_head_target; + transport_ls_refs_options.unborn_head_target = NULL; + create_symref("HEAD", ref, reflog_msg.buf); + } else { + branch = git_default_branch_name(0); + ref = xstrfmt("refs/heads/%s", branch); + } - install_branch_config(0, branch, option_origin, ref); + install_branch_config(0, branch, remote_name, ref); free(ref); } } @@ -1295,12 +1353,15 @@ int cmd_clone(int argc, const char **argv, const char *prefix) remote_head_points_at, &branch_top); if (filter_options.choice) - partial_clone_register(option_origin, &filter_options); + partial_clone_register(remote_name, &filter_options); if (is_local) clone_local(path, git_dir); - else if (refs && complete_refs_before_fetch) - transport_fetch_refs(transport, mapped_refs); + else if (refs && complete_refs_before_fetch) { + err = transport_fetch_refs(transport, mapped_refs); + if (err) + goto cleanup; + } update_remote_refs(refs, mapped_refs, remote_head_points_at, branch_top.buf, reflog_msg.buf, transport, @@ -1327,11 +1388,14 @@ int cmd_clone(int argc, const char **argv, const char *prefix) junk_mode = JUNK_LEAVE_REPO; err = checkout(submodule_progress); +cleanup: + free(remote_name); strbuf_release(&reflog_msg); strbuf_release(&branch_top); strbuf_release(&key); junk_mode = JUNK_LEAVE_ALL; - strvec_clear(&ref_prefixes); + strvec_clear(&transport_ls_refs_options.ref_prefixes); + free(transport_ls_refs_options.unborn_head_target); return err; } diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index 523501f217..cd86315221 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -13,7 +13,8 @@ static char const * const builtin_commit_graph_usage[] = { N_("git commit-graph verify [--object-dir <objdir>] [--shallow] [--[no-]progress]"), N_("git commit-graph write [--object-dir <objdir>] [--append] " "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] " - "[--changed-paths] [--[no-]progress] <split options>"), + "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] " + "<split options>"), NULL }; @@ -25,7 +26,8 @@ static const char * const builtin_commit_graph_verify_usage[] = { static const char * const builtin_commit_graph_write_usage[] = { N_("git commit-graph write [--object-dir <objdir>] [--append] " "[--split[=<strategy>]] [--reachable|--stdin-packs|--stdin-commits] " - "[--changed-paths] [--[no-]progress] <split options>"), + "[--changed-paths] [--[no-]max-new-filters <n>] [--[no-]progress] " + "<split options>"), NULL }; @@ -76,7 +78,7 @@ static int graph_verify(int argc, const char **argv) static struct option builtin_commit_graph_verify_options[] = { OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"), - N_("The object directory to store the graph")), + N_("the object directory to store the graph")), OPT_BOOL(0, "shallow", &opts.shallow, N_("if the commit-graph is split, only verify the tip file")), OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")), @@ -106,7 +108,7 @@ 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, odb); + graph = load_commit_graph_one_fd_st(the_repository, fd, &st, odb); else graph = read_commit_graph_one(the_repository, odb); @@ -119,13 +121,15 @@ static int graph_verify(int argc, const char **argv) } extern int read_replace_refs; -static struct split_commit_graph_opts split_opts; +static struct commit_graph_opts write_opts; static int write_option_parse_split(const struct option *opt, const char *arg, int unset) { enum commit_graph_split_flags *flags = opt->value; + BUG_ON_OPT_NEG(unset); + opts.split = 1; if (!arg) return 0; @@ -162,6 +166,35 @@ static int read_one_commit(struct oidset *commits, struct progress *progress, return 0; } +static int write_option_max_new_filters(const struct option *opt, + const char *arg, + int unset) +{ + int *to = opt->value; + if (unset) + *to = -1; + else { + const char *s; + *to = strtol(arg, (char **)&s, 10); + if (*s) + return error(_("%s expects a numerical value"), + optname(opt, opt->flags)); + } + return 0; +} + +static int git_commit_graph_write_config(const char *var, const char *value, + void *cb) +{ + if (!strcmp(var, "commitgraph.maxnewfilters")) + write_opts.max_new_filters = git_config_int(var, value); + /* + * No need to fall-back to 'git_default_config', since this was already + * called in 'cmd_commit_graph()'. + */ + return 0; +} + static int graph_write(int argc, const char **argv) { struct string_list pack_indexes = STRING_LIST_INIT_NODUP; @@ -175,7 +208,7 @@ static int graph_write(int argc, const char **argv) static struct option builtin_commit_graph_write_options[] = { OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"), - N_("The object directory to store the graph")), + N_("the object directory to store the graph")), OPT_BOOL(0, "reachable", &opts.reachable, N_("start walk at all refs")), OPT_BOOL(0, "stdin-packs", &opts.stdin_packs, @@ -187,27 +220,33 @@ static int graph_write(int argc, const char **argv) OPT_BOOL(0, "changed-paths", &opts.enable_changed_paths, N_("enable computation for changed paths")), OPT_BOOL(0, "progress", &opts.progress, N_("force progress reporting")), - OPT_CALLBACK_F(0, "split", &split_opts.flags, NULL, + OPT_CALLBACK_F(0, "split", &write_opts.split_flags, NULL, N_("allow writing an incremental commit-graph file"), PARSE_OPT_OPTARG | PARSE_OPT_NONEG, write_option_parse_split), - OPT_INTEGER(0, "max-commits", &split_opts.max_commits, + OPT_INTEGER(0, "max-commits", &write_opts.max_commits, N_("maximum number of commits in a non-base split commit-graph")), - OPT_INTEGER(0, "size-multiple", &split_opts.size_multiple, + OPT_INTEGER(0, "size-multiple", &write_opts.size_multiple, N_("maximum ratio between two levels of a split commit-graph")), - OPT_EXPIRY_DATE(0, "expire-time", &split_opts.expire_time, + OPT_EXPIRY_DATE(0, "expire-time", &write_opts.expire_time, N_("only expire files older than a given date-time")), + OPT_CALLBACK_F(0, "max-new-filters", &write_opts.max_new_filters, + NULL, N_("maximum number of changed-path Bloom filters to compute"), + 0, write_option_max_new_filters), OPT_END(), }; opts.progress = isatty(2); opts.enable_changed_paths = -1; - split_opts.size_multiple = 2; - split_opts.max_commits = 0; - split_opts.expire_time = 0; + write_opts.size_multiple = 2; + write_opts.max_commits = 0; + write_opts.expire_time = 0; + write_opts.max_new_filters = -1; trace2_cmd_mode("write"); + git_config(git_commit_graph_write_config, &opts); + argc = parse_options(argc, argv, NULL, builtin_commit_graph_write_options, builtin_commit_graph_write_usage, 0); @@ -232,7 +271,7 @@ static int graph_write(int argc, const char **argv) odb = find_odb(the_repository, opts.obj_dir); if (opts.reachable) { - if (write_commit_graph_reachable(odb, flags, &split_opts)) + if (write_commit_graph_reachable(odb, flags, &write_opts)) return 1; return 0; } @@ -261,7 +300,7 @@ static int graph_write(int argc, const char **argv) opts.stdin_packs ? &pack_indexes : NULL, opts.stdin_commits ? &commits : NULL, flags, - &split_opts)) + &write_opts)) result = 1; cleanup: @@ -275,7 +314,7 @@ int cmd_commit_graph(int argc, const char **argv, const char *prefix) static struct option builtin_commit_graph_options[] = { OPT_STRING(0, "object-dir", &opts.obj_dir, N_("dir"), - N_("The object directory to store the graph")), + N_("the object directory to store the graph")), OPT_END(), }; diff --git a/builtin/commit.c b/builtin/commit.c index 5d91b13a5c..739110c5a7 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -326,7 +326,7 @@ static void refresh_cache_or_die(int refresh_flags) die_resolve_conflict("commit"); } -static const char *prepare_index(int argc, const char **argv, const char *prefix, +static const char *prepare_index(const char **argv, const char *prefix, const struct commit *current_head, int is_status) { struct string_list partial = STRING_LIST_INIT_DUP; @@ -378,7 +378,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT)); setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1); - if (interactive_add(argc, argv, prefix, patch_interactive) != 0) + if (interactive_add(argv, prefix, patch_interactive) != 0) die(_("interactive add failed")); the_repository->index_file = old_repo_index_file; @@ -1039,7 +1039,7 @@ static const char *find_author_by_nickname(const char *name) av[++ac] = NULL; setup_revisions(ac, av, &revs, NULL); revs.mailmap = &mailmap; - read_mailmap(revs.mailmap, NULL); + read_mailmap(revs.mailmap); if (prepare_revision_walk(&revs)) die(_("revision walk setup failed")); @@ -1241,13 +1241,13 @@ static int parse_and_validate_options(int argc, const char *argv[], return argc; } -static int dry_run_commit(int argc, const char **argv, const char *prefix, +static int dry_run_commit(const char **argv, const char *prefix, const struct commit *current_head, struct wt_status *s) { int committable; const char *index_file; - index_file = prepare_index(argc, argv, prefix, current_head, 1); + index_file = prepare_index(argv, prefix, current_head, 1); committable = run_status(stdout, index_file, prefix, 0, s); rollback_index_files(); @@ -1507,7 +1507,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")), OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")), OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")), - OPT_BOOL('s', "signoff", &signoff, N_("add Signed-off-by:")), + OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")), OPT_FILENAME('t', "template", &template_file, N_("use specified template file")), OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")), OPT_CLEANUP(&cleanup_arg), @@ -1584,8 +1584,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) verbose = (config_commit_verbose < 0) ? 0 : config_commit_verbose; if (dry_run) - return dry_run_commit(argc, argv, prefix, current_head, &s); - index_file = prepare_index(argc, argv, prefix, current_head, 0); + return dry_run_commit(argv, prefix, current_head, &s); + index_file = prepare_index(argv, prefix, current_head, 0); /* Set up everything for writing the commit object. This includes running hooks, writing the trees, and interacting with the user. */ @@ -1700,7 +1700,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) git_test_write_commit_graph_or_die(); repo_rerere(the_repository, 0); - run_auto_gc(quiet); + run_auto_maintenance(quiet); run_commit_hook(use_editor, get_index_file(), "post-commit", NULL); if (amend && !no_post_rewrite) { commit_post_rewrite(the_repository, current_head, &oid); diff --git a/builtin/config.c b/builtin/config.c index 963d65fd3f..f71fa39b38 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -14,6 +14,7 @@ static const char *const builtin_config_usage[] = { static char *key; static regex_t *key_regexp; +static const char *value_pattern; static regex_t *regexp; static int show_keys; static int omit_values; @@ -34,6 +35,7 @@ static int respect_includes_opt = -1; static struct config_options config_options; static int show_origin; static int show_scope; +static int fixed_value; #define ACTION_GET (1<<0) #define ACTION_GET_ALL (1<<1) @@ -133,17 +135,18 @@ static struct option builtin_config_options[] = { OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")), OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object")), OPT_GROUP(N_("Action")), - OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET), - OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-regex]"), ACTION_GET_ALL), - OPT_BIT(0, "get-regexp", &actions, N_("get values for regexp: name-regex [value-regex]"), ACTION_GET_REGEXP), + OPT_BIT(0, "get", &actions, N_("get value: name [value-pattern]"), ACTION_GET), + OPT_BIT(0, "get-all", &actions, N_("get all values: key [value-pattern]"), ACTION_GET_ALL), + OPT_BIT(0, "get-regexp", &actions, N_("get values for regexp: name-regex [value-pattern]"), ACTION_GET_REGEXP), OPT_BIT(0, "get-urlmatch", &actions, N_("get value specific for the URL: section[.var] URL"), ACTION_GET_URLMATCH), - OPT_BIT(0, "replace-all", &actions, N_("replace all matching variables: name value [value_regex]"), ACTION_REPLACE_ALL), + OPT_BIT(0, "replace-all", &actions, N_("replace all matching variables: name value [value-pattern]"), ACTION_REPLACE_ALL), OPT_BIT(0, "add", &actions, N_("add a new variable: name value"), ACTION_ADD), - OPT_BIT(0, "unset", &actions, N_("remove a variable: name [value-regex]"), ACTION_UNSET), - OPT_BIT(0, "unset-all", &actions, N_("remove all matches: name [value-regex]"), ACTION_UNSET_ALL), + OPT_BIT(0, "unset", &actions, N_("remove a variable: name [value-pattern]"), ACTION_UNSET), + OPT_BIT(0, "unset-all", &actions, N_("remove all matches: name [value-pattern]"), ACTION_UNSET_ALL), OPT_BIT(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION), OPT_BIT(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION), OPT_BIT('l', "list", &actions, N_("list all"), ACTION_LIST), + OPT_BOOL(0, "fixed-value", &fixed_value, N_("use string equality when comparing values to 'value-pattern'")), OPT_BIT('e', "edit", &actions, N_("open an editor"), ACTION_EDIT), OPT_BIT(0, "get-color", &actions, N_("find the color configured: slot [default]"), ACTION_GET_COLOR), OPT_BIT(0, "get-colorbool", &actions, N_("find the color setting: slot [stdout-is-tty]"), ACTION_GET_COLORBOOL), @@ -296,6 +299,8 @@ static int collect_config(const char *key_, const char *value_, void *cb) return 0; if (use_key_regexp && regexec(key_regexp, key_, 0, NULL, 0)) return 0; + if (fixed_value && strcmp(value_pattern, (value_?value_:""))) + return 0; if (regexp != NULL && (do_not_match ^ !!regexec(regexp, (value_?value_:""), 0, NULL, 0))) return 0; @@ -306,7 +311,7 @@ static int collect_config(const char *key_, const char *value_, void *cb) return format_config(&values->items[values->nr++], key_, value_); } -static int get_value(const char *key_, const char *regex_) +static int get_value(const char *key_, const char *regex_, unsigned flags) { int ret = CONFIG_GENERIC_ERROR; struct strbuf_list values = {NULL}; @@ -343,7 +348,9 @@ static int get_value(const char *key_, const char *regex_) } } - if (regex_) { + if (regex_ && (flags & CONFIG_FLAGS_FIXED_VALUE)) + value_pattern = regex_; + else if (regex_) { if (regex_[0] == '!') { do_not_match = 1; regex_++; @@ -631,6 +638,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) { int nongit = !startup_info->have_repository; char *value; + int flags = 0; given_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT)); @@ -766,6 +774,42 @@ int cmd_config(int argc, const char **argv, const char *prefix) usage_builtin_config(); } + /* check usage of --fixed-value */ + if (fixed_value) { + int allowed_usage = 0; + + switch (actions) { + /* git config --get <name> <value-pattern> */ + case ACTION_GET: + /* git config --get-all <name> <value-pattern> */ + case ACTION_GET_ALL: + /* git config --get-regexp <name-pattern> <value-pattern> */ + case ACTION_GET_REGEXP: + /* git config --unset <name> <value-pattern> */ + case ACTION_UNSET: + /* git config --unset-all <name> <value-pattern> */ + case ACTION_UNSET_ALL: + allowed_usage = argc > 1 && !!argv[1]; + break; + + /* git config <name> <value> <value-pattern> */ + case ACTION_SET_ALL: + /* git config --replace-all <name> <value> <value-pattern> */ + case ACTION_REPLACE_ALL: + allowed_usage = argc > 2 && !!argv[2]; + break; + + /* other options don't allow --fixed-value */ + } + + if (!allowed_usage) { + error(_("--fixed-value only applies with 'value-pattern'")); + usage_builtin_config(); + } + + flags |= CONFIG_FLAGS_FIXED_VALUE; + } + if (actions & PAGING_ACTIONS) setup_auto_pager("config", 1); @@ -827,7 +871,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) value = normalize_value(argv[0], argv[1]); UNLEAK(value); return git_config_set_multivar_in_file_gently(given_config_source.file, - argv[0], value, argv[2], 0); + argv[0], value, argv[2], + flags); } else if (actions == ACTION_ADD) { check_write(); @@ -836,7 +881,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) UNLEAK(value); return git_config_set_multivar_in_file_gently(given_config_source.file, argv[0], value, - CONFIG_REGEX_NONE, 0); + CONFIG_REGEX_NONE, + flags); } else if (actions == ACTION_REPLACE_ALL) { check_write(); @@ -844,23 +890,24 @@ int cmd_config(int argc, const char **argv, const char *prefix) value = normalize_value(argv[0], argv[1]); UNLEAK(value); return git_config_set_multivar_in_file_gently(given_config_source.file, - argv[0], value, argv[2], 1); + argv[0], value, argv[2], + flags | CONFIG_FLAGS_MULTI_REPLACE); } else if (actions == ACTION_GET) { check_argc(argc, 1, 2); - return get_value(argv[0], argv[1]); + return get_value(argv[0], argv[1], flags); } else if (actions == ACTION_GET_ALL) { do_all = 1; check_argc(argc, 1, 2); - return get_value(argv[0], argv[1]); + return get_value(argv[0], argv[1], flags); } else if (actions == ACTION_GET_REGEXP) { show_keys = 1; use_key_regexp = 1; do_all = 1; check_argc(argc, 1, 2); - return get_value(argv[0], argv[1]); + return get_value(argv[0], argv[1], flags); } else if (actions == ACTION_GET_URLMATCH) { check_argc(argc, 2, 2); @@ -871,7 +918,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_argc(argc, 1, 2); if (argc == 2) return git_config_set_multivar_in_file_gently(given_config_source.file, - argv[0], NULL, argv[1], 0); + argv[0], NULL, argv[1], + flags); else return git_config_set_in_file_gently(given_config_source.file, argv[0], NULL); @@ -880,7 +928,8 @@ int cmd_config(int argc, const char **argv, const char *prefix) check_write(); check_argc(argc, 1, 2); return git_config_set_multivar_in_file_gently(given_config_source.file, - argv[0], NULL, argv[1], 1); + argv[0], NULL, argv[1], + flags | CONFIG_FLAGS_MULTI_REPLACE); } else if (actions == ACTION_RENAME_SECTION) { int ret; diff --git a/builtin/credential-store.c b/builtin/credential-store.c index 5331ab151a..ae3c1ba75f 100644 --- a/builtin/credential-store.c +++ b/builtin/credential-store.c @@ -1,4 +1,5 @@ #include "builtin.h" +#include "config.h" #include "lockfile.h" #include "credential.h" #include "string-list.h" @@ -58,8 +59,11 @@ static void print_line(struct strbuf *buf) static void rewrite_credential_file(const char *fn, struct credential *c, struct strbuf *extra) { - if (hold_lock_file_for_update(&credential_lock, fn, 0) < 0) - die_errno("unable to get credential storage lock"); + int timeout_ms = 1000; + + git_config_get_int("credentialstore.locktimeoutms", &timeout_ms); + if (hold_lock_file_for_update_timeout(&credential_lock, fn, 0, timeout_ms) < 0) + die_errno(_("unable to get credential storage lock in %d ms"), timeout_ms); if (extra) print_line(extra); parse_credential_file(fn, c, NULL, print_line); diff --git a/builtin/credential.c b/builtin/credential.c index 879acfbcda..d75dcdc64a 100644 --- a/builtin/credential.c +++ b/builtin/credential.c @@ -1,6 +1,7 @@ #include "git-compat-util.h" #include "credential.h" #include "builtin.h" +#include "config.h" static const char usage_msg[] = "git credential [fill|approve|reject]"; @@ -10,6 +11,8 @@ int cmd_credential(int argc, const char **argv, const char *prefix) const char *op; struct credential c = CREDENTIAL_INIT; + git_config(git_default_config, NULL); + if (argc != 2 || !strcmp(argv[1], "-h")) usage(usage_msg); op = argv[1]; diff --git a/builtin/describe.c b/builtin/describe.c index 7668591d57..40482d8e9f 100644 --- a/builtin/describe.c +++ b/builtin/describe.c @@ -194,7 +194,7 @@ static int get_name(const char *path, const struct object_id *oid, int flag, voi } /* Is it annotated? */ - if (!peel_ref(path, &peeled)) { + if (!peel_iterated_oid(oid, &peeled)) { is_annotated = !oideq(oid, &peeled); } else { oidcpy(&peeled, oid); diff --git a/builtin/diff-files.c b/builtin/diff-files.c index 1e352dd8f7..70103c4095 100644 --- a/builtin/diff-files.c +++ b/builtin/diff-files.c @@ -7,6 +7,7 @@ #include "cache.h" #include "config.h" #include "diff.h" +#include "diff-merges.h" #include "commit.h" #include "revision.h" #include "builtin.h" @@ -35,7 +36,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) */ rev.diffopt.ita_invisible_in_index = 1; - precompose_argv(argc, argv); + prefix = precompose_argv_prefix(argc, argv, prefix); argc = setup_revisions(argc, argv, &rev, NULL); while (1 < argc && argv[1][0] == '-') { @@ -53,6 +54,7 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) } if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; + rev.diffopt.rotate_to_strict = 1; /* * Make sure there are NO revision (i.e. pending object) parameter, @@ -69,9 +71,9 @@ int cmd_diff_files(int argc, const char **argv, const char *prefix) * was not asked to. "diff-files -c -p" should not densify * (the user should ask with "diff-files --cc" explicitly). */ - if (rev.max_count == -1 && !rev.combine_merges && + if (rev.max_count == -1 && (rev.diffopt.output_format & DIFF_FORMAT_PATCH)) - rev.combine_merges = rev.dense_combined_merges = 1; + diff_merges_set_dense_combined_if_unset(&rev); if (read_cache_preload(&rev.diffopt.pathspec) < 0) { perror("read_cache_preload"); diff --git a/builtin/diff-index.c b/builtin/diff-index.c index 93ec642423..176fe7ff2b 100644 --- a/builtin/diff-index.c +++ b/builtin/diff-index.c @@ -15,7 +15,7 @@ COMMON_DIFF_OPTIONS_HELP; int cmd_diff_index(int argc, const char **argv, const char *prefix) { struct rev_info rev; - int cached = 0; + unsigned int option = 0; int i; int result; @@ -25,20 +25,24 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) git_config(git_diff_basic_config, NULL); /* no "diff" UI options */ repo_init_revisions(the_repository, &rev, prefix); rev.abbrev = 0; - precompose_argv(argc, argv); + prefix = precompose_argv_prefix(argc, argv, prefix); argc = setup_revisions(argc, argv, &rev, NULL); for (i = 1; i < argc; i++) { const char *arg = argv[i]; if (!strcmp(arg, "--cached")) - cached = 1; + option |= DIFF_INDEX_CACHED; + else if (!strcmp(arg, "--merge-base")) + option |= DIFF_INDEX_MERGE_BASE; else usage(diff_cache_usage); } if (!rev.diffopt.output_format) rev.diffopt.output_format = DIFF_FORMAT_RAW; + rev.diffopt.rotate_to_strict = 1; + /* * Make sure there is one revision (i.e. pending object), * and there is no revision filtering parameters. @@ -46,7 +50,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) if (rev.pending.nr != 1 || rev.max_count != -1 || rev.min_age != -1 || rev.max_age != -1) usage(diff_cache_usage); - if (!cached) { + if (!(option & DIFF_INDEX_CACHED)) { setup_work_tree(); if (read_cache_preload(&rev.diffopt.pathspec) < 0) { perror("read_cache_preload"); @@ -56,7 +60,7 @@ int cmd_diff_index(int argc, const char **argv, const char *prefix) perror("read_cache"); return -1; } - result = run_diff_index(&rev, cached); + result = run_diff_index(&rev, option); UNLEAK(rev); return diff_result_code(&rev.diffopt, result); } diff --git a/builtin/diff-tree.c b/builtin/diff-tree.c index 802363d0a2..f33d30d57b 100644 --- a/builtin/diff-tree.c +++ b/builtin/diff-tree.c @@ -111,6 +111,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) struct setup_revision_opt s_r_opt; struct userformat_want w; int read_stdin = 0; + int merge_base = 0; if (argc == 2 && !strcmp(argv[1], "-h")) usage(diff_tree_usage); @@ -125,7 +126,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) memset(&s_r_opt, 0, sizeof(s_r_opt)); s_r_opt.tweak = diff_tree_tweak_rev; - precompose_argv(argc, argv); + prefix = precompose_argv_prefix(argc, argv, prefix); argc = setup_revisions(argc, argv, opt, &s_r_opt); memset(&w, 0, sizeof(w)); @@ -143,9 +144,20 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) read_stdin = 1; continue; } + if (!strcmp(arg, "--merge-base")) { + merge_base = 1; + continue; + } usage(diff_tree_usage); } + if (read_stdin && merge_base) + die(_("--stdin and --merge-base are mutually exclusive")); + if (merge_base && opt->pending.nr != 2) + die(_("--merge-base only works with two commits")); + + opt->diffopt.rotate_to_strict = 1; + /* * NOTE! We expect "a..b" to expand to "^a b" but it is * perfectly valid for revision range parser to yield "b ^a", @@ -165,7 +177,12 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) case 2: tree1 = opt->pending.objects[0].item; tree2 = opt->pending.objects[1].item; - if (tree2->flags & UNINTERESTING) { + if (merge_base) { + struct object_id oid; + + diff_get_merge_base(opt, &oid); + tree1 = lookup_object(the_repository, &oid); + } else if (tree2->flags & UNINTERESTING) { SWAP(tree2, tree1); } diff_tree_oid(&tree1->oid, &tree2->oid, "", &opt->diffopt); @@ -177,6 +194,7 @@ int cmd_diff_tree(int argc, const char **argv, const char *prefix) int saved_nrl = 0; int saved_dcctc = 0; + opt->diffopt.rotate_to_strict = 0; if (opt->diffopt.detect_rename) { if (!the_index.cache) repo_read_index(the_repository); diff --git a/builtin/diff.c b/builtin/diff.c index cb98811c21..617b9a4101 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -13,6 +13,7 @@ #include "blob.h" #include "tag.h" #include "diff.h" +#include "diff-merges.h" #include "diffcore.h" #include "revision.h" #include "log-tree.h" @@ -26,7 +27,7 @@ static const char builtin_diff_usage[] = "git diff [<options>] [<commit>] [--] [<path>...]\n" " or: git diff [<options>] --cached [<commit>] [--] [<path>...]\n" -" or: git diff [<options>] <commit> [<commit>...] <commit> [--] [<path>...]\n" +" or: git diff [<options>] <commit> [--merge-base] [<commit>...] <commit> [--] [<path>...]\n" " or: git diff [<options>] <commit>...<commit>] [--] [<path>...]\n" " or: git diff [<options>] <blob> <blob>]\n" " or: git diff [<options>] --no-index [--] <path> <path>]\n" @@ -134,11 +135,13 @@ static int builtin_diff_blobs(struct rev_info *revs, static int builtin_diff_index(struct rev_info *revs, int argc, const char **argv) { - int cached = 0; + unsigned int option = 0; while (1 < argc) { const char *arg = argv[1]; if (!strcmp(arg, "--cached") || !strcmp(arg, "--staged")) - cached = 1; + option |= DIFF_INDEX_CACHED; + else if (!strcmp(arg, "--merge-base")) + option |= DIFF_INDEX_MERGE_BASE; else usage(builtin_diff_usage); argv++; argc--; @@ -151,7 +154,7 @@ static int builtin_diff_index(struct rev_info *revs, revs->max_count != -1 || revs->min_age != -1 || revs->max_age != -1) usage(builtin_diff_usage); - if (!cached) { + if (!(option & DIFF_INDEX_CACHED)) { setup_work_tree(); if (read_cache_preload(&revs->diffopt.pathspec) < 0) { perror("read_cache_preload"); @@ -161,7 +164,7 @@ static int builtin_diff_index(struct rev_info *revs, perror("read_cache"); return -1; } - return run_diff_index(revs, cached); + return run_diff_index(revs, option); } static int builtin_diff_tree(struct rev_info *revs, @@ -170,19 +173,34 @@ static int builtin_diff_tree(struct rev_info *revs, struct object_array_entry *ent1) { const struct object_id *(oid[2]); - int swap = 0; + struct object_id mb_oid; + int merge_base = 0; - if (argc > 1) - usage(builtin_diff_usage); + while (1 < argc) { + const char *arg = argv[1]; + if (!strcmp(arg, "--merge-base")) + merge_base = 1; + else + usage(builtin_diff_usage); + argv++; argc--; + } - /* - * We saw two trees, ent0 and ent1. If ent1 is uninteresting, - * swap them. - */ - if (ent1->item->flags & UNINTERESTING) - swap = 1; - oid[swap] = &ent0->item->oid; - oid[1 - swap] = &ent1->item->oid; + if (merge_base) { + diff_get_merge_base(revs, &mb_oid); + oid[0] = &mb_oid; + oid[1] = &revs->pending.objects[1].item->oid; + } else { + int swap = 0; + + /* + * We saw two trees, ent0 and ent1. If ent1 is uninteresting, + * swap them. + */ + if (ent1->item->flags & UNINTERESTING) + swap = 1; + oid[swap] = &ent0->item->oid; + oid[1 - swap] = &ent1->item->oid; + } diff_tree_oid(oid[0], oid[1], "", &revs->diffopt); log_tree_diff_flush(revs); return 0; @@ -199,12 +217,11 @@ static int builtin_diff_combined(struct rev_info *revs, if (argc > 1) usage(builtin_diff_usage); - if (!revs->dense_combined_merges && !revs->combine_merges) - revs->dense_combined_merges = revs->combine_merges = 1; + diff_merges_set_dense_combined_if_unset(revs); + for (i = 1; i < ents; i++) oid_array_append(&parents, &ent[i].item->oid); - diff_tree_combined(&ent[0].item->oid, &parents, - revs->dense_combined_merges, revs); + diff_tree_combined(&ent[0].item->oid, &parents, revs); oid_array_clear(&parents); return 0; } @@ -249,9 +266,9 @@ static int builtin_diff_files(struct rev_info *revs, int argc, const char **argv * dense one, --cc can be explicitly asked for, or just rely * on the default). */ - if (revs->max_count == -1 && !revs->combine_merges && + if (revs->max_count == -1 && (revs->diffopt.output_format & DIFF_FORMAT_PATCH)) - revs->combine_merges = revs->dense_combined_merges = 1; + diff_merges_set_dense_combined_if_unset(revs); setup_work_tree(); if (read_cache_preload(&revs->diffopt.pathspec) < 0) { @@ -436,7 +453,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) init_diff_ui_defaults(); git_config(git_diff_ui_config, NULL); - precompose_argv(argc, argv); + prefix = precompose_argv_prefix(argc, argv, prefix); repo_init_revisions(the_repository, &rev, prefix); @@ -474,6 +491,7 @@ int cmd_diff(int argc, const char **argv, const char *prefix) } rev.diffopt.flags.recursive = 1; + rev.diffopt.rotate_to_strict = 1; setup_diff_pager(&rev.diffopt); diff --git a/builtin/difftool.c b/builtin/difftool.c index 7ac432b881..6e18e623fd 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -342,7 +342,10 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, const char *workdir, *tmp; int ret = 0, i; FILE *fp; - struct hashmap working_tree_dups, submodules, symlinks2; + struct hashmap working_tree_dups = HASHMAP_INIT(working_tree_entry_cmp, + NULL); + struct hashmap submodules = HASHMAP_INIT(pair_cmp, NULL); + struct hashmap symlinks2 = HASHMAP_INIT(pair_cmp, NULL); struct hashmap_iter iter; struct pair_entry *entry; struct index_state wtindex; @@ -383,10 +386,6 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, rdir_len = rdir.len; wtdir_len = wtdir.len; - hashmap_init(&working_tree_dups, working_tree_entry_cmp, NULL, 0); - hashmap_init(&submodules, pair_cmp, NULL, 0); - hashmap_init(&symlinks2, pair_cmp, NULL, 0); - child.no_stdin = 1; child.git_cmd = 1; child.use_shell = 0; diff --git a/builtin/env--helper.c b/builtin/env--helper.c index 23c214fff6..27349098b0 100644 --- a/builtin/env--helper.c +++ b/builtin/env--helper.c @@ -7,18 +7,22 @@ static char const * const env__helper_usage[] = { NULL }; -static enum { +enum cmdmode { ENV_HELPER_TYPE_BOOL = 1, ENV_HELPER_TYPE_ULONG -} cmdmode = 0; +}; static int option_parse_type(const struct option *opt, const char *arg, int unset) { + enum cmdmode *cmdmode = opt->value; + + BUG_ON_OPT_NEG(unset); + if (!strcmp(arg, "bool")) - cmdmode = ENV_HELPER_TYPE_BOOL; + *cmdmode = ENV_HELPER_TYPE_BOOL; else if (!strcmp(arg, "ulong")) - cmdmode = ENV_HELPER_TYPE_ULONG; + *cmdmode = ENV_HELPER_TYPE_ULONG; else die(_("unrecognized --type argument, %s"), arg); @@ -33,6 +37,7 @@ int cmd_env__helper(int argc, const char **argv, const char *prefix) int ret; int ret_int, default_int; unsigned long ret_ulong, default_ulong; + enum cmdmode cmdmode = 0; struct option opts[] = { OPT_CALLBACK_F(0, "type", &cmdmode, N_("type"), N_("value is given this type"), PARSE_OPT_NONEG, diff --git a/builtin/fast-export.c b/builtin/fast-export.c index 1b8fca3ee0..85a76e0ef8 100644 --- a/builtin/fast-export.c +++ b/builtin/fast-export.c @@ -405,12 +405,12 @@ static char *generate_fake_oid(void *data) { static uint32_t counter = 1; /* avoid null oid */ const unsigned hashsz = the_hash_algo->rawsz; - unsigned char out[GIT_MAX_RAWSZ]; + struct object_id oid; char *hex = xmallocz(GIT_MAX_HEXSZ); - hashclr(out); - put_be32(out + hashsz - 4, counter++); - return hash_to_hex_algop_r(hex, out, the_hash_algo); + oidclr(&oid); + put_be32(oid.hash + hashsz - 4, counter++); + return oid_to_hex_r(hex, &oid); } static const char *anonymize_oid(const char *oid_hex) @@ -923,7 +923,6 @@ static struct commit *get_commit(struct rev_cmdline_entry *e, char *full_name) if (!tag) die("Tag %s points nowhere?", e->name); return (struct commit *)tag; - break; } default: return NULL; @@ -1026,7 +1025,7 @@ static void handle_tags_and_duplicates(struct string_list *extras) /* * Getting here means we have a commit which * was excluded by a negative refspec (e.g. - * fast-export ^master master). If we are + * fast-export ^HEAD HEAD). If we are * referencing excluded commits, set the ref * to the exact commit. Otherwise, the user * wants the branch exported but every commit @@ -1206,32 +1205,32 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) N_("select handling of commit messages in an alternate encoding"), parse_opt_reencode_mode), OPT_STRING(0, "export-marks", &export_filename, N_("file"), - N_("Dump marks to this file")), + N_("dump marks to this file")), OPT_STRING(0, "import-marks", &import_filename, N_("file"), - N_("Import marks from this file")), + N_("import marks from this file")), OPT_STRING(0, "import-marks-if-exists", &import_filename_if_exists, N_("file"), - N_("Import marks from this file if it exists")), + N_("import marks from this file if it exists")), OPT_BOOL(0, "fake-missing-tagger", &fake_missing_tagger, - N_("Fake a tagger when tags lack one")), + N_("fake a tagger when tags lack one")), OPT_BOOL(0, "full-tree", &full_tree, - N_("Output full tree for each commit")), + N_("output full tree for each commit")), OPT_BOOL(0, "use-done-feature", &use_done_feature, - N_("Use the done feature to terminate the stream")), - OPT_BOOL(0, "no-data", &no_data, N_("Skip output of blob data")), + N_("use the done feature to terminate the stream")), + OPT_BOOL(0, "no-data", &no_data, N_("skip output of blob data")), OPT_STRING_LIST(0, "refspec", &refspecs_list, N_("refspec"), - N_("Apply refspec to exported refs")), + N_("apply refspec to exported refs")), OPT_BOOL(0, "anonymize", &anonymize, N_("anonymize output")), OPT_CALLBACK_F(0, "anonymize-map", &anonymized_seeds, N_("from:to"), N_("convert <from> to <to> in anonymized output"), PARSE_OPT_NONEG, parse_opt_anonymize_map), OPT_BOOL(0, "reference-excluded-parents", - &reference_excluded_commits, N_("Reference parents which are not in fast-export stream by object id")), + &reference_excluded_commits, N_("reference parents which are not in fast-export stream by object id")), OPT_BOOL(0, "show-original-ids", &show_original_ids, - N_("Show original object ids of blobs/commits")), + N_("show original object ids of blobs/commits")), OPT_BOOL(0, "mark-tags", &mark_tags, - N_("Label tags with mark ids")), + N_("label tags with mark ids")), OPT_END() }; diff --git a/builtin/fast-import.c b/builtin/fast-import.c index 1bf50a73dc..dd4d09cece 100644 --- a/builtin/fast-import.c +++ b/builtin/fast-import.c @@ -150,7 +150,7 @@ struct recent_command { char *buf; }; -typedef void (*mark_set_inserter_t)(struct mark_set *s, struct object_id *oid, uintmax_t mark); +typedef void (*mark_set_inserter_t)(struct mark_set **s, struct object_id *oid, uintmax_t mark); typedef void (*each_mark_fn_t)(uintmax_t mark, void *obj, void *cbp); /* Configured limits on output */ @@ -526,13 +526,15 @@ static unsigned int hc_str(const char *s, size_t len) return r; } -static void insert_mark(struct mark_set *s, uintmax_t idnum, struct object_entry *oe) +static void insert_mark(struct mark_set **top, uintmax_t idnum, struct object_entry *oe) { + struct mark_set *s = *top; + while ((idnum >> s->shift) >= 1024) { s = mem_pool_calloc(&fi_mem_pool, 1, sizeof(struct mark_set)); - s->shift = marks->shift + 10; - s->data.sets[0] = marks; - marks = s; + s->shift = (*top)->shift + 10; + s->data.sets[0] = *top; + *top = s; } while (s->shift) { uintmax_t i = idnum >> s->shift; @@ -944,7 +946,7 @@ static int store_object( e = insert_object(&oid); if (mark) - insert_mark(marks, mark, e); + insert_mark(&marks, mark, e); if (e->idx.offset) { duplicate_count_by_type[type]++; return 1; @@ -1142,7 +1144,7 @@ static void stream_blob(uintmax_t len, struct object_id *oidout, uintmax_t mark) e = insert_object(&oid); if (mark) - insert_mark(marks, mark, e); + insert_mark(&marks, mark, e); if (e->idx.offset) { duplicate_count_by_type[OBJ_BLOB]++; @@ -1717,7 +1719,7 @@ static void dump_marks(void) } } -static void insert_object_entry(struct mark_set *s, struct object_id *oid, uintmax_t mark) +static void insert_object_entry(struct mark_set **s, struct object_id *oid, uintmax_t mark) { struct object_entry *e; e = find_object(oid); @@ -1734,12 +1736,12 @@ static void insert_object_entry(struct mark_set *s, struct object_id *oid, uintm insert_mark(s, mark, e); } -static void insert_oid_entry(struct mark_set *s, struct object_id *oid, uintmax_t mark) +static void insert_oid_entry(struct mark_set **s, struct object_id *oid, uintmax_t mark) { insert_mark(s, mark, xmemdupz(oid, sizeof(*oid))); } -static void read_mark_file(struct mark_set *s, FILE *f, mark_set_inserter_t inserter) +static void read_mark_file(struct mark_set **s, FILE *f, mark_set_inserter_t inserter) { char line[512]; while (fgets(line, sizeof(line), f)) { @@ -1772,7 +1774,7 @@ static void read_marks(void) goto done; /* Marks file does not exist */ else die_errno("cannot read '%s'", import_marks_file); - read_mark_file(marks, f, insert_object_entry); + read_mark_file(&marks, f, insert_object_entry); fclose(f); done: import_marks_file_done = 1; @@ -3228,7 +3230,7 @@ static void parse_alias(void) die(_("Expected 'to' command, got %s"), command_buf.buf); e = find_object(&b.oid); assert(e); - insert_mark(marks, next_mark, e); + insert_mark(&marks, next_mark, e); } static char* make_fast_import_path(const char *path) @@ -3321,13 +3323,14 @@ static void option_rewrite_submodules(const char *arg, struct string_list *list) *f = '\0'; f++; ms = xcalloc(1, sizeof(*ms)); - string_list_insert(list, s)->util = ms; fp = fopen(f, "r"); if (!fp) die_errno("cannot read '%s'", f); - read_mark_file(ms, fp, insert_oid_entry); + read_mark_file(&ms, fp, insert_oid_entry); fclose(fp); + + string_list_insert(list, s)->util = ms; } static int parse_one_option(const char *option) @@ -3396,7 +3399,6 @@ static int parse_one_feature(const char *feature, int from_stream) option_rewrite_submodules(arg, &sub_marks_to); } else if (skip_prefix(feature, "rewrite-submodules-from=", &arg)) { option_rewrite_submodules(arg, &sub_marks_from); - } else if (skip_prefix(feature, "rewrite-submodules-from=", &arg)) { } else if (!strcmp(feature, "get-mark")) { ; /* Don't die - this feature is supported */ } else if (!strcmp(feature, "cat-blob")) { diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 58b7c1fbdc..c2d96f4c89 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -220,7 +220,8 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) version = discover_version(&reader); switch (version) { case protocol_v2: - get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, args.stateless_rpc); + get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL, + args.stateless_rpc); break; case protocol_v1: case protocol_v0: diff --git a/builtin/fetch.c b/builtin/fetch.c index 1b00c2bee9..0b90de87c7 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -63,6 +63,7 @@ static int enable_auto_gc = 1; static int tags = TAGS_DEFAULT, unshallow, update_shallow, deepen; static int max_jobs = -1, submodule_fetch_jobs_config = -1; static int fetch_parallel_config = 1; +static int atomic_fetch; static enum transport_family family; static const char *depth; static const char *deepen_since; @@ -144,6 +145,8 @@ static struct option builtin_fetch_options[] = { N_("set upstream for git pull/fetch")), OPT_BOOL('a', "append", &append, N_("append to .git/FETCH_HEAD instead of overwriting")), + OPT_BOOL(0, "atomic", &atomic_fetch, + N_("use atomic transaction to update references")), OPT_STRING(0, "upload-pack", &upload_pack, N_("path"), N_("path to upload pack on remote end")), OPT__FORCE(&force, N_("force overwrite of local reference"), 0), @@ -200,8 +203,10 @@ static struct option builtin_fetch_options[] = { OPT_STRING_LIST(0, "negotiation-tip", &negotiation_tip, N_("revision"), N_("report that we have only objects reachable from this object")), OPT_PARSE_LIST_OBJECTS_FILTER(&filter_options), + OPT_BOOL(0, "auto-maintenance", &enable_auto_gc, + N_("run 'maintenance --auto' after fetching")), OPT_BOOL(0, "auto-gc", &enable_auto_gc, - N_("run 'gc --auto' after fetching")), + N_("run 'maintenance --auto' after fetching")), OPT_BOOL(0, "show-forced-updates", &fetch_show_forced_updates, N_("check for forced-updates on all updated branches")), OPT_BOOL(0, "write-commit-graph", &fetch_write_commit_graph, @@ -391,7 +396,7 @@ static void find_non_local_tags(const struct ref *refs, item = refname_hash_add(&remote_refs, ref->name, &ref->old_oid); string_list_insert(&remote_refs_list, ref->name); } - hashmap_free_entries(&existing_refs, struct refname_hash_entry, ent); + hashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent); /* * We may have a final lightweight tag that needs to be @@ -426,7 +431,7 @@ static void find_non_local_tags(const struct ref *refs, **tail = rm; *tail = &rm->next; } - hashmap_free_entries(&remote_refs, struct refname_hash_entry, ent); + hashmap_clear_and_free(&remote_refs, struct refname_hash_entry, ent); string_list_clear(&remote_refs_list, 0); oidset_clear(&fetch_oids); } @@ -537,6 +542,16 @@ static struct ref *get_ref_map(struct remote *remote, tail = &rm->next; } + /* + * apply negative refspecs first, before we remove duplicates. This is + * necessary as negative refspecs might remove an otherwise conflicting + * duplicate. + */ + if (rs->nr) + ref_map = apply_negative_refspecs(ref_map, rs); + else + ref_map = apply_negative_refspecs(ref_map, &remote->fetch); + ref_map = ref_remove_duplicates(ref_map); for (rm = ref_map; rm; rm = rm->next) { @@ -561,7 +576,7 @@ static struct ref *get_ref_map(struct remote *remote, } } if (existing_refs_populated) - hashmap_free_entries(&existing_refs, struct refname_hash_entry, ent); + hashmap_clear_and_free(&existing_refs, struct refname_hash_entry, ent); return ref_map; } @@ -571,13 +586,14 @@ static struct ref *get_ref_map(struct remote *remote, static int s_update_ref(const char *action, struct ref *ref, + struct ref_transaction *transaction, int check_old) { char *msg; char *rla = getenv("GIT_REFLOG_ACTION"); - struct ref_transaction *transaction; + struct ref_transaction *our_transaction = NULL; struct strbuf err = STRBUF_INIT; - int ret, df_conflict = 0; + int ret; if (dry_run) return 0; @@ -585,31 +601,47 @@ static int s_update_ref(const char *action, rla = default_rla.buf; msg = xstrfmt("%s: %s", rla, action); - transaction = ref_transaction_begin(&err); - if (!transaction || - ref_transaction_update(transaction, ref->name, - &ref->new_oid, - check_old ? &ref->old_oid : NULL, - 0, msg, &err)) - goto fail; + /* + * If no transaction was passed to us, we manage the transaction + * ourselves. Otherwise, we trust the caller to handle the transaction + * lifecycle. + */ + if (!transaction) { + transaction = our_transaction = ref_transaction_begin(&err); + if (!transaction) { + ret = STORE_REF_ERROR_OTHER; + goto out; + } + } - ret = ref_transaction_commit(transaction, &err); + ret = ref_transaction_update(transaction, ref->name, &ref->new_oid, + check_old ? &ref->old_oid : NULL, + 0, msg, &err); if (ret) { - df_conflict = (ret == TRANSACTION_NAME_CONFLICT); - goto fail; + ret = STORE_REF_ERROR_OTHER; + goto out; } - ref_transaction_free(transaction); - strbuf_release(&err); - free(msg); - return 0; -fail: - ref_transaction_free(transaction); - error("%s", err.buf); + if (our_transaction) { + switch (ref_transaction_commit(our_transaction, &err)) { + case 0: + break; + case TRANSACTION_NAME_CONFLICT: + ret = STORE_REF_ERROR_DF_CONFLICT; + goto out; + default: + ret = STORE_REF_ERROR_OTHER; + goto out; + } + } + +out: + ref_transaction_free(our_transaction); + if (ret) + error("%s", err.buf); strbuf_release(&err); free(msg); - return df_conflict ? STORE_REF_ERROR_DF_CONFLICT - : STORE_REF_ERROR_OTHER; + return ret; } static int refcol_width = 10; @@ -747,6 +779,7 @@ static void format_display(struct strbuf *display, char code, } static int update_local_ref(struct ref *ref, + struct ref_transaction *transaction, const char *remote, const struct ref *remote_ref, struct strbuf *display, @@ -787,7 +820,7 @@ static int update_local_ref(struct ref *ref, starts_with(ref->name, "refs/tags/")) { if (force || ref->force) { int r; - r = s_update_ref("updating tag", ref, 0); + r = s_update_ref("updating tag", ref, transaction, 0); format_display(display, r ? '!' : 't', _("[tag update]"), r ? _("unable to update local ref") : NULL, remote, pretty_ref, summary_width); @@ -824,7 +857,7 @@ static int update_local_ref(struct ref *ref, what = _("[new ref]"); } - r = s_update_ref(msg, ref, 0); + r = s_update_ref(msg, ref, transaction, 0); format_display(display, r ? '!' : '*', what, r ? _("unable to update local ref") : NULL, remote, pretty_ref, summary_width); @@ -846,7 +879,7 @@ static int update_local_ref(struct ref *ref, strbuf_add_unique_abbrev(&quickref, ¤t->object.oid, DEFAULT_ABBREV); strbuf_addstr(&quickref, ".."); strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV); - r = s_update_ref("fast-forward", ref, 1); + r = s_update_ref("fast-forward", ref, transaction, 1); format_display(display, r ? '!' : ' ', quickref.buf, r ? _("unable to update local ref") : NULL, remote, pretty_ref, summary_width); @@ -858,7 +891,7 @@ static int update_local_ref(struct ref *ref, strbuf_add_unique_abbrev(&quickref, ¤t->object.oid, DEFAULT_ABBREV); strbuf_addstr(&quickref, "..."); strbuf_add_unique_abbrev(&quickref, &ref->new_oid, DEFAULT_ABBREV); - r = s_update_ref("forced-update", ref, 1); + r = s_update_ref("forced-update", ref, transaction, 1); format_display(display, r ? '!' : '+', quickref.buf, r ? _("unable to update local ref") : _("forced update"), remote, pretty_ref, summary_width); @@ -885,6 +918,89 @@ static int iterate_ref_map(void *cb_data, struct object_id *oid) return 0; } +struct fetch_head { + FILE *fp; + struct strbuf buf; +}; + +static int open_fetch_head(struct fetch_head *fetch_head) +{ + const char *filename = git_path_fetch_head(the_repository); + + if (write_fetch_head) { + fetch_head->fp = fopen(filename, "a"); + if (!fetch_head->fp) + return error_errno(_("cannot open %s"), filename); + strbuf_init(&fetch_head->buf, 0); + } else { + fetch_head->fp = NULL; + } + + return 0; +} + +static void append_fetch_head(struct fetch_head *fetch_head, + const struct object_id *old_oid, + enum fetch_head_status fetch_head_status, + const char *note, + const char *url, size_t url_len) +{ + char old_oid_hex[GIT_MAX_HEXSZ + 1]; + const char *merge_status_marker; + size_t i; + + if (!fetch_head->fp) + return; + + switch (fetch_head_status) { + case FETCH_HEAD_NOT_FOR_MERGE: + merge_status_marker = "not-for-merge"; + break; + case FETCH_HEAD_MERGE: + merge_status_marker = ""; + break; + default: + /* do not write anything to FETCH_HEAD */ + return; + } + + strbuf_addf(&fetch_head->buf, "%s\t%s\t%s", + oid_to_hex_r(old_oid_hex, old_oid), merge_status_marker, note); + for (i = 0; i < url_len; ++i) + if ('\n' == url[i]) + strbuf_addstr(&fetch_head->buf, "\\n"); + else + strbuf_addch(&fetch_head->buf, url[i]); + strbuf_addch(&fetch_head->buf, '\n'); + + /* + * When using an atomic fetch, we do not want to update FETCH_HEAD if + * any of the reference updates fails. We thus have to write all + * updates to a buffer first and only commit it as soon as all + * references have been successfully updated. + */ + if (!atomic_fetch) { + strbuf_write(&fetch_head->buf, fetch_head->fp); + strbuf_reset(&fetch_head->buf); + } +} + +static void commit_fetch_head(struct fetch_head *fetch_head) +{ + if (!fetch_head->fp || !atomic_fetch) + return; + strbuf_write(&fetch_head->buf, fetch_head->fp); +} + +static void close_fetch_head(struct fetch_head *fetch_head) +{ + if (!fetch_head->fp) + return; + + fclose(fetch_head->fp); + strbuf_release(&fetch_head->buf); +} + static const char warn_show_forced_updates[] = N_("Fetch normally indicates which branches had a forced update,\n" "but that check has been disabled. To re-enable, use '--show-forced-updates'\n" @@ -897,22 +1013,20 @@ N_("It took %.2f seconds to check forced updates. You can use\n" static int store_updated_refs(const char *raw_url, const char *remote_name, int connectivity_checked, struct ref *ref_map) { - FILE *fp; + struct fetch_head fetch_head; struct commit *commit; int url_len, i, rc = 0; - struct strbuf note = STRBUF_INIT; + struct strbuf note = STRBUF_INIT, err = STRBUF_INIT; + struct ref_transaction *transaction = NULL; const char *what, *kind; struct ref *rm; char *url; - const char *filename = (!write_fetch_head - ? "/dev/null" - : git_path_fetch_head(the_repository)); int want_status; int summary_width = transport_summary_width(ref_map); - fp = fopen(filename, "a"); - if (!fp) - return error_errno(_("cannot open %s"), filename); + rc = open_fetch_head(&fetch_head); + if (rc) + return -1; if (raw_url) url = transport_anonymize_url(raw_url); @@ -929,6 +1043,14 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, } } + if (atomic_fetch) { + transaction = ref_transaction_begin(&err); + if (!transaction) { + error("%s", err.buf); + goto abort; + } + } + prepare_format_display(ref_map); /* @@ -941,7 +1063,6 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, want_status++) { for (rm = ref_map; rm; rm = rm->next) { struct ref *ref = NULL; - const char *merge_status_marker = ""; if (rm->status == REF_STATUS_REJECT_SHALLOW) { if (want_status == FETCH_HEAD_MERGE) @@ -999,31 +1120,15 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, strbuf_addf(¬e, "%s ", kind); strbuf_addf(¬e, "'%s' of ", what); } - switch (rm->fetch_head_status) { - case FETCH_HEAD_NOT_FOR_MERGE: - merge_status_marker = "not-for-merge"; - /* fall-through */ - case FETCH_HEAD_MERGE: - fprintf(fp, "%s\t%s\t%s", - oid_to_hex(&rm->old_oid), - merge_status_marker, - note.buf); - for (i = 0; i < url_len; ++i) - if ('\n' == url[i]) - fputs("\\n", fp); - else - fputc(url[i], fp); - fputc('\n', fp); - break; - default: - /* do not write anything to FETCH_HEAD */ - break; - } + + append_fetch_head(&fetch_head, &rm->old_oid, + rm->fetch_head_status, + note.buf, url, url_len); strbuf_reset(¬e); if (ref) { - rc |= update_local_ref(ref, what, rm, ¬e, - summary_width); + rc |= update_local_ref(ref, transaction, what, + rm, ¬e, summary_width); free(ref); } else if (write_fetch_head || dry_run) { /* @@ -1048,6 +1153,17 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, } } + if (!rc && transaction) { + rc = ref_transaction_commit(transaction, &err); + if (rc) { + error("%s", err.buf); + goto abort; + } + } + + if (!rc) + commit_fetch_head(&fetch_head); + if (rc & STORE_REF_ERROR_DF_CONFLICT) error(_("some local refs could not be updated; try running\n" " 'git remote prune %s' to remove any old, conflicting " @@ -1064,8 +1180,10 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, abort: strbuf_release(¬e); + strbuf_release(&err); + ref_transaction_free(transaction); free(url); - fclose(fp); + close_fetch_head(&fetch_head); return rc; } @@ -1337,7 +1455,8 @@ static int do_fetch(struct transport *transport, int autotags = (transport->remote->fetch_tags == 1); int retcode = 0; const struct ref *remote_refs; - struct strvec ref_prefixes = STRVEC_INIT; + struct transport_ls_refs_options transport_ls_refs_options = + TRANSPORT_LS_REFS_OPTIONS_INIT; int must_list_refs = 1; if (tags == TAGS_DEFAULT) { @@ -1357,7 +1476,7 @@ static int do_fetch(struct transport *transport, if (rs->nr) { int i; - refspec_ref_prefixes(rs, &ref_prefixes); + refspec_ref_prefixes(rs, &transport_ls_refs_options.ref_prefixes); /* * We can avoid listing refs if all of them are exact @@ -1371,22 +1490,25 @@ static int do_fetch(struct transport *transport, } } } else if (transport->remote && transport->remote->fetch.nr) - refspec_ref_prefixes(&transport->remote->fetch, &ref_prefixes); + refspec_ref_prefixes(&transport->remote->fetch, + &transport_ls_refs_options.ref_prefixes); if (tags == TAGS_SET || tags == TAGS_DEFAULT) { must_list_refs = 1; - if (ref_prefixes.nr) - strvec_push(&ref_prefixes, "refs/tags/"); + if (transport_ls_refs_options.ref_prefixes.nr) + strvec_push(&transport_ls_refs_options.ref_prefixes, + "refs/tags/"); } if (must_list_refs) { trace2_region_enter("fetch", "remote_refs", the_repository); - remote_refs = transport_get_remote_refs(transport, &ref_prefixes); + remote_refs = transport_get_remote_refs(transport, + &transport_ls_refs_options); trace2_region_leave("fetch", "remote_refs", the_repository); } else remote_refs = NULL; - strvec_clear(&ref_prefixes); + strvec_clear(&transport_ls_refs_options.ref_prefixes); ref_map = get_ref_map(transport->remote, remote_refs, rs, tags, &autotags); @@ -1682,7 +1804,7 @@ static inline void fetch_one_setup_partial(struct remote *remote) * If this is a partial-fetch request, we enable partial on * this repo if not already enabled and remember the given * filter-spec as the default for subsequent fetches to this - * remote. + * remote if there is currently no default filter-spec. */ if (filter_options.choice) { partial_clone_register(remote->name, &filter_options); @@ -1875,6 +1997,10 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) die(_("--filter can only be used with the remote " "configured in extensions.partialclone")); + if (atomic_fetch) + die(_("--atomic can only be used when fetching " + "from one remote")); + if (stdin_refspecs) die(_("--stdin can only be used when fetching " "from one remote")); @@ -1925,7 +2051,7 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) close_object_store(the_repository->objects); if (enable_auto_gc) - run_auto_gc(verbosity < 0); + run_auto_maintenance(verbosity < 0); return result; } diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c index 9d1ecda2b8..cb9c81a046 100644 --- a/builtin/for-each-ref.c +++ b/builtin/for-each-ref.c @@ -70,7 +70,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix) if (!sorting) sorting = ref_default_sorting(); - ref_sorting_icase_all(sorting, icase); + ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase); filter.ignore_case = icase; filter.name_patterns = argv; diff --git a/builtin/for-each-repo.c b/builtin/for-each-repo.c new file mode 100644 index 0000000000..52be64a437 --- /dev/null +++ b/builtin/for-each-repo.c @@ -0,0 +1,65 @@ +#include "cache.h" +#include "config.h" +#include "builtin.h" +#include "parse-options.h" +#include "run-command.h" +#include "string-list.h" + +static const char * const for_each_repo_usage[] = { + N_("git for-each-repo --config=<config> <command-args>"), + NULL +}; + +static int run_command_on_repo(const char *path, + void *cbdata) +{ + int i; + struct child_process child = CHILD_PROCESS_INIT; + struct strvec *args = (struct strvec *)cbdata; + + child.git_cmd = 1; + strvec_pushl(&child.args, "-C", path, NULL); + + for (i = 0; i < args->nr; i++) + strvec_push(&child.args, args->v[i]); + + return run_command(&child); +} + +int cmd_for_each_repo(int argc, const char **argv, const char *prefix) +{ + static const char *config_key = NULL; + int i, result = 0; + const struct string_list *values; + struct strvec args = STRVEC_INIT; + + const struct option options[] = { + OPT_STRING(0, "config", &config_key, N_("config"), + N_("config key storing a list of repository paths")), + OPT_END() + }; + + argc = parse_options(argc, argv, prefix, options, for_each_repo_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (!config_key) + die(_("missing --config=<config>")); + + for (i = 0; i < argc; i++) + strvec_push(&args, argv[i]); + + values = repo_config_get_value_multi(the_repository, + config_key); + + /* + * Do nothing on an empty list, which is equivalent to the case + * where the config variable does not exist at all. + */ + if (!values) + return 0; + + for (i = 0; !result && i < values->nr; i++) + result = run_command_on_repo(values->items[i].string, &args); + + return result; +} diff --git a/builtin/fsck.c b/builtin/fsck.c index fbf26cafcf..821e7798c7 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -73,25 +73,7 @@ static const char *printable_type(const struct object_id *oid, static int fsck_config(const char *var, const char *value, void *cb) { - if (strcmp(var, "fsck.skiplist") == 0) { - const char *path; - struct strbuf sb = STRBUF_INIT; - - if (git_config_pathname(&path, var, value)) - return 1; - strbuf_addf(&sb, "skiplist=%s", path); - free((char *)path); - fsck_set_msg_types(&fsck_obj_options, sb.buf); - strbuf_release(&sb); - return 0; - } - - if (skip_prefix(var, "fsck.", &var)) { - fsck_set_msg_type(&fsck_obj_options, var, value); - return 0; - } - - return git_default_config(var, value, cb); + return fsck_config_internal(var, value, cb, &fsck_obj_options); } static int objerror(struct object *obj, const char *err) diff --git a/builtin/gc.c b/builtin/gc.c index aafa0946f5..ef7226d7bc 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -28,6 +28,10 @@ #include "blob.h" #include "tree.h" #include "promisor-remote.h" +#include "refs.h" +#include "remote.h" +#include "object-store.h" +#include "exec-cmd.h" #define FAILED_RUN "failed to run %s" @@ -50,7 +54,6 @@ static const char *prune_worktrees_expire = "3.months.ago"; static unsigned long big_pack_threshold; static unsigned long max_delta_cache_size = DEFAULT_DELTA_CACHE_SIZE; -static struct strvec pack_refs_cmd = STRVEC_INIT; static struct strvec reflog = STRVEC_INIT; static struct strvec repack = STRVEC_INIT; static struct strvec prune = STRVEC_INIT; @@ -88,7 +91,7 @@ static void process_log_file(void) */ int saved_errno = errno; fprintf(stderr, _("Failed to fstat %s: %s"), - get_tempfile_path(log_lock.tempfile), + get_lock_file_path(&log_lock), strerror(saved_errno)); fflush(stderr); commit_lock_file(&log_lock); @@ -159,6 +162,15 @@ static void gc_config(void) git_config(git_default_config, NULL); } +struct maintenance_run_opts; +static int maintenance_task_pack_refs(MAYBE_UNUSED struct maintenance_run_opts *opts) +{ + struct strvec pack_refs_cmd = STRVEC_INIT; + strvec_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); + + return run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD); +} + static int too_many_loose_objects(void) { /* @@ -297,7 +309,7 @@ static uint64_t estimate_repack_memory(struct packed_git *pack) /* and then obj_hash[], underestimated in fact */ heap += sizeof(struct object *) * nr_objects; /* revindex is used also */ - heap += sizeof(struct revindex_entry) * nr_objects; + heap += (sizeof(off_t) + sizeof(uint32_t)) * nr_objects; /* * read_sha1_file() (either at delta calculation phase, or * writing phase) also fills up the delta base cache @@ -514,8 +526,8 @@ static void gc_before_repack(void) if (done++) return; - if (pack_refs && run_command_v_opt(pack_refs_cmd.v, RUN_GIT_CMD)) - die(FAILED_RUN, pack_refs_cmd.v[0]); + if (pack_refs && maintenance_task_pack_refs(NULL)) + die(FAILED_RUN, "pack-refs"); if (prune_reflogs && run_command_v_opt(reflog.v, RUN_GIT_CMD)) die(FAILED_RUN, reflog.v[0]); @@ -530,7 +542,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) const char *name; pid_t pid; int daemonized = 0; - int keep_base_pack = -1; + int keep_largest_pack = -1; timestamp_t dummy; struct option builtin_gc_options[] = { @@ -544,7 +556,7 @@ int cmd_gc(int argc, const char **argv, const char *prefix) OPT_BOOL_F(0, "force", &force, N_("force running gc even if there may be another gc running"), PARSE_OPT_NOCOMPLETE), - OPT_BOOL(0, "keep-largest-pack", &keep_base_pack, + OPT_BOOL(0, "keep-largest-pack", &keep_largest_pack, N_("repack all other packs except the largest pack")), OPT_END() }; @@ -552,7 +564,6 @@ int cmd_gc(int argc, const char **argv, const char *prefix) if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_gc_usage, builtin_gc_options); - strvec_pushl(&pack_refs_cmd, "pack-refs", "--all", "--prune", NULL); strvec_pushl(&reflog, "reflog", "expire", "--all", NULL); strvec_pushl(&repack, "repack", "-d", "-l", NULL); strvec_pushl(&prune, "prune", "--expire", NULL); @@ -621,8 +632,8 @@ int cmd_gc(int argc, const char **argv, const char *prefix) } else { struct string_list keep_pack = STRING_LIST_INIT_NODUP; - if (keep_base_pack != -1) { - if (keep_base_pack) + if (keep_largest_pack != -1) { + if (keep_largest_pack) find_base_packs(&keep_pack, 0); } else if (big_pack_threshold) { find_base_packs(&keep_pack, big_pack_threshold); @@ -699,3 +710,1330 @@ int cmd_gc(int argc, const char **argv, const char *prefix) return 0; } + +static const char *const builtin_maintenance_run_usage[] = { + N_("git maintenance run [--auto] [--[no-]quiet] [--task=<task>] [--schedule]"), + NULL +}; + +enum schedule_priority { + SCHEDULE_NONE = 0, + SCHEDULE_WEEKLY = 1, + SCHEDULE_DAILY = 2, + SCHEDULE_HOURLY = 3, +}; + +static enum schedule_priority parse_schedule(const char *value) +{ + if (!value) + return SCHEDULE_NONE; + if (!strcasecmp(value, "hourly")) + return SCHEDULE_HOURLY; + if (!strcasecmp(value, "daily")) + return SCHEDULE_DAILY; + if (!strcasecmp(value, "weekly")) + return SCHEDULE_WEEKLY; + return SCHEDULE_NONE; +} + +static int maintenance_opt_schedule(const struct option *opt, const char *arg, + int unset) +{ + enum schedule_priority *priority = opt->value; + + if (unset) + die(_("--no-schedule is not allowed")); + + *priority = parse_schedule(arg); + + if (!*priority) + die(_("unrecognized --schedule argument '%s'"), arg); + + return 0; +} + +struct maintenance_run_opts { + int auto_flag; + int quiet; + enum schedule_priority schedule; +}; + +/* Remember to update object flag allocation in object.h */ +#define SEEN (1u<<0) + +struct cg_auto_data { + int num_not_in_graph; + int limit; +}; + +static int dfs_on_ref(const char *refname, + const struct object_id *oid, int flags, + void *cb_data) +{ + struct cg_auto_data *data = (struct cg_auto_data *)cb_data; + int result = 0; + struct object_id peeled; + struct commit_list *stack = NULL; + struct commit *commit; + + if (!peel_iterated_oid(oid, &peeled)) + oid = &peeled; + if (oid_object_info(the_repository, oid, NULL) != OBJ_COMMIT) + return 0; + + commit = lookup_commit(the_repository, oid); + if (!commit) + return 0; + if (parse_commit(commit) || + commit_graph_position(commit) != COMMIT_NOT_FROM_GRAPH) + return 0; + + data->num_not_in_graph++; + + if (data->num_not_in_graph >= data->limit) + return 1; + + commit_list_append(commit, &stack); + + while (!result && stack) { + struct commit_list *parent; + + commit = pop_commit(&stack); + + for (parent = commit->parents; parent; parent = parent->next) { + if (parse_commit(parent->item) || + commit_graph_position(parent->item) != COMMIT_NOT_FROM_GRAPH || + parent->item->object.flags & SEEN) + continue; + + parent->item->object.flags |= SEEN; + data->num_not_in_graph++; + + if (data->num_not_in_graph >= data->limit) { + result = 1; + break; + } + + commit_list_append(parent->item, &stack); + } + } + + free_commit_list(stack); + return result; +} + +static int should_write_commit_graph(void) +{ + int result; + struct cg_auto_data data; + + data.num_not_in_graph = 0; + data.limit = 100; + git_config_get_int("maintenance.commit-graph.auto", + &data.limit); + + if (!data.limit) + return 0; + if (data.limit < 0) + return 1; + + result = for_each_ref(dfs_on_ref, &data); + + repo_clear_commit_marks(the_repository, SEEN); + + return result; +} + +static int run_write_commit_graph(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = 1; + strvec_pushl(&child.args, "commit-graph", "write", + "--split", "--reachable", NULL); + + if (opts->quiet) + strvec_push(&child.args, "--no-progress"); + + return !!run_command(&child); +} + +static int maintenance_task_commit_graph(struct maintenance_run_opts *opts) +{ + prepare_repo_settings(the_repository); + if (!the_repository->settings.core_commit_graph) + return 0; + + close_object_store(the_repository->objects); + if (run_write_commit_graph(opts)) { + error(_("failed to write commit-graph")); + return 1; + } + + return 0; +} + +static int fetch_remote(const char *remote, struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = 1; + strvec_pushl(&child.args, "fetch", remote, "--prune", "--no-tags", + "--no-write-fetch-head", "--recurse-submodules=no", + "--refmap=", NULL); + + if (opts->quiet) + strvec_push(&child.args, "--quiet"); + + strvec_pushf(&child.args, "+refs/heads/*:refs/prefetch/%s/*", remote); + + return !!run_command(&child); +} + +static int append_remote(struct remote *remote, void *cbdata) +{ + struct string_list *remotes = (struct string_list *)cbdata; + + string_list_append(remotes, remote->name); + return 0; +} + +static int maintenance_task_prefetch(struct maintenance_run_opts *opts) +{ + int result = 0; + struct string_list_item *item; + struct string_list remotes = STRING_LIST_INIT_DUP; + + git_config_set_multivar_gently("log.excludedecoration", + "refs/prefetch/", + "refs/prefetch/", + CONFIG_FLAGS_FIXED_VALUE | + CONFIG_FLAGS_MULTI_REPLACE); + + if (for_each_remote(append_remote, &remotes)) { + error(_("failed to fill remotes")); + result = 1; + goto cleanup; + } + + for_each_string_list_item(item, &remotes) + result |= fetch_remote(item->string, opts); + +cleanup: + string_list_clear(&remotes, 0); + return result; +} + +static int maintenance_task_gc(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = 1; + strvec_push(&child.args, "gc"); + + if (opts->auto_flag) + strvec_push(&child.args, "--auto"); + if (opts->quiet) + strvec_push(&child.args, "--quiet"); + else + strvec_push(&child.args, "--no-quiet"); + + close_object_store(the_repository->objects); + return run_command(&child); +} + +static int prune_packed(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = 1; + strvec_push(&child.args, "prune-packed"); + + if (opts->quiet) + strvec_push(&child.args, "--quiet"); + + return !!run_command(&child); +} + +struct write_loose_object_data { + FILE *in; + int count; + int batch_size; +}; + +static int loose_object_auto_limit = 100; + +static int loose_object_count(const struct object_id *oid, + const char *path, + void *data) +{ + int *count = (int*)data; + if (++(*count) >= loose_object_auto_limit) + return 1; + return 0; +} + +static int loose_object_auto_condition(void) +{ + int count = 0; + + git_config_get_int("maintenance.loose-objects.auto", + &loose_object_auto_limit); + + if (!loose_object_auto_limit) + return 0; + if (loose_object_auto_limit < 0) + return 1; + + return for_each_loose_file_in_objdir(the_repository->objects->odb->path, + loose_object_count, + NULL, NULL, &count); +} + +static int bail_on_loose(const struct object_id *oid, + const char *path, + void *data) +{ + return 1; +} + +static int write_loose_object_to_stdin(const struct object_id *oid, + const char *path, + void *data) +{ + struct write_loose_object_data *d = (struct write_loose_object_data *)data; + + fprintf(d->in, "%s\n", oid_to_hex(oid)); + + return ++(d->count) > d->batch_size; +} + +static int pack_loose(struct maintenance_run_opts *opts) +{ + struct repository *r = the_repository; + int result = 0; + struct write_loose_object_data data; + struct child_process pack_proc = CHILD_PROCESS_INIT; + + /* + * Do not start pack-objects process + * if there are no loose objects. + */ + if (!for_each_loose_file_in_objdir(r->objects->odb->path, + bail_on_loose, + NULL, NULL, NULL)) + return 0; + + pack_proc.git_cmd = 1; + + strvec_push(&pack_proc.args, "pack-objects"); + if (opts->quiet) + strvec_push(&pack_proc.args, "--quiet"); + strvec_pushf(&pack_proc.args, "%s/pack/loose", r->objects->odb->path); + + pack_proc.in = -1; + + if (start_command(&pack_proc)) { + error(_("failed to start 'git pack-objects' process")); + return 1; + } + + data.in = xfdopen(pack_proc.in, "w"); + data.count = 0; + data.batch_size = 50000; + + for_each_loose_file_in_objdir(r->objects->odb->path, + write_loose_object_to_stdin, + NULL, + NULL, + &data); + + fclose(data.in); + + if (finish_command(&pack_proc)) { + error(_("failed to finish 'git pack-objects' process")); + result = 1; + } + + return result; +} + +static int maintenance_task_loose_objects(struct maintenance_run_opts *opts) +{ + return prune_packed(opts) || pack_loose(opts); +} + +static int incremental_repack_auto_condition(void) +{ + struct packed_git *p; + int enabled; + int incremental_repack_auto_limit = 10; + int count = 0; + + if (git_config_get_bool("core.multiPackIndex", &enabled) || + !enabled) + return 0; + + git_config_get_int("maintenance.incremental-repack.auto", + &incremental_repack_auto_limit); + + if (!incremental_repack_auto_limit) + return 0; + if (incremental_repack_auto_limit < 0) + return 1; + + for (p = get_packed_git(the_repository); + count < incremental_repack_auto_limit && p; + p = p->next) { + if (!p->multi_pack_index) + count++; + } + + return count >= incremental_repack_auto_limit; +} + +static int multi_pack_index_write(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = 1; + strvec_pushl(&child.args, "multi-pack-index", "write", NULL); + + if (opts->quiet) + strvec_push(&child.args, "--no-progress"); + + if (run_command(&child)) + return error(_("failed to write multi-pack-index")); + + return 0; +} + +static int multi_pack_index_expire(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = 1; + strvec_pushl(&child.args, "multi-pack-index", "expire", NULL); + + if (opts->quiet) + strvec_push(&child.args, "--no-progress"); + + close_object_store(the_repository->objects); + + if (run_command(&child)) + return error(_("'git multi-pack-index expire' failed")); + + return 0; +} + +#define TWO_GIGABYTES (INT32_MAX) + +static off_t get_auto_pack_size(void) +{ + /* + * The "auto" value is special: we optimize for + * one large pack-file (i.e. from a clone) and + * expect the rest to be small and they can be + * repacked quickly. + * + * The strategy we select here is to select a + * size that is one more than the second largest + * pack-file. This ensures that we will repack + * at least two packs if there are three or more + * packs. + */ + off_t max_size = 0; + off_t second_largest_size = 0; + off_t result_size; + struct packed_git *p; + struct repository *r = the_repository; + + reprepare_packed_git(r); + for (p = get_all_packs(r); p; p = p->next) { + if (p->pack_size > max_size) { + second_largest_size = max_size; + max_size = p->pack_size; + } else if (p->pack_size > second_largest_size) + second_largest_size = p->pack_size; + } + + result_size = second_largest_size + 1; + + /* But limit ourselves to a batch size of 2g */ + if (result_size > TWO_GIGABYTES) + result_size = TWO_GIGABYTES; + + return result_size; +} + +static int multi_pack_index_repack(struct maintenance_run_opts *opts) +{ + struct child_process child = CHILD_PROCESS_INIT; + + child.git_cmd = 1; + strvec_pushl(&child.args, "multi-pack-index", "repack", NULL); + + if (opts->quiet) + strvec_push(&child.args, "--no-progress"); + + strvec_pushf(&child.args, "--batch-size=%"PRIuMAX, + (uintmax_t)get_auto_pack_size()); + + close_object_store(the_repository->objects); + + if (run_command(&child)) + return error(_("'git multi-pack-index repack' failed")); + + return 0; +} + +static int maintenance_task_incremental_repack(struct maintenance_run_opts *opts) +{ + prepare_repo_settings(the_repository); + if (!the_repository->settings.core_multi_pack_index) { + warning(_("skipping incremental-repack task because core.multiPackIndex is disabled")); + return 0; + } + + if (multi_pack_index_write(opts)) + return 1; + if (multi_pack_index_expire(opts)) + return 1; + if (multi_pack_index_repack(opts)) + return 1; + return 0; +} + +typedef int maintenance_task_fn(struct maintenance_run_opts *opts); + +/* + * An auto condition function returns 1 if the task should run + * and 0 if the task should NOT run. See needs_to_gc() for an + * example. + */ +typedef int maintenance_auto_fn(void); + +struct maintenance_task { + const char *name; + maintenance_task_fn *fn; + maintenance_auto_fn *auto_condition; + unsigned enabled:1; + + enum schedule_priority schedule; + + /* -1 if not selected. */ + int selected_order; +}; + +enum maintenance_task_label { + TASK_PREFETCH, + TASK_LOOSE_OBJECTS, + TASK_INCREMENTAL_REPACK, + TASK_GC, + TASK_COMMIT_GRAPH, + TASK_PACK_REFS, + + /* Leave as final value */ + TASK__COUNT +}; + +static struct maintenance_task tasks[] = { + [TASK_PREFETCH] = { + "prefetch", + maintenance_task_prefetch, + }, + [TASK_LOOSE_OBJECTS] = { + "loose-objects", + maintenance_task_loose_objects, + loose_object_auto_condition, + }, + [TASK_INCREMENTAL_REPACK] = { + "incremental-repack", + maintenance_task_incremental_repack, + incremental_repack_auto_condition, + }, + [TASK_GC] = { + "gc", + maintenance_task_gc, + need_to_gc, + 1, + }, + [TASK_COMMIT_GRAPH] = { + "commit-graph", + maintenance_task_commit_graph, + should_write_commit_graph, + }, + [TASK_PACK_REFS] = { + "pack-refs", + maintenance_task_pack_refs, + NULL, + }, +}; + +static int compare_tasks_by_selection(const void *a_, const void *b_) +{ + const struct maintenance_task *a = a_; + const struct maintenance_task *b = b_; + + return b->selected_order - a->selected_order; +} + +static int maintenance_run_tasks(struct maintenance_run_opts *opts) +{ + int i, found_selected = 0; + int result = 0; + struct lock_file lk; + struct repository *r = the_repository; + char *lock_path = xstrfmt("%s/maintenance", r->objects->odb->path); + + if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) { + /* + * Another maintenance command is running. + * + * If --auto was provided, then it is likely due to a + * recursive process stack. Do not report an error in + * that case. + */ + if (!opts->auto_flag && !opts->quiet) + warning(_("lock file '%s' exists, skipping maintenance"), + lock_path); + free(lock_path); + return 0; + } + free(lock_path); + + for (i = 0; !found_selected && i < TASK__COUNT; i++) + found_selected = tasks[i].selected_order >= 0; + + if (found_selected) + QSORT(tasks, TASK__COUNT, compare_tasks_by_selection); + + for (i = 0; i < TASK__COUNT; i++) { + if (found_selected && tasks[i].selected_order < 0) + continue; + + if (!found_selected && !tasks[i].enabled) + continue; + + if (opts->auto_flag && + (!tasks[i].auto_condition || + !tasks[i].auto_condition())) + continue; + + if (opts->schedule && tasks[i].schedule < opts->schedule) + continue; + + trace2_region_enter("maintenance", tasks[i].name, r); + if (tasks[i].fn(opts)) { + error(_("task '%s' failed"), tasks[i].name); + result = 1; + } + trace2_region_leave("maintenance", tasks[i].name, r); + } + + rollback_lock_file(&lk); + return result; +} + +static void initialize_maintenance_strategy(void) +{ + char *config_str; + + if (git_config_get_string("maintenance.strategy", &config_str)) + return; + + if (!strcasecmp(config_str, "incremental")) { + tasks[TASK_GC].schedule = SCHEDULE_NONE; + tasks[TASK_COMMIT_GRAPH].enabled = 1; + tasks[TASK_COMMIT_GRAPH].schedule = SCHEDULE_HOURLY; + tasks[TASK_PREFETCH].enabled = 1; + tasks[TASK_PREFETCH].schedule = SCHEDULE_HOURLY; + tasks[TASK_INCREMENTAL_REPACK].enabled = 1; + tasks[TASK_INCREMENTAL_REPACK].schedule = SCHEDULE_DAILY; + tasks[TASK_LOOSE_OBJECTS].enabled = 1; + tasks[TASK_LOOSE_OBJECTS].schedule = SCHEDULE_DAILY; + tasks[TASK_PACK_REFS].enabled = 1; + tasks[TASK_PACK_REFS].schedule = SCHEDULE_WEEKLY; + } +} + +static void initialize_task_config(int schedule) +{ + int i; + struct strbuf config_name = STRBUF_INIT; + gc_config(); + + if (schedule) + initialize_maintenance_strategy(); + + for (i = 0; i < TASK__COUNT; i++) { + int config_value; + char *config_str; + + strbuf_reset(&config_name); + strbuf_addf(&config_name, "maintenance.%s.enabled", + tasks[i].name); + + if (!git_config_get_bool(config_name.buf, &config_value)) + tasks[i].enabled = config_value; + + strbuf_reset(&config_name); + strbuf_addf(&config_name, "maintenance.%s.schedule", + tasks[i].name); + + if (!git_config_get_string(config_name.buf, &config_str)) { + tasks[i].schedule = parse_schedule(config_str); + free(config_str); + } + } + + strbuf_release(&config_name); +} + +static int task_option_parse(const struct option *opt, + const char *arg, int unset) +{ + int i, num_selected = 0; + struct maintenance_task *task = NULL; + + BUG_ON_OPT_NEG(unset); + + for (i = 0; i < TASK__COUNT; i++) { + if (tasks[i].selected_order >= 0) + num_selected++; + if (!strcasecmp(tasks[i].name, arg)) { + task = &tasks[i]; + } + } + + if (!task) { + error(_("'%s' is not a valid task"), arg); + return 1; + } + + if (task->selected_order >= 0) { + error(_("task '%s' cannot be selected multiple times"), arg); + return 1; + } + + task->selected_order = num_selected + 1; + + return 0; +} + +static int maintenance_run(int argc, const char **argv, const char *prefix) +{ + int i; + struct maintenance_run_opts opts; + struct option builtin_maintenance_run_options[] = { + OPT_BOOL(0, "auto", &opts.auto_flag, + N_("run tasks based on the state of the repository")), + OPT_CALLBACK(0, "schedule", &opts.schedule, N_("frequency"), + N_("run tasks based on frequency"), + maintenance_opt_schedule), + OPT_BOOL(0, "quiet", &opts.quiet, + N_("do not report progress or other information over stderr")), + OPT_CALLBACK_F(0, "task", NULL, N_("task"), + N_("run a specific task"), + PARSE_OPT_NONEG, task_option_parse), + OPT_END() + }; + memset(&opts, 0, sizeof(opts)); + + opts.quiet = !isatty(2); + + for (i = 0; i < TASK__COUNT; i++) + tasks[i].selected_order = -1; + + argc = parse_options(argc, argv, prefix, + builtin_maintenance_run_options, + builtin_maintenance_run_usage, + PARSE_OPT_STOP_AT_NON_OPTION); + + if (opts.auto_flag && opts.schedule) + die(_("use at most one of --auto and --schedule=<frequency>")); + + initialize_task_config(opts.schedule); + + if (argc != 0) + usage_with_options(builtin_maintenance_run_usage, + builtin_maintenance_run_options); + return maintenance_run_tasks(&opts); +} + +static char *get_maintpath(void) +{ + struct strbuf sb = STRBUF_INIT; + const char *p = the_repository->worktree ? + the_repository->worktree : the_repository->gitdir; + + strbuf_realpath(&sb, p, 1); + return strbuf_detach(&sb, NULL); +} + +static int maintenance_register(void) +{ + int rc; + char *config_value; + struct child_process config_set = CHILD_PROCESS_INIT; + struct child_process config_get = CHILD_PROCESS_INIT; + char *maintpath = get_maintpath(); + + /* Disable foreground maintenance */ + git_config_set("maintenance.auto", "false"); + + /* Set maintenance strategy, if unset */ + if (!git_config_get_string("maintenance.strategy", &config_value)) + free(config_value); + else + git_config_set("maintenance.strategy", "incremental"); + + config_get.git_cmd = 1; + strvec_pushl(&config_get.args, "config", "--global", "--get", + "--fixed-value", "maintenance.repo", maintpath, NULL); + config_get.out = -1; + + if (start_command(&config_get)) { + rc = error(_("failed to run 'git config'")); + goto done; + } + + /* We already have this value in our config! */ + if (!finish_command(&config_get)) { + rc = 0; + goto done; + } + + config_set.git_cmd = 1; + strvec_pushl(&config_set.args, "config", "--add", "--global", "maintenance.repo", + maintpath, NULL); + + rc = run_command(&config_set); + +done: + free(maintpath); + return rc; +} + +static int maintenance_unregister(void) +{ + int rc; + struct child_process config_unset = CHILD_PROCESS_INIT; + char *maintpath = get_maintpath(); + + config_unset.git_cmd = 1; + strvec_pushl(&config_unset.args, "config", "--global", "--unset", + "--fixed-value", "maintenance.repo", maintpath, NULL); + + rc = run_command(&config_unset); + free(maintpath); + return rc; +} + +static const char *get_frequency(enum schedule_priority schedule) +{ + switch (schedule) { + case SCHEDULE_HOURLY: + return "hourly"; + case SCHEDULE_DAILY: + return "daily"; + case SCHEDULE_WEEKLY: + return "weekly"; + default: + BUG("invalid schedule %d", schedule); + } +} + +static char *launchctl_service_name(const char *frequency) +{ + struct strbuf label = STRBUF_INIT; + strbuf_addf(&label, "org.git-scm.git.%s", frequency); + return strbuf_detach(&label, NULL); +} + +static char *launchctl_service_filename(const char *name) +{ + char *expanded; + struct strbuf filename = STRBUF_INIT; + strbuf_addf(&filename, "~/Library/LaunchAgents/%s.plist", name); + + expanded = expand_user_path(filename.buf, 1); + if (!expanded) + die(_("failed to expand path '%s'"), filename.buf); + + strbuf_release(&filename); + return expanded; +} + +static char *launchctl_get_uid(void) +{ + return xstrfmt("gui/%d", getuid()); +} + +static int launchctl_boot_plist(int enable, const char *filename, const char *cmd) +{ + int result; + struct child_process child = CHILD_PROCESS_INIT; + char *uid = launchctl_get_uid(); + + strvec_split(&child.args, cmd); + if (enable) + strvec_push(&child.args, "bootstrap"); + else + strvec_push(&child.args, "bootout"); + strvec_push(&child.args, uid); + strvec_push(&child.args, filename); + + child.no_stderr = 1; + child.no_stdout = 1; + + if (start_command(&child)) + die(_("failed to start launchctl")); + + result = finish_command(&child); + + free(uid); + return result; +} + +static int launchctl_remove_plist(enum schedule_priority schedule, const char *cmd) +{ + const char *frequency = get_frequency(schedule); + char *name = launchctl_service_name(frequency); + char *filename = launchctl_service_filename(name); + int result = launchctl_boot_plist(0, filename, cmd); + unlink(filename); + free(filename); + free(name); + return result; +} + +static int launchctl_remove_plists(const char *cmd) +{ + return launchctl_remove_plist(SCHEDULE_HOURLY, cmd) || + launchctl_remove_plist(SCHEDULE_DAILY, cmd) || + launchctl_remove_plist(SCHEDULE_WEEKLY, cmd); +} + +static int launchctl_schedule_plist(const char *exec_path, enum schedule_priority schedule, const char *cmd) +{ + FILE *plist; + int i; + const char *preamble, *repeat; + const char *frequency = get_frequency(schedule); + char *name = launchctl_service_name(frequency); + char *filename = launchctl_service_filename(name); + + if (safe_create_leading_directories(filename)) + die(_("failed to create directories for '%s'"), filename); + plist = xfopen(filename, "w"); + + preamble = "<?xml version=\"1.0\"?>\n" + "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" + "<plist version=\"1.0\">" + "<dict>\n" + "<key>Label</key><string>%s</string>\n" + "<key>ProgramArguments</key>\n" + "<array>\n" + "<string>%s/git</string>\n" + "<string>--exec-path=%s</string>\n" + "<string>for-each-repo</string>\n" + "<string>--config=maintenance.repo</string>\n" + "<string>maintenance</string>\n" + "<string>run</string>\n" + "<string>--schedule=%s</string>\n" + "</array>\n" + "<key>StartCalendarInterval</key>\n" + "<array>\n"; + fprintf(plist, preamble, name, exec_path, exec_path, frequency); + + switch (schedule) { + case SCHEDULE_HOURLY: + repeat = "<dict>\n" + "<key>Hour</key><integer>%d</integer>\n" + "<key>Minute</key><integer>0</integer>\n" + "</dict>\n"; + for (i = 1; i <= 23; i++) + fprintf(plist, repeat, i); + break; + + case SCHEDULE_DAILY: + repeat = "<dict>\n" + "<key>Day</key><integer>%d</integer>\n" + "<key>Hour</key><integer>0</integer>\n" + "<key>Minute</key><integer>0</integer>\n" + "</dict>\n"; + for (i = 1; i <= 6; i++) + fprintf(plist, repeat, i); + break; + + case SCHEDULE_WEEKLY: + fprintf(plist, + "<dict>\n" + "<key>Day</key><integer>0</integer>\n" + "<key>Hour</key><integer>0</integer>\n" + "<key>Minute</key><integer>0</integer>\n" + "</dict>\n"); + break; + + default: + /* unreachable */ + break; + } + fprintf(plist, "</array>\n</dict>\n</plist>\n"); + fclose(plist); + + /* bootout might fail if not already running, so ignore */ + launchctl_boot_plist(0, filename, cmd); + if (launchctl_boot_plist(1, filename, cmd)) + die(_("failed to bootstrap service %s"), filename); + + free(filename); + free(name); + return 0; +} + +static int launchctl_add_plists(const char *cmd) +{ + const char *exec_path = git_exec_path(); + + return launchctl_schedule_plist(exec_path, SCHEDULE_HOURLY, cmd) || + launchctl_schedule_plist(exec_path, SCHEDULE_DAILY, cmd) || + launchctl_schedule_plist(exec_path, SCHEDULE_WEEKLY, cmd); +} + +static int launchctl_update_schedule(int run_maintenance, int fd, const char *cmd) +{ + if (run_maintenance) + return launchctl_add_plists(cmd); + else + return launchctl_remove_plists(cmd); +} + +static char *schtasks_task_name(const char *frequency) +{ + struct strbuf label = STRBUF_INIT; + strbuf_addf(&label, "Git Maintenance (%s)", frequency); + return strbuf_detach(&label, NULL); +} + +static int schtasks_remove_task(enum schedule_priority schedule, const char *cmd) +{ + int result; + struct strvec args = STRVEC_INIT; + const char *frequency = get_frequency(schedule); + char *name = schtasks_task_name(frequency); + + strvec_split(&args, cmd); + strvec_pushl(&args, "/delete", "/tn", name, "/f", NULL); + + result = run_command_v_opt(args.v, 0); + + strvec_clear(&args); + free(name); + return result; +} + +static int schtasks_remove_tasks(const char *cmd) +{ + return schtasks_remove_task(SCHEDULE_HOURLY, cmd) || + schtasks_remove_task(SCHEDULE_DAILY, cmd) || + schtasks_remove_task(SCHEDULE_WEEKLY, cmd); +} + +static int schtasks_schedule_task(const char *exec_path, enum schedule_priority schedule, const char *cmd) +{ + int result; + struct child_process child = CHILD_PROCESS_INIT; + const char *xml; + struct tempfile *tfile; + const char *frequency = get_frequency(schedule); + char *name = schtasks_task_name(frequency); + struct strbuf tfilename = STRBUF_INIT; + + strbuf_addf(&tfilename, "%s/schedule_%s_XXXXXX", + get_git_common_dir(), frequency); + tfile = xmks_tempfile(tfilename.buf); + strbuf_release(&tfilename); + + if (!fdopen_tempfile(tfile, "w")) + die(_("failed to create temp xml file")); + + xml = "<?xml version=\"1.0\" ?>\n" + "<Task version=\"1.4\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\n" + "<Triggers>\n" + "<CalendarTrigger>\n"; + fputs(xml, tfile->fp); + + switch (schedule) { + case SCHEDULE_HOURLY: + fprintf(tfile->fp, + "<StartBoundary>2020-01-01T01:00:00</StartBoundary>\n" + "<Enabled>true</Enabled>\n" + "<ScheduleByDay>\n" + "<DaysInterval>1</DaysInterval>\n" + "</ScheduleByDay>\n" + "<Repetition>\n" + "<Interval>PT1H</Interval>\n" + "<Duration>PT23H</Duration>\n" + "<StopAtDurationEnd>false</StopAtDurationEnd>\n" + "</Repetition>\n"); + break; + + case SCHEDULE_DAILY: + fprintf(tfile->fp, + "<StartBoundary>2020-01-01T00:00:00</StartBoundary>\n" + "<Enabled>true</Enabled>\n" + "<ScheduleByWeek>\n" + "<DaysOfWeek>\n" + "<Monday />\n" + "<Tuesday />\n" + "<Wednesday />\n" + "<Thursday />\n" + "<Friday />\n" + "<Saturday />\n" + "</DaysOfWeek>\n" + "<WeeksInterval>1</WeeksInterval>\n" + "</ScheduleByWeek>\n"); + break; + + case SCHEDULE_WEEKLY: + fprintf(tfile->fp, + "<StartBoundary>2020-01-01T00:00:00</StartBoundary>\n" + "<Enabled>true</Enabled>\n" + "<ScheduleByWeek>\n" + "<DaysOfWeek>\n" + "<Sunday />\n" + "</DaysOfWeek>\n" + "<WeeksInterval>1</WeeksInterval>\n" + "</ScheduleByWeek>\n"); + break; + + default: + break; + } + + xml = "</CalendarTrigger>\n" + "</Triggers>\n" + "<Principals>\n" + "<Principal id=\"Author\">\n" + "<LogonType>InteractiveToken</LogonType>\n" + "<RunLevel>LeastPrivilege</RunLevel>\n" + "</Principal>\n" + "</Principals>\n" + "<Settings>\n" + "<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>\n" + "<Enabled>true</Enabled>\n" + "<Hidden>true</Hidden>\n" + "<UseUnifiedSchedulingEngine>true</UseUnifiedSchedulingEngine>\n" + "<WakeToRun>false</WakeToRun>\n" + "<ExecutionTimeLimit>PT72H</ExecutionTimeLimit>\n" + "<Priority>7</Priority>\n" + "</Settings>\n" + "<Actions Context=\"Author\">\n" + "<Exec>\n" + "<Command>\"%s\\git.exe\"</Command>\n" + "<Arguments>--exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%s</Arguments>\n" + "</Exec>\n" + "</Actions>\n" + "</Task>\n"; + fprintf(tfile->fp, xml, exec_path, exec_path, frequency); + strvec_split(&child.args, cmd); + strvec_pushl(&child.args, "/create", "/tn", name, "/f", "/xml", + get_tempfile_path(tfile), NULL); + close_tempfile_gently(tfile); + + child.no_stdout = 1; + child.no_stderr = 1; + + if (start_command(&child)) + die(_("failed to start schtasks")); + result = finish_command(&child); + + delete_tempfile(&tfile); + free(name); + return result; +} + +static int schtasks_schedule_tasks(const char *cmd) +{ + const char *exec_path = git_exec_path(); + + return schtasks_schedule_task(exec_path, SCHEDULE_HOURLY, cmd) || + schtasks_schedule_task(exec_path, SCHEDULE_DAILY, cmd) || + schtasks_schedule_task(exec_path, SCHEDULE_WEEKLY, cmd); +} + +static int schtasks_update_schedule(int run_maintenance, int fd, const char *cmd) +{ + if (run_maintenance) + return schtasks_schedule_tasks(cmd); + else + return schtasks_remove_tasks(cmd); +} + +#define BEGIN_LINE "# BEGIN GIT MAINTENANCE SCHEDULE" +#define END_LINE "# END GIT MAINTENANCE SCHEDULE" + +static int crontab_update_schedule(int run_maintenance, int fd, const char *cmd) +{ + int result = 0; + int in_old_region = 0; + struct child_process crontab_list = CHILD_PROCESS_INIT; + struct child_process crontab_edit = CHILD_PROCESS_INIT; + FILE *cron_list, *cron_in; + struct strbuf line = STRBUF_INIT; + + strvec_split(&crontab_list.args, cmd); + strvec_push(&crontab_list.args, "-l"); + crontab_list.in = -1; + crontab_list.out = dup(fd); + crontab_list.git_cmd = 0; + + if (start_command(&crontab_list)) + return error(_("failed to run 'crontab -l'; your system might not support 'cron'")); + + /* Ignore exit code, as an empty crontab will return error. */ + finish_command(&crontab_list); + + /* + * Read from the .lock file, filtering out the old + * schedule while appending the new schedule. + */ + cron_list = fdopen(fd, "r"); + rewind(cron_list); + + strvec_split(&crontab_edit.args, cmd); + crontab_edit.in = -1; + crontab_edit.git_cmd = 0; + + if (start_command(&crontab_edit)) + return error(_("failed to run 'crontab'; your system might not support 'cron'")); + + cron_in = fdopen(crontab_edit.in, "w"); + if (!cron_in) { + result = error(_("failed to open stdin of 'crontab'")); + goto done_editing; + } + + while (!strbuf_getline_lf(&line, cron_list)) { + if (!in_old_region && !strcmp(line.buf, BEGIN_LINE)) + in_old_region = 1; + else if (in_old_region && !strcmp(line.buf, END_LINE)) + in_old_region = 0; + else if (!in_old_region) + fprintf(cron_in, "%s\n", line.buf); + } + + if (run_maintenance) { + struct strbuf line_format = STRBUF_INIT; + const char *exec_path = git_exec_path(); + + fprintf(cron_in, "%s\n", BEGIN_LINE); + fprintf(cron_in, + "# The following schedule was created by Git\n"); + fprintf(cron_in, "# Any edits made in this region might be\n"); + fprintf(cron_in, + "# replaced in the future by a Git command.\n\n"); + + strbuf_addf(&line_format, + "%%s %%s * * %%s \"%s/git\" --exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%%s\n", + exec_path, exec_path); + fprintf(cron_in, line_format.buf, "0", "1-23", "*", "hourly"); + fprintf(cron_in, line_format.buf, "0", "0", "1-6", "daily"); + fprintf(cron_in, line_format.buf, "0", "0", "0", "weekly"); + strbuf_release(&line_format); + + fprintf(cron_in, "\n%s\n", END_LINE); + } + + fflush(cron_in); + fclose(cron_in); + close(crontab_edit.in); + +done_editing: + if (finish_command(&crontab_edit)) + result = error(_("'crontab' died")); + else + fclose(cron_list); + return result; +} + +#if defined(__APPLE__) +static const char platform_scheduler[] = "launchctl"; +#elif defined(GIT_WINDOWS_NATIVE) +static const char platform_scheduler[] = "schtasks"; +#else +static const char platform_scheduler[] = "crontab"; +#endif + +static int update_background_schedule(int enable) +{ + int result; + const char *scheduler = platform_scheduler; + const char *cmd = scheduler; + char *testing; + struct lock_file lk; + char *lock_path = xstrfmt("%s/schedule", the_repository->objects->odb->path); + + testing = xstrdup_or_null(getenv("GIT_TEST_MAINT_SCHEDULER")); + if (testing) { + char *sep = strchr(testing, ':'); + if (!sep) + die("GIT_TEST_MAINT_SCHEDULER unparseable: %s", testing); + *sep = '\0'; + scheduler = testing; + cmd = sep + 1; + } + + if (hold_lock_file_for_update(&lk, lock_path, LOCK_NO_DEREF) < 0) + return error(_("another process is scheduling background maintenance")); + + if (!strcmp(scheduler, "launchctl")) + result = launchctl_update_schedule(enable, get_lock_file_fd(&lk), cmd); + else if (!strcmp(scheduler, "schtasks")) + result = schtasks_update_schedule(enable, get_lock_file_fd(&lk), cmd); + else if (!strcmp(scheduler, "crontab")) + result = crontab_update_schedule(enable, get_lock_file_fd(&lk), cmd); + else + die("unknown background scheduler: %s", scheduler); + + rollback_lock_file(&lk); + free(testing); + return result; +} + +static int maintenance_start(void) +{ + if (maintenance_register()) + warning(_("failed to add repo to global config")); + + return update_background_schedule(1); +} + +static int maintenance_stop(void) +{ + return update_background_schedule(0); +} + +static const char builtin_maintenance_usage[] = N_("git maintenance <subcommand> [<options>]"); + +int cmd_maintenance(int argc, const char **argv, const char *prefix) +{ + if (argc < 2 || + (argc == 2 && !strcmp(argv[1], "-h"))) + usage(builtin_maintenance_usage); + + if (!strcmp(argv[1], "run")) + return maintenance_run(argc - 1, argv + 1, prefix); + if (!strcmp(argv[1], "start")) + return maintenance_start(); + if (!strcmp(argv[1], "stop")) + return maintenance_stop(); + if (!strcmp(argv[1], "register")) + return maintenance_register(); + if (!strcmp(argv[1], "unregister")) + return maintenance_unregister(); + + die(_("invalid subcommand: %s"), argv[1]); +} diff --git a/builtin/grep.c b/builtin/grep.c index c8037388c6..4e91a253ac 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -216,8 +216,6 @@ static void start_threads(struct grep_opt *opt) int err; struct grep_opt *o = grep_opt_dup(opt); o->output = strbuf_out; - if (i) - o->debug = 0; compile_grep_patterns(o); err = pthread_create(&threads[i], NULL, run, o); @@ -508,6 +506,10 @@ static int grep_cache(struct grep_opt *opt, for (nr = 0; nr < repo->index->cache_nr; nr++) { const struct cache_entry *ce = repo->index->cache[nr]; + + if (!cached && ce_skip_worktree(ce)) + continue; + strbuf_setlen(&name, name_base_len); strbuf_addstr(&name, ce->name); @@ -520,8 +522,7 @@ static int grep_cache(struct grep_opt *opt, * cache entry are identical, even if worktree file has * been modified, so use cache version instead */ - if (cached || (ce->ce_flags & CE_VALID) || - ce_skip_worktree(ce)) { + if (cached || (ce->ce_flags & CE_VALID)) { if (ce_stage(ce) || ce_intent_to_add(ce)) continue; hit |= grep_oid(opt, &ce->oid, name.buf, @@ -670,6 +671,17 @@ static int grep_objects(struct grep_opt *opt, const struct pathspec *pathspec, NULL, 0); obj_read_unlock(); + if (!real_obj) { + char hex[GIT_MAX_HEXSZ + 1]; + const char *name = list->objects[i].name; + + if (!name) { + oid_to_hex_r(hex, &list->objects[i].item->oid); + name = hex; + } + die(_("invalid object '%s' given."), name); + } + /* load the gitmodules file for this rev */ if (recurse_submodules) { submodule_free(opt->repo); @@ -925,9 +937,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) N_("indicate hit with exit status without output")), OPT_BOOL(0, "all-match", &opt.all_match, N_("show only matches from files that match all patterns")), - OPT_SET_INT_F(0, "debug", &opt.debug, - N_("show parse tree for grep expression"), - 1, PARSE_OPT_HIDDEN), OPT_GROUP(""), { OPTION_STRING, 'O', "open-files-in-pager", &show_in_pager, N_("pager"), N_("show matching files in the pager"), @@ -939,7 +948,6 @@ int cmd_grep(int argc, const char **argv, const char *prefix) OPT_END() }; - init_grep_defaults(the_repository); git_config(grep_cmd_config, NULL); grep_init(&opt, the_repository, prefix); @@ -1147,6 +1155,9 @@ int cmd_grep(int argc, const char **argv, const char *prefix) if (!use_index && (untracked || cached)) die(_("--cached or --untracked cannot be used with --no-index")); + if (untracked && cached) + die(_("--untracked cannot be used with --cached")); + if (!use_index || untracked) { int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude; hit = grep_directory(&opt, &pathspec, use_exclude, use_index); diff --git a/builtin/index-pack.c b/builtin/index-pack.c index 8acd078aa0..bad5748807 100644 --- a/builtin/index-pack.c +++ b/builtin/index-pack.c @@ -17,7 +17,7 @@ #include "promisor-remote.h" static const char index_pack_usage[] = -"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])"; +"git index-pack [-v] [-o <index-file>] [--keep | --keep=<msg>] [--[no-]rev-index] [--verify] [--strict] (<pack-file> | --stdin [--fix-thin] [<pack-file>])"; struct object_entry { struct pack_idx_entry idx; @@ -159,10 +159,6 @@ static pthread_mutex_t deepest_delta_mutex; #define deepest_delta_lock() lock_mutex(&deepest_delta_mutex) #define deepest_delta_unlock() unlock_mutex(&deepest_delta_mutex) -static pthread_mutex_t type_cas_mutex; -#define type_cas_lock() lock_mutex(&type_cas_mutex) -#define type_cas_unlock() unlock_mutex(&type_cas_mutex) - static pthread_key_t key; static inline void lock_mutex(pthread_mutex_t *mutex) @@ -186,7 +182,6 @@ static void init_thread(void) init_recursive_mutex(&read_mutex); pthread_mutex_init(&counter_mutex, NULL); pthread_mutex_init(&work_mutex, NULL); - pthread_mutex_init(&type_cas_mutex, NULL); if (show_stat) pthread_mutex_init(&deepest_delta_mutex, NULL); pthread_key_create(&key, NULL); @@ -209,7 +204,6 @@ static void cleanup_thread(void) pthread_mutex_destroy(&read_mutex); pthread_mutex_destroy(&counter_mutex); pthread_mutex_destroy(&work_mutex); - pthread_mutex_destroy(&type_cas_mutex); if (show_stat) pthread_mutex_destroy(&deepest_delta_mutex); for (i = 0; i < nr_threads; i++) @@ -894,18 +888,15 @@ static void sha1_object(const void *data, struct object_entry *obj_entry, } /* - * Walk from current node up - * to top parent if necessary to deflate the node. In normal - * situation, its parent node would be already deflated, so it just - * needs to apply delta. - * - * In the worst case scenario, parent node is no longer deflated because - * we're running out of delta_base_cache_limit; we need to re-deflate - * parents, possibly up to the top base. + * Ensure that this node has been reconstructed and return its contents. * - * All deflated objects here are subject to be freed if we exceed - * delta_base_cache_limit, just like in find_unresolved_deltas(), we - * just need to make sure the last node is not freed. + * In the typical and best case, this node would already be reconstructed + * (through the invocation to resolve_delta() in threaded_second_pass()) and it + * would not be pruned. However, if pruning of this node was necessary due to + * reaching delta_base_cache_limit, this function will find the closest + * ancestor with reconstructed data that has not been pruned (or if there is + * none, the ultimate base object), and reconstruct each node in the delta + * chain in order to generate the reconstructed data for this node. */ static void *get_base_data(struct base_data *c) { @@ -1028,6 +1019,10 @@ static void *threaded_second_pass(void *data) struct object_entry *child_obj; struct base_data *child; + counter_lock(); + display_progress(progress, nr_resolved_deltas); + counter_unlock(); + work_lock(); if (list_empty(&work_head)) { /* @@ -1441,15 +1436,15 @@ static void fix_unresolved_deltas(struct hashfile *f) free(sorted_by_pos); } -static const char *derive_filename(const char *pack_name, const char *suffix, - struct strbuf *buf) +static const char *derive_filename(const char *pack_name, const char *strip, + const char *suffix, struct strbuf *buf) { size_t len; - if (!strip_suffix(pack_name, ".pack", &len)) - die(_("packfile name '%s' does not end with '.pack'"), - pack_name); + if (!strip_suffix(pack_name, strip, &len) || !len || + pack_name[len - 1] != '.') + die(_("packfile name '%s' does not end with '.%s'"), + pack_name, strip); strbuf_add(buf, pack_name, len); - strbuf_addch(buf, '.'); strbuf_addstr(buf, suffix); return buf->buf; } @@ -1464,7 +1459,7 @@ static void write_special_file(const char *suffix, const char *msg, int msg_len = strlen(msg); if (pack_name) - filename = derive_filename(pack_name, suffix, &name_buf); + filename = derive_filename(pack_name, "pack", suffix, &name_buf); else filename = odb_pack_name(&name_buf, hash, suffix); @@ -1489,12 +1484,14 @@ static void write_special_file(const char *suffix, const char *msg, static void final(const char *final_pack_name, const char *curr_pack_name, const char *final_index_name, const char *curr_index_name, + const char *final_rev_index_name, const char *curr_rev_index_name, const char *keep_msg, const char *promisor_msg, unsigned char *hash) { const char *report = "pack"; struct strbuf pack_name = STRBUF_INIT; struct strbuf index_name = STRBUF_INIT; + struct strbuf rev_index_name = STRBUF_INIT; int err; if (!from_stdin) { @@ -1529,6 +1526,16 @@ static void final(const char *final_pack_name, const char *curr_pack_name, } else chmod(final_index_name, 0444); + if (curr_rev_index_name) { + if (final_rev_index_name != curr_rev_index_name) { + if (!final_rev_index_name) + final_rev_index_name = odb_pack_name(&rev_index_name, hash, "rev"); + if (finalize_object_file(curr_rev_index_name, final_rev_index_name)) + die(_("cannot store reverse index file")); + } else + chmod(final_rev_index_name, 0444); + } + if (do_fsck_object) { struct packed_git *p; p = add_packed_git(final_index_name, strlen(final_index_name), 0); @@ -1558,6 +1565,7 @@ static void final(const char *final_pack_name, const char *curr_pack_name, } } + strbuf_release(&rev_index_name); strbuf_release(&index_name); strbuf_release(&pack_name); } @@ -1583,6 +1591,12 @@ static int git_index_pack_config(const char *k, const char *v, void *cb) } return 0; } + if (!strcmp(k, "pack.writereverseindex")) { + if (git_config_bool(k, v)) + opts->flags |= WRITE_REV; + else + opts->flags &= ~WRITE_REV; + } return git_default_config(k, v, cb); } @@ -1602,7 +1616,7 @@ static void read_v2_anomalous_offsets(struct packed_git *p, /* The address of the 4-byte offset table */ idx1 = (((const uint32_t *)((const uint8_t *)p->index_data + p->crc_offset)) - + p->num_objects /* CRC32 table */ + + (size_t)p->num_objects /* CRC32 table */ ); /* The address of the 8-byte offset table */ @@ -1646,7 +1660,7 @@ static void read_idx_option(struct pack_idx_option *opts, const char *pack_name) /* * Get rid of the idx file as we do not need it anymore. * NEEDSWORK: extract this bit from free_pack_by_name() in - * sha1-file.c, perhaps? It shouldn't matter very much as we + * object-file.c, perhaps? It shouldn't matter very much as we * know we haven't installed this pack (hence we never have * read anything from it). */ @@ -1698,14 +1712,32 @@ static void show_pack_info(int stat_only) } } +static int print_dangling_gitmodules(struct fsck_options *o, + const struct object_id *oid, + enum object_type object_type, + int msg_type, const char *message) +{ + /* + * NEEDSWORK: Plumb the MSG_ID (from fsck.c) here and use it + * instead of relying on this string check. + */ + if (starts_with(message, "gitmodulesMissing")) { + printf("%s\n", oid_to_hex(oid)); + return 0; + } + return fsck_error_function(o, oid, object_type, msg_type, message); +} + int cmd_index_pack(int argc, const char **argv, const char *prefix) { - int i, fix_thin_pack = 0, verify = 0, stat_only = 0; + int i, fix_thin_pack = 0, verify = 0, stat_only = 0, rev_index; const char *curr_index; - const char *index_name = NULL, *pack_name = NULL; + const char *curr_rev_index = NULL; + const char *index_name = NULL, *pack_name = NULL, *rev_index_name = NULL; const char *keep_msg = NULL; const char *promisor_msg = NULL; struct strbuf index_name_buf = STRBUF_INIT; + struct strbuf rev_index_name_buf = STRBUF_INIT; struct pack_idx_entry **idx_objects; struct pack_idx_option opts; unsigned char pack_hash[GIT_MAX_RAWSZ]; @@ -1732,6 +1764,11 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (prefix && chdir(prefix)) die(_("Cannot come back to cwd")); + if (git_env_bool(GIT_TEST_WRITE_REV_INDEX, 0)) + rev_index = 1; + else + rev_index = !!(opts.flags & (WRITE_REV_VERIFY | WRITE_REV)); + for (i = 1; i < argc; i++) { const char *arg = argv[i]; @@ -1810,6 +1847,10 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (hash_algo == GIT_HASH_UNKNOWN) die(_("unknown hash algorithm '%s'"), arg); repo_set_hash_algo(the_repository, hash_algo); + } else if (!strcmp(arg, "--rev-index")) { + rev_index = 1; + } else if (!strcmp(arg, "--no-rev-index")) { + rev_index = 0; } else usage(index_pack_usage); continue; @@ -1829,7 +1870,16 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) if (from_stdin && hash_algo) die(_("--object-format cannot be used with --stdin")); if (!index_name && pack_name) - index_name = derive_filename(pack_name, "idx", &index_name_buf); + index_name = derive_filename(pack_name, "pack", "idx", &index_name_buf); + + opts.flags &= ~(WRITE_REV | WRITE_REV_VERIFY); + if (rev_index) { + opts.flags |= verify ? WRITE_REV_VERIFY : WRITE_REV; + if (index_name) + rev_index_name = derive_filename(index_name, + "idx", "rev", + &rev_index_name_buf); + } if (verify) { if (!index_name) @@ -1883,25 +1933,38 @@ int cmd_index_pack(int argc, const char **argv, const char *prefix) for (i = 0; i < nr_objects; i++) idx_objects[i] = &objects[i].idx; curr_index = write_idx_file(index_name, idx_objects, nr_objects, &opts, pack_hash); + if (rev_index) + curr_rev_index = write_rev_file(rev_index_name, idx_objects, + nr_objects, pack_hash, + opts.flags); free(idx_objects); if (!verify) final(pack_name, curr_pack, index_name, curr_index, + rev_index_name, curr_rev_index, keep_msg, promisor_msg, pack_hash); else close(input_fd); - if (do_fsck_object && fsck_finish(&fsck_options)) - die(_("fsck error in pack objects")); + if (do_fsck_object) { + struct fsck_options fo = fsck_options; + + fo.error_func = print_dangling_gitmodules; + if (fsck_finish(&fo)) + die(_("fsck error in pack objects")); + } free(objects); strbuf_release(&index_name_buf); + strbuf_release(&rev_index_name_buf); if (pack_name == NULL) free((void *) curr_pack); if (index_name == NULL) free((void *) curr_index); + if (rev_index_name == NULL) + free((void *) curr_rev_index); /* * Let the caller know this pack is not self contained diff --git a/builtin/init-db.c b/builtin/init-db.c index cd3e760541..dcc45bef51 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -179,7 +179,7 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree) return 1; } -void initialize_repository_version(int hash_algo) +void initialize_repository_version(int hash_algo, int reinit) { char repo_version_string[10]; int repo_version = GIT_REPO_VERSION; @@ -195,12 +195,15 @@ void initialize_repository_version(int hash_algo) if (hash_algo != GIT_HASH_SHA1) git_config_set("extensions.objectformat", hash_algos[hash_algo].name); + else if (reinit) + git_config_set_gently("extensions.objectformat", NULL); } static int create_default_files(const char *template_path, const char *original_git_dir, const char *initial_branch, - const struct repository_format *fmt) + const struct repository_format *fmt, + int quiet) { struct stat st1; struct strbuf buf = STRBUF_INIT; @@ -265,7 +268,7 @@ static int create_default_files(const char *template_path, char *ref; if (!initial_branch) - initial_branch = git_default_branch_name(); + initial_branch = git_default_branch_name(quiet); ref = xstrfmt("refs/heads/%s", initial_branch); if (check_refname_format(ref, 0) < 0) @@ -277,7 +280,7 @@ static int create_default_files(const char *template_path, free(ref); } - initialize_repository_version(fmt->hash_algo); + initialize_repository_version(fmt->hash_algo, 0); /* Check filemode trustability */ path = git_path_buf(&buf, "config"); @@ -436,7 +439,8 @@ int init_db(const char *git_dir, const char *real_git_dir, validate_hash_algorithm(&repo_fmt, hash); reinit = create_default_files(template_dir, original_git_dir, - initial_branch, &repo_fmt); + initial_branch, &repo_fmt, + flags & INIT_DB_QUIET); if (reinit && initial_branch) warning(_("re-init: ignored --initial-branch=%s"), initial_branch); diff --git a/builtin/log.c b/builtin/log.c index b8824d898f..f67b67d80e 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -12,6 +12,7 @@ #include "color.h" #include "commit.h" #include "diff.h" +#include "diff-merges.h" #include "revision.h" #include "log-tree.h" #include "builtin.h" @@ -37,6 +38,7 @@ #define MAIL_DEFAULT_WRAP 72 #define COVER_FROM_AUTO_MAX_SUBJECT_LEN 100 +#define FORMAT_PATCH_NAME_MAX_DEFAULT 64 /* Set a default date-time format for git log ("log.date" config variable) */ static const char *default_date_mode = NULL; @@ -50,6 +52,7 @@ static int decoration_style; static int decoration_given; static int use_mailmap_config = 1; static const char *fmt_patch_subject_prefix = "PATCH"; +static int fmt_patch_name_max = FORMAT_PATCH_NAME_MAX_DEFAULT; static const char *fmt_pretty; static const char * const builtin_log_usage[] = { @@ -131,7 +134,6 @@ static int log_line_range_callback(const struct option *option, const char *arg, static void init_log_defaults(void) { - init_grep_defaults(the_repository); init_diff_ui_defaults(); decoration_style = auto_decoration_style(); @@ -150,6 +152,7 @@ static void cmd_log_init_defaults(struct rev_info *rev) rev->abbrev_commit = default_abbrev_commit; rev->show_root_diff = default_show_root; rev->subject_prefix = fmt_patch_subject_prefix; + rev->patch_name_max = fmt_patch_name_max; rev->show_signature = default_show_signature; rev->encode_email_headers = default_encode_email_headers; rev->diffopt.flags.allow_textconv = 1; @@ -175,7 +178,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, const struct option builtin_log_options[] = { OPT__QUIET(&quiet, N_("suppress diff output")), OPT_BOOL(0, "source", &source, N_("show source")), - OPT_BOOL(0, "use-mailmap", &mailmap, N_("Use mail map file")), + OPT_BOOL(0, "use-mailmap", &mailmap, N_("use mail map file")), OPT_ALIAS(0, "mailmap", "use-mailmap"), OPT_STRING_LIST(0, "decorate-refs", &decorate_refs_include, N_("pattern"), N_("only decorate refs that match <pattern>")), @@ -183,8 +186,8 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, N_("pattern"), N_("do not decorate refs that match <pattern>")), OPT_CALLBACK_F(0, "decorate", NULL, NULL, N_("decorate options"), PARSE_OPT_OPTARG, decorate_callback), - OPT_CALLBACK('L', NULL, &line_cb, "n,m:file", - N_("Process line range n,m in file, counting from 1"), + OPT_CALLBACK('L', NULL, &line_cb, "range:file", + N_("trace the evolution of line range <start>,<end> or function :<funcname> in <file>"), log_line_range_callback), OPT_END() }; @@ -206,6 +209,9 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, if (argc > 1) die(_("unrecognized argument: %s"), argv[1]); + if (rev->line_level_traverse && rev->prune_data.nr) + die(_("-L<range>:<file> cannot be used with pathspec")); + memset(&w, 0, sizeof(w)); userformat_find_requirements(NULL, &w); @@ -225,7 +231,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, if (mailmap) { rev->mailmap = xcalloc(1, sizeof(struct string_list)); - read_mailmap(rev->mailmap, NULL); + read_mailmap(rev->mailmap); } if (rev->pretty_given && rev->commit_format == CMIT_FMT_RAW) { @@ -301,10 +307,11 @@ static struct itimerval early_output_timer; static void log_show_early(struct rev_info *revs, struct commit_list *list) { - int i = revs->early_output, close_file = revs->diffopt.close_file; + int i = revs->early_output; int show_header = 1; + int no_free = revs->diffopt.no_free; - revs->diffopt.close_file = 0; + revs->diffopt.no_free = 0; sort_in_topological_order(&list, revs->sort_order); while (list && i) { struct commit *commit = list->item; @@ -321,8 +328,8 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list) case commit_ignore: break; case commit_error: - if (close_file) - fclose(revs->diffopt.file); + revs->diffopt.no_free = no_free; + diff_free(&revs->diffopt); return; } list = list->next; @@ -330,8 +337,8 @@ static void log_show_early(struct rev_info *revs, struct commit_list *list) /* Did we already get enough commits for the early output? */ if (!i) { - if (close_file) - fclose(revs->diffopt.file); + revs->diffopt.no_free = 0; + diff_free(&revs->diffopt); return; } @@ -395,7 +402,7 @@ static int cmd_log_walk(struct rev_info *rev) { struct commit *commit; int saved_nrl = 0; - int saved_dcctc = 0, close_file = rev->diffopt.close_file; + int saved_dcctc = 0; if (rev->early_output) setup_early_output(); @@ -411,7 +418,7 @@ static int cmd_log_walk(struct rev_info *rev) * and HAS_CHANGES being accumulated in rev->diffopt, so be careful to * retain that state information if replacing rev->diffopt in this loop */ - rev->diffopt.close_file = 0; + rev->diffopt.no_free = 1; while ((commit = get_revision(rev)) != NULL) { if (!log_tree_commit(rev, commit) && rev->max_count >= 0) /* @@ -436,8 +443,8 @@ static int cmd_log_walk(struct rev_info *rev) } rev->diffopt.degraded_cc_to_c = saved_dcctc; rev->diffopt.needed_rename_limit = saved_nrl; - if (close_file) - fclose(rev->diffopt.file); + rev->diffopt.no_free = 0; + diff_free(&rev->diffopt); if (rev->diffopt.output_format & DIFF_FORMAT_CHECKDIFF && rev->diffopt.flags.check_failed) { @@ -454,6 +461,10 @@ static int git_log_config(const char *var, const char *value, void *cb) return git_config_string(&fmt_pretty, var, value); if (!strcmp(var, "format.subjectprefix")) return git_config_string(&fmt_patch_subject_prefix, var, value); + if (!strcmp(var, "format.filenamemaxlength")) { + fmt_patch_name_max = git_config_int(var, value); + return 0; + } if (!strcmp(var, "format.encodeemailheaders")) { default_encode_email_headers = git_config_bool(var, value); return 0; @@ -598,15 +609,10 @@ static int show_tree_object(const struct object_id *oid, static void show_setup_revisions_tweak(struct rev_info *rev, struct setup_revision_opt *opt) { - if (rev->ignore_merges < 0) { - /* There was no "-m" variant on the command line */ - rev->ignore_merges = 0; - if (!rev->first_parent_only && !rev->combine_merges) { - /* No "--first-parent", "-c", or "--cc" */ - rev->combine_merges = 1; - rev->dense_combined_merges = 1; - } - } + if (rev->first_parent_only) + diff_merges_default_to_first_parent(rev); + else + diff_merges_default_to_dense_combined(rev); if (!rev->diffopt.output_format) rev->diffopt.output_format = DIFF_FORMAT_PATCH; } @@ -727,12 +733,8 @@ static void log_setup_revisions_tweak(struct rev_info *rev, rev->prune_data.nr == 1) rev->diffopt.flags.follow_renames = 1; - /* Turn --cc/-c into -p --cc/-c when -p was not given */ - if (!rev->diffopt.output_format && rev->combine_merges) - rev->diffopt.output_format = DIFF_FORMAT_PATCH; - - if (rev->first_parent_only && rev->ignore_merges < 0) - rev->ignore_merges = 0; + if (rev->first_parent_only) + diff_merges_default_to_first_parent(rev); } int cmd_log(int argc, const char **argv, const char *prefix) @@ -805,9 +807,15 @@ enum cover_from_description { COVER_FROM_AUTO }; +enum auto_base_setting { + AUTO_BASE_NEVER, + AUTO_BASE_ALWAYS, + AUTO_BASE_WHEN_ABLE +}; + static enum thread_level thread; static int do_signoff; -static int base_auto; +static enum auto_base_setting auto_base; static char *from; static const char *signature = git_version_string; static const char *signature_file; @@ -906,7 +914,11 @@ static int git_format_config(const char *var, const char *value, void *cb) if (!strcmp(var, "format.outputdirectory")) return git_config_string(&config_output_directory, var, value); if (!strcmp(var, "format.useautobase")) { - base_auto = git_config_bool(var, value); + if (value && !strcasecmp(value, "whenAble")) { + auto_base = AUTO_BASE_WHEN_ABLE; + return 0; + } + auto_base = git_config_bool(var, value) ? AUTO_BASE_ALWAYS : AUTO_BASE_NEVER; return 0; } if (!strcmp(var, "format.from")) { @@ -945,15 +957,9 @@ static int open_next_file(struct commit *commit, const char *subject, struct rev_info *rev, int quiet) { struct strbuf filename = STRBUF_INIT; - int suffix_len = strlen(rev->patch_suffix) + 1; if (output_directory) { strbuf_addstr(&filename, output_directory); - if (filename.len >= - PATH_MAX - FORMAT_PATCH_NAME_MAX - suffix_len) { - strbuf_release(&filename); - return error(_("name of output directory is too long")); - } strbuf_complete(&filename, '/'); } @@ -1143,7 +1149,7 @@ static void get_notes_args(struct strvec *arg, struct rev_info *rev) } } -static void make_cover_letter(struct rev_info *rev, int use_stdout, +static void make_cover_letter(struct rev_info *rev, int use_separate_file, struct commit *origin, int nr, struct commit **list, const char *branch_name, @@ -1163,7 +1169,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, committer = git_committer_info(0); - if (!use_stdout && + if (use_separate_file && open_next_file(NULL, rev->numbered_files ? NULL : "cover-letter", rev, quiet)) die(_("failed to create cover-letter file")); @@ -1195,6 +1201,7 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, log.in1 = 2; log.in2 = 4; log.file = rev->diffopt.file; + log.groups = SHORTLOG_GROUP_AUTHOR; for (i = 0; i < nr; i++) shortlog_add_commit(&log, list[i]); @@ -1217,14 +1224,20 @@ static void make_cover_letter(struct rev_info *rev, int use_stdout, */ struct diff_options opts; struct strvec other_arg = STRVEC_INIT; + struct range_diff_options range_diff_opts = { + .creation_factor = rev->creation_factor, + .dual_color = 1, + .diffopt = &opts, + .other_arg = &other_arg + }; + diff_setup(&opts); opts.file = rev->diffopt.file; opts.use_color = rev->diffopt.use_color; diff_setup_done(&opts); fprintf_ln(rev->diffopt.file, "%s", rev->rdiff_title); get_notes_args(&other_arg, rev); - show_range_diff(rev->rdiff1, rev->rdiff2, - rev->creation_factor, 1, &opts, &other_arg); + show_range_diff(rev->rdiff1, rev->rdiff2, &range_diff_opts); strvec_clear(&other_arg); } } @@ -1424,6 +1437,23 @@ static int from_callback(const struct option *opt, const char *arg, int unset) return 0; } +static int base_callback(const struct option *opt, const char *arg, int unset) +{ + const char **base_commit = opt->value; + + if (unset) { + auto_base = AUTO_BASE_NEVER; + *base_commit = NULL; + } else if (!strcmp(arg, "auto")) { + auto_base = AUTO_BASE_ALWAYS; + *base_commit = NULL; + } else { + auto_base = AUTO_BASE_NEVER; + *base_commit = arg; + } + return 0; +} + struct base_tree_info { struct object_id base_commit; int nr_patch_id, alloc_patch_id; @@ -1436,13 +1466,36 @@ static struct commit *get_base_commit(const char *base_commit, { struct commit *base = NULL; struct commit **rev; - int i = 0, rev_nr = 0; + int i = 0, rev_nr = 0, auto_select, die_on_failure; + + switch (auto_base) { + case AUTO_BASE_NEVER: + if (base_commit) { + auto_select = 0; + die_on_failure = 1; + } else { + /* no base information is requested */ + return NULL; + } + break; + case AUTO_BASE_ALWAYS: + case AUTO_BASE_WHEN_ABLE: + if (base_commit) { + BUG("requested automatic base selection but a commit was provided"); + } else { + auto_select = 1; + die_on_failure = auto_base == AUTO_BASE_ALWAYS; + } + break; + default: + BUG("unexpected automatic base selection method"); + } - if (base_commit && strcmp(base_commit, "auto")) { + if (!auto_select) { base = lookup_commit_reference_by_name(base_commit); if (!base) die(_("unknown commit %s"), base_commit); - } else if ((base_commit && !strcmp(base_commit, "auto"))) { + } else { struct branch *curr_branch = branch_get(NULL); const char *upstream = branch_get_upstream(curr_branch, NULL); if (upstream) { @@ -1450,19 +1503,32 @@ static struct commit *get_base_commit(const char *base_commit, struct commit *commit; struct object_id oid; - if (get_oid(upstream, &oid)) - die(_("failed to resolve '%s' as a valid ref"), upstream); + if (get_oid(upstream, &oid)) { + if (die_on_failure) + die(_("failed to resolve '%s' as a valid ref"), upstream); + else + return NULL; + } commit = lookup_commit_or_die(&oid, "upstream base"); base_list = get_merge_bases_many(commit, total, list); /* There should be one and only one merge base. */ - if (!base_list || base_list->next) - die(_("could not find exact merge base")); + if (!base_list || base_list->next) { + if (die_on_failure) { + die(_("could not find exact merge base")); + } else { + free_commit_list(base_list); + return NULL; + } + } base = base_list->item; free_commit_list(base_list); } else { - die(_("failed to get upstream, if you want to record base commit automatically,\n" - "please use git branch --set-upstream-to to track a remote branch.\n" - "Or you could specify base commit by --base=<base-commit-id> manually")); + if (die_on_failure) + die(_("failed to get upstream, if you want to record base commit automatically,\n" + "please use git branch --set-upstream-to to track a remote branch.\n" + "Or you could specify base commit by --base=<base-commit-id> manually")); + else + return NULL; } } @@ -1479,8 +1545,14 @@ static struct commit *get_base_commit(const char *base_commit, for (i = 0; i < rev_nr / 2; i++) { struct commit_list *merge_base; merge_base = get_merge_bases(rev[2 * i], rev[2 * i + 1]); - if (!merge_base || merge_base->next) - die(_("failed to find exact merge base")); + if (!merge_base || merge_base->next) { + if (die_on_failure) { + die(_("failed to find exact merge base")); + } else { + free(rev); + return NULL; + } + } rev[i] = merge_base->item; } @@ -1490,12 +1562,24 @@ static struct commit *get_base_commit(const char *base_commit, rev_nr = DIV_ROUND_UP(rev_nr, 2); } - if (!in_merge_bases(base, rev[0])) - die(_("base commit should be the ancestor of revision list")); + if (!in_merge_bases(base, rev[0])) { + if (die_on_failure) { + die(_("base commit should be the ancestor of revision list")); + } else { + free(rev); + return NULL; + } + } for (i = 0; i < total; i++) { - if (base == list[i]) - die(_("base commit shouldn't be in revision list")); + if (base == list[i]) { + if (die_on_failure) { + die(_("base commit shouldn't be in revision list")); + } else { + free(rev); + return NULL; + } + } } free(rev); @@ -1595,7 +1679,7 @@ static void infer_range_diff_ranges(struct strbuf *r1, struct commit *head) { const char *head_oid = oid_to_hex(&head->object.oid); - int prev_is_range = !!strstr(prev, ".."); + int prev_is_range = is_range_diff_range(prev); if (prev_is_range) strbuf_addstr(r1, prev); @@ -1638,6 +1722,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) char *branch_name = NULL; char *base_commit = NULL; struct base_tree_info bases; + struct commit *base; int show_progress = 0; struct progress *progress = NULL; struct oid_array idiff_prev = OID_ARRAY_INIT; @@ -1655,7 +1740,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) OPT_CALLBACK_F('N', "no-numbered", &numbered, NULL, N_("use [PATCH] even with multiple patches"), PARSE_OPT_NOARG | PARSE_OPT_NONEG, no_numbered_callback), - OPT_BOOL('s', "signoff", &do_signoff, N_("add Signed-off-by:")), + OPT_BOOL('s', "signoff", &do_signoff, N_("add a Signed-off-by trailer")), OPT_BOOL(0, "stdout", &use_stdout, N_("print patches to standard out")), OPT_BOOL(0, "cover-letter", &cover_letter, @@ -1668,14 +1753,16 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) N_("start numbering patches at <n> instead of 1")), OPT_INTEGER('v', "reroll-count", &reroll_count, N_("mark the series as Nth re-roll")), + OPT_INTEGER(0, "filename-max-length", &fmt_patch_name_max, + N_("max length of output filename")), OPT_CALLBACK_F(0, "rfc", &rev, NULL, - N_("Use [RFC PATCH] instead of [PATCH]"), + 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")), OPT_CALLBACK_F(0, "subject-prefix", &rev, N_("prefix"), - N_("Use [<prefix>] instead of [PATCH]"), + N_("use [<prefix>] instead of [PATCH]"), PARSE_OPT_NONEG, subject_prefix_callback), OPT_CALLBACK_F('o', "output-directory", &output_directory, N_("dir"), N_("store resulting files in <dir>"), @@ -1714,8 +1801,9 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, thread_callback), OPT_STRING(0, "signature", &signature, N_("signature"), N_("add a signature")), - OPT_STRING(0, "base", &base_commit, N_("base-commit"), - N_("add prerequisite tree info to the patch series")), + OPT_CALLBACK_F(0, "base", &base_commit, N_("base-commit"), + N_("add prerequisite tree info to the patch series"), + 0, base_callback), OPT_FILENAME(0, "signature-file", &signature_file, N_("add a signature from a file")), OPT__QUIET(&quiet, N_("don't print the patch filenames")), @@ -1752,9 +1840,6 @@ 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; @@ -1770,6 +1855,10 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_ARGV0 | PARSE_OPT_KEEP_UNKNOWN | PARSE_OPT_KEEP_DASHDASH); + /* Make sure "0000-$sub.patch" gives non-negative length for $sub */ + if (fmt_patch_name_max <= strlen("0000-") + strlen(fmt_patch_suffix)) + fmt_patch_name_max = strlen("0000-") + strlen(fmt_patch_suffix); + if (cover_from_description_arg) cover_from_description_mode = parse_cover_from_description(cover_from_description_arg); @@ -1854,6 +1943,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) rev.diffopt.output_format |= DIFF_FORMAT_PATCH; rev.zero_commit = zero_commit; + rev.patch_name_max = fmt_patch_name_max; if (!rev.diffopt.flags.text && !no_binary_diff) rev.diffopt.flags.binary = 1; @@ -1861,20 +1951,27 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (rev.show_notes) load_display_notes(&rev.notes_opt); - if (!output_directory && !use_stdout) - output_directory = config_output_directory; + if (use_stdout + rev.diffopt.close_file + !!output_directory > 1) + die(_("--stdout, --output, and --output-directory are mutually exclusive")); - if (!use_stdout) - output_directory = set_outdir(prefix, output_directory); - else + if (use_stdout) { setup_pager(); - - if (output_directory) { + } else if (rev.diffopt.close_file) { + /* + * The diff code parsed --output; it has already opened the + * file, but but we must instruct it not to close after each + * diff. + */ + rev.diffopt.no_free = 1; + } else { int saved; + + if (!output_directory) + output_directory = config_output_directory; + output_directory = set_outdir(prefix, output_directory); + if (rev.diffopt.use_color != GIT_COLOR_ALWAYS) rev.diffopt.use_color = GIT_COLOR_NEVER; - if (use_stdout) - die(_("standard output, or directory, which one?")); /* * We consider <outdir> as 'outside of gitdir', therefore avoid * applying adjust_shared_perm in s-c-l-d. @@ -2018,8 +2115,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) } memset(&bases, 0, sizeof(bases)); - if (base_commit) { - struct commit *base = get_base_commit(base_commit, list, nr); + base = get_base_commit(base_commit, list, nr); + if (base) { reset_revision_walk(); clear_object_flags(UNINTERESTING); prepare_bases(&bases, base, list, nr); @@ -2036,7 +2133,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) if (cover_letter) { if (thread) gen_message_id(&rev, "cover"); - make_cover_letter(&rev, use_stdout, + make_cover_letter(&rev, !!output_directory, origin, nr, list, branch_name, quiet); print_bases(&bases, rev.diffopt.file); print_signature(rev.diffopt.file); @@ -2091,7 +2188,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) gen_message_id(&rev, oid_to_hex(&commit->object.oid)); } - if (!use_stdout && + if (output_directory && open_next_file(rev.numbered_files ? NULL : commit, NULL, &rev, quiet)) die(_("failed to create output files")); shown = log_tree_commit(&rev, commit); @@ -2104,7 +2201,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) * the log; when using one file per patch, we do * not want the extra blank line. */ - if (!use_stdout) + if (output_directory) rev.shown_one = 0; if (shown) { print_bases(&bases, rev.diffopt.file); @@ -2115,7 +2212,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) else print_signature(rev.diffopt.file); } - if (!use_stdout) + if (output_directory) fclose(rev.diffopt.file); } stop_progress(&progress); diff --git a/builtin/ls-files.c b/builtin/ls-files.c index c8eae899b8..f6f9e483b2 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -35,6 +35,7 @@ static int line_terminator = '\n'; static int debug_mode; static int show_eol; static int recurse_submodules; +static int skipping_duplicates; static const char *prefix; static int max_prefix_len; @@ -312,45 +313,59 @@ static void show_files(struct repository *repo, struct dir_struct *dir) if (show_killed) show_killed_files(repo->index, dir); } - if (show_cached || show_stage) { - for (i = 0; i < repo->index->cache_nr; i++) { - const struct cache_entry *ce = repo->index->cache[i]; - construct_fullname(&fullname, repo, ce); + if (!(show_cached || show_stage || show_deleted || show_modified)) + return; + for (i = 0; i < repo->index->cache_nr; i++) { + const struct cache_entry *ce = repo->index->cache[i]; + struct stat st; + int stat_err; - if ((dir->flags & DIR_SHOW_IGNORED) && - !ce_excluded(dir, repo->index, fullname.buf, ce)) - continue; - if (show_unmerged && !ce_stage(ce)) - continue; - if (ce->ce_flags & CE_UPDATE) - continue; + construct_fullname(&fullname, repo, ce); + + if ((dir->flags & DIR_SHOW_IGNORED) && + !ce_excluded(dir, repo->index, fullname.buf, ce)) + continue; + if (ce->ce_flags & CE_UPDATE) + continue; + if ((show_cached || show_stage) && + (!show_unmerged || ce_stage(ce))) { show_ce(repo, dir, ce, fullname.buf, ce_stage(ce) ? tag_unmerged : (ce_skip_worktree(ce) ? tag_skip_worktree : tag_cached)); + if (skipping_duplicates) + goto skip_to_next_name; } - } - if (show_deleted || show_modified) { - for (i = 0; i < repo->index->cache_nr; i++) { - const struct cache_entry *ce = repo->index->cache[i]; - struct stat st; - int err; - construct_fullname(&fullname, repo, ce); - - if ((dir->flags & DIR_SHOW_IGNORED) && - !ce_excluded(dir, repo->index, fullname.buf, ce)) - continue; - if (ce->ce_flags & CE_UPDATE) - continue; - if (ce_skip_worktree(ce)) - continue; - err = lstat(fullname.buf, &st); - if (show_deleted && err) - show_ce(repo, dir, ce, fullname.buf, tag_removed); - if (show_modified && ie_modified(repo->index, ce, &st, 0)) - show_ce(repo, dir, ce, fullname.buf, tag_modified); + if (!(show_deleted || show_modified)) + continue; + if (ce_skip_worktree(ce)) + continue; + stat_err = lstat(fullname.buf, &st); + if (stat_err && (errno != ENOENT && errno != ENOTDIR)) + error_errno("cannot lstat '%s'", fullname.buf); + if (stat_err && show_deleted) { + show_ce(repo, dir, ce, fullname.buf, tag_removed); + if (skipping_duplicates) + goto skip_to_next_name; + } + if (show_modified && + (stat_err || ie_modified(repo->index, ce, &st, 0))) { + show_ce(repo, dir, ce, fullname.buf, tag_modified); + if (skipping_duplicates) + goto skip_to_next_name; + } + continue; + +skip_to_next_name: + { + int j; + struct cache_entry **cache = repo->index->cache; + for (j = i + 1; j < repo->index->cache_nr; j++) + if (strcmp(ce->name, cache[j]->name)) + break; + i = j - 1; /* compensate for the for loop */ } } @@ -578,6 +593,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) N_("pretend that paths removed since <tree-ish> are still present")), OPT__ABBREV(&abbrev), OPT_BOOL(0, "debug", &debug_mode, N_("show debugging data")), + OPT_BOOL(0, "deduplicate", &skipping_duplicates, + N_("suppress duplicate entries")), OPT_END() }; @@ -617,6 +634,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix) * you also show the stage information. */ show_stage = 1; + if (show_tag || show_stage) + skipping_duplicates = 0; if (dir.exclude_per_dir) exc_given = 1; diff --git a/builtin/ls-remote.c b/builtin/ls-remote.c index 092917eca2..ef604752a0 100644 --- a/builtin/ls-remote.c +++ b/builtin/ls-remote.c @@ -45,7 +45,8 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) int show_symref_target = 0; const char *uploadpack = NULL; const char **pattern = NULL; - struct strvec ref_prefixes = STRVEC_INIT; + struct transport_ls_refs_options transport_options = + TRANSPORT_LS_REFS_OPTIONS_INIT; int i; struct string_list server_options = STRING_LIST_INIT_DUP; @@ -94,9 +95,9 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) } if (flags & REF_TAGS) - strvec_push(&ref_prefixes, "refs/tags/"); + strvec_push(&transport_options.ref_prefixes, "refs/tags/"); if (flags & REF_HEADS) - strvec_push(&ref_prefixes, "refs/heads/"); + strvec_push(&transport_options.ref_prefixes, "refs/heads/"); remote = remote_get(dest); if (!remote) { @@ -118,7 +119,7 @@ int cmd_ls_remote(int argc, const char **argv, const char *prefix) if (server_options.nr) transport->server_options = &server_options; - ref = transport_get_remote_refs(transport, &ref_prefixes); + ref = transport_get_remote_refs(transport, &transport_options); if (ref) { int hash_algo = hash_algo_by_ptr(transport_get_hash_algo(transport)); repo_set_hash_algo(the_repository, hash_algo); diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index e72714a5a8..de8520778d 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -109,6 +109,7 @@ static void show_diff(struct merge_list *entry) xdemitconf_t xecfg; xdemitcb_t ecb; + memset(&xpp, 0, sizeof(xpp)); xpp.flags = 0; memset(&xecfg, 0, sizeof(xecfg)); xecfg.ctxlen = 3; diff --git a/builtin/merge.c b/builtin/merge.c index 032a8f5434..eb00b273e6 100644 --- a/builtin/merge.c +++ b/builtin/merge.c @@ -14,6 +14,7 @@ #include "lockfile.h" #include "run-command.h" #include "diff.h" +#include "diff-merges.h" #include "refs.h" #include "refspec.h" #include "commit.h" @@ -28,6 +29,7 @@ #include "rerere.h" #include "help.h" #include "merge-recursive.h" +#include "merge-ort-wrappers.h" #include "resolve-undo.h" #include "remote.h" #include "fmt-merge-msg.h" @@ -88,6 +90,7 @@ static int no_verify; static struct strategy all_strategy[] = { { "recursive", DEFAULT_TWOHEAD | NO_TRIVIAL }, { "octopus", DEFAULT_OCTOPUS }, + { "ort", NO_TRIVIAL }, { "resolve", 0 }, { "ours", NO_FAST_FORWARD | NO_TRIVIAL }, { "subtree", NO_FAST_FORWARD | NO_TRIVIAL }, @@ -159,10 +162,17 @@ static struct strategy *get_strategy(const char *name) struct strategy *ret; static struct cmdnames main_cmds, other_cmds; static int loaded; + char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM"); if (!name) return NULL; + if (default_strategy && + !strcmp(default_strategy, "ort") && + !strcmp(name, "recursive")) { + name = "ort"; + } + for (i = 0; i < ARRAY_SIZE(all_strategy); i++) if (!strcmp(name, all_strategy[i].name)) return &all_strategy[i]; @@ -289,7 +299,7 @@ static struct option builtin_merge_options[] = { N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, OPT_AUTOSTASH(&autostash), OPT_BOOL(0, "overwrite-ignore", &overwrite_ignore, N_("update ignored files (default)")), - OPT_BOOL(0, "signoff", &signoff, N_("add Signed-off-by:")), + OPT_BOOL(0, "signoff", &signoff, N_("add a Signed-off-by trailer")), OPT_BOOL(0, "no-verify", &no_verify, N_("bypass pre-merge-commit and commit-msg hooks")), OPT_END() }; @@ -400,7 +410,7 @@ static void squash_message(struct commit *commit, struct commit_list *remotehead printf(_("Squash commit -- not updating HEAD\n")); repo_init_revisions(the_repository, &rev, NULL); - rev.ignore_merges = 1; + diff_merges_suppress(&rev); rev.commit_format = CMIT_FMT_MEDIUM; commit->object.flags |= UNINTERESTING; @@ -456,7 +466,7 @@ static void finish(struct commit *head_commit, * user should see them. */ close_object_store(the_repository->objects); - run_auto_gc(verbosity < 0); + run_auto_maintenance(verbosity < 0); } } if (new_head && show_diffstat) { @@ -701,7 +711,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, if (refresh_and_write_cache(REFRESH_QUIET, SKIP_IF_UNCHANGED, 0) < 0) return error(_("Unable to write index.")); - if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree")) { + if (!strcmp(strategy, "recursive") || !strcmp(strategy, "subtree") || + !strcmp(strategy, "ort")) { struct lock_file lock = LOCK_INIT; int clean, x; struct commit *result; @@ -732,8 +743,12 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common, commit_list_insert(j->item, &reversed); hold_locked_index(&lock, LOCK_DIE_ON_ERROR); - clean = merge_recursive(&o, head, - remoteheads->item, reversed, &result); + if (!strcmp(strategy, "ort")) + clean = merge_ort_recursive(&o, head, remoteheads->item, + reversed, &result); + else + clean = merge_recursive(&o, head, remoteheads->item, + reversed, &result); if (clean < 0) exit(128); if (write_locked_index(&the_index, &lock, @@ -1264,6 +1279,12 @@ int cmd_merge(int argc, const char **argv, const char *prefix) if (branch) skip_prefix(branch, "refs/heads/", &branch); + if (!pull_twohead) { + char *default_strategy = getenv("GIT_TEST_MERGE_ALGORITHM"); + if (default_strategy && !strcmp(default_strategy, "ort")) + pull_twohead = "ort"; + } + init_diff_ui_defaults(); git_config(git_merge_config, NULL); diff --git a/builtin/mktag.c b/builtin/mktag.c index 4982d3a93e..41a399a69e 100644 --- a/builtin/mktag.c +++ b/builtin/mktag.c @@ -1,179 +1,110 @@ #include "builtin.h" +#include "parse-options.h" #include "tag.h" #include "replace-object.h" #include "object-store.h" +#include "fsck.h" +#include "config.h" -/* - * A signature file has a very simple fixed format: four lines - * of "object <sha1>" + "type <typename>" + "tag <tagname>" + - * "tagger <committer>", followed by a blank line, a free-form tag - * message and a signature block that git itself doesn't care about, - * but that can be verified with gpg or similar. - * - * The first four lines are guaranteed to be at least 83 bytes: - * "object <sha1>\n" is 48 bytes, "type tag\n" at 9 bytes is the - * shortest possible type-line, "tag .\n" at 6 bytes is the shortest - * single-character-tag line, and "tagger . <> 0 +0000\n" at 20 bytes is - * the shortest possible tagger-line. - */ - -/* - * We refuse to tag something we can't verify. Just because. - */ -static int verify_object(const struct object_id *oid, const char *expected_type) +static char const * const builtin_mktag_usage[] = { + N_("git mktag"), + NULL +}; +static int option_strict = 1; + +static struct fsck_options fsck_options = FSCK_OPTIONS_STRICT; + +static int mktag_config(const char *var, const char *value, void *cb) { - int ret = -1; - enum object_type type; - unsigned long size; - void *buffer = read_object_file(oid, &type, &size); - const struct object_id *repl = lookup_replace_object(the_repository, oid); - - if (buffer) { - if (type == type_from_string(expected_type)) { - ret = check_object_signature(the_repository, repl, - buffer, size, - expected_type); + return fsck_config_internal(var, value, cb, &fsck_options); +} + +static int mktag_fsck_error_func(struct fsck_options *o, + const struct object_id *oid, + enum object_type object_type, + int msg_type, const char *message) +{ + switch (msg_type) { + case FSCK_WARN: + if (!option_strict) { + fprintf_ln(stderr, _("warning: tag input does not pass fsck: %s"), message); + return 0; + } - free(buffer); + /* fallthrough */ + case FSCK_ERROR: + /* + * We treat both warnings and errors as errors, things + * like missing "tagger" lines are "only" warnings + * under fsck, we've always considered them an error. + */ + fprintf_ln(stderr, _("error: tag input does not pass fsck: %s"), message); + return 1; + default: + BUG(_("%d (FSCK_IGNORE?) should never trigger this callback"), + msg_type); } - return ret; } -static int verify_tag(char *buffer, unsigned long size) +static int verify_object_in_tag(struct object_id *tagged_oid, int *tagged_type) { - int typelen; - char type[20]; - struct object_id oid; - const char *object, *type_line, *tag_line, *tagger_line, *lb, *rb, *p; - size_t len; - - if (size < 84) - return error("wanna fool me ? you obviously got the size wrong !"); - - buffer[size] = 0; - - /* Verify object line */ - object = buffer; - if (memcmp(object, "object ", 7)) - return error("char%d: does not start with \"object \"", 0); - - if (parse_oid_hex(object + 7, &oid, &p)) - return error("char%d: could not get SHA1 hash", 7); - - /* Verify type line */ - type_line = p + 1; - if (memcmp(type_line - 1, "\ntype ", 6)) - return error("char%d: could not find \"\\ntype \"", 47); - - /* Verify tag-line */ - tag_line = strchr(type_line, '\n'); - if (!tag_line) - return error("char%"PRIuMAX": could not find next \"\\n\"", - (uintmax_t) (type_line - buffer)); - tag_line++; - if (memcmp(tag_line, "tag ", 4) || tag_line[4] == '\n') - return error("char%"PRIuMAX": no \"tag \" found", - (uintmax_t) (tag_line - buffer)); - - /* Get the actual type */ - typelen = tag_line - type_line - strlen("type \n"); - if (typelen >= sizeof(type)) - return error("char%"PRIuMAX": type too long", - (uintmax_t) (type_line+5 - buffer)); - - memcpy(type, type_line+5, typelen); - type[typelen] = 0; - - /* Verify that the object matches */ - if (verify_object(&oid, type)) - return error("char%d: could not verify object %s", 7, oid_to_hex(&oid)); - - /* Verify the tag-name: we don't allow control characters or spaces in it */ - tag_line += 4; - for (;;) { - unsigned char c = *tag_line++; - if (c == '\n') - break; - if (c > ' ') - continue; - return error("char%"PRIuMAX": could not verify tag name", - (uintmax_t) (tag_line - buffer)); - } + int ret; + enum object_type type; + unsigned long size; + void *buffer; + const struct object_id *repl; + + buffer = read_object_file(tagged_oid, &type, &size); + if (!buffer) + die(_("could not read tagged object '%s'"), + oid_to_hex(tagged_oid)); + if (type != *tagged_type) + die(_("object '%s' tagged as '%s', but is a '%s' type"), + oid_to_hex(tagged_oid), + type_name(*tagged_type), type_name(type)); + + repl = lookup_replace_object(the_repository, tagged_oid); + ret = check_object_signature(the_repository, repl, + buffer, size, type_name(*tagged_type)); + free(buffer); - /* Verify the tagger line */ - tagger_line = tag_line; - - if (memcmp(tagger_line, "tagger ", 7)) - return error("char%"PRIuMAX": could not find \"tagger \"", - (uintmax_t) (tagger_line - buffer)); - - /* - * Check for correct form for name and email - * i.e. " <" followed by "> " on _this_ line - * No angle brackets within the name or email address fields. - * No spaces within the email address field. - */ - tagger_line += 7; - if (!(lb = strstr(tagger_line, " <")) || !(rb = strstr(lb+2, "> ")) || - strpbrk(tagger_line, "<>\n") != lb+1 || - strpbrk(lb+2, "><\n ") != rb) - return error("char%"PRIuMAX": malformed tagger field", - (uintmax_t) (tagger_line - buffer)); - - /* Check for author name, at least one character, space is acceptable */ - if (lb == tagger_line) - return error("char%"PRIuMAX": missing tagger name", - (uintmax_t) (tagger_line - buffer)); - - /* timestamp, 1 or more digits followed by space */ - tagger_line = rb + 2; - if (!(len = strspn(tagger_line, "0123456789"))) - return error("char%"PRIuMAX": missing tag timestamp", - (uintmax_t) (tagger_line - buffer)); - tagger_line += len; - if (*tagger_line != ' ') - return error("char%"PRIuMAX": malformed tag timestamp", - (uintmax_t) (tagger_line - buffer)); - tagger_line++; - - /* timezone, 5 digits [+-]hhmm, max. 1400 */ - if (!((tagger_line[0] == '+' || tagger_line[0] == '-') && - strspn(tagger_line+1, "0123456789") == 4 && - tagger_line[5] == '\n' && atoi(tagger_line+1) <= 1400)) - return error("char%"PRIuMAX": malformed tag timezone", - (uintmax_t) (tagger_line - buffer)); - tagger_line += 6; - - /* Verify the blank line separating the header from the body */ - if (*tagger_line != '\n') - return error("char%"PRIuMAX": trailing garbage in tag header", - (uintmax_t) (tagger_line - buffer)); - - /* The actual stuff afterwards we don't care about.. */ - return 0; + return ret; } int cmd_mktag(int argc, const char **argv, const char *prefix) { + static struct option builtin_mktag_options[] = { + OPT_BOOL(0, "strict", &option_strict, + N_("enable more strict checking")), + OPT_END(), + }; struct strbuf buf = STRBUF_INIT; + struct object_id tagged_oid; + int tagged_type; struct object_id result; - if (argc != 1) - usage("git mktag"); + argc = parse_options(argc, argv, NULL, + builtin_mktag_options, + builtin_mktag_usage, 0); - if (strbuf_read(&buf, 0, 4096) < 0) { - die_errno("could not read from stdin"); - } + if (strbuf_read(&buf, 0, 0) < 0) + die_errno(_("could not read from stdin")); + + fsck_options.error_func = mktag_fsck_error_func; + fsck_set_msg_type(&fsck_options, "extraheaderentry", "warn"); + /* config might set fsck.extraHeaderEntry=* again */ + git_config(mktag_config, NULL); + if (fsck_tag_standalone(NULL, buf.buf, buf.len, &fsck_options, + &tagged_oid, &tagged_type)) + die(_("tag on stdin did not pass our strict fsck check")); - /* Verify it for some basic sanity: it needs to start with - "object <sha1>\ntype\ntagger " */ - if (verify_tag(buf.buf, buf.len) < 0) - die("invalid tag signature file"); + if (verify_object_in_tag(&tagged_oid, &tagged_type)) + die(_("tag on stdin did not refer to a valid object")); if (write_object_file(buf.buf, buf.len, tag_type, &result) < 0) - die("unable to write tag file"); + die(_("unable to write tag file")); strbuf_release(&buf); - printf("%s\n", oid_to_hex(&result)); + puts(oid_to_hex(&result)); return 0; } diff --git a/builtin/name-rev.c b/builtin/name-rev.c index 725dd04519..b221d30014 100644 --- a/builtin/name-rev.c +++ b/builtin/name-rev.c @@ -7,7 +7,7 @@ #include "refs.h" #include "parse-options.h" #include "prio-queue.h" -#include "sha1-lookup.h" +#include "hash-lookup.h" #include "commit-slab.h" /* @@ -390,10 +390,10 @@ static void name_tips(void) } } -static const unsigned char *nth_tip_table_ent(size_t ix, void *table_) +static const struct object_id *nth_tip_table_ent(size_t ix, const void *table_) { - struct tip_table_entry *table = table_; - return table[ix].oid.hash; + const struct tip_table_entry *table = table_; + return &table[ix].oid; } static const char *get_exact_ref_match(const struct object *o) @@ -408,8 +408,8 @@ static const char *get_exact_ref_match(const struct object *o) tip_table.sorted = 1; } - found = sha1_pos(o->oid.hash, tip_table.table, tip_table.nr, - nth_tip_table_ent); + found = oid_pos(&o->oid, tip_table.table, tip_table.nr, + nth_tip_table_ent); if (0 <= found) return tip_table.table[found].refname; return NULL; diff --git a/builtin/notes.c b/builtin/notes.c index 2987c08a2e..08b8914d29 100644 --- a/builtin/notes.c +++ b/builtin/notes.c @@ -373,7 +373,7 @@ static int list(int argc, const char **argv, const char *prefix) git_notes_list_usage, 0); if (1 < argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_list_usage, options); } @@ -428,7 +428,7 @@ static int add(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_ARGV0); if (2 < argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_add_usage, options); } @@ -506,7 +506,7 @@ static int copy(int argc, const char **argv, const char *prefix) if (from_stdin || rewrite_cmd) { if (argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_copy_usage, options); } else { return notes_copy_from_stdin(force, rewrite_cmd); @@ -514,11 +514,11 @@ static int copy(int argc, const char **argv, const char *prefix) } if (argc < 1) { - error(_("too few parameters")); + error(_("too few arguments")); usage_with_options(git_notes_copy_usage, options); } if (2 < argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_copy_usage, options); } @@ -595,7 +595,7 @@ static int append_edit(int argc, const char **argv, const char *prefix) PARSE_OPT_KEEP_ARGV0); if (2 < argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(usage, options); } @@ -662,7 +662,7 @@ static int show(int argc, const char **argv, const char *prefix) 0); if (1 < argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_show_usage, options); } @@ -812,7 +812,7 @@ static int merge(int argc, const char **argv, const char *prefix) error(_("must specify a notes ref to merge")); usage_with_options(git_notes_merge_usage, options); } else if (!do_merge && argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_merge_usage, options); } @@ -960,7 +960,7 @@ static int prune(int argc, const char **argv, const char *prefix) 0); if (argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_prune_usage, options); } @@ -982,7 +982,7 @@ static int get_ref(int argc, const char **argv, const char *prefix) git_notes_get_ref_usage, 0); if (argc) { - error(_("too many parameters")); + error(_("too many arguments")); usage_with_options(git_notes_get_ref_usage, options); } diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index 5617c01b5a..6d62aaf59a 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -419,7 +419,7 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, { struct packed_git *p = IN_PACK(entry); struct pack_window *w_curs = NULL; - struct revindex_entry *revidx; + uint32_t pos; off_t offset; enum object_type type = oe_type(entry); off_t datalen; @@ -436,10 +436,15 @@ static off_t write_reuse_object(struct hashfile *f, struct object_entry *entry, type, entry_size); offset = entry->in_pack_offset; - revidx = find_pack_revindex(p, offset); - datalen = revidx[1].offset - offset; + if (offset_to_pack_pos(p, offset, &pos) < 0) + die(_("write_reuse_object: could not locate %s, expected at " + "offset %"PRIuMAX" in pack %s"), + oid_to_hex(&entry->idx.oid), (uintmax_t)offset, + p->pack_name); + datalen = pack_pos_to_offset(p, pos + 1) - offset; if (!pack_to_stdout && p->index_version > 1 && - check_pack_crc(p, &w_curs, offset, datalen, revidx->nr)) { + check_pack_crc(p, &w_curs, offset, datalen, + pack_pos_to_index(p, pos))) { error(_("bad packed object CRC for %s"), oid_to_hex(&entry->idx.oid)); unuse_pack(&w_curs); @@ -629,7 +634,7 @@ static int mark_tagged(const char *path, const struct object_id *oid, int flag, if (entry) entry->tagged = 1; - if (!peel_ref(path, &peeled)) { + if (!peel_iterated_oid(oid, &peeled)) { entry = packlist_find(&to_pack, &peeled); if (entry) entry->tagged = 1; @@ -863,8 +868,8 @@ static void write_reused_pack_one(size_t pos, struct hashfile *out, enum object_type type; unsigned long size; - offset = reuse_packfile->revindex[pos].offset; - next = reuse_packfile->revindex[pos + 1].offset; + offset = pack_pos_to_offset(reuse_packfile, pos); + next = pack_pos_to_offset(reuse_packfile, pos + 1); record_reused_object(offset, offset - hashfile_total(out)); @@ -884,11 +889,17 @@ static void write_reused_pack_one(size_t pos, struct hashfile *out, /* Convert to REF_DELTA if we must... */ if (!allow_ofs_delta) { - int base_pos = find_revindex_position(reuse_packfile, base_offset); + uint32_t base_pos; struct object_id base_oid; + if (offset_to_pack_pos(reuse_packfile, base_offset, &base_pos) < 0) + die(_("expected object at offset %"PRIuMAX" " + "in pack %s"), + (uintmax_t)base_offset, + reuse_packfile->pack_name); + nth_packed_object_id(&base_oid, reuse_packfile, - reuse_packfile->revindex[base_pos].nr); + pack_pos_to_index(reuse_packfile, base_pos)); len = encode_in_pack_object_header(header, sizeof(header), OBJ_REF_DELTA, size); @@ -941,7 +952,7 @@ static size_t write_reused_pack_verbatim(struct hashfile *out, off_t to_write; written = (pos * BITS_IN_EWORD); - to_write = reuse_packfile->revindex[written].offset + to_write = pack_pos_to_offset(reuse_packfile, written) - sizeof(struct pack_header); /* We're recording one chunk, not one object. */ @@ -1104,7 +1115,6 @@ static void write_pack_file(void) stop_progress(&progress_state); bitmap_writer_show_progress(progress); - bitmap_writer_reuse_bitmaps(&to_pack); bitmap_writer_select_commits(indexed_commits, indexed_commits_nr, -1); bitmap_writer_build(&to_pack); bitmap_writer_finish(written_list, nr_written, @@ -1807,11 +1817,11 @@ static void check_object(struct object_entry *entry, uint32_t object_index) goto give_up; } if (reuse_delta && !entry->preferred_base) { - struct revindex_entry *revidx; - revidx = find_pack_revindex(p, ofs); - if (!revidx) + uint32_t pos; + if (offset_to_pack_pos(p, ofs, &pos) < 0) goto give_up; - if (!nth_packed_object_id(&base_ref, p, revidx->nr)) + if (!nth_packed_object_id(&base_ref, p, + pack_pos_to_index(p, pos))) have_base = 1; } entry->in_pack_header_size = used + used_0; @@ -2804,13 +2814,11 @@ static void add_tag_chain(const struct object_id *oid) } } -static int add_ref_tag(const char *path, const struct object_id *oid, int flag, void *cb_data) +static int add_ref_tag(const char *tag, const struct object_id *oid, int flag, void *cb_data) { struct object_id peeled; - if (starts_with(path, "refs/tags/") && /* is a tag? */ - !peel_ref(path, &peeled) && /* peelable? */ - obj_is_packed(&peeled)) /* object packed? */ + if (!peel_iterated_oid(oid, &peeled) && obj_is_packed(&peeled)) add_tag_chain(oid); return 0; } @@ -2945,6 +2953,13 @@ static int git_pack_config(const char *k, const char *v, void *cb) pack_idx_opts.version); return 0; } + if (!strcmp(k, "pack.writereverseindex")) { + if (git_config_bool(k, v)) + pack_idx_opts.flags |= WRITE_REV; + else + pack_idx_opts.flags &= ~WRITE_REV; + return 0; + } if (!strcmp(k, "uploadpack.blobpackfileuri")) { struct configured_exclusion *ex = xmalloc(sizeof(*ex)); const char *oid_end, *pack_end; @@ -3584,6 +3599,8 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) reset_pack_idx_option(&pack_idx_opts); git_config(git_pack_config, NULL); + if (git_env_bool(GIT_TEST_WRITE_REV_INDEX, 0)) + pack_idx_opts.flags |= WRITE_REV; progress = isatty(2); argc = parse_options(argc, argv, prefix, pack_objects_options, @@ -3741,7 +3758,7 @@ int cmd_pack_objects(int argc, const char **argv, const char *prefix) } cleanup_preferred_base(); if (include_tag && nr_result) - for_each_ref(add_ref_tag, NULL); + for_each_tag_ref(add_ref_tag, NULL); stop_progress(&progress_state); trace2_region_leave("pack-objects", "enumerate-objects", the_repository); diff --git a/builtin/pack-redundant.c b/builtin/pack-redundant.c index 178e3409b7..6e115a811a 100644 --- a/builtin/pack-redundant.c +++ b/builtin/pack-redundant.c @@ -236,7 +236,7 @@ static struct pack_list * pack_list_difference(const struct pack_list *A, static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) { - unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step; + size_t p1_off = 0, p2_off = 0, p1_step, p2_step; const unsigned char *p1_base, *p2_base; struct llist_item *p1_hint = NULL, *p2_hint = NULL; const unsigned int hashsz = the_hash_algo->rawsz; @@ -280,7 +280,7 @@ static void cmp_two_packs(struct pack_list *p1, struct pack_list *p2) static size_t sizeof_union(struct packed_git *p1, struct packed_git *p2) { size_t ret = 0; - unsigned long p1_off = 0, p2_off = 0, p1_step, p2_step; + size_t p1_off = 0, p2_off = 0, p1_step, p2_step; const unsigned char *p1_base, *p2_base; const unsigned int hashsz = the_hash_algo->rawsz; @@ -473,6 +473,12 @@ static void cmp_local_packs(void) { struct pack_list *subset, *pl = local_packs; + /* only one packfile */ + if (!pl->next) { + llist_init(&pl->unique_objects); + return; + } + while ((subset = pl)) { while ((subset = subset->next)) cmp_two_packs(pl, subset); @@ -499,7 +505,7 @@ static void scan_alt_odb_packs(void) static struct pack_list * add_pack(struct packed_git *p) { struct pack_list l; - unsigned long off = 0, step; + size_t off = 0, step; const unsigned char *base; if (!p->pack_local && !(alt_odb || verbose)) @@ -554,6 +560,7 @@ static void load_all(void) int cmd_pack_redundant(int argc, const char **argv, const char *prefix) { int i; + int i_still_use_this = 0; struct pack_list *min = NULL, *red, *pl; struct llist *ignore; struct object_id *oid; @@ -580,12 +587,24 @@ int cmd_pack_redundant(int argc, const char **argv, const char *prefix) alt_odb = 1; continue; } + if (!strcmp(arg, "--i-still-use-this")) { + i_still_use_this = 1; + continue; + } if (*arg == '-') usage(pack_redundant_usage); else break; } + if (!i_still_use_this) { + fputs(_("'git pack-redundant' is nominated for removal.\n" + "If you still use this command, please add an extra\n" + "option, '--i-still-use-this', on the command line\n" + "and let us know you still use it by sending an e-mail\n" + "to <git@vger.kernel.org>. Thanks.\n"), stderr); + } + if (load_all_packs) load_all(); else diff --git a/builtin/pull.c b/builtin/pull.c index 015f6ded0b..e8927fc2ff 100644 --- a/builtin/pull.c +++ b/builtin/pull.c @@ -142,7 +142,7 @@ static struct option pull_options[] = { N_("add (at most <n>) entries from shortlog to merge commit message"), PARSE_OPT_OPTARG), OPT_PASSTHRU(0, "signoff", &opt_signoff, NULL, - N_("add Signed-off-by:"), + N_("add a Signed-off-by trailer"), PARSE_OPT_OPTARG), OPT_PASSTHRU(0, "squash", &opt_squash, NULL, N_("create a single commit instead of doing a merge"), @@ -324,7 +324,7 @@ static const char *config_get_ff(void) * looks for the value of "pull.rebase". If both configuration keys do not * exist, returns REBASE_FALSE. */ -static enum rebase_type config_get_rebase(void) +static enum rebase_type config_get_rebase(int *rebase_unspecified) { struct branch *curr_branch = branch_get("HEAD"); const char *value; @@ -344,21 +344,7 @@ static enum rebase_type config_get_rebase(void) if (!git_config_get_value("pull.rebase", &value)) return parse_config_rebase("pull.rebase", value, 1); - if (opt_verbosity >= 0 && - (!opt_ff || strcmp(opt_ff, "--ff-only"))) { - warning(_("Pulling without specifying how to reconcile divergent branches is\n" - "discouraged. You can squelch this message by running one of the following\n" - "commands sometime before your next pull:\n" - "\n" - " git config pull.rebase false # merge (the default strategy)\n" - " git config pull.rebase true # rebase\n" - " git config pull.ff only # fast-forward only\n" - "\n" - "You can replace \"git config\" with \"git config --global\" to set a default\n" - "preference for all repositories. You can also pass --rebase, --no-rebase,\n" - "or --ff-only on the command line to override the configured default per\n" - "invocation.\n")); - } + *rebase_unspecified = 1; return REBASE_FALSE; } @@ -853,21 +839,42 @@ static int get_octopus_merge_base(struct object_id *merge_base, /** * Given the current HEAD oid, the merge head returned from git-fetch and the - * fork point calculated by get_rebase_fork_point(), runs git-rebase with the - * appropriate arguments and returns its exit status. + * fork point calculated by get_rebase_fork_point(), compute the <newbase> and + * <upstream> arguments to use for the upcoming git-rebase invocation. */ -static int run_rebase(const struct object_id *curr_head, +static int get_rebase_newbase_and_upstream(struct object_id *newbase, + struct object_id *upstream, + const struct object_id *curr_head, const struct object_id *merge_head, const struct object_id *fork_point) { - int ret; struct object_id oct_merge_base; - struct strvec args = STRVEC_INIT; if (!get_octopus_merge_base(&oct_merge_base, curr_head, merge_head, fork_point)) if (!is_null_oid(fork_point) && oideq(&oct_merge_base, fork_point)) fork_point = NULL; + if (fork_point && !is_null_oid(fork_point)) + oidcpy(upstream, fork_point); + else + oidcpy(upstream, merge_head); + + oidcpy(newbase, merge_head); + + return 0; +} + +/** + * Given the <newbase> and <upstream> calculated by + * get_rebase_newbase_and_upstream(), runs git-rebase with the + * appropriate arguments and returns its exit status. + */ +static int run_rebase(const struct object_id *newbase, + const struct object_id *upstream) +{ + int ret; + struct strvec args = STRVEC_INIT; + strvec_push(&args, "rebase"); /* Shared options */ @@ -895,18 +902,45 @@ static int run_rebase(const struct object_id *curr_head, warning(_("ignoring --verify-signatures for rebase")); strvec_push(&args, "--onto"); - strvec_push(&args, oid_to_hex(merge_head)); + strvec_push(&args, oid_to_hex(newbase)); - if (fork_point && !is_null_oid(fork_point)) - strvec_push(&args, oid_to_hex(fork_point)); - else - strvec_push(&args, oid_to_hex(merge_head)); + strvec_push(&args, oid_to_hex(upstream)); ret = run_command_v_opt(args.v, RUN_GIT_CMD); strvec_clear(&args); return ret; } +static int get_can_ff(struct object_id *orig_head, struct object_id *orig_merge_head) +{ + int ret; + struct commit_list *list = NULL; + struct commit *merge_head, *head; + + head = lookup_commit_reference(the_repository, orig_head); + commit_list_insert(head, &list); + merge_head = lookup_commit_reference(the_repository, orig_merge_head); + ret = repo_is_descendant_of(the_repository, merge_head, list); + free_commit_list(list); + return ret; +} + +static void show_advice_pull_non_ff(void) +{ + advise(_("Pulling without specifying how to reconcile divergent branches is\n" + "discouraged. You can squelch this message by running one of the following\n" + "commands sometime before your next pull:\n" + "\n" + " git config pull.rebase false # merge (the default strategy)\n" + " git config pull.rebase true # rebase\n" + " git config pull.ff only # fast-forward only\n" + "\n" + "You can replace \"git config\" with \"git config --global\" to set a default\n" + "preference for all repositories. You can also pass --rebase, --no-rebase,\n" + "or --ff-only on the command line to override the configured default per\n" + "invocation.\n")); +} + int cmd_pull(int argc, const char **argv, const char *prefix) { const char *repo, **refspecs; @@ -914,6 +948,8 @@ int cmd_pull(int argc, const char **argv, const char *prefix) struct object_id orig_head, curr_head; struct object_id rebase_fork_point; int autostash; + int rebase_unspecified = 0; + int can_ff; if (!getenv("GIT_REFLOG_ACTION")) set_reflog_message(argc, argv); @@ -935,7 +971,7 @@ int cmd_pull(int argc, const char **argv, const char *prefix) opt_ff = xstrdup_or_null(config_get_ff()); if (opt_rebase < 0) - opt_rebase = config_get_rebase(); + opt_rebase = config_get_rebase(&rebase_unspecified); if (read_cache_unmerged()) die_resolve_conflict("pull"); @@ -1009,33 +1045,36 @@ int cmd_pull(int argc, const char **argv, const char *prefix) if (opt_rebase && merge_heads.nr > 1) die(_("Cannot rebase onto multiple branches.")); + can_ff = get_can_ff(&orig_head, &merge_heads.oid[0]); + + if (rebase_unspecified && !opt_ff && !can_ff) { + if (opt_verbosity >= 0) + show_advice_pull_non_ff(); + } + if (opt_rebase) { int ret = 0; int ran_ff = 0; + + struct object_id newbase; + struct object_id upstream; + get_rebase_newbase_and_upstream(&newbase, &upstream, &curr_head, + merge_heads.oid, &rebase_fork_point); + if ((recurse_submodules == RECURSE_SUBMODULES_ON || recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND) && - submodule_touches_in_range(the_repository, &rebase_fork_point, &curr_head)) + submodule_touches_in_range(the_repository, &upstream, &curr_head)) die(_("cannot rebase with locally recorded submodule modifications")); if (!autostash) { - struct commit_list *list = NULL; - struct commit *merge_head, *head; - - head = lookup_commit_reference(the_repository, - &orig_head); - commit_list_insert(head, &list); - merge_head = lookup_commit_reference(the_repository, - &merge_heads.oid[0]); - if (repo_is_descendant_of(the_repository, - merge_head, list)) { + if (can_ff) { /* we can fast-forward this without invoking rebase */ opt_ff = "--ff-only"; ran_ff = 1; ret = run_merge(); } - free_commit_list(list); } if (!ran_ff) - ret = run_rebase(&curr_head, merge_heads.oid, &rebase_fork_point); + ret = run_rebase(&newbase, &upstream); if (!ret && (recurse_submodules == RECURSE_SUBMODULES_ON || recurse_submodules == RECURSE_SUBMODULES_ON_DEMAND)) diff --git a/builtin/push.c b/builtin/push.c index 0eeb2c8dd5..194967ed79 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -115,7 +115,7 @@ static void set_refspecs(const char **refs, int nr, const char *repo) else refspec_appendf(&rs, "refs/tags/%s", ref); } else if (deleterefs) { - if (strchr(ref, ':')) + if (strchr(ref, ':') || !*ref) die(_("--delete only accepts plain target ref names")); refspec_appendf(&rs, ":%s", ref); } else if (!strchr(ref, ':')) { @@ -290,6 +290,12 @@ static const char message_advice_ref_needs_force[] = "or update a remote ref to make it point at a non-commit object,\n" "without using the '--force' option.\n"); +static const char message_advice_ref_needs_update[] = + N_("Updates were rejected because the tip of the remote-tracking\n" + "branch has been updated since the last checkout. You may want\n" + "to integrate those changes locally (e.g., 'git pull ...')\n" + "before forcing an update.\n"); + static void advise_pull_before_push(void) { if (!advice_push_non_ff_current || !advice_push_update_rejected) @@ -325,6 +331,13 @@ static void advise_ref_needs_force(void) advise(_(message_advice_ref_needs_force)); } +static void advise_ref_needs_update(void) +{ + if (!advice_push_ref_needs_update || !advice_push_update_rejected) + return; + advise(_(message_advice_ref_needs_update)); +} + static int push_with_options(struct transport *transport, struct refspec *rs, int flags) { @@ -374,12 +387,14 @@ static int push_with_options(struct transport *transport, struct refspec *rs, advise_ref_fetch_first(); } else if (reject_reasons & REJECT_NEEDS_FORCE) { advise_ref_needs_force(); + } else if (reject_reasons & REJECT_REF_NEEDS_UPDATE) { + advise_ref_needs_update(); } return 1; } -static int do_push(const char *repo, int flags, +static int do_push(int flags, const struct string_list *push_options, struct remote *remote) { @@ -510,6 +525,12 @@ static int git_push_config(const char *k, const char *v, void *cb) if (!v) return config_error_nonbool(k); return color_parse(v, push_colors[slot]); + } else if (!strcmp(k, "push.useforceifincludes")) { + if (git_config_bool(k, v)) + *flags |= TRANSPORT_PUSH_FORCE_IF_INCLUDES; + else + *flags &= ~TRANSPORT_PUSH_FORCE_IF_INCLUDES; + return 0; } return git_default_config(k, v, NULL); @@ -541,6 +562,9 @@ int cmd_push(int argc, const char **argv, const char *prefix) OPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"), N_("require old value of ref to be at this value"), PARSE_OPT_OPTARG | PARSE_OPT_LITERAL_ARGHELP, parseopt_push_cas_option), + OPT_BIT(0, TRANS_OPT_FORCE_IF_INCLUDES, &flags, + N_("require remote updates to be integrated locally"), + TRANSPORT_PUSH_FORCE_IF_INCLUDES), OPT_CALLBACK(0, "recurse-submodules", &recurse_submodules, "(check|on-demand|no)", N_("control recursive pushing of submodules"), option_parse_recurse_submodules), OPT_BOOL_F( 0 , "thin", &thin, N_("use thin pack"), PARSE_OPT_NOCOMPLETE), @@ -625,11 +649,14 @@ int cmd_push(int argc, const char **argv, const char *prefix) if ((flags & TRANSPORT_PUSH_ALL) && (flags & TRANSPORT_PUSH_MIRROR)) die(_("--all and --mirror are incompatible")); + if (!is_empty_cas(&cas) && (flags & TRANSPORT_PUSH_FORCE_IF_INCLUDES)) + cas.use_force_if_includes = 1; + for_each_string_list_item(item, push_options) if (strchr(item->string, '\n')) die(_("push options must not have new line characters")); - rc = do_push(repo, flags, push_options, remote); + rc = do_push(flags, push_options, remote); string_list_clear(&push_options_cmdline, 0); string_list_clear(&push_options_config, 0); if (rc == -1) diff --git a/builtin/range-diff.c b/builtin/range-diff.c index 24c4162f74..78bc9fa770 100644 --- a/builtin/range-diff.c +++ b/builtin/range-diff.c @@ -3,6 +3,7 @@ #include "parse-options.h" #include "range-diff.h" #include "config.h" +#include "revision.h" static const char * const builtin_range_diff_usage[] = { N_("git range-diff [<options>] <old-base>..<old-tip> <new-base>..<new-tip>"), @@ -13,18 +14,27 @@ NULL int cmd_range_diff(int argc, const char **argv, const char *prefix) { - int creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT; struct diff_options diffopt = { NULL }; struct strvec other_arg = STRVEC_INIT; - int simple_color = -1; + struct range_diff_options range_diff_opts = { + .creation_factor = RANGE_DIFF_CREATION_FACTOR_DEFAULT, + .diffopt = &diffopt, + .other_arg = &other_arg + }; + int simple_color = -1, left_only = 0, right_only = 0; struct option range_diff_options[] = { - OPT_INTEGER(0, "creation-factor", &creation_factor, + OPT_INTEGER(0, "creation-factor", + &range_diff_opts.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_BOOL(0, "left-only", &left_only, + N_("only emit output related to the first range")), + OPT_BOOL(0, "right-only", &right_only, + N_("only emit output related to the second range")), OPT_END() }; struct option *options; @@ -46,12 +56,12 @@ int cmd_range_diff(int argc, const char **argv, const char *prefix) diffopt.use_color = 1; if (argc == 2) { - if (!strstr(argv[0], "..")) - die(_("no .. in range: '%s'"), argv[0]); + if (!is_range_diff_range(argv[0])) + die(_("not a commit range: '%s'"), argv[0]); strbuf_addstr(&range1, argv[0]); - if (!strstr(argv[1], "..")) - die(_("no .. in range: '%s'"), argv[1]); + if (!is_range_diff_range(argv[1])) + die(_("not a commit range: '%s'"), argv[1]); strbuf_addstr(&range2, argv[1]); } else if (argc == 3) { strbuf_addf(&range1, "%s..%s", argv[0], argv[1]); @@ -81,8 +91,10 @@ 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, &other_arg); + range_diff_opts.dual_color = simple_color < 1; + range_diff_opts.left_only = left_only; + range_diff_opts.right_only = right_only; + res = show_range_diff(range1.buf, range2.buf, &range_diff_opts); strvec_clear(&other_arg); strbuf_release(&range1); diff --git a/builtin/rebase.c b/builtin/rebase.c index c4ff2039ef..de400f9a19 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -102,6 +102,7 @@ struct rebase_options { int reschedule_failed_exec; int use_legacy_rebase; int reapply_cherry_picks; + int fork_point; }; #define REBASE_OPTIONS_INIT { \ @@ -111,7 +112,8 @@ struct rebase_options { .default_backend = "merge", \ .flags = REBASE_NO_QUIET, \ .git_am_opts = STRVEC_INIT, \ - .git_format_patch_opt = STRBUF_INIT \ + .git_format_patch_opt = STRBUF_INIT, \ + .fork_point = -1, \ } static struct replay_opts get_replay_opts(const struct rebase_options *opts) @@ -119,6 +121,7 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) struct replay_opts replay = REPLAY_OPTS_INIT; replay.action = REPLAY_INTERACTIVE_REBASE; + replay.strategy = NULL; sequencer_init_config(&replay); replay.signoff = opts->signoff; @@ -136,7 +139,12 @@ static struct replay_opts get_replay_opts(const struct rebase_options *opts) opts->committer_date_is_author_date; replay.ignore_date = opts->ignore_date; replay.gpg_sign = xstrdup_or_null(opts->gpg_sign_opt); - replay.strategy = opts->strategy; + if (opts->strategy) + replay.strategy = opts->strategy; + else if (!replay.strategy && replay.default_strategy) { + replay.strategy = replay.default_strategy; + replay.default_strategy = NULL; + } if (opts->strategy_opts) parse_strategy_opts(&replay, opts->strategy_opts); @@ -270,15 +278,14 @@ static int edit_todo_file(unsigned flags) } static int get_revision_ranges(struct commit *upstream, struct commit *onto, - struct object_id *orig_head, const char **head_hash, - char **revisions, char **shortrevisions) + struct object_id *orig_head, char **revisions, + char **shortrevisions) { struct commit *base_rev = upstream ? upstream : onto; const char *shorthead; - *head_hash = find_unique_abbrev(orig_head, GIT_MAX_HEXSZ); *revisions = xstrfmt("%s...%s", oid_to_hex(&base_rev->object.oid), - *head_hash); + oid_to_hex(orig_head)); shorthead = find_unique_abbrev(orig_head, DEFAULT_ABBREV); @@ -296,7 +303,8 @@ static int get_revision_ranges(struct commit *upstream, struct commit *onto, } static int init_basic_state(struct replay_opts *opts, const char *head_name, - struct commit *onto, const char *orig_head) + struct commit *onto, + const struct object_id *orig_head) { FILE *interactive; @@ -327,7 +335,6 @@ static void split_exec_commands(const char *cmd, struct string_list *commands) static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) { int ret; - const char *head_hash = NULL; char *revisions = NULL, *shortrevisions = NULL; struct strvec make_script_args = STRVEC_INIT; struct todo_list todo_list = TODO_LIST_INIT; @@ -335,12 +342,12 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) struct string_list commands = STRING_LIST_INIT_DUP; if (get_revision_ranges(opts->upstream, opts->onto, &opts->orig_head, - &head_hash, &revisions, &shortrevisions)) + &revisions, &shortrevisions)) return -1; if (init_basic_state(&replay, opts->head_name ? opts->head_name : "detached HEAD", - opts->onto, head_hash)) { + opts->onto, &opts->orig_head)) { free(revisions); free(shortrevisions); @@ -370,8 +377,9 @@ static int do_interactive_rebase(struct rebase_options *opts, unsigned flags) split_exec_commands(opts->cmd, &commands); ret = complete_action(the_repository, &replay, flags, - shortrevisions, opts->onto_name, opts->onto, head_hash, - &commands, opts->autosquash, &todo_list); + shortrevisions, opts->onto_name, opts->onto, + &opts->orig_head, &commands, opts->autosquash, + &todo_list); } string_list_clear(&commands, 0); @@ -734,10 +742,10 @@ static int finish_rebase(struct rebase_options *opts) apply_autostash(state_dir_path("autostash", opts)); close_object_store(the_repository->objects); /* - * We ignore errors in 'gc --auto', since the + * We ignore errors in 'git maintenance run --auto', since the * user should see them. */ - run_auto_gc(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE))); + run_auto_maintenance(!(opts->flags & (REBASE_NO_QUIET|REBASE_VERBOSE))); if (opts->type == REBASE_MERGE) { struct replay_opts replay = REPLAY_OPTS_INIT; @@ -1089,6 +1097,11 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "rebase.forkpoint")) { + opts->fork_point = git_config_bool(var, value) ? -1 : 0; + return 0; + } + if (!strcmp(var, "rebase.usebuiltin")) { opts->use_legacy_rebase = !git_config_bool(var, value); return 0; @@ -1300,7 +1313,6 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) const char *gpg_sign = NULL; struct string_list exec = STRING_LIST_INIT_NODUP; const char *rebase_merges = NULL; - int fork_point = -1; struct string_list strategy_options = STRING_LIST_INIT_NODUP; struct object_id squash_onto; char *squash_onto_name = NULL; @@ -1324,7 +1336,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("do not show diffstat of what changed upstream"), PARSE_OPT_NOARG, NULL, REBASE_DIFFSTAT }, OPT_BOOL(0, "signoff", &options.signoff, - N_("add a Signed-off-by: line to each commit")), + N_("add a Signed-off-by trailer to each commit")), OPT_BOOL(0, "committer-date-is-author-date", &options.committer_date_is_author_date, N_("make committer date match author date")), @@ -1400,7 +1412,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) N_("mode"), N_("try to rebase merges instead of skipping them"), PARSE_OPT_OPTARG, NULL, (intptr_t)""}, - OPT_BOOL(0, "fork-point", &fork_point, + OPT_BOOL(0, "fork-point", &options.fork_point, N_("use 'merge-base --fork-point' to refine upstream")), OPT_STRING('s', "strategy", &options.strategy, N_("strategy"), N_("use the given merge strategy")), @@ -1488,7 +1500,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) die(_("cannot combine '--keep-base' with '--root'")); } - if (options.root && fork_point > 0) + if (options.root && options.fork_point > 0) die(_("cannot combine '--root' with '--fork-point'")); if (action != ACTION_NONE && !in_progress) @@ -1771,6 +1783,11 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.default_backend); } + if (options.type == REBASE_MERGE && + !options.strategy && + getenv("GIT_TEST_MERGE_ALGORITHM")) + options.strategy = xstrdup(getenv("GIT_TEST_MERGE_ALGORITHM")); + switch (options.type) { case REBASE_MERGE: case REBASE_PRESERVE_MERGES: @@ -1829,8 +1846,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) NULL); if (!options.upstream_name) error_on_missing_default_upstream(); - if (fork_point < 0) - fork_point = 1; + if (options.fork_point < 0) + options.fork_point = 1; } else { options.upstream_name = argv[0]; argc--; @@ -1906,7 +1923,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) 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) && + lookup_commit_reference(the_repository, + &options.orig_head)) options.head_name = NULL; else die(_("fatal: no such branch/commit '%s'"), @@ -1932,7 +1951,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } else BUG("unexpected number of arguments left to parse"); - if (fork_point > 0) { + if (options.fork_point > 0) { struct commit *head = lookup_commit_reference(the_repository, &options.orig_head); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 439f29d6c7..d26040c477 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -54,9 +54,11 @@ static int receive_unpack_limit = -1; static int transfer_unpack_limit = -1; static int advertise_atomic_push = 1; static int advertise_push_options; +static int advertise_sid; static int unpack_limit = 100; static off_t max_input_size; static int report_status; +static int report_status_v2; static int use_sideband; static int use_atomic; static int use_push_options; @@ -97,6 +99,17 @@ static int keepalive_in_sec = 5; static struct tmp_objdir *tmp_objdir; +static struct proc_receive_ref { + unsigned int want_add:1, + want_delete:1, + want_modify:1, + negative_ref:1; + char *ref_prefix; + struct proc_receive_ref *next; +} *proc_receive_ref; + +static void proc_receive_ref_append(const char *prefix); + static enum deny_action parse_deny_action(const char *var, const char *value) { if (value) { @@ -229,6 +242,18 @@ static int receive_pack_config(const char *var, const char *value, void *cb) return 0; } + if (strcmp(var, "receive.procreceiverefs") == 0) { + if (!value) + return config_error_nonbool(var); + proc_receive_ref_append(value); + return 0; + } + + if (strcmp(var, "transfer.advertisesid") == 0) { + advertise_sid = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, cb); } @@ -240,7 +265,7 @@ static void show_ref(const char *path, const struct object_id *oid) struct strbuf cap = STRBUF_INIT; strbuf_addstr(&cap, - "report-status delete-refs side-band-64k quiet"); + "report-status report-status-v2 delete-refs side-band-64k quiet"); if (advertise_atomic_push) strbuf_addstr(&cap, " atomic"); if (prefer_ofs_delta) @@ -249,6 +274,8 @@ static void show_ref(const char *path, const struct object_id *oid) strbuf_addf(&cap, " push-cert=%s", push_cert_nonce); if (advertise_push_options) strbuf_addstr(&cap, " push-options"); + if (advertise_sid) + strbuf_addf(&cap, " session-id=%s", trace2_session_id()); strbuf_addf(&cap, " object-format=%s", the_hash_algo->name); strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized()); packet_write_fmt(1, "%s %s%c%s\n", @@ -310,17 +337,94 @@ static void write_head_info(void) packet_flush(1); } +#define RUN_PROC_RECEIVE_SCHEDULED 1 +#define RUN_PROC_RECEIVE_RETURNED 2 struct command { struct command *next; const char *error_string; + struct ref_push_report *report; unsigned int skip_update:1, - did_not_exist:1; + did_not_exist:1, + run_proc_receive:2; int index; struct object_id old_oid; struct object_id new_oid; char ref_name[FLEX_ARRAY]; /* more */ }; +static void proc_receive_ref_append(const char *prefix) +{ + struct proc_receive_ref *ref_pattern; + char *p; + int len; + + ref_pattern = xcalloc(1, sizeof(struct proc_receive_ref)); + p = strchr(prefix, ':'); + if (p) { + while (prefix < p) { + if (*prefix == 'a') + ref_pattern->want_add = 1; + else if (*prefix == 'd') + ref_pattern->want_delete = 1; + else if (*prefix == 'm') + ref_pattern->want_modify = 1; + else if (*prefix == '!') + ref_pattern->negative_ref = 1; + prefix++; + } + prefix++; + } else { + ref_pattern->want_add = 1; + ref_pattern->want_delete = 1; + ref_pattern->want_modify = 1; + } + len = strlen(prefix); + while (len && prefix[len - 1] == '/') + len--; + ref_pattern->ref_prefix = xmemdupz(prefix, len); + if (!proc_receive_ref) { + proc_receive_ref = ref_pattern; + } else { + struct proc_receive_ref *end; + + end = proc_receive_ref; + while (end->next) + end = end->next; + end->next = ref_pattern; + } +} + +static int proc_receive_ref_matches(struct command *cmd) +{ + struct proc_receive_ref *p; + + if (!proc_receive_ref) + return 0; + + for (p = proc_receive_ref; p; p = p->next) { + const char *match = p->ref_prefix; + const char *remains; + + if (!p->want_add && is_null_oid(&cmd->old_oid)) + continue; + else if (!p->want_delete && is_null_oid(&cmd->new_oid)) + continue; + else if (!p->want_modify && + !is_null_oid(&cmd->old_oid) && + !is_null_oid(&cmd->new_oid)) + continue; + + if (skip_prefix(cmd->ref_name, match, &remains) && + (!*remains || *remains == '/')) { + if (!p->negative_ref) + return 1; + } else if (p->negative_ref) { + return 1; + } + } + return 0; +} + static void rp_error(const char *err, ...) __attribute__((format (printf, 1, 2))); static void rp_warning(const char *err, ...) __attribute__((format (printf, 1, 2))); @@ -660,7 +764,7 @@ static void prepare_push_cert_sha1(struct child_process *proc) memset(&sigcheck, '\0', sizeof(sigcheck)); - bogs = parse_signature(push_cert.buf, push_cert.len); + bogs = parse_signed_buffer(push_cert.buf, push_cert.len); check_signature(push_cert.buf, bogs, push_cert.buf + bogs, push_cert.len - bogs, &sigcheck); @@ -692,6 +796,7 @@ static void prepare_push_cert_sha1(struct child_process *proc) struct receive_hook_feed_state { struct command *cmd; + struct ref_push_report *report; int skip_broken; struct strbuf buf; const struct string_list *push_options; @@ -779,11 +884,31 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep) cmd = cmd->next; if (!cmd) return -1; /* EOF */ + if (!bufp) + return 0; /* OK, can feed something. */ strbuf_reset(&state->buf); - strbuf_addf(&state->buf, "%s %s %s\n", - oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid), - cmd->ref_name); - state->cmd = cmd->next; + if (!state->report) + state->report = cmd->report; + if (state->report) { + struct object_id *old_oid; + struct object_id *new_oid; + const char *ref_name; + + old_oid = state->report->old_oid ? state->report->old_oid : &cmd->old_oid; + new_oid = state->report->new_oid ? state->report->new_oid : &cmd->new_oid; + ref_name = state->report->ref_name ? state->report->ref_name : cmd->ref_name; + strbuf_addf(&state->buf, "%s %s %s\n", + oid_to_hex(old_oid), oid_to_hex(new_oid), + ref_name); + state->report = state->report->next; + if (!state->report) + state->cmd = cmd->next; + } else { + strbuf_addf(&state->buf, "%s %s %s\n", + oid_to_hex(&cmd->old_oid), oid_to_hex(&cmd->new_oid), + cmd->ref_name); + state->cmd = cmd->next; + } if (bufp) { *bufp = state->buf.buf; *sizep = state->buf.len; @@ -802,6 +927,7 @@ static int run_receive_hook(struct command *commands, strbuf_init(&state.buf, 0); state.cmd = commands; state.skip_broken = skip_broken; + state.report = NULL; if (feed_receive_hook(&state, NULL, NULL)) return 0; state.cmd = commands; @@ -840,6 +966,312 @@ static int run_update_hook(struct command *cmd) return finish_command(&proc); } +static struct command *find_command_by_refname(struct command *list, + const char *refname) +{ + for (; list; list = list->next) + if (!strcmp(list->ref_name, refname)) + return list; + return NULL; +} + +static int read_proc_receive_report(struct packet_reader *reader, + struct command *commands, + struct strbuf *errmsg) +{ + struct command *cmd; + struct command *hint = NULL; + struct ref_push_report *report = NULL; + int new_report = 0; + int code = 0; + int once = 0; + int response = 0; + + for (;;) { + struct object_id old_oid, new_oid; + const char *head; + const char *refname; + char *p; + enum packet_read_status status; + + status = packet_reader_read(reader); + if (status != PACKET_READ_NORMAL) { + /* Check whether proc-receive exited abnormally */ + if (status == PACKET_READ_EOF && !response) { + strbuf_addstr(errmsg, "proc-receive exited abnormally"); + return -1; + } + break; + } + response++; + + head = reader->line; + p = strchr(head, ' '); + if (!p) { + strbuf_addf(errmsg, "proc-receive reported incomplete status line: '%s'\n", head); + code = -1; + continue; + } + *p++ = '\0'; + if (!strcmp(head, "option")) { + const char *key, *val; + + if (!hint || !(report || new_report)) { + if (!once++) + strbuf_addstr(errmsg, "proc-receive reported 'option' without a matching 'ok/ng' directive\n"); + code = -1; + continue; + } + if (new_report) { + if (!hint->report) { + hint->report = xcalloc(1, sizeof(struct ref_push_report)); + report = hint->report; + } else { + report = hint->report; + while (report->next) + report = report->next; + report->next = xcalloc(1, sizeof(struct ref_push_report)); + report = report->next; + } + new_report = 0; + } + key = p; + p = strchr(key, ' '); + if (p) + *p++ = '\0'; + val = p; + if (!strcmp(key, "refname")) + report->ref_name = xstrdup_or_null(val); + else if (!strcmp(key, "old-oid") && val && + !parse_oid_hex(val, &old_oid, &val)) + report->old_oid = oiddup(&old_oid); + else if (!strcmp(key, "new-oid") && val && + !parse_oid_hex(val, &new_oid, &val)) + report->new_oid = oiddup(&new_oid); + else if (!strcmp(key, "forced-update")) + report->forced_update = 1; + else if (!strcmp(key, "fall-through")) + /* Fall through, let 'receive-pack' to execute it. */ + hint->run_proc_receive = 0; + continue; + } + + report = NULL; + new_report = 0; + refname = p; + p = strchr(refname, ' '); + if (p) + *p++ = '\0'; + if (strcmp(head, "ok") && strcmp(head, "ng")) { + strbuf_addf(errmsg, "proc-receive reported bad status '%s' on ref '%s'\n", + head, refname); + code = -1; + continue; + } + + /* first try searching at our hint, falling back to all refs */ + if (hint) + hint = find_command_by_refname(hint, refname); + if (!hint) + hint = find_command_by_refname(commands, refname); + if (!hint) { + strbuf_addf(errmsg, "proc-receive reported status on unknown ref: %s\n", + refname); + code = -1; + continue; + } + if (!hint->run_proc_receive) { + strbuf_addf(errmsg, "proc-receive reported status on unexpected ref: %s\n", + refname); + code = -1; + continue; + } + hint->run_proc_receive |= RUN_PROC_RECEIVE_RETURNED; + if (!strcmp(head, "ng")) { + if (p) + hint->error_string = xstrdup(p); + else + hint->error_string = "failed"; + code = -1; + continue; + } + new_report = 1; + } + + for (cmd = commands; cmd; cmd = cmd->next) + if (cmd->run_proc_receive && !cmd->error_string && + !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED)) { + cmd->error_string = "proc-receive failed to report status"; + code = -1; + } + return code; +} + +static int run_proc_receive_hook(struct command *commands, + const struct string_list *push_options) +{ + struct child_process proc = CHILD_PROCESS_INIT; + struct async muxer; + struct command *cmd; + const char *argv[2]; + struct packet_reader reader; + struct strbuf cap = STRBUF_INIT; + struct strbuf errmsg = STRBUF_INIT; + int hook_use_push_options = 0; + int version = 0; + int code; + + argv[0] = find_hook("proc-receive"); + if (!argv[0]) { + rp_error("cannot find hook 'proc-receive'"); + return -1; + } + argv[1] = NULL; + + proc.argv = argv; + proc.in = -1; + proc.out = -1; + proc.trace2_hook_name = "proc-receive"; + + if (use_sideband) { + memset(&muxer, 0, sizeof(muxer)); + muxer.proc = copy_to_sideband; + muxer.in = -1; + code = start_async(&muxer); + if (code) + return code; + proc.err = muxer.in; + } else { + proc.err = 0; + } + + code = start_command(&proc); + if (code) { + if (use_sideband) + finish_async(&muxer); + return code; + } + + sigchain_push(SIGPIPE, SIG_IGN); + + /* Version negotiaton */ + packet_reader_init(&reader, proc.out, NULL, 0, + PACKET_READ_CHOMP_NEWLINE | + PACKET_READ_GENTLE_ON_EOF); + if (use_atomic) + strbuf_addstr(&cap, " atomic"); + if (use_push_options) + strbuf_addstr(&cap, " push-options"); + if (cap.len) { + code = packet_write_fmt_gently(proc.in, "version=1%c%s\n", '\0', cap.buf + 1); + strbuf_release(&cap); + } else { + code = packet_write_fmt_gently(proc.in, "version=1\n"); + } + if (!code) + code = packet_flush_gently(proc.in); + + if (!code) + for (;;) { + int linelen; + enum packet_read_status status; + + status = packet_reader_read(&reader); + if (status != PACKET_READ_NORMAL) { + /* Check whether proc-receive exited abnormally */ + if (status == PACKET_READ_EOF) + code = -1; + break; + } + + if (reader.pktlen > 8 && starts_with(reader.line, "version=")) { + version = atoi(reader.line + 8); + linelen = strlen(reader.line); + if (linelen < reader.pktlen) { + const char *feature_list = reader.line + linelen + 1; + if (parse_feature_request(feature_list, "push-options")) + hook_use_push_options = 1; + } + } + } + + if (code) { + strbuf_addstr(&errmsg, "fail to negotiate version with proc-receive hook"); + goto cleanup; + } + + switch (version) { + case 0: + /* fallthrough */ + case 1: + break; + default: + strbuf_addf(&errmsg, "proc-receive version '%d' is not supported", + version); + code = -1; + goto cleanup; + } + + /* Send commands */ + for (cmd = commands; cmd; cmd = cmd->next) { + if (!cmd->run_proc_receive || cmd->skip_update || cmd->error_string) + continue; + code = packet_write_fmt_gently(proc.in, "%s %s %s", + oid_to_hex(&cmd->old_oid), + oid_to_hex(&cmd->new_oid), + cmd->ref_name); + if (code) + break; + } + if (!code) + code = packet_flush_gently(proc.in); + if (code) { + strbuf_addstr(&errmsg, "fail to write commands to proc-receive hook"); + goto cleanup; + } + + /* Send push options */ + if (hook_use_push_options) { + struct string_list_item *item; + + for_each_string_list_item(item, push_options) { + code = packet_write_fmt_gently(proc.in, "%s", item->string); + if (code) + break; + } + if (!code) + code = packet_flush_gently(proc.in); + if (code) { + strbuf_addstr(&errmsg, + "fail to write push-options to proc-receive hook"); + goto cleanup; + } + } + + /* Read result from proc-receive */ + code = read_proc_receive_report(&reader, commands, &errmsg); + +cleanup: + close(proc.in); + close(proc.out); + if (use_sideband) + finish_async(&muxer); + if (finish_command(&proc)) + code = -1; + if (errmsg.len >0) { + char *p = errmsg.buf; + + p += errmsg.len - 1; + if (*p == '\n') + *p = '\0'; + rp_error("%s", errmsg.buf); + strbuf_release(&errmsg); + } + sigchain_pop(SIGPIPE); + + return code; +} + static char *refuse_unconfigured_deny_msg = 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" @@ -1415,7 +1847,7 @@ static void execute_commands_non_atomic(struct command *commands, struct strbuf err = STRBUF_INIT; for (cmd = commands; cmd; cmd = cmd->next) { - if (!should_process_cmd(cmd)) + if (!should_process_cmd(cmd) || cmd->run_proc_receive) continue; transaction = ref_transaction_begin(&err); @@ -1455,7 +1887,7 @@ static void execute_commands_atomic(struct command *commands, } for (cmd = commands; cmd; cmd = cmd->next) { - if (!should_process_cmd(cmd)) + if (!should_process_cmd(cmd) || cmd->run_proc_receive) continue; cmd->error_string = update(cmd, si); @@ -1491,6 +1923,7 @@ static void execute_commands(struct command *commands, struct iterate_data data; struct async muxer; int err_fd = 0; + int run_proc_receive = 0; if (unpacker_error) { for (cmd = commands; cmd; cmd = cmd->next) @@ -1520,6 +1953,22 @@ static void execute_commands(struct command *commands, reject_updates_to_hidden(commands); + /* + * Try to find commands that have special prefix in their reference names, + * and mark them to run an external "proc-receive" hook later. + */ + if (proc_receive_ref) { + for (cmd = commands; cmd; cmd = cmd->next) { + if (!should_process_cmd(cmd)) + continue; + + if (proc_receive_ref_matches(cmd)) { + cmd->run_proc_receive = RUN_PROC_RECEIVE_SCHEDULED; + run_proc_receive = 1; + } + } + } + if (run_receive_hook(commands, "pre-receive", 0, push_options)) { for (cmd = commands; cmd; cmd = cmd->next) { if (!cmd->error_string) @@ -1546,6 +1995,14 @@ static void execute_commands(struct command *commands, free(head_name_to_free); head_name = head_name_to_free = resolve_refdup("HEAD", 0, NULL, NULL); + if (run_proc_receive && + run_proc_receive_hook(commands, push_options)) + for (cmd = commands; cmd; cmd = cmd->next) + if (!cmd->error_string && + !(cmd->run_proc_receive & RUN_PROC_RECEIVE_RETURNED) && + (cmd->run_proc_receive || use_atomic)) + cmd->error_string = "fail to run proc-receive hook"; + if (use_atomic) execute_commands_atomic(commands, si); else @@ -1593,7 +2050,7 @@ static void queue_commands_from_cert(struct command **tail, die("malformed push certificate %.*s", 100, push_cert->buf); else boc += 2; - eoc = push_cert->buf + parse_signature(push_cert->buf, push_cert->len); + eoc = push_cert->buf + parse_signed_buffer(push_cert->buf, push_cert->len); while (boc < eoc) { const char *eol = memchr(boc, '\n', eoc - boc); @@ -1626,9 +2083,12 @@ static struct command *read_head_info(struct packet_reader *reader, if (linelen < reader->pktlen) { const char *feature_list = reader->line + linelen + 1; const char *hash = NULL; + const char *client_sid; int len = 0; if (parse_feature_request(feature_list, "report-status")) report_status = 1; + if (parse_feature_request(feature_list, "report-status-v2")) + report_status_v2 = 1; if (parse_feature_request(feature_list, "side-band-64k")) use_sideband = LARGE_PACKET_MAX; if (parse_feature_request(feature_list, "quiet")) @@ -1646,6 +2106,12 @@ static struct command *read_head_info(struct packet_reader *reader, } if (xstrncmpz(the_hash_algo->name, hash, len)) die("error: unsupported object format '%s'", hash); + client_sid = parse_feature_value(feature_list, "session-id", &len, NULL); + if (client_sid) { + char *sid = xstrndup(client_sid, len); + trace2_data_string("transfer", NULL, "client-sid", client_sid); + free(sid); + } } if (!strcmp(reader->line, "push-cert")) { @@ -1809,7 +2275,7 @@ static const char *unpack(int err_fd, struct shallow_info *si) status = start_command(&child); if (status) return "index-pack fork failed"; - pack_lockfile = index_pack_lockfile(child.out); + pack_lockfile = index_pack_lockfile(child.out, NULL); close(child.out); status = finish_command(&child); if (status) @@ -1947,6 +2413,51 @@ static void report(struct command *commands, const char *unpack_status) strbuf_release(&buf); } +static void report_v2(struct command *commands, const char *unpack_status) +{ + struct command *cmd; + struct strbuf buf = STRBUF_INIT; + struct ref_push_report *report; + + packet_buf_write(&buf, "unpack %s\n", + unpack_status ? unpack_status : "ok"); + for (cmd = commands; cmd; cmd = cmd->next) { + int count = 0; + + if (cmd->error_string) { + packet_buf_write(&buf, "ng %s %s\n", + cmd->ref_name, + cmd->error_string); + continue; + } + packet_buf_write(&buf, "ok %s\n", + cmd->ref_name); + for (report = cmd->report; report; report = report->next) { + if (count++ > 0) + packet_buf_write(&buf, "ok %s\n", + cmd->ref_name); + if (report->ref_name) + packet_buf_write(&buf, "option refname %s\n", + report->ref_name); + if (report->old_oid) + packet_buf_write(&buf, "option old-oid %s\n", + oid_to_hex(report->old_oid)); + if (report->new_oid) + packet_buf_write(&buf, "option new-oid %s\n", + oid_to_hex(report->new_oid)); + if (report->forced_update) + packet_buf_write(&buf, "option forced-update\n"); + } + } + packet_buf_flush(&buf); + + if (use_sideband) + send_sideband(1, 1, buf.buf, buf.len, use_sideband); + else + write_or_die(1, buf.buf, buf.len); + strbuf_release(&buf); +} + static int delete_only(struct command *commands) { struct command *cmd; @@ -2055,7 +2566,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) &push_options); if (pack_lockfile) unlink_or_warn(pack_lockfile); - if (report_status) + if (report_status_v2) + report_v2(commands, unpack_status); + else if (report_status) report(commands, unpack_status); run_receive_hook(commands, "post-receive", 1, &push_options); diff --git a/builtin/reflog.c b/builtin/reflog.c index ca1d8079f3..09541d1c80 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -602,6 +602,9 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix) */ if (cb.cmd.stalefix) { repo_init_revisions(the_repository, &cb.cmd.revs, prefix); + cb.cmd.revs.do_not_die_on_missing_tree = 1; + cb.cmd.revs.ignore_missing = 1; + cb.cmd.revs.ignore_missing_links = 1; if (flags & EXPIRE_REFLOGS_VERBOSE) printf(_("Marking reachable objects...")); mark_reachable_objects(&cb.cmd.revs, 0, 0, NULL); diff --git a/builtin/remote.c b/builtin/remote.c index 64b4b551eb..d11a5589e4 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -191,11 +191,12 @@ static int add(int argc, const char **argv) url = argv[1]; remote = remote_get(name); - if (remote_is_configured(remote, 1)) - die(_("remote %s already exists."), name); + if (remote_is_configured(remote, 1)) { + error(_("remote %s already exists."), name); + exit(3); + } - strbuf_addf(&buf2, "refs/heads/test:refs/remotes/%s/test", name); - if (!valid_fetch_refspec(buf2.buf)) + if (!valid_remote_name(name)) die(_("'%s' is not a valid remote name"), name); strbuf_addf(&buf, "remote.%s.url", name); @@ -686,21 +687,23 @@ static int mv(int argc, const char **argv) rename.remote_branches = &remote_branches; oldremote = remote_get(rename.old_name); - if (!remote_is_configured(oldremote, 1)) - die(_("No such remote: '%s'"), rename.old_name); + if (!remote_is_configured(oldremote, 1)) { + error(_("No such remote: '%s'"), rename.old_name); + exit(2); + } if (!strcmp(rename.old_name, rename.new_name) && oldremote->origin != REMOTE_CONFIG) return migrate_file(oldremote); newremote = remote_get(rename.new_name); - if (remote_is_configured(newremote, 1)) - die(_("remote %s already exists."), rename.new_name); + if (remote_is_configured(newremote, 1)) { + error(_("remote %s already exists."), rename.new_name); + exit(3); + } - strbuf_addf(&buf, "refs/heads/test:refs/remotes/%s/test", rename.new_name); - if (!valid_fetch_refspec(buf.buf)) + if (!valid_remote_name(rename.new_name)) die(_("'%s' is not a valid remote name"), rename.new_name); - strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s", rename.old_name); strbuf_addf(&buf2, "remote.%s", rename.new_name); if (git_config_rename_section(buf.buf, buf2.buf) < 1) @@ -709,7 +712,7 @@ static int mv(int argc, const char **argv) strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s.fetch", rename.new_name); - git_config_set_multivar(buf.buf, NULL, NULL, 1); + git_config_set_multivar(buf.buf, NULL, NULL, CONFIG_FLAGS_MULTI_REPLACE); strbuf_addf(&old_remote_context, ":refs/remotes/%s/", rename.old_name); for (i = 0; i < oldremote->fetch.raw_nr; i++) { char *ptr; @@ -829,8 +832,10 @@ static int rm(int argc, const char **argv) usage_with_options(builtin_remote_rm_usage, options); remote = remote_get(argv[1]); - if (!remote_is_configured(remote, 1)) - die(_("No such remote: '%s'"), argv[1]); + if (!remote_is_configured(remote, 1)) { + error(_("No such remote: '%s'"), argv[1]); + exit(2); + } known_remotes.to_delete = remote; for_each_remote(add_known_remote, &known_remotes); @@ -1486,7 +1491,8 @@ static int update(int argc, const char **argv) static int remove_all_fetch_refspecs(const char *key) { - return git_config_set_multivar_gently(key, NULL, NULL, 1); + return git_config_set_multivar_gently(key, NULL, NULL, + CONFIG_FLAGS_MULTI_REPLACE); } static void add_branches(struct remote *remote, const char **branches, @@ -1511,8 +1517,10 @@ static int set_remote_branches(const char *remotename, const char **branches, strbuf_addf(&key, "remote.%s.fetch", remotename); remote = remote_get(remotename); - if (!remote_is_configured(remote, 1)) - die(_("No such remote '%s'"), remotename); + if (!remote_is_configured(remote, 1)) { + error(_("No such remote '%s'"), remotename); + exit(2); + } if (!add_mode && remove_all_fetch_refspecs(key.buf)) { strbuf_release(&key); @@ -1565,8 +1573,10 @@ static int get_url(int argc, const char **argv) remotename = argv[0]; remote = remote_get(remotename); - if (!remote_is_configured(remote, 1)) - die(_("No such remote '%s'"), remotename); + if (!remote_is_configured(remote, 1)) { + error(_("No such remote '%s'"), remotename); + exit(2); + } url_nr = 0; if (push_mode) { @@ -1633,8 +1643,10 @@ static int set_url(int argc, const char **argv) oldurl = newurl; remote = remote_get(remotename); - if (!remote_is_configured(remote, 1)) - die(_("No such remote '%s'"), remotename); + if (!remote_is_configured(remote, 1)) { + error(_("No such remote '%s'"), remotename); + exit(2); + } if (push_mode) { strbuf_addf(&name_buf, "remote.%s.pushurl", remotename); @@ -1675,7 +1687,8 @@ static int set_url(int argc, const char **argv) if (!delete_mode) git_config_set_multivar(name_buf.buf, newurl, oldurl, 0); else - git_config_set_multivar(name_buf.buf, NULL, oldurl, 1); + git_config_set_multivar(name_buf.buf, NULL, oldurl, + CONFIG_FLAGS_MULTI_REPLACE); out: strbuf_release(&name_buf); return 0; diff --git a/builtin/repack.c b/builtin/repack.c index 01e7767c79..01440de2d5 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -14,6 +14,7 @@ #include "object-store.h" #include "promisor-remote.h" #include "shallow.h" +#include "pack.h" static int delta_base_offset = 1; static int pack_kept_objects = -1; @@ -202,6 +203,38 @@ static int write_oid(const struct object_id *oid, struct packed_git *pack, return 0; } +static struct { + const char *name; + unsigned optional:1; +} exts[] = { + {".pack"}, + {".idx"}, + {".rev", 1}, + {".bitmap", 1}, + {".promisor", 1}, +}; + +static unsigned populate_pack_exts(char *name) +{ + struct stat statbuf; + struct strbuf path = STRBUF_INIT; + unsigned ret = 0; + int i; + + for (i = 0; i < ARRAY_SIZE(exts); i++) { + strbuf_reset(&path); + strbuf_addf(&path, "%s-%s%s", packtmp, name, exts[i].name); + + if (stat(path.buf, &statbuf)) + continue; + + ret |= (1 << i); + } + + strbuf_release(&path); + return ret; +} + static void repack_promisor_objects(const struct pack_objects_args *args, struct string_list *names) { @@ -230,11 +263,12 @@ static void repack_promisor_objects(const struct pack_objects_args *args, out = xfdopen(cmd.out, "r"); while (strbuf_getline_lf(&line, out) != EOF) { + struct string_list_item *item; char *promisor_name; - int fd; + if (line.len != the_hash_algo->hexsz) die(_("repack: Expecting full hex object ID lines only from pack-objects.")); - string_list_append(names, line.buf); + item = string_list_append(names, line.buf); /* * pack-objects creates the .pack and .idx files, but not the @@ -249,10 +283,10 @@ static void repack_promisor_objects(const struct pack_objects_args *args, */ promisor_name = mkpathdup("%s-%s.promisor", packtmp, line.buf); - fd = open(promisor_name, O_CREAT|O_EXCL|O_WRONLY, 0600); - if (fd < 0) - die_errno(_("unable to create '%s'"), promisor_name); - close(fd); + write_promisor_file(promisor_name, NULL, 0); + + item->util = (void *)(uintptr_t)populate_pack_exts(item->string); + free(promisor_name); } fclose(out); @@ -265,22 +299,13 @@ static void repack_promisor_objects(const struct pack_objects_args *args, int cmd_repack(int argc, const char **argv, const char *prefix) { - struct { - const char *name; - unsigned optional:1; - } exts[] = { - {".pack"}, - {".idx"}, - {".bitmap", 1}, - {".promisor", 1}, - }; struct child_process cmd = CHILD_PROCESS_INIT; struct string_list_item *item; struct string_list names = STRING_LIST_INIT_DUP; struct string_list rollback = STRING_LIST_INIT_NODUP; struct string_list existing_packs = STRING_LIST_INIT_DUP; struct strbuf line = STRBUF_INIT; - int i, ext, ret, failed; + int i, ext, ret; FILE *out; /* variables to be filled by option parsing */ @@ -429,113 +454,42 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (!names.nr && !po_args.quiet) printf_ln(_("Nothing new to pack.")); + for_each_string_list_item(item, &names) { + item->util = (void *)(uintptr_t)populate_pack_exts(item->string); + } + close_object_store(the_repository->objects); /* * Ok we have prepared all new packfiles. - * First see if there are packs of the same name and if so - * if we can move them out of the way (this can happen if we - * repacked immediately after packing fully. */ - failed = 0; for_each_string_list_item(item, &names) { for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { char *fname, *fname_old; - fname = mkpathdup("%s/pack-%s%s", packdir, - item->string, exts[ext].name); - if (!file_exists(fname)) { - free(fname); - continue; - } - - fname_old = mkpathdup("%s/old-%s%s", packdir, - item->string, exts[ext].name); - if (file_exists(fname_old)) - if (unlink(fname_old)) - failed = 1; - - if (!failed && rename(fname, fname_old)) { - free(fname); - free(fname_old); - failed = 1; - break; - } else { - string_list_append(&rollback, fname); - free(fname_old); - } - } - if (failed) - break; - } - if (failed) { - struct string_list rollback_failure = STRING_LIST_INIT_DUP; - for_each_string_list_item(item, &rollback) { - char *fname, *fname_old; - fname = mkpathdup("%s/%s", packdir, item->string); - fname_old = mkpathdup("%s/old-%s", packdir, item->string); - if (rename(fname_old, fname)) - string_list_append(&rollback_failure, fname); - free(fname); - free(fname_old); - } - - if (rollback_failure.nr) { - int i; - fprintf(stderr, - _("WARNING: Some packs in use have been renamed by\n" - "WARNING: prefixing old- to their name, in order to\n" - "WARNING: replace them with the new version of the\n" - "WARNING: file. But the operation failed, and the\n" - "WARNING: attempt to rename them back to their\n" - "WARNING: original names also failed.\n" - "WARNING: Please rename them in %s manually:\n"), packdir); - for (i = 0; i < rollback_failure.nr; i++) - fprintf(stderr, "WARNING: old-%s -> %s\n", - rollback_failure.items[i].string, - rollback_failure.items[i].string); - } - exit(1); - } - - /* Now the ones with the same name are out of the way... */ - for_each_string_list_item(item, &names) { - for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { - char *fname, *fname_old; - struct stat statbuffer; - int exists = 0; fname = mkpathdup("%s/pack-%s%s", packdir, item->string, exts[ext].name); fname_old = mkpathdup("%s-%s%s", packtmp, item->string, exts[ext].name); - if (!stat(fname_old, &statbuffer)) { - statbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); - chmod(fname_old, statbuffer.st_mode); - exists = 1; - } - if (exists || !exts[ext].optional) { + + if (((uintptr_t)item->util) & (1 << ext)) { + struct stat statbuffer; + if (!stat(fname_old, &statbuffer)) { + statbuffer.st_mode &= ~(S_IWUSR | S_IWGRP | S_IWOTH); + chmod(fname_old, statbuffer.st_mode); + } + if (rename(fname_old, fname)) die_errno(_("renaming '%s' failed"), fname_old); - } - free(fname); - free(fname_old); - } - } + } else if (!exts[ext].optional) + die(_("missing required file: %s"), fname_old); + else if (unlink(fname) < 0 && errno != ENOENT) + die_errno(_("could not unlink: %s"), fname); - /* Remove the "old-" files */ - for_each_string_list_item(item, &names) { - for (ext = 0; ext < ARRAY_SIZE(exts); ext++) { - char *fname; - fname = mkpathdup("%s/old-%s%s", - packdir, - item->string, - exts[ext].name); - if (remove_path(fname)) - warning(_("failed to remove '%s'"), fname); free(fname); + free(fname_old); } } - /* End of pack replacement. */ reprepare_packed_git(the_repository); diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 25c6c3b38d..b4d8ea0a35 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -80,6 +80,19 @@ static int arg_show_object_names = 1; #define DEFAULT_OIDSET_SIZE (16*1024) +static int show_disk_usage; +static off_t total_disk_usage; + +static off_t get_object_disk_usage(struct object *obj) +{ + off_t size; + struct object_info oi = OBJECT_INFO_INIT; + oi.disk_sizep = &size; + if (oid_object_info_extended(the_repository, &obj->oid, &oi, 0) < 0) + die(_("unable to get disk usage of %s"), oid_to_hex(&obj->oid)); + return size; +} + static void finish_commit(struct commit *commit); static void show_commit(struct commit *commit, void *data) { @@ -88,6 +101,9 @@ static void show_commit(struct commit *commit, void *data) display_progress(progress, ++progress_counter); + if (show_disk_usage) + total_disk_usage += get_object_disk_usage(&commit->object); + if (info->flags & REV_LIST_QUIET) { finish_commit(commit); return; @@ -258,6 +274,8 @@ static void show_object(struct object *obj, const char *name, void *cb_data) if (finish_object(obj, name, cb_data)) return; display_progress(progress, ++progress_counter); + if (show_disk_usage) + total_disk_usage += get_object_disk_usage(obj); if (info->flags & REV_LIST_QUIET) return; @@ -452,6 +470,23 @@ static int try_bitmap_traversal(struct rev_info *revs, return 0; } +static int try_bitmap_disk_usage(struct rev_info *revs, + struct list_objects_filter_options *filter) +{ + struct bitmap_index *bitmap_git; + + if (!show_disk_usage) + return -1; + + bitmap_git = prepare_bitmap_walk(revs, filter); + if (!bitmap_git) + return -1; + + printf("%"PRIuMAX"\n", + (uintmax_t)get_disk_usage_from_bitmap(bitmap_git, revs)); + return 0; +} + int cmd_rev_list(int argc, const char **argv, const char *prefix) { struct rev_info revs; @@ -584,6 +619,12 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) continue; } + if (!strcmp(arg, "--disk-usage")) { + show_disk_usage = 1; + info.flags |= REV_LIST_QUIET; + continue; + } + usage(rev_list_usage); } @@ -626,6 +667,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) if (use_bitmap_index) { if (!try_bitmap_count(&revs, &filter_options)) return 0; + if (!try_bitmap_disk_usage(&revs, &filter_options)) + return 0; if (!try_bitmap_traversal(&revs, &filter_options)) return 0; } @@ -690,5 +733,8 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) printf("%d\n", revs.count_left + revs.count_right); } + if (show_disk_usage) + printf("%"PRIuMAX"\n", (uintmax_t)total_disk_usage); + return 0; } diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index ed200c8af1..85bad9052e 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -583,6 +583,75 @@ static void handle_ref_opt(const char *pattern, const char *prefix) clear_ref_exclusion(&ref_excludes); } +enum format_type { + /* We would like a relative path. */ + FORMAT_RELATIVE, + /* We would like a canonical absolute path. */ + FORMAT_CANONICAL, + /* We would like the default behavior. */ + FORMAT_DEFAULT, +}; + +enum default_type { + /* Our default is a relative path. */ + DEFAULT_RELATIVE, + /* Our default is a relative path if there's a shared root. */ + DEFAULT_RELATIVE_IF_SHARED, + /* Our default is a canonical absolute path. */ + DEFAULT_CANONICAL, + /* Our default is not to modify the item. */ + DEFAULT_UNMODIFIED, +}; + +static void print_path(const char *path, const char *prefix, enum format_type format, enum default_type def) +{ + char *cwd = NULL; + /* + * We don't ever produce a relative path if prefix is NULL, so set the + * prefix to the current directory so that we can produce a relative + * path whenever possible. If we're using RELATIVE_IF_SHARED mode, then + * we want an absolute path unless the two share a common prefix, so don't + * set it in that case, since doing so causes a relative path to always + * be produced if possible. + */ + if (!prefix && (format != FORMAT_DEFAULT || def != DEFAULT_RELATIVE_IF_SHARED)) + prefix = cwd = xgetcwd(); + if (format == FORMAT_DEFAULT && def == DEFAULT_UNMODIFIED) { + puts(path); + } else if (format == FORMAT_RELATIVE || + (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE)) { + /* + * In order for relative_path to work as expected, we need to + * make sure that both paths are absolute paths. If we don't, + * we can end up with an unexpected absolute path that the user + * didn't want. + */ + struct strbuf buf = STRBUF_INIT, realbuf = STRBUF_INIT, prefixbuf = STRBUF_INIT; + if (!is_absolute_path(path)) { + strbuf_realpath_forgiving(&realbuf, path, 1); + path = realbuf.buf; + } + if (!is_absolute_path(prefix)) { + strbuf_realpath_forgiving(&prefixbuf, prefix, 1); + prefix = prefixbuf.buf; + } + puts(relative_path(path, prefix, &buf)); + strbuf_release(&buf); + strbuf_release(&realbuf); + strbuf_release(&prefixbuf); + } else if (format == FORMAT_DEFAULT && def == DEFAULT_RELATIVE_IF_SHARED) { + struct strbuf buf = STRBUF_INIT; + puts(relative_path(path, prefix, &buf)); + strbuf_release(&buf); + } else { + struct strbuf buf = STRBUF_INIT; + strbuf_realpath_forgiving(&buf, path, 1); + puts(buf.buf); + strbuf_release(&buf); + } + free(cwd); +} + int cmd_rev_parse(int argc, const char **argv, const char *prefix) { int i, as_is = 0, verify = 0, quiet = 0, revs_count = 0, type = 0; @@ -595,6 +664,8 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) struct object_context unused; struct strbuf buf = STRBUF_INIT; const int hexsz = the_hash_algo->hexsz; + int seen_end_of_options = 0; + enum format_type format = FORMAT_DEFAULT; if (argc > 1 && !strcmp("--parseopt", argv[1])) return cmd_parseopt(argc - 1, argv + 1, prefix); @@ -622,21 +693,29 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) for (i = 1; i < argc; i++) { const char *arg = argv[i]; - if (!strcmp(arg, "--local-env-vars")) { - int i; - for (i = 0; local_repo_env[i]; i++) - printf("%s\n", local_repo_env[i]); + if (as_is) { + if (show_file(arg, output_prefix) && as_is < 2) + verify_filename(prefix, arg, 0); continue; } - if (!strcmp(arg, "--resolve-git-dir")) { - const char *gitdir = argv[++i]; - if (!gitdir) - die("--resolve-git-dir requires an argument"); - gitdir = resolve_gitdir(gitdir); - if (!gitdir) - die("not a gitdir '%s'", argv[i]); - puts(gitdir); - continue; + + if (!seen_end_of_options) { + if (!strcmp(arg, "--local-env-vars")) { + int i; + for (i = 0; local_repo_env[i]; i++) + printf("%s\n", local_repo_env[i]); + continue; + } + if (!strcmp(arg, "--resolve-git-dir")) { + const char *gitdir = argv[++i]; + if (!gitdir) + die("--resolve-git-dir requires an argument"); + gitdir = resolve_gitdir(gitdir); + if (!gitdir) + die("not a gitdir '%s'", argv[i]); + puts(gitdir); + continue; + } } /* The rest of the options require a git repository. */ @@ -646,41 +725,47 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) did_repo_setup = 1; } - if (!strcmp(arg, "--git-path")) { - if (!argv[i + 1]) - die("--git-path requires an argument"); - strbuf_reset(&buf); - puts(relative_path(git_path("%s", argv[i + 1]), - prefix, &buf)); - i++; - continue; - } - if (as_is) { - if (show_file(arg, output_prefix) && as_is < 2) - verify_filename(prefix, arg, 0); - continue; - } - if (!strcmp(arg,"-n")) { - if (++i >= argc) - die("-n requires an argument"); - if ((filter & DO_FLAGS) && (filter & DO_REVS)) { - show(arg); - show(argv[i]); - } - continue; - } - if (starts_with(arg, "-n")) { - if ((filter & DO_FLAGS) && (filter & DO_REVS)) - show(arg); + if (!strcmp(arg, "--")) { + as_is = 2; + /* Pass on the "--" if we show anything but files.. */ + if (filter & (DO_FLAGS | DO_REVS)) + show_file(arg, 0); continue; } - if (*arg == '-') { - if (!strcmp(arg, "--")) { - as_is = 2; - /* Pass on the "--" if we show anything but files.. */ - if (filter & (DO_FLAGS | DO_REVS)) - show_file(arg, 0); + if (!seen_end_of_options && *arg == '-') { + if (!strcmp(arg, "--git-path")) { + if (!argv[i + 1]) + die("--git-path requires an argument"); + strbuf_reset(&buf); + print_path(git_path("%s", argv[i + 1]), prefix, + format, + DEFAULT_RELATIVE_IF_SHARED); + i++; + continue; + } + if (!strcmp(arg,"-n")) { + if (++i >= argc) + die("-n requires an argument"); + if ((filter & DO_FLAGS) && (filter & DO_REVS)) { + show(arg); + show(argv[i]); + } + continue; + } + if (starts_with(arg, "-n")) { + if ((filter & DO_FLAGS) && (filter & DO_REVS)) + show(arg); + continue; + } + if (opt_with_value(arg, "--path-format", &arg)) { + if (!strcmp(arg, "absolute")) { + format = FORMAT_CANONICAL; + } else if (!strcmp(arg, "relative")) { + format = FORMAT_RELATIVE; + } else { + die("unknown argument to --path-format: %s", arg); + } continue; } if (!strcmp(arg, "--default")) { @@ -803,7 +888,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--show-toplevel")) { const char *work_tree = get_git_work_tree(); if (work_tree) - puts(work_tree); + print_path(work_tree, prefix, format, DEFAULT_UNMODIFIED); else die("this operation must be run in a work tree"); continue; @@ -811,7 +896,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (!strcmp(arg, "--show-superproject-working-tree")) { struct strbuf superproject = STRBUF_INIT; if (get_superproject_working_tree(&superproject)) - puts(superproject.buf); + print_path(superproject.buf, prefix, format, DEFAULT_UNMODIFIED); strbuf_release(&superproject); continue; } @@ -846,16 +931,18 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) const char *gitdir = getenv(GIT_DIR_ENVIRONMENT); char *cwd; int len; + enum format_type wanted = format; if (arg[2] == 'g') { /* --git-dir */ if (gitdir) { - puts(gitdir); + print_path(gitdir, prefix, format, DEFAULT_UNMODIFIED); continue; } if (!prefix) { - puts(".git"); + print_path(".git", prefix, format, DEFAULT_UNMODIFIED); continue; } } else { /* --absolute-git-dir */ + wanted = FORMAT_CANONICAL; if (!gitdir && !prefix) gitdir = ".git"; if (gitdir) { @@ -868,14 +955,14 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) } cwd = xgetcwd(); len = strlen(cwd); - printf("%s%s.git\n", cwd, len && cwd[len-1] != '/' ? "/" : ""); + strbuf_reset(&buf); + strbuf_addf(&buf, "%s%s.git", cwd, len && cwd[len-1] != '/' ? "/" : ""); free(cwd); + print_path(buf.buf, prefix, wanted, DEFAULT_CANONICAL); continue; } if (!strcmp(arg, "--git-common-dir")) { - strbuf_reset(&buf); - puts(relative_path(get_git_common_dir(), - prefix, &buf)); + print_path(get_git_common_dir(), prefix, format, DEFAULT_RELATIVE_IF_SHARED); continue; } if (!strcmp(arg, "--is-inside-git-dir")) { @@ -905,8 +992,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) if (the_index.split_index) { const struct object_id *oid = &the_index.split_index->base_oid; const char *path = git_path("sharedindex.%s", oid_to_hex(oid)); - strbuf_reset(&buf); - puts(relative_path(path, prefix, &buf)); + print_path(path, prefix, format, DEFAULT_RELATIVE); } continue; } @@ -937,6 +1023,12 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix) puts(the_hash_algo->name); continue; } + if (!strcmp(arg, "--end-of-options")) { + seen_end_of_options = 1; + if (filter & (DO_FLAGS | DO_REVS)) + show_file(arg, 0); + continue; + } if (show_flag(arg) && verify) die_no_single_rev(quiet); continue; diff --git a/builtin/revert.c b/builtin/revert.c index f61cc5d82c..314a86c562 100644 --- a/builtin/revert.c +++ b/builtin/revert.c @@ -107,7 +107,7 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) OPT_BOOL('n', "no-commit", &opts->no_commit, N_("don't automatically commit")), OPT_BOOL('e', "edit", &opts->edit, N_("edit the commit message")), OPT_NOOP_NOARG('r', NULL), - OPT_BOOL('s', "signoff", &opts->signoff, N_("add Signed-off-by:")), + OPT_BOOL('s', "signoff", &opts->signoff, N_("add a Signed-off-by trailer")), OPT_CALLBACK('m', "mainline", opts, N_("parent-number"), N_("select mainline parent"), option_parse_m), OPT_RERERE_AUTOUPDATE(&opts->allow_rerere_auto), @@ -172,6 +172,11 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) NULL); } + if (!opts->strategy && opts->default_strategy) { + opts->strategy = opts->default_strategy; + opts->default_strategy = NULL; + } + if (opts->allow_ff) verify_opt_compatible(me, "--ff", "--signoff", opts->signoff, @@ -202,6 +207,8 @@ static int run_sequencer(int argc, const char **argv, struct replay_opts *opts) /* These option values will be free()d */ opts->gpg_sign = xstrdup_or_null(opts->gpg_sign); opts->strategy = xstrdup_or_null(opts->strategy); + if (!opts->strategy && getenv("GIT_TEST_MERGE_ALGORITHM")) + opts->strategy = xstrdup(getenv("GIT_TEST_MERGE_ALGORITHM")); if (cmd == 'q') { int ret = sequencer_remove_state(opts); diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 2b9610f121..a7e01667b0 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -29,10 +29,12 @@ static struct send_pack_args args; static void print_helper_status(struct ref *ref) { struct strbuf buf = STRBUF_INIT; + struct ref_push_report *report; for (; ref; ref = ref->next) { const char *msg = NULL; const char *res; + int count = 0; switch(ref->status) { case REF_STATUS_NONE: @@ -69,6 +71,11 @@ static void print_helper_status(struct ref *ref) msg = "stale info"; break; + case REF_STATUS_REJECT_REMOTE_UPDATED: + res = "error"; + msg = "remote ref updated since checkout"; + break; + case REF_STATUS_REJECT_ALREADY_EXISTS: res = "error"; msg = "already exists"; @@ -94,6 +101,23 @@ static void print_helper_status(struct ref *ref) } strbuf_addch(&buf, '\n'); + if (ref->status == REF_STATUS_OK) { + for (report = ref->report; report; report = report->next) { + if (count++ > 0) + strbuf_addf(&buf, "ok %s\n", ref->name); + if (report->ref_name) + strbuf_addf(&buf, "option refname %s\n", + report->ref_name); + if (report->old_oid) + strbuf_addf(&buf, "option old-oid %s\n", + oid_to_hex(report->old_oid)); + if (report->new_oid) + strbuf_addf(&buf, "option new-oid %s\n", + oid_to_hex(report->new_oid)); + if (report->forced_update) + strbuf_addstr(&buf, "option forced-update\n"); + } + } write_or_die(1, buf.buf, buf.len); } strbuf_release(&buf); @@ -154,6 +178,7 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) int progress = -1; int from_stdin = 0; struct push_cas_option cas = {0}; + int force_if_includes = 0; struct packet_reader reader; struct option options[] = { @@ -179,6 +204,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) OPT_CALLBACK_F(0, CAS_OPT_NAME, &cas, N_("<refname>:<expect>"), N_("require old value of ref to be at this value"), PARSE_OPT_OPTARG, parseopt_push_cas_option), + OPT_BOOL(0, TRANS_OPT_FORCE_IF_INCLUDES, &force_if_includes, + N_("require remote updates to be integrated locally")), OPT_END() }; @@ -280,6 +307,9 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) if (!is_empty_cas(&cas)) apply_push_cas(&cas, remote, remote_refs); + if (!is_empty_cas(&cas) && force_if_includes) + cas.use_force_if_includes = 1; + set_ref_status_for_push(remote_refs, args.send_mirror, args.force_update); diff --git a/builtin/shortlog.c b/builtin/shortlog.c index c856c58bb5..3e7ab1ca82 100644 --- a/builtin/shortlog.c +++ b/builtin/shortlog.c @@ -9,6 +9,8 @@ #include "mailmap.h" #include "shortlog.h" #include "parse-options.h" +#include "trailer.h" +#include "strmap.h" static char const * const shortlog_usage[] = { N_("git shortlog [<options>] [<revision-range>] [[--] <path>...]"), @@ -49,18 +51,17 @@ static int compare_by_list(const void *a1, const void *a2) } static void insert_one_record(struct shortlog *log, - const char *author, + const char *ident, const char *oneline) { struct string_list_item *item; - item = string_list_insert(&log->list, author); + item = string_list_insert(&log->list, ident); if (log->summary) item->util = (void *)(UTIL_TO_INT(item) + 1); else { - const char *dot3 = log->common_repo_prefix; - char *buffer, *p; + char *buffer; struct strbuf subject = STRBUF_INIT; const char *eol; @@ -80,25 +81,14 @@ static void insert_one_record(struct shortlog *log, format_subject(&subject, oneline, " "); buffer = strbuf_detach(&subject, NULL); - if (dot3) { - int dot3len = strlen(dot3); - if (dot3len > 5) { - while ((p = strstr(buffer, dot3)) != NULL) { - int taillen = strlen(p) - dot3len; - memcpy(p, "/.../", 5); - memmove(p + 5, p + dot3len, taillen + 1); - } - } - } - if (item->util == NULL) item->util = xcalloc(1, sizeof(struct string_list)); string_list_append(item->util, buffer); } } -static int parse_stdin_author(struct shortlog *log, - struct strbuf *out, const char *in) +static int parse_ident(struct shortlog *log, + struct strbuf *out, const char *in) { const char *mailbuf, *namebuf; size_t namelen, maillen; @@ -122,18 +112,33 @@ static int parse_stdin_author(struct shortlog *log, static void read_from_stdin(struct shortlog *log) { - struct strbuf author = STRBUF_INIT; - struct strbuf mapped_author = STRBUF_INIT; + struct strbuf ident = STRBUF_INIT; + struct strbuf mapped_ident = STRBUF_INIT; struct strbuf oneline = STRBUF_INIT; static const char *author_match[2] = { "Author: ", "author " }; static const char *committer_match[2] = { "Commit: ", "committer " }; const char **match; - match = log->committer ? committer_match : author_match; - while (strbuf_getline_lf(&author, stdin) != EOF) { + if (HAS_MULTI_BITS(log->groups)) + die(_("using multiple --group options with stdin is not supported")); + + switch (log->groups) { + case SHORTLOG_GROUP_AUTHOR: + match = author_match; + break; + case SHORTLOG_GROUP_COMMITTER: + match = committer_match; + break; + case SHORTLOG_GROUP_TRAILER: + die(_("using --group=trailer with stdin is not supported")); + default: + BUG("unhandled shortlog group"); + } + + while (strbuf_getline_lf(&ident, stdin) != EOF) { const char *v; - if (!skip_prefix(author.buf, match[0], &v) && - !skip_prefix(author.buf, match[1], &v)) + if (!skip_prefix(ident.buf, match[0], &v) && + !skip_prefix(ident.buf, match[1], &v)) continue; while (strbuf_getline_lf(&oneline, stdin) != EOF && oneline.len) @@ -142,23 +147,64 @@ static void read_from_stdin(struct shortlog *log) !oneline.len) ; /* discard blanks */ - strbuf_reset(&mapped_author); - if (parse_stdin_author(log, &mapped_author, v) < 0) + strbuf_reset(&mapped_ident); + if (parse_ident(log, &mapped_ident, v) < 0) continue; - insert_one_record(log, mapped_author.buf, oneline.buf); + insert_one_record(log, mapped_ident.buf, oneline.buf); } - strbuf_release(&author); - strbuf_release(&mapped_author); + strbuf_release(&ident); + strbuf_release(&mapped_ident); strbuf_release(&oneline); } +static void insert_records_from_trailers(struct shortlog *log, + struct strset *dups, + struct commit *commit, + struct pretty_print_context *ctx, + const char *oneline) +{ + struct trailer_iterator iter; + const char *commit_buffer, *body; + struct strbuf ident = STRBUF_INIT; + + /* + * Using format_commit_message("%B") would be simpler here, but + * this saves us copying the message. + */ + commit_buffer = logmsg_reencode(commit, NULL, ctx->output_encoding); + body = strstr(commit_buffer, "\n\n"); + if (!body) + return; + + trailer_iterator_init(&iter, body); + while (trailer_iterator_advance(&iter)) { + const char *value = iter.val.buf; + + if (!string_list_has_string(&log->trailers, iter.key.buf)) + continue; + + strbuf_reset(&ident); + if (!parse_ident(log, &ident, value)) + value = ident.buf; + + if (!strset_add(dups, value)) + continue; + insert_one_record(log, value, oneline); + } + trailer_iterator_release(&iter); + + strbuf_release(&ident); + unuse_commit_buffer(commit, commit_buffer); +} + void shortlog_add_commit(struct shortlog *log, struct commit *commit) { - struct strbuf author = STRBUF_INIT; + struct strbuf ident = STRBUF_INIT; struct strbuf oneline = STRBUF_INIT; + struct strset dups = STRSET_INIT; struct pretty_print_context ctx = {0}; - const char *fmt; + const char *oneline_str; ctx.fmt = CMIT_FMT_USERFORMAT; ctx.abbrev = log->abbrev; @@ -166,21 +212,38 @@ void shortlog_add_commit(struct shortlog *log, struct commit *commit) ctx.date_mode.type = DATE_NORMAL; ctx.output_encoding = get_log_output_encoding(); - fmt = log->committer ? - (log->email ? "%cN <%cE>" : "%cN") : - (log->email ? "%aN <%aE>" : "%aN"); - - format_commit_message(commit, fmt, &author, &ctx); if (!log->summary) { if (log->user_format) pretty_print_commit(&ctx, commit, &oneline); else format_commit_message(commit, "%s", &oneline, &ctx); } + oneline_str = oneline.len ? oneline.buf : "<none>"; + + if (log->groups & SHORTLOG_GROUP_AUTHOR) { + strbuf_reset(&ident); + format_commit_message(commit, + log->email ? "%aN <%aE>" : "%aN", + &ident, &ctx); + if (!HAS_MULTI_BITS(log->groups) || + strset_add(&dups, ident.buf)) + insert_one_record(log, ident.buf, oneline_str); + } + if (log->groups & SHORTLOG_GROUP_COMMITTER) { + strbuf_reset(&ident); + format_commit_message(commit, + log->email ? "%cN <%cE>" : "%cN", + &ident, &ctx); + if (!HAS_MULTI_BITS(log->groups) || + strset_add(&dups, ident.buf)) + insert_one_record(log, ident.buf, oneline_str); + } + if (log->groups & SHORTLOG_GROUP_TRAILER) { + insert_records_from_trailers(log, &dups, commit, &ctx, oneline_str); + } - insert_one_record(log, author.buf, oneline.len ? oneline.buf : "<none>"); - - strbuf_release(&author); + strset_clear(&dups); + strbuf_release(&ident); strbuf_release(&oneline); } @@ -241,16 +304,40 @@ static int parse_wrap_args(const struct option *opt, const char *arg, int unset) return 0; } +static int parse_group_option(const struct option *opt, const char *arg, int unset) +{ + struct shortlog *log = opt->value; + const char *field; + + if (unset) { + log->groups = 0; + string_list_clear(&log->trailers, 0); + } else if (!strcasecmp(arg, "author")) + log->groups |= SHORTLOG_GROUP_AUTHOR; + else if (!strcasecmp(arg, "committer")) + log->groups |= SHORTLOG_GROUP_COMMITTER; + else if (skip_prefix(arg, "trailer:", &field)) { + log->groups |= SHORTLOG_GROUP_TRAILER; + string_list_append(&log->trailers, field); + } else + return error(_("unknown group type: %s"), arg); + + return 0; +} + + void shortlog_init(struct shortlog *log) { memset(log, 0, sizeof(*log)); - read_mailmap(&log->mailmap, &log->common_repo_prefix); + read_mailmap(&log->mailmap); log->list.strdup_strings = 1; log->wrap = DEFAULT_WRAPLEN; log->in1 = DEFAULT_INDENT1; log->in2 = DEFAULT_INDENT2; + log->trailers.strdup_strings = 1; + log->trailers.cmp = strcasecmp; } int cmd_shortlog(int argc, const char **argv, const char *prefix) @@ -260,17 +347,20 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix) int nongit = !startup_info->have_repository; const struct option options[] = { - OPT_BOOL('c', "committer", &log.committer, - N_("Group by committer rather than author")), + OPT_BIT('c', "committer", &log.groups, + N_("group by committer rather than author"), + SHORTLOG_GROUP_COMMITTER), OPT_BOOL('n', "numbered", &log.sort_by_number, N_("sort output according to the number of commits per author")), OPT_BOOL('s', "summary", &log.summary, - N_("Suppress commit descriptions, only provides commit count")), + N_("suppress commit descriptions, only provides commit count")), OPT_BOOL('e', "email", &log.email, - N_("Show the email address of each author")), + N_("show the email address of each author")), OPT_CALLBACK_F('w', NULL, &log, N_("<w>[,<i1>[,<i2>]]"), - N_("Linewrap output"), PARSE_OPT_OPTARG, + N_("linewrap output"), PARSE_OPT_OPTARG, &parse_wrap_args), + OPT_CALLBACK(0, "group", &log, N_("field"), + N_("group by field"), parse_group_option), OPT_END(), }; @@ -311,6 +401,10 @@ parse_done: log.abbrev = rev.abbrev; log.file = rev.diffopt.file; + if (!log.groups) + log.groups = SHORTLOG_GROUP_AUTHOR; + string_list_sort(&log.trailers); + /* assume HEAD if from a tty */ if (!nongit && !rev.pending.nr && isatty(0)) add_head_to_pending(&rev); diff --git a/builtin/show-ref.c b/builtin/show-ref.c index ae60b4acf2..7f8a5332f8 100644 --- a/builtin/show-ref.c +++ b/builtin/show-ref.c @@ -40,7 +40,7 @@ static void show_one(const char *refname, const struct object_id *oid) if (!deref_tags) return; - if (!peel_ref(refname, &peeled)) { + if (!peel_iterated_oid(oid, &peeled)) { hex = find_unique_abbrev(&peeled, abbrev); printf("%s %s^{}\n", hex, refname); } diff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c index 4003f4d13a..2306a9ad98 100644 --- a/builtin/sparse-checkout.c +++ b/builtin/sparse-checkout.c @@ -22,11 +22,6 @@ static char const * const builtin_sparse_checkout_usage[] = { 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; @@ -46,12 +41,24 @@ static void write_patterns_to_file(FILE *fp, struct pattern_list *pl) } } +static char const * const builtin_sparse_checkout_list_usage[] = { + N_("git sparse-checkout list"), + NULL +}; + static int sparse_checkout_list(int argc, const char **argv) { + static struct option builtin_sparse_checkout_list_options[] = { + OPT_END(), + }; struct pattern_list pl; char *sparse_filename; int res; + argc = parse_options(argc, argv, NULL, + builtin_sparse_checkout_list_options, + builtin_sparse_checkout_list_usage, 0); + memset(&pl, 0, sizeof(pl)); pl.use_cone_patterns = core_sparse_checkout_cone; @@ -560,17 +567,42 @@ static int sparse_checkout_set(int argc, const char **argv, const char *prefix, return modify_pattern_list(argc, argv, m); } +static char const * const builtin_sparse_checkout_reapply_usage[] = { + N_("git sparse-checkout reapply"), + NULL +}; + static int sparse_checkout_reapply(int argc, const char **argv) { + static struct option builtin_sparse_checkout_reapply_options[] = { + OPT_END(), + }; + + argc = parse_options(argc, argv, NULL, + builtin_sparse_checkout_reapply_options, + builtin_sparse_checkout_reapply_usage, 0); + repo_read_index(the_repository); return update_working_directory(NULL); } +static char const * const builtin_sparse_checkout_disable_usage[] = { + N_("git sparse-checkout disable"), + NULL +}; + static int sparse_checkout_disable(int argc, const char **argv) { + static struct option builtin_sparse_checkout_disable_options[] = { + OPT_END(), + }; struct pattern_list pl; struct strbuf match_all = STRBUF_INIT; + argc = parse_options(argc, argv, NULL, + builtin_sparse_checkout_disable_options, + builtin_sparse_checkout_disable_usage, 0); + repo_read_index(the_repository); memset(&pl, 0, sizeof(pl)); diff --git a/builtin/stash.c b/builtin/stash.c index 3f811f3050..ba774cce67 100644 --- a/builtin/stash.c +++ b/builtin/stash.c @@ -87,7 +87,7 @@ static const char * const git_stash_save_usage[] = { NULL }; -static const char *ref_stash = "refs/stash"; +static const char ref_stash[] = "refs/stash"; static struct strbuf stash_index_path = STRBUF_INIT; /* @@ -222,7 +222,7 @@ static int clear_stash(int argc, const char **argv, const char *prefix) PARSE_OPT_STOP_AT_NON_OPTION); if (argc) - return error(_("git stash clear with parameters is " + return error(_("git stash clear with arguments is " "unimplemented")); return do_clear_stash(); @@ -325,35 +325,6 @@ static void add_diff_to_buf(struct diff_queue_struct *q, } } -static int get_newly_staged(struct strbuf *out, struct object_id *c_tree) -{ - struct child_process cp = CHILD_PROCESS_INIT; - const char *c_tree_hex = oid_to_hex(c_tree); - - /* - * diff-index is very similar to diff-tree above, and should be - * converted together with update_index. - */ - cp.git_cmd = 1; - strvec_pushl(&cp.args, "diff-index", "--cached", "--name-only", - "--diff-filter=A", NULL); - strvec_push(&cp.args, c_tree_hex); - return pipe_command(&cp, NULL, 0, out, 0, NULL, 0); -} - -static int update_index(struct strbuf *out) -{ - struct child_process cp = CHILD_PROCESS_INIT; - - /* - * Update-index is very complicated and may need to have a public - * function exposed in order to remove this forking. - */ - cp.git_cmd = 1; - strvec_pushl(&cp.args, "update-index", "--add", "--stdin", NULL); - return pipe_command(&cp, out->buf, out->len, NULL, 0, NULL, 0); -} - static int restore_untracked(struct object_id *u_tree) { int res; @@ -385,6 +356,121 @@ static int restore_untracked(struct object_id *u_tree) return res; } +static void unstage_changes_unless_new(struct object_id *orig_tree) +{ + /* + * When we enter this function, there has been a clean merge of + * relevant trees, and the merge logic always stages whatever merges + * cleanly. We want to unstage those changes, unless it corresponds + * to a file that didn't exist as of orig_tree. + * + * However, if any SKIP_WORKTREE path is modified relative to + * orig_tree, then we want to clear the SKIP_WORKTREE bit and write + * it to the worktree before unstaging. + */ + + struct checkout state = CHECKOUT_INIT; + struct diff_options diff_opts; + struct lock_file lock = LOCK_INIT; + int i; + + /* If any entries have skip_worktree set, we'll have to check 'em out */ + state.force = 1; + state.quiet = 1; + state.refresh_cache = 1; + state.istate = &the_index; + + /* + * Step 1: get a difference between orig_tree (which corresponding + * to the index before a merge was run) and the current index + * (reflecting the changes brought in by the merge). + */ + diff_setup(&diff_opts); + diff_opts.flags.recursive = 1; + diff_opts.detect_rename = 0; + diff_opts.output_format = DIFF_FORMAT_NO_OUTPUT; + diff_setup_done(&diff_opts); + + do_diff_cache(orig_tree, &diff_opts); + diffcore_std(&diff_opts); + + /* Iterate over the paths that changed due to the merge... */ + for (i = 0; i < diff_queued_diff.nr; i++) { + struct diff_filepair *p; + struct cache_entry *ce; + int pos; + + /* Look up the path's position in the current index. */ + p = diff_queued_diff.queue[i]; + pos = index_name_pos(&the_index, p->two->path, + strlen(p->two->path)); + + /* + * Step 2: Place changes in the working tree + * + * Stash is about restoring changes *to the working tree*. + * So if the merge successfully got a new version of some + * path, but left it out of the working tree, then clear the + * SKIP_WORKTREE bit and write it to the working tree. + */ + if (pos >= 0 && ce_skip_worktree(active_cache[pos])) { + struct stat st; + + ce = active_cache[pos]; + if (!lstat(ce->name, &st)) { + /* Conflicting path present; relocate it */ + struct strbuf new_path = STRBUF_INIT; + int fd; + + strbuf_addf(&new_path, + "%s.stash.XXXXXX", ce->name); + fd = xmkstemp(new_path.buf); + close(fd); + printf(_("WARNING: Untracked file in way of " + "tracked file! Renaming\n " + " %s -> %s\n" + " to make room.\n"), + ce->name, new_path.buf); + if (rename(ce->name, new_path.buf)) + die("Failed to move %s to %s\n", + ce->name, new_path.buf); + strbuf_release(&new_path); + } + checkout_entry(ce, &state, NULL, NULL); + ce->ce_flags &= ~CE_SKIP_WORKTREE; + } + + /* + * Step 3: "unstage" changes, as long as they are still tracked + */ + if (p->one->oid_valid) { + /* + * Path existed in orig_tree; restore index entry + * from that tree in order to "unstage" the changes. + */ + int option = ADD_CACHE_OK_TO_REPLACE; + if (pos < 0) + option = ADD_CACHE_OK_TO_ADD; + + ce = make_cache_entry(&the_index, + p->one->mode, + &p->one->oid, + p->one->path, + 0, 0); + add_index_entry(&the_index, ce, option); + } + } + diff_flush(&diff_opts); + + /* + * Step 4: write the new index to disk + */ + repo_hold_locked_index(the_repository, &lock, LOCK_DIE_ON_ERROR); + if (write_locked_index(&the_index, &lock, + COMMIT_LOCK | SKIP_IF_UNCHANGED)) + die(_("Unable to write index.")); +} + static int do_apply_stash(const char *prefix, struct stash_info *info, int index, int quiet) { @@ -419,7 +505,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, ret = apply_cached(&out); strbuf_release(&out); if (ret) - return error(_("conflicts in index." + return error(_("conflicts in index. " "Try without --index.")); discard_cache(); @@ -467,26 +553,7 @@ static int do_apply_stash(const char *prefix, struct stash_info *info, if (reset_tree(&index_tree, 0, 0)) return -1; } else { - struct strbuf out = STRBUF_INIT; - - if (get_newly_staged(&out, &c_tree)) { - strbuf_release(&out); - return -1; - } - - if (reset_tree(&c_tree, 0, 1)) { - strbuf_release(&out); - return -1; - } - - ret = update_index(&out); - strbuf_release(&out); - if (ret) - return -1; - - /* read back the result of update_index() back from the disk */ - discard_cache(); - read_cache(); + unstage_changes_unless_new(&c_tree); } if (!quiet) { @@ -534,11 +601,22 @@ static int apply_stash(int argc, const char **argv, const char *prefix) return ret; } +static int reject_reflog_ent(struct object_id *ooid, struct object_id *noid, + const char *email, timestamp_t timestamp, int tz, + const char *message, void *cb_data) +{ + return 1; +} + +static int reflog_is_empty(const char *refname) +{ + return !for_each_reflog_ent(refname, reject_reflog_ent, NULL); +} + static int do_drop_stash(struct stash_info *info, int quiet) { int ret; struct child_process cp_reflog = CHILD_PROCESS_INIT; - struct child_process cp = CHILD_PROCESS_INIT; /* * reflog does not provide a simple function for deleting refs. One will @@ -559,19 +637,7 @@ static int do_drop_stash(struct stash_info *info, int quiet) info->revision.buf); } - /* - * This could easily be replaced by get_oid, but currently it will throw - * a fatal error when a reflog is empty, which we can not recover from. - */ - cp.git_cmd = 1; - /* Even though --quiet is specified, rev-parse still outputs the hash */ - cp.no_stdout = 1; - strvec_pushl(&cp.args, "rev-parse", "--verify", "--quiet", NULL); - strvec_pushf(&cp.args, "%s@{0}", ref_stash); - ret = run_command(&cp); - - /* do_clear_stash if we just dropped the last stash entry */ - if (ret) + if (reflog_is_empty(ref_stash)) do_clear_stash(); return 0; diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index de5ad73bb8..9d505a6329 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -562,9 +562,9 @@ static int module_foreach(int argc, const char **argv, const char *prefix) struct module_list list = MODULE_LIST_INIT; struct option module_foreach_options[] = { - OPT__QUIET(&info.quiet, N_("Suppress output of entering each submodule command")), + OPT__QUIET(&info.quiet, N_("suppress output of entering each submodule command")), OPT_BOOL(0, "recursive", &info.recursive, - N_("Recurse into nested submodules")), + N_("recurse into nested submodules")), OPT_END() }; @@ -706,7 +706,7 @@ static int module_init(int argc, const char **argv, const char *prefix) int quiet = 0; struct option module_init_options[] = { - OPT__QUIET(&quiet, N_("Suppress output for initializing a submodule")), + OPT__QUIET(&quiet, N_("suppress output for initializing a submodule")), OPT_END() }; @@ -883,8 +883,8 @@ static int module_status(int argc, const char **argv, const char *prefix) int quiet = 0; struct option module_status_options[] = { - OPT__QUIET(&quiet, N_("Suppress submodule status output")), - OPT_BIT(0, "cached", &info.flags, N_("Use commit stored in the index instead of the one stored in the submodule HEAD"), OPT_CACHED), + OPT__QUIET(&quiet, N_("suppress submodule status output")), + OPT_BIT(0, "cached", &info.flags, N_("use commit stored in the index instead of the one stored in the submodule HEAD"), OPT_CACHED), OPT_BIT(0, "recursive", &info.flags, N_("recurse into nested submodules"), OPT_RECURSIVE), OPT_END() }; @@ -1257,7 +1257,7 @@ static int compute_summary_module_list(struct object_id *head_oid, git_config(git_diff_basic_config, NULL); init_revisions(&rev, info->prefix); rev.abbrev = 0; - precompose_argv(diff_args.nr, diff_args.v); + precompose_argv_prefix(diff_args.nr, diff_args.v, NULL); setup_revisions(diff_args.nr, diff_args.v, &rev, NULL); rev.diffopt.output_format = DIFF_FORMAT_NO_OUTPUT | DIFF_FORMAT_CALLBACK; rev.diffopt.format_callback = submodule_summary_callback; @@ -1308,7 +1308,7 @@ static int module_summary(int argc, const char **argv, const char *prefix) }; const char *const git_submodule_helper_usage[] = { - N_("git submodule--helper summary [<options>] [commit] [--] [<path>]"), + N_("git submodule--helper summary [<options>] [<commit>] [--] [<path>]"), NULL }; @@ -1482,9 +1482,9 @@ static int module_sync(int argc, const char **argv, const char *prefix) int recursive = 0; struct option module_sync_options[] = { - OPT__QUIET(&quiet, N_("Suppress output of synchronizing submodule url")), + OPT__QUIET(&quiet, N_("suppress output of synchronizing submodule url")), OPT_BOOL(0, "recursive", &recursive, - N_("Recurse into nested submodules")), + N_("recurse into nested submodules")), OPT_END() }; @@ -1620,9 +1620,9 @@ static int module_deinit(int argc, const char **argv, const char *prefix) int all = 0; struct option module_deinit_options[] = { - OPT__QUIET(&quiet, N_("Suppress submodule status output")), - OPT__FORCE(&force, N_("Remove submodule working trees even if they contain local changes"), 0), - OPT_BOOL(0, "all", &all, N_("Unregister all submodules")), + OPT__QUIET(&quiet, N_("suppress submodule status output")), + OPT__FORCE(&force, N_("remove submodule working trees even if they contain local changes"), 0), + OPT_BOOL(0, "all", &all, N_("unregister all submodules")), OPT_END() }; @@ -2337,7 +2337,7 @@ static int update_clone(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "dissociate", &suc.dissociate, N_("use --reference only while cloning")), OPT_STRING(0, "depth", &suc.depth, "<depth>", - N_("Create a shallow clone truncated to the " + N_("create a shallow clone truncated to the " "specified number of revisions")), OPT_INTEGER('j', "jobs", &suc.max_jobs, N_("parallel jobs")), @@ -2678,7 +2678,7 @@ static int module_set_url(int argc, const char **argv, const char *prefix) char *config_name; struct option options[] = { - OPT__QUIET(&quiet, N_("Suppress output for setting url of a submodule")), + OPT__QUIET(&quiet, N_("suppress output for setting url of a submodule")), OPT_END() }; const char *const usage[] = { diff --git a/builtin/tag.c b/builtin/tag.c index ecf011776d..d403417b56 100644 --- a/builtin/tag.c +++ b/builtin/tag.c @@ -72,10 +72,10 @@ static int list_tags(struct ref_filter *filter, struct ref_sorting *sorting, } typedef int (*each_tag_name_fn)(const char *name, const char *ref, - const struct object_id *oid, const void *cb_data); + const struct object_id *oid, void *cb_data); static int for_each_tag_name(const char **argv, each_tag_name_fn fn, - const void *cb_data) + void *cb_data) { const char **p; struct strbuf ref = STRBUF_INIT; @@ -97,18 +97,42 @@ static int for_each_tag_name(const char **argv, each_tag_name_fn fn, return had_error; } -static int delete_tag(const char *name, const char *ref, - const struct object_id *oid, const void *cb_data) +static int collect_tags(const char *name, const char *ref, + const struct object_id *oid, void *cb_data) { - if (delete_ref(NULL, ref, oid, 0)) - return 1; - printf(_("Deleted tag '%s' (was %s)\n"), name, - find_unique_abbrev(oid, DEFAULT_ABBREV)); + struct string_list *ref_list = cb_data; + + string_list_append(ref_list, ref); + ref_list->items[ref_list->nr - 1].util = oiddup(oid); return 0; } +static int delete_tags(const char **argv) +{ + int result; + struct string_list refs_to_delete = STRING_LIST_INIT_DUP; + struct string_list_item *item; + + result = for_each_tag_name(argv, collect_tags, (void *)&refs_to_delete); + if (delete_refs(NULL, &refs_to_delete, REF_NO_DEREF)) + result = 1; + + for_each_string_list_item(item, &refs_to_delete) { + const char *name = item->string; + struct object_id *oid = item->util; + if (!ref_exists(name)) + printf(_("Deleted tag '%s' (was %s)\n"), + item->string + 10, + find_unique_abbrev(oid, DEFAULT_ABBREV)); + + free(oid); + } + string_list_clear(&refs_to_delete, 0); + return result; +} + static int verify_tag(const char *name, const char *ref, - const struct object_id *oid, const void *cb_data) + const struct object_id *oid, void *cb_data) { int flags; const struct ref_format *format = cb_data; @@ -174,11 +198,17 @@ static void write_tag_body(int fd, const struct object_id *oid) { unsigned long size; enum object_type type; - char *buf, *sp; + char *buf, *sp, *orig; + struct strbuf payload = STRBUF_INIT; + struct strbuf signature = STRBUF_INIT; - buf = read_object_file(oid, &type, &size); + orig = buf = read_object_file(oid, &type, &size); if (!buf) return; + if (parse_signature(buf, size, &payload, &signature)) { + buf = payload.buf; + size = payload.len; + } /* skip header */ sp = strstr(buf, "\n\n"); @@ -187,9 +217,11 @@ static void write_tag_body(int fd, const struct object_id *oid) return; } sp += 2; /* skip the 2 LFs */ - write_or_die(fd, sp, parse_signature(sp, buf + size - sp)); + write_or_die(fd, sp, buf + size - sp); - free(buf); + free(orig); + strbuf_release(&payload); + strbuf_release(&signature); } static int build_tag_object(struct strbuf *buf, int sign, struct object_id *result) @@ -485,7 +517,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) } if (!sorting) sorting = ref_default_sorting(); - ref_sorting_icase_all(sorting, icase); + ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase); filter.ignore_case = icase; if (cmdmode == 'l') { int ret; @@ -512,7 +544,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) if (filter.reachable_from || filter.unreachable_from) die(_("--merged and --no-merged options are only allowed in list mode")); if (cmdmode == 'd') - return for_each_tag_name(argv, delete_tag, NULL); + return delete_tags(argv); if (cmdmode == 'v') { if (format.format && verify_ref_format(&format)) usage_with_options(git_tag_usage, options); @@ -540,7 +572,7 @@ int cmd_tag(int argc, const char **argv, const char *prefix) object_ref = argc == 2 ? argv[1] : "HEAD"; if (argc > 2) - die(_("too many params")); + die(_("too many arguments")); if (get_oid(object_ref, &object)) die(_("Failed to resolve '%s' as a valid ref."), object_ref); diff --git a/builtin/update-ref.c b/builtin/update-ref.c index 8a2df4459c..6029a80544 100644 --- a/builtin/update-ref.c +++ b/builtin/update-ref.c @@ -436,6 +436,8 @@ static void update_refs_stdin(void) switch (state) { case UPDATE_REFS_OPEN: case UPDATE_REFS_STARTED: + if (state == UPDATE_REFS_STARTED && cmd->state == UPDATE_REFS_STARTED) + die("cannot restart ongoing transaction"); /* Do not downgrade a transaction to a non-transaction. */ if (cmd->state >= state) state = cmd->state; @@ -446,7 +448,18 @@ static void update_refs_stdin(void) state = cmd->state; break; case UPDATE_REFS_CLOSED: - die("transaction is closed"); + if (cmd->state != UPDATE_REFS_STARTED) + die("transaction is closed"); + + /* + * Open a new transaction if we're currently closed and + * get a "start". + */ + state = cmd->state; + transaction = ref_transaction_begin(&err); + if (!transaction) + die("%s", err.buf); + break; } diff --git a/builtin/worktree.c b/builtin/worktree.c index 99abaeec6c..1cd5c2016e 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -12,6 +12,7 @@ #include "submodule.h" #include "utf8.h" #include "worktree.h" +#include "quote.h" static const char * const worktree_usage[] = { N_("git worktree add [<options>] <path> [<commit-ish>]"), @@ -67,79 +68,6 @@ static void delete_worktrees_dir_if_empty(void) rmdir(git_path("worktrees")); /* ignore failed removal */ } -/* - * Return true if worktree entry should be pruned, along with the reason for - * pruning. Otherwise, return false and the worktree's path, or NULL if it - * cannot be determined. Caller is responsible for freeing returned path. - */ -static int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath) -{ - struct stat st; - char *path; - int fd; - size_t len; - ssize_t read_result; - - *wtpath = NULL; - if (!is_directory(git_path("worktrees/%s", id))) { - strbuf_addstr(reason, _("not a valid directory")); - return 1; - } - if (file_exists(git_path("worktrees/%s/locked", id))) - return 0; - if (stat(git_path("worktrees/%s/gitdir", id), &st)) { - strbuf_addstr(reason, _("gitdir file does not exist")); - return 1; - } - fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY); - if (fd < 0) { - strbuf_addf(reason, _("unable to read gitdir file (%s)"), - strerror(errno)); - return 1; - } - len = xsize_t(st.st_size); - path = xmallocz(len); - - read_result = read_in_full(fd, path, len); - if (read_result < 0) { - strbuf_addf(reason, _("unable to read gitdir file (%s)"), - strerror(errno)); - close(fd); - free(path); - return 1; - } - close(fd); - - if (read_result != len) { - strbuf_addf(reason, - _("short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"), - (uintmax_t)len, (uintmax_t)read_result); - free(path); - return 1; - } - while (len && (path[len - 1] == '\n' || path[len - 1] == '\r')) - len--; - if (!len) { - strbuf_addstr(reason, _("invalid gitdir file")); - free(path); - return 1; - } - path[len] = '\0'; - if (!file_exists(path)) { - if (stat(git_path("worktrees/%s/index", id), &st) || - st.st_mtime <= expire) { - strbuf_addstr(reason, _("gitdir file points to non-existent location")); - free(path); - return 1; - } else { - *wtpath = path; - return 0; - } - } - *wtpath = path; - return 0; -} - static void prune_worktree(const char *id, const char *reason) { if (show_only || verbose) @@ -195,7 +123,7 @@ static void prune_worktrees(void) if (is_dot_or_dotdot(d->d_name)) continue; strbuf_reset(&reason); - if (should_prune_worktree(d->d_name, &reason, &path)) + if (should_prune_worktree(d->d_name, &reason, &path, expire)) prune_worktree(d->d_name, reason.buf); else if (path) string_list_append(&kept, path)->util = xstrdup(d->d_name); @@ -304,9 +232,9 @@ static void check_candidate_path(const char *path, } if (locked) - die(_("'%s' is a missing but locked worktree;\nuse '%s -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), cmd, path); + die(_("'%s' is a missing but locked worktree;\nuse '%s -f -f' to override, or 'unlock' and 'prune' or 'remove' to clear"), path, cmd); else - die(_("'%s' is a missing but already registered worktree;\nuse '%s -f' to override, or 'prune' or 'remove' to clear"), cmd, path); + die(_("'%s' is a missing but already registered worktree;\nuse '%s -f' to override, or 'prune' or 'remove' to clear"), path, cmd); } static int add_worktree(const char *path, const char *refname, @@ -642,6 +570,8 @@ static int add(int ac, const char **av, const char *prefix) static void show_worktree_porcelain(struct worktree *wt) { + const char *reason; + printf("worktree %s\n", wt->path); if (wt->is_bare) printf("bare\n"); @@ -652,6 +582,20 @@ static void show_worktree_porcelain(struct worktree *wt) else if (wt->head_ref) printf("branch %s\n", wt->head_ref); } + + reason = worktree_lock_reason(wt); + if (reason && *reason) { + struct strbuf sb = STRBUF_INIT; + quote_c_style(reason, &sb, NULL, 0); + printf("locked %s\n", sb.buf); + strbuf_release(&sb); + } else if (reason) + printf("locked\n"); + + reason = worktree_prune_reason(wt, expire); + if (reason) + printf("prunable %s\n", reason); + printf("\n"); } @@ -660,6 +604,7 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len) struct strbuf sb = STRBUF_INIT; int cur_path_len = strlen(wt->path); int path_adj = cur_path_len - utf8_strwidth(wt->path); + const char *reason; strbuf_addf(&sb, "%-*s ", 1 + path_maxlen + path_adj, wt->path); if (wt->is_bare) @@ -676,8 +621,20 @@ static void show_worktree(struct worktree *wt, int path_maxlen, int abbrev_len) } else strbuf_addstr(&sb, "(error)"); } - printf("%s\n", sb.buf); + reason = worktree_lock_reason(wt); + if (verbose && reason && *reason) + strbuf_addf(&sb, "\n\tlocked: %s", reason); + else if (reason) + strbuf_addstr(&sb, " locked"); + + reason = worktree_prune_reason(wt, expire); + if (verbose && reason) + strbuf_addf(&sb, "\n\tprunable: %s", reason); + else if (reason) + strbuf_addstr(&sb, " prunable"); + + printf("%s\n", sb.buf); strbuf_release(&sb); } @@ -720,12 +677,18 @@ static int list(int ac, const char **av, const char *prefix) struct option options[] = { OPT_BOOL(0, "porcelain", &porcelain, N_("machine-readable output")), + OPT__VERBOSE(&verbose, N_("show extended annotations and reasons, if available")), + OPT_EXPIRY_DATE(0, "expire", &expire, + N_("add 'prunable' annotation to worktrees older than <time>")), OPT_END() }; + expire = TIME_MAX; ac = parse_options(ac, av, prefix, options, worktree_usage, 0); if (ac) usage_with_options(worktree_usage, options); + else if (verbose && porcelain) + die(_("--verbose and --porcelain are mutually exclusive")); else { struct worktree **worktrees = get_worktrees(); int path_maxlen = 0, abbrev = DEFAULT_ABBREV, i; @@ -1049,10 +1012,10 @@ static int repair(int ac, const char **av, const char *prefix) int rc = 0; ac = parse_options(ac, av, prefix, options, worktree_usage, 0); - repair_worktrees(report_repair, &rc); p = ac > 0 ? av : self; for (; *p; p++) repair_worktree_at_path(*p, report_repair, &rc); + repair_worktrees(report_repair, &rc); return rc; } |