From: Johannes Schindelin <johannes.schindelin@xxxxxx> When rearranging the todo list so that the fixups/squashes are reordered just after the commits they intend to fix up, we use two arrays to maintain that list: `next` and `tail`. The idea is that `next[i]`, if set to a non-negative value, contains the index of the item that should be rearranged just after the `i`th item. To avoid having to walk the entire `next` chain when appending another fixup/squash, we also store the end of the `next` chain in `last[i]`. The logic we currently use to update these array items is based on the assumption that given a fixup/squash item at index `i`, we just found the index `i2` indicating the first item in that fixup chain. However, as reported by Paul Ganssle, that need not be true: the special form `fixup! <commit-hash>` is allowed to point to _another_ fixup commit in the middle of the fixup chain. Example: * 0192a To fixup * 02f12 fixup! To fixup * 03763 fixup! To fixup * 04ecb fixup! 02f12 Note how the fourth commit targets the second commit, which is already a fixup that targets the first commit. The good news is that it is easy to fix this: we can detect the situation by looking at `last[i2]` (which will be `-1` if `i2` is actually in the middle of a fixup chain), and in that case we simply need to squeeze the current item into the middle of the `next` chain, without touching `last` (i.e. leaving the end index of the fixup chain alone). Reported-by: Paul Ganssle <paul@xxxxxxxxxx> Helped-by: Jeff King <peff@xxxxxxxx> Signed-off-by: Johannes Schindelin <johannes.schindelin@xxxxxx> --- rebase --autosquash: fix a potential segfault This patch is in response to https://lore.kernel.org/git/017dbc40-8d21-00fb-7b0e-6708d2dcb366@xxxxxxxxxx/ . Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-625%2Fdscho%2Fautosquash-segfault-v1 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-625/dscho/autosquash-segfault-v1 Pull-Request: https://github.com/gitgitgadget/git/pull/625 sequencer.c | 11 ++++++++++- t/t3415-rebase-autosquash.sh | 14 ++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/sequencer.c b/sequencer.c index e528225e787..0d4d53d2a49 100644 --- a/sequencer.c +++ b/sequencer.c @@ -5266,8 +5266,17 @@ int todo_list_rearrange_squash(struct todo_list *todo_list) TODO_FIXUP : TODO_SQUASH; if (next[i2] < 0) next[i2] = i; - else + else if (tail[i2] >= 0) next[tail[i2]] = i; + else { + /* + * i2 refers to a fixup commit in the middle of + * a fixup chain + */ + next[i] = next[i2]; + next[i2] = i; + continue; + } tail[i2] = i; } else if (!hashmap_get_from_hash(&subject2item, strhash(subject), subject)) { diff --git a/t/t3415-rebase-autosquash.sh b/t/t3415-rebase-autosquash.sh index 093de9005b7..ca135349346 100755 --- a/t/t3415-rebase-autosquash.sh +++ b/t/t3415-rebase-autosquash.sh @@ -424,4 +424,18 @@ test_expect_success 'abort last squash' ' ! grep first actual ' +test_expect_success 'fixup a fixup' ' + echo 0to-fixup >file0 && + test_tick && + git commit -m "to-fixup" file0 && + test_tick && + git commit --squash HEAD -m X --allow-empty && + test_tick && + git commit --squash HEAD^ -m Y --allow-empty && + test_tick && + git commit -m "squash! $(git rev-parse HEAD^)" -m Z --allow-empty && + git rebase -ki --autosquash HEAD~4 && + test XZY = $(git show | tr -cd X-Z) +' + test_done base-commit: af6b65d45ef179ed52087e80cb089f6b2349f4ec -- gitgitgadget