sequencer: learn about the special "fake root commit" handling
When an interactive rebase wants to recreate a root commit, it - first creates a new, empty root commit, - checks it out, - converts the next `pick` command so that it amends the empty root commit Introduce support in the sequencer to handle such an empty root commit, by looking for the file <GIT_DIR>/rebase-merge/squash-onto; if it exists and contains a commit name, the sequencer will compare the HEAD to said root commit, and if identical, a new root commit will be created. While converting scripted code into proper, portable C, we also do away with the old "amend with an empty commit message, then cherry-pick without committing, then amend again" dance and replace it with code that uses the internal API properly to do exactly what we want: create a new root commit. To keep the implementation simple, we always spawn `git commit` to create new root commits. Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
ba97aea165
commit
d87d48b2e0
124
sequencer.c
124
sequencer.c
@ -125,6 +125,12 @@ static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list")
|
|||||||
static GIT_PATH_FUNC(rebase_path_rewritten_pending,
|
static GIT_PATH_FUNC(rebase_path_rewritten_pending,
|
||||||
"rebase-merge/rewritten-pending")
|
"rebase-merge/rewritten-pending")
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The path of the file containig the OID of the "squash onto" commit, i.e.
|
||||||
|
* the dummy commit used for `reset [new root]`.
|
||||||
|
*/
|
||||||
|
static GIT_PATH_FUNC(rebase_path_squash_onto, "rebase-merge/squash-onto")
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The path of the file listing refs that need to be deleted after the rebase
|
* The path of the file listing refs that need to be deleted after the rebase
|
||||||
* finishes. This is used by the `label` command to record the need for cleanup.
|
* finishes. This is used by the `label` command to record the need for cleanup.
|
||||||
@ -470,7 +476,8 @@ static int fast_forward_to(const struct object_id *to, const struct object_id *f
|
|||||||
transaction = ref_transaction_begin(&err);
|
transaction = ref_transaction_begin(&err);
|
||||||
if (!transaction ||
|
if (!transaction ||
|
||||||
ref_transaction_update(transaction, "HEAD",
|
ref_transaction_update(transaction, "HEAD",
|
||||||
to, unborn ? &null_oid : from,
|
to, unborn && !is_rebase_i(opts) ?
|
||||||
|
&null_oid : from,
|
||||||
0, sb.buf, &err) ||
|
0, sb.buf, &err) ||
|
||||||
ref_transaction_commit(transaction, &err)) {
|
ref_transaction_commit(transaction, &err)) {
|
||||||
ref_transaction_free(transaction);
|
ref_transaction_free(transaction);
|
||||||
@ -692,6 +699,52 @@ static char *get_author(const char *message)
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Read author-script and return an ident line (author <email> timestamp) */
|
||||||
|
static const char *read_author_ident(struct strbuf *buf)
|
||||||
|
{
|
||||||
|
const char *keys[] = {
|
||||||
|
"GIT_AUTHOR_NAME=", "GIT_AUTHOR_EMAIL=", "GIT_AUTHOR_DATE="
|
||||||
|
};
|
||||||
|
char *in, *out, *eol;
|
||||||
|
int i = 0, len;
|
||||||
|
|
||||||
|
if (strbuf_read_file(buf, rebase_path_author_script(), 256) <= 0)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/* dequote values and construct ident line in-place */
|
||||||
|
for (in = out = buf->buf; i < 3 && in - buf->buf < buf->len; i++) {
|
||||||
|
if (!skip_prefix(in, keys[i], (const char **)&in)) {
|
||||||
|
warning("could not parse '%s' (looking for '%s'",
|
||||||
|
rebase_path_author_script(), keys[i]);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
eol = strchrnul(in, '\n');
|
||||||
|
*eol = '\0';
|
||||||
|
sq_dequote(in);
|
||||||
|
len = strlen(in);
|
||||||
|
|
||||||
|
if (i > 0) /* separate values by spaces */
|
||||||
|
*(out++) = ' ';
|
||||||
|
if (i == 1) /* email needs to be surrounded by <...> */
|
||||||
|
*(out++) = '<';
|
||||||
|
memmove(out, in, len);
|
||||||
|
out += len;
|
||||||
|
if (i == 1) /* email needs to be surrounded by <...> */
|
||||||
|
*(out++) = '>';
|
||||||
|
in = eol + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i < 3) {
|
||||||
|
warning("could not parse '%s' (looking for '%s')",
|
||||||
|
rebase_path_author_script(), keys[i]);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->len = out - buf->buf;
|
||||||
|
return buf->buf;
|
||||||
|
}
|
||||||
|
|
||||||
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"
|
||||||
@ -711,6 +764,7 @@ N_("you have staged changes in your working tree\n"
|
|||||||
#define AMEND_MSG (1<<2)
|
#define AMEND_MSG (1<<2)
|
||||||
#define CLEANUP_MSG (1<<3)
|
#define CLEANUP_MSG (1<<3)
|
||||||
#define VERIFY_MSG (1<<4)
|
#define VERIFY_MSG (1<<4)
|
||||||
|
#define CREATE_ROOT_COMMIT (1<<5)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If we are cherry-pick, and if the merge did not result in
|
* If we are cherry-pick, and if the merge did not result in
|
||||||
@ -730,6 +784,40 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
|
|||||||
struct child_process cmd = CHILD_PROCESS_INIT;
|
struct child_process cmd = CHILD_PROCESS_INIT;
|
||||||
const char *value;
|
const char *value;
|
||||||
|
|
||||||
|
if (flags & CREATE_ROOT_COMMIT) {
|
||||||
|
struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT;
|
||||||
|
const char *author = is_rebase_i(opts) ?
|
||||||
|
read_author_ident(&script) : NULL;
|
||||||
|
struct object_id root_commit, *cache_tree_oid;
|
||||||
|
int res = 0;
|
||||||
|
|
||||||
|
if (!defmsg)
|
||||||
|
BUG("root commit without message");
|
||||||
|
|
||||||
|
if (!(cache_tree_oid = get_cache_tree_oid()))
|
||||||
|
res = -1;
|
||||||
|
|
||||||
|
if (!res)
|
||||||
|
res = strbuf_read_file(&msg, defmsg, 0);
|
||||||
|
|
||||||
|
if (res <= 0)
|
||||||
|
res = error_errno(_("could not read '%s'"), defmsg);
|
||||||
|
else
|
||||||
|
res = commit_tree(msg.buf, msg.len, cache_tree_oid,
|
||||||
|
NULL, &root_commit, author,
|
||||||
|
opts->gpg_sign);
|
||||||
|
|
||||||
|
strbuf_release(&msg);
|
||||||
|
strbuf_release(&script);
|
||||||
|
if (!res) {
|
||||||
|
update_ref(NULL, "CHERRY_PICK_HEAD", &root_commit, NULL,
|
||||||
|
REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR);
|
||||||
|
res = update_ref(NULL, "HEAD", &root_commit, NULL, 0,
|
||||||
|
UPDATE_REFS_MSG_ON_ERR);
|
||||||
|
}
|
||||||
|
return res < 0 ? error(_("writing root commit")) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
cmd.git_cmd = 1;
|
cmd.git_cmd = 1;
|
||||||
|
|
||||||
if (is_rebase_i(opts)) {
|
if (is_rebase_i(opts)) {
|
||||||
@ -1216,7 +1304,8 @@ static int do_commit(const char *msg_file, const char *author,
|
|||||||
{
|
{
|
||||||
int res = 1;
|
int res = 1;
|
||||||
|
|
||||||
if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) {
|
if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG) &&
|
||||||
|
!(flags & CREATE_ROOT_COMMIT)) {
|
||||||
struct object_id oid;
|
struct object_id oid;
|
||||||
struct strbuf sb = STRBUF_INIT;
|
struct strbuf sb = STRBUF_INIT;
|
||||||
|
|
||||||
@ -1369,6 +1458,22 @@ static int is_fixup(enum todo_command command)
|
|||||||
return command == TODO_FIXUP || command == TODO_SQUASH;
|
return command == TODO_FIXUP || command == TODO_SQUASH;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Does this command create a (non-merge) commit? */
|
||||||
|
static int is_pick_or_similar(enum todo_command command)
|
||||||
|
{
|
||||||
|
switch (command) {
|
||||||
|
case TODO_PICK:
|
||||||
|
case TODO_REVERT:
|
||||||
|
case TODO_EDIT:
|
||||||
|
case TODO_REWORD:
|
||||||
|
case TODO_FIXUP:
|
||||||
|
case TODO_SQUASH:
|
||||||
|
return 1;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static int update_squash_messages(enum todo_command command,
|
static int update_squash_messages(enum todo_command command,
|
||||||
struct commit *commit, struct replay_opts *opts)
|
struct commit *commit, struct replay_opts *opts)
|
||||||
{
|
{
|
||||||
@ -1523,7 +1628,14 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
|
|||||||
return error(_("your index file is unmerged."));
|
return error(_("your index file is unmerged."));
|
||||||
} else {
|
} else {
|
||||||
unborn = get_oid("HEAD", &head);
|
unborn = get_oid("HEAD", &head);
|
||||||
if (unborn)
|
/* Do we want to generate a root commit? */
|
||||||
|
if (is_pick_or_similar(command) && opts->have_squash_onto &&
|
||||||
|
!oidcmp(&head, &opts->squash_onto)) {
|
||||||
|
if (is_fixup(command))
|
||||||
|
return error(_("cannot fixup root commit"));
|
||||||
|
flags |= CREATE_ROOT_COMMIT;
|
||||||
|
unborn = 1;
|
||||||
|
} else if (unborn)
|
||||||
oidcpy(&head, the_hash_algo->empty_tree);
|
oidcpy(&head, the_hash_algo->empty_tree);
|
||||||
if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD",
|
if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD",
|
||||||
NULL, 0))
|
NULL, 0))
|
||||||
@ -2136,6 +2248,12 @@ static int read_populate_opts(struct replay_opts *opts)
|
|||||||
read_strategy_opts(opts, &buf);
|
read_strategy_opts(opts, &buf);
|
||||||
strbuf_release(&buf);
|
strbuf_release(&buf);
|
||||||
|
|
||||||
|
if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) {
|
||||||
|
if (get_oid_hex(buf.buf, &opts->squash_onto) < 0)
|
||||||
|
return error(_("unusable squash-onto"));
|
||||||
|
opts->have_squash_onto = 1;
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,6 +44,10 @@ struct replay_opts {
|
|||||||
char **xopts;
|
char **xopts;
|
||||||
size_t xopts_nr, xopts_alloc;
|
size_t xopts_nr, xopts_alloc;
|
||||||
|
|
||||||
|
/* placeholder commit for -i --root */
|
||||||
|
struct object_id squash_onto;
|
||||||
|
int have_squash_onto;
|
||||||
|
|
||||||
/* Only used by REPLAY_NONE */
|
/* Only used by REPLAY_NONE */
|
||||||
struct rev_info *revs;
|
struct rev_info *revs;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user