diff options
Diffstat (limited to 'builtin')
49 files changed, 7190 insertions, 558 deletions
diff --git a/builtin/add.c b/builtin/add.c index ab39a60a0d..a825887c50 100644 --- a/builtin/add.c +++ b/builtin/add.c @@ -192,9 +192,15 @@ int run_add_interactive(const char *revision, const char *patch_mode, int use_builtin_add_i = git_env_bool("GIT_TEST_ADD_I_USE_BUILTIN", -1); - if (use_builtin_add_i < 0) - git_config_get_bool("add.interactive.usebuiltin", - &use_builtin_add_i); + if (use_builtin_add_i < 0) { + int experimental; + if (!git_config_get_bool("add.interactive.usebuiltin", + &use_builtin_add_i)) + ; /* ok */ + else if (!git_config_get_bool("feature.experimental", &experimental) && + experimental) + use_builtin_add_i = 1; + } if (use_builtin_add_i == 1) { enum add_p_mode mode; @@ -233,7 +239,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; @@ -445,7 +451,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; @@ -534,11 +540,11 @@ int cmd_add(int argc, const char **argv, const char *prefix) die_in_unpopulated_submodule(&the_index, prefix); die_path_inside_submodule(&the_index, &pathspec); + dir_init(&dir); if (add_new_files) { int baselen; /* Set up the default git porcelain excludes */ - memset(&dir, 0, sizeof(dir)); if (!ignored_too) { dir.flags |= DIR_COLLECT_IGNORED; setup_standard_excludes(&dir); @@ -611,7 +617,7 @@ finish: COMMIT_LOCK | SKIP_IF_UNCHANGED)) die(_("Unable to write new index file")); + dir_clear(&dir); UNLEAK(pathspec); - UNLEAK(dir); return exit_status; } diff --git a/builtin/am.c b/builtin/am.c index 68e9d17379..4949535a7f 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -98,6 +98,8 @@ struct am_state { char *author_name; char *author_email; char *author_date; + char *committer_name; + char *committer_email; char *msg; size_t msg_len; @@ -130,6 +132,8 @@ struct am_state { */ static void am_state_init(struct am_state *state) { + const char *committer; + struct ident_split id; int gpgsign; memset(state, 0, sizeof(*state)); @@ -150,6 +154,14 @@ 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_begin); } /** @@ -161,6 +173,8 @@ 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); } @@ -1556,7 +1570,7 @@ static void do_commit(const struct am_state *state) struct object_id tree, parent, commit; const struct object_id *old_oid; struct commit_list *parents = NULL; - const char *reflog_msg, *author; + const char *reflog_msg, *author, *committer = NULL; struct strbuf sb = STRBUF_INIT; if (run_hook_le(NULL, "pre-applypatch", NULL)) @@ -1580,11 +1594,15 @@ static void do_commit(const struct am_state *state) IDENT_STRICT); if (state->committer_date_is_author_date) - setenv("GIT_COMMITTER_DATE", - state->ignore_date ? "" : state->author_date, 1); - - if (commit_tree(state->msg, state->msg_len, &tree, parents, &commit, - author, state->sign_commit)) + committer = fmt_ident(state->committer_name, + state->committer_email, WANT_COMMITTER_IDENT, + state->ignore_date ? NULL + : state->author_date, + IDENT_STRICT); + + if (commit_tree_extended(state->msg, state->msg_len, &tree, parents, + &commit, author, committer, state->sign_commit, + NULL)) die(_("failed to write commit object")); reflog_msg = getenv("GIT_REFLOG_ACTION"); @@ -2162,6 +2180,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])) diff --git a/builtin/bisect--helper.c b/builtin/bisect--helper.c index cdda279b23..7512b880f0 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") @@ -27,11 +28,19 @@ static const char * const git_bisect_helper_usage[] = { N_("git bisect--helper --bisect-check-and-set-terms <command> <good_term> <bad_term>"), N_("git bisect--helper --bisect-next-check <good_term> <bad_term> [<term>]"), N_("git bisect--helper --bisect-terms [--term-good | --term-old | --term-bad | --term-new]"), - N_("git bisect--helper --bisect-start [--term-{old,good}=<term> --term-{new,bad}=<term>]" + N_("git bisect--helper --bisect-start [--term-{new,bad}=<term> --term-{old,good}=<term>]" " [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] [<paths>...]"), + N_("git bisect--helper --bisect-next"), + N_("git bisect--helper --bisect-auto-next"), + N_("git bisect--helper --bisect-autostart"), NULL }; +struct add_bisect_ref_data { + struct rev_info *revs; + unsigned int object_flags; +}; + struct bisect_terms { char *term_good; char *term_bad; @@ -55,6 +64,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 +85,52 @@ static int one_of(const char *term, ...) return res; } +static int write_in_file(const char *path, const char *mode, const char *format, va_list args) +{ + FILE *fp = NULL; + int res = 0; + + if (strcmp(mode, "w") && strcmp(mode, "a")) + BUG("write-in-file does not support '%s' mode", mode); + fp = fopen(path, mode); + if (!fp) + return error_errno(_("cannot open file '%s' in mode '%s'"), path, mode); + res = vfprintf(fp, format, args); + + if (res < 0) { + int saved_errno = errno; + fclose(fp); + errno = saved_errno; + return error_errno(_("could not write to file '%s'"), path); + } + + return fclose(fp); +} + +static int write_to_file(const char *path, const char *format, ...) +{ + int res; + va_list args; + + va_start(args, format); + res = write_in_file(path, "w", format, args); + va_end(args); + + return res; +} + +static int append_to_file(const char *path, const char *format, ...) +{ + int res; + va_list args; + + va_start(args, format); + res = write_in_file(path, "a", format, args); + va_end(args); + + return res; +} + static int check_term_format(const char *term, const char *orig_term) { int res; @@ -104,7 +161,6 @@ static int check_term_format(const char *term, const char *orig_term) static int write_terms(const char *bad, const char *good) { - FILE *fp = NULL; int res; if (!strcmp(bad, good)) @@ -113,13 +169,9 @@ static int write_terms(const char *bad, const char *good) if (check_term_format(bad, "bad") || check_term_format(good, "good")) return -1; - fp = fopen(git_path_bisect_terms(), "w"); - if (!fp) - return error_errno(_("could not open the file BISECT_TERMS")); + res = write_to_file(git_path_bisect_terms(), "%s\n%s\n", bad, good); - res = fprintf(fp, "%s\n%s\n", bad, good); - res |= fclose(fp); - return (res < 0) ? -1 : 0; + return res; } static int is_expected_rev(const char *expected_hex) @@ -421,6 +473,142 @@ finish: return res; } +static int add_bisect_ref(const char *refname, const struct object_id *oid, + int flags, void *cb) +{ + struct add_bisect_ref_data *data = cb; + + add_pending_oid(data->revs, refname, oid, data->object_flags); + + return 0; +} + +static int prepare_revs(struct bisect_terms *terms, struct rev_info *revs) +{ + int res = 0; + struct add_bisect_ref_data cb = { revs }; + char *good = xstrfmt("%s-*", terms->term_good); + + /* + * We cannot use terms->term_bad directly in + * for_each_glob_ref_in() and we have to append a '*' to it, + * otherwise for_each_glob_ref_in() will append '/' and '*'. + */ + char *bad = xstrfmt("%s*", terms->term_bad); + + /* + * It is important to reset the flags used by revision walks + * as the previous call to bisect_next_all() in turn + * sets up a revision walk. + */ + reset_revision_walk(); + init_revisions(revs, NULL); + setup_revisions(0, NULL, revs, NULL); + for_each_glob_ref_in(add_bisect_ref, bad, "refs/bisect/", &cb); + cb.object_flags = UNINTERESTING; + for_each_glob_ref_in(add_bisect_ref, good, "refs/bisect/", &cb); + if (prepare_revision_walk(revs)) + res = error(_("revision walk setup failed\n")); + + free(good); + free(bad); + return res; +} + +static int bisect_skipped_commits(struct bisect_terms *terms) +{ + int res; + FILE *fp = NULL; + struct rev_info revs; + struct commit *commit; + struct pretty_print_context pp = {0}; + struct strbuf commit_name = STRBUF_INIT; + + res = prepare_revs(terms, &revs); + if (res) + return res; + + fp = fopen(git_path_bisect_log(), "a"); + if (!fp) + return error_errno(_("could not open '%s' for appending"), + git_path_bisect_log()); + + if (fprintf(fp, "# only skipped commits left to test\n") < 0) + return error_errno(_("failed to write to '%s'"), git_path_bisect_log()); + + while ((commit = get_revision(&revs)) != NULL) { + strbuf_reset(&commit_name); + format_commit_message(commit, "%s", + &commit_name, &pp); + fprintf(fp, "# possible first %s commit: [%s] %s\n", + terms->term_bad, oid_to_hex(&commit->object.oid), + commit_name.buf); + } + + /* + * Reset the flags used by revision walks in case + * there is another revision walk after this one. + */ + reset_revision_walk(); + + strbuf_release(&commit_name); + fclose(fp); + return 0; +} + +static int bisect_successful(struct bisect_terms *terms) +{ + struct object_id oid; + struct commit *commit; + struct pretty_print_context pp = {0}; + struct strbuf commit_name = STRBUF_INIT; + char *bad_ref = xstrfmt("refs/bisect/%s",terms->term_bad); + int res; + + read_ref(bad_ref, &oid); + commit = lookup_commit_reference_by_name(bad_ref); + format_commit_message(commit, "%s", &commit_name, &pp); + + res = append_to_file(git_path_bisect_log(), "# first %s commit: [%s] %s\n", + terms->term_bad, oid_to_hex(&commit->object.oid), + commit_name.buf); + + strbuf_release(&commit_name); + free(bad_ref); + return res; +} + +static enum bisect_error bisect_next(struct bisect_terms *terms, const char *prefix) +{ + enum bisect_error res; + + if (bisect_autostart(terms)) + return BISECT_FAILED; + + if (bisect_next_check(terms, terms->term_good)) + return BISECT_FAILED; + + /* Perform all bisection computation */ + res = bisect_next_all(the_repository, prefix); + + if (res == BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND) { + res = bisect_successful(terms); + return res ? res : BISECT_INTERNAL_SUCCESS_1ST_BAD_FOUND; + } else if (res == BISECT_ONLY_SKIPPED_LEFT) { + res = bisect_skipped_commits(terms); + return res ? res : BISECT_ONLY_SKIPPED_LEFT; + } + return res; +} + +static enum bisect_error bisect_auto_next(struct bisect_terms *terms, const char *prefix) +{ + if (bisect_next_check(terms, NULL)) + return BISECT_OK; + + return bisect_next(terms, prefix); +} + static int bisect_start(struct bisect_terms *terms, const char **argv, int argc) { int no_checkout = 0; @@ -484,14 +672,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; @@ -624,6 +811,38 @@ finish: return res; } +static inline int file_is_not_empty(const char *path) +{ + return !is_empty_or_missing_file(path); +} + +static int bisect_autostart(struct bisect_terms *terms) +{ + int res; + const char *yesno; + + if (file_is_not_empty(git_path_bisect_start())) + return 0; + + fprintf_ln(stderr, _("You need to start by \"git bisect " + "start\"\n")); + + if (!isatty(STDIN_FILENO)) + return -1; + + /* + * TRANSLATORS: Make sure to include [Y] and [n] in your + * translation. The program will only accept English input + * at this point. + */ + yesno = git_prompt(_("Do you want me to do it for you " + "[Y/n]? "), PROMPT_ECHO); + res = tolower(*yesno) == 'n' ? + -1 : bisect_start(terms, empty_strvec, 0); + + return res; +} + int cmd_bisect__helper(int argc, const char **argv, const char *prefix) { enum { @@ -636,7 +855,10 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) CHECK_AND_SET_TERMS, BISECT_NEXT_CHECK, BISECT_TERMS, - BISECT_START + BISECT_START, + BISECT_AUTOSTART, + BISECT_NEXT, + BISECT_AUTO_NEXT } cmdmode = 0; int res = 0, nolog = 0; struct option options[] = { @@ -660,6 +882,12 @@ int cmd_bisect__helper(int argc, const char **argv, const char *prefix) N_("print out the bisect terms"), BISECT_TERMS), OPT_CMDMODE(0, "bisect-start", &cmdmode, N_("start the bisect session"), BISECT_START), + OPT_CMDMODE(0, "bisect-next", &cmdmode, + N_("find the next bisection commit"), BISECT_NEXT), + OPT_CMDMODE(0, "bisect-auto-next", &cmdmode, + N_("verify the next bisection state then checkout the next bisection commit"), BISECT_AUTO_NEXT), + OPT_CMDMODE(0, "bisect-autostart", &cmdmode, + N_("start the bisection if it has not yet been started"), BISECT_AUTOSTART), OPT_BOOL(0, "no-log", &nolog, N_("no log for BISECT_WRITE")), OPT_END() @@ -719,8 +947,26 @@ 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_AUTO_NEXT: + if (argc) + return error(_("--bisect-auto-next requires 0 arguments")); + get_terms(&terms); + res = bisect_auto_next(&terms, prefix); + break; + case BISECT_AUTOSTART: + if (argc) + return error(_("--bisect-autostart does not accept arguments")); + set_terms(&terms, "bad", "good"); + res = bisect_autostart(&terms); + break; default: - return error("BUG: unknown subcommand '%d'", cmdmode); + BUG("unknown subcommand %d", cmdmode); } free_terms(&terms); @@ -728,8 +974,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 94ef57c1cc..b5036ab327 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>"); @@ -803,6 +804,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 +838,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); } @@ -842,7 +867,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix) const char *contents_from = NULL; const struct option options[] = { OPT_BOOL(0, "incremental", &incremental, N_("Show blame entries as we find them, incrementally")), - OPT_BOOL('b', NULL, &blank_boundary, N_("Show blank SHA-1 for boundary commits (Default: off)")), + OPT_BOOL('b', NULL, &blank_boundary, N_("Do not show object names of boundary commits (Default: off)")), OPT_BOOL(0, "root", &show_root, N_("Do not treat root commits as boundaries (Default: off)")), OPT_BOOL(0, "show-stats", &show_stats, N_("Show work cost statistics")), OPT_BOOL(0, "progress", &show_progress, N_("Force progress reporting")), diff --git a/builtin/branch.c b/builtin/branch.c index e82301fb1b..efb30b8820 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -26,7 +26,7 @@ #include "commit-reach.h" static const char * const builtin_branch_usage[] = { - N_("git branch [<options>] [-r | -a] [--merged | --no-merged]"), + N_("git branch [<options>] [-r | -a] [--merged] [--no-merged]"), N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"), N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."), N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"), @@ -688,8 +688,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) !show_current && !unset_upstream && argc == 0) list = 1; - if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr || - filter.no_commit) + if (filter.with_commit || filter.no_commit || + filter.reachable_from || filter.unreachable_from || filter.points_at.nr) list = 1; if (!!delete + !!rename + !!copy + !!new_upstream + !!show_current + diff --git a/builtin/bugreport.c b/builtin/bugreport.c new file mode 100644 index 0000000000..3ad4b9b62e --- /dev/null +++ b/builtin/bugreport.c @@ -0,0 +1,194 @@ +#include "builtin.h" +#include "parse-options.h" +#include "strbuf.h" +#include "help.h" +#include "compat/compiler.h" +#include "run-command.h" + + +static void get_system_info(struct strbuf *sys_info) +{ + struct utsname uname_info; + char *shell = NULL; + + /* get git version from native cmd */ + strbuf_addstr(sys_info, _("git version:\n")); + get_version_info(sys_info, 1); + + /* system call for other version info */ + strbuf_addstr(sys_info, "uname: "); + if (uname(&uname_info)) + strbuf_addf(sys_info, _("uname() failed with error '%s' (%d)\n"), + strerror(errno), + errno); + else + strbuf_addf(sys_info, "%s %s %s %s\n", + uname_info.sysname, + uname_info.release, + uname_info.version, + uname_info.machine); + + strbuf_addstr(sys_info, _("compiler info: ")); + get_compiler_info(sys_info); + + strbuf_addstr(sys_info, _("libc info: ")); + get_libc_info(sys_info); + + shell = getenv("SHELL"); + strbuf_addf(sys_info, "$SHELL (typically, interactive shell): %s\n", + shell ? shell : "<unset>"); +} + +static void get_populated_hooks(struct strbuf *hook_info, int nongit) +{ + /* + * NEEDSWORK: Doesn't look like there is a list of all possible hooks; + * so below is a transcription of `git help hooks`. Later, this should + * be replaced with some programmatically generated list (generated from + * doc or else taken from some library which tells us about all the + * hooks) + */ + static const char *hook[] = { + "applypatch-msg", + "pre-applypatch", + "post-applypatch", + "pre-commit", + "pre-merge-commit", + "prepare-commit-msg", + "commit-msg", + "post-commit", + "pre-rebase", + |