git-send-email: SIG{TERM,INT} handlers
[gitweb.git] / builtin-commit.c
index 518ebe0347e631c72f4e2a83b948259ee20fd213..c787bed696591f58d1336a701e57414c9d5c8cbf 100644 (file)
@@ -21,6 +21,7 @@
 #include "utf8.h"
 #include "parse-options.h"
 #include "path-list.h"
+#include "unpack-trees.h"
 
 static const char * const builtin_commit_usage[] = {
        "git-commit [options] [--] <filepattern>...",
@@ -47,8 +48,21 @@ static char *logfile, *force_author, *template_file;
 static char *edit_message, *use_message;
 static int all, edit_flag, also, interactive, only, amend, signoff;
 static int quiet, verbose, untracked_files, no_verify, allow_empty;
+/*
+ * The default commit message cleanup mode will remove the lines
+ * beginning with # (shell comments) and leading and trailing
+ * whitespaces (empty lines or containing only whitespaces)
+ * if editor is used, and only the whitespaces if the message
+ * is specified explicitly.
+ */
+static enum {
+       CLEANUP_SPACE,
+       CLEANUP_NONE,
+       CLEANUP_ALL,
+} cleanup_mode;
+static char *cleanup_arg;
 
-static int no_edit, initial_commit, in_merge;
+static int use_editor = 1, initial_commit, in_merge;
 const char *only_include_assumed;
 struct strbuf message;
 
@@ -88,6 +102,7 @@ static struct option builtin_commit_options[] = {
        OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
        OPT_BOOLEAN(0, "untracked-files", &untracked_files, "show all untracked files"),
        OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
+       OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
 
        OPT_END()
 };
@@ -107,19 +122,23 @@ static void rollback_index_files(void)
        }
 }
 
-static void commit_index_files(void)
+static int commit_index_files(void)
 {
+       int err = 0;
+
        switch (commit_style) {
        case COMMIT_AS_IS:
                break; /* nothing to do */
        case COMMIT_NORMAL:
-               commit_lock_file(&index_lock);
+               err = commit_lock_file(&index_lock);
                break;
        case COMMIT_PARTIAL:
-               commit_lock_file(&index_lock);
+               err = commit_lock_file(&index_lock);
                rollback_lock_file(&false_lock);
                break;
        }
+
+       return err;
 }
 
 /*
@@ -163,10 +182,34 @@ static void add_remove_files(struct path_list *list)
        }
 }
 
+static void create_base_index(void)
+{
+       struct tree *tree;
+       struct unpack_trees_options opts;
+       struct tree_desc t;
+
+       if (initial_commit) {
+               discard_cache();
+               return;
+       }
+
+       memset(&opts, 0, sizeof(opts));
+       opts.head_idx = 1;
+       opts.index_only = 1;
+       opts.merge = 1;
+
+       opts.fn = oneway_merge;
+       tree = parse_tree_indirect(head_sha1);
+       if (!tree)
+               die("failed to unpack HEAD tree object");
+       parse_tree(tree);
+       init_tree_desc(&t, tree->buffer, tree->size);
+       unpack_trees(1, &t, &opts);
+}
+
 static char *prepare_index(int argc, const char **argv, const char *prefix)
 {
        int fd;
-       struct tree *tree;
        struct path_list partial;
        const char **pathspec = NULL;
 
@@ -198,7 +241,8 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
                int fd = hold_locked_index(&index_lock, 1);
                add_files_to_cache(0, also ? prefix : NULL, pathspec);
                refresh_cache(REFRESH_QUIET);
-               if (write_cache(fd, active_cache, active_nr) || close(fd))
+               if (write_cache(fd, active_cache, active_nr) ||
+                   close_lock_file(&index_lock))
                        die("unable to write new_index file");
                commit_style = COMMIT_NORMAL;
                return index_lock.filename;
@@ -217,7 +261,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
                fd = hold_locked_index(&index_lock, 1);
                refresh_cache(REFRESH_QUIET);
                if (write_cache(fd, active_cache, active_nr) ||
-                   close(fd) || commit_locked_index(&index_lock))
+                   commit_locked_index(&index_lock))
                        die("unable to write new_index file");
                commit_style = COMMIT_AS_IS;
                return get_index_file();
@@ -259,23 +303,19 @@ static char *prepare_index(int argc, const char **argv, const char *prefix)
        fd = hold_locked_index(&index_lock, 1);
        add_remove_files(&partial);
        refresh_cache(REFRESH_QUIET);
-       if (write_cache(fd, active_cache, active_nr) || close(fd))
+       if (write_cache(fd, active_cache, active_nr) ||
+           close_lock_file(&index_lock))
                die("unable to write new_index file");
 
        fd = hold_lock_file_for_update(&false_lock,
                                       git_path("next-index-%d", getpid()), 1);
-       discard_cache();
-       if (!initial_commit) {
-               tree = parse_tree_indirect(head_sha1);
-               if (!tree)
-                       die("failed to unpack HEAD tree object");
-               if (read_tree(tree, 0, NULL))
-                       die("failed to read HEAD tree object");
-       }
+
+       create_base_index();
        add_remove_files(&partial);
        refresh_cache(REFRESH_QUIET);
 
-       if (write_cache(fd, active_cache, active_nr) || close(fd))
+       if (write_cache(fd, active_cache, active_nr) ||
+           close_lock_file(&false_lock))
                die("unable to write temporary index file");
        return false_lock.filename;
 }
@@ -346,7 +386,8 @@ static int prepare_log_message(const char *index_file, const char *prefix)
        if (fp == NULL)
                die("could not open %s", git_path(commit_editmsg));
 
-       stripspace(&sb, 0);
+       if (cleanup_mode != CLEANUP_NONE)
+               stripspace(&sb, 0);
 
        if (signoff) {
                struct strbuf sob;
@@ -372,21 +413,25 @@ static int prepare_log_message(const char *index_file, const char *prefix)
 
        strbuf_release(&sb);
 
-       if (no_edit) {
+       if (!use_editor) {
                struct rev_info rev;
-               unsigned char sha1[40];
+               unsigned char sha1[20];
+               const char *parent = "HEAD";
 
                fclose(fp);
 
                if (!active_nr && read_cache() < 0)
                        die("Cannot read index");
 
-               if (get_sha1("HEAD", sha1) != 0)
+               if (amend)
+                       parent = "HEAD^1";
+
+               if (get_sha1(parent, sha1))
                        return !!active_nr;
 
                init_revisions(&rev, "");
                rev.abbrev = 0;
-               setup_revisions(0, NULL, &rev, "HEAD");
+               setup_revisions(0, NULL, &rev, parent);
                DIFF_OPT_SET(&rev.diffopt, QUIET);
                DIFF_OPT_SET(&rev.diffopt, EXIT_WITH_STATUS);
                run_diff_index(&rev, 1 /* cached */);
@@ -394,7 +439,7 @@ static int prepare_log_message(const char *index_file, const char *prefix)
                return !!DIFF_OPT_TST(&rev.diffopt, HAS_CHANGES);
        }
 
-       if (in_merge && !no_edit)
+       if (in_merge)
                fprintf(fp,
                        "#\n"
                        "# It looks like you may be committing a MERGE.\n"
@@ -407,7 +452,12 @@ static int prepare_log_message(const char *index_file, const char *prefix)
        fprintf(fp,
                "\n"
                "# Please enter the commit message for your changes.\n"
-               "# (Comment lines starting with '#' will not be included)\n");
+               "# (Comment lines starting with '#' will ");
+       if (cleanup_mode == CLEANUP_ALL)
+               fprintf(fp, "not be included)\n");
+       else /* CLEANUP_SPACE, that is. */
+               fprintf(fp, "be kept.\n"
+                       "# You can remove them yourself if you want to)\n");
        if (only_include_assumed)
                fprintf(fp, "# %s\n", only_include_assumed);
 
@@ -431,10 +481,13 @@ static int message_is_empty(struct strbuf *sb, int start)
        const char *nl;
        int eol, i;
 
+       if (cleanup_mode == CLEANUP_NONE && sb->len)
+               return 0;
+
        /* See if the template is just a prefix of the message. */
        strbuf_init(&tmpl, 0);
        if (template_file && strbuf_read_file(&tmpl, template_file, 0) > 0) {
-               stripspace(&tmpl, 1);
+               stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
                if (start + tmpl.len <= sb->len &&
                    memcmp(tmpl.buf, sb->buf + start, tmpl.len) == 0)
                        start += tmpl.len;
@@ -509,9 +562,9 @@ static int parse_and_validate_options(int argc, const char *argv[],
        argc = parse_options(argc, argv, builtin_commit_options, usage, 0);
 
        if (logfile || message.len || use_message)
-               no_edit = 1;
+               use_editor = 0;
        if (edit_flag)
-               no_edit = 0;
+               use_editor = 1;
 
        if (get_sha1("HEAD", head_sha1))
                initial_commit = 1;
@@ -548,7 +601,7 @@ static int parse_and_validate_options(int argc, const char *argv[],
 
                if (get_sha1(use_message, sha1))
                        die("could not lookup commit %s", use_message);
-               commit = lookup_commit(sha1);
+               commit = lookup_commit_reference(sha1);
                if (!commit || parse_commit(commit))
                        die("could not parse commit %s", use_message);
 
@@ -587,6 +640,16 @@ static int parse_and_validate_options(int argc, const char *argv[],
                only_include_assumed = "Explicit paths specified without -i nor -o; assuming --only paths...";
                also = 0;
        }
+       if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
+               cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE;
+       else if (!strcmp(cleanup_arg, "verbatim"))
+               cleanup_mode = CLEANUP_NONE;
+       else if (!strcmp(cleanup_arg, "whitespace"))
+               cleanup_mode = CLEANUP_SPACE;
+       else if (!strcmp(cleanup_arg, "strip"))
+               cleanup_mode = CLEANUP_ALL;
+       else
+               die("Invalid cleanup mode %s", cleanup_arg);
 
        if (all && argc > 0)
                die("Paths with -a does not make sense.");
@@ -662,6 +725,10 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
        rev.show_root_diff = 1;
        rev.commit_format = get_commit_format("format:%h: %s");
        rev.always_show_header = 0;
+       rev.diffopt.detect_rename = 1;
+       rev.diffopt.rename_limit = 100;
+       rev.diffopt.break_opt = 0;
+       diff_setup_done(&rev.diffopt);
 
        printf("Created %scommit ", initial_commit ? "initial " : "");
 
@@ -696,6 +763,17 @@ static const char commit_utf8_warn[] =
 "You may want to amend it after fixing the message, or set the config\n"
 "variable i18n.commitencoding to the encoding your project uses.\n";
 
+static void add_parent(struct strbuf *sb, const unsigned char *sha1)
+{
+       struct object *obj = parse_object(sha1);
+       const char *parent = sha1_to_hex(sha1);
+       if (!obj)
+               die("Unable to find commit parent %s", parent);
+       if (obj->type != OBJ_COMMIT)
+               die("Parent %s isn't a proper commit", parent);
+       strbuf_addf(sb, "parent %s\n", parent);
+}
+
 int cmd_commit(int argc, const char **argv, const char *prefix)
 {
        int header_len;
@@ -758,21 +836,24 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                        die("could not parse HEAD commit");
 
                for (c = commit->parents; c; c = c->next)
-                       strbuf_addf(&sb, "parent %s\n",
-                                     sha1_to_hex(c->item->object.sha1));
+                       add_parent(&sb, c->item->object.sha1);
        } else if (in_merge) {
                struct strbuf m;
                FILE *fp;
 
                reflog_msg = "commit (merge)";
-               strbuf_addf(&sb, "parent %s\n", sha1_to_hex(head_sha1));
+               add_parent(&sb, head_sha1);
                strbuf_init(&m, 0);
                fp = fopen(git_path("MERGE_HEAD"), "r");
                if (fp == NULL)
                        die("could not open %s for reading: %s",
                            git_path("MERGE_HEAD"), strerror(errno));
-               while (strbuf_getline(&m, fp, '\n') != EOF)
-                       strbuf_addf(&sb, "parent %s\n", m.buf);
+               while (strbuf_getline(&m, fp, '\n') != EOF) {
+                       unsigned char sha1[20];
+                       if (get_sha1_hex(m.buf, sha1) < 0)
+                               die("Corrupt MERGE_HEAD file (%s)", m.buf);
+                       add_parent(&sb, sha1);
+               }
                fclose(fp);
                strbuf_release(&m);
        } else {
@@ -788,7 +869,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
 
        /* Get the commit message and validate it */
        header_len = sb.len;
-       if (!no_edit) {
+       if (use_editor) {
                char index[PATH_MAX];
                const char *env[2] = { index, NULL };
                snprintf(index, sizeof(index), "GIT_INDEX_FILE=%s", index_file);
@@ -809,7 +890,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        if (p != NULL)
                strbuf_setlen(&sb, p - sb.buf + 1);
 
-       stripspace(&sb, 1);
+       if (cleanup_mode != CLEANUP_NONE)
+               stripspace(&sb, cleanup_mode == CLEANUP_ALL);
        if (sb.len < header_len || message_is_empty(&sb, header_len)) {
                rollback_index_files();
                die("no commit message?  aborting commit.");
@@ -848,7 +930,10 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
        unlink(git_path("MERGE_HEAD"));
        unlink(git_path("MERGE_MSG"));
 
-       commit_index_files();
+       if (commit_index_files())
+               die ("Repository has been updated, but unable to write\n"
+                    "new_index file. Check that disk is not full or quota is\n"
+                    "not exceeded, and then \"git reset HEAD\" to recover.");
 
        rerere();
        run_hook(get_index_file(), "post-commit", NULL);