Merge branch 'pw/sequencer-in-process-commit'
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
This commit is contained in:
commit
0f57f731ea
291
builtin/commit.c
291
builtin/commit.c
@ -31,9 +31,7 @@
|
||||
#include "gpg-interface.h"
|
||||
#include "column.h"
|
||||
#include "sequencer.h"
|
||||
#include "notes-utils.h"
|
||||
#include "mailmap.h"
|
||||
#include "sigchain.h"
|
||||
|
||||
static const char * const builtin_commit_usage[] = {
|
||||
N_("git commit [<options>] [--] <pathspec>..."),
|
||||
@ -45,31 +43,6 @@ static const char * const builtin_status_usage[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
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 empty_amend_advice[] =
|
||||
N_("You asked to amend the most recent commit, but doing so would make\n"
|
||||
"it empty. You can repeat your command with --allow-empty, or you can\n"
|
||||
@ -93,8 +66,6 @@ N_("If you wish to skip this commit, use:\n"
|
||||
"Then \"git cherry-pick --continue\" will resume cherry-picking\n"
|
||||
"the remaining commits.\n");
|
||||
|
||||
static GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG")
|
||||
|
||||
static const char *use_message_buffer;
|
||||
static struct lock_file index_lock; /* real index */
|
||||
static struct lock_file false_lock; /* used only for partial commits */
|
||||
@ -128,12 +99,7 @@ static char *sign_commit;
|
||||
* if editor is used, and only the whitespaces if the message
|
||||
* is specified explicitly.
|
||||
*/
|
||||
static enum {
|
||||
CLEANUP_SPACE,
|
||||
CLEANUP_NONE,
|
||||
CLEANUP_SCISSORS,
|
||||
CLEANUP_ALL
|
||||
} cleanup_mode;
|
||||
static enum commit_msg_cleanup_mode cleanup_mode;
|
||||
static const char *cleanup_arg;
|
||||
|
||||
static enum commit_whence whence;
|
||||
@ -673,7 +639,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
const char *hook_arg1 = NULL;
|
||||
const char *hook_arg2 = NULL;
|
||||
int clean_message_contents = (cleanup_mode != CLEANUP_NONE);
|
||||
int clean_message_contents = (cleanup_mode != COMMIT_MSG_CLEANUP_NONE);
|
||||
int old_display_comment_prefix;
|
||||
|
||||
/* This checks and barfs if author is badly specified */
|
||||
@ -814,7 +780,7 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
|
||||
struct ident_split ci, ai;
|
||||
|
||||
if (whence != FROM_COMMIT) {
|
||||
if (cleanup_mode == CLEANUP_SCISSORS)
|
||||
if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
|
||||
wt_status_add_cut_line(s->fp);
|
||||
status_printf_ln(s, GIT_COLOR_NORMAL,
|
||||
whence == FROM_MERGE
|
||||
@ -834,14 +800,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
|
||||
}
|
||||
|
||||
fprintf(s->fp, "\n");
|
||||
if (cleanup_mode == CLEANUP_ALL)
|
||||
if (cleanup_mode == COMMIT_MSG_CLEANUP_ALL)
|
||||
status_printf(s, GIT_COLOR_NORMAL,
|
||||
_("Please enter the commit message for your changes."
|
||||
" Lines starting\nwith '%c' will be ignored, and an empty"
|
||||
" message aborts the commit.\n"), comment_line_char);
|
||||
else if (cleanup_mode == CLEANUP_SCISSORS && whence == FROM_COMMIT)
|
||||
else if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS &&
|
||||
whence == FROM_COMMIT)
|
||||
wt_status_add_cut_line(s->fp);
|
||||
else /* CLEANUP_SPACE, that is. */
|
||||
else /* COMMIT_MSG_CLEANUP_SPACE, that is. */
|
||||
status_printf(s, GIT_COLOR_NORMAL,
|
||||
_("Please enter the commit message for your changes."
|
||||
" Lines starting\n"
|
||||
@ -986,65 +953,6 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int rest_is_empty(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.
|
||||
*/
|
||||
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;
|
||||
const char *start;
|
||||
|
||||
if (cleanup_mode == CLEANUP_NONE && sb->len)
|
||||
return 0;
|
||||
|
||||
if (!template_file || strbuf_read_file(&tmpl, template_file, 0) <= 0)
|
||||
return 0;
|
||||
|
||||
strbuf_stripspace(&tmpl, cleanup_mode == CLEANUP_ALL);
|
||||
if (!skip_prefix(sb->buf, tmpl.buf, &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;
|
||||
@ -1229,15 +1137,17 @@ static int parse_and_validate_options(int argc, const char *argv[],
|
||||
if (argc == 0 && (also || (only && !amend && !allow_empty)))
|
||||
die(_("No paths with --include/--only does not make sense."));
|
||||
if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
|
||||
cleanup_mode = use_editor ? CLEANUP_ALL : CLEANUP_SPACE;
|
||||
cleanup_mode = use_editor ? COMMIT_MSG_CLEANUP_ALL :
|
||||
COMMIT_MSG_CLEANUP_SPACE;
|
||||
else if (!strcmp(cleanup_arg, "verbatim"))
|
||||
cleanup_mode = CLEANUP_NONE;
|
||||
cleanup_mode = COMMIT_MSG_CLEANUP_NONE;
|
||||
else if (!strcmp(cleanup_arg, "whitespace"))
|
||||
cleanup_mode = CLEANUP_SPACE;
|
||||
cleanup_mode = COMMIT_MSG_CLEANUP_SPACE;
|
||||
else if (!strcmp(cleanup_arg, "strip"))
|
||||
cleanup_mode = CLEANUP_ALL;
|
||||
cleanup_mode = COMMIT_MSG_CLEANUP_ALL;
|
||||
else if (!strcmp(cleanup_arg, "scissors"))
|
||||
cleanup_mode = use_editor ? CLEANUP_SCISSORS : CLEANUP_SPACE;
|
||||
cleanup_mode = use_editor ? COMMIT_MSG_CLEANUP_SCISSORS :
|
||||
COMMIT_MSG_CLEANUP_SPACE;
|
||||
else
|
||||
die(_("Invalid cleanup mode %s"), cleanup_arg);
|
||||
|
||||
@ -1439,98 +1349,6 @@ int cmd_status(int argc, const char **argv, const char *prefix)
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
|
||||
static void print_summary(const char *prefix, const struct object_id *oid,
|
||||
int initial_commit)
|
||||
{
|
||||
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 (author_date_is_interesting()) {
|
||||
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, 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 git_commit_config(const char *k, const char *v, void *cb)
|
||||
{
|
||||
struct wt_status *s = cb;
|
||||
@ -1560,37 +1378,6 @@ static int git_commit_config(const char *k, const char *v, void *cb)
|
||||
return git_status_config(k, v, s);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...)
|
||||
{
|
||||
struct argv_array hook_env = ARGV_ARRAY_INIT;
|
||||
@ -1673,13 +1460,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
struct strbuf author_ident = STRBUF_INIT;
|
||||
const char *index_file, *reflog_msg;
|
||||
char *nl;
|
||||
struct object_id oid;
|
||||
struct commit_list *parents = NULL;
|
||||
struct stat statbuf;
|
||||
struct commit *current_head = NULL;
|
||||
struct commit_extra_header *extra = NULL;
|
||||
struct ref_transaction *transaction;
|
||||
struct strbuf err = STRBUF_INIT;
|
||||
|
||||
if (argc == 2 && !strcmp(argv[1], "-h"))
|
||||
@ -1770,17 +1555,17 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
|
||||
}
|
||||
|
||||
if (verbose || /* Truncate the message just before the diff, if any. */
|
||||
cleanup_mode == CLEANUP_SCISSORS)
|
||||
cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
|
||||
strbuf_setlen(&sb, wt_status_locate_end(sb.buf, sb.len));
|
||||
if (cleanup_mode != CLEANUP_NONE)
|
||||
strbuf_stripspace(&sb, cleanup_mode == CLEANUP_ALL);
|
||||
if (cleanup_mode != COMMIT_MSG_CLEANUP_NONE)
|
||||
strbuf_stripspace(&sb, cleanup_mode == COMMIT_MSG_CLEANUP_ALL);
|
||||
|
||||
if (message_is_empty(&sb) && !allow_empty_message) {
|
||||
if (message_is_empty(&sb, cleanup_mode) && !allow_empty_message) {
|
||||
rollback_index_files();
|
||||
fprintf(stderr, _("Aborting commit due to empty commit message.\n"));
|
||||
exit(1);
|
||||
}
|
||||
if (template_untouched(&sb) && !allow_empty_message) {
|
||||
if (template_untouched(&sb, template_file, cleanup_mode) && !allow_empty_message) {
|
||||
rollback_index_files();
|
||||
fprintf(stderr, _("Aborting commit; you did not edit the message.\n"));
|
||||
exit(1);
|
||||
@ -1802,25 +1587,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
|
||||
strbuf_release(&author_ident);
|
||||
free_commit_extra_headers(extra);
|
||||
|
||||
nl = strchr(sb.buf, '\n');
|
||||
if (nl)
|
||||
strbuf_setlen(&sb, nl + 1 - sb.buf);
|
||||
else
|
||||
strbuf_addch(&sb, '\n');
|
||||
strbuf_insert(&sb, 0, reflog_msg, strlen(reflog_msg));
|
||||
strbuf_insert(&sb, strlen(reflog_msg), ": ", 2);
|
||||
|
||||
transaction = ref_transaction_begin(&err);
|
||||
if (!transaction ||
|
||||
ref_transaction_update(transaction, "HEAD", &oid,
|
||||
current_head
|
||||
? ¤t_head->object.oid : &null_oid,
|
||||
0, sb.buf, &err) ||
|
||||
ref_transaction_commit(transaction, &err)) {
|
||||
if (update_head_with_reflog(current_head, &oid, reflog_msg, &sb,
|
||||
&err)) {
|
||||
rollback_index_files();
|
||||
die("%s", err.buf);
|
||||
}
|
||||
ref_transaction_free(transaction);
|
||||
|
||||
unlink(git_path_cherry_pick_head());
|
||||
unlink(git_path_revert_head());
|
||||
@ -1837,17 +1608,17 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
|
||||
rerere(0);
|
||||
run_commit_hook(use_editor, 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) {
|
||||
/* we are amending, so current_head is not NULL */
|
||||
copy_note_for_rewrite(cfg, ¤t_head->object.oid, &oid);
|
||||
finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'");
|
||||
}
|
||||
run_rewrite_hook(¤t_head->object.oid, &oid);
|
||||
commit_post_rewrite(current_head, &oid);
|
||||
}
|
||||
if (!quiet) {
|
||||
unsigned int flags = 0;
|
||||
|
||||
if (!current_head)
|
||||
flags |= SUMMARY_INITIAL_COMMIT;
|
||||
if (author_date_is_interesting())
|
||||
flags |= SUMMARY_SHOW_AUTHOR_DATE;
|
||||
print_commit_summary(prefix, &oid, flags);
|
||||
}
|
||||
if (!quiet)
|
||||
print_summary(prefix, &oid, !current_head);
|
||||
|
||||
UNLEAK(err);
|
||||
UNLEAK(sb);
|
||||
|
@ -43,7 +43,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
|
||||
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;
|
||||
|
@ -208,7 +208,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
|
||||
if (isatty(0))
|
||||
opts.edit = 1;
|
||||
opts.action = REPLAY_REVERT;
|
||||
git_config(git_default_config, NULL);
|
||||
sequencer_init_config(&opts);
|
||||
res = run_sequencer(argc, argv, &opts);
|
||||
if (res < 0)
|
||||
die(_("revert failed"));
|
||||
@ -221,7 +221,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
|
||||
int res;
|
||||
|
||||
opts.action = REPLAY_PICK;
|
||||
git_config(git_default_config, NULL);
|
||||
sequencer_init_config(&opts);
|
||||
res = run_sequencer(argc, argv, &opts);
|
||||
if (res < 0)
|
||||
die(_("cherry-pick failed"));
|
||||
|
538
sequencer.c
538
sequencer.c
@ -1,10 +1,10 @@
|
||||
#include "cache.h"
|
||||
#include "config.h"
|
||||
#include "lockfile.h"
|
||||
#include "sequencer.h"
|
||||
#include "dir.h"
|
||||
#include "object.h"
|
||||
#include "commit.h"
|
||||
#include "sequencer.h"
|
||||
#include "tag.h"
|
||||
#include "run-command.h"
|
||||
#include "exec_cmd.h"
|
||||
@ -21,12 +21,16 @@
|
||||
#include "log-tree.h"
|
||||
#include "wt-status.h"
|
||||
#include "hashmap.h"
|
||||
#include "notes-utils.h"
|
||||
#include "sigchain.h"
|
||||
|
||||
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
|
||||
|
||||
const char sign_off_header[] = "Signed-off-by: ";
|
||||
static const char cherry_picked_prefix[] = "(cherry picked from commit ";
|
||||
|
||||
GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG")
|
||||
|
||||
GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
|
||||
|
||||
static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
|
||||
@ -130,6 +134,51 @@ static GIT_PATH_FUNC(rebase_path_strategy, "rebase-merge/strategy")
|
||||
static GIT_PATH_FUNC(rebase_path_strategy_opts, "rebase-merge/strategy_opts")
|
||||
static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
|
||||
|
||||
static int git_sequencer_config(const char *k, const char *v, void *cb)
|
||||
{
|
||||
struct replay_opts *opts = cb;
|
||||
int status;
|
||||
|
||||
if (!strcmp(k, "commit.cleanup")) {
|
||||
const char *s;
|
||||
|
||||
status = git_config_string(&s, k, v);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
if (!strcmp(s, "verbatim"))
|
||||
opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_NONE;
|
||||
else if (!strcmp(s, "whitespace"))
|
||||
opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SPACE;
|
||||
else if (!strcmp(s, "strip"))
|
||||
opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_ALL;
|
||||
else if (!strcmp(s, "scissors"))
|
||||
opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_SPACE;
|
||||
else
|
||||
warning(_("invalid commit message cleanup mode '%s'"),
|
||||
s);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
if (!strcmp(k, "commit.gpgsign")) {
|
||||
opts->gpg_sign = git_config_bool(k, v) ? xstrdup("") : NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
status = git_gpg_config(k, v, NULL);
|
||||
if (status)
|
||||
return status;
|
||||
|
||||
return git_diff_basic_config(k, v, NULL);
|
||||
}
|
||||
|
||||
void sequencer_init_config(struct replay_opts *opts)
|
||||
{
|
||||
opts->default_msg_cleanup = COMMIT_MSG_CLEANUP_NONE;
|
||||
git_config(git_sequencer_config, opts);
|
||||
}
|
||||
|
||||
static inline int is_rebase_i(const struct replay_opts *opts)
|
||||
{
|
||||
return opts->action == REPLAY_INTERACTIVE_REBASE;
|
||||
@ -478,9 +527,6 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
|
||||
_(action_name(opts)));
|
||||
rollback_lock_file(&index_lock);
|
||||
|
||||
if (opts->signoff)
|
||||
append_signoff(msgbuf, 0, 0);
|
||||
|
||||
if (!clean)
|
||||
append_conflicts_hint(msgbuf);
|
||||
|
||||
@ -596,6 +642,18 @@ static int read_env_script(struct argv_array *env)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *get_author(const char *message)
|
||||
{
|
||||
size_t len;
|
||||
const char *a;
|
||||
|
||||
a = find_commit_header(message, "author", &len);
|
||||
if (a)
|
||||
return xmemdupz(a, len);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char staged_changes_advice[] =
|
||||
N_("you have staged changes in your working tree\n"
|
||||
"If these changes are meant to be squashed into the previous commit, run:\n"
|
||||
@ -658,8 +716,6 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
|
||||
argv_array_push(&cmd.args, "--amend");
|
||||
if (opts->gpg_sign)
|
||||
argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
|
||||
if (opts->signoff)
|
||||
argv_array_push(&cmd.args, "-s");
|
||||
if (defmsg)
|
||||
argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
|
||||
if ((flags & CLEANUP_MSG))
|
||||
@ -694,6 +750,461 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
|
||||
return run_command(&cmd);
|
||||
}
|
||||
|
||||
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, ¤t_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(¤t_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 ?
|
||||
¤t_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;
|
||||
@ -952,6 +1463,7 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
|
||||
struct object_id head;
|
||||
struct commit *base, *next, *parent;
|
||||
const char *base_label, *next_label;
|
||||
char *author = NULL;
|
||||
struct commit_message msg = { NULL, NULL, NULL, NULL };
|
||||
struct strbuf msgbuf = STRBUF_INIT;
|
||||
int res, unborn = 0, allow;
|
||||
@ -1066,6 +1578,8 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
|
||||
strbuf_addstr(&msgbuf, oid_to_hex(&commit->object.oid));
|
||||
strbuf_addstr(&msgbuf, ")\n");
|
||||
}
|
||||
if (!is_fixup(command))
|
||||
author = get_author(msg.message);
|
||||
}
|
||||
|
||||
if (command == TODO_REWORD)
|
||||
@ -1091,6 +1605,9 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
|
||||
}
|
||||
}
|
||||
|
||||
if (opts->signoff)
|
||||
append_signoff(&msgbuf, 0, 0);
|
||||
|
||||
if (is_rebase_i(opts) && write_author_script(msg.message) < 0)
|
||||
res = -1;
|
||||
else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) {
|
||||
@ -1148,9 +1665,13 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
|
||||
goto leave;
|
||||
} else if (allow)
|
||||
flags |= ALLOW_EMPTY;
|
||||
if (!opts->no_commit)
|
||||
if (!opts->no_commit) {
|
||||
fast_forward_edit:
|
||||
res = run_git_commit(msg_file, opts, flags);
|
||||
if (author || command == TODO_REVERT || (flags & AMEND_MSG))
|
||||
res = do_commit(msg_file, author, opts, flags);
|
||||
else
|
||||
res = error(_("unable to parse commit author"));
|
||||
}
|
||||
|
||||
if (!res && final_fixup) {
|
||||
unlink(rebase_path_fixup_msg());
|
||||
@ -1159,6 +1680,7 @@ fast_forward_edit:
|
||||
|
||||
leave:
|
||||
free_message(commit, &msg);
|
||||
free(author);
|
||||
update_abort_safety_file();
|
||||
|
||||
return res;
|
||||
|
25
sequencer.h
25
sequencer.h
@ -1,6 +1,7 @@
|
||||
#ifndef SEQUENCER_H
|
||||
#define SEQUENCER_H
|
||||
|
||||
const char *git_path_commit_editmsg(void);
|
||||
const char *git_path_seq_dir(void);
|
||||
|
||||
#define APPEND_SIGNOFF_DEDUP (1u << 0)
|
||||
@ -11,6 +12,13 @@ enum replay_action {
|
||||
REPLAY_INTERACTIVE_REBASE
|
||||
};
|
||||
|
||||
enum commit_msg_cleanup_mode {
|
||||
COMMIT_MSG_CLEANUP_SPACE,
|
||||
COMMIT_MSG_CLEANUP_NONE,
|
||||
COMMIT_MSG_CLEANUP_SCISSORS,
|
||||
COMMIT_MSG_CLEANUP_ALL
|
||||
};
|
||||
|
||||
struct replay_opts {
|
||||
enum replay_action action;
|
||||
|
||||
@ -29,6 +37,7 @@ struct replay_opts {
|
||||
int mainline;
|
||||
|
||||
char *gpg_sign;
|
||||
enum commit_msg_cleanup_mode default_msg_cleanup;
|
||||
|
||||
/* Merge strategy */
|
||||
char *strategy;
|
||||
@ -40,6 +49,8 @@ struct replay_opts {
|
||||
};
|
||||
#define REPLAY_OPTS_INIT { -1 }
|
||||
|
||||
/* Call this to setup defaults before parsing command line options */
|
||||
void sequencer_init_config(struct replay_opts *opts);
|
||||
int sequencer_pick_revisions(struct replay_opts *opts);
|
||||
int sequencer_continue(struct replay_opts *opts);
|
||||
int sequencer_rollback(struct replay_opts *opts);
|
||||
@ -61,5 +72,19 @@ extern const char sign_off_header[];
|
||||
|
||||
void append_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag);
|
||||
void append_conflicts_hint(struct strbuf *msgbuf);
|
||||
int message_is_empty(const struct strbuf *sb,
|
||||
enum commit_msg_cleanup_mode cleanup_mode);
|
||||
int template_untouched(const struct strbuf *sb, const char *template_file,
|
||||
enum commit_msg_cleanup_mode cleanup_mode);
|
||||
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);
|
||||
void commit_post_rewrite(const struct commit *current_head,
|
||||
const struct object_id *new_head);
|
||||
|
||||
#define SUMMARY_INITIAL_COMMIT (1 << 0)
|
||||
#define SUMMARY_SHOW_AUTHOR_DATE (1 << 1)
|
||||
void print_commit_summary(const char *prefix, const struct object_id *oid,
|
||||
unsigned int flags);
|
||||
#endif
|
||||
|
@ -453,6 +453,10 @@ test_expect_success C_LOCALE_OUTPUT 'squash and fixup generate correct log messa
|
||||
git rebase -i $base &&
|
||||
git cat-file commit HEAD | sed -e 1,/^\$/d > actual-squash-fixup &&
|
||||
test_cmp expect-squash-fixup actual-squash-fixup &&
|
||||
git cat-file commit HEAD@{2} |
|
||||
grep "^# This is a combination of 3 commits\." &&
|
||||
git cat-file commit HEAD@{3} |
|
||||
grep "^# This is a combination of 2 commits\." &&
|
||||
git checkout to-be-rebased &&
|
||||
git branch -D squash-fixup
|
||||
'
|
||||
@ -1336,6 +1340,16 @@ test_expect_success 'editor saves as CR/LF' '
|
||||
|
||||
SQ="'"
|
||||
test_expect_success 'rebase -i --gpg-sign=<key-id>' '
|
||||
test_when_finished "test_might_fail git rebase --abort" &&
|
||||
set_fake_editor &&
|
||||
FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" HEAD^ \
|
||||
>out 2>err &&
|
||||
test_i18ngrep "$SQ-S\"S I Gner\"$SQ" err
|
||||
'
|
||||
|
||||
test_expect_success 'rebase -i --gpg-sign=<key-id> overrides commit.gpgSign' '
|
||||
test_when_finished "test_might_fail git rebase --abort" &&
|
||||
test_config commit.gpgsign true &&
|
||||
set_fake_editor &&
|
||||
FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" HEAD^ \
|
||||
>out 2>err &&
|
||||
|
@ -5,7 +5,6 @@ test_description='cherry-pick can handle submodules'
|
||||
. ./test-lib.sh
|
||||
. "$TEST_DIRECTORY"/lib-submodule-update.sh
|
||||
|
||||
KNOWN_FAILURE_CHERRY_PICK_SEES_EMPTY_COMMIT=1
|
||||
KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
|
||||
KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
|
||||
test_submodule_switch "git cherry-pick"
|
||||
|
@ -25,7 +25,6 @@ git_revert () {
|
||||
git revert HEAD
|
||||
}
|
||||
|
||||
KNOWN_FAILURE_CHERRY_PICK_SEES_EMPTY_COMMIT=1
|
||||
KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
|
||||
test_submodule_switch "git_revert"
|
||||
|
||||
|
@ -4,6 +4,38 @@ test_description='prepare-commit-msg hook'
|
||||
|
||||
. ./test-lib.sh
|
||||
|
||||
test_expect_success 'set up commits for rebasing' '
|
||||
test_commit root &&
|
||||
test_commit a a a &&
|
||||
test_commit b b b &&
|
||||
git checkout -b rebase-me root &&
|
||||
test_commit rebase-a a aa &&
|
||||
test_commit rebase-b b bb &&
|
||||
for i in $(test_seq 1 13)
|
||||
do
|
||||
test_commit rebase-$i c $i
|
||||
done &&
|
||||
git checkout master &&
|
||||
|
||||
cat >rebase-todo <<-EOF
|
||||
pick $(git rev-parse rebase-a)
|
||||
pick $(git rev-parse rebase-b)
|
||||
fixup $(git rev-parse rebase-1)
|
||||
fixup $(git rev-parse rebase-2)
|
||||
pick $(git rev-parse rebase-3)
|
||||
fixup $(git rev-parse rebase-4)
|
||||
squash $(git rev-parse rebase-5)
|
||||
reword $(git rev-parse rebase-6)
|
||||
squash $(git rev-parse rebase-7)
|
||||
fixup $(git rev-parse rebase-8)
|
||||
fixup $(git rev-parse rebase-9)
|
||||
edit $(git rev-parse rebase-10)
|
||||
squash $(git rev-parse rebase-11)
|
||||
squash $(git rev-parse rebase-12)
|
||||
edit $(git rev-parse rebase-13)
|
||||
EOF
|
||||
'
|
||||
|
||||
test_expect_success 'with no hook' '
|
||||
|
||||
echo "foo" > file &&
|
||||
@ -31,17 +63,41 @@ mkdir -p "$HOOKDIR"
|
||||
echo "#!$SHELL_PATH" > "$HOOK"
|
||||
cat >> "$HOOK" <<'EOF'
|
||||
|
||||
if test "$2" = commit; then
|
||||
source=$(git rev-parse "$3")
|
||||
GIT_DIR=$(git rev-parse --git-dir)
|
||||
if test -d "$GIT_DIR/rebase-merge"
|
||||
then
|
||||
rebasing=1
|
||||
else
|
||||
source=${2-default}
|
||||
rebasing=0
|
||||
fi
|
||||
if test "$GIT_EDITOR" = :; then
|
||||
sed -e "1s/.*/$source (no editor)/" "$1" > msg.tmp
|
||||
|
||||
get_last_cmd () {
|
||||
tail -n1 "$GIT_DIR/rebase-merge/done" | {
|
||||
read cmd id _
|
||||
git log --pretty="[$cmd %s]" -n1 $id
|
||||
}
|
||||
}
|
||||
|
||||
if test "$2" = commit
|
||||
then
|
||||
if test $rebasing = 1
|
||||
then
|
||||
source="$3"
|
||||
else
|
||||
source=$(git rev-parse "$3")
|
||||
fi
|
||||
else
|
||||
sed -e "1s/.*/$source/" "$1" > msg.tmp
|
||||
source=${2-default}
|
||||
fi
|
||||
test "$GIT_EDITOR" = : && source="$source (no editor)"
|
||||
|
||||
if test $rebasing = 1
|
||||
then
|
||||
echo "$source $(get_last_cmd)" >"$1"
|
||||
else
|
||||
sed -e "1s/.*/$source/" "$1" >msg.tmp
|
||||
mv msg.tmp "$1"
|
||||
fi
|
||||
mv msg.tmp "$1"
|
||||
exit 0
|
||||
EOF
|
||||
chmod +x "$HOOK"
|
||||
@ -156,6 +212,63 @@ test_expect_success 'with hook and editor (merge)' '
|
||||
test "$(git log -1 --pretty=format:%s)" = "merge"
|
||||
'
|
||||
|
||||
test_rebase () {
|
||||
expect=$1 &&
|
||||
mode=$2 &&
|
||||
test_expect_$expect C_LOCALE_OUTPUT "with hook (rebase $mode)" '
|
||||
test_when_finished "\
|
||||
git rebase --abort
|
||||
git checkout -f master
|
||||
git branch -D tmp" &&
|
||||
git checkout -b tmp rebase-me &&
|
||||
GIT_SEQUENCE_EDITOR="cp rebase-todo" &&
|
||||
GIT_EDITOR="\"$FAKE_EDITOR\"" &&
|
||||
(
|
||||
export GIT_SEQUENCE_EDITOR GIT_EDITOR &&
|
||||
test_must_fail git rebase $mode b &&
|
||||
echo x >a &&
|
||||
git add a &&
|
||||
test_must_fail git rebase --continue &&
|
||||
echo x >b &&
|
||||
git add b &&
|
||||
git commit &&
|
||||
git rebase --continue &&
|
||||
echo y >a &&
|
||||
git add a &&
|
||||
git commit &&
|
||||
git rebase --continue &&
|
||||
echo y >b &&
|
||||
git add b &&
|
||||
git rebase --continue
|
||||
) &&
|
||||
if test $mode = -p # reword amended after pick
|
||||
then
|
||||
n=18
|
||||
else
|
||||
n=17
|
||||
fi &&
|
||||
git log --pretty=%s -g -n$n HEAD@{1} >actual &&
|
||||
test_cmp "$TEST_DIRECTORY/t7505/expected-rebase$mode" actual
|
||||
'
|
||||
}
|
||||
|
||||
test_rebase success -i
|
||||
test_rebase success -p
|
||||
|
||||
test_expect_success 'with hook (cherry-pick)' '
|
||||
test_when_finished "git checkout -f master" &&
|
||||
git checkout -B other b &&
|
||||
git cherry-pick rebase-1 &&
|
||||
test "$(git log -1 --pretty=format:%s)" = "message (no editor)"
|
||||
'
|
||||
|
||||
test_expect_success 'with hook and editor (cherry-pick)' '
|
||||
test_when_finished "git checkout -f master" &&
|
||||
git checkout -B other b &&
|
||||
git cherry-pick -e rebase-1 &&
|
||||
test "$(git log -1 --pretty=format:%s)" = merge
|
||||
'
|
||||
|
||||
cat > "$HOOK" <<'EOF'
|
||||
#!/bin/sh
|
||||
exit 1
|
||||
@ -197,4 +310,11 @@ test_expect_success 'with failing hook (merge)' '
|
||||
|
||||
'
|
||||
|
||||
test_expect_success C_LOCALE_OUTPUT 'with failing hook (cherry-pick)' '
|
||||
test_when_finished "git checkout -f master" &&
|
||||
git checkout -B other b &&
|
||||
test_must_fail git cherry-pick rebase-1 2>actual &&
|
||||
test $(grep -c prepare-commit-msg actual) = 1
|
||||
'
|
||||
|
||||
test_done
|
||||
|
17
t/t7505/expected-rebase-i
Normal file
17
t/t7505/expected-rebase-i
Normal file
@ -0,0 +1,17 @@
|
||||
message [edit rebase-13]
|
||||
message (no editor) [edit rebase-13]
|
||||
message [squash rebase-12]
|
||||
message (no editor) [squash rebase-11]
|
||||
default [edit rebase-10]
|
||||
message (no editor) [edit rebase-10]
|
||||
message [fixup rebase-9]
|
||||
message (no editor) [fixup rebase-8]
|
||||
message (no editor) [squash rebase-7]
|
||||
message [reword rebase-6]
|
||||
message [squash rebase-5]
|
||||
message (no editor) [fixup rebase-4]
|
||||
message (no editor) [pick rebase-3]
|
||||
message (no editor) [fixup rebase-2]
|
||||
message (no editor) [fixup rebase-1]
|
||||
merge [pick rebase-b]
|
||||
message [pick rebase-a]
|
18
t/t7505/expected-rebase-p
Normal file
18
t/t7505/expected-rebase-p
Normal file
@ -0,0 +1,18 @@
|
||||
message [edit rebase-13]
|
||||
message (no editor) [edit rebase-13]
|
||||
message [squash rebase-12]
|
||||
message (no editor) [squash rebase-11]
|
||||
default [edit rebase-10]
|
||||
message (no editor) [edit rebase-10]
|
||||
message [fixup rebase-9]
|
||||
message (no editor) [fixup rebase-8]
|
||||
message (no editor) [squash rebase-7]
|
||||
HEAD [reword rebase-6]
|
||||
message (no editor) [reword rebase-6]
|
||||
message [squash rebase-5]
|
||||
message (no editor) [fixup rebase-4]
|
||||
message (no editor) [pick rebase-3]
|
||||
message (no editor) [fixup rebase-2]
|
||||
message (no editor) [fixup rebase-1]
|
||||
merge [pick rebase-b]
|
||||
message [pick rebase-a]
|
Loading…
Reference in New Issue
Block a user