From: Fabian Ruch <bafain@gmail.com>
To: git@vger.kernel.org
Cc: Michael Haggerty <mhagger@alum.mit.edu>,
Thomas Rast <tr@thomasrast.ch>, Jeff King <peff@peff.net>,
Chris Webb <chris@arachsys.com>
Subject: [PATCH v1] rebase --root: sentinel commit cloaks empty commits
Date: Wed, 16 Jul 2014 21:32:45 +0200 [thread overview]
Message-ID: <8d5cf2e1ff45e2e60072bf6c6e05371e4b265709.1405539123.git.bafain@gmail.com> (raw)
In-Reply-To: <53A18198.7070301@gmail.com>
git-rebase supports the option `--root` both with and without
`--onto`. In rebase root mode it replays all commits reachable from a
branch on top of another branch, including the very first commit. In
case `--onto` is not specified, that other branch is temporarily
provided by creating an empty commit. When the first commit on the
to-do list is being replayed, the so-called sentinel commit is
amended using the log message and patch of the replayed commit. Since
the sentinel commit is empty, this results in a replacement of the
sentinel commit with the new root commit of the rebased branch.
The combination of `--root` and no `--onto` implies an interactive
rebase. When `--preserve-merges` is not specified on the command
line, git-rebase--interactive uses `--cherry-pick` with git-rev-list
to put the initial to-do list together. The left side is given by the
fake base and the right side by the branch being rebased. What
happens now is that any empty commit on the original branch is
treated as a cherry-pick of the sentinel commit and subsequently
omitted from the to-do list. This is a bug if `--keep-empty` is
specified also.
Even without `--keep-empty`, using the sentinel commit as left side
with git-rev-list can result in a faulty rebased branch. Indeed, in
the unlikely case that the original branch consists solely of empty
commits, the bug crops up in the strangest fashion as all commits are
skipped and the sentinel commit is not replaced. As a result,
git-rebase produces a branch with a single empty commit.
To trigger the replacement of the sentinel commit, git-rebase assigns
the variable `squash_onto`. Special case a second time regarding
`squash_onto` and run git-rev-list without a left side if the
variable is assigned. The latter is the case if and only if `--root`
is used without `--onto`, that is `upstream` points to the sentinel
commit and `$upstream...$orig_head` would subtract a commit that is
not actually there from the original branch.
Fix a typo in `is_empty_commit`. It always found root commits
non-empty so that empty root commits were scheduled even without
`--keep-empty`. The POSIX specification states that command
substitutions are to be executed in sub-shells, which makes exit(1)
and variable assignments not affect the script execution state. That
was the reason why `ptree` was null for parentless commits and the
test `"$tree" = "$ptree"` always false for them.
Add tests.
Signed-off-by: Fabian Ruch <bafain@gmail.com>
---
Hi,
Three test cases were added to the bug report to account for the
additional cases in which the bug strikes (raised by Michael on the
other sub-thread). A bugfix is included now as well.
Concerning the bugfix: Obviously, the patch misuses the `squash_onto`
flag because it assumes that the new base is empty except for the
sentinel commit. The variable name does not imply anything close to
that. An additional flag to disable the use of the git-rev-list
option `--cherry-pick` would work and make sense again (for instance,
`keep_redundant`). However, the following two bugs, not related to
empty commits, seem to suggest that git-rebase--interactive cannot
work obliviously to non-existent bases.
1) git-rebase--interactive when used with `--root` and the to-do
list `noop` results in the original branch's history being
rewritten to contain only the sentinel commit.
git-rebase--interactive correctly checkouts `$onto` and replays
no commits on top of it but git-rebase has forgotten that `$onto`
was fake.
2) git-rebase--interactive when used with `--root` always creates a
fresh root commit, regardless of `--no-ff` being specified.
With the current meaning of `squash_onto`,
git-rebase--interactive cannot just reset the branch to the old
root commit. It is really the fault of git-rebase to start off
with a new commit.
Please take a closer look at the last two test cases that specify the
expected behaviour of rebasing a branch that tracks the empty tree.
At this point they expect the "Nothing to do" error (aborts with
untouched history). This is consistent with rebasing only empty
commits without `--root`, which also doesn't just delete them from
the history. Furthermore, I think the two alternatives adding a note
that all commits in the range were empty, and removing the empty
commits (thus making the branch empty) are better discussed in a
separate bug report.
Thanks for your time,
Fabian
git-rebase--interactive.sh | 20 ++++++++++++++-----
t/t3412-rebase-root.sh | 49 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 64 insertions(+), 5 deletions(-)
diff --git a/git-rebase--interactive.sh b/git-rebase--interactive.sh
index f267d8b..71ca0f0 100644
--- a/git-rebase--interactive.sh
+++ b/git-rebase--interactive.sh
@@ -201,10 +201,10 @@ has_action () {
}
is_empty_commit() {
- tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null ||
- die "$1: not a commit that can be picked")
- ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null ||
- ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904)
+ tree=$(git rev-parse -q --verify "$1"^{tree} 2>/dev/null) ||
+ die "$1: not a commit that can be picked"
+ ptree=$(git rev-parse -q --verify "$1"^^{tree} 2>/dev/null) ||
+ ptree=4b825dc642cb6eb9a060e54bf8d69288fbee4904
test "$tree" = "$ptree"
}
@@ -958,7 +958,17 @@ then
revisions=$upstream...$orig_head
shortrevisions=$shortupstream..$shorthead
else
- revisions=$onto...$orig_head
+ if test -n "$squash_onto"
+ then
+ # $onto points to an empty commit (the sentinel
+ # commit) which was not created by the user.
+ # Exclude it from the rev list to avoid skipping
+ # empty user commits prematurely, i. e. before
+ # --keep-empty can take effect.
+ revisions=$orig_head
+ else
+ revisions=$onto...$orig_head
+ fi
shortrevisions=$shorthead
fi
git rev-list $merges_option --pretty=oneline --abbrev-commit \
diff --git a/t/t3412-rebase-root.sh b/t/t3412-rebase-root.sh
index 0b52105..7c09efc 100755
--- a/t/t3412-rebase-root.sh
+++ b/t/t3412-rebase-root.sh
@@ -278,4 +278,53 @@ test_expect_success 'rebase -i -p --root with conflict (second part)' '
test_cmp expect-conflict-p out
'
+test_expect_success 'recreate empty commits with --keep-empty (root commit only)' '
+ # commit the empty tree, no parents
+ empty_tree=$(git hash-object -t tree /dev/null) &&
+ echo Empty\ root >expected.msg &&
+ empty_root_commit=$(git commit-tree $empty_tree -F expected.msg) &&
+ git checkout -b empty-root-commit $empty_root_commit &&
+ git rebase --root --keep-empty &&
+ git show -s --pretty=format:%s%n HEAD >actual.msg &&
+ test_cmp expected.msg actual.msg
+'
+
+test_expect_success 'recreate empty commits with --keep-empty (root commit with child)' '
+ git checkout -b empty-root-commit-with-child empty-root-commit &&
+ >file &&
+ git add file &&
+ git commit -m file &&
+ git rebase --root --keep-empty &&
+ git show -s --pretty=format:%s%n HEAD^ >actual.msg &&
+ test_cmp expected.msg actual.msg
+'
+
+test_expect_success 'recreate empty commits with --keep-empty (child commit)' '
+ git checkout -b empty-child-commit other &&
+ echo Empty\ child >expected.msg &&
+ git commit --allow-empty -F expected.msg &&
+ git rebase --root --keep-empty &&
+ git show -s --pretty=format:%s%n HEAD >actual.msg &&
+ test_cmp expected.msg actual.msg
+'
+
+test_expect_success 'abort if branch has solely empty commits without --keep-empty (single)' '
+ git checkout empty-root-commit &&
+ git rev-parse HEAD >expected.rev &&
+ test_must_fail git rebase --root &&
+ test_path_is_missing .git/rebase-merge &&
+ git rev-parse HEAD >actual.rev &&
+ test_cmp expected.rev actual.rev
+'
+
+test_expect_success 'abort if branch has solely empty commits without --keep-empty (many)' '
+ git checkout -b empty-commits-only empty-root-commit &&
+ git commit --allow-empty -m Child &&
+ git rev-parse HEAD >expected.rev &&
+ test_must_fail git rebase --root &&
+ test_path_is_missing .git/rebase-merge &&
+ git rev-parse HEAD >actual.rev &&
+ test_cmp expected.rev actual.rev
+'
+
test_done
--
2.0.1
next prev parent reply other threads:[~2014-07-16 19:33 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-06-18 12:10 [RFC] rebase --root: Empty root commit is replaced with sentinel Fabian Ruch
2014-06-19 11:35 ` Michael Haggerty
2014-06-19 12:39 ` Fabian Ruch
2014-06-19 13:08 ` Michael Haggerty
2014-07-16 19:32 ` Fabian Ruch [this message]
2014-07-18 12:10 ` [PATCH v1] rebase --root: sentinel commit cloaks empty commits Thomas Rast
2014-07-20 20:52 ` Chris Webb
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=8d5cf2e1ff45e2e60072bf6c6e05371e4b265709.1405539123.git.bafain@gmail.com \
--to=bafain@gmail.com \
--cc=chris@arachsys.com \
--cc=git@vger.kernel.org \
--cc=mhagger@alum.mit.edu \
--cc=peff@peff.net \
--cc=tr@thomasrast.ch \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).