diff options
Diffstat (limited to 'builtin')
36 files changed, 1199 insertions, 327 deletions
diff --git a/builtin/add.c b/builtin/add.c index 81df0d34de..db2dfa4350 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -138,7 +138,7 @@ static int renormalize_tracked_files(const struct pathspec *pathspec, int flags) continue; /* do not touch non blobs */ if (pathspec && !ce_path_match(&the_index, ce, pathspec, NULL)) continue; - retval |= add_file_to_cache(ce->name, flags | HASH_RENORMALIZE); + retval |= add_file_to_cache(ce->name, flags | ADD_CACHE_RENORMALIZE); } return retval; @@ -177,7 +177,7 @@ static void refresh(int verbose, const struct pathspec *pathspec) die(_("pathspec '%s' did not match any files"), pathspec->items[i].match); } - free(seen); + free(seen); } int run_add_interactive(const char *revision, const char *patch_mode, @@ -240,7 +240,7 @@ static int edit_patch(int argc, const char **argv, const char *prefix) rev.diffopt.output_format = DIFF_FORMAT_PATCH; rev.diffopt.use_color = 0; rev.diffopt.flags.ignore_dirty_submodules = 1; - out = open(file, O_CREAT | O_WRONLY, 0666); + out = open(file, O_CREAT | O_WRONLY | O_TRUNC, 0666); if (out < 0) die(_("Could not open '%s' for writing."), file); rev.diffopt.file = xfdopen(out, "w"); diff --git a/builtin/am.c b/builtin/am.c index ad913ef20e..58a2aef28b 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -36,22 +36,6 @@ #include "repository.h" /** - * Returns 1 if the file is empty or does not exist, 0 otherwise. - */ -static int is_empty_file(const char *filename) -{ - struct stat st; - - if (stat(filename, &st) < 0) { - if (errno == ENOENT) - return 1; - die_errno(_("could not stat %s"), filename); - } - - return !st.st_size; -} - -/** * Returns the length of the first line of msg. */ static int linelen(const char *msg) @@ -1221,7 +1205,7 @@ static int parse_mail(struct am_state *state, const char *mail) goto finish; } - if (is_empty_file(am_path(state, "patch"))) { + if (is_empty_or_missing_file(am_path(state, "patch"))) { printf_ln(_("Patch is empty.")); die_user_resolve(state); } @@ -1804,7 +1788,7 @@ next: resume = 0; } - if (!is_empty_file(am_path(state, "rewritten"))) { + if (!is_empty_or_missing_file(am_path(state, "rewritten"))) { assert(state->rebasing); copy_notes_for_rebase(state); run_post_rewrite_hook(state); @@ -2001,6 +1985,15 @@ static void am_skip(struct am_state *state) if (clean_index(&head, &head)) die(_("failed to clean index")); + if (state->rebasing) { + FILE *fp = xfopen(am_path(state, "rewritten"), "a"); + + assert(!is_null_oid(&state->orig_commit)); + fprintf(fp, "%s ", oid_to_hex(&state->orig_commit)); + fprintf(fp, "%s\n", oid_to_hex(&head)); + fclose(fp); + } + am_next(state); am_load(state); am_run(state, 0); diff --git a/builtin/archive.c b/builtin/archive.c index d2455237ce..45d11669aa 100644 --- a/builtin/archive.c +++ b/builtin/archive.c @@ -27,10 +27,10 @@ static int run_remote_archiver(int argc, const char **argv, const char *remote, const char *exec, const char *name_hint) { - char *buf; int fd[2], i, rv; struct transport *transport; struct remote *_remote; + struct packet_reader reader; _remote = remote_get(remote); if (!_remote->url[0]) @@ -53,18 +53,19 @@ static int run_remote_archiver(int argc, const char **argv, packet_write_fmt(fd[1], "argument %s\n", argv[i]); packet_flush(fd[1]); - buf = packet_read_line(fd[0], NULL); - if (!buf) + packet_reader_init(&reader, fd[0], NULL, 0, + PACKET_READ_CHOMP_NEWLINE | + PACKET_READ_DIE_ON_ERR_PACKET); + + if (packet_reader_read(&reader) != PACKET_READ_NORMAL) die(_("git archive: expected ACK/NAK, got a flush packet")); - if (strcmp(buf, "ACK")) { - if (starts_with(buf, "NACK ")) - die(_("git archive: NACK %s"), buf + 5); - if (starts_with(buf, "ERR ")) - die(_("remote error: %s"), buf + 4); + if (strcmp(reader.line, "ACK")) { + if (starts_with(reader.line, "NACK ")) + die(_("git archive: NACK %s"), reader.line + 5); die(_("git archive: protocol error")); } - if (packet_read_line(fd[0], NULL)) + if (packet_reader_read(&reader) != PACKET_READ_FLUSH) die(_("git archive: expected a flush")); /* Now, start reading from fd[0] and spit it out to stdout */ diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index 417d141c09..c1cff32661 100644 --- a/builtin/bisect--helper.c +++ b/builtin/bisect--helper.c @@ -3,18 +3,58 @@ #include "parse-options.h" #include "bisect.h" #include "refs.h" +#include "dir.h" +#include "argv-array.h" +#include "run-command.h" +#include "prompt.h" +#include "quote.h" static GIT_PATH_FUNC(git_path_bisect_terms, "BISECT_TERMS") static GIT_PATH_FUNC(git_path_bisect_expected_rev, "BISECT_EXPECTED_REV") static GIT_PATH_FUNC(git_path_bisect_ancestors_ok, "BISECT_ANCESTORS_OK") +static GIT_PATH_FUNC(git_path_bisect_start, "BISECT_START") +static GIT_PATH_FUNC(git_path_bisect_head, "BISECT_HEAD") +static GIT_PATH_FUNC(git_path_bisect_log, "BISECT_LOG") +static GIT_PATH_FUNC(git_path_head_name, "head-name") +static GIT_PATH_FUNC(git_path_bisect_names, "BISECT_NAMES") static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --next-all [--no-checkout]"), 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-{old,good}=<term> --term-{new,bad}=<term>]" + "[--no-checkout] [<bad> [<good>...]] [--] [<paths>...]"), NULL }; +struct bisect_terms { + char *term_good; + char *term_bad; +}; + +static void free_terms(struct bisect_terms *terms) +{ + FREE_AND_NULL(terms->term_good); + FREE_AND_NULL(terms->term_bad); +} + +static void set_terms(struct bisect_terms *terms, const char *bad, + const char *good) +{ + free((void *)terms->term_good); + terms->term_good = xstrdup(good); + free((void *)terms->term_bad); + terms->term_bad = xstrdup(bad); +} + +static const char *vocab_bad = "bad|new"; +static const char *vocab_good = "good|old"; + /* * Check whether the string `term` belongs to the set of strings * included in the variable arguments. @@ -106,15 +146,482 @@ static void check_expected_revs(const char **revs, int rev_nr) } } +static int bisect_reset(const char *commit) +{ + struct strbuf branch = STRBUF_INIT; + + if (!commit) { + if (strbuf_read_file(&branch, git_path_bisect_start(), 0) < 1) { + printf(_("We are not bisecting.\n")); + return 0; + } + strbuf_rtrim(&branch); + } else { + struct object_id oid; + + if (get_oid_commit(commit, &oid)) + return error(_("'%s' is not a valid commit"), commit); + strbuf_addstr(&branch, commit); + } + + if (!file_exists(git_path_bisect_head())) { + struct argv_array argv = ARGV_ARRAY_INIT; + + argv_array_pushl(&argv, "checkout", branch.buf, "--", NULL); + if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { + strbuf_release(&branch); + argv_array_clear(&argv); + return error(_("could not check out original" + " HEAD '%s'. Try 'git bisect" + "reset <commit>'."), branch.buf); + } + argv_array_clear(&argv); + } + + strbuf_release(&branch); + return bisect_clean_state(); +} + +static void log_commit(FILE *fp, char *fmt, const char *state, + struct commit *commit) +{ + struct pretty_print_context pp = {0}; + struct strbuf commit_msg = STRBUF_INIT; + char *label = xstrfmt(fmt, state); + + format_commit_message(commit, "%s", &commit_msg, &pp); + + fprintf(fp, "# %s: [%s] %s\n", label, oid_to_hex(&commit->object.oid), + commit_msg.buf); + + strbuf_release(&commit_msg); + free(label); +} + +static int bisect_write(const char *state, const char *rev, + const struct bisect_terms *terms, int nolog) +{ + struct strbuf tag = STRBUF_INIT; + struct object_id oid; + struct commit *commit; + FILE *fp = NULL; + int retval = 0; + + if (!strcmp(state, terms->term_bad)) { + strbuf_addf(&tag, "refs/bisect/%s", state); + } else if (one_of(state, terms->term_good, "skip", NULL)) { + strbuf_addf(&tag, "refs/bisect/%s-%s", state, rev); + } else { + retval = error(_("Bad bisect_write argument: %s"), state); + goto finish; + } + + if (get_oid(rev, &oid)) { + retval = error(_("couldn't get the oid of the rev '%s'"), rev); + goto finish; + } + + if (update_ref(NULL, tag.buf, &oid, NULL, 0, + UPDATE_REFS_MSG_ON_ERR)) { + retval = -1; + goto finish; + } + + fp = fopen(git_path_bisect_log(), "a"); + if (!fp) { + retval = error_errno(_("couldn't open the file '%s'"), git_path_bisect_log()); + goto finish; + } + + commit = lookup_commit_reference(the_repository, &oid); + log_commit(fp, "%s", state, commit); + + if (!nolog) + fprintf(fp, "git bisect %s %s\n", state, rev); + +finish: + if (fp) + fclose(fp); + strbuf_release(&tag); + return retval; +} + +static int check_and_set_terms(struct bisect_terms *terms, const char *cmd) +{ + int has_term_file = !is_empty_or_missing_file(git_path_bisect_terms()); + + if (one_of(cmd, "skip", "start", "terms", NULL)) + return 0; + + if (has_term_file && strcmp(cmd, terms->term_bad) && + strcmp(cmd, terms->term_good)) + return error(_("Invalid command: you're currently in a " + "%s/%s bisect"), terms->term_bad, + terms->term_good); + + if (!has_term_file) { + if (one_of(cmd, "bad", "good", NULL)) { + set_terms(terms, "bad", "good"); + return write_terms(terms->term_bad, terms->term_good); + } + if (one_of(cmd, "new", "old", NULL)) { + set_terms(terms, "new", "old"); + return write_terms(terms->term_bad, terms->term_good); + } + } + + return 0; +} + +static int mark_good(const char *refname, const struct object_id *oid, + int flag, void *cb_data) +{ + int *m_good = (int *)cb_data; + *m_good = 0; + return 1; +} + +static const char *need_bad_and_good_revision_warning = + N_("You need to give me at least one %s and %s revision.\n" + "You can use \"git bisect %s\" and \"git bisect %s\" for that."); + +static const char *need_bisect_start_warning = + N_("You need to start by \"git bisect start\".\n" + "You then need to give me at least one %s and %s revision.\n" + "You can use \"git bisect %s\" and \"git bisect %s\" for that."); + +static int bisect_next_check(const struct bisect_terms *terms, + const char *current_term) +{ + int missing_good = 1, missing_bad = 1, retval = 0; + const char *bad_ref = xstrfmt("refs/bisect/%s", terms->term_bad); + const char *good_glob = xstrfmt("%s-*", terms->term_good); + + if (ref_exists(bad_ref)) + missing_bad = 0; + + for_each_glob_ref_in(mark_good, good_glob, "refs/bisect/", + (void *) &missing_good); + + if (!missing_good && !missing_bad) + goto finish; + + if (!current_term) { + retval = -1; + goto finish; + } + + if (missing_good && !missing_bad && + !strcmp(current_term, terms->term_good)) { + char *yesno; + /* + * have bad (or new) but not good (or old). We could bisect + * although this is less optimum. + */ + warning(_("bisecting only with a %s commit"), terms->term_bad); + if (!isatty(0)) + goto finish; + /* + * TRANSLATORS: Make sure to include [Y] and [n] in your + * translation. The program will only accept English input + * at this point. + */ + yesno = git_prompt(_("Are you sure [Y/n]? "), PROMPT_ECHO); + if (starts_with(yesno, "N") || starts_with(yesno, "n")) + retval = -1; + goto finish; + } + if (!is_empty_or_missing_file(git_path_bisect_start())) { + retval = error(_(need_bad_and_good_revision_warning), + vocab_bad, vocab_good, vocab_bad, vocab_good); + } else { + retval = error(_(need_bisect_start_warning), + vocab_good, vocab_bad, vocab_good, vocab_bad); + } + +finish: + free((void *) good_glob); + free((void *) bad_ref); + return retval; +} + +static int get_terms(struct bisect_terms *terms) +{ + struct strbuf str = STRBUF_INIT; + FILE *fp = NULL; + int res = 0; + + fp = fopen(git_path_bisect_terms(), "r"); + if (!fp) { + res = -1; + goto finish; + } + + free_terms(terms); + strbuf_getline_lf(&str, fp); + terms->term_bad = strbuf_detach(&str, NULL); + strbuf_getline_lf(&str, fp); + terms->term_good = strbuf_detach(&str, NULL); + +finish: + if (fp) + fclose(fp); + strbuf_release(&str); + return res; +} + +static int bisect_terms(struct bisect_terms *terms, const char *option) +{ + if (get_terms(terms)) + return error(_("no terms defined")); + + if (option == NULL) { + printf(_("Your current terms are %s for the old state\n" + "and %s for the new state.\n"), + terms->term_good, terms->term_bad); + return 0; + } + if (one_of(option, "--term-good", "--term-old", NULL)) + printf("%s\n", terms->term_good); + else if (one_of(option, "--term-bad", "--term-new", NULL)) + printf("%s\n", terms->term_bad); + else + return error(_("invalid argument %s for 'git bisect terms'.\n" + "Supported options are: " + "--term-good|--term-old and " + "--term-bad|--term-new."), option); + + return 0; +} + +static int bisect_append_log_quoted(const char **argv) +{ + int retval = 0; + FILE *fp = fopen(git_path_bisect_log(), "a"); + struct strbuf orig_args = STRBUF_INIT; + + if (!fp) + return -1; + + if (fprintf(fp, "git bisect start") < 1) { + retval = -1; + goto finish; + } + + sq_quote_argv(&orig_args, argv); + if (fprintf(fp, "%s\n", orig_args.buf) < 1) + retval = -1; + +finish: + fclose(fp); + strbuf_release(&orig_args); + return retval; +} + +static int bisect_start(struct bisect_terms *terms, int no_checkout, + const char **argv, int argc) +{ + int i, has_double_dash = 0, must_write_terms = 0, bad_seen = 0; + int flags, pathspec_pos, retval = 0; + struct string_list revs = STRING_LIST_INIT_DUP; + struct string_list states = STRING_LIST_INIT_DUP; + struct strbuf start_head = STRBUF_INIT; + struct strbuf bisect_names = STRBUF_INIT; + struct object_id head_oid; + struct object_id oid; + const char *head; + + if (is_bare_repository()) + no_checkout = 1; + + /* + * Check for one bad and then some good revisions + */ + for (i = 0; i < argc; i++) { + if (!strcmp(argv[i], "--")) { + has_double_dash = 1; + break; + } + } + + for (i = 0; i < argc; i++) { + const char *arg = argv[i]; + if (!strcmp(argv[i], "--")) { + break; + } else if (!strcmp(arg, "--no-checkout")) { + no_checkout = 1; + } else if (!strcmp(arg, "--term-good") || + !strcmp(arg, "--term-old")) { + must_write_terms = 1; + free((void *) terms->term_good); + terms->term_good = xstrdup(argv[++i]); + } else if (skip_prefix(arg, "--term-good=", &arg) || + skip_prefix(arg, "--term-old=", &arg)) { + must_write_terms = 1; + free((void *) terms->term_good); + terms->term_good = xstrdup(arg); + } else if (!strcmp(arg, "--term-bad") || + !strcmp(arg, "--term-new")) { + must_write_terms = 1; + free((void *) terms->term_bad); + terms->term_bad = xstrdup(argv[++i]); + } else if (skip_prefix(arg, "--term-bad=", &arg) || + skip_prefix(arg, "--term-new=", &arg)) { + must_write_terms = 1; + free((void *) terms->term_bad); + terms->term_bad = xstrdup(arg); + } else if (starts_with(arg, "--") && + !one_of(arg, "--term-good", "--term-bad", NULL)) { + 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); + + string_list_append(&revs, oid_to_hex(&oid)); + free(commit_id); + } + } + pathspec_pos = i; + + /* + * The user ran "git bisect start <sha1> <sha1>", hence did not + * explicitly specify the terms, but we are already starting to + * set references named with the default terms, and won't be able + * to change afterwards. + */ + if (revs.nr) + must_write_terms = 1; + for (i = 0; i < revs.nr; i++) { + if (bad_seen) { + string_list_append(&states, terms->term_good); + } else { + bad_seen = 1; + string_list_append(&states, terms->term_bad); + } + } + + /* + * Verify HEAD + */ + head = resolve_ref_unsafe("HEAD", 0, &head_oid, &flags); + if (!head) + if (get_oid("HEAD", &head_oid)) + return error(_("bad HEAD - I need a HEAD")); + + /* + * Check if we are bisecting + */ + if (!is_empty_or_missing_file(git_path_bisect_start())) { + /* Reset to the rev from where we started */ + strbuf_read_file(&start_head, git_path_bisect_start(), 0); + strbuf_trim(&start_head); + if (!no_checkout) { + struct argv_array argv = ARGV_ARRAY_INIT; + + argv_array_pushl(&argv, "checkout", start_head.buf, + "--", NULL); + if (run_command_v_opt(argv.argv, RUN_GIT_CMD)) { + retval = error(_("checking out '%s' failed." + " Try 'git bisect start " + "<valid-branch>'."), + start_head.buf); + goto finish; + } + } + } else { + /* Get the rev from where we start. */ + if (!get_oid(head, &head_oid) && + !starts_with(head, "refs/heads/")) { + strbuf_reset(&start_head); + strbuf_addstr(&start_head, oid_to_hex(&head_oid)); + } else if (!get_oid(head, &head_oid) && + skip_prefix(head, "refs/heads/", &head)) { + /* + * This error message should only be triggered by + * cogito usage, and cogito users should understand + * it relates to cg-seek. + */ + if (!is_empty_or_missing_file(git_path_head_name())) + return error(_("won't bisect on cg-seek'ed tree")); + strbuf_addstr(&start_head, head); + } else { + return error(_("bad HEAD - strange symbolic ref")); + } + } + + /* + * 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". + */ + + /* + * Write new start state + */ + write_file(git_path_bisect_start(), "%s\n", start_head.buf); + + if (no_checkout) { + get_oid(start_head.buf, &oid); + if (update_ref(NULL, "BISECT_HEAD", &oid, NULL, 0, + UPDATE_REFS_MSG_ON_ERR)) { + retval = -1; + goto finish; + } + } + + if (pathspec_pos < argc - 1) + sq_quote_argv(&bisect_names, argv + pathspec_pos); + write_file(git_path_bisect_names(), "%s\n", bisect_names.buf); + + for (i = 0; i < states.nr; i++) + if (bisect_write(states.items[i].string, + revs.items[i].string, terms, 1)) { + retval = -1; + goto finish; + } + + if (must_write_terms && write_terms(terms->term_bad, + terms->term_good)) { + retval = -1; + goto finish; + } + + retval = bisect_append_log_quoted(argv); + if (retval) + retval = -1; + +finish: + string_list_clear(&revs, 0); + string_list_clear(&states, 0); + strbuf_release(&start_head); + strbuf_release(&bisect_names); + return retval; +} + int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { enum { NEXT_ALL = 1, WRITE_TERMS, BISECT_CLEAN_STATE, - CHECK_EXPECTED_REVS + CHECK_EXPECTED_REVS, + BISECT_RESET, + BISECT_WRITE, + CHECK_AND_SET_TERMS, + BISECT_NEXT_CHECK, + BISECT_TERMS, + BISECT_START } cmdmode = 0; - int no_checkout = 0; + int no_checkout = 0, res = 0, nolog = 0; struct option options[] = { OPT_CMDMODE(0, "next-all", &cmdmode, N_("perform 'git bisect next'"), NEXT_ALL), @@ -124,13 +631,29 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) N_("cleanup the bisection state"), BISECT_CLEAN_STATE), OPT_CMDMODE(0, "check-expected-revs", &cmdmode, N_("check for expected revs"), CHECK_EXPECTED_REVS), + OPT_CMDMODE(0, "bisect-reset", &cmdmode, + N_("reset the bisection state"), BISECT_RESET), + OPT_CMDMODE(0, "bisect-write", &cmdmode, + N_("write out the bisection state in BISECT_LOG"), BISECT_WRITE), + OPT_CMDMODE(0, "check-and-set-terms", &cmdmode, + N_("check and set terms in a bisection state"), CHECK_AND_SET_TERMS), + OPT_CMDMODE(0, "bisect-next-check", &cmdmode, + N_("check whether bad or good terms exist"), BISECT_NEXT_CHECK), + OPT_CMDMODE(0, "bisect-terms", &cmdmode, + N_("print out the bisect terms"), BISECT_TERMS), + OPT_CMDMODE(0, "bisect-start", &cmdmode, + N_("start the bisect session"), BISECT_START), OPT_BOOL(0, "no-checkout", &no_checkout, N_("update BISECT_HEAD instead of checking out the current commit")), + OPT_BOOL(0, "no-log", &nolog, + N_("no log for BISECT_WRITE ")), OPT_END() }; + struct bisect_terms terms = { .term_good = NULL, .term_bad = NULL }; argc = parse_options(argc, argv, prefix, options, - git_bisect_helper_usage, 0); + git_bisect_helper_usage, + PARSE_OPT_KEEP_DASHDASH | PARSE_OPT_KEEP_UNKNOWN); if (!cmdmode) usage_with_options(git_bisect_helper_usage, options); @@ -149,8 +672,40 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) case CHECK_EXPECTED_REVS: check_expected_revs(argv, argc); return 0; + case BISECT_RESET: + if (argc > 1) + return error(_("--bisect-reset requires either no argument or a commit")); + return !!bisect_reset(argc ? argv[0] : NULL); + case BISECT_WRITE: + if (argc != 4 && argc != 5) + return error(_("--bisect-write requires either 4 or 5 arguments")); + set_terms(&terms, argv[3], argv[2]); + res = bisect_write(argv[0], argv[1], &terms, nolog); + break; + case CHECK_AND_SET_TERMS: + if (argc != 3) + return error(_("--check-and-set-terms requires 3 arguments")); + set_terms(&terms, argv[2], argv[1]); + res = check_and_set_terms(&terms, argv[0]); + break; + case BISECT_NEXT_CHECK: + if (argc != 2 && argc != 3) + return error(_("--bisect-next-check requires 2 or 3 arguments")); + set_terms(&terms, argv[1], argv[0]); + res = bisect_next_check(&terms, argc == 3 ? argv[2] : NULL); + break; + case BISECT_TERMS: + if (argc > 1) + return error(_("--bisect-terms requires 0 or 1 argument")); + res = bisect_terms(&terms, argc == 1 ? argv[0] : NULL); + break; + case BISECT_START: + set_terms(&terms, "bad", "good"); + res = bisect_start(&terms, no_checkout, argv, argc); + break; default: return error("BUG: unknown subcommand '%d'", cmdmode); } - return 0; + free_terms(&terms); + return !!res; } diff --git a/builtin/cat-file.c b/builtin/cat-file.c index a5ca47c697..8487cd7dba 100644 --- a/builtin/cat-file.c +++ b/builtin/cat-file.c @@ -382,7 +382,7 @@ static void batch_one_object(const char *obj_name, { struct object_context ctx; int flags = opt->follow_symlinks ? GET_OID_FOLLOW_SYMLINKS : 0; - enum follow_symlinks_result result; + enum get_oid_result result; result = get_oid_with_context(the_repository, obj_name, flags, &data->oid, &ctx); @@ -391,6 +391,9 @@ static void batch_one_object(const char *obj_name, case MISSING_OBJECT: printf("%s missing\n", obj_name); break; + case SHORT_NAME_AMBIGUOUS: + printf("%s ambiguous\n", obj_name); + break; case DANGLING_SYMLINK: printf("dangling %"PRIuMAX"\n%s\n", (uintmax_t)strlen(obj_name), obj_name); diff --git a/builtin/checkout-index.c b/builtin/checkout-index.c index 345591bc4b..1ac1cc290e 100644 --- a/builtin/checkout-index.c +++ b/builtin/checkout-index.c @@ -68,7 +68,8 @@ static int checkout_file(const char *name, const char *prefix) continue; did_checkout = 1; if (checkout_entry(ce, &state, - to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) + to_tempfile ? topath[ce_stage(ce)] : NULL, + NULL) < 0) errs++; } @@ -112,7 +113,8 @@ static void checkout_all(const char *prefix, int prefix_length) write_tempfile_record(last_ce->name, prefix); } if (checkout_entry(ce, &state, - to_tempfile ? topath[ce_stage(ce)] : NULL) < 0) + to_tempfile ? topath[ce_stage(ce)] : NULL, + NULL) < 0) errs++; last_ce = ce; } diff --git a/builtin/checkout.c b/builtin/checkout.c index 0446cac05e..ece4eb14bf 100644 --- a/builtin/checkout.c +++ b/builtin/checkout.c @@ -45,6 +45,7 @@ struct checkout_opts { int ignore_skipworktree; int ignore_other_worktrees; int show_progress; + int count_checkout_paths; /* * If new checkout options are added, skip_merge_working_tree * should be updated accordingly. @@ -116,7 +117,8 @@ static int update_some(const struct object_id *oid, struct strbuf *base, static int read_tree_some(struct tree *tree, const struct pathspec *pathspec) { - read_tree_recursive(tree, "", 0, 0, pathspec, update_some, NULL); + read_tree_recursive(the_repository, tree, "", 0, 0, + pathspec, update_some, NULL); /* update the index with the given tree's info * for all args, expanding wildcards, and exit @@ -166,12 +168,13 @@ static int check_stages(unsigned stages, const struct cache_entry *ce, int pos) } static int checkout_stage(int stage, const struct cache_entry *ce, int pos, - const struct checkout *state) + const struct checkout *state, int *nr_checkouts) { while (pos < active_nr && !strcmp(active_cache[pos]->name, ce->name)) { if (ce_stage(active_cache[pos]) == stage) - return checkout_entry(active_cache[pos], state, NULL); + return checkout_entry(active_cache[pos], state, + NULL, nr_checkouts); pos++; } if (stage == 2) @@ -180,7 +183,7 @@ static int checkout_stage(int stage, const struct cache_entry *ce, int pos, return error(_("path '%s' does not have their version"), ce->name); } -static int checkout_merged(int pos, const struct checkout *state) +static int checkout_merged(int pos, const struct checkout *state, int *nr_checkouts) { struct cache_entry *ce = active_cache[pos]; const char *path = ce->name; @@ -243,7 +246,7 @@ static int checkout_merged(int pos, const struct checkout *state) ce = make_transient_cache_entry(mode, &oid, path, 2); if (!ce) die(_("make_cache_entry failed for path '%s'"), path); - status = checkout_entry(ce, state, NULL); + status = checkout_entry(ce, state, NULL, nr_checkouts); discard_cache_entry(ce); return status; } @@ -258,6 +261,7 @@ static int checkout_paths(const struct checkout_opts *opts, struct commit *head; int errs = 0; struct lock_file lock_file = LOCK_INIT; + int nr_checkouts = 0; if (opts->track != BRANCH_TRACK_UNSPECIFIED) die(_("'%s' cannot be used with updating paths"), "--track"); @@ -372,17 +376,36 @@ static int checkout_paths(const struct checkout_opts *opts, struct cache_entry *ce = active_cache[pos]; if (ce->ce_flags & CE_MATCHED) { if (!ce_stage(ce)) { - errs |= checkout_entry(ce, &state, NULL); + errs |= checkout_entry(ce, &state, + NULL, &nr_checkouts); continue; } if (opts->writeout_stage) - errs |= checkout_stage(opts->writeout_stage, ce, pos, &state); + errs |= checkout_stage(opts->writeout_stage, + ce, pos, + &state, &nr_checkouts); else if (opts->merge) - errs |= checkout_merged(pos, &state); + errs |= checkout_merged(pos, &state, + &nr_checkouts); pos = skip_same_name(ce, pos) - 1; } } - errs |= finish_delayed_checkout(&state); + errs |= finish_delayed_checkout(&state, &nr_checkouts); + + if (opts->count_checkout_paths) { + if (opts->source_tree) + fprintf_ln(stderr, Q_("Checked out %d path out of %s", + "Checked out %d paths out of %s", + nr_checkouts), + nr_checkouts, + find_unique_abbrev(&opts->source_tree->object.oid, + DEFAULT_ABBREV)); + else + fprintf_ln(stderr, Q_("Checked out %d path out of the index", + "Checked out %d paths out of the index", + nr_checkouts), + nr_checkouts); + } if (write_locked_index(&the_index, &lock_file, COMMIT_LOCK)) die(_("unable to write new index file")); @@ -570,6 +593,14 @@ static int skip_merge_working_tree(const struct checkout_opts *opts, * Remaining variables are not checkout options but used to track state */ + /* + * Do the merge if this is the initial checkout. We cannot use + * is_cache_unborn() here because the index hasn't been loaded yet + * so cache_nr and timestamp.sec are always zero. + */ + if (!file_exists(get_index_file())) + return 0; + return 1; } @@ -1066,6 +1097,7 @@ static int parse_branchname_arg(int argc, const char **argv, has_dash_dash = 1; /* case (3) or (1) */ else if (dash_dash_pos >= 2) die(_("only one reference expected, %d given."), dash_dash_pos); + opts->count_checkout_paths = !opts->quiet && !has_dash_dash; if (!strcmp(arg, "-")) arg = "@{-1}"; diff --git a/builtin/clone.c b/builtin/clone.c index ddb3230d21..50bde99618 100644 --- a/builtin/clone.c +++ b/builtin/clone.c @@ -1137,9 +1137,13 @@ int cmd_clone(int argc, const char **argv, const char *prefix) option_upload_pack); if (filter_options.choice) { + struct strbuf expanded_filter_spec = STRBUF_INIT; + expand_list_objects_filter_spec(&filter_options, + &expanded_filter_spec); transport_set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, - filter_options.filter_spec); + expanded_filter_spec.buf); transport_set_option(transport, TRANS_OPT_FROM_PROMISOR, "1"); + strbuf_release(&expanded_filter_spec); } if (transport->smart_options && !deepen && !filter_options.choice) diff --git a/builtin/commit-graph.c b/builtin/commit-graph.c index c02a3f1221..4ae502754c 100644 --- a/builtin/commit-graph.c +++ b/builtin/commit-graph.c @@ -110,8 +110,8 @@ static int graph_read(int argc, const char **argv) printf(" oid_lookup"); if (graph->chunk_commit_data) printf(" commit_metadata"); - if (graph->chunk_large_edges) - printf(" large_edges"); + if (graph->chunk_extra_edges) + printf(" extra_edges"); printf("\n"); UNLEAK(graph); diff --git a/builtin/commit.c b/builtin/commit.c index 2f4af02a27..2986553d5f 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -352,7 +352,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (write_locked_index(&the_index, &index_lock, 0)) die(_("unable to create temporary index")); - old_index_env = getenv(INDEX_ENVIRONMENT); + old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT)); setenv(INDEX_ENVIRONMENT, get_lock_file_path(&index_lock), 1); if (interactive_add(argc, argv, prefix, patch_interactive) != 0) @@ -362,6 +362,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix setenv(INDEX_ENVIRONMENT, old_index_env, 1); else unsetenv(INDEX_ENVIRONMENT); + FREE_AND_NULL(old_index_env); discard_cache(); read_cache_from(get_lock_file_path(&index_lock)); diff --git a/builtin/config.c b/builtin/config.c index 84385ef165..98d65bc0ad 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -164,7 +164,8 @@ static NORETURN void usage_builtin_config(void) usage_with_options(builtin_config_usage, builtin_config_options); } -static void check_argc(int argc, int min, int max) { +static void check_argc(int argc, int min, int max) +{ if (argc >= min && argc <= max) return; if (min == max) @@ -598,7 +599,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) int nongit = !startup_info->have_repository; char *value; - given_config_source.file = getenv(CONFIG_ENVIRONMENT); + given_config_source.file = xstrdup_or_null(getenv(CONFIG_ENVIRONMENT)); argc = parse_options(argc, argv, prefix, builtin_config_options, builtin_config_usage, diff --git a/builtin/difftool.c b/builtin/difftool.c index eeb9e370b9..a3ea60ea71 100644 --- a/builtin/difftool.c +++ b/builtin/difftool.c @@ -324,7 +324,7 @@ static int checkout_path(unsigned mode, struct object_id *oid, int ret; ce = make_transient_cache_entry(mode, oid, path, 0); - ret = checkout_entry(ce, state, NULL); + ret = checkout_entry(ce, state, NULL, NULL); discard_cache_entry(ce); return ret; diff --git a/builtin/fetch-pack.c b/builtin/fetch-pack.c index 63e69a5801..153a2bd282 100644 --- a/builtin/fetch-pack.c +++ b/builtin/fetch-pack.c @@ -55,6 +55,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) struct oid_array shallow = OID_ARRAY_INIT; struct string_list deepen_not = STRING_LIST_INIT_DUP; struct packet_reader reader; + enum protocol_version version; fetch_if_missing = 0; @@ -217,11 +218,14 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) packet_reader_init(&reader, fd[0], NULL, 0, PACKET_READ_CHOMP_NEWLINE | - PACKET_READ_GENTLE_ON_EOF); + PACKET_READ_GENTLE_ON_EOF | + PACKET_READ_DIE_ON_ERR_PACKET); - switch (discover_version(&reader)) { + version = discover_version(&reader); + switch (version) { case protocol_v2: - die("support for protocol v2 not implemented yet"); + get_remote_refs(fd[1], &reader, &ref, 0, NULL, NULL); + break; case protocol_v1: case protocol_v0: get_remote_heads(&reader, &ref, 0, NULL, &shallow); @@ -231,7 +235,7 @@ int cmd_fetch_pack(int argc, const char **argv, const char *prefix) } ref = fetch_pack(&args, fd, conn, ref, dest, sought, nr_sought, - &shallow, pack_lockfile_ptr, protocol_v0); + &shallow, pack_lockfile_ptr, version); if (pack_lockfile) { printf("lock %s\n", pack_lockfile); fflush(stdout); diff --git a/builtin/fetch.c b/builtin/fetch.c index e0140327aa..39c4210610 100644 --- a/builtin/fetch.c +++ b/builtin/fetch.c @@ -629,9 +629,14 @@ static int find_and_replace(struct strbuf *haystack, const char *needle, const char *placeholder) { - const char *p = strstr(haystack->buf, needle); + const char *p = NULL; int plen, nlen; + nlen = strlen(needle); + if (ends_with(haystack->buf, needle)) + p = haystack->buf + haystack->len - nlen; + else + p = strstr(haystack->buf, needle); if (!p) return 0; @@ -639,7 +644,6 @@ static int find_and_replace(struct strbuf *haystack, return 0; plen = strlen(p); - nlen = strlen(needle); if (plen > nlen && p[nlen] != '/') return 0; @@ -763,9 +767,6 @@ static int update_local_ref(struct ref *ref, what = _("[new ref]"); } - if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && - (recurse_submodules != RECURSE_SUBMODULES_ON)) - check_for_new_submodule_commits(&ref->new_oid); r = s_update_ref(msg, ref, 0); format_display(display, r ? '!' : '*', what, r ? _("unable to update local ref") : NULL, @@ -779,9 +780,6 @@ 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); - if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && - (recurse_submodules != RECURSE_SUBMODULES_ON)) - check_for_new_submodule_commits(&ref->new_oid); r = s_update_ref("fast-forward", ref, 1); format_display(display, r ? '!' : ' ', quickref.buf, r ? _("unable to update local ref") : NULL, @@ -794,9 +792,6 @@ 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); - if ((recurse_submodules != RECURSE_SUBMODULES_OFF) && - (recurse_submodules != RECURSE_SUBMODULES_ON)) - check_for_new_submodule_commits(&ref->new_oid); r = s_update_ref("forced-update", ref, 1); format_display(display, r ? '!' : '+', quickref.buf, r ? _("unable to update local ref") : _("forced update"), @@ -892,6 +887,8 @@ static int store_updated_refs(const char *raw_url, const char *remote_name, ref->force = rm->peer_ref->force; } + if (recurse_submodules != RECURSE_SUBMODULES_OFF) + check_for_new_submodule_commits(&rm->old_oid); if (!strcmp(rm->name, "HEAD")) { kind = ""; @@ -1172,6 +1169,7 @@ static void add_negotiation_tips(struct git_transport_options *smart_options) static struct transport *prepare_transport(struct remote *remote, int deepen) { struct transport *transport; + transport = transport_get(remote, NULL); transport_set_verbosity(transport, verbosity, progress); transport->family = family; @@ -1191,9 +1189,13 @@ static struct transport *prepare_transport(struct remote *remote, int deepen) if (update_shallow) set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes"); if (filter_options.choice) { + struct strbuf expanded_filter_spec = STRBUF_INIT; + expand_list_objects_filter_spec(&filter_options, + &expanded_filter_spec); set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, - filter_options.filter_spec); + expanded_filter_spec.buf); set_option(transport, TRANS_OPT_FROM_PROMISOR, "1"); + strbuf_release(&expanded_filter_spec); } if (negotiation_tip.nr) { if (transport->smart_options) @@ -1478,7 +1480,8 @@ static inline void fetch_one_setup_partial(struct remote *remote) */ if (strcmp(remote->name, repository_format_partial_clone)) { if (filter_options.choice) - die(_("--filter can only be used with the remote configured in core.partialClone")); + die(_("--filter can only be used with the remote " + "configured in extensions.partialclone")); return; } @@ -1646,7 +1649,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix) result = fetch_one(remote, argc, argv, prune_tags_ok); } else { if (filter_options.choice) - die(_("--filter can only be used with the remote configured in core.partialClone")); + die(_("--filter can only be used with the remote " + "configured in extensions.partialclone")); /* TODO should this also die if we have a previous partial-clone? */ result = fetch_multiple(&list); } diff --git a/builtin/fsck.c b/builtin/fsck.c index 46f6ea9baa..bb4227bebc 100644 --- a/builtin/fsck.c +++ b/builtin/fsck.c @@ -402,7 +402,8 @@ out: if (obj->type == OBJ_TREE) free_tree_buffer((struct tree *)obj); if (obj->type == OBJ_COMMIT) - free_commit_buffer((struct commit *)obj); + free_commit_buffer(the_repository->parsed_objects, + (struct commit *)obj); return err; } diff --git a/builtin/gc.c b/builtin/gc.c index 871a56f1c5..020f725acc 100644 --- a/builtin/gc.c +++ b/builtin/gc.c @@ -317,7 +317,7 @@ static void add_repack_all_option(struct string_list *keep_pack) static void add_repack_incremental_option(void) { - argv_array_push(&repack, "--no-write-bitmap-index"); + argv_array_push(&repack, "--no-write-bitmap-index"); } static int need_to_gc(void) @@ -659,8 +659,10 @@ int cmd_gc(int argc, const char **argv, const char *prefix) report_garbage = report_pack_garbage; reprepare_packed_git(the_repository); - if (pack_garbage.nr > 0) + if (pack_garbage.nr > 0) { + close_all_packs(the_repository->objects); clean_pack_garbage(); + } if (gc_write_commit_graph) write_commit_graph_reachable(get_object_directory(), 0, diff --git a/builtin/grep.c b/builtin/grep.c index 39a8e9d4a3..580fd38f41 100644 --- a/builtin/grep.c +++ b/builtin/grep.c @@ -405,8 +405,10 @@ static int grep_submodule(struct grep_opt *opt, const struct object_id *oid, const char *filename, const char *path) { + struct repository subrepo; struct repository *superproject = opt->repo; - struct repository submodule; + const struct submodule *sub = submodule_from_path(superproject, + &null_oid, path); struct grep_opt subopt; int hit; @@ -423,12 +425,12 @@ static int grep_submodule(struct grep_opt *opt, return 0; } - if (repo_submodule_init(&submodule, superproject, path)) { + if (repo_submodule_init(&subrepo, superproject, sub)) { grep_read_unlock(); return 0; } - repo_read_gitmodules(&submodule); + repo_read_gitmodules(&subrepo); /* * NEEDSWORK: This adds the submodule's object directory to the list of @@ -440,11 +442,11 @@ static int grep_submodule(struct grep_opt *opt, * store is no longer global and instead is a member of the repository * object. */ - add_to_alternates_memory(submodule.objects->odb->path); + add_to_alternates_memory(subrepo.objects->odb->path); grep_read_unlock(); memcpy(&subopt, opt, sizeof(subopt)); - subopt.repo = &submodule; + subopt.repo = &subrepo; if (oid) { struct object *object; @@ -475,7 +477,7 @@ static int grep_submodule(struct grep_opt *opt, hit = grep_cache(&subopt, pathspec, 1); } - repo_clear(&submodule); + repo_clear(&subrepo); return hit; } @@ -561,7 +563,8 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, if (match != all_entries_interesting) { strbuf_addstr(&name, base->buf + tn_len); - match = tree_entry_interesting(&entry, &name, + match = tree_entry_interesting(repo->index, + &entry, &name, 0, pathspec); strbuf_setlen(&name, name_base_len); @@ -574,7 +577,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, strbuf_add(base, entry.path, te_len); if (S_ISREG(entry.mode)) { - hit |= grep_oid(opt, entry.oid, base->buf, tn_len, + hit |= grep_oid(opt, &entry.oid, base->buf, tn_len, check_attr ? base->buf + tn_len : NULL); } else if (S_ISDIR(entry.mode)) { enum object_type type; @@ -582,10 +585,10 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, void *data; unsigned long size; - data = lock_and_read_oid_file(entry.oid, &type, &size); + data = lock_and_read_oid_file(&entry.oid, &type, &size); if (!data) die(_("unable to read tree (%s)"), - oid_to_hex(entry.oid)); + oid_to_hex(&entry.oid)); strbuf_addch(base, '/'); init_tree_desc(&sub, data, size); @@ -593,7 +596,7 @@ static int grep_tree(struct grep_opt *opt, const struct pathspec *pathspec, check_attr); free(data); } else if (recurse_submodules && S_ISGITLINK(entry.mode)) { - hit |= grep_submodule(opt, pathspec, entry.oid, + hit |= grep_submodule(opt, pathspec, &entry.oid, base->buf, base->buf + tn_len); } diff --git a/builtin/init-db.c b/builtin/init-db.c index 41faffd28d..93eff7618c 100644 --- a/builtin/init-db.c +++ b/builtin/init-db.c @@ -542,8 +542,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR * without --bare. Catch the error early. */ - git_dir = getenv(GIT_DIR_ENVIRONMENT); - work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT); + git_dir = xstrdup_or_null(getenv(GIT_DIR_ENVIRONMENT)); + work_tree = xstrdup_or_null(getenv(GIT_WORK_TREE_ENVIRONMENT)); if ((!git_dir || is_bare_repository_cfg == 1) && work_tree) die(_("%s (or --work-tree=<directory>) not allowed without " "specifying %s (or --git-dir=<directory>)"), @@ -582,6 +582,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix) } UNLEAK(real_git_dir); + UNLEAK(git_dir); + UNLEAK(work_tree); flags |= INIT_DB_EXIST_OK; return init_db(git_dir, real_git_dir, template_dir, flags); diff --git a/builtin/log.c b/builtin/log.c index 195ff0b471..57869267d8 100644 --- a/builtin/log.c +++ b/builtin/log.c @@ -204,7 +204,7 @@ static void cmd_log_init_finish(int argc, const char **argv, const char *prefix, rev->diffopt.filter || rev->diffopt.flags.follow_renames) rev->always_show_header = 0; - if (source) { + if (source || w.source) { init_revision_sources(&revision_sources); rev->sources = &revision_sources; } @@ -398,7 +398,8 @@ static int cmd_log_walk(struct rev_info *rev) * We may show a given commit multiple times when * walking the reflogs. */ - free_commit_buffer(commit); + free_commit_buffer(the_repository->parsed_objects, + commit); free_commit_list(commit->parents); commit->parents = NULL; } @@ -643,8 +644,9 @@ int cmd_show(int argc, const char **argv, const char *prefix) diff_get_color_opt(&rev.diffopt, DIFF_COMMIT), name, diff_get_color_opt(&rev.diffopt, DIFF_RESET)); - read_tree_recursive((struct tree *)o, "", 0, 0, &match_all, - show_tree_object, rev.diffopt.file); + read_tree_recursive(the_repository, (struct tree *)o, "", + 0, 0, &match_all, show_tree_object, + rev.diffopt.file); rev.shown_one = 1; break; case OBJ_COMMIT: @@ -1941,7 +1943,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix) open_next_file(rev.numbered_files ? NULL : commit, NULL, &rev, quiet)) die(_("Failed to create output files")); shown = log_tree_commit(&rev, commit); - free_commit_buffer(commit); + free_commit_buffer(the_repository->parsed_objects, + commit); /* We put one extra blank line between formatted * patches and this flag is used by log-tree code diff --git a/builtin/ls-files.c b/builtin/ls-files.c index 7cc7ec22c9..29a8762d46 100644 --- a/builtin/ls-files.c +++ b/builtin/ls-files.c @@ -205,17 +205,19 @@ static void show_files(struct repository *repo, struct dir_struct *dir); static void show_submodule(struct repository *superproject, struct dir_struct *dir, const char *path) { - struct repository submodule; + struct repository subrepo; + const struct submodule *sub = submodule_from_path(superproject, + &null_oid, path); - if (repo_submodule_init(&submodule, superproject, path)) + if (repo_submodule_init(&subrepo, superproject, sub)) return; - if (repo_read_index(&submodule) < 0) + if (repo_read_index(&subrepo) < 0) die("index file corrupt"); - show_files(&submodule, dir); + show_files(&subrepo, dir); - repo_clear(&submodule); + repo_clear(&subrepo); } static void show_ce(struct repository *repo, struct dir_struct *dir, @@ -440,7 +442,7 @@ void overlay_tree_on_index(struct index_state *istate, PATHSPEC_PREFER_CWD, prefix, matchbuf); } else memset(&pathspec, 0, sizeof(pathspec)); - if (read_tree(tree, 1, &pathspec, istate)) + if (read_tree(the_repository, tree, 1, &pathspec, istate)) die("unable to read tree entries %s", tree_name); for (i = 0; i < istate->cache_nr; i++) { diff --git a/builtin/ls-tree.c b/builtin/ls-tree.c index 7d581d6463..7cad3f24eb 100644 --- a/builtin/ls-tree.c +++ b/builtin/ls-tree.c @@ -185,5 +185,6 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix) tree = parse_tree_indirect(&oid); if (!tree) die("not a tree object"); - return !!read_tree_recursive(tree, "", 0, 0, &pathspec, show_tree, NULL); + return !!read_tree_recursive(the_repository, tree, "", 0, 0, + &pathspec, show_tree, NULL); } diff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c index 4864f7b22f..5b910e351e 100644 --- a/builtin/merge-recursive.c +++ b/builtin/merge-recursive.c @@ -7,16 +7,16 @@ static const char builtin_merge_recursive_usage[] = "git %s <base>... -- <head> <remote> ..."; -static const char *better_branch_name(const char *branch) +static char *better_branch_name(const char *branch) { static char githead_env[8 + GIT_MAX_HEXSZ + 1]; char *name; if (strlen(branch) != the_hash_algo->hexsz) - return branch; + return xstrdup(branch); xsnprintf(githead_env, sizeof(githead_env), "GITHEAD_%s", branch); name = getenv(githead_env); - return name ? name : branch; + return xstrdup(name ? name : branch); } int cmd_merge_recursive(int argc, const char **argv, const char *prefix) @@ -26,6 +26,7 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) int i, failed; struct object_id h1, h2; struct merge_options o; + char *better1, *better2; struct commit *result; init_merge_options(&o, the_repository); @@ -70,13 +71,17 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix) if (get_oid(o.branch2, &h2)) die(_("could not resolve ref '%s'"), o.branch2); - o.branch1 = better_branch_name(o.branch1); - o.branch2 = better_branch_name(o.branch2); + o.branch1 = better1 = better_branch_name(o.branch1); + o.branch2 = better2 = better_branch_name(o.branch2); if (o.verbosity >= 3) printf(_("Merging %s with %s\n"), o.branch1, o.branch2); failed = merge_recursive_generic(&o, &h1, &h2, bases_count, bases, &result); + + free(better1); + free(better2); + if (failed < 0) return 128; /* die() error code */ return failed; diff --git a/builtin/merge-tree.c b/builtin/merge-tree.c index 53719e0b9d..34ca0258b1 100644 --- a/builtin/merge-tree.c +++ b/builtin/merge-tree.c @@ -156,15 +156,15 @@ static void show_result(void) /* An empty entry never compares same, not even to another empty entry */ static int same_entry(struct name_entry *a, struct name_entry *b) { - return a->oid && - b->oid && - oideq(a->oid, b->oid) && + return !is_null_oid(&a->oid) && + !is_null_oid(&b->oid) && + oideq(&a->oid, &b->oid) && a->mode == b->mode; } static int both_empty(struct name_entry *a, struct name_entry *b) { - return !(a->oid || b->oid); + return is_null_oid(&a->oid) && is_null_oid(&b->oid); } static struct merge_list *create_entry(unsigned stage, unsigned mode, const struct object_id *oid, const char *path) @@ -180,7 +180,7 @@ static struct merge_list *create_entry(unsigned stage, unsigned mode, const stru static char *traverse_path(const struct traverse_info *info, const struct name_entry *n) { - char *path = xmallocz(traverse_path_len(info, n)); + char *path = xmallocz(traverse_path_len(info, n) + the_hash_algo->rawsz); return make_traverse_path(path, info, n); } @@ -194,8 +194,8 @@ static void resolve(const struct traverse_info *info, struct name_entry *ours, s return; path = traverse_path(info, result); - orig = create_entry(2, ours->mode, ours->oid, path); - final = create_entry(0, result->mode, result->oid, path); + orig = create_entry(2, ours->mode, &ours->oid, path); + final = create_entry(0, result->mode, &result->oid, path); final->link = orig; @@ -219,7 +219,7 @@ static void unresolved_directory(const struct traverse_info *info, newbase = traverse_path(info, p); -#define ENTRY_OID(e) (((e)->mode && S_ISDIR((e)->mode)) ? (e)->oid : NULL) +#define ENTRY_OID(e) (((e)->mode && S_ISDIR((e)->mode)) ? &(e)->oid : NULL) buf0 = fill_tree_descriptor(t + 0, ENTRY_OID(n + 0)); buf1 = fill_tree_descriptor(t + 1, ENTRY_OID(n + 1)); buf2 = fill_tree_descriptor(t + 2, ENTRY_OID(n + 2)); @@ -245,7 +245,7 @@ static struct merge_list *link_entry(unsigned stage, const struct traverse_info path = entry->path; else path = traverse_path(info, n); - link = create_entry(stage, n->mode, n->oid, path); + link = create_entry(stage, n->mode, &n->oid, path); link->link = entry; return link; } @@ -320,7 +320,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s } if (same_entry(entry+0, entry+1)) { - if (entry[2].oid && !S_ISDIR(entry[2].mode)) { + if (!is_null_oid(&entry[2].oid) && !S_ISDIR(entry[2].mode)) { /* We did not touch, they modified -- take theirs */ resolve(info, entry+1, entry+2); return mask; @@ -348,7 +348,7 @@ static void merge_trees(struct tree_desc t[3], const char *base) setup_traverse_info(&info, base); info.fn = threeway_callback; - traverse_trees(3, t, &info); + traverse_trees(&the_index, 3, t, &info); } static void *get_tree_descriptor(struct tree_desc *desc, const char *rev) diff --git a/builtin/pack-objects.c b/builtin/pack-objects.c index d9d3b90b23..ffef8dcf2f 100644 --- a/builtin/pack-objects.c +++ b/builtin/pack-objects.c @@ -1334,7 +1334,7 @@ static void add_pbase_object(struct tree_desc *tree, if (cmp < 0) return; if (name[cmplen] != '/') { - add_object_entry(entry.oid, + add_object_entry(&entry.oid, object_type(entry.mode), fullname, 1); return; @@ -1345,7 +1345,7 @@ static void add_pbase_object(struct tree_desc *tree, const char *down = name+cmplen+1; int downlen = name_cmp_len(down); - tree = pbase_tree_get(entry.oid); + tree = pbase_tree_get(&entry.oid); if (!tree) return; init_tree_desc(&sub, tree->tree_data, tree->tree_size); @@ -1953,11 +1953,6 @@ static int delta_cacheable(unsigned long src_size, unsigned long trg_size, return 0; } -/* Protect access to object database */ -static pthread_mutex_t read_mutex; -#define read_lock() pthread_mutex_lock(&read_mutex) -#define read_unlock() pthread_mutex_unlock(&read_mutex) - /* Protect delta_cache_size */ static pthread_mutex_t cache_mutex; #define cache_lock() pthread_mutex_lock(&cache_mutex) @@ -1993,11 +1988,11 @@ unsigned long oe_get_size_slow(struct packing_data *pack, unsigned long used, avail, size; if (e->type_ != OBJ_OFS_DELTA && e->type_ != OBJ_REF_DELTA) { - read_lock(); + packing_data_lock(&to_pack); if (oid_object_info(the_repository, &e->idx.oid, &size) < 0) die(_("unable to get size of %s"), oid_to_hex(&e->idx.oid)); - read_unlock(); + packing_data_unlock(&to_pack); return size; } @@ -2005,7 +2000,7 @@ unsigned long oe_get_size_slow(struct packing_data *pack, if (!p) BUG("when e->type is a delta, it must belong to a pack"); - read_lock(); + packing_data_lock(&to_pack); w_curs = NULL; buf = use_pack(p, &w_curs, e->in_pack_offset, &avail); used = unpack_object_header_buffer(buf, avail, &type, &size); @@ -2014,7 +2009,7 @@ unsigned long oe_get_size_slow(struct packing_data *pack, oid_to_hex(&e->idx.oid)); unuse_pack(&w_curs); - read_unlock(); + packing_data_unlock(&to_pack); return size; } @@ -2076,9 +2071,9 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, /* Load data if not already done */ if (!trg->data) { - read_lock(); + packing_data_lock(&to_pack); trg->data = read_object_file(&trg_entry->idx.oid, &type, &sz); - read_unlock(); + packing_data_unlock(&to_pack); if (!trg->data) die(_("object %s cannot be read"), oid_to_hex(&trg_entry->idx.oid)); @@ -2089,9 +2084,9 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, *mem_usage += sz; } if (!src->data) { - read_lock(); + packing_data_lock(&to_pack); src->data = read_object_file(&src_entry->idx.oid, &type, &sz); - read_unlock(); + packing_data_unlock(&to_pack); if (!src->data) { if (src_entry->preferred_base) { static int warned = 0; @@ -2337,9 +2332,9 @@ static void find_deltas(struct object_entry **list, unsigned *list_size, static void try_to_free_from_threads(size_t size) { - read_lock(); + packing_data_lock(&to_pack); release_pack_memory(size); - read_unlock(); + packing_data_unlock(&to_pack); } static try_to_free_t old_try_to_free_routine; @@ -2381,7 +2376,6 @@ static pthread_cond_t progress_cond; */ static void init_threaded_search(void) { - init_recursive_mutex(&read_mutex); pthread_mutex_init(&cache_mutex, NULL); pthread_mutex_init(&progress_mutex, NULL); pthread_cond_init(&progress_cond, NULL); @@ -2392,7 +2386,6 @@ static void cleanup_threaded_search(void) { set_try_to_free_routine(old_try_to_free_routine); pthread_cond_destroy(&progress_cond); - pthread_mutex_destroy(&read_mutex); pthread_mutex_destroy(&cache_mutex); pthread_mutex_destroy(&progress_mutex); } @@ -3084,14 +3077,16 @@ static void record_recent_commit(struct commit *commit, void *data) static void get_object_list(int ac, const char **av) { struct rev_info revs; + struct setup_revision_opt s_r_opt = { + .allow_exclude_promisor_objects = 1, + }; char line[1000]; int flags = 0; int save_warning; repo_init_revisions(the_repository, &revs, NULL); save_commit_buffer = 0; - revs.allow_exclude_promisor_objects_opt = 1; - setup_revisions(ac, av, &revs, NULL); + setup_revisions(ac, av, &revs, &s_r_opt); /* make sure shallows are read */ is_repository_shallow(the_repository); diff --git a/builtin/prune.c b/builtin/prune.c index e42653b99c..1ec9ddd751 100644 --- a/builtin/prune.c +++ b/builtin/prune.c @@ -120,7 +120,6 @@ int cmd_prune(int argc, const char **argv, const char *prefix) save_commit_buffer = 0; read_replace_refs = 0; ref_paranoia = 1; - revs.allow_exclude_promisor_objects_opt = 1; repo_init_revisions(the_repository, &revs, prefix); argc = parse_options(argc, argv, prefix, options, prune_usage, 0); diff --git a/builtin/push.c b/builtin/push.c index ee1e842027..021dd3b1e4 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -143,7 +143,9 @@ static int push_url_of_remote(struct remote *remote, const char ***url_p) return remote->url_nr; } -static NORETURN int die_push_simple(struct branch *branch, struct remote *remote) { +static NORETURN int die_push_simple(struct branch *branch, + struct remote *remote) +{ /* * There's no point in using shorten_unambiguous_ref here, * as the ambiguity would be on the remote side, not what diff --git a/builtin/rebase--interactive.c b/builtin/rebase--interactive.c index 6895322d61..888390f911 100644 --- a/builtin/rebase--interactive.c +++ b/builtin/rebase--interactive.c @@ -194,6 +194,8 @@ int cmd_rebase__interactive(int argc, const char **argv, const char *prefix) OPT_STRING(0, "onto-name", &onto_name, N_("onto-name"), N_("onto name")), OPT_STRING(0, "cmd", &cmd, N_("cmd"), N_("the command to run")), OPT_RERERE_AUTOUPDATE(&opts.allow_rerere_auto), + OPT_BOOL(0, "reschedule-failed-exec", &opts.reschedule_failed_exec, + N_("automatically re-schedule any `exec` that fails")), OPT_END() }; diff --git a/builtin/rebase.c b/builtin/rebase.c index b667837276..0b039319e1 100644 --- a/builtin/rebase.c +++ b/builtin/rebase.c @@ -105,6 +105,7 @@ struct rebase_options { int rebase_merges, rebase_cousins; char *strategy, *strategy_opts; struct strbuf git_format_patch_opt; + int reschedule_failed_exec; }; static int is_interactive(struct rebase_options *opts) @@ -123,7 +124,7 @@ static void imply_interactive(struct rebase_options *opts, const char *option) case REBASE_PRESERVE_MERGES: break; case REBASE_MERGE: - /* we silently *upgrade* --merge to --interactive if needed */ + /* we now implement --merge via --interactive */ default: opts->type = REBASE_INTERACTIVE; /* implied */ break; @@ -186,10 +187,7 @@ static int read_basic_state(struct rebase_options *opts) if (get_oid(buf.buf, &opts->orig_head)) return error(_("invalid orig-head: '%s'"), buf.buf); - strbuf_reset(&buf); - if (read_one(state_dir_path("quiet", opts), &buf)) - return -1; - if (buf.len) + if (file_exists(state_dir_path("quiet", opts))) opts->flags &= ~REBASE_NO_QUIET; else opts->flags |= REBASE_NO_QUIET; @@ -247,6 +245,37 @@ static int read_basic_state(struct rebase_options *opts) return 0; } +static int write_basic_state(struct rebase_options *opts) +{ + write_file(state_dir_path("head-name", opts), "%s", + opts->head_name ? opts->head_name : "detached HEAD"); + write_file(state_dir_path("onto", opts), "%s", + opts->onto ? oid_to_hex(&opts->onto->object.oid) : ""); + write_file(state_dir_path("orig-head", opts), "%s", + oid_to_hex(&opts->orig_head)); + write_file(state_dir_path("quiet", opts), "%s", + opts->flags & REBASE_NO_QUIET ? "" : "t"); + if (opts->flags & REBASE_VERBOSE) + write_file(state_dir_path("verbose", opts), "%s", ""); + if (opts->strategy) + write_file(state_dir_path("strategy", opts), "%s", + opts->strategy); + if (opts->strategy_opts) + write_file(state_dir_path("strategy_opts", opts), "%s", + opts->strategy_opts); + if (opts->allow_rerere_autoupdate >= 0) + write_file(state_dir_path("allow_rerere_autoupdate", opts), + "-%s-rerere-autoupdate", + opts->allow_rerere_autoupdate ? "" : "-no"); + if (opts->gpg_sign_opt) + write_file(state_dir_path("gpg_sign_opt", opts), "%s", + opts->gpg_sign_opt); + if (opts->signoff) + write_file(state_dir_path("strategy", opts), "--signoff"); + + return 0; +} + static int apply_autostash(struct rebase_options *opts) { const char *path = state_dir_path("autostash", opts); @@ -334,6 +363,161 @@ static void add_var(struct strbuf *buf, const char *name, const char *value) } } +#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION" + +#define RESET_HEAD_DETACH (1<<0) +#define RESET_HEAD_HARD (1<<1) +#define RESET_HEAD_RUN_POST_CHECKOUT_HOOK (1<<2) +#define RESET_HEAD_REFS_ONLY (1<<3) + +static int reset_head(struct object_id *oid, const char *action, + const char *switch_to_branch, unsigned flags, + const char *reflog_orig_head, const char *reflog_head) +{ + unsigned detach_head = flags & RESET_HEAD_DETACH; + unsigned reset_hard = flags & RESET_HEAD_HARD; + unsigned run_hook = flags & RESET_HEAD_RUN_POST_CHECKOUT_HOOK; + unsigned refs_only = flags & RESET_HEAD_REFS_ONLY; + struct object_id head_oid; + struct tree_desc desc[2] = { { NULL }, { NULL } }; + struct lock_file lock = LOCK_INIT; + struct unpack_trees_options unpack_tree_opts; + struct tree *tree; + const char *reflog_action; + struct strbuf msg = STRBUF_INIT; + size_t prefix_len; + struct object_id *orig = NULL, oid_orig, + *old_orig = NULL, oid_old_orig; + int ret = 0, nr = 0; + + if (switch_to_branch && !starts_with(switch_to_branch, "refs/")) + BUG("Not a fully qualified branch: '%s'", switch_to_branch); + + if (!refs_only && hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) { + ret = -1; + goto leave_reset_head; + } + + if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) { + ret = error(_("could not determine HEAD revision")); + goto leave_reset_head; + } + + if (!oid) + oid = &head_oid; + + if (refs_only) + goto reset_head_refs; + + memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts)); + setup_unpack_trees_porcelain(&unpack_tree_opts, action); + unpack_tree_opts.head_idx = 1; + unpack_tree_opts.src_index = the_repository->index; + unpack_tree_opts.dst_index = the_repository->index; + unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge; + unpack_tree_opts.update = 1; + unpack_tree_opts.merge = 1; + if (!detach_head) + unpack_tree_opts.reset = 1; + + if (repo_read_index_unmerged(the_repository) < 0) { + ret = error(_("could not read index")); + goto leave_reset_head; + } + + if (!reset_hard && !fill_tree_descriptor(&desc[nr++], &head_oid)) { + ret = error(_("failed to find tree of %s"), + oid_to_hex(&head_oid)); + goto leave_reset_head; + } + + if (!fill_tree_descriptor(&desc[nr++], oid)) { + ret = error(_("failed to find tree of %s"), oid_to_hex(oid)); + goto leave_reset_head; + } + + if (unpack_trees(nr, desc, &unpack_tree_opts)) { + ret = -1; + goto leave_reset_head; + } + + tree = parse_tree_indirect(oid); + prime_cache_tree(the_repository, the_repository->index, tree); + + if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK) < 0) { + ret = error(_("could not write index")); + goto leave_reset_head; + } + +reset_head_refs: + reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); + strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase"); + prefix_len = msg.len; + + if (!get_oid("ORIG_HEAD", &oid_old_orig)) + old_orig = &oid_old_orig; + if (!get_oid("HEAD", &oid_orig)) { + orig = &oid_orig; + if (!reflog_orig_head) { + strbuf_addstr(&msg, "updating ORIG_HEAD"); + reflog_orig_head = msg.buf; + } + update_ref(reflog_orig_head, "ORIG_HEAD", orig, old_orig, 0, + UPDATE_REFS_MSG_ON_ERR); + } else if (old_orig) + delete_ref(NULL, "ORIG_HEAD", old_orig, 0); + if (!reflog_head) { + strbuf_setlen(&msg, prefix_len); + strbuf_addstr(&msg, "updating HEAD"); + reflog_head = msg.buf; + } + if (!switch_to_branch) + ret = update_ref(reflog_head, "HEAD", oid, orig, + detach_head ? REF_NO_DEREF : 0, + UPDATE_REFS_MSG_ON_ERR); + else { + ret = update_ref(reflog_orig_head, switch_to_branch, oid, + NULL, 0, UPDATE_REFS_MSG_ON_ERR); + if (!ret) + ret = create_symref("HEAD", switch_to_branch, + reflog_head); + } + if (run_hook) + run_hook_le(NULL, "post-checkout", + oid_to_hex(orig ? orig : &null_oid), + oid_to_hex(oid), "1", NULL); + +leave_reset_head: + strbuf_release(&msg); + rollback_lock_file(&lock); + while (nr) + free((void *)desc[--nr].buffer); + return ret; +} + +static int move_to_original_branch(struct rebase_options *opts) +{ + struct strbuf orig_head_reflog = STRBUF_INIT, head_reflog = STRBUF_INIT; + int ret; + + if (!opts->head_name) + return 0; /* nothing to move back to */ + + if (!opts->onto) + BUG("move_to_original_branch without onto"); + + strbuf_addf(&orig_head_reflog, "rebase finished: %s onto %s", + opts->head_name, oid_to_hex(&opts->onto->object.oid)); + strbuf_addf(&head_reflog, "rebase finished: returning to %s", + opts->head_name); + ret = reset_head(NULL, "", opts->head_name, RESET_HEAD_REFS_ONLY, + orig_head_reflog.buf, head_reflog.buf); + + strbuf_release(&orig_head_reflog); + strbuf_release(&head_reflog); + return ret; +} + static const char *resolvemsg = N_("Resolve all conflicts manually, mark them as resolved with\n" "\"git add/rm <conflicted_files>\", then run \"git rebase --continue\".\n" @@ -341,6 +525,126 @@ N_("Resolve all conflicts manually, mark them as resolved with\n" "To abort and get back to the state before \"git rebase\", run " "\"git rebase --abort\"."); +static int run_am(struct rebase_options *opts) +{ + struct child_process am = CHILD_PROCESS_INIT; + struct child_process format_patch = CHILD_PROCESS_INIT; + struct strbuf revisions = STRBUF_INIT; + int status; + char *rebased_patches; + + am.git_cmd = 1; + argv_array_push(&am.args, "am"); + + if (opts->action && !strcmp("continue", opts->action)) { + argv_array_push(&am.args, "--resolved"); + argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg); + if (opts->gpg_sign_opt) + argv_array_push(&am.args, opts->gpg_sign_opt); + status = run_command(&am); + if (status) + return status; + + return move_to_original_branch(opts); + } + if (opts->action && !strcmp("skip", opts->action)) { + argv_array_push(&am.args, "--skip"); + argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg); + status = run_command(&am); + if (status) + return status; + + return move_to_original_branch(opts); + } + if (opts->action && !strcmp("show-current-patch", opts->action)) { + argv_array_push(&am.args, "--show-current-patch"); + return run_command(&am); + } + + strbuf_addf(&revisions, "%s...%s", + oid_to_hex(opts->root ? + /* this is now equivalent to !opts->upstream */ + &opts->onto->object.oid : + &opts->upstream->object.oid), + oid_to_hex(&opts->orig_head)); + + rebased_patches = xstrdup(git_path("rebased-patches")); + format_patch.out = open(rebased_patches, + O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (format_patch.out < 0) { + status = error_errno(_("could not open '%s' for writing"), + rebased_patches); + free(rebased_patches); + argv_array_clear(&am.args); + return status; + } + + format_patch.git_cmd = 1; + argv_array_pushl(&format_patch.args, "format-patch", "-k", "--stdout", + "--full-index", "--cherry-pick", "--right-only", + "--src-prefix=a/", "--dst-prefix=b/", "--no-renames", + "--no-cover-letter", "--pretty=mboxrd", "--topo-order", NULL); + if (opts->git_format_patch_opt.len) + argv_array_split(&format_patch.args, + opts->git_format_patch_opt.buf); + argv_array_push(&format_patch.args, revisions.buf); + if (opts->restrict_revision) + argv_array_pushf(&format_patch.args, "^%s", + oid_to_hex(&opts->restrict_revision->object.oid)); + + status = run_command(&format_patch); + if (status) { + unlink(rebased_patches); + free(rebased_patches); + argv_array_clear(&am.args); + + reset_head(&opts->orig_head, "checkout", opts->head_name, 0, + "HEAD", NULL); + error(_("\ngit encountered an error while preparing the " + "patches to replay\n" + "these revisions:\n" + "\n %s\n\n" + "As a result, git cannot rebase them."), + opts->revisions); + + strbuf_release(&revisions); + return status; + } + strbuf_release(&revisions); + + am.in = open(rebased_patches, O_RDONLY); + if (am.in < 0) { + status = error_errno(_("could not open '%s' for reading"), + rebased_patches); + free(rebased_patches); + argv_array_clear(&am.args); + return status; + } + + argv_array_pushv(&am.args, opts->git_am_opts.argv); + argv_array_push(&am.args, "--rebasing"); + argv_array_pushf(&am.args, "--resolvemsg=%s", resolvemsg); + argv_array_push(&am.args, "--patch-format=mboxrd"); + if (opts->allow_rerere_autoupdate > 0) + argv_array_push(&am.args, "--rerere-autoupdate"); + else if (opts->allow_rerere_autoupdate == 0) + argv_array_push(&am.args, "--no-rerere-autoupdate"); + if (opts->gpg_sign_opt) + argv_array_push(&am.args, opts->gpg_sign_opt); + status = run_command(&am); + unlink(rebased_patches); + free(rebased_patches); + + if (!status) { + return move_to_original_branch(opts); + } + + if (is_directory(opts->state_dir)) + write_basic_state(opts); + + return status; +} + static int run_specific_rebase(struct rebase_options *opts) { const char *argv[] = { NULL, NULL }; @@ -355,7 +659,8 @@ static int run_specific_rebase(struct rebase_options *opts) argv_array_pushf(&child.env_array, "GIT_CHERRY_PICK_HELP=%s", resolvemsg); if (!(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) { - argv_array_push(&child.env_array, "GIT_EDITOR=:"); + argv_array_push(&child.env_array, + "GIT_SEQUENCE_EDITOR=:"); opts->autosquash = 0; } @@ -416,11 +721,18 @@ static int run_specific_rebase(struct rebase_options *opts) argv_array_push(&child.args, opts->gpg_sign_opt); if (opts->signoff) argv_array_push(&child.args, "--signoff"); + if (opts->reschedule_failed_exec) + argv_array_push(&child.args, "--reschedule-failed-exec"); status = run_command(&child); goto finished_rebase; } + if (opts->type == REBASE_AM) { + status = run_am(opts); + goto finished_rebase; + } + add_var(&script_snippet, "GIT_DIR", absolute_path(get_git_dir())); add_var(&script_snippet, "state_dir", opts->state_dir); @@ -476,7 +788,7 @@ static int run_specific_rebase(struct rebase_options *opts) if (is_interactive(opts) && !(opts->flags & REBASE_INTERACTIVE_EXPLICIT)) { strbuf_addstr(&script_snippet, - "GIT_EDITOR=:; export GIT_EDITOR; "); + "GIT_SEQUENCE_EDITOR=:; export GIT_SEQUENCE_EDITOR; "); opts->autosquash = 0; } @@ -485,10 +797,6 @@ static int run_specific_rebase(struct rebase_options *opts) backend = "git-rebase--am"; backend_func = "git_rebase__am"; break; - case REBASE_MERGE: - backend = "git-rebase--merge"; - backend_func = "git_rebase__merge"; - break; case REBASE_PRESERVE_MERGES: backend = "git-rebase--preserve-merges"; backend_func = "git_rebase__preserve_merges"; @@ -527,125 +835,6 @@ finished_rebase: return status ? -1 : 0; } -#define GIT_REFLOG_ACTION_ENVIRONMENT "GIT_REFLOG_ACTION" - -#define RESET_HEAD_DETACH (1<<0) -#define RESET_HEAD_HARD (1<<1) - -static int reset_head(struct object_id *oid, const char *action, - const char *switch_to_branch, unsigned flags, - const char *reflog_orig_head, const char *reflog_head) -{ - unsigned detach_head = flags & RESET_HEAD_DETACH; - unsigned reset_hard = flags & RESET_HEAD_HARD; - struct object_id head_oid; - struct tree_desc desc[2] = { { NULL }, { NULL } }; - struct lock_file lock = LOCK_INIT; - struct unpack_trees_options unpack_tree_opts; - struct tree *tree; - const char *reflog_action; - struct strbuf msg = STRBUF_INIT; - size_t prefix_len; - struct object_id *orig = NULL, oid_orig, - *old_orig = NULL, oid_old_orig; - int ret = 0, nr = 0; - - if (switch_to_branch && !starts_with(switch_to_branch, "refs/")) - BUG("Not a fully qualified branch: '%s'", switch_to_branch); - - if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0) { - ret = -1; - goto leave_reset_head; - } - - if ((!oid || !reset_hard) && get_oid("HEAD", &head_oid)) { - ret = error(_("could not determine HEAD revision")); - goto leave_reset_head; - } - - if (!oid) - oid = &head_oid; - - memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts)); - setup_unpack_trees_porcelain(&unpack_tree_opts, action); - unpack_tree_opts.head_idx = 1; - unpack_tree_opts.src_index = the_repository->index; - unpack_tree_opts.dst_index = the_repository->index; - unpack_tree_opts.fn = reset_hard ? oneway_merge : twoway_merge; - unpack_tree_opts.update = 1; - unpack_tree_opts.merge = 1; - if (!detach_head) - unpack_tree_opts.reset = 1; - - if (repo_read_index_unmerged(the_repository) < 0) { - ret = error(_("could not read index")); - goto leave_reset_head; - } - - if (!reset_hard && !fill_tree_descriptor(&desc[nr++], &head_oid)) { - ret = error(_("failed to find tree of %s"), - oid_to_hex(&head_oid)); - goto leave_reset_head; - } - - if (!fill_tree_descriptor(&desc[nr++], oid)) { - ret = error(_("failed to find tree of %s"), oid_to_hex(oid)); - goto leave_reset_head; - } - - if (unpack_trees(nr, desc, &unpack_tree_opts)) { - ret = -1; - goto leave_reset_head; - } - - tree = parse_tree_indirect(oid); - prime_cache_tree(the_repository, the_repository->index, tree); - - if (write_locked_index(the_repository->index, &lock, COMMIT_LOCK) < 0) { - ret = error(_("could not write index")); - goto leave_reset_head; - } - - reflog_action = getenv(GIT_REFLOG_ACTION_ENVIRONMENT); - strbuf_addf(&msg, "%s: ", reflog_action ? reflog_action : "rebase"); - prefix_len = msg.len; - - if (!get_oid("ORIG_HEAD", &oid_old_orig)) - old_orig = &oid_old_orig; - if (!get_oid("HEAD", &oid_orig)) { - orig = &oid_orig; - if (!reflog_orig_head) { - strbuf_addstr(&msg, "updating ORIG_HEAD"); - reflog_orig_head = msg.buf; - } - update_ref(reflog_orig_head, "ORIG_HEAD", orig, old_orig, 0, - UPDATE_REFS_MSG_ON_ERR); - } else if (old_orig) - delete_ref(NULL, "ORIG_HEAD", old_orig, 0); - if (!reflog_head) { - strbuf_setlen(&msg, prefix_len); - strbuf_addstr(&msg, "updating HEAD"); - reflog_head = msg.buf; - } - if (!switch_to_branch) - ret = update_ref(reflog_head, "HEAD", oid, orig, - detach_head ? REF_NO_DEREF : 0, - UPDATE_REFS_MSG_ON_ERR); - else { - ret = create_symref("HEAD", switch_to_branch, msg.buf); - if (!ret) - ret = update_ref(reflog_head, "HEAD", oid, NULL, 0, - UPDATE_REFS_MSG_ON_ERR); - } - -leave_reset_head: - strbuf_release(&msg); - rollback_lock_file(&lock); - while (nr) - free((void *)desc[--nr].buffer); - return ret; -} - static int rebase_config(const char *var, const char *value, void *data) { struct rebase_options *opts = data; @@ -675,6 +864,11 @@ static int rebase_config(const char *var, const char *value, void *data) return 0; } + if (!strcmp(var, "rebase.reschedulefailedexec")) { + opts->reschedule_failed_exec = git_config_bool(var, value); + return 0; + } + return git_default_config(var, value, data); } @@ -747,6 +941,23 @@ static int parse_opt_interactive(const struct option *opt, const char *arg, return 0; } +struct opt_y { + struct string_list *list; + struct rebase_options *options; +}; + +static int parse_opt_y(const struct option *opt, const char *arg, int unset) +{ + struct opt_y *o = opt->value; + + if (unset || !arg) + return -1; + + o->options->reschedule_failed_exec = 1; + string_list_append(o->list, arg); + return 0; +} + static void NORETURN error_on_missing_default_upstream(void) { struct branch *current_branch = branch_get(NULL); @@ -827,6 +1038,7 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) struct string_list strategy_options = STRING_LIST_INIT_NODUP; struct object_id squash_onto; char *squash_onto_name = NULL; + struct opt_y opt_y = { .list = &exec, .options = &options }; struct option builtin_rebase_options[] = { OPT_STRING(0, "onto", &options.onto_name, N_("revision"), @@ -904,6 +1116,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) OPT_STRING_LIST('x', "exec", &exec, N_("exec"), N_("add exec lines after each commit of the " "editable list")), + { OPTION_CALLBACK, 'y', NULL, &opt_y, N_("<cmd>"), + N_("same as --reschedule-failed-exec -x <cmd>"), + PARSE_OPT_NONEG, parse_opt_y }, OPT_BOOL(0, "allow-empty-message", &options.allow_empty_message, N_("allow rebasing commits with empty messages")), @@ -921,6 +1136,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) "strategy")), OPT_BOOL(0, "root", &options.root, N_("rebase all reachable commits up to the root(s)")), + OPT_BOOL(0, "reschedule-failed-exec", + &options.reschedule_failed_exec, + N_("automatically re-schedule any `exec` that fails")), OPT_END(), }; int i; @@ -1194,6 +1412,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) } } + if (options.type == REBASE_MERGE) + imply_interactive(&options, "--merge"); + if (options.root && !options.onto_name) imply_interactive(&options, "--root without --onto"); @@ -1216,6 +1437,9 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) break; } + if (options.reschedule_failed_exec && !is_interactive(&options)) + die(_("--reschedule-failed-exec requires an interactive rebase")); + if (options.git_am_opts.argc) { /* all am options except -q are compatible only with --am */ for (i = options.git_am_opts.argc - 1; i >= 0; i--) @@ -1223,14 +1447,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) break; if (is_interactive(&options) && i >= 0) - die(_("error: cannot combine interactive options " - "(--interactive, --exec, --rebase-merges, " - "--preserve-merges, --keep-empty, --root + " - "--onto) with am options (%s)"), buf.buf); - if (options.type == REBASE_MERGE && i >= 0) - die(_("error: cannot combine merge options (--merge, " - "--strategy, --strategy-option) with am options " - "(%s)"), buf.buf); + die(_("cannot combine am options with either " + "interactive or merge options")); } if (options.signoff) { @@ -1241,22 +1459,27 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) options.flags |= REBASE_FORCE; } - if (options.type == REBASE_PRESERVE_MERGES) + if (options.type == REBASE_PRESERVE_MERGES) { /* * Note: incompatibility with --signoff handled in signoff block above * Note: incompatibility with --interactive is just a strong warning; * git-rebase.txt caveats with "unless you know what you are doing" */ if (options.rebase_merges) - die(_("error: cannot combine '--preserve-merges' with " + die(_("cannot combine '--preserve-merges' with " "'--rebase-merges'")); + if (options.reschedule_failed_exec) + die(_("error: cannot combine '--preserve-merges' with " + "'--reschedule-failed-exec'")); + } + if (options.rebase_merges) { if (strategy_options.nr) - die(_("error: cannot combine '--rebase-merges' with " + die(_("cannot combine '--rebase-merges' with " "'--strategy-option'")); if (options.strategy) - die(_("error: cannot combine '--rebase-merges' with " + die(_("cannot combine '--rebase-merges' with " "'--strategy'")); } @@ -1466,7 +1689,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.switch_to); if (reset_head(&oid, "checkout", - options.head_name, 0, + options.head_name, + RESET_HEAD_RUN_POST_CHECKOUT_HOOK, NULL, buf.buf) < 0) { ret = !!error(_("could not switch to " "%s"), @@ -1540,7 +1764,8 @@ int cmd_rebase(int argc, const char **argv, const char *prefix) strbuf_addf(&msg, "%s: checkout %s", getenv(GIT_REFLOG_ACTION_ENVIRONMENT), options.onto_name); if (reset_head(&options.onto->object.oid, "checkout", NULL, - RESET_HEAD_DETACH, NULL, msg.buf)) + RESET_HEAD_DETACH | RESET_HEAD_RUN_POST_CHECKOUT_HOOK, + NULL, msg.buf)) die(_("Could not detach HEAD")); strbuf_release(&msg); diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c index 33187bd8e9..d58b7750b6 100644 --- a/builtin/receive-pack.c +++ b/builtin/receive-pack.c @@ -1569,30 +1569,29 @@ static void queue_commands_from_cert(struct command **tail, } } -static struct command *read_head_info(struct oid_array *shallow) +static struct command *read_head_info(struct packet_reader *reader, + struct oid_array *shallow) { struct command *commands = NULL; struct command **p = &commands; for (;;) { - char *line; - int len, linelen; + int linelen; - line = packet_read_line(0, &len); - if (!line) + if (packet_reader_read(reader) != PACKET_READ_NORMAL) break; - if (len > 8 && starts_with(line, "shallow ")) { + if (reader->pktlen > 8 && starts_with(reader->line, "shallow ")) { struct object_id oid; - if (get_oid_hex(line + 8, &oid)) + if (get_oid_hex(reader->line + 8, &oid)) die("protocol error: expected shallow sha, got '%s'", - line + 8); + reader->line + 8); oid_array_append(shallow, &oid); continue; } - linelen = strlen(line); - if (linelen < len) { - const char *feature_list = line + linelen + 1; + linelen = strlen(reader->line); + if (linelen < reader->pktlen) { + const char *feature_list = reader->line + linelen + 1; if (parse_feature_request(feature_list, "report-status")) report_status = 1; if (parse_feature_request(feature_list, "side-band-64k")) @@ -1607,28 +1606,32 @@ static struct command *read_head_info(struct oid_array *shallow) use_push_options = 1; } - if (!strcmp(line, "push-cert")) { + if (!strcmp(reader->line, "push-cert")) { int true_flush = 0; - char certbuf[1024]; + int saved_options = reader->options; + reader->options &= ~PACKET_READ_CHOMP_NEWLINE; for (;;) { - len = packet_read(0, NULL, NULL, - certbuf, sizeof(certbuf), 0); - if (!len) { + packet_reader_read(reader); + if (reader->status == PACKET_READ_FLUSH) { true_flush = 1; break; } - if (!strcmp(certbuf, "push-cert-end\n")) + if (reader->status != PACKET_READ_NORMAL) { + die("protocol error: got an unexpected packet"); + } + if (!strcmp(reader->line, "push-cert-end\n")) break; /* end of cert */ - strbuf_addstr(&push_cert, certbuf); + strbuf_addstr(&push_cert, reader->line); } + reader->options = saved_options; if (true_flush) break; continue; } - p = queue_command(p, line, linelen); + p = queue_command(p, reader->line, linelen); } if (push_cert.len) @@ -1637,18 +1640,14 @@ static struct command *read_head_info(struct oid_array *shallow) return commands; } -static void read_push_options(struct string_list *options) +static void read_push_options(struct packet_reader *reader, + struct string_list *options) { while (1) { - char *line; - int len; - - line = packet_read_line(0, &len); - - if (!line) + if (packet_reader_read(reader) != PACKET_READ_NORMAL) break; - string_list_append(options, line); + string_list_append(options, reader->line); } } @@ -1924,6 +1923,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) struct oid_array shallow = OID_ARRAY_INIT; struct oid_array ref = OID_ARRAY_INIT; struct shallow_info si; + struct packet_reader reader; struct option options[] = { OPT__QUIET(&quiet, N_("quiet")), @@ -1986,12 +1986,16 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix) if (advertise_refs) return 0; - if ((commands = read_head_info(&shallow)) != NULL) { + packet_reader_init(&reader, 0, NULL, 0, + PACKET_READ_CHOMP_NEWLINE | + PACKET_READ_DIE_ON_ERR_PACKET); + + if ((commands = read_head_info(&reader, &shallow)) != NULL) { const char *unpack_status = NULL; struct string_list push_options = STRING_LIST_INIT_DUP; if (use_push_options) - read_push_options(&push_options); + read_push_options(&reader, &push_options); if (!check_cert_push_options(&push_options)) { struct command *cmd; for (cmd = commands; cmd; cmd = cmd->next) diff --git a/builtin/reflog.c b/builtin/reflog.c index 64a8df4f25..1f1010e2d9 100644 --- a/builtin/reflog.c +++ b/builtin/reflog.c @@ -94,8 +94,8 @@ static int tree_is_complete(const struct object_id *oid) init_tree_desc(&desc, tree->buffer, tree->size); complete = 1; while (tree_entry(&desc, &entry)) { - if (!has_sha1_file(entry.oid->hash) || - (S_ISDIR(entry.mode) && !tree_is_complete(entry.oid))) { + if (!has_sha1_file(entry.oid.hash) || + (S_ISDIR(entry.mode) && !tree_is_complete(&entry.oid))) { tree->object.flags |= INCOMPLETE; complete = 0; } diff --git a/builtin/repack.c b/builtin/repack.c index 2a1c7b21c5..67f8978043 100644 --- a/builtin/repack.c +++ b/builtin/repack.c @@ -419,6 +419,8 @@ int cmd_repack(int argc, const char **argv, const char *prefix) if (!names.nr && !po_args.quiet) printf_ln(_("Nothing new to pack.")); + close_all_packs(the_repository->objects); + /* * Ok we have prepared all new packfiles. * First see if there are packs of the same name and if so diff --git a/builtin/rev-list.c b/builtin/rev-list.c index 2880ed37e3..14ef659c12 100644 --- a/builtin/rev-list.c +++ b/builtin/rev-list.c @@ -197,7 +197,8 @@ static void finish_commit(struct commit *commit, void *data) free_commit_list(commit->parents); commit->parents = NULL; } - free_commit_buffer(commit); + free_commit_buffer(the_repository->parsed_objects, + commit); } static inline void finish_object__ma(struct object *obj) @@ -361,6 +362,9 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) { struct rev_info revs; struct rev_list_info info; + struct setup_revision_opt s_r_opt = { + .allow_exclude_promisor_objects = 1, + }; int i; int bisect_list = 0; int bisect_show_vars = 0; @@ -374,7 +378,6 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) git_config(git_default_config, NULL); repo_init_revisions(the_repository, &revs, prefix); revs.abbrev = DEFAULT_ABBREV; - revs.allow_exclude_promisor_objects_opt = 1; revs.commit_format = CMIT_FMT_UNSPECIFIED; revs.do_not_die_on_missing_tree = 1; @@ -406,7 +409,7 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) } } - argc = setup_revisions(argc, argv, &revs, NULL); + argc = setup_revisions(argc, argv, &revs, &s_r_opt); memset(&info, 0, sizeof(info)); info.revs = &revs; diff --git a/builtin/send-pack.c b/builtin/send-pack.c index 8e3c7490f7..098ebf22d0 100644 --- a/builtin/send-pack.c +++ b/builtin/send-pack.c @@ -250,7 +250,8 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix) packet_reader_init(&reader, fd[0], NULL, 0, PACKET_READ_CHOMP_NEWLINE | - PACKET_READ_GENTLE_ON_EOF); + PACKET_READ_GENTLE_ON_EOF | + PACKET_READ_DIE_ON_ERR_PACKET); switch (discover_version(&reader)) { case protocol_v2: diff --git a/builtin/stripspace.c b/builtin/stripspace.c index bdf0328869..be33eb83c1 100644 --- a/builtin/stripspace.c +++ b/builtin/stripspace.c @@ -30,6 +30,7 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) { struct strbuf buf = STRBUF_INIT; enum stripspace_mode mode = STRIP_DEFAULT; + int nongit; const struct option options[] = { OPT_CMDMODE('s', "strip-comments", &mode, @@ -46,7 +47,7 @@ int cmd_stripspace(int argc, const char **argv, const char *prefix) usage_with_options(stripspace_usage, options); if (mode == STRIP_COMMENTS || mode == COMMENT_LINES) { - setup_git_directory_gently(NULL); + setup_git_directory_gently(&nongit); git_config(git_default_config, NULL); } diff --git a/builtin/submodule--helper.c b/builtin/submodule--helper.c index 9c832fc606..b80fc4ba3d 100644 --- a/builtin/submodule--helper.c +++ b/builtin/submodule--helper.c @@ -1132,6 +1132,8 @@ static void deinit_submodule(const char *path, const char *prefix, if (!(flags & OPT_QUIET)) printf(format, displaypath); + submodule_unset_core_worktree(sub); + strbuf_release(&sb_rm); } @@ -1553,7 +1555,7 @@ struct submodule_update_clone { #define SUBMODULE_UPDATE_CLONE_INIT {0, MODULE_LIST_INIT, 0, \ SUBMODULE_UPDATE_STRATEGY_INIT, 0, 0, -1, STRING_LIST_INIT_DUP, 0, \ NULL, NULL, NULL, \ - NULL, 0, 0, 0, NULL, 0, 0, 0} + NULL, 0, 0, 0, NULL, 0, 0, 1} static void next_submodule_warn_missing(struct submodule_update_clone *suc, @@ -2047,7 +2049,7 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix) struct repository subrepo; if (argc != 2) - BUG("submodule--helper connect-gitdir-workingtree <name> <path>"); + BUG("submodule--helper ensure-core-worktree <path>"); path = argv[1]; @@ -2055,7 +2057,7 @@ static int ensure_core_worktree(int argc, const char **argv, const char *prefix) if (!sub) BUG("We could get the submodule handle before?"); - if (repo_submodule_init(&subrepo, the_repository, path)) + if (repo_submodule_init(&subrepo, the_repository, sub)) die(_("could not get a repository handle for submodule '%s'"), path); if (!repo_config_get_string(&subrepo, "core.worktree", &cw)) { diff --git a/builtin/worktree.c b/builtin/worktree.c index 5e84026177..3f9907fcc9 100644 --- a/builtin/worktree.c +++ b/builtin/worktree.c @@ -9,6 +9,7 @@ #include "refs.h" #include "run-command.h" #include "sigchain.h" +#include "submodule.h" #include "refs.h" #include "utf8.h" #include "worktree.h" @@ -724,20 +725,36 @@ static int unlock_worktree(int ac, const char **av, const char *prefix) static void validate_no_submodules(const struct worktree *wt) { struct index_state istate = { NULL }; + struct strbuf path = STRBUF_INIT; int i, found_submodules = 0; - if (read_index_from(&istate, worktree_git_path(wt, "index"), - get_worktree_git_dir(wt)) > 0) { + if (is_directory(worktree_git_path(wt, "modules"))) { + /* + * There could be false positives, e.g. the "modules" + * directory exists but is empty. But it's a rare case and + * this simpler check is probably good enough for now. + */ + found_submodules = 1; + } else if (read_index_from(&istate, worktree_git_path(wt, "index"), + get_worktree_git_dir(wt)) > 0) { for (i = 0; i < istate.cache_nr; i++) { struct cache_entry *ce = istate.cache[i]; + int err; - if (S_ISGITLINK(ce->ce_mode)) { - found_submodules = 1; - break; - } + if (!S_ISGITLINK(ce->ce_mode)) + continue; + + strbuf_reset(&path); + strbuf_addf(&path, "%s/%s", wt->path, ce->name); + if (!is_submodule_populated_gently(path.buf, &err)) + continue; + + found_submodules = 1; + break; } } discard_index(&istate); + strbuf_release(&path); if (found_submodules) die(_("working trees containing submodules cannot be moved or removed")); |