Merge branch 'maint'
[gitweb.git] / builtin / commit.c
index 3714582e1988f7c286412afb779cbfefe4849657..a876a73e6b4c1f1690839ce2d447a4b77573c9f5 100644 (file)
@@ -27,6 +27,7 @@
 #include "quote.h"
 #include "submodule.h"
 #include "gpg-interface.h"
+#include "column.h"
 
 static const char * const builtin_commit_usage[] = {
        "git commit [options] [--] <filepattern>...",
@@ -88,6 +89,7 @@ static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
 static int no_post_rewrite, allow_empty_message;
 static char *untracked_files_arg, *force_date, *ignore_submodule_arg;
 static char *sign_commit;
+static unsigned int colopts;
 
 /*
  * The default commit message cleanup mode will remove the lines
@@ -194,24 +196,6 @@ static void determine_whence(struct wt_status *s)
                s->whence = whence;
 }
 
-static const char *whence_s(void)
-{
-       const char *s = "";
-
-       switch (whence) {
-       case FROM_COMMIT:
-               break;
-       case FROM_MERGE:
-               s = _("merge");
-               break;
-       case FROM_CHERRY_PICK:
-               s = _("cherry-pick");
-               break;
-       }
-
-       return s;
-}
-
 static void rollback_index_files(void)
 {
        switch (commit_style) {
@@ -453,8 +437,12 @@ static char *prepare_index(int argc, const char **argv, const char *prefix,
         */
        commit_style = COMMIT_PARTIAL;
 
-       if (whence != FROM_COMMIT)
-               die(_("cannot do a partial commit during a %s."), whence_s());
+       if (whence != FROM_COMMIT) {
+               if (whence == FROM_MERGE)
+                       die(_("cannot do a partial commit during a merge."));
+               else if (whence == FROM_CHERRY_PICK)
+                       die(_("cannot do a partial commit during a cherry-pick."));
+       }
 
        memset(&partial, 0, sizeof(partial));
        partial.strdup_strings = 1;
@@ -533,9 +521,20 @@ static int is_a_merge(const struct commit *current_head)
 
 static const char sign_off_header[] = "Signed-off-by: ";
 
+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);
+       setenv(var, buf.buf, 1);
+       strbuf_release(&buf);
+}
+
 static void determine_author_info(struct strbuf *author_ident)
 {
        char *name, *email, *date;
+       struct ident_split author;
 
        name = getenv("GIT_AUTHOR_NAME");
        email = getenv("GIT_AUTHOR_EMAIL");
@@ -585,6 +584,11 @@ static void determine_author_info(struct strbuf *author_ident)
                date = force_date;
        strbuf_addstr(author_ident, fmt_ident(name, email, date,
                                              IDENT_ERROR_ON_NO_NAME));
+       if (!split_ident_line(&author, author_ident->buf, author_ident->len)) {
+               export_one("GIT_AUTHOR_NAME", author.name_begin, author.name_end, 0);
+               export_one("GIT_AUTHOR_EMAIL", author.mail_begin, author.mail_end, 0);
+               export_one("GIT_AUTHOR_DATE", author.date_begin, author.tz_end, '@');
+       }
 }
 
 static int ends_rfc2822_footer(struct strbuf *sb)
@@ -652,6 +656,9 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
        int ident_shown = 0;
        int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
 
+       /* This checks and barfs if author is badly specified */
+       determine_author_info(author_ident);
+
        if (!no_verify && run_hook(index_file, "pre-commit", NULL))
                return 0;
 
@@ -771,37 +778,37 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
        strbuf_release(&sb);
 
-       /* This checks and barfs if author is badly specified */
-       determine_author_info(author_ident);
-
        /* This checks if committer ident is explicitly given */
        strbuf_addstr(&committer_ident, git_committer_info(0));
        if (use_editor && include_status) {
                char *ai_tmp, *ci_tmp;
                if (whence != FROM_COMMIT)
                        status_printf_ln(s, GIT_COLOR_NORMAL,
-                               _("\n"
-                               "It looks like you may be committing a %s.\n"
-                               "If this is not correct, please remove the file\n"
-                               "       %s\n"
-                               "and try again.\n"
-                               ""),
-                               whence_s(),
+                           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"),
                                git_path(whence == FROM_MERGE
                                         ? "MERGE_HEAD"
                                         : "CHERRY_PICK_HEAD"));
 
                fprintf(s->fp, "\n");
-               status_printf(s, GIT_COLOR_NORMAL,
-                       _("Please enter the commit message for your changes."));
                if (cleanup_mode == CLEANUP_ALL)
-                       status_printf_more(s, GIT_COLOR_NORMAL,
-                               _(" Lines starting\n"
-                               "with '#' will be ignored, and an empty"
+                       status_printf(s, GIT_COLOR_NORMAL,
+                               _("Please enter the commit message for your changes."
+                               " Lines starting\nwith '#' will be ignored, and an empty"
                                " message aborts the commit.\n"));
                else /* CLEANUP_SPACE, that is. */
-                       status_printf_more(s, GIT_COLOR_NORMAL,
-                               _(" Lines starting\n"
+                       status_printf(s, GIT_COLOR_NORMAL,
+                               _("Please enter the commit message for your changes."
+                               " Lines starting\n"
                                "with '#' will be kept; you may remove them"
                                " yourself if you want to.\n"
                                "An empty message aborts the commit.\n"));
@@ -905,27 +912,10 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
        return 1;
 }
 
-/*
- * Find out if the message in the strbuf contains only whitespace and
- * Signed-off-by lines.
- */
-static int message_is_empty(struct strbuf *sb)
+static int rest_is_empty(struct strbuf *sb, int start)
 {
-       struct strbuf tmpl = STRBUF_INIT;
+       int i, eol;
        const char *nl;
-       int eol, i, start = 0;
-
-       if (cleanup_mode == CLEANUP_NONE && sb->len)
-               return 0;
-
-       /* See if the template is just a prefix of the message. */
-       if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) {
-               stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
-               if (start + tmpl.len <= sb->len &&
-                   memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0)
-                       start += tmpl.len;
-       }
-       strbuf_release(&tmpl);
 
        /* Check if the rest is just whitespace and Signed-of-by's. */
        for (i = start; i < sb->len; i++) {
@@ -948,6 +938,40 @@ static int message_is_empty(struct strbuf *sb)
        return 1;
 }
 
+/*
+ * Find out if the message in the strbuf contains only whitespace and
+ * Signed-off-by lines.
+ */
+static int message_is_empty(struct strbuf *sb)
+{
+       if (cleanup_mode == CLEANUP_NONE && sb->len)
+               return 0;
+       return rest_is_empty(sb, 0);
+}
+
+/*
+ * See if the user edited the message in the editor or left what
+ * was in the template intact
+ */
+static int template_untouched(struct strbuf *sb)
+{
+       struct strbuf tmpl = STRBUF_INIT;
+       char *start;
+
+       if (cleanup_mode == CLEANUP_NONE && sb->len)
+               return 0;
+
+       if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
+               return 0;
+
+       stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
+       start = (char *)skip_prefix(sb->buf, tmpl.buf);
+       if (!start)
+               start = sb->buf;
+       strbuf_release(&tmpl);
+       return rest_is_empty(sb, start - sb->buf);
+}
+
 static const char *find_author_by_nickname(const char *name)
 {
        struct rev_info revs;
@@ -1039,8 +1063,12 @@ static int parse_and_validate_options(int argc, const char *argv[],
        /* Sanity check options */
        if (amend && !current_head)
                die(_("You have nothing to amend."));
-       if (amend && whence != FROM_COMMIT)
-               die(_("You are in the middle of a %s -- cannot amend."), whence_s());
+       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)
+                       die(_("You are in the middle of a cherry-pick -- cannot amend."));
+       }
        if (fixup_message && squash_message)
                die(_("Options --squash and --fixup cannot be used together"));
        if (use_message)
@@ -1055,6 +1083,8 @@ static int parse_and_validate_options(int argc, const char *argv[],
                die(_("Only one of -c/-C/-F/--fixup can be used."));
        if (message.len && f > 0)
                die((_("Option -m cannot be combined with -c/-C/-F/--fixup.")));
+       if (f || message.len)
+               template_file = NULL;
        if (edit_message)
                use_message = edit_message;
        if (amend && !use_message && !fixup_message)
@@ -1145,6 +1175,8 @@ static int git_status_config(const char *k, const char *v, void *cb)
 {
        struct wt_status *s = cb;
 
+       if (!prefixcmp(k, "column."))
+               return git_column_config(k, v, "status", &colopts);
        if (!strcmp(k, "status.submodulesummary")) {
                int is_bool;
                s->submodule_summary = git_config_bool_or_int(k, v, &is_bool);
@@ -1210,6 +1242,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
                { OPTION_STRING, 0, "ignore-submodules", &ignore_submodule_arg, "when",
                  "ignore changes to submodules, optional when: all, dirty, untracked. (Default: all)",
                  PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
+               OPT_COLUMN(0, "column", &colopts, "list untracked files in columns"),
                OPT_END(),
        };
 
@@ -1223,6 +1256,8 @@ int cmd_status(int argc, const char **argv, const char *prefix)
        argc = parse_options(argc, argv, prefix,
                             builtin_status_options,
                             builtin_status_usage, 0);
+       finalize_colopts(&colopts, -1);
+       s.colopts = colopts;
 
        if (null_termination && status_format == STATUS_FORMAT_LONG)
                status_format = STATUS_FORMAT_PORCELAIN;
@@ -1494,6 +1529,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
        if (cleanup_mode != CLEANUP_NONE)
                stripspace(&sb, cleanup_mode == CLEANUP_ALL);
+       if (template_untouched(&sb) && !allow_empty_message) {
+               rollback_index_files();
+               fprintf(stderr, _("Aborting commit; you did not edit the message.\n"));
+               exit(1);
+       }
        if (message_is_empty(&sb) && !allow_empty_message) {
                rollback_index_files();
                fprintf(stderr, _("Aborting commit due to empty commit message.\n"));