Skip to content

Commit c5aa4bc

Browse files
committed
Merge branch 'js/sequencer-and-root-commits'
The implementation of "git rebase -i --root" has been updated to use the sequencer machinery more. * js/sequencer-and-root-commits: rebase --rebase-merges: root commits can be cousins, too rebase --rebase-merges: a "merge" into a new root is a fast-forward sequencer: allow introducing new root commits rebase -i --root: let the sequencer handle even the initial part sequencer: learn about the special "fake root commit" handling sequencer: extract helper to update active_cache_tree
2 parents 89be19d + 8fa6eea commit c5aa4bc

File tree

6 files changed

+276
-35
lines changed

6 files changed

+276
-35
lines changed

git-rebase--interactive.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -894,6 +894,8 @@ init_revisions_and_shortrevisions () {
894894
else
895895
revisions=$onto...$orig_head
896896
shortrevisions=$shorthead
897+
test -z "$squash_onto" ||
898+
echo "$squash_onto" >"$state_dir"/squash-onto
897899
fi
898900
}
899901

@@ -948,7 +950,7 @@ EOF
948950
die "Could not skip unnecessary pick commands"
949951

950952
checkout_onto
951-
if test -z "$rebase_root" && test ! -d "$rewritten"
953+
if test ! -d "$rewritten"
952954
then
953955
require_clean_work_tree "rebase"
954956
exec git rebase--helper ${force_rebase:+--no-ff} $allow_empty_message \

sequencer.c

Lines changed: 181 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,12 @@ static GIT_PATH_FUNC(rebase_path_rewritten_list, "rebase-merge/rewritten-list")
123123
static GIT_PATH_FUNC(rebase_path_rewritten_pending,
124124
"rebase-merge/rewritten-pending")
125125

126+
/*
127+
* The path of the file containig the OID of the "squash onto" commit, i.e.
128+
* the dummy commit used for `reset [new root]`.
129+
*/
130+
static GIT_PATH_FUNC(rebase_path_squash_onto, "rebase-merge/squash-onto")
131+
126132
/*
127133
* The path of the file listing refs that need to be deleted after the rebase
128134
* finishes. This is used by the `label` command to record the need for cleanup.
@@ -469,7 +475,8 @@ static int fast_forward_to(const struct object_id *to, const struct object_id *f
469475
transaction = ref_transaction_begin(&err);
470476
if (!transaction ||
471477
ref_transaction_update(transaction, "HEAD",
472-
to, unborn ? &null_oid : from,
478+
to, unborn && !is_rebase_i(opts) ?
479+
&null_oid : from,
473480
0, sb.buf, &err) ||
474481
ref_transaction_commit(transaction, &err)) {
475482
ref_transaction_free(transaction);
@@ -561,9 +568,23 @@ static int do_recursive_merge(struct commit *base, struct commit *next,
561568
return !clean;
562569
}
563570

571+
static struct object_id *get_cache_tree_oid(void)
572+
{
573+
if (!active_cache_tree)
574+
active_cache_tree = cache_tree();
575+
576+
if (!cache_tree_fully_valid(active_cache_tree))
577+
if (cache_tree_update(&the_index, 0)) {
578+
error(_("unable to update cache tree"));
579+
return NULL;
580+
}
581+
582+
return &active_cache_tree->oid;
583+
}
584+
564585
static int is_index_unchanged(void)
565586
{
566-
struct object_id head_oid;
587+
struct object_id head_oid, *cache_tree_oid;
567588
struct commit *head_commit;
568589

569590
if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, &head_oid, NULL))
@@ -582,15 +603,10 @@ static int is_index_unchanged(void)
582603
if (parse_commit(head_commit))
583604
return -1;
584605

585-
if (!active_cache_tree)
586-
active_cache_tree = cache_tree();
587-
588-
if (!cache_tree_fully_valid(active_cache_tree))
589-
if (cache_tree_update(&the_index, 0))
590-
return error(_("unable to update cache tree"));
606+
if (!(cache_tree_oid = get_cache_tree_oid()))
607+
return -1;
591608

592-
return !oidcmp(&active_cache_tree->oid,
593-
get_commit_tree_oid(head_commit));
609+
return !oidcmp(cache_tree_oid, get_commit_tree_oid(head_commit));
594610
}
595611

596612
static int write_author_script(const char *message)
@@ -682,6 +698,52 @@ static char *get_author(const char *message)
682698
return NULL;
683699
}
684700

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

705768
/*
706769
* If we are cherry-pick, and if the merge did not result in
@@ -720,6 +783,40 @@ static int run_git_commit(const char *defmsg, struct replay_opts *opts,
720783
struct child_process cmd = CHILD_PROCESS_INIT;
721784
const char *value;
722785

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

725822
if (is_rebase_i(opts)) {
@@ -1210,7 +1307,8 @@ static int do_commit(const char *msg_file, const char *author,
12101307
{
12111308
int res = 1;
12121309

1213-
if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG)) {
1310+
if (!(flags & EDIT_MSG) && !(flags & VERIFY_MSG) &&
1311+
!(flags & CREATE_ROOT_COMMIT)) {
12141312
struct object_id oid;
12151313
struct strbuf sb = STRBUF_INIT;
12161314

@@ -1363,6 +1461,22 @@ static int is_fixup(enum todo_command command)
13631461
return command == TODO_FIXUP || command == TODO_SQUASH;
13641462
}
13651463

1464+
/* Does this command create a (non-merge) commit? */
1465+
static int is_pick_or_similar(enum todo_command command)
1466+
{
1467+
switch (command) {
1468+
case TODO_PICK:
1469+
case TODO_REVERT:
1470+
case TODO_EDIT:
1471+
case TODO_REWORD:
1472+
case TODO_FIXUP:
1473+
case TODO_SQUASH:
1474+
return 1;
1475+
default:
1476+
return 0;
1477+
}
1478+
}
1479+
13661480
static int update_squash_messages(enum todo_command command,
13671481
struct commit *commit, struct replay_opts *opts)
13681482
{
@@ -1516,7 +1630,14 @@ static int do_pick_commit(enum todo_command command, struct commit *commit,
15161630
return error(_("your index file is unmerged."));
15171631
} else {
15181632
unborn = get_oid("HEAD", &head);
1519-
if (unborn)
1633+
/* Do we want to generate a root commit? */
1634+
if (is_pick_or_similar(command) && opts->have_squash_onto &&
1635+
!oidcmp(&head, &opts->squash_onto)) {
1636+
if (is_fixup(command))
1637+
return error(_("cannot fixup root commit"));
1638+
flags |= CREATE_ROOT_COMMIT;
1639+
unborn = 1;
1640+
} else if (unborn)
15201641
oidcpy(&head, the_hash_algo->empty_tree);
15211642
if (index_differs_from(unborn ? EMPTY_TREE_SHA1_HEX : "HEAD",
15221643
NULL, 0))
@@ -2142,6 +2263,12 @@ static int read_populate_opts(struct replay_opts *opts)
21422263
}
21432264
}
21442265

2266+
if (read_oneliner(&buf, rebase_path_squash_onto(), 0)) {
2267+
if (get_oid_hex(buf.buf, &opts->squash_onto) < 0)
2268+
return error(_("unusable squash-onto"));
2269+
opts->have_squash_onto = 1;
2270+
}
2271+
21452272
return 0;
21462273
}
21472274

@@ -2634,18 +2761,34 @@ static int do_reset(const char *name, int len, struct replay_opts *opts)
26342761
if (hold_locked_index(&lock, LOCK_REPORT_ON_ERROR) < 0)
26352762
return -1;
26362763

2637-
/* Determine the length of the label */
2638-
for (i = 0; i < len; i++)
2639-
if (isspace(name[i]))
2640-
len = i;
2641-
2642-
strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
2643-
if (get_oid(ref_name.buf, &oid) &&
2644-
get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) {
2645-
error(_("could not read '%s'"), ref_name.buf);
2646-
rollback_lock_file(&lock);
2647-
strbuf_release(&ref_name);
2648-
return -1;
2764+
if (len == 10 && !strncmp("[new root]", name, len)) {
2765+
if (!opts->have_squash_onto) {
2766+
const char *hex;
2767+
if (commit_tree("", 0, the_hash_algo->empty_tree,
2768+
NULL, &opts->squash_onto,
2769+
NULL, NULL))
2770+
return error(_("writing fake root commit"));
2771+
opts->have_squash_onto = 1;
2772+
hex = oid_to_hex(&opts->squash_onto);
2773+
if (write_message(hex, strlen(hex),
2774+
rebase_path_squash_onto(), 0))
2775+
return error(_("writing squash-onto"));
2776+
}
2777+
oidcpy(&oid, &opts->squash_onto);
2778+
} else {
2779+
/* Determine the length of the label */
2780+
for (i = 0; i < len; i++)
2781+
if (isspace(name[i]))
2782+
len = i;
2783+
2784+
strbuf_addf(&ref_name, "refs/rewritten/%.*s", len, name);
2785+
if (get_oid(ref_name.buf, &oid) &&
2786+
get_oid(ref_name.buf + strlen("refs/rewritten/"), &oid)) {
2787+
error(_("could not read '%s'"), ref_name.buf);
2788+
rollback_lock_file(&lock);
2789+
strbuf_release(&ref_name);
2790+
return -1;
2791+
}
26492792
}
26502793

26512794
memset(&unpack_tree_opts, 0, sizeof(unpack_tree_opts));
@@ -2741,6 +2884,18 @@ static int do_merge(struct commit *commit, const char *arg, int arg_len,
27412884
goto leave_merge;
27422885
}
27432886

2887+
if (opts->have_squash_onto &&
2888+
!oidcmp(&head_commit->object.oid, &opts->squash_onto)) {
2889+
/*
2890+
* When the user tells us to "merge" something into a
2891+
* "[new root]", let's simply fast-forward to the merge head.
2892+
*/
2893+
rollback_lock_file(&lock);
2894+
ret = fast_forward_to(&merge_commit->object.oid,
2895+
&head_commit->object.oid, 0, opts);
2896+
goto leave_merge;
2897+
}
2898+
27442899
if (commit) {
27452900
const char *message = get_commit_buffer(commit, NULL);
27462901
const char *body;
@@ -3850,7 +4005,8 @@ static int make_script_with_merges(struct pretty_print_context *pp,
38504005
}
38514006

38524007
if (!commit)
3853-
fprintf(out, "%s onto\n", cmd_reset);
4008+
fprintf(out, "%s %s\n", cmd_reset,
4009+
rebase_cousins ? "onto" : "[new root]");
38544010
else {
38554011
const char *to = NULL;
38564012

sequencer.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ struct replay_opts {
4848
struct strbuf current_fixups;
4949
int current_fixup_count;
5050

51+
/* placeholder commit for -i --root */
52+
struct object_id squash_onto;
53+
int have_squash_onto;
54+
5155
/* Only used by REPLAY_NONE */
5256
struct rev_info *revs;
5357
};

t/t3404-rebase-interactive.sh

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1204,18 +1204,16 @@ test_expect_success 'drop' '
12041204
test A = $(git cat-file commit HEAD^^ | sed -ne \$p)
12051205
'
12061206

1207-
cat >expect <<EOF
1208-
Successfully rebased and updated refs/heads/missing-commit.
1209-
EOF
1210-
12111207
test_expect_success 'rebase -i respects rebase.missingCommitsCheck = ignore' '
12121208
test_config rebase.missingCommitsCheck ignore &&
12131209
rebase_setup_and_clean missing-commit &&
12141210
set_fake_editor &&
12151211
FAKE_LINES="1 2 3 4" \
12161212
git rebase -i --root 2>actual &&
12171213
test D = $(git cat-file commit HEAD | sed -ne \$p) &&
1218-
test_i18ncmp expect actual
1214+
test_i18ngrep \
1215+
"Successfully rebased and updated refs/heads/missing-commit" \
1216+
actual
12191217
'
12201218

12211219
cat >expect <<EOF
@@ -1227,15 +1225,24 @@ To avoid this message, use "drop" to explicitly remove a commit.
12271225
Use 'git config rebase.missingCommitsCheck' to change the level of warnings.
12281226
The possible behaviours are: ignore, warn, error.
12291227
1228+
Rebasing (1/4)
1229+
Rebasing (2/4)
1230+
Rebasing (3/4)
1231+
Rebasing (4/4)
12301232
Successfully rebased and updated refs/heads/missing-commit.
12311233
EOF
12321234

1235+
cr_to_nl () {
1236+
tr '\015' '\012'
1237+
}
1238+
12331239
test_expect_success 'rebase -i respects rebase.missingCommitsCheck = warn' '
12341240
test_config rebase.missingCommitsCheck warn &&
12351241
rebase_setup_and_clean missing-commit &&
12361242
set_fake_editor &&
12371243
FAKE_LINES="1 2 3 4" \
1238-
git rebase -i --root 2>actual &&
1244+
git rebase -i --root 2>actual.2 &&
1245+
cr_to_nl <actual.2 >actual &&
12391246
test_i18ncmp expect actual &&
12401247
test D = $(git cat-file commit HEAD | sed -ne \$p)
12411248
'

t/t3421-rebase-topology-linear.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -328,9 +328,9 @@ test_run_rebase () {
328328
test_cmp_rev c HEAD
329329
"
330330
}
331-
test_run_rebase failure ''
332-
test_run_rebase failure -m
333-
test_run_rebase failure -i
331+
test_run_rebase success ''
332+
test_run_rebase success -m
333+
test_run_rebase success -i
334334
test_run_rebase failure -p
335335

336336
test_run_rebase () {

0 commit comments

Comments
 (0)