Skip to content

Commit d87d48b

Browse files
dschogitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent ba97aea commit d87d48b

File tree

2 files changed

+125
-3
lines changed

2 files changed

+125
-3
lines changed

sequencer.c

Lines changed: 121 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list")
125125
static GIT_PATH_FUNC(rebase_path_rewritten_pending,
126126
"rebase-merge/rewritten-pending")
127127

128+
/*
129+
* The path of the file containig the OID of the "squash onto" commit, i.e.
130+
* the dummy commit used for `reset [new root]`.
131+
*/
132+
static GIT_PATH_FUNC(rebase_path_squash_onto, "rebase-merge/squash-onto")
133+
128134
/*
129135
* The path of the file listing refs that need to be deleted after the rebase
130136
* 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
470476
transaction = ref_transaction_begin(&err);
471477
if (!transaction ||
472478
ref_transaction_update(transaction, "HEAD",
473-
to, unborn ? &null_oid : from,
479+
to, unborn && !is_rebase_i(opts) ?
480+
&null_oid : from,
474481
0, sb.buf, &err) ||
475482
ref_transaction_commit(transaction, &err)) {
476483
ref_transaction_free(transaction);
@@ -692,6 +699,52 @@ static char *get_author(const char *message)
692699
return NULL;
693700
}
694701

702+
/* Read author-script and return an ident line (author <email> timestamp) */
703+
static const char *read_author_ident(struct strbuf *buf)
704+
{
705+
const char *keys[] = {
706+
"GIT_AUTHOR_NAME=", "GIT_AUTHOR_EMAIL=", "GIT_AUTHOR_DATE="
707+
};
708+
char *in, *out, *eol;
709+
int i = 0, len;
710+
711+
if (strbuf_read_file(buf, rebase_path_author_script(), 256) <= 0)
712+
return NULL;
713+
714+
/* dequote values and construct ident line in-place */
715+
for (in = out = buf->buf; i < 3 && in - buf->buf < buf->len; i++) {
716+
if (!skip_prefix(in, keys[i], (const char **)&in)) {
717+
warning("could not parse '%s' (looking for '%s'",
718+
rebase_path_author_script(), keys[i]);
719+
return NULL;
720+
}
721+
722+
eol = strchrnul(in, '\n');
723+
*eol = '\0';
724+
sq_dequote(in);
725+
len = strlen(in);
726+
727+
if (i > 0) /* separate values by spaces */
728+
*(out++) = ' ';
729+
if (i == 1) /* email needs to be surrounded by <...> */
730+
*(out++) = '<';
731+
memmove(out, in, len);
732+
out += len;
733+
if (i == 1) /* email needs to be surrounded by <...> */
734+
*(out++) = '>';
735+
in = eol + 1;
736+
}
737+
738+
if (i < 3) {
739+
warning("could not parse '%s' (looking for '%s')",
740+
rebase_path_author_script(), keys[i]);
741+
return NULL;
742+
}
743+
744+
buf->len = out - buf->buf;
745+
return buf->buf;
746+
}
747+
695748
static const char staged_changes_advice[] =
696749
N_("you have staged changes in your working tree\n"
697750
"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"
711764
#define AMEND_MSG (1<<2)
712765
#define CLEANUP_MSG (1<<3)
713766
#define VERIFY_MSG (1<<4)
767+
#define CREATE_ROOT_COMMIT (1<<5)
714768

715769
/*
716770
* 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,
730784
struct child_process cmd = CHILD_PROCESS_INIT;
731785
const char *value;
732786

787+
if (flags & CREATE_ROOT_COMMIT) {
788+
struct strbuf msg = STRBUF_INIT, script = STRBUF_INIT;
789+
const char *author = is_rebase_i(opts) ?
790+
read_author_ident(&script) : NULL;
791+
struct object_id root_commit, *cache_tree_oid;
792+
int res = 0;
793+
794+
if (!defmsg)
795+
BUG("root commit without message");
796+
797+
if (!(cache_tree_oid = get_cache_tree_oid()))
798+
res = -1;
799+
800+
if (!res)
801+
res = strbuf_read_file(&msg, defmsg, 0);
802+
803+
if (res <= 0)
804+
res = error_errno(_("could not read '%s'"), defmsg);
805+
else
806+
res = commit_tree(msg.buf, msg.len, cache_tree_oid,
807+
NULL, &root_commit, author,
808+
opts->gpg_sign);
809+
810+
strbuf_release(&msg);
811+
strbuf_release(&script);
812+
if (!res) {
813+
update_ref(NULL, "CHERRY_PICK_HEAD", &root_commit, NULL,
814+
REF_NO_DEREF, UPDATE_REFS_MSG_ON_ERR);
815+
res = update_ref(NULL, "HEAD", &root_commit, NULL, 0,
816+
UPDATE_REFS_MSG_ON_ERR);
817+
}
818+
return res < 0 ? error(_("writing root commit")) : 0;
819+
}
820+
733821
cmd.git_cmd = 1;
734822

735823
if (is_rebase_i(opts)) {
@@ -1216,7 +1304,8 @@ static int do_commit(const char *msg_file, const char *author,
12161304
{
12171305
int res = 1;
12181306

1219-
if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) {
1307+
if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG) &&
1308+
!(flags & CREATE_ROOT_COMMIT)) {
12201309
struct object_id oid;
12211310
struct strbuf sb = STRBUF_INIT;
12221311

@@ -1369,6 +1458,22 @@ static int is_fixup(enum todo_command command)
13691458
return command == TODO_FIXUP || command == TODO_SQUASH;
13701459
}
13711460

1461+
/* Does this command create a (non-merge) commit? */
1462+
static int is_pick_or_similar(enum todo_command command)
1463+
{
1464+
switch (command) {
1465+
case TODO_PICK:
1466+
case TODO_REVERT:
1467+
case TODO_EDIT:
1468+
case TODO_REWORD:
1469+
case TODO_FIXUP:
1470+
case TODO_SQUASH:
1471+
return 1;
1472+
default:
1473+
return 0;
1474+
}
1475+
}
1476+
13721477
static int update_squash_messages(enum todo_command command,
13731478
struct commit *commit, struct replay_opts *opts)
13741479
{
@@ -1523,7 +1628,14 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
15231628
return error(_("your index file is unmerged."));
15241629
} else {
15251630
unborn = get_oid("HEAD", &head);
1526-
if (unborn)
1631+
/* Do we want to generate a root commit? */
1632+
if (is_pick_or_similar(command) && opts->have_squash_onto &&
1633+
!oidcmp(&head, &opts->squash_onto)) {
1634+
if (is_fixup(command))
1635+
return error(_("cannot fixup root commit"));
1636+
flags |= CREATE_ROOT_COMMIT;
1637+
unborn = 1;
1638+
} else if (unborn)
15271639
oidcpy(&head, the_hash_algo->empty_tree);
15281640
if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD",
15291641
NULL, 0))
@@ -2136,6 +2248,12 @@ static int read_populate_opts(struct replay_opts *opts)
21362248
read_strategy_opts(opts, &buf);
21372249
strbuf_release(&buf);
21382250

2251+
if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) {
2252+
if (get_oid_hex(buf.buf, &opts->squash_onto) < 0)
2253+
return error(_("unusable squash-onto"));
2254+
opts->have_squash_onto = 1;
2255+
}
2256+
21392257
return 0;
21402258
}
21412259

sequencer.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@ struct replay_opts {
4444
char **xopts;
4545
size_t xopts_nr, xopts_alloc;
4646

47+
/* placeholder commit for -i --root */
48+
struct object_id squash_onto;
49+
int have_squash_onto;
50+
4751
/* Only used by REPLAY_NONE */
4852
struct rev_info *revs;
4953
};

0 commit comments

Comments
 (0)