Merge branch 'pw/sequencer-in-process-commit'
authorJunio C Hamano <gitster@pobox.com>
Tue, 13 Feb 2018 21:39:15 +0000 (13:39 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 13 Feb 2018 21:39:15 +0000 (13:39 -0800)
The sequencer infrastructure is shared across "git cherry-pick",
"git rebase -i", etc., and has always spawned "git commit" when it
needs to create a commit.  It has been taught to do so internally,
when able, by reusing the codepath "git commit" itself uses, which
gives performance boost for a few tens of percents in some sample
scenarios.

* pw/sequencer-in-process-commit:
  sequencer: run 'prepare-commit-msg' hook
  t7505: add tests for cherry-pick and rebase -i/-p
  t7505: style fixes
  sequencer: assign only free()able strings to gpg_sign
  sequencer: improve config handling
  t3512/t3513: remove KNOWN_FAILURE_CHERRY_PICK_SEES_EMPTY_COMMIT=1
  sequencer: try to commit without forking 'git commit'
  sequencer: load commit related config
  sequencer: simplify adding Signed-off-by: trailer
  commit: move print_commit_summary() to libgit
  commit: move post-rewrite code to libgit
  Add a function to update HEAD after creating a commit
  commit: move empty message checks to libgit
  t3404: check intermediate squash messages

1  2 
builtin/commit.c
builtin/rebase--helper.c
sequencer.c
sequencer.h
t/t3404-rebase-interactive.sh
t/t3512-cherry-pick-submodule.sh

Simple merge
@@@ -43,8 -39,7 +43,8 @@@ int cmd_rebase__helper(int argc, const 
                OPT_END()
        };
  
-       git_config(git_default_config, NULL);
+       sequencer_init_config(&opts);
 +      git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
  
        opts.action = REPLAY_INTERACTIVE_REBASE;
        opts.allow_ff = 1;
diff --cc sequencer.c
@@@ -694,6 -747,461 +750,461 @@@ static int run_git_commit(const char *d
        return run_command(&cmd);
  }
  
 -      rev.diffopt.detect_rename = 1;
+ static int rest_is_empty(const struct strbuf *sb, int start)
+ {
+       int i, eol;
+       const char *nl;
+       /* Check if the rest is just whitespace and Signed-off-by's. */
+       for (i = start; i < sb->len; i++) {
+               nl = memchr(sb->buf + i, '\n', sb->len - i);
+               if (nl)
+                       eol = nl - sb->buf;
+               else
+                       eol = sb->len;
+               if (strlen(sign_off_header) <= eol - i &&
+                   starts_with(sb->buf + i, sign_off_header)) {
+                       i = eol;
+                       continue;
+               }
+               while (i < eol)
+                       if (!isspace(sb->buf[i++]))
+                               return 0;
+       }
+       return 1;
+ }
+ /*
+  * Find out if the message in the strbuf contains only whitespace and
+  * Signed-off-by lines.
+  */
+ int message_is_empty(const struct strbuf *sb,
+                    enum commit_msg_cleanup_mode cleanup_mode)
+ {
+       if (cleanup_mode == COMMIT_MSG_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
+  */
+ int template_untouched(const struct strbuf *sb, const char *template_file,
+                      enum commit_msg_cleanup_mode cleanup_mode)
+ {
+       struct strbuf tmpl = STRBUF_INIT;
+       const char *start;
+       if (cleanup_mode == COMMIT_MSG_CLEANUP_NONE && sb->len)
+               return 0;
+       if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
+               return 0;
+       strbuf_stripspace(&tmpl, cleanup_mode == COMMIT_MSG_CLEANUP_ALL);
+       if (!skip_prefix(sb->buf, tmpl.buf, &start))
+               start = sb->buf;
+       strbuf_release(&tmpl);
+       return rest_is_empty(sb, start - sb->buf);
+ }
+ int update_head_with_reflog(const struct commit *old_head,
+                           const struct object_id *new_head,
+                           const char *action, const struct strbuf *msg,
+                           struct strbuf *err)
+ {
+       struct ref_transaction *transaction;
+       struct strbuf sb = STRBUF_INIT;
+       const char *nl;
+       int ret = 0;
+       if (action) {
+               strbuf_addstr(&sb, action);
+               strbuf_addstr(&sb, ": ");
+       }
+       nl = strchr(msg->buf, '\n');
+       if (nl) {
+               strbuf_add(&sb, msg->buf, nl + 1 - msg->buf);
+       } else {
+               strbuf_addbuf(&sb, msg);
+               strbuf_addch(&sb, '\n');
+       }
+       transaction = ref_transaction_begin(err);
+       if (!transaction ||
+           ref_transaction_update(transaction, "HEAD", new_head,
+                                  old_head ? &old_head->object.oid : &null_oid,
+                                  0, sb.buf, err) ||
+           ref_transaction_commit(transaction, err)) {
+               ret = -1;
+       }
+       ref_transaction_free(transaction);
+       strbuf_release(&sb);
+       return ret;
+ }
+ static int run_rewrite_hook(const struct object_id *oldoid,
+                           const struct object_id *newoid)
+ {
+       struct child_process proc = CHILD_PROCESS_INIT;
+       const char *argv[3];
+       int code;
+       struct strbuf sb = STRBUF_INIT;
+       argv[0] = find_hook("post-rewrite");
+       if (!argv[0])
+               return 0;
+       argv[1] = "amend";
+       argv[2] = NULL;
+       proc.argv = argv;
+       proc.in = -1;
+       proc.stdout_to_stderr = 1;
+       code = start_command(&proc);
+       if (code)
+               return code;
+       strbuf_addf(&sb, "%s %s\n", oid_to_hex(oldoid), oid_to_hex(newoid));
+       sigchain_push(SIGPIPE, SIG_IGN);
+       write_in_full(proc.in, sb.buf, sb.len);
+       close(proc.in);
+       strbuf_release(&sb);
+       sigchain_pop(SIGPIPE);
+       return finish_command(&proc);
+ }
+ void commit_post_rewrite(const struct commit *old_head,
+                        const struct object_id *new_head)
+ {
+       struct notes_rewrite_cfg *cfg;
+       cfg = init_copy_notes_for_rewrite("amend");
+       if (cfg) {
+               /* we are amending, so old_head is not NULL */
+               copy_note_for_rewrite(cfg, &old_head->object.oid, new_head);
+               finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'");
+       }
+       run_rewrite_hook(&old_head->object.oid, new_head);
+ }
+ static int run_prepare_commit_msg_hook(struct strbuf *msg, const char *commit)
+ {
+       struct argv_array hook_env = ARGV_ARRAY_INIT;
+       int ret;
+       const char *name;
+       name = git_path_commit_editmsg();
+       if (write_message(msg->buf, msg->len, name, 0))
+               return -1;
+       argv_array_pushf(&hook_env, "GIT_INDEX_FILE=%s", get_index_file());
+       argv_array_push(&hook_env, "GIT_EDITOR=:");
+       if (commit)
+               ret = run_hook_le(hook_env.argv, "prepare-commit-msg", name,
+                                 "commit", commit, NULL);
+       else
+               ret = run_hook_le(hook_env.argv, "prepare-commit-msg", name,
+                                 "message", NULL);
+       if (ret)
+               ret = error(_("'prepare-commit-msg' hook failed"));
+       argv_array_clear(&hook_env);
+       return ret;
+ }
+ 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 *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);
+ }
+ void print_commit_summary(const char *prefix, const struct object_id *oid,
+                         unsigned int flags)
+ {
+       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 (flags & SUMMARY_SHOW_AUTHOR_DATE) {
+               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 = DIFF_DETECT_RENAME;
+       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, (flags & SUMMARY_INITIAL_COMMIT) ?
+                                               _(" (root-commit)") : "");
+       if (!log_tree_commit(&rev, commit)) {
+               rev.always_show_header = 1;
+               rev.use_terminator = 1;
+               log_tree_commit(&rev, commit);
+       }
+       strbuf_release(&format);
+ }
+ static int parse_head(struct commit **head)
+ {
+       struct commit *current_head;
+       struct object_id oid;
+       if (get_oid("HEAD", &oid)) {
+               current_head = NULL;
+       } else {
+               current_head = lookup_commit_reference(&oid);
+               if (!current_head)
+                       return error(_("could not parse HEAD"));
+               if (oidcmp(&oid, &current_head->object.oid)) {
+                       warning(_("HEAD %s is not a commit!"),
+                               oid_to_hex(&oid));
+               }
+               if (parse_commit(current_head))
+                       return error(_("could not parse HEAD commit"));
+       }
+       *head = current_head;
+       return 0;
+ }
+ /*
+  * Try to commit without forking 'git commit'. In some cases we need
+  * to run 'git commit' to display an error message
+  *
+  * Returns:
+  *  -1 - error unable to commit
+  *   0 - success
+  *   1 - run 'git commit'
+  */
+ static int try_to_commit(struct strbuf *msg, const char *author,
+                        struct replay_opts *opts, unsigned int flags,
+                        struct object_id *oid)
+ {
+       struct object_id tree;
+       struct commit *current_head;
+       struct commit_list *parents = NULL;
+       struct commit_extra_header *extra = NULL;
+       struct strbuf err = STRBUF_INIT;
+       struct strbuf commit_msg = STRBUF_INIT;
+       char *amend_author = NULL;
+       const char *hook_commit = NULL;
+       enum commit_msg_cleanup_mode cleanup;
+       int res = 0;
+       if (parse_head(&current_head))
+               return -1;
+       if (flags & AMEND_MSG) {
+               const char *exclude_gpgsig[] = { "gpgsig", NULL };
+               const char *out_enc = get_commit_output_encoding();
+               const char *message = logmsg_reencode(current_head, NULL,
+                                                     out_enc);
+               if (!msg) {
+                       const char *orig_message = NULL;
+                       find_commit_subject(message, &orig_message);
+                       msg = &commit_msg;
+                       strbuf_addstr(msg, orig_message);
+                       hook_commit = "HEAD";
+               }
+               author = amend_author = get_author(message);
+               unuse_commit_buffer(current_head, message);
+               if (!author) {
+                       res = error(_("unable to parse commit author"));
+                       goto out;
+               }
+               parents = copy_commit_list(current_head->parents);
+               extra = read_commit_extra_headers(current_head, exclude_gpgsig);
+       } else if (current_head) {
+               commit_list_insert(current_head, &parents);
+       }
+       if (write_cache_as_tree(tree.hash, 0, NULL)) {
+               res = error(_("git write-tree failed to write a tree"));
+               goto out;
+       }
+       if (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ?
+                                             &current_head->tree->object.oid :
+                                             &empty_tree_oid, &tree)) {
+               res = 1; /* run 'git commit' to display error message */
+               goto out;
+       }
+       if (find_hook("prepare-commit-msg")) {
+               res = run_prepare_commit_msg_hook(msg, hook_commit);
+               if (res)
+                       goto out;
+               if (strbuf_read_file(&commit_msg, git_path_commit_editmsg(),
+                                    2048) < 0) {
+                       res = error_errno(_("unable to read commit message "
+                                             "from '%s'"),
+                                           git_path_commit_editmsg());
+                       goto out;
+               }
+               msg = &commit_msg;
+       }
+       cleanup = (flags & CLEANUP_MSG) ? COMMIT_MSG_CLEANUP_ALL :
+                                         opts->default_msg_cleanup;
+       if (cleanup != COMMIT_MSG_CLEANUP_NONE)
+               strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL);
+       if (!opts->allow_empty_message && message_is_empty(msg, cleanup)) {
+               res = 1; /* run 'git commit' to display error message */
+               goto out;
+       }
+       if (commit_tree_extended(msg->buf, msg->len, tree.hash, parents,
+                                oid->hash, author, opts->gpg_sign, extra)) {
+               res = error(_("failed to write commit object"));
+               goto out;
+       }
+       if (update_head_with_reflog(current_head, oid,
+                                   getenv("GIT_REFLOG_ACTION"), msg, &err)) {
+               res = error("%s", err.buf);
+               goto out;
+       }
+       if (flags & AMEND_MSG)
+               commit_post_rewrite(current_head, oid);
+ out:
+       free_commit_extra_headers(extra);
+       strbuf_release(&err);
+       strbuf_release(&commit_msg);
+       free(amend_author);
+       return res;
+ }
+ static int do_commit(const char *msg_file, const char *author,
+                    struct replay_opts *opts, unsigned int flags)
+ {
+       int res = 1;
+       if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) {
+               struct object_id oid;
+               struct strbuf sb = STRBUF_INIT;
+               if (msg_file && strbuf_read_file(&sb, msg_file, 2048) < 0)
+                       return error_errno(_("unable to read commit message "
+                                            "from '%s'"),
+                                          msg_file);
+               res = try_to_commit(msg_file ? &sb : NULL, author, opts, flags,
+                                   &oid);
+               strbuf_release(&sb);
+               if (!res) {
+                       unlink(git_path_cherry_pick_head());
+                       unlink(git_path_merge_msg());
+                       if (!is_rebase_i(opts))
+                               print_commit_summary(NULL, &oid,
+                                               SUMMARY_SHOW_AUTHOR_DATE);
+                       return res;
+               }
+       }
+       if (res == 1)
+               return run_git_commit(msg_file, opts, flags);
+       return res;
+ }
  static int is_original_commit_empty(struct commit *commit)
  {
        const struct object_id *ptree_oid;
diff --cc sequencer.h
Simple merge
Simple merge
Simple merge