Skip to content

Commit 356ee46

Browse files
phillipwoodgitster
authored andcommitted
sequencer: try to commit without forking 'git commit'
If the commit message does not need to be edited then create the commit without forking 'git commit'. Taking the best time of ten runs with a warm cache this reduces the time taken to cherry-pick 10 commits by 27% (from 282ms to 204ms), and the time taken by 'git rebase --continue' to pick 10 commits by 45% (from 386ms to 212ms) on my computer running linux. Some of greater saving for rebase is because it no longer wastes time creating the commit summary just to throw it away. The code to create the commit is based on builtin/commit.c. It is simplified as it doesn't have to deal with merges and modified so that it does not die but returns an error to make sure the sequencer exits cleanly, as it would when forking 'git commit' Even when not forking 'git commit' the commit message is written to a file and CHERRY_PICK_HEAD is created unnecessarily. This could be eliminated in future. I hacked up a version that does not write these files and just passed an strbuf (with the wrong message for fixup and squash commands) to do_commit() but I couldn't measure any significant time difference when running cherry-pick or rebase. I think eliminating the writes properly for rebase would require a bit of effort as the code would need to be restructured. Signed-off-by: Phillip Wood <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent b36c590 commit 356ee46

File tree

1 file changed

+176
-2
lines changed

1 file changed

+176
-2
lines changed

sequencer.c

Lines changed: 176 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -592,6 +592,18 @@ static int read_env_script(struct argv_array *env)
592592
return 0;
593593
}
594594

595+
static char *get_author(const char *message)
596+
{
597+
size_t len;
598+
const char *a;
599+
600+
a = find_commit_header(message, "author", &len);
601+
if (a)
602+
return xmemdupz(a, len);
603+
604+
return NULL;
605+
}
606+
595607
static const char staged_changes_advice[] =
596608
N_("you have staged changes in your working tree\n"
597609
"If these changes are meant to be squashed into the previous commit, run:\n"
@@ -984,6 +996,160 @@ void print_commit_summary(const char *prefix, const struct object_id *oid,
984996
strbuf_release(&format);
985997
}
986998

999+
static int parse_head(struct commit **head)
1000+
{
1001+
struct commit *current_head;
1002+
struct object_id oid;
1003+
1004+
if (get_oid("HEAD", &oid)) {
1005+
current_head = NULL;
1006+
} else {
1007+
current_head = lookup_commit_reference(&oid);
1008+
if (!current_head)
1009+
return error(_("could not parse HEAD"));
1010+
if (oidcmp(&oid, &current_head->object.oid)) {
1011+
warning(_("HEAD %s is not a commit!"),
1012+
oid_to_hex(&oid));
1013+
}
1014+
if (parse_commit(current_head))
1015+
return error(_("could not parse HEAD commit"));
1016+
}
1017+
*head = current_head;
1018+
1019+
return 0;
1020+
}
1021+
1022+
/*
1023+
* Try to commit without forking 'git commit'. In some cases we need
1024+
* to run 'git commit' to display an error message
1025+
*
1026+
* Returns:
1027+
* -1 - error unable to commit
1028+
* 0 - success
1029+
* 1 - run 'git commit'
1030+
*/
1031+
static int try_to_commit(struct strbuf *msg, const char *author,
1032+
struct replay_opts *opts, unsigned int flags,
1033+
struct object_id *oid)
1034+
{
1035+
struct object_id tree;
1036+
struct commit *current_head;
1037+
struct commit_list *parents = NULL;
1038+
struct commit_extra_header *extra = NULL;
1039+
struct strbuf err = STRBUF_INIT;
1040+
struct strbuf amend_msg = STRBUF_INIT;
1041+
char *amend_author = NULL;
1042+
const char *gpg_sign;
1043+
enum commit_msg_cleanup_mode cleanup;
1044+
int res = 0;
1045+
1046+
if (parse_head(&current_head))
1047+
return -1;
1048+
1049+
if (flags & AMEND_MSG) {
1050+
const char *exclude_gpgsig[] = { "gpgsig", NULL };
1051+
const char *out_enc = get_commit_output_encoding();
1052+
const char *message = logmsg_reencode(current_head, NULL,
1053+
out_enc);
1054+
1055+
if (!msg) {
1056+
const char *orig_message = NULL;
1057+
1058+
find_commit_subject(message, &orig_message);
1059+
msg = &amend_msg;
1060+
strbuf_addstr(msg, orig_message);
1061+
}
1062+
author = amend_author = get_author(message);
1063+
unuse_commit_buffer(current_head, message);
1064+
if (!author) {
1065+
res = error(_("unable to parse commit author"));
1066+
goto out;
1067+
}
1068+
parents = copy_commit_list(current_head->parents);
1069+
extra = read_commit_extra_headers(current_head, exclude_gpgsig);
1070+
} else if (current_head) {
1071+
commit_list_insert(current_head, &parents);
1072+
}
1073+
1074+
cleanup = (flags & CLEANUP_MSG) ? COMMIT_MSG_CLEANUP_ALL :
1075+
default_msg_cleanup;
1076+
if (cleanup != COMMIT_MSG_CLEANUP_NONE)
1077+
strbuf_stripspace(msg, cleanup == COMMIT_MSG_CLEANUP_ALL);
1078+
if (!opts->allow_empty_message && message_is_empty(msg, cleanup)) {
1079+
res = 1; /* run 'git commit' to display error message */
1080+
goto out;
1081+
}
1082+
1083+
gpg_sign = opts->gpg_sign ? opts->gpg_sign : default_gpg_sign;
1084+
1085+
if (write_cache_as_tree(tree.hash, 0, NULL)) {
1086+
res = error(_("git write-tree failed to write a tree"));
1087+
goto out;
1088+
}
1089+
1090+
if (!(flags & ALLOW_EMPTY) && !oidcmp(current_head ?
1091+
&current_head->tree->object.oid :
1092+
&empty_tree_oid, &tree)) {
1093+
res = 1; /* run 'git commit' to display error message */
1094+
goto out;
1095+
}
1096+
1097+
if (commit_tree_extended(msg->buf, msg->len, tree.hash, parents,
1098+
oid->hash, author, gpg_sign, extra)) {
1099+
res = error(_("failed to write commit object"));
1100+
goto out;
1101+
}
1102+
1103+
if (update_head_with_reflog(current_head, oid,
1104+
getenv("GIT_REFLOG_ACTION"), msg, &err)) {
1105+
res = error("%s", err.buf);
1106+
goto out;
1107+
}
1108+
1109+
if (flags & AMEND_MSG)
1110+
commit_post_rewrite(current_head, oid);
1111+
1112+
out:
1113+
free_commit_extra_headers(extra);
1114+
strbuf_release(&err);
1115+
strbuf_release(&amend_msg);
1116+
free(amend_author);
1117+
1118+
return res;
1119+
}
1120+
1121+
static int do_commit(const char *msg_file, const char *author,
1122+
struct replay_opts *opts, unsigned int flags)
1123+
{
1124+
int res = 1;
1125+
1126+
if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) {
1127+
struct object_id oid;
1128+
struct strbuf sb = STRBUF_INIT;
1129+
1130+
if (msg_file && strbuf_read_file(&sb, msg_file, 2048) < 0)
1131+
return error_errno(_("unable to read commit message "
1132+
"from '%s'"),
1133+
msg_file);
1134+
1135+
res = try_to_commit(msg_file ? &sb : NULL, author, opts, flags,
1136+
&oid);
1137+
strbuf_release(&sb);
1138+
if (!res) {
1139+
unlink(git_path_cherry_pick_head());
1140+
unlink(git_path_merge_msg());
1141+
if (!is_rebase_i(opts))
1142+
print_commit_summary(NULL, &oid,
1143+
SUMMARY_SHOW_AUTHOR_DATE);
1144+
return res;
1145+
}
1146+
}
1147+
if (res == 1)
1148+
return run_git_commit(msg_file, opts, flags);
1149+
1150+
return res;
1151+
}
1152+
9871153
static int is_original_commit_empty(struct commit *commit)
9881154
{
9891155
const struct object_id *ptree_oid;
@@ -1235,6 +1401,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
12351401
struct object_id head;
12361402
struct commit *base, *next, *parent;
12371403
const char *base_label, *next_label;
1404+
char *author = NULL;
12381405
struct commit_message msg = { NULL, NULL, NULL, NULL };
12391406
struct strbuf msgbuf = STRBUF_INIT;
12401407
int res, unborn = 0, allow;
@@ -1351,6 +1518,8 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
13511518
strbuf_addstr(&msgbuf, oid_to_hex(&commit->object.oid));
13521519
strbuf_addstr(&msgbuf, ")\n");
13531520
}
1521+
if (!is_fixup(command))
1522+
author = get_author(msg.message);
13541523
}
13551524

13561525
if (command == TODO_REWORD)
@@ -1436,9 +1605,13 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
14361605
goto leave;
14371606
} else if (allow)
14381607
flags |= ALLOW_EMPTY;
1439-
if (!opts->no_commit)
1608+
if (!opts->no_commit) {
14401609
fast_forward_edit:
1441-
res = run_git_commit(msg_file, opts, flags);
1610+
if (author || command == TODO_REVERT || (flags & AMEND_MSG))
1611+
res = do_commit(msg_file, author, opts, flags);
1612+
else
1613+
res = error(_("unable to parse commit author"));
1614+
}
14421615

14431616
if (!res && final_fixup) {
14441617
unlink(rebase_path_fixup_msg());
@@ -1447,6 +1620,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
14471620

14481621
leave:
14491622
free_message(commit, &msg);
1623+
free(author);
14501624
update_abort_safety_file();
14511625

14521626
return res;

0 commit comments

Comments
 (0)