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 "gpg-interface.h"
|
||||||
#include "column.h"
|
#include "column.h"
|
||||||
#include "sequencer.h"
|
#include "sequencer.h"
|
||||||
#include "notes-utils.h"
|
|
||||||
#include "mailmap.h"
|
#include "mailmap.h"
|
||||||
#include "sigchain.h"
|
|
||||||
|
|
||||||
static const char * const builtin_commit_usage[] = {
|
static const char * const builtin_commit_usage[] = {
|
||||||
N_("git commit [<options>] [--] <pathspec>..."),
|
N_("git commit [<options>] [--] <pathspec>..."),
|
||||||
@ -45,31 +43,6 @@ static const char * const builtin_status_usage[] = {
|
|||||||
NULL
|
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[] =
|
static const char empty_amend_advice[] =
|
||||||
N_("You asked to amend the most recent commit, but doing so would make\n"
|
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"
|
"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"
|
"Then \"git cherry-pick --continue\" will resume cherry-picking\n"
|
||||||
"the remaining commits.\n");
|
"the remaining commits.\n");
|
||||||
|
|
||||||
static GIT_PATH_FUNC(git_path_commit_editmsg, "COMMIT_EDITMSG")
|
|
||||||
|
|
||||||
static const char *use_message_buffer;
|
static const char *use_message_buffer;
|
||||||
static struct lock_file index_lock; /* real index */
|
static struct lock_file index_lock; /* real index */
|
||||||
static struct lock_file false_lock; /* used only for partial commits */
|
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
|
* if editor is used, and only the whitespaces if the message
|
||||||
* is specified explicitly.
|
* is specified explicitly.
|
||||||
*/
|
*/
|
||||||
static enum {
|
static enum commit_msg_cleanup_mode cleanup_mode;
|
||||||
CLEANUP_SPACE,
|
|
||||||
CLEANUP_NONE,
|
|
||||||
CLEANUP_SCISSORS,
|
|
||||||
CLEANUP_ALL
|
|
||||||
} cleanup_mode;
|
|
||||||
static const char *cleanup_arg;
|
static const char *cleanup_arg;
|
||||||
|
|
||||||
static enum commit_whence whence;
|
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;
|
struct strbuf sb = STRBUF_INIT;
|
||||||
const char *hook_arg1 = NULL;
|
const char *hook_arg1 = NULL;
|
||||||
const char *hook_arg2 = 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;
|
int old_display_comment_prefix;
|
||||||
|
|
||||||
/* This checks and barfs if author is badly specified */
|
/* 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;
|
struct ident_split ci, ai;
|
||||||
|
|
||||||
if (whence != FROM_COMMIT) {
|
if (whence != FROM_COMMIT) {
|
||||||
if (cleanup_mode == CLEANUP_SCISSORS)
|
if (cleanup_mode == COMMIT_MSG_CLEANUP_SCISSORS)
|
||||||
wt_status_add_cut_line(s->fp);
|
wt_status_add_cut_line(s->fp);
|
||||||
status_printf_ln(s, GIT_COLOR_NORMAL,
|
status_printf_ln(s, GIT_COLOR_NORMAL,
|
||||||
whence == FROM_MERGE
|
whence == FROM_MERGE
|
||||||
@ -834,14 +800,15 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fprintf(s->fp, "\n");
|
fprintf(s->fp, "\n");
|
||||||
if (cleanup_mode == CLEANUP_ALL)
|
if (cleanup_mode == COMMIT_MSG_CLEANUP_ALL)
|
||||||
status_printf(s, GIT_COLOR_NORMAL,
|
status_printf(s, GIT_COLOR_NORMAL,
|
||||||
_("Please enter the commit message for your changes."
|
_("Please enter the commit message for your changes."
|
||||||
" Lines starting\nwith '%c' will be ignored, and an empty"
|
" Lines starting\nwith '%c' will be ignored, and an empty"
|
||||||
" message aborts the commit.\n"), comment_line_char);
|
" 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);
|
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,
|
status_printf(s, GIT_COLOR_NORMAL,
|
||||||
_("Please enter the commit message for your changes."
|
_("Please enter the commit message for your changes."
|
||||||
" Lines starting\n"
|
" Lines starting\n"
|
||||||
@ -986,65 +953,6 @@ static int prepare_to_commit(const char *index_file, const char *prefix,
|
|||||||
return 1;
|
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)
|
static const char *find_author_by_nickname(const char *name)
|
||||||
{
|
{
|
||||||
struct rev_info revs;
|
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)))
|
if (argc == 0 && (also || (only && !amend && !allow_empty)))
|
||||||
die(_("No paths with --include/--only does not make sense."));
|
die(_("No paths with --include/--only does not make sense."));
|
||||||
if (!cleanup_arg || !strcmp(cleanup_arg, "default"))
|
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"))
|
else if (!strcmp(cleanup_arg, "verbatim"))
|
||||||
cleanup_mode = CLEANUP_NONE;
|
cleanup_mode = COMMIT_MSG_CLEANUP_NONE;
|
||||||
else if (!strcmp(cleanup_arg, "whitespace"))
|
else if (!strcmp(cleanup_arg, "whitespace"))
|
||||||
cleanup_mode = CLEANUP_SPACE;
|
cleanup_mode = COMMIT_MSG_CLEANUP_SPACE;
|
||||||
else if (!strcmp(cleanup_arg, "strip"))
|
else if (!strcmp(cleanup_arg, "strip"))
|
||||||
cleanup_mode = CLEANUP_ALL;
|
cleanup_mode = COMMIT_MSG_CLEANUP_ALL;
|
||||||
else if (!strcmp(cleanup_arg, "scissors"))
|
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
|
else
|
||||||
die(_("Invalid cleanup mode %s"), cleanup_arg);
|
die(_("Invalid cleanup mode %s"), cleanup_arg);
|
||||||
|
|
||||||
@ -1439,98 +1349,6 @@ int cmd_status(int argc, const char **argv, const char *prefix)
|
|||||||
return 0;
|
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)
|
static int git_commit_config(const char *k, const char *v, void *cb)
|
||||||
{
|
{
|
||||||
struct wt_status *s = 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);
|
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, ...)
|
int run_commit_hook(int editor_is_used, const char *index_file, const char *name, ...)
|
||||||
{
|
{
|
||||||
struct argv_array hook_env = ARGV_ARRAY_INIT;
|
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 sb = STRBUF_INIT;
|
||||||
struct strbuf author_ident = STRBUF_INIT;
|
struct strbuf author_ident = STRBUF_INIT;
|
||||||
const char *index_file, *reflog_msg;
|
const char *index_file, *reflog_msg;
|
||||||
char *nl;
|
|
||||||
struct object_id oid;
|
struct object_id oid;
|
||||||
struct commit_list *parents = NULL;
|
struct commit_list *parents = NULL;
|
||||||
struct stat statbuf;
|
struct stat statbuf;
|
||||||
struct commit *current_head = NULL;
|
struct commit *current_head = NULL;
|
||||||
struct commit_extra_header *extra = NULL;
|
struct commit_extra_header *extra = NULL;
|
||||||
struct ref_transaction *transaction;
|
|
||||||
struct strbuf err = STRBUF_INIT;
|
struct strbuf err = STRBUF_INIT;
|
||||||
|
|
||||||
if (argc == 2 && !strcmp(argv[1], "-h"))
|
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. */
|
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));
|
strbuf_setlen(&sb, wt_status_locate_end(sb.buf, sb.len));
|
||||||
if (cleanup_mode != CLEANUP_NONE)
|
if (cleanup_mode != COMMIT_MSG_CLEANUP_NONE)
|
||||||
strbuf_stripspace(&sb, cleanup_mode == CLEANUP_ALL);
|
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();
|
rollback_index_files();
|
||||||
fprintf(stderr, _("Aborting commit due to empty commit message.\n"));
|
fprintf(stderr, _("Aborting commit due to empty commit message.\n"));
|
||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
if (template_untouched(&sb) && !allow_empty_message) {
|
if (template_untouched(&sb, template_file, cleanup_mode) && !allow_empty_message) {
|
||||||
rollback_index_files();
|
rollback_index_files();
|
||||||
fprintf(stderr, _("Aborting commit; you did not edit the message.\n"));
|
fprintf(stderr, _("Aborting commit; you did not edit the message.\n"));
|
||||||
exit(1);
|
exit(1);
|
||||||
@ -1802,25 +1587,11 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
|
|||||||
strbuf_release(&author_ident);
|
strbuf_release(&author_ident);
|
||||||
free_commit_extra_headers(extra);
|
free_commit_extra_headers(extra);
|
||||||
|
|
||||||
nl = strchr(sb.buf, '\n');
|
if (update_head_with_reflog(current_head, &oid, reflog_msg, &sb,
|
||||||
if (nl)
|
&err)) {
|
||||||
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)) {
|
|
||||||
rollback_index_files();
|
rollback_index_files();
|
||||||
die("%s", err.buf);
|
die("%s", err.buf);
|
||||||
}
|
}
|
||||||
ref_transaction_free(transaction);
|
|
||||||
|
|
||||||
unlink(git_path_cherry_pick_head());
|
unlink(git_path_cherry_pick_head());
|
||||||
unlink(git_path_revert_head());
|
unlink(git_path_revert_head());
|
||||||
@ -1837,17 +1608,17 @@ int cmd_commit(int argc, const char **argv, const char *prefix)
|
|||||||
rerere(0);
|
rerere(0);
|
||||||
run_commit_hook(use_editor, get_index_file(), "post-commit", NULL);
|
run_commit_hook(use_editor, get_index_file(), "post-commit", NULL);
|
||||||
if (amend && !no_post_rewrite) {
|
if (amend && !no_post_rewrite) {
|
||||||
struct notes_rewrite_cfg *cfg;
|
commit_post_rewrite(current_head, &oid);
|
||||||
cfg = init_copy_notes_for_rewrite("amend");
|
}
|
||||||
if (cfg) {
|
if (!quiet) {
|
||||||
/* we are amending, so current_head is not NULL */
|
unsigned int flags = 0;
|
||||||
copy_note_for_rewrite(cfg, ¤t_head->object.oid, &oid);
|
|
||||||
finish_copy_notes_for_rewrite(cfg, "Notes added by 'git commit --amend'");
|
if (!current_head)
|
||||||
}
|
flags |= SUMMARY_INITIAL_COMMIT;
|
||||||
run_rewrite_hook(¤t_head->object.oid, &oid);
|
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(err);
|
||||||
UNLEAK(sb);
|
UNLEAK(sb);
|
||||||
|
@ -43,7 +43,7 @@ int cmd_rebase__helper(int argc, const char **argv, const char *prefix)
|
|||||||
OPT_END()
|
OPT_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
git_config(git_default_config, NULL);
|
sequencer_init_config(&opts);
|
||||||
git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
|
git_config_get_bool("rebase.abbreviatecommands", &abbreviate_commands);
|
||||||
|
|
||||||
opts.action = REPLAY_INTERACTIVE_REBASE;
|
opts.action = REPLAY_INTERACTIVE_REBASE;
|
||||||
|
@ -208,7 +208,7 @@ int cmd_revert(int argc, const char **argv, const char *prefix)
|
|||||||
if (isatty(0))
|
if (isatty(0))
|
||||||
opts.edit = 1;
|
opts.edit = 1;
|
||||||
opts.action = REPLAY_REVERT;
|
opts.action = REPLAY_REVERT;
|
||||||
git_config(git_default_config, NULL);
|
sequencer_init_config(&opts);
|
||||||
res = run_sequencer(argc, argv, &opts);
|
res = run_sequencer(argc, argv, &opts);
|
||||||
if (res < 0)
|
if (res < 0)
|
||||||
die(_("revert failed"));
|
die(_("revert failed"));
|
||||||
@ -221,7 +221,7 @@ int cmd_cherry_pick(int argc, const char **argv, const char *prefix)
|
|||||||
int res;
|
int res;
|
||||||
|
|
||||||
opts.action = REPLAY_PICK;
|
opts.action = REPLAY_PICK;
|
||||||
git_config(git_default_config, NULL);
|
sequencer_init_config(&opts);
|
||||||
res = run_sequencer(argc, argv, &opts);
|
res = run_sequencer(argc, argv, &opts);
|
||||||
if (res < 0)
|
if (res < 0)
|
||||||
die(_("cherry-pick failed"));
|
die(_("cherry-pick failed"));
|
||||||
|
538
sequencer.c
538
sequencer.c
@ -1,10 +1,10 @@
|
|||||||
#include "cache.h"
|
#include "cache.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "lockfile.h"
|
#include "lockfile.h"
|
||||||
#include "sequencer.h"
|
|
||||||
#include "dir.h"
|
#include "dir.h"
|
||||||
#include "object.h"
|
#include "object.h"
|
||||||
#include "commit.h"
|
#include "commit.h"
|
||||||
|
#include "sequencer.h"
|
||||||
#include "tag.h"
|
#include "tag.h"
|
||||||
#include "run-command.h"
|
#include "run-command.h"
|
||||||
#include "exec_cmd.h"
|
#include "exec_cmd.h"
|
||||||
@ -21,12 +21,16 @@
|
|||||||
#include "log-tree.h"
|
#include "log-tree.h"
|
||||||
#include "wt-status.h"
|
#include "wt-status.h"
|
||||||
#include "hashmap.h"
|
#include "hashmap.h"
|
||||||
|
#include "notes-utils.h"
|
||||||
|
#include "sigchain.h"
|
||||||
|
|
||||||
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
|
#define GIT_REFLOG_ACTION "GIT_REFLOG_ACTION"
|
||||||
|
|
||||||
const char sign_off_header[] = "Signed-off-by: ";
|
const char sign_off_header[] = "Signed-off-by: ";
|
||||||
static const char cherry_picked_prefix[] = "(cherry picked from commit ";
|
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")
|
GIT_PATH_FUNC(git_path_seq_dir, "sequencer")
|
||||||
|
|
||||||
static GIT_PATH_FUNC(git_path_todo_file, "sequencer/todo")
|
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_strategy_opts, "rebase-merge/strategy_opts")
|
||||||
static GIT_PATH_FUNC(rebase_path_allow_rerere_autoupdate, "rebase-merge/allow_rerere_autoupdate")
|
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)
|
static inline int is_rebase_i(const struct replay_opts *opts)
|
||||||
{
|
{
|
||||||
return opts->action == REPLAY_INTERACTIVE_REBASE;
|
return opts->action == REPLAY_INTERACTIVE_REBASE;
|
||||||
@ -478,9 +527,6 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
|
|||||||
_(action_name(opts)));
|
_(action_name(opts)));
|
||||||
rollback_lock_file(&index_lock);
|
rollback_lock_file(&index_lock);
|
||||||
|
|
||||||
if (opts->signoff)
|
|
||||||
append_signoff(msgbuf, 0, 0);
|
|
||||||
|
|
||||||
if (!clean)
|
if (!clean)
|
||||||
append_conflicts_hint(msgbuf);
|
append_conflicts_hint(msgbuf);
|
||||||
|
|
||||||
@ -596,6 +642,18 @@ static int read_env_script(struct argv_array *env)
|
|||||||
return 0;
|
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[] =
|
static const char staged_changes_advice[] =
|
||||||
N_("you have staged changes in your working tree\n"
|
N_("you have staged changes in your working tree\n"
|
||||||
"If these changes are meant to be squashed into the previous commit, run:\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");
|
argv_array_push(&cmd.args, "--amend");
|
||||||
if (opts->gpg_sign)
|
if (opts->gpg_sign)
|
||||||
argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
|
argv_array_pushf(&cmd.args, "-S%s", opts->gpg_sign);
|
||||||
if (opts->signoff)
|
|
||||||
argv_array_push(&cmd.args, "-s");
|
|
||||||
if (defmsg)
|
if (defmsg)
|
||||||
argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
|
argv_array_pushl(&cmd.args, "-F", defmsg, NULL);
|
||||||
if ((flags & CLEANUP_MSG))
|
if ((flags & CLEANUP_MSG))
|
||||||
@ -694,6 +750,461 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
|
|||||||
return run_command(&cmd);
|
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)
|
static int is_original_commit_empty(struct commit *commit)
|
||||||
{
|
{
|
||||||
const struct object_id *ptree_oid;
|
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 object_id head;
|
||||||
struct commit *base, *next, *parent;
|
struct commit *base, *next, *parent;
|
||||||
const char *base_label, *next_label;
|
const char *base_label, *next_label;
|
||||||
|
char *author = NULL;
|
||||||
struct commit_message msg = { NULL, NULL, NULL, NULL };
|
struct commit_message msg = { NULL, NULL, NULL, NULL };
|
||||||
struct strbuf msgbuf = STRBUF_INIT;
|
struct strbuf msgbuf = STRBUF_INIT;
|
||||||
int res, unborn = 0, allow;
|
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, oid_to_hex(&commit->object.oid));
|
||||||
strbuf_addstr(&msgbuf, ")\n");
|
strbuf_addstr(&msgbuf, ")\n");
|
||||||
}
|
}
|
||||||
|
if (!is_fixup(command))
|
||||||
|
author = get_author(msg.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (command == TODO_REWORD)
|
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)
|
if (is_rebase_i(opts) && write_author_script(msg.message) < 0)
|
||||||
res = -1;
|
res = -1;
|
||||||
else if (!opts->strategy || !strcmp(opts->strategy, "recursive") || command == TODO_REVERT) {
|
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;
|
goto leave;
|
||||||
} else if (allow)
|
} else if (allow)
|
||||||
flags |= ALLOW_EMPTY;
|
flags |= ALLOW_EMPTY;
|
||||||
if (!opts->no_commit)
|
if (!opts->no_commit) {
|
||||||
fast_forward_edit:
|
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) {
|
if (!res && final_fixup) {
|
||||||
unlink(rebase_path_fixup_msg());
|
unlink(rebase_path_fixup_msg());
|
||||||
@ -1159,6 +1680,7 @@ fast_forward_edit:
|
|||||||
|
|
||||||
leave:
|
leave:
|
||||||
free_message(commit, &msg);
|
free_message(commit, &msg);
|
||||||
|
free(author);
|
||||||
update_abort_safety_file();
|
update_abort_safety_file();
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
25
sequencer.h
25
sequencer.h
@ -1,6 +1,7 @@
|
|||||||
#ifndef SEQUENCER_H
|
#ifndef SEQUENCER_H
|
||||||
#define SEQUENCER_H
|
#define SEQUENCER_H
|
||||||
|
|
||||||
|
const char *git_path_commit_editmsg(void);
|
||||||
const char *git_path_seq_dir(void);
|
const char *git_path_seq_dir(void);
|
||||||
|
|
||||||
#define APPEND_SIGNOFF_DEDUP (1u << 0)
|
#define APPEND_SIGNOFF_DEDUP (1u << 0)
|
||||||
@ -11,6 +12,13 @@ enum replay_action {
|
|||||||
REPLAY_INTERACTIVE_REBASE
|
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 {
|
struct replay_opts {
|
||||||
enum replay_action action;
|
enum replay_action action;
|
||||||
|
|
||||||
@ -29,6 +37,7 @@ struct replay_opts {
|
|||||||
int mainline;
|
int mainline;
|
||||||
|
|
||||||
char *gpg_sign;
|
char *gpg_sign;
|
||||||
|
enum commit_msg_cleanup_mode default_msg_cleanup;
|
||||||
|
|
||||||
/* Merge strategy */
|
/* Merge strategy */
|
||||||
char *strategy;
|
char *strategy;
|
||||||
@ -40,6 +49,8 @@ struct replay_opts {
|
|||||||
};
|
};
|
||||||
#define REPLAY_OPTS_INIT { -1 }
|
#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_pick_revisions(struct replay_opts *opts);
|
||||||
int sequencer_continue(struct replay_opts *opts);
|
int sequencer_continue(struct replay_opts *opts);
|
||||||
int sequencer_rollback(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_signoff(struct strbuf *msgbuf, int ignore_footer, unsigned flag);
|
||||||
void append_conflicts_hint(struct strbuf *msgbuf);
|
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
|
#endif
|
||||||
|
@ -453,6 +453,10 @@ test_expect_success C_LOCALE_OUTPUT 'squash and fixup generate correct log messa
|
|||||||
git rebase -i $base &&
|
git rebase -i $base &&
|
||||||
git cat-file commit HEAD | sed -e 1,/^\$/d > actual-squash-fixup &&
|
git cat-file commit HEAD | sed -e 1,/^\$/d > actual-squash-fixup &&
|
||||||
test_cmp expect-squash-fixup 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 checkout to-be-rebased &&
|
||||||
git branch -D squash-fixup
|
git branch -D squash-fixup
|
||||||
'
|
'
|
||||||
@ -1336,6 +1340,16 @@ test_expect_success 'editor saves as CR/LF' '
|
|||||||
|
|
||||||
SQ="'"
|
SQ="'"
|
||||||
test_expect_success 'rebase -i --gpg-sign=<key-id>' '
|
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 &&
|
set_fake_editor &&
|
||||||
FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" HEAD^ \
|
FAKE_LINES="edit 1" git rebase -i --gpg-sign="\"S I Gner\"" HEAD^ \
|
||||||
>out 2>err &&
|
>out 2>err &&
|
||||||
|
@ -5,7 +5,6 @@ test_description='cherry-pick can handle submodules'
|
|||||||
. ./test-lib.sh
|
. ./test-lib.sh
|
||||||
. "$TEST_DIRECTORY"/lib-submodule-update.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_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
|
||||||
KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
|
KNOWN_FAILURE_NOFF_MERGE_ATTEMPTS_TO_MERGE_REMOVED_SUBMODULE_FILES=1
|
||||||
test_submodule_switch "git cherry-pick"
|
test_submodule_switch "git cherry-pick"
|
||||||
|
@ -25,7 +25,6 @@ git_revert () {
|
|||||||
git revert HEAD
|
git revert HEAD
|
||||||
}
|
}
|
||||||
|
|
||||||
KNOWN_FAILURE_CHERRY_PICK_SEES_EMPTY_COMMIT=1
|
|
||||||
KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
|
KNOWN_FAILURE_NOFF_MERGE_DOESNT_CREATE_EMPTY_SUBMODULE_DIR=1
|
||||||
test_submodule_switch "git_revert"
|
test_submodule_switch "git_revert"
|
||||||
|
|
||||||
|
@ -4,6 +4,38 @@ test_description='prepare-commit-msg hook'
|
|||||||
|
|
||||||
. ./test-lib.sh
|
. ./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' '
|
test_expect_success 'with no hook' '
|
||||||
|
|
||||||
echo "foo" > file &&
|
echo "foo" > file &&
|
||||||
@ -31,17 +63,41 @@ mkdir -p "$HOOKDIR"
|
|||||||
echo "#!$SHELL_PATH" > "$HOOK"
|
echo "#!$SHELL_PATH" > "$HOOK"
|
||||||
cat >> "$HOOK" <<'EOF'
|
cat >> "$HOOK" <<'EOF'
|
||||||
|
|
||||||
if test "$2" = commit; then
|
GIT_DIR=$(git rev-parse --git-dir)
|
||||||
source=$(git rev-parse "$3")
|
if test -d "$GIT_DIR/rebase-merge"
|
||||||
|
then
|
||||||
|
rebasing=1
|
||||||
else
|
else
|
||||||
source=${2-default}
|
rebasing=0
|
||||||
fi
|
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
|
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
|
fi
|
||||||
mv msg.tmp "$1"
|
|
||||||
exit 0
|
exit 0
|
||||||
EOF
|
EOF
|
||||||
chmod +x "$HOOK"
|
chmod +x "$HOOK"
|
||||||
@ -156,6 +212,63 @@ test_expect_success 'with hook and editor (merge)' '
|
|||||||
test "$(git log -1 --pretty=format:%s)" = "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'
|
cat > "$HOOK" <<'EOF'
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
exit 1
|
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
|
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