Code

commit --amend: copy notes to the new commit
[git.git] / builtin-commit.c
index 073fe90ba1e04c3845fe49521db39b28339acb43..ccc4f926c09b4650751795075a8d64ab52a36b0a 100644 (file)
@@ -36,7 +36,20 @@ static const char * const builtin_status_usage[] = {
        NULL
 };
 
+static const char implicit_ident_advice[] =
+"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"
+"If the identity used for this commit is wrong, you can fix it with:\n"
+"\n"
+"    git commit --amend --author='Your Name <you@example.com>'\n";
+
 static unsigned char head_sha1[20];
+
 static char *use_message_buffer;
 static const char commit_editmsg[] = "COMMIT_EDITMSG";
 static struct lock_file index_lock; /* real index */
@@ -53,6 +66,7 @@ static char *edit_message, *use_message;
 static char *author_name, *author_email, *author_date;
 static int all, edit_flag, also, interactive, only, amend, signoff;
 static int quiet, verbose, no_verify, allow_empty, dry_run, renew_authorship;
+static int no_post_rewrite;
 static char *untracked_files_arg, *force_date;
 /*
  * The default commit message cleanup mode will remove the lines
@@ -68,7 +82,7 @@ static enum {
 } cleanup_mode;
 static char *cleanup_arg;
 
-static int use_editor = 1, initial_commit, in_merge;
+static int use_editor = 1, initial_commit, in_merge, include_status = 1;
 static const char *only_include_assumed;
 static struct strbuf message;
 
@@ -107,6 +121,7 @@ static struct option builtin_commit_options[] = {
        OPT_FILENAME('t', "template", &template_file, "use specified template file"),
        OPT_BOOLEAN('e', "edit", &edit_flag, "force edit of commit"),
        OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"),
+       OPT_BOOLEAN(0, "status", &include_status, "include status in commit message template"),
        /* end commit message options */
 
        OPT_GROUP("Commit contents options"),
@@ -123,6 +138,7 @@ static struct option builtin_commit_options[] = {
        OPT_BOOLEAN('z', "null", &null_termination,
                    "terminate entries with NUL"),
        OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"),
+       OPT_BOOLEAN(0, "no-post-rewrite", &no_post_rewrite, "bypass post-rewrite hook"),
        { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" },
        OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"),
        /* end commit contents options */
@@ -183,11 +199,15 @@ static int list_paths(struct string_list *list, const char *with_tree,
 
        for (i = 0; i < active_nr; i++) {
                struct cache_entry *ce = active_cache[i];
+               struct string_list_item *item;
+
                if (ce->ce_flags & CE_UPDATE)
                        continue;
                if (!match_pathspec(pattern, ce->name, ce_namelen(ce), 0, m))
                        continue;
-               string_list_insert(ce->name, list);
+               item = string_list_insert(ce->name, list);
+               if (ce_skip_worktree(ce))
+                       item->util = item; /* better a valid pointer than a fake one */
        }
 
        return report_path_error(m, pattern, prefix ? strlen(prefix) : 0);
@@ -200,6 +220,10 @@ static void add_remove_files(struct string_list *list)
                struct stat st;
                struct string_list_item *p = &(list->items[i]);
 
+               /* p->util is skip-worktree */
+               if (p->util)
+                       continue;
+
                if (!lstat(p->string, &st)) {
                        if (add_to_cache(p->string, &st, 0))
                                die("updating files failed");
@@ -236,6 +260,16 @@ static void create_base_index(void)
                exit(128); /* We've already reported the error, finish dying */
 }
 
+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))
+               die_resolve_conflict("commit");
+}
+
 static char *prepare_index(int argc, const char **argv, const char *prefix, int is_status)
 {
        int fd;
@@ -275,7 +309,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
        if (all || (also && pathspec && *pathspec)) {
                int fd = hold_locked_index(&index_lock, 1);
                add_files_to_cache(also ? prefix : NULL, pathspec, 0);
-               refresh_cache(refresh_flags);
+               refresh_cache_or_die(refresh_flags);
                if (write_cache(fd, active_cache, active_nr) ||
                    close_lock_file(&index_lock))
                        die("unable to write new_index file");
@@ -294,7 +328,7 @@ static char *prepare_index(int argc, const char **argv, const char *prefix, int
         */
        if (!pathspec || !*pathspec) {
                fd = hold_locked_index(&index_lock, 1);
-               refresh_cache(refresh_flags);
+               refresh_cache_or_die(refresh_flags);
                if (write_cache(fd, active_cache, active_nr) ||
                    commit_locked_index(&index_lock))
                        die("unable to write new_index file");
@@ -582,7 +616,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
 
        /* This checks if committer ident is explicitly given */
        git_committer_info(0);
-       if (use_editor) {
+       if (use_editor && include_status) {
                char *author_ident;
                const char *committer_ident;
 
@@ -624,7 +658,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
                                author_ident);
                free(author_ident);
 
-               if (!user_ident_explicitly_given)
+               if (!user_ident_sufficiently_given())
                        fprintf(fp,
                                "%s"
                                "# Committer: %s\n",
@@ -1015,7 +1049,7 @@ int cmd_status(int argc, const char **argv, const char *prefix)
                s.pathspec = get_pathspec(prefix, argv);
 
        read_cache();
-       refresh_cache(REFRESH_QUIET|REFRESH_UNMERGED);
+       refresh_index(&the_index, REFRESH_QUIET|REFRESH_UNMERGED, s.pathspec, NULL, NULL);
        s.is_initial = get_sha1(s.reference, sha1) ? 1 : 0;
        s.in_merge = in_merge;
        wt_status_collect(&s);
@@ -1046,9 +1080,12 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
 {
        struct rev_info rev;
        struct commit *commit;
-       static const char *format = "format:%h] %s";
+       struct strbuf format = STRBUF_INIT;
        unsigned char junk_sha1[20];
        const char *head = resolve_ref("HEAD", junk_sha1, 0, NULL);
+       struct pretty_print_context pctx = {0};
+       struct strbuf author_ident = STRBUF_INIT;
+       struct strbuf committer_ident = STRBUF_INIT;
 
        commit = lookup_commit(sha1);
        if (!commit)
@@ -1056,6 +1093,25 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
        if (!commit || 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 (!user_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);
 
@@ -1066,7 +1122,7 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
 
        rev.verbose_header = 1;
        rev.show_root_diff = 1;
-       get_commit_format(format, &rev);
+       get_commit_format(format.buf, &rev);
        rev.always_show_header = 0;
        rev.diffopt.detect_rename = 1;
        rev.diffopt.rename_limit = 100;
@@ -1085,10 +1141,11 @@ static void print_summary(const char *prefix, const unsigned char *sha1)
                struct pretty_print_context ctx = {0};
                struct strbuf buf = STRBUF_INIT;
                ctx.date_mode = DATE_NORMAL;
-               format_commit_message(commit, format + 7, &buf, &ctx);
+               format_commit_message(commit, format.buf + 7, &buf, &ctx);
                printf("%s\n", buf.buf);
                strbuf_release(&buf);
        }
+       strbuf_release(&format);
 }
 
 static int git_commit_config(const char *k, const char *v, void *cb)
@@ -1097,10 +1154,48 @@ static int git_commit_config(const char *k, const char *v, void *cb)
 
        if (!strcmp(k, "commit.template"))
                return git_config_pathname(&template_file, k, v);
+       if (!strcmp(k, "commit.status")) {
+               include_status = git_config_bool(k, v);
+               return 0;
+       }
 
        return git_status_config(k, v, s);
 }
 
+static const char post_rewrite_hook[] = "hooks/post-rewrite";
+
+static int run_rewrite_hook(const unsigned char *oldsha1,
+                           const unsigned char *newsha1)
+{
+       /* oldsha1 SP newsha1 LF NUL */
+       static char buf[2*40 + 3];
+       struct child_process proc;
+       const char *argv[3];
+       int code;
+       size_t n;
+
+       if (access(git_path(post_rewrite_hook), X_OK) < 0)
+               return 0;
+
+       argv[0] = git_path(post_rewrite_hook);
+       argv[1] = "amend";
+       argv[2] = NULL;
+
+       memset(&proc, 0, sizeof(proc));
+       proc.argv = argv;
+       proc.in = -1;
+       proc.stdout_to_stderr = 1;
+
+       code = start_command(&proc);
+       if (code)
+               return code;
+       n = snprintf(buf, sizeof(buf), "%s %s\n",
+                    sha1_to_hex(oldsha1), sha1_to_hex(newsha1));
+       write_in_full(proc.in, buf, n);
+       close(proc.in);
+       return finish_command(&proc);
+}
+
 int cmd_commit(int argc, const char **argv, const char *prefix)
 {
        struct strbuf sb = STRBUF_INIT;
@@ -1242,8 +1337,17 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
                     "new_index file. Check that disk is not full or quota is\n"
                     "not exceeded, and then \"git reset HEAD\" to recover.");
 
-       rerere();
+       rerere(0);
        run_hook(get_index_file(), "post-commit", NULL);
+       if (amend && !no_post_rewrite) {
+               struct notes_rewrite_cfg *cfg;
+               cfg = init_copy_notes_for_rewrite("amend");
+               if (cfg) {
+                       copy_note_for_rewrite(cfg, head_sha1, commit_sha1);
+                       finish_copy_notes_for_rewrite(cfg);
+               }
+               run_rewrite_hook(head_sha1, commit_sha1);
+       }
        if (!quiet)
                print_summary(prefix, commit_sha1);