pack-bitmap: convert khash_sha1 maps into kh_oid_map
[gitweb.git] / builtin / commit.c
index 3bc5dff2c00c9556e6c032381d5baf18246bf8dc..1c9e8e2228c7ce58375bc247c4ad5850a1bd7d2d 100644 (file)
@@ -5,6 +5,7 @@
  * Based on git-commit.sh by Junio C Hamano and Linus Torvalds
  */
 
+#define USE_THE_INDEX_COMPATIBILITY_MACROS
 #include "cache.h"
 #include "config.h"
 #include "lockfile.h"
@@ -32,6 +33,9 @@
 #include "column.h"
 #include "sequencer.h"
 #include "mailmap.h"
+#include "help.h"
+#include "commit-reach.h"
+#include "commit-graph.h"
 
 static const char * const builtin_commit_usage[] = {
        N_("git commit [<options>] [--] <pathspec>..."),
@@ -43,31 +47,6 @@ static const char * const builtin_status_usage[] = {
        NULL
 };
 
-static const char implicit_ident_advice_noconfig[] =
-N_("Your name and email address were configured automatically based\n"
-"on your username and hostname. Please check that they are accurate.\n"
-"You can suppress this message by setting them explicitly. Run the\n"
-"following command and follow the instructions in your editor to edit\n"
-"your configuration file:\n"
-"\n"
-"    git config --global --edit\n"
-"\n"
-"After doing this, you may fix the identity used for this commit with:\n"
-"\n"
-"    git commit --amend --reset-author\n");
-
-static const char implicit_ident_advice_config[] =
-N_("Your name and email address were configured automatically based\n"
-"on your username and hostname. Please check that they are accurate.\n"
-"You can suppress this message by setting them explicitly:\n"
-"\n"
-"    git config --global user.name \"Your Name\"\n"
-"    git config --global user.email you@example.com\n"
-"\n"
-"After doing this, you may fix the identity used for this commit with:\n"
-"\n"
-"    git commit --amend --reset-author\n");
-
 static const char empty_amend_advice[] =
 N_("You asked to amend the most recent commit, but doing so would make\n"
 "it empty. You can repeat your command with --allow-empty, or you can\n"
@@ -91,7 +70,17 @@ N_("If you wish to skip this commit, use:\n"
 "Then \"git cherry-pick --continue\" will resume cherry-picking\n"
 "the remaining commits.\n");
 
-static GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG")
+static const char *color_status_slots[] = {
+       [WT_STATUS_HEADER]        = "header",
+       [WT_STATUS_UPDATED]       = "updated",
+       [WT_STATUS_CHANGED]       = "changed",
+       [WT_STATUS_UNTRACKED]     = "untracked",
+       [WT_STATUS_NOBRANCH]      = "noBranch",
+       [WT_STATUS_UNMERGED]      = "unmerged",
+       [WT_STATUS_LOCAL_BRANCH]  = "localBranch",
+       [WT_STATUS_REMOTE_BRANCH] = "remoteBranch",
+       [WT_STATUS_ONBRANCH]      = "branch",
+};
 
 static const char *use_message_buffer;
 static struct lock_file index_lock; /* real index */
@@ -116,7 +105,7 @@ 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 char *untracked_files_arg, *force_date, *ignore_submodule_arg;
+static char *untracked_files_arg, *force_date, *ignore_submodule_arg, *ignored_arg;
 static char *sign_commit;
 
 /*
@@ -132,7 +121,7 @@ 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 show_ignored_in_status, have_option_m;
+static int have_option_m;
 static struct strbuf message = STRBUF_INIT;
 
 static enum wt_status_format status_format = STATUS_FORMAT_UNSPECIFIED;
@@ -170,11 +159,24 @@ static int opt_parse_m(const struct option *opt, const char *arg, int unset)
        return 0;
 }
 
+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;
+
+       *value = arg;
+       return 0;
+}
+
 static void determine_whence(struct wt_status *s)
 {
-       if (file_exists(git_path_merge_head()))
+       if (file_exists(git_path_merge_head(the_repository)))
                whence = FROM_MERGE;
-       else if (file_exists(git_path_cherry_pick_head())) {
+       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;
@@ -187,10 +189,10 @@ 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);
-       init_diff_ui_defaults();
        s->hints = advice_status_hints; /* must come after git_config() */
 }
 
@@ -233,7 +235,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;
@@ -245,8 +247,7 @@ static int list_paths(struct string_list *list, const char *with_tree,
 
        if (with_tree) {
                char *max_prefix = common_prefix(pattern);
-               overlay_tree_on_index(&the_index, with_tree,
-                                     max_prefix ? max_prefix : prefix);
+               overlay_tree_on_index(&the_index, with_tree, max_prefix);
                free(max_prefix);
        }
 
@@ -256,14 +257,14 @@ static int list_paths(struct string_list *list, const char *with_tree,
 
                if (ce->ce_flags & CE_UPDATE)
                        continue;
-               if (!ce_path_match(ce, pattern, m))
+               if (!ce_path_match(&the_index, ce, pattern, m))
                        continue;
                item = string_list_insert(list, ce->name);
                if (ce_skip_worktree(ce))
                        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;
 }
@@ -351,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)
@@ -361,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));
@@ -416,13 +418,9 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
                if (active_cache_changed
                    || !cache_tree_fully_valid(active_cache_tree))
                        update_main_cache_tree(WRITE_TREE_SILENT);
-               if (active_cache_changed) {
-                       if (write_locked_index(&the_index, &index_lock,
-                                              COMMIT_LOCK))
-                               die(_("unable to write new_index file"));
-               } else {
-                       rollback_lock_file(&index_lock);
-               }
+               if (write_locked_index(&the_index, &index_lock,
+                                      COMMIT_LOCK | SKIP_IF_UNCHANGED))
+                       die(_("unable to write new_index file"));
                commit_style = COMMIT_AS_IS;
                ret = get_index_file();
                goto out;
@@ -456,7 +454,7 @@ static const char *prepare_index(int argc, const char **argv, const char *prefix
                        die(_("cannot do a partial commit during a cherry-pick."));
        }
 
-       if (list_paths(&partial, !current_head ? NULL : "HEAD", prefix, &pathspec))
+       if (list_paths(&partial, !current_head ? NULL : "HEAD", &pathspec))
                exit(1);
 
        discard_cache();
@@ -515,8 +513,9 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int
 
        wt_status_collect(s);
        wt_status_print(s);
+       wt_status_collect_free_buffers(s);
 
-       return s->commitable;
+       return s->committable;
 }
 
 static int is_a_merge(const struct commit *current_head)
@@ -527,7 +526,7 @@ static int is_a_merge(const struct commit *current_head)
 static void assert_split_ident(struct ident_split *id, const struct strbuf *buf)
 {
        if (split_ident_line(id, buf->buf, buf->len) || !id->date_begin)
-               die("BUG: unable to parse our own ident: %s", buf->buf);
+               BUG("unable to parse our own ident: %s", buf->buf);
 }
 
 static void export_one(const char *var, const char *s, const char *e, int hack)
@@ -610,7 +609,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);
@@ -662,12 +662,13 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 {
        struct stat statbuf;
        struct strbuf committer_ident = STRBUF_INIT;
-       int commitable;
+       int committable;
        struct strbuf sb = STRBUF_INIT;
        const char *hook_arg1 = NULL;
        const char *hook_arg2 = NULL;
        int clean_message_contents = (cleanup_mode != COMMIT_MSG_CLEANUP_NONE);
        int old_display_comment_prefix;
+       int merge_contains_scissors = 0;
 
        /* This checks and barfs if author is badly specified */
        determine_author_info(author_ident);
@@ -694,7 +695,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                }
        }
 
-       if (have_option_m) {
+       if (have_option_m && !fixup_message) {
                strbuf_addbuf(&sb, &message);
                hook_arg1 = "message";
        } else if (logfile && !strcmp(logfile, "-")) {
@@ -724,22 +725,34 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                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);
                hook_arg1 = "message";
-       } else if (!stat(git_path_merge_msg(), &statbuf)) {
+       } 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
                 */
-               if (!stat(git_path_squash_msg(), &statbuf)) {
-                       if (strbuf_read_file(&sb, git_path_squash_msg(), 0) < 0)
+               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"));
                        hook_arg1 = "squash";
                } else
                        hook_arg1 = "merge";
-               if (strbuf_read_file(&sb, git_path_merge_msg(), 0) < 0)
+
+               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"));
-       } else if (!stat(git_path_squash_msg(), &statbuf)) {
-               if (strbuf_read_file(&sb, git_path_squash_msg(), 0) < 0)
+
+               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)
+                       merge_contains_scissors = 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"));
                hook_arg1 = "squash";
        } else if (template_file) {
@@ -805,7 +818,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                struct ident_split ci, ai;
 
                if (whence != FROM_COMMIT) {
-                       if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
+                       if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS &&
+                               !merge_contains_scissors)
                                wt_status_add_cut_line(s->fp);
                        status_printf_ln(s, GIT_COLOR_NORMAL,
                            whence == FROM_MERGE
@@ -820,8 +834,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                                        "       %s\n"
                                        "and try again.\n"),
                                whence == FROM_MERGE ?
-                                       git_path_merge_head() :
-                                       git_path_cherry_pick_head());
+                                       git_path_merge_head(the_repository) :
+                                       git_path_cherry_pick_head(the_repository));
                }
 
                fprintf(s->fp, "\n");
@@ -830,10 +844,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                                _("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. */
+               else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS) {
+                       if (whence == FROM_COMMIT && !merge_contains_scissors)
+                               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"
@@ -877,8 +891,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
                saved_color_setting = s->use_color;
                s->use_color = 0;
-               commitable = run_status(s->fp, index_file, prefix, 1, s);
+               committable = run_status(s->fp, index_file, prefix, 1, s);
                s->use_color = saved_color_setting;
+               string_list_clear(&s->change, 1);
        } else {
                struct object_id oid;
                const char *parent = "HEAD";
@@ -895,7 +910,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                        for (i = 0; i < active_nr; i++)
                                if (ce_intent_to_add(active_cache[i]))
                                        ita_nr++;
-                       commitable = active_nr - ita_nr > 0;
+                       committable = active_nr - ita_nr > 0;
                } else {
                        /*
                         * Unless the user did explicitly request a submodule
@@ -911,7 +926,8 @@ 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;
-                       commitable = index_differs_from(parent, &flags, 1);
+                       committable = index_differs_from(the_repository,
+                                                        parent, &flags, 1);
                }
        }
        strbuf_release(&committer_ident);
@@ -923,7 +939,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
         * explicit --allow-empty. In the cherry-pick case, it may be
         * empty due to conflict resolution, which the user should okay.
         */
-       if (!commitable && whence != FROM_MERGE && !allow_empty &&
+       if (!committable && whence != FROM_MERGE && !allow_empty &&
            !(amend && is_a_merge(current_head))) {
                s->display_comment_prefix = old_display_comment_prefix;
                run_status(stdout, index_file, prefix, 0, s);
@@ -987,7 +1003,7 @@ static const char *find_author_by_nickname(const char *name)
        const char *av[20];
        int ac = 0;
 
-       init_revisions(&revs, NULL);
+       repo_init_revisions(the_repository, &revs, NULL);
        strbuf_addf(&buf, "--author=%s", name);
        av[++ac] = "--all";
        av[++ac] = "-i";
@@ -1011,6 +1027,19 @@ static const char *find_author_by_nickname(const char *name)
        die(_("--author '%s' is not 'Name <email>' and matches no existing author"), name);
 }
 
+static void handle_ignored_arg(struct wt_status *s)
+{
+       if (!ignored_arg)
+               ; /* default already initialized */
+       else if (!strcmp(ignored_arg, "traditional"))
+               s->show_ignored_mode = SHOW_TRADITIONAL_IGNORED;
+       else if (!strcmp(ignored_arg, "no"))
+               s->show_ignored_mode = SHOW_NO_IGNORED;
+       else if (!strcmp(ignored_arg, "matching"))
+               s->show_ignored_mode = SHOW_MATCHING_IGNORED;
+       else
+               die(_("Invalid ignored mode '%s'"), ignored_arg);
+}
 
 static void handle_untracked_files_arg(struct wt_status *s)
 {
@@ -1022,6 +1051,10 @@ static void handle_untracked_files_arg(struct wt_status *s)
                s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
        else if (!strcmp(untracked_files_arg, "all"))
                s->show_untracked_files = SHOW_ALL_UNTRACKED_FILES;
+       /*
+        * Please update $__git_untracked_file_modes in
+        * git-completion.bash when you add new options
+        */
        else
                die(_("Invalid untracked files mode '%s'"), untracked_files_arg);
 }
@@ -1073,6 +1106,9 @@ static void finalize_deferred_config(struct wt_status *s)
                s->show_branch = status_deferred_config.show_branch;
        if (s->show_branch < 0)
                s->show_branch = 0;
+
+       if (s->ahead_behind_flags == AHEAD_BEHIND_UNSPECIFIED)
+               s->ahead_behind_flags = AHEAD_BEHIND_FULL;
 }
 
 static int parse_and_validate_options(int argc, const char *argv[],
@@ -1119,8 +1155,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
                f++;
        if (f > 1)
                die(_("Only one of -c/-C/-F/--fixup can be used."));
-       if (have_option_m && f > 0)
-               die((_("Option -m cannot be combined with -c/-C/-F/--fixup.")));
+       if (have_option_m && (edit_message || use_message || logfile))
+               die((_("Option -m cannot be combined with -c/-C/-F.")));
        if (f || have_option_m)
                template_file = NULL;
        if (edit_message)
@@ -1148,25 +1184,13 @@ static int parse_and_validate_options(int argc, const char *argv[],
                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);
+       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;
@@ -1177,37 +1201,24 @@ static int parse_and_validate_options(int argc, const char *argv[],
 static int dry_run_commit(int argc, const char **argv, const char *prefix,
                          const struct commit *current_head, struct wt_status *s)
 {
-       int commitable;
+       int committable;
        const char *index_file;
 
        index_file = prepare_index(argc, argv, prefix, current_head, 1);
-       commitable = run_status(stdout, index_file, prefix, 0, s);
+       committable = run_status(stdout, index_file, prefix, 0, s);
        rollback_index_files();
 
-       return commitable ? 0 : 1;
+       return committable ? 0 : 1;
 }
 
+define_list_config_array_extra(color_status_slots, {"added"});
+
 static int parse_status_slot(const char *slot)
 {
-       if (!strcasecmp(slot, "header"))
-               return WT_STATUS_HEADER;
-       if (!strcasecmp(slot, "branch"))
-               return WT_STATUS_ONBRANCH;
-       if (!strcasecmp(slot, "updated") || !strcasecmp(slot, "added"))
+       if (!strcasecmp(slot, "added"))
                return WT_STATUS_UPDATED;
-       if (!strcasecmp(slot, "changed"))
-               return WT_STATUS_CHANGED;
-       if (!strcasecmp(slot, "untracked"))
-               return WT_STATUS_UNTRACKED;
-       if (!strcasecmp(slot, "nobranch"))
-               return WT_STATUS_NOBRANCH;
-       if (!strcasecmp(slot, "unmerged"))
-               return WT_STATUS_UNMERGED;
-       if (!strcasecmp(slot, "localBranch"))
-               return WT_STATUS_LOCAL_BRANCH;
-       if (!strcasecmp(slot, "remoteBranch"))
-               return WT_STATUS_REMOTE_BRANCH;
-       return -1;
+
+       return LOOKUP_CONFIG(color_status_slots, slot);
 }
 
 static int git_status_config(const char *k, const char *v, void *cb)
@@ -1273,12 +1284,33 @@ static int git_status_config(const char *k, const char *v, void *cb)
                        return error(_("Invalid untracked files mode '%s'"), v);
                return 0;
        }
+       if (!strcmp(k, "diff.renamelimit")) {
+               if (s->rename_limit == -1)
+                       s->rename_limit = git_config_int(k, v);
+               return 0;
+       }
+       if (!strcmp(k, "status.renamelimit")) {
+               s->rename_limit = git_config_int(k, v);
+               return 0;
+       }
+       if (!strcmp(k, "diff.renames")) {
+               if (s->detect_rename == -1)
+                       s->detect_rename = git_config_rename(k, v);
+               return 0;
+       }
+       if (!strcmp(k, "status.renames")) {
+               s->detect_rename = git_config_rename(k, v);
+               return 0;
+       }
        return git_diff_ui_config(k, v, NULL);
 }
 
 int cmd_status(int argc, const char **argv, const char *prefix)
 {
+       static int no_renames = -1;
+       static const char *rename_score_arg = (const char *)-1;
        static struct wt_status s;
+       unsigned int progress_flag = 0;
        int fd;
        struct object_id oid;
        static struct option builtin_status_options[] = {
@@ -1289,6 +1321,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)
                         N_("show branch information")),
                OPT_BOOL(0, "show-stash", &s.show_stash,
                         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,
                  N_("version"), N_("machine-readable output"),
                  PARSE_OPT_OPTARG, opt_parse_porcelain },
@@ -1301,12 +1335,18 @@ int cmd_status(int argc, const char **argv, const char *prefix)
                  N_("mode"),
                  N_("show untracked files, optional modes: all, normal, no. (Default: all)"),
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
-               OPT_BOOL(0, "ignored", &show_ignored_in_status,
-                        N_("show ignored files")),
+               { OPTION_STRING, 0, "ignored", &ignored_arg,
+                 N_("mode"),
+                 N_("show ignored files, optional modes: traditional, matching, no. (Default: traditional)"),
+                 PARSE_OPT_OPTARG, NULL, (intptr_t)"traditional" },
                { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, N_("when"),
                  N_("ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)"),
                  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,
+                 N_("n"), N_("detect renames, optionally set similarity index"),
+                 PARSE_OPT_OPTARG | PARSE_OPT_NONEG, opt_parse_rename_score },
                OPT_END(),
        };
 
@@ -1321,14 +1361,23 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        finalize_deferred_config(&s);
 
        handle_untracked_files_arg(&s);
-       if (show_ignored_in_status)
-               s.show_ignored_files = 1;
+       handle_ignored_arg(&s);
+
+       if (s.show_ignored_mode == SHOW_MATCHING_IGNORED &&
+           s.show_untracked_files == SHOW_NO_UNTRACKED_FILES)
+               die(_("Unsupported combination of ignored and untracked-files arguments"));
+
        parse_pathspec(&s.pathspec, 0,
                       PATHSPEC_PREFER_FULL,
                       prefix, argv);
 
-       read_cache_preload(&s.pathspec);
-       refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, &s.pathspec, NULL, NULL);
+       if (status_format != STATUS_FORMAT_PORCELAIN &&
+           status_format != STATUS_FORMAT_PORCELAIN_V2)
+               progress_flag = REFRESH_PROGRESS;
+       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);
@@ -1342,109 +1391,27 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        s.ignore_submodule_arg = ignore_submodule_arg;
        s.status_format = status_format;
        s.verbose = verbose;
+       if (no_renames != -1)
+               s.detect_rename = !no_renames;
+       if ((intptr_t)rename_score_arg != -1) {
+               if (s.detect_rename < DIFF_DETECT_RENAME)
+                       s.detect_rename = DIFF_DETECT_RENAME;
+               if (rename_score_arg)
+                       s.rename_score = parse_rename_score(&rename_score_arg);
+       }
 
        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;
 
        wt_status_print(&s);
-       return 0;
-}
-
-static const char *implicit_ident_advice(void)
-{
-       char *user_config = expand_user_path("~/.gitconfig", 0);
-       char *xdg_config = xdg_config_home("config");
-       int config_exists = file_exists(user_config) || file_exists(xdg_config);
-
-       free(user_config);
-       free(xdg_config);
-
-       if (config_exists)
-               return _(implicit_ident_advice_config);
-       else
-               return _(implicit_ident_advice_noconfig);
-
-}
-
-static void print_summary(const char *prefix, const struct object_id *oid,
-                         int initial_commit)
-{
-       struct rev_info rev;
-       struct commit *commit;
-       struct strbuf format = STRBUF_INIT;
-       const char *head;
-       struct pretty_print_context pctx = {0};
-       struct strbuf author_ident = STRBUF_INIT;
-       struct strbuf committer_ident = STRBUF_INIT;
-
-       commit = lookup_commit(oid);
-       if (!commit)
-               die(_("couldn't look up newly created commit"));
-       if (parse_commit(commit))
-               die(_("could not parse newly created commit"));
-
-       strbuf_addstr(&format, "format:%h] %s");
-
-       format_commit_message(commit, "%an <%ae>", &author_ident, &pctx);
-       format_commit_message(commit, "%cn <%ce>", &committer_ident, &pctx);
-       if (strbuf_cmp(&author_ident, &committer_ident)) {
-               strbuf_addstr(&format, "\n Author: ");
-               strbuf_addbuf_percentquote(&format, &author_ident);
-       }
-       if (author_date_is_interesting()) {
-               struct strbuf date = STRBUF_INIT;
-               format_commit_message(commit, "%ad", &date, &pctx);
-               strbuf_addstr(&format, "\n Date: ");
-               strbuf_addbuf_percentquote(&format, &date);
-               strbuf_release(&date);
-       }
-       if (!committer_ident_sufficiently_given()) {
-               strbuf_addstr(&format, "\n Committer: ");
-               strbuf_addbuf_percentquote(&format, &committer_ident);
-               if (advice_implicit_identity) {
-                       strbuf_addch(&format, '\n');
-                       strbuf_addstr(&format, implicit_ident_advice());
-               }
-       }
-       strbuf_release(&author_ident);
-       strbuf_release(&committer_ident);
-
-       init_revisions(&rev, prefix);
-       setup_revisions(0, NULL, &rev, NULL);
-
-       rev.diff = 1;
-       rev.diffopt.output_format =
-               DIFF_FORMAT_SHORTSTAT | DIFF_FORMAT_SUMMARY;
-
-       rev.verbose_header = 1;
-       rev.show_root_diff = 1;
-       get_commit_format(format.buf, &rev);
-       rev.always_show_header = 0;
-       rev.diffopt.detect_rename = 1;
-       rev.diffopt.break_opt = 0;
-       diff_setup_done(&rev.diffopt);
-
-       head = resolve_ref_unsafe("HEAD", 0, NULL, NULL);
-       if (!head)
-               die_errno(_("unable to resolve HEAD after creating commit"));
-       if (!strcmp(head, "HEAD"))
-               head = _("detached HEAD");
-       else
-               skip_prefix(head, "refs/heads/", &head);
-       printf("[%s%s ", head, initial_commit ? _(" (root-commit)") : "");
-
-       if (!log_tree_commit(&rev, commit)) {
-               rev.always_show_header = 1;
-               rev.use_terminator = 1;
-               log_tree_commit(&rev, commit);
-       }
+       wt_status_collect_free_buffers(&s);
 
-       strbuf_release(&format);
+       return 0;
 }
 
 static int git_commit_config(const char *k, const char *v, void *cb)
@@ -1500,6 +1467,7 @@ int run_commit_hook(int editor_is_used, const char *index_file, const char *name
 
 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")),
@@ -1518,7 +1486,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                OPT_BOOL('s', "signoff", &signoff, N_("add Signed-off-by:")),
                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) "" },
@@ -1535,6 +1503,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                OPT_SET_INT(0, "short", &status_format, N_("show status concisely"),
                            STATUS_FORMAT_SHORT),
                OPT_BOOL(0, "branch", &s.show_branch, N_("show branch information")),
+               OPT_BOOL(0, "ahead-behind", &s.ahead_behind_flags,
+                        N_("compute full ahead/behind values")),
                OPT_SET_INT(0, "porcelain", &status_format,
                            N_("machine-readable output"), STATUS_FORMAT_PORCELAIN),
                OPT_SET_INT(0, "long", &status_format,
@@ -1617,7 +1587,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                if (!reflog_msg)
                        reflog_msg = "commit (merge)";
                pptr = commit_list_append(current_head, pptr);
-               fp = xfopen(git_path_merge_head(), "r");
+               fp = xfopen(git_path_merge_head(the_repository), "r");
                while (strbuf_getline_lf(&m, fp) != EOF) {
                        struct commit *parent;
 
@@ -1628,14 +1598,14 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                }
                fclose(fp);
                strbuf_release(&m);
-               if (!stat(git_path_merge_mode(), &statbuf)) {
-                       if (strbuf_read_file(&sb, git_path_merge_mode(), 0) < 0)
+               if (!stat(git_path_merge_mode(the_repository), &statbuf)) {
+                       if (strbuf_read_file(&sb, git_path_merge_mode(the_repository), 0) < 0)
                                die_errno(_("could not read MERGE_MODE"));
                        if (!strcmp(sb.buf, "no-ff"))
                                allow_fast_forward = 0;
                }
                if (allow_fast_forward)
-                       parents = reduce_heads(parents);
+                       reduce_heads_replace(&parents);
        } else {
                if (!reflog_msg)
                        reflog_msg = (whence == FROM_CHERRY_PICK)
@@ -1652,11 +1622,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();
@@ -1677,8 +1643,9 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                append_merge_tag_headers(parents, &tail);
        }
 
-       if (commit_tree_extended(sb.buf, sb.len, active_cache_tree->oid.hash,
-                        parents, oid.hash, author_ident.buf, sign_commit, extra)) {
+       if (commit_tree_extended(sb.buf, sb.len, &active_cache_tree->oid,
+                                parents, &oid, author_ident.buf, sign_commit,
+                                extra)) {
                rollback_index_files();
                die(_("failed to write commit object"));
        }
@@ -1691,25 +1658,36 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                die("%s", err.buf);
        }
 
-       unlink(git_path_cherry_pick_head());
-       unlink(git_path_revert_head());
-       unlink(git_path_merge_head());
-       unlink(git_path_merge_msg());
-       unlink(git_path_merge_mode());
-       unlink(git_path_squash_msg());
+       sequencer_post_commit_cleanup(the_repository);
+       unlink(git_path_merge_head(the_repository));
+       unlink(git_path_merge_msg(the_repository));
+       unlink(git_path_merge_mode(the_repository));
+       unlink(git_path_squash_msg(the_repository));
 
        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."));
+               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."));
+
+       if (git_env_bool(GIT_TEST_COMMIT_GRAPH, 0))
+               write_commit_graph_reachable(get_object_directory(), 0, 0);
 
-       rerere(0);
+       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);
        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;
+
+               if (!current_head)
+                       flags |= SUMMARY_INITIAL_COMMIT;
+               if (author_date_is_interesting())
+                       flags |= SUMMARY_SHOW_AUTHOR_DATE;
+               print_commit_summary(the_repository, prefix,
+                                    &oid, flags);
        }
-       if (!quiet)
-               print_summary(prefix, &oid, !current_head);
 
        UNLEAK(err);
        UNLEAK(sb);