diff options
Diffstat (limited to 'builtin/commit.c')
-rw-r--r-- | builtin/commit.c | 792 |
1 files changed, 505 insertions, 287 deletions
diff --git a/builtin/commit.c b/builtin/commit.c index 074bd9a551..7ba7201cfb 100644 --- a/builtin/commit.c +++ b/builtin/commit.c @@ -5,44 +5,54 @@ * Based on git-commit.sh by Junio C Hamano and Linus Torvalds */ -#include "cache.h" +#define USE_THE_INDEX_VARIABLE +#include "builtin.h" +#include "advice.h" #include "config.h" #include "lockfile.h" #include "cache-tree.h" #include "color.h" #include "dir.h" -#include "builtin.h" +#include "editor.h" +#include "environment.h" #include "diff.h" -#include "diffcore.h" #include "commit.h" +#include "gettext.h" #include "revision.h" #include "wt-status.h" #include "run-command.h" -#include "refs.h" -#include "log-tree.h" #include "strbuf.h" -#include "utf8.h" +#include "object-name.h" #include "parse-options.h" +#include "path.h" +#include "preload-index.h" +#include "read-cache.h" #include "string-list.h" #include "rerere.h" #include "unpack-trees.h" -#include "quote.h" -#include "submodule.h" -#include "gpg-interface.h" #include "column.h" #include "sequencer.h" +#include "sparse-index.h" #include "mailmap.h" #include "help.h" #include "commit-reach.h" #include "commit-graph.h" +#include "pretty.h" static const char * const builtin_commit_usage[] = { - N_("git commit [<options>] [--] <pathspec>..."), + N_("git commit [-a | --interactive | --patch] [-s] [-v] [-u<mode>] [--amend]\n" + " [--dry-run] [(-c | -C | --squash) <commit> | --fixup [(amend|reword):]<commit>)]\n" + " [-F <file> | -m <msg>] [--reset-author] [--allow-empty]\n" + " [--allow-empty-message] [--no-verify] [-e] [--author=<author>]\n" + " [--date=<date>] [--cleanup=<mode>] [--[no-]status]\n" + " [-i | -o] [--pathspec-from-file=<file> [--pathspec-file-nul]]\n" + " [(--trailer <token>[(=|:)<value>])...] [-S[<keyid>]]\n" + " [--] [<pathspec>...]"), NULL }; static const char * const builtin_status_usage[] = { - N_("git status [<options>] [--] <pathspec>..."), + N_("git status [<options>] [--] [<pathspec>...]"), NULL }; @@ -58,16 +68,22 @@ N_("The previous cherry-pick is now empty, possibly due to conflict resolution.\ " git commit --allow-empty\n" "\n"); +static const char empty_rebase_pick_advice[] = +N_("Otherwise, please use 'git rebase --skip'\n"); + static const char empty_cherry_pick_advice_single[] = -N_("Otherwise, please use 'git reset'\n"); +N_("Otherwise, please use 'git cherry-pick --skip'\n"); static const char empty_cherry_pick_advice_multi[] = -N_("If you wish to skip this commit, use:\n" +N_("and then use:\n" +"\n" +" git cherry-pick --continue\n" "\n" -" git reset\n" +"to resume cherry-picking the remaining commits.\n" +"If you wish to skip this commit, use:\n" "\n" -"Then \"git cherry-pick --continue\" will resume cherry-picking\n" -"the remaining commits.\n"); +" git cherry-pick --skip\n" +"\n"); static const char *color_status_slots[] = { [WT_STATUS_HEADER] = "header", @@ -98,14 +114,16 @@ static const char *template_file; */ static const char *author_message, *author_message_buffer; static char *edit_message, *use_message; -static char *fixup_message, *squash_message; +static char *fixup_message, *fixup_commit, *squash_message; +static const char *fixup_prefix; static int all, also, interactive, patch_interactive, only, amend, signoff; static int edit_flag = -1; /* unspecified */ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship; static int config_commit_verbose = -1; /* unspecified */ -static int no_post_rewrite, allow_empty_message; +static int no_post_rewrite, allow_empty_message, pathspec_file_nul; static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg; -static char *sign_commit; +static char *sign_commit, *pathspec_from_file; +static struct strvec trailer_args = STRVEC_INIT; /* * The default commit message cleanup mode will remove the lines @@ -118,13 +136,20 @@ static enum commit_msg_cleanup_mode cleanup_mode; static const char *cleanup_arg; static enum commit_whence whence; -static int sequencer_in_use; static int use_editor = 1, include_status = 1; static int have_option_m; static struct strbuf message = STRBUF_INIT; static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED; +static int opt_pass_trailer(const struct option *opt, const char *arg, int unset) +{ + BUG_ON_OPT_NEG(unset); + + strvec_pushl(opt->value, "--trailer", arg, NULL); + return 0; +} + static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset) { enum wt_status_format *value = (enum wt_status_format *)opt->value; @@ -161,6 +186,9 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset) static int opt_parse_rename_score(const struct option *opt, const char *arg, int unset) { const char **value = opt->value; + + BUG_ON_OPT_NEG(unset); + if (arg != NULL && *arg == '=') arg = arg + 1; @@ -172,12 +200,7 @@ static void determine_whence(struct wt_status *s) { if (file_exists(git_path_merge_head(the_repository))) whence = FROM_MERGE; - else if (file_exists(git_path_cherry_pick_head(the_repository))) { - whence = FROM_CHERRY_PICK; - if (file_exists(git_path_seq_dir())) - sequencer_in_use = 1; - } - else + else if (!sequencer_determine_whence(the_repository, &whence)) whence = FROM_COMMIT; if (s) s->whence = whence; @@ -185,11 +208,11 @@ static void determine_whence(struct wt_status *s) static void status_init_config(struct wt_status *s, config_fn_t fn) { - wt_status_prepare(s); + wt_status_prepare(the_repository, s); init_diff_ui_defaults(); git_config(fn, s); determine_whence(s); - s->hints = advice_status_hints; /* must come after git_config() */ + s->hints = advice_enabled(ADVICE_STATUS_HINTS); /* must come after git_config() */ } static void rollback_index_files(void) @@ -231,7 +254,7 @@ static int commit_index_files(void) * and return the paths that match the given pattern in list. */ static int list_paths(struct string_list *list, const char *with_tree, - const char *prefix, const struct pathspec *pattern) + const struct pathspec *pattern) { int i, ret; char *m; @@ -247,8 +270,10 @@ static int list_paths(struct string_list *list, const char *with_tree, free(max_prefix); } - for (i = 0; i < active_nr; i++) { - const struct cache_entry *ce = active_cache[i]; + /* TODO: audit for interaction with sparse-index. */ + ensure_full_index(&the_index); + for (i = 0; i < the_index.cache_nr; i++) { + const struct cache_entry *ce = the_index.cache[i]; struct string_list_item *item; if (ce->ce_flags & CE_UPDATE) @@ -260,7 +285,7 @@ static int list_paths(struct string_list *list, const char *with_tree, item->util = item; /* better a valid pointer than a fake one */ } - ret = report_path_error(m, pattern, prefix); + ret = report_path_error(m, pattern); free(m); return ret; } @@ -277,10 +302,10 @@ static void add_remove_files(struct string_list *list) continue; if (!lstat(p->string, &st)) { - if (add_to_cache(p->string, &st, 0)) + if (add_to_index(&the_index, p->string, &st, 0)) die(_("updating files failed")); } else - remove_file_from_cache(p->string); + remove_file_from_index(&the_index, p->string); } } @@ -291,7 +316,7 @@ static void create_base_index(const struct commit *current_head) struct tree_desc t; if (!current_head) { - discard_cache(); + discard_index(&the_index); return; } @@ -306,8 +331,9 @@ static void create_base_index(const struct commit *current_head) tree = parse_tree_indirect(¤t_head->object.oid); if (!tree) die(_("failed to unpack HEAD tree object")); - parse_tree(tree); - init_tree_desc(&t, tree->buffer, tree->size); + if (parse_tree(tree) < 0) + exit(128); + init_tree_desc(&t, &tree->object.oid, tree->buffer, tree->size); if (unpack_trees(1, &t, &opts)) exit(128); /* We've already reported the error, finish dying */ } @@ -318,11 +344,11 @@ static void refresh_cache_or_die(int refresh_flags) * refresh_flags contains REFRESH_QUIET, so the only errors * are for unmerged entries. */ - if (refresh_cache(refresh_flags | REFRESH_IN_PORCELAIN)) + if (refresh_index(&the_index, refresh_flags | REFRESH_IN_PORCELAIN, NULL, NULL, NULL)) die_resolve_conflict("commit"); } -static const char *prepare_index(int argc, const char **argv, const char *prefix, +static const char *prepare_index(const char **argv, const char *prefix, const struct commit *current_head, int is_status) { struct string_list partial = STRING_LIST_INIT_DUP; @@ -336,32 +362,60 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix PATHSPEC_PREFER_FULL, prefix, argv); - if (read_cache_preload(&pathspec) < 0) + if (pathspec_from_file) { + if (interactive) + die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--interactive/--patch"); + + if (all) + die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "-a"); + + if (pathspec.nr) + die(_("'%s' and pathspec arguments cannot be used together"), "--pathspec-from-file"); + + parse_pathspec_file(&pathspec, 0, + PATHSPEC_PREFER_FULL, + prefix, pathspec_from_file, pathspec_file_nul); + } else if (pathspec_file_nul) { + die(_("the option '%s' requires '%s'"), "--pathspec-file-nul", "--pathspec-from-file"); + } + + if (!pathspec.nr && (also || (only && !allow_empty && + (!amend || (fixup_message && strcmp(fixup_prefix, "amend")))))) + die(_("No paths with --include/--only does not make sense.")); + + if (repo_read_index_preload(the_repository, &pathspec, 0) < 0) die(_("index file corrupt")); if (interactive) { - char *old_index_env = NULL; - hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); + char *old_index_env = NULL, *old_repo_index_file; + repo_hold_locked_index(the_repository, &index_lock, + LOCK_DIE_ON_ERROR); refresh_cache_or_die(refresh_flags); if (write_locked_index(&the_index, &index_lock, 0)) die(_("unable to create temporary index")); - old_index_env = getenv(INDEX_ENVIRONMENT); - setenv(INDEX_ENVIRONMENT, get_lock_file_path(&index_lock), 1); + old_repo_index_file = the_repository->index_file; + the_repository->index_file = + (char *)get_lock_file_path(&index_lock); + old_index_env = xstrdup_or_null(getenv(INDEX_ENVIRONMENT)); + setenv(INDEX_ENVIRONMENT, the_repository->index_file, 1); - if (interactive_add(argc, argv, prefix, patch_interactive) != 0) + if (interactive_add(argv, prefix, patch_interactive) != 0) die(_("interactive add failed")); + the_repository->index_file = old_repo_index_file; if (old_index_env && *old_index_env) 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)); - if (update_main_cache_tree(WRITE_TREE_SILENT) == 0) { + discard_index(&the_index); + read_index_from(&the_index, get_lock_file_path(&index_lock), + get_git_dir()); + if (cache_tree_update(&the_index, WRITE_TREE_SILENT) == 0) { if (reopen_lock_file(&index_lock) < 0) die(_("unable to write index file")); if (write_locked_index(&the_index, &index_lock, 0)) @@ -387,12 +441,14 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix * (B) on failure, rollback the real index. */ if (all || (also && pathspec.nr)) { - hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); - add_files_to_cache(also ? prefix : NULL, &pathspec, 0); + repo_hold_locked_index(the_repository, &index_lock, + LOCK_DIE_ON_ERROR); + add_files_to_cache(the_repository, also ? prefix : NULL, + &pathspec, 0, 0); refresh_cache_or_die(refresh_flags); - update_main_cache_tree(WRITE_TREE_SILENT); + cache_tree_update(&the_index, WRITE_TREE_SILENT); if (write_locked_index(&the_index, &index_lock, 0)) - die(_("unable to write new_index file")); + die(_("unable to write new index file")); commit_style = COMMIT_NORMAL; ret = get_lock_file_path(&index_lock); goto out; @@ -408,14 +464,15 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix * We still need to refresh the index here. */ if (!only && !pathspec.nr) { - hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); + repo_hold_locked_index(the_repository, &index_lock, + LOCK_DIE_ON_ERROR); refresh_cache_or_die(refresh_flags); - if (active_cache_changed - || !cache_tree_fully_valid(active_cache_tree)) - update_main_cache_tree(WRITE_TREE_SILENT); + if (the_index.cache_changed + || !cache_tree_fully_valid(the_index.cache_tree)) + cache_tree_update(&the_index, WRITE_TREE_SILENT); if (write_locked_index(&the_index, &index_lock, COMMIT_LOCK | SKIP_IF_UNCHANGED)) - die(_("unable to write new_index file")); + die(_("unable to write new index file")); commit_style = COMMIT_AS_IS; ret = get_index_file(); goto out; @@ -445,23 +502,25 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix if (whence != FROM_COMMIT) { if (whence == FROM_MERGE) die(_("cannot do a partial commit during a merge.")); - else if (whence == FROM_CHERRY_PICK) + else if (is_from_cherry_pick(whence)) die(_("cannot do a partial commit during a cherry-pick.")); + else if (is_from_rebase(whence)) + die(_("cannot do a partial commit during a rebase.")); } - if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec)) + if (list_paths(&partial, !current_head ? NULL : "HEAD", &pathspec)) exit(1); - discard_cache(); - if (read_cache() < 0) + discard_index(&the_index); + if (repo_read_index(the_repository) < 0) die(_("cannot read the index")); - hold_locked_index(&index_lock, LOCK_DIE_ON_ERROR); + repo_hold_locked_index(the_repository, &index_lock, LOCK_DIE_ON_ERROR); add_remove_files(&partial); - refresh_cache(REFRESH_QUIET); - update_main_cache_tree(WRITE_TREE_SILENT); + refresh_index(&the_index, REFRESH_QUIET, NULL, NULL, NULL); + cache_tree_update(&the_index, WRITE_TREE_SILENT); if (write_locked_index(&the_index, &index_lock, 0)) - die(_("unable to write new_index file")); + die(_("unable to write new index file")); hold_lock_file_for_update(&false_lock, git_path("next-index-%"PRIuMAX, @@ -470,14 +529,14 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix create_base_index(current_head); add_remove_files(&partial); - refresh_cache(REFRESH_QUIET); + refresh_index(&the_index, REFRESH_QUIET, NULL, NULL, NULL); if (write_locked_index(&the_index, &false_lock, 0)) die(_("unable to write temporary index file")); - discard_cache(); + discard_index(&the_index); ret = get_lock_file_path(&false_lock); - read_cache_from(ret); + read_index_from(&the_index, ret, get_git_dir()); out: string_list_clear(&partial, 0); clear_pathspec(&pathspec); @@ -500,9 +559,9 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int s->index_file = index_file; s->fp = fp; s->nowarn = nowarn; - s->is_initial = get_oid(s->reference, &oid) ? 1 : 0; + s->is_initial = repo_get_oid(the_repository, s->reference, &oid) ? 1 : 0; if (!s->is_initial) - hashcpy(s->sha1_commit, oid.hash); + oidcpy(&s->oid_commit, &oid); s->status_format = status_format; s->ignore_submodule_arg = ignore_submodule_arg; @@ -529,7 +588,7 @@ static void export_one(const char *var, const char *s, const char *e, int hack) struct strbuf buf = STRBUF_INIT; if (hack) strbuf_addch(&buf, hack); - strbuf_addf(&buf, "%.*s", (int)(e - s), s); + strbuf_add(&buf, s, e - s); setenv(var, buf.buf, 1); strbuf_release(&buf); } @@ -604,7 +663,8 @@ static void determine_author_info(struct strbuf *author_ident) set_ident_var(&date, strbuf_detach(&date_buf, NULL)); } - strbuf_addstr(author_ident, fmt_ident(name, email, date, IDENT_STRICT)); + strbuf_addstr(author_ident, fmt_ident(name, email, WANT_AUTHOR_IDENT, date, + IDENT_STRICT)); assert_split_ident(&author, author_ident); export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0); export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0); @@ -625,9 +685,10 @@ static void adjust_comment_line_char(const struct strbuf *sb) char *candidate; const char *p; - comment_line_char = candidates[0]; - if (!memchr(sb->buf, comment_line_char, sb->len)) + if (!memchr(sb->buf, candidates[0], sb->len)) { + comment_line_str = xstrfmt("%c", candidates[0]); return; + } p = sb->buf; candidate = strchr(candidates, *p); @@ -646,7 +707,23 @@ static void adjust_comment_line_char(const struct strbuf *sb) if (!*p) die(_("unable to select a comment character that is not used\n" "in the current commit message")); - comment_line_char = *p; + comment_line_str = xstrfmt("%c", *p); +} + +static void prepare_amend_commit(struct commit *commit, struct strbuf *sb, + struct pretty_print_context *ctx) +{ + const char *buffer, *subject, *fmt; + + buffer = repo_get_commit_buffer(the_repository, commit, NULL); + find_commit_subject(buffer, &subject); + /* + * If we amend the 'amend!' commit then we don't want to + * duplicate the subject line. + */ + fmt = starts_with(subject, "amend!") ? "%b" : "%B"; + repo_format_commit_message(the_repository, commit, fmt, sb, ctx); + repo_unuse_commit_buffer(the_repository, commit, buffer); } static int prepare_to_commit(const char *index_file, const char *prefix, @@ -662,11 +739,13 @@ static int prepare_to_commit(const char *index_file, const char *prefix, const char *hook_arg2 = NULL; int clean_message_contents = (cleanup_mode != COMMIT_MSG_CLEANUP_NONE); int old_display_comment_prefix; + int invoked_hook; /* This checks and barfs if author is badly specified */ determine_author_info(author_ident); - if (!no_verify && run_commit_hook(use_editor, index_file, "pre-commit", NULL)) + if (!no_verify && run_commit_hook(use_editor, index_file, &invoked_hook, + "pre-commit", NULL)) return 0; if (squash_message) { @@ -681,10 +760,11 @@ static int prepare_to_commit(const char *index_file, const char *prefix, struct commit *c; c = lookup_commit_reference_by_name(squash_message); if (!c) - die(_("could not lookup commit %s"), squash_message); + die(_("could not lookup commit '%s'"), squash_message); ctx.output_encoding = get_commit_output_encoding(); - format_commit_message(c, "squash! %s\n\n", &sb, - &ctx); + repo_format_commit_message(the_repository, c, + "squash! %s\n\n", &sb, + &ctx); } } @@ -712,16 +792,37 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } else if (fixup_message) { struct pretty_print_context ctx = {0}; struct commit *commit; - commit = lookup_commit_reference_by_name(fixup_message); + char *fmt; + commit = lookup_commit_reference_by_name(fixup_commit); if (!commit) - die(_("could not lookup commit %s"), fixup_message); + die(_("could not lookup commit '%s'"), fixup_commit); ctx.output_encoding = get_commit_output_encoding(); - format_commit_message(commit, "fixup! %s\n\n", - &sb, &ctx); - if (have_option_m) - strbuf_addbuf(&sb, &message); + fmt = xstrfmt("%s! %%s\n\n", fixup_prefix); + repo_format_commit_message(the_repository, commit, fmt, &sb, + &ctx); + free(fmt); hook_arg1 = "message"; + + /* + * Only `-m` commit message option is checked here, as + * it supports `--fixup` to append the commit message. + * + * The other commit message options `-c`/`-C`/`-F` are + * incompatible with all the forms of `--fixup` and + * have already errored out while parsing the `git commit` + * options. + */ + if (have_option_m && !strcmp(fixup_prefix, "fixup")) + strbuf_addbuf(&sb, &message); + + if (!strcmp(fixup_prefix, "amend")) { + if (have_option_m) + die(_("options '%s' and '%s:%s' cannot be used together"), "-m", "--fixup", fixup_message); + prepare_amend_commit(commit, &sb, &ctx); + } } else if (!stat(git_path_merge_msg(the_repository), &statbuf)) { + size_t merge_msg_start; + /* * prepend SQUASH_MSG here if it exists and a * "merge --squash" was originally performed @@ -732,8 +833,16 @@ static int prepare_to_commit(const char *index_file, const char *prefix, hook_arg1 = "squash"; } else hook_arg1 = "merge"; + + merge_msg_start = sb.len; if (strbuf_read_file(&sb, git_path_merge_msg(the_repository), 0) < 0) die_errno(_("could not read MERGE_MSG")); + + if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS && + wt_status_locate_end(sb.buf + merge_msg_start, + sb.len - merge_msg_start) < + sb.len - merge_msg_start) + s->added_cut_line = 1; } else if (!stat(git_path_squash_msg(the_repository), &statbuf)) { if (strbuf_read_file(&sb, git_path_squash_msg(the_repository), 0) < 0) die_errno(_("could not read SQUASH_MSG")); @@ -751,7 +860,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, */ else if (whence == FROM_MERGE) hook_arg1 = "merge"; - else if (whence == FROM_CHERRY_PICK) { + else if (is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) { hook_arg1 = "commit"; hook_arg2 = "CHERRY_PICK_HEAD"; } @@ -767,7 +876,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix, } s->fp = fopen_for_writing(git_path_commit_editmsg()); - if (s->fp == NULL) + if (!s->fp) die_errno(_("could not open '%s'"), git_path_commit_editmsg()); /* Ignore status.displayCommentPrefix: we do need comments in COMMIT_EDITMSG. */ @@ -781,10 +890,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix, s->hints = 0; if (clean_message_contents) - strbuf_stripspace(&sb, 0); + strbuf_stripspace(&sb, NULL); if (signoff) - append_signoff(&sb, ignore_non_trailer(sb.buf, sb.len), 0); + append_signoff(&sb, ignored_log_message_bytes(sb.buf, sb.len), 0); if (fwrite(sb.buf, 1, sb.len, s->fp) < sb.len) die_errno(_("could not write commit template")); @@ -799,43 +908,48 @@ static int prepare_to_commit(const char *index_file, const char *prefix, int ident_shown = 0; int saved_color_setting; struct ident_split ci, ai; - + const char *hint_cleanup_all = allow_empty_message ? + _("Please enter the commit message for your changes." + " Lines starting\nwith '%s' will be ignored.\n") : + _("Please enter the commit message for your changes." + " Lines starting\nwith '%s' will be ignored, and an empty" + " message aborts the commit.\n"); + const char *hint_cleanup_space = allow_empty_message ? + _("Please enter the commit message for your changes." + " Lines starting\n" + "with '%s' will be kept; you may remove them" + " yourself if you want to.\n") : + _("Please enter the commit message for your changes." + " Lines starting\n" + "with '%s' will be kept; you may remove them" + " yourself if you want to.\n" + "An empty message aborts the commit.\n"); if (whence != FROM_COMMIT) { if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) - wt_status_add_cut_line(s->fp); - status_printf_ln(s, GIT_COLOR_NORMAL, - whence == FROM_MERGE - ? _("\n" - "It looks like you may be committing a merge.\n" - "If this is not correct, please remove the file\n" - " %s\n" - "and try again.\n") - : _("\n" - "It looks like you may be committing a cherry-pick.\n" - "If this is not correct, please remove the file\n" - " %s\n" - "and try again.\n"), + wt_status_add_cut_line(s); + status_printf_ln( + s, GIT_COLOR_NORMAL, whence == FROM_MERGE ? - git_path_merge_head(the_repository) : - git_path_cherry_pick_head(the_repository)); + _("\n" + "It looks like you may be committing a merge.\n" + "If this is not correct, please run\n" + " git update-ref -d MERGE_HEAD\n" + "and try again.\n") : + _("\n" + "It looks like you may be committing a cherry-pick.\n" + "If this is not correct, please run\n" + " git update-ref -d CHERRY_PICK_HEAD\n" + "and try again.\n")); } fprintf(s->fp, "\n"); if (cleanup_mode == COMMIT_MSG_CLEANUP_ALL) - status_printf(s, GIT_COLOR_NORMAL, - _("Please enter the commit message for your changes." - " Lines starting\nwith '%c' will be ignored, and an empty" - " message aborts the commit.\n"), comment_line_char); - else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS && - whence == FROM_COMMIT) - wt_status_add_cut_line(s->fp); - else /* COMMIT_MSG_CLEANUP_SPACE, that is. */ - status_printf(s, GIT_COLOR_NORMAL, - _("Please enter the commit message for your changes." - " Lines starting\n" - "with '%c' will be kept; you may remove them" - " yourself if you want to.\n" - "An empty message aborts the commit.\n"), comment_line_char); + status_printf(s, GIT_COLOR_NORMAL, hint_cleanup_all, comment_line_str); + else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) { + if (whence == FROM_COMMIT) + wt_status_add_cut_line(s); + } else /* COMMIT_MSG_CLEANUP_SPACE, that is. */ + status_printf(s, GIT_COLOR_NORMAL, hint_cleanup_space, comment_line_str); /* * These should never fail because they come from our own @@ -880,19 +994,21 @@ static int prepare_to_commit(const char *index_file, const char *prefix, struct object_id oid; const char *parent = "HEAD"; - if (!active_nr && read_cache() < 0) + if (!the_index.initialized && repo_read_index(the_repository) < 0) die(_("Cannot read index")); if (amend) parent = "HEAD^1"; - if (get_oid(parent, &oid)) { + if (repo_get_oid(the_repository, parent, &oid)) { int i, ita_nr = 0; - for (i = 0; i < active_nr; i++) - if (ce_intent_to_add(active_cache[i])) + /* TODO: audit for interaction with sparse-index. */ + ensure_full_index(&the_index); + for (i = 0; i < the_index.cache_nr; i++) + if (ce_intent_to_add(the_index.cache[i])) ita_nr++; - committable = active_nr - ita_nr > 0; + committable = the_index.cache_nr - ita_nr > 0; } else { /* * Unless the user did explicitly request a submodule @@ -908,13 +1024,27 @@ static int prepare_to_commit(const char *index_file, const char *prefix, if (ignore_submodule_arg && !strcmp(ignore_submodule_arg, "all")) flags.ignore_submodules = 1; - committable = index_differs_from(parent, &flags, 1); + committable = index_differs_from(the_repository, + parent, &flags, 1); } } strbuf_release(&committer_ident); fclose(s->fp); + if (trailer_args.nr) { + struct child_process run_trailer = CHILD_PROCESS_INIT; + + strvec_pushl(&run_trailer.args, "interpret-trailers", + "--in-place", "--no-divider", + git_path_commit_editmsg(), NULL); + strvec_pushv(&run_trailer.args, trailer_args.v); + run_trailer.git_cmd = 1; + if (run_command(&run_trailer)) + die(_("unable to pass trailers to --trailers")); + strvec_clear(&trailer_args); + } + /* * Reject an attempt to record a non-merge empty commit without * explicit --allow-empty. In the cherry-pick case, it may be @@ -922,53 +1052,58 @@ static int prepare_to_commit(const char *index_file, const char *prefix, */ if (!committable && whence != FROM_MERGE && !allow_empty && !(amend && is_a_merge(current_head))) { + s->hints = advice_enabled(ADVICE_STATUS_HINTS); s->display_comment_prefix = old_display_comment_prefix; run_status(stdout, index_file, prefix, 0, s); if (amend) fputs(_(empty_amend_advice), stderr); - else if (whence == FROM_CHERRY_PICK) { + else if (is_from_cherry_pick(whence) || + whence == FROM_REBASE_PICK) { fputs(_(empty_cherry_pick_advice), stderr); - if (!sequencer_in_use) + if (whence == FROM_CHERRY_PICK_SINGLE) fputs(_(empty_cherry_pick_advice_single), stderr); - else + else if (whence == FROM_CHERRY_PICK_MULTI) fputs(_(empty_cherry_pick_advice_multi), stderr); + else + fputs(_(empty_rebase_pick_advice), stderr); } return 0; } - if (!no_verify && find_hook("pre-commit")) { + if (!no_verify && invoked_hook) { /* - * Re-read the index as pre-commit hook could have updated it, - * and write it out as a tree. We must do this before we invoke + * Re-read the index as the pre-commit-commit hook was invoked + * and could have updated it. We must do this before we invoke * the editor and after we invoke run_status above. */ - discard_cache(); + discard_index(&the_index); } - read_cache_from(index_file); + read_index_from(&the_index, index_file, get_git_dir()); - if (update_main_cache_tree(0)) { + if (cache_tree_update(&the_index, 0)) { error(_("Error building trees")); return 0; } - if (run_commit_hook(use_editor, index_file, "prepare-commit-msg", + if (run_commit_hook(use_editor, index_file, NULL, "prepare-commit-msg", git_path_commit_editmsg(), hook_arg1, hook_arg2, NULL)) return 0; if (use_editor) { - struct argv_array env = ARGV_ARRAY_INIT; + struct strvec env = STRVEC_INIT; - argv_array_pushf(&env, "GIT_INDEX_FILE=%s", index_file); - if (launch_editor(git_path_commit_editmsg(), NULL, env.argv)) { + strvec_pushf(&env, "GIT_INDEX_FILE=%s", index_file); + if (launch_editor(git_path_commit_editmsg(), NULL, env.v)) { fprintf(stderr, _("Please supply the message using either -m or -F option.\n")); exit(1); } - argv_array_clear(&env); + strvec_clear(&env); } if (!no_verify && - run_commit_hook(use_editor, index_file, "commit-msg", git_path_commit_editmsg(), NULL)) { + run_commit_hook(use_editor, index_file, NULL, "commit-msg", + git_path_commit_editmsg(), NULL)) { return 0; } @@ -980,7 +1115,6 @@ static const char *find_author_by_nickname(const char *name) struct rev_info revs; struct commit *commit; struct strbuf buf = STRBUF_INIT; - struct string_list mailmap = STRING_LIST_INIT_NODUP; const char *av[20]; int ac = 0; @@ -991,8 +1125,9 @@ static const char *find_author_by_nickname(const char *name) av[++ac] = buf.buf; av[++ac] = NULL; setup_revisions(ac, av, &revs, NULL); - revs.mailmap = &mailmap; - read_mailmap(revs.mailmap, NULL); + revs.mailmap = xmalloc(sizeof(struct string_list)); + string_list_init_nodup(revs.mailmap); + read_mailmap(revs.mailmap); if (prepare_revision_walk(&revs)) die(_("revision walk setup failed")); @@ -1001,8 +1136,9 @@ static const char *find_author_by_nickname(const char *name) struct pretty_print_context ctx = {0}; ctx.date_mode.type = DATE_NORMAL; strbuf_release(&buf); - format_commit_message(commit, "%aN <%aE>", &buf, &ctx); - clear_mailmap(&mailmap); + repo_format_commit_message(the_repository, commit, + "%aN <%aE>", &buf, &ctx); + release_revisions(&revs); return strbuf_detach(&buf, NULL); } die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name); @@ -1022,18 +1158,45 @@ static void handle_ignored_arg(struct wt_status *s) die(_("Invalid ignored mode '%s'"), ignored_arg); } +static enum untracked_status_type parse_untracked_setting_name(const char *u) +{ + /* + * Please update $__git_untracked_file_modes in + * git-completion.bash when you add new options + */ + switch (git_parse_maybe_bool(u)) { + case 0: + u = "no"; + break; + case 1: + u = "normal"; + break; + default: + break; + } + + if (!strcmp(u, "no")) + return SHOW_NO_UNTRACKED_FILES; + else if (!strcmp(u, "normal")) + return SHOW_NORMAL_UNTRACKED_FILES; + else if (!strcmp(u, "all")) + return SHOW_ALL_UNTRACKED_FILES; + else + return SHOW_UNTRACKED_FILES_ERROR; +} + static void handle_untracked_files_arg(struct wt_status *s) { + enum untracked_status_type u; + if (!untracked_files_arg) - ; /* default already initialized */ - else if (!strcmp(untracked_files_arg, "no")) - s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; - else if (!strcmp(untracked_files_arg, "normal")) - s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; - else if (!strcmp(untracked_files_arg, "all")) - s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; - else - die(_("Invalid untracked files mode '%s'"), untracked_files_arg); + return; /* default already initialized */ + + u = parse_untracked_setting_name(untracked_files_arg); + if (u == SHOW_UNTRACKED_FILES_ERROR) + die(_("Invalid untracked files mode '%s'"), + untracked_files_arg); + s->show_untracked_files = u; } static const char *read_commit_message(const char *name) @@ -1043,9 +1206,9 @@ static const char *read_commit_message(const char *name) commit = lookup_commit_reference_by_name(name); if (!commit) - die(_("could not lookup commit %s"), name); + die(_("could not lookup commit '%s'"), name); out_enc = get_commit_output_encoding(); - return logmsg_reencode(commit, NULL, out_enc); + return repo_logmsg_reencode(the_repository, commit, NULL, out_enc); } /* @@ -1055,9 +1218,11 @@ static const char *read_commit_message(const char *name) static struct status_deferred_config { enum wt_status_format status_format; int show_branch; + enum ahead_behind_flags ahead_behind; } status_deferred_config = { STATUS_FORMAT_UNSPECIFIED, - -1 /* unspecified */ + -1, /* unspecified */ + AHEAD_BEHIND_UNSPECIFIED, }; static void finalize_deferred_config(struct wt_status *s) @@ -1071,7 +1236,7 @@ static void finalize_deferred_config(struct wt_status *s) status_format == STATUS_FORMAT_UNSPECIFIED) status_format = STATUS_FORMAT_PORCELAIN; else if (status_format == STATUS_FORMAT_LONG) - die(_("--long and -z are incompatible")); + die(_("options '%s' and '%s' cannot be used together"), "--long", "-z"); } if (use_deferred_config && status_format == STATUS_FORMAT_UNSPECIFIED) @@ -1084,10 +1249,35 @@ static void finalize_deferred_config(struct wt_status *s) if (s->show_branch < 0) s->show_branch = 0; + /* + * If the user did not give a "--[no]-ahead-behind" command + * line argument *AND* we will print in a human-readable format + * (short, long etc.) then we inherit from the status.aheadbehind + * config setting. In all other cases (and porcelain V[12] formats + * in particular), we inherit _FULL for backwards compatibility. + */ + if (use_deferred_config && + s->ahead_behind_flags == AHEAD_BEHIND_UNSPECIFIED) + s->ahead_behind_flags = status_deferred_config.ahead_behind; + if (s->ahead_behind_flags == AHEAD_BEHIND_UNSPECIFIED) s->ahead_behind_flags = AHEAD_BEHIND_FULL; } +static void check_fixup_reword_options(int argc, const char *argv[]) { + if (whence != FROM_COMMIT) { + if (whence == FROM_MERGE) + die(_("You are in the middle of a merge -- cannot reword.")); + else if (is_from_cherry_pick(whence)) + die(_("You are in the middle of a cherry-pick -- cannot reword.")); + } + if (argc) + die(_("reword option of '%s' and path '%s' cannot be used together"), "--fixup", *argv); + if (patch_interactive || interactive || all || also || only) + die(_("reword option of '%s' and '%s' cannot be used together"), + "--fixup", "--patch/--interactive/--all/--include/--only"); +} + static int parse_and_validate_options(int argc, const char *argv[], const struct option *options, const char * const usage[], @@ -1095,8 +1285,6 @@ static int parse_and_validate_options(int argc, const char *argv[], struct commit *current_head, struct wt_status *s) { - int f = 0; - argc = parse_options(argc, argv, prefix, options, usage, 0); finalize_deferred_config(s); @@ -1104,12 +1292,10 @@ static int parse_and_validate_options(int argc, const char *argv[], force_author = find_author_by_nickname(force_author); if (force_author && renew_authorship) - die(_("Using both --reset-author and --author does not make sense")); + die(_("options '%s' and '%s' cannot be used together"), "--reset-author", "--author"); - if (logfile || have_option_m || use_message || fixup_message) + if (logfile || have_option_m || use_message) use_editor = 0; - if (0 <= edit_flag) - use_editor = edit_flag; /* Sanity check options */ if (amend && !current_head) @@ -1117,30 +1303,29 @@ static int parse_and_validate_options(int argc, const char *argv[], if (amend && whence != FROM_COMMIT) { if (whence == FROM_MERGE) die(_("You are in the middle of a merge -- cannot amend.")); - else if (whence == FROM_CHERRY_PICK) + else if (is_from_cherry_pick(whence)) die(_("You are in the middle of a cherry-pick -- cannot amend.")); + else if (whence == FROM_REBASE_PICK) + die(_("You are in the middle of a rebase -- cannot amend.")); } if (fixup_message && squash_message) - die(_("Options --squash and --fixup cannot be used together")); - if (use_message) - f++; - if (edit_message) - f++; - if (fixup_message) - f++; - if (logfile) - f++; - if (f > 1) - die(_("Only one of -c/-C/-F/--fixup can be used.")); - if (have_option_m && (edit_message || use_message || logfile)) - die((_("Option -m cannot be combined with -c/-C/-F."))); - if (f || have_option_m) + die(_("options '%s' and '%s' cannot be used together"), "--squash", "--fixup"); + die_for_incompatible_opt4(!!use_message, "-C", + !!edit_message, "-c", + !!logfile, "-F", + !!fixup_message, "--fixup"); + die_for_incompatible_opt4(have_option_m, "-m", + !!edit_message, "-c", + !!use_message, "-C", + !!logfile, "-F"); + if (use_message || edit_message || logfile ||fixup_message || have_option_m) template_file = NULL; if (edit_message) use_message = edit_message; if (amend && !use_message && !fixup_message) use_message = "HEAD"; - if (!use_message && whence != FROM_CHERRY_PICK && renew_authorship) + if (!use_message && !is_from_cherry_pick(whence) && + !is_from_rebase(whence) && renew_authorship) die(_("--reset-author can be used only with -C, -c or --amend.")); if (use_message) { use_message_buffer = read_commit_message(use_message); @@ -1149,7 +1334,8 @@ static int parse_and_validate_options(int argc, const char *argv[], author_message_buffer = use_message_buffer; } } - if (whence == FROM_CHERRY_PICK && !renew_authorship) { + if ((is_from_cherry_pick(whence) || whence == FROM_REBASE_PICK) && + !renew_authorship) { author_message = "CHERRY_PICK_HEAD"; author_message_buffer = read_commit_message(author_message); } @@ -1157,29 +1343,55 @@ static int parse_and_validate_options(int argc, const char *argv[], if (patch_interactive) interactive = 1; - if (also + only + all + interactive > 1) - die(_("Only one of --include/--only/--all/--interactive/--patch can be used.")); - if (argc == 0 && (also || (only && !amend && !allow_empty))) - die(_("No paths with --include/--only does not make sense.")); - if (!cleanup_arg || !strcmp(cleanup_arg, "default")) - cleanup_mode = use_editor ? COMMIT_MSG_CLEANUP_ALL : - COMMIT_MSG_CLEANUP_SPACE; - else if (!strcmp(cleanup_arg, "verbatim")) - cleanup_mode = COMMIT_MSG_CLEANUP_NONE; - else if (!strcmp(cleanup_arg, "whitespace")) - cleanup_mode = COMMIT_MSG_CLEANUP_SPACE; - else if (!strcmp(cleanup_arg, "strip")) - cleanup_mode = COMMIT_MSG_CLEANUP_ALL; - else if (!strcmp(cleanup_arg, "scissors")) - cleanup_mode = use_editor ? COMMIT_MSG_CLEANUP_SCISSORS : - COMMIT_MSG_CLEANUP_SPACE; - else - die(_("Invalid cleanup mode %s"), cleanup_arg); + die_for_incompatible_opt4(also, "-i/--include", + only, "-o/--only", + all, "-a/--all", + interactive, "--interactive/-p/--patch"); + if (fixup_message) { + /* + * We limit --fixup's suboptions to only alpha characters. + * If the first character after a run of alpha is colon, + * then the part before the colon may be a known suboption + * name like `amend` or `reword`, or a misspelt suboption + * name. In either case, we treat it as + * --fixup=<suboption>:<arg>. + * + * Otherwise, we are dealing with --fixup=<commit>. + */ + char *p = fixup_message; + while (isalpha(*p)) + p++; + if (p > fixup_message && *p == ':') { + *p = '\0'; + fixup_commit = p + 1; + if (!strcmp("amend", fixup_message) || + !strcmp("reword", fixup_message)) { + fixup_prefix = "amend"; + allow_empty = 1; + if (*fixup_message == 'r') { + check_fixup_reword_options(argc, argv); + only = 1; + } + } else { + die(_("unknown option: --fixup=%s:%s"), fixup_message, fixup_commit); + } + } else { + fixup_commit = fixup_message; + fixup_prefix = "fixup"; + use_editor = 0; + } + } + + if (0 <= edit_flag) + use_editor = edit_flag; + + cleanup_mode = get_cleanup_mode(cleanup_arg, use_editor); handle_untracked_files_arg(s); if (all && argc > 0) - die(_("Paths with -a does not make sense.")); + die(_("paths '%s ...' with -a does not make sense"), + argv[0]); if (status_format != STATUS_FORMAT_NONE) dry_run = 1; @@ -1187,13 +1399,13 @@ static int parse_and_validate_options(int argc, const char *argv[], return argc; } -static int dry_run_commit(int argc, const char **argv, const char *prefix, +static int dry_run_commit(const char **argv, const char *prefix, const struct commit *current_head, struct wt_status *s) { int committable; const char *index_file; - index_file = prepare_index(argc, argv, prefix, current_head, 1); + index_file = prepare_index(argv, prefix, current_head, 1); committable = run_status(stdout, index_file, prefix, 0, s); rollback_index_files(); @@ -1210,7 +1422,8 @@ static int parse_status_slot(const char *slot) return LOOKUP_CONFIG(color_status_slots, slot); } -static int git_status_config(const char *k, const char *v, void *cb) +static int git_status_config(const char *k, const char *v, + const struct config_context *ctx, void *cb) { struct wt_status *s = cb; const char *slot_name; @@ -1219,7 +1432,8 @@ static int git_status_config(const char *k, const char *v, void *cb) return git_column_config(k, v, "status", &s->colopts); if (!strcmp(k, "status.submodulesummary")) { int is_bool; - s->submodule_summary = git_config_bool_or_int(k, v, &is_bool); + s->submodule_summary = git_config_bool_or_int(k, v, ctx->kvi, + &is_bool); if (is_bool && s->submodule_summary) s->submodule_summary = -1; return 0; @@ -1235,6 +1449,10 @@ static int git_status_config(const char *k, const char *v, void *cb) status_deferred_config.show_branch = git_config_bool(k, v); return 0; } + if (!strcmp(k, "status.aheadbehind")) { + status_deferred_config.ahead_behind = git_config_bool(k, v); + return 0; + } if (!strcmp(k, "status.showstash")) { s->show_stash = git_config_bool(k, v); return 0; @@ -1261,25 +1479,21 @@ static int git_status_config(const char *k, const char *v, void *cb) return 0; } if (!strcmp(k, "status.showuntrackedfiles")) { - if (!v) - return config_error_nonbool(k); - else if (!strcmp(v, "no")) - s->show_untracked_files = SHOW_NO_UNTRACKED_FILES; - else if (!strcmp(v, "normal")) - s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; - else if (!strcmp(v, "all")) - s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES; - else + enum untracked_status_type u; + + u = parse_untracked_setting_name(v); + if (u == SHOW_UNTRACKED_FILES_ERROR) return error(_("Invalid untracked files mode '%s'"), v); + s->show_untracked_files = u; return 0; } if (!strcmp(k, "diff.renamelimit")) { if (s->rename_limit == -1) - s->rename_limit = git_config_int(k, v); + s->rename_limit = git_config_int(k, v, ctx->kvi); return 0; } if (!strcmp(k, "status.renamelimit")) { - s->rename_limit = git_config_int(k, v); + s->rename_limit = git_config_int(k, v, ctx->kvi); return 0; } if (!strcmp(k, "diff.renames")) { @@ -1291,7 +1505,7 @@ static int git_status_config(const char *k, const char *v, void *cb) s->detect_rename = git_config_rename(k, v); return 0; } - return git_diff_ui_config(k, v, NULL); + return git_diff_ui_config(k, v, ctx, NULL); } int cmd_status(int argc, const char **argv, const char *prefix) @@ -1312,9 +1526,9 @@ int cmd_status(int argc, const char **argv, const char *prefix) N_("show stash information")), OPT_BOOL(0, "ahead-behind", &s.ahead_behind_flags, N_("compute full ahead/behind values")), - { OPTION_CALLBACK, 0, "porcelain", &status_format, + OPT_CALLBACK_F(0, "porcelain", &status_format, N_("version"), N_("machine-readable output"), - PARSE_OPT_OPTARG, opt_parse_porcelain }, + PARSE_OPT_OPTARG, opt_parse_porcelain), OPT_SET_INT(0, "long", &status_format, N_("show status in long format (default)"), STATUS_FORMAT_LONG), @@ -1333,15 +1547,18 @@ int cmd_status(int argc, const char **argv, const char *prefix) PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, OPT_COLUMN(0, "column", &s.colopts, N_("list untracked files in columns")), OPT_BOOL(0, "no-renames", &no_renames, N_("do not detect renames")), - { OPTION_CALLBACK, 'M', "find-renames", &rename_score_arg, + OPT_CALLBACK_F('M', "find-renames", &rename_score_arg, N_("n"), N_("detect renames, optionally set similarity index"), - PARSE_OPT_OPTARG, opt_parse_rename_score }, + PARSE_OPT_OPTARG | PARSE_OPT_NONEG, opt_parse_rename_score), OPT_END(), }; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_status_usage, builtin_status_options); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + status_init_config(&s, git_status_config); argc = parse_options(argc, argv, prefix, builtin_status_options, @@ -1363,19 +1580,19 @@ int cmd_status(int argc, const char **argv, const char *prefix) if (status_format != STATUS_FORMAT_PORCELAIN && status_format != STATUS_FORMAT_PORCELAIN_V2) progress_flag = REFRESH_PROGRESS; - read_index_preload(&the_index, &s.pathspec, progress_flag); + repo_read_index(the_repository); refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED|progress_flag, &s.pathspec, NULL, NULL); if (use_optional_locks()) - fd = hold_locked_index(&index_lock, 0); + fd = repo_hold_locked_index(the_repository, &index_lock, 0); else fd = -1; - s.is_initial = get_oid(s.reference, &oid) ? 1 : 0; + s.is_initial = repo_get_oid(the_repository, s.reference, &oid) ? 1 : 0; if (!s.is_initial) - hashcpy(s.sha1_commit, oid.hash); + oidcpy(&s.oid_commit, &oid); s.ignore_submodule_arg = ignore_submodule_arg; s.status_format = status_format; @@ -1392,7 +1609,7 @@ int cmd_status(int argc, const char **argv, const char *prefix) wt_status_collect(&s); if (0 <= fd) - update_index_if_able(&the_index, &index_lock); + repo_update_index_if_able(the_repository, &index_lock); if (s.relative_paths) s.prefix = prefix; @@ -1403,10 +1620,10 @@ int cmd_status(int argc, const char **argv, const char *prefix) return 0; } -static int git_commit_config(const char *k, const char *v, void *cb) +static int git_commit_config(const char *k, const char *v, + const struct config_context *ctx, void *cb) { struct wt_status *s = cb; - int status; if (!strcmp(k, "commit.template")) return git_config_pathname(&template_file, k, v); @@ -1422,41 +1639,16 @@ static int git_commit_config(const char *k, const char *v, void *cb) } if (!strcmp(k, "commit.verbose")) { int is_bool; - config_commit_verbose = git_config_bool_or_int(k, v, &is_bool); + config_commit_verbose = git_config_bool_or_int(k, v, ctx->kvi, + &is_bool); return 0; } - status = git_gpg_config(k, v, NULL); - if (status) - return status; - return git_status_config(k, v, s); -} - -int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...) -{ - struct argv_array hook_env = ARGV_ARRAY_INIT; - va_list args; - int ret; - - argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", index_file); - - /* - * Let the hook know that no editor will be launched. - */ - if (!editor_is_used) - argv_array_push(&hook_env, "GIT_EDITOR=:"); - - va_start(args, name); - ret = run_hook_ve(hook_env.argv,name, args); - va_end(args); - argv_array_clear(&hook_env); - - return ret; + return git_status_config(k, v, ctx, s); } int cmd_commit(int argc, const char **argv, const char *prefix) { - const char *argv_gc_auto[] = {"gc", "--auto", NULL}; static struct wt_status s; static struct option builtin_commit_options[] = { OPT__QUIET(&quiet, N_("suppress summary after successful commit")), @@ -1469,13 +1661,18 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_CALLBACK('m', "message", &message, N_("message"), N_("commit message"), opt_parse_m), OPT_STRING('c', "reedit-message", &edit_message, N_("commit"), N_("reuse and edit message from specified commit")), OPT_STRING('C', "reuse-message", &use_message, N_("commit"), N_("reuse message from specified commit")), - OPT_STRING(0, "fixup", &fixup_message, N_("commit"), N_("use autosquash formatted message to fixup specified commit")), + /* + * TRANSLATORS: Leave "[(amend|reword):]" as-is, + * and only translate <commit>. + */ + OPT_STRING(0, "fixup", &fixup_message, N_("[(amend|reword):]commit"), N_("use autosquash formatted message to fixup or amend/reword specified commit")), OPT_STRING(0, "squash", &squash_message, N_("commit"), N_("use autosquash formatted message to squash specified commit")), OPT_BOOL(0, "reset-author", &renew_authorship, N_("the commit is authored by me now (used with -C/-c/--amend)")), - OPT_BOOL('s', "signoff", &signoff, N_("add Signed-off-by:")), + OPT_CALLBACK_F(0, "trailer", &trailer_args, N_("trailer"), N_("add custom trailer(s)"), PARSE_OPT_NONEG, opt_pass_trailer), + OPT_BOOL('s', "signoff", &signoff, N_("add a Signed-off-by trailer")), OPT_FILENAME('t', "template", &template_file, N_("use specified template file")), OPT_BOOL('e', "edit", &edit_flag, N_("force edit of commit")), - OPT_STRING(0, "cleanup", &cleanup_arg, N_("default"), N_("how to strip spaces and #comments from message")), + OPT_CLEANUP(&cleanup_arg), OPT_BOOL(0, "status", &include_status, N_("include status in commit message template")), { OPTION_STRING, 'S', "gpg-sign", &sign_commit, N_("key-id"), N_("GPG sign commit"), PARSE_OPT_OPTARG, NULL, (intptr_t) "" }, @@ -1504,6 +1701,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix) OPT_BOOL(0, "amend", &amend, N_("amend previous commit")), OPT_BOOL(0, "no-post-rewrite", &no_post_rewrite, N_("bypass post-rewrite hook")), { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, N_("mode"), N_("show untracked files, optional modes: all, normal, no. (Default: all)"), PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, + OPT_PATHSPEC_FROM_FILE(&pathspec_from_file), + OPT_PATHSPEC_FILE_NUL(&pathspec_file_nul), /* end commit contents options */ OPT_HIDDEN_BOOL(0, "allow-empty", &allow_empty, @@ -1523,20 +1722,24 @@ int cmd_commit(int argc, const char **argv, const char *prefix) struct commit *current_head = NULL; struct commit_extra_header *extra = NULL; struct strbuf err = STRBUF_INIT; + int ret = 0; if (argc == 2 && !strcmp(argv[1], "-h")) usage_with_options(builtin_commit_usage, builtin_commit_options); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + status_init_config(&s, git_commit_config); s.commit_template = 1; status_format = STATUS_FORMAT_NONE; /* Ignore status.short */ s.colopts = 0; - if (get_oid("HEAD", &oid)) + if (repo_get_oid(the_repository, "HEAD", &oid)) current_head = NULL; else { current_head = lookup_commit_or_die(&oid, "HEAD"); - if (parse_commit(current_head)) + if (repo_parse_commit(the_repository, current_head)) die(_("could not parse HEAD commit")); } verbose = -1; /* unspecified */ @@ -1547,15 +1750,16 @@ int cmd_commit(int argc, const char **argv, const char *prefix) verbose = (config_commit_verbose < 0) ? 0 : config_commit_verbose; if (dry_run) - return dry_run_commit(argc, argv, prefix, current_head, &s); - index_file = prepare_index(argc, argv, prefix, current_head, 0); + return dry_run_commit(argv, prefix, current_head, &s); + index_file = prepare_index(argv, prefix, current_head, 0); /* Set up everything for writing the commit object. This includes running hooks, writing the trees, and interacting with the user. */ if (!prepare_to_commit(index_file, prefix, current_head, &s, &author_ident)) { + ret = 1; rollback_index_files(); - return 1; + goto cleanup; } /* Determine parents */ @@ -1597,8 +1801,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix) reduce_heads_replace(&parents); } else { if (!reflog_msg) - reflog_msg = (whence == FROM_CHERRY_PICK) + reflog_msg = is_from_cherry_pick(whence) ? "commit (cherry-pick)" + : is_from_rebase(whence) + ? "commit (rebase)" : "commit"; commit_list_insert(current_head, &parents); } @@ -1611,11 +1817,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) die(_("could not read commit message: %s"), strerror(saved_errno)); } - if (verbose || /* Truncate the message just before the diff, if any. */ - cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) - strbuf_setlen(&sb, wt_status_locate_end(sb.buf, sb.len)); - if (cleanup_mode != COMMIT_MSG_CLEANUP_NONE) - strbuf_stripspace(&sb, cleanup_mode == COMMIT_MSG_CLEANUP_ALL); + cleanup_message(&sb, cleanup_mode, verbose); if (message_is_empty(&sb, cleanup_mode) && !allow_empty_message) { rollback_index_files(); @@ -1628,21 +1830,33 @@ int cmd_commit(int argc, const char **argv, const char *prefix) exit(1); } + if (fixup_message && starts_with(sb.buf, "amend! ") && + !allow_empty_message) { + struct strbuf body = STRBUF_INIT; + size_t len = commit_subject_length(sb.buf); + strbuf_addstr(&body, sb.buf + len); + if (message_is_empty(&body, cleanup_mode)) { + rollback_index_files(); + fprintf(stderr, _("Aborting commit due to empty commit message body.\n")); + exit(1); + } + strbuf_release(&body); + } + if (amend) { - const char *exclude_gpgsig[2] = { "gpgsig", NULL }; + const char *exclude_gpgsig[3] = { "gpgsig", "gpgsig-sha256", NULL }; extra = read_commit_extra_headers(current_head, exclude_gpgsig); } else { struct commit_extra_header **tail = &extra; append_merge_tag_headers(parents, &tail); } - if (commit_tree_extended(sb.buf, sb.len, &active_cache_tree->oid, - parents, &oid, author_ident.buf, sign_commit, - extra)) { + if (commit_tree_extended(sb.buf, sb.len, &the_index.cache_tree->oid, + parents, &oid, author_ident.buf, NULL, + sign_commit, extra)) { rollback_index_files(); die(_("failed to write commit object")); } - strbuf_release(&author_ident); free_commit_extra_headers(extra); if (update_head_with_reflog(current_head, &oid, reflog_msg, &sb, @@ -1651,8 +1865,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix) die("%s", err.buf); } - unlink(git_path_cherry_pick_head(the_repository)); - unlink(git_path_revert_head(the_repository)); + sequencer_post_commit_cleanup(the_repository, 0); unlink(git_path_merge_head(the_repository)); unlink(git_path_merge_msg(the_repository)); unlink(git_path_merge_mode(the_repository)); @@ -1660,17 +1873,17 @@ int cmd_commit(int argc, const char **argv, const char *prefix) if (commit_index_files()) die(_("repository has been updated, but unable to write\n" - "new_index file. Check that disk is not full and quota is\n" - "not exceeded, and then \"git reset HEAD\" to recover.")); + "new index file. Check that disk is not full and quota is\n" + "not exceeded, and then \"git restore --staged :/\" to recover.")); - if (git_env_bool(GIT_TEST_COMMIT_GRAPH, 0)) - write_commit_graph_reachable(get_object_directory(), 0, 0); + git_test_write_commit_graph_or_die(); repo_rerere(the_repository, 0); - run_command_v_opt(argv_gc_auto, RUN_GIT_CMD); - run_commit_hook(use_editor, get_index_file(), "post-commit", NULL); + run_auto_maintenance(quiet); + run_commit_hook(use_editor, get_index_file(), NULL, "post-commit", + NULL); if (amend && !no_post_rewrite) { - commit_post_rewrite(current_head, &oid); + commit_post_rewrite(the_repository, current_head, &oid); } if (!quiet) { unsigned int flags = 0; @@ -1679,10 +1892,15 @@ int cmd_commit(int argc, const char **argv, const char *prefix) flags |= SUMMARY_INITIAL_COMMIT; if (author_date_is_interesting()) flags |= SUMMARY_SHOW_AUTHOR_DATE; - print_commit_summary(prefix, &oid, flags); + print_commit_summary(the_repository, prefix, + &oid, flags); } - UNLEAK(err); - UNLEAK(sb); - return 0; + apply_autostash_ref(the_repository, "MERGE_AUTOSTASH"); + +cleanup: + strbuf_release(&author_ident); + strbuf_release(&err); + strbuf_release(&sb); + return ret; } |