Skip to content

Commit 9433f57

Browse files
committed
[RFC] rebase -m: partial support for copying extra commit headers
There are external projects such as GitButler that add extra headers to the commit objects that they create. Unfortunately these headers are lost when the user runs "git rebase". In the absence of merge conflicts copying these headers across to the rebased commit is relatively straight forward as the sequencer creates the rebased commits using commit_tree_extended() rather than forking "git commit". Preserving them in the presence of merge conflict would mean that we either need to add a way to creating extra headers when running "git commit" or modifying the sequencer to stop using git commit when the commit message needs to be edited. Both of these options involve a significant amount of more work. While losing the extra headers if there are merge conflicts is a significant shortcoming for users rebasing their branches it is not such a problem on the server where forges typically reject a rebase if it has conflicts. Therefore even though this commit is far from a complete solution it is a significant improvement for forges that have not yet moved to using "git replay" which already preserves extra commit headers. In the long run we would also want to preserve extra headers when cherry-picking a commit. As we cannot currently preserve extra headers when the user wishes to edit the commit message of the cherry-picked commit this patch only changes the behavior of "git rebase" Signed-off-by: Phillip Wood <[email protected]>
1 parent a36e024 commit 9433f57

File tree

2 files changed

+66
-7
lines changed

2 files changed

+66
-7
lines changed

sequencer.c

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1527,6 +1527,9 @@ static int parse_head(struct repository *r, struct commit **head)
15271527
return 0;
15281528
}
15291529

1530+
/* Headers to exclude when copying extra commit headers */
1531+
static const char *exclude_gpgsig[] = { "gpgsig", "gpgsig-sha256", NULL };
1532+
15301533
/*
15311534
* Try to commit without forking 'git commit'. In some cases we need
15321535
* to run 'git commit' to display an error message
@@ -1538,14 +1541,15 @@ static int parse_head(struct repository *r, struct commit **head)
15381541
*/
15391542
static int try_to_commit(struct repository *r,
15401543
struct strbuf *msg, const char *author,
1544+
struct commit_extra_header *extra_header,
15411545
struct replay_opts *opts, unsigned int flags,
15421546
struct object_id *oid)
15431547
{
15441548
struct replay_ctx *ctx = opts->ctx;
15451549
struct object_id tree;
15461550
struct commit *current_head = NULL;
15471551
struct commit_list *parents = NULL;
1548-
struct commit_extra_header *extra = NULL;
1552+
struct commit_extra_header *extra = extra_header;
15491553
struct strbuf err = STRBUF_INIT;
15501554
struct strbuf commit_msg = STRBUF_INIT;
15511555
char *amend_author = NULL;
@@ -1561,7 +1565,6 @@ static int try_to_commit(struct repository *r,
15611565
return -1;
15621566

15631567
if (flags & AMEND_MSG) {
1564-
const char *exclude_gpgsig[] = { "gpgsig", "gpgsig-sha256", NULL };
15651568
const char *out_enc = get_commit_output_encoding();
15661569
const char *message = repo_logmsg_reencode(r, current_head,
15671570
NULL, out_enc);
@@ -1714,7 +1717,8 @@ static int try_to_commit(struct repository *r,
17141717
commit_post_rewrite(r, current_head, oid);
17151718

17161719
out:
1717-
free_commit_extra_headers(extra);
1720+
if (extra != extra_header)
1721+
free_commit_extra_headers(extra);
17181722
free_commit_list(parents);
17191723
strbuf_release(&err);
17201724
strbuf_release(&commit_msg);
@@ -1734,6 +1738,7 @@ static int write_rebase_head(struct object_id *oid)
17341738

17351739
static int do_commit(struct repository *r,
17361740
const char *msg_file, const char *author,
1741+
struct commit_extra_header *extra_header,
17371742
struct replay_opts *opts, unsigned int flags,
17381743
struct object_id *oid)
17391744
{
@@ -1749,7 +1754,7 @@ static int do_commit(struct repository *r,
17491754
msg_file);
17501755

17511756
res = try_to_commit(r, msg_file ? &sb : NULL,
1752-
author, opts, flags, &oid);
1757+
author, extra_header, opts, flags, &oid);
17531758
strbuf_release(&sb);
17541759
if (!res) {
17551760
refs_delete_ref(get_main_ref_store(r), "",
@@ -2251,6 +2256,7 @@ static int do_pick_commit(struct repository *r,
22512256
int res, unborn = 0, reword = 0, allow, drop_commit;
22522257
enum todo_command command = item->command;
22532258
struct commit *commit = item->commit;
2259+
struct commit_extra_header *extra_header = NULL;
22542260

22552261
if (opts->no_commit) {
22562262
/*
@@ -2391,8 +2397,12 @@ static int do_pick_commit(struct repository *r,
23912397
strbuf_addstr(&ctx->message, oid_to_hex(&commit->object.oid));
23922398
strbuf_addstr(&ctx->message, ")\n");
23932399
}
2394-
if (!is_fixup(command))
2400+
if (!is_fixup(command)) {
23952401
author = get_author(msg.message);
2402+
if (is_rebase_i(opts))
2403+
extra_header = read_commit_extra_headers(commit,
2404+
exclude_gpgsig);
2405+
}
23962406
}
23972407
ctx->have_message = 1;
23982408

@@ -2503,8 +2513,8 @@ static int do_pick_commit(struct repository *r,
25032513
} /* else allow == 0 and there's nothing special to do */
25042514
if (!opts->no_commit && !drop_commit) {
25052515
if (author || command == TODO_REVERT || (flags & AMEND_MSG))
2506-
res = do_commit(r, msg_file, author, opts, flags,
2507-
commit? &commit->object.oid : NULL);
2516+
res = do_commit(r, msg_file, author, extra_header, opts,
2517+
flags, commit? &commit->object.oid : NULL);
25082518
else
25092519
res = error(_("unable to parse commit author"));
25102520
*check_todo = !!(flags & EDIT_MSG);
@@ -2535,6 +2545,7 @@ static int do_pick_commit(struct repository *r,
25352545
leave:
25362546
free_message(commit, &msg);
25372547
free(author);
2548+
free_commit_extra_headers(extra_header);
25382549
update_abort_safety_file();
25392550

25402551
return res;

t/t3404-rebase-interactive.sh

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2297,6 +2297,54 @@ test_expect_success 'non-merge commands reject merge commits' '
22972297
test_cmp expect actual
22982298
'
22992299

2300+
test_expect_success 'unconflicted pick copies extra commit headers' '
2301+
tree="$(git rev-parse C^{tree})" &&
2302+
parent="$(git rev-parse C^{commit})" &&
2303+
for i in 1 2 3 4 5 6 7
2304+
do
2305+
cat >commit <<-EOF &&
2306+
tree $tree
2307+
parent $parent
2308+
author $GIT_AUTHOR_NAME <$GIT_AUTHOR_EMAIL> $GIT_AUTHOR_DATE
2309+
committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
2310+
x-extra-header value $i
2311+
2312+
An empty commit with an extra header $i
2313+
EOF
2314+
2315+
parent="$(git hash-object -t commit -w commit)" &&
2316+
eval "oid$i=\$parent" &&
2317+
test_tick || return 1
2318+
done &&
2319+
2320+
cat >todo <<-EOF &&
2321+
pick $oid1
2322+
pick $oid2
2323+
fixup $oid3
2324+
reword $oid4
2325+
edit $oid5
2326+
pick $oid6
2327+
squash $oid7
2328+
EOF
2329+
2330+
(
2331+
set_replace_editor todo &&
2332+
FAKE_COMMIT_AMEND=EDITED git rebase -i --onto A $oid1^ $oid5
2333+
) &&
2334+
echo changed >file2 &&
2335+
git add file2 &&
2336+
FAKE_COMMIT_AMEND=EDITED git rebase --continue &&
2337+
j=4 &&
2338+
for i in 1 2 4 5 6
2339+
do
2340+
git cat-file commit HEAD~$j >actual &&
2341+
sed -n -e /^\$/q -e /^x-extra/p actual >actual.extra-header &&
2342+
echo "x-extra-header value $i" >expect &&
2343+
test_cmp expect actual.extra-header &&
2344+
j=$((j-1)) || return 1
2345+
done
2346+
'
2347+
23002348
# This must be the last test in this file
23012349
test_expect_success '$EDITOR and friends are unchanged' '
23022350
test_editor_unchanged

0 commit comments

Comments
 (0)