Re: [RFC PATCH] add t3420-rebase-topology

2012-09-29 Thread Chris Webb
Martin von Zweigbergk martinv...@gmail.com writes:

 For consistency, it seems like git rebase -p --root should always be
 a no-op, while git rebase [-i/-m] --root should be no-op if the
 history has no merges. Also, since git rebase -i tries to
 fast-forward through existing commits, it seems like git rebase -i
 --root should ideally not create a sentinel commit, but instead stop
 at the first commit marked for editing.
 
 If, OTOH, --force-rebase is given, we should rewrite history from the
 first commit, which in the case of --root would mean creating a
 sentinel commit.
 
 So, in short, I have a feeling that the sentinel commit should be
 created if and only if both --root and --force-rebase (but not --onto)
 are given.
[...]
 So I'm getting more and more convinced that the sentinel commit should
 only be created if --force-rebase was given. Let me know if I'm
 missing something.

No, that sounds fairly convincing to me. Personally, the only behaviour I
want to be able to get at without --force-rebase is for rebase -i --root to
allow the root commit to be dropped, edited or reworded, and commits to
reordered to make a different one the root, in the same way as normal
interactive rebase does for later commits.

The least intrusive implementation for rebase --root was the unconditional
sentinel, but as you say, you don't need it (and it's a bit surprising) if
the root commit on the instruction sheet is the same as the original root:
in the edit/reword case, you could just checkout the existing root and then
drop out in the normal way. You only really need the sentinel to deal with
the possibility of a conflict if the root is replaced with a different
commit.

I think I agree with you it would be better only to create the sentinel on
demand when it's required or if --force is given.

Cheers,

Chris.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [RFC PATCH] add t3420-rebase-topology

2012-09-28 Thread Martin von Zweigbergk
On Thu, Sep 27, 2012 at 5:20 AM, Chris Webb ch...@arachsys.com wrote:
 You're right that rebase --root without --onto always creates a brand new
 root as a result of the implementation using a sentinel commit. Clearly this
 is what's wanted with --interactive

That's not as clear as one might first think. git rebase -i actually
honors --force; if one marks a commit for edit, but then --continue
without making any changes, the next commit(s) will be fast-forwarded.
See the first few lines of pick_one (or pick_one_preserving_merges).

 but rebase --root with neither --onto
 nor --interactive is a slightly odd combination for which I struggle to
 imagine a natural use. Perhaps you're right that for consistency it should
 be a no-op unless --force-rebase is given?

 If we did this, this combination would be a no-op unconditionally as by
 definition we're always descended from the root of our current commit.

For consistency, it seems like git rebase -p --root should always be
a no-op, while git rebase [-i/-m] --root should be no-op if the
history has no merges. Also, since git rebase -i tries to
fast-forward through existing commits, it seems like git rebase -i
--root should ideally not create a sentinel commit, but instead stop
at the first commit marked for editing.

If, OTOH, --force-rebase is given, we should rewrite history from the
first commit, which in the case of --root would mean creating a
sentinel commit.

So, in short, I have a feeling that the sentinel commit should be
created if and only if both --root and --force-rebase (but not --onto)
are given.

 However, given the not-very-useful behaviour, I suspect that rebase --root
 is much more likely to be a mistyped version of rebase -i --root than rebase
 --root --force-rebase. (Unless I'm missing a reasonable use for this?
 History linearisation perhaps?)

Yes, the not-very-useful-ness of this is the clear argument against
making them no-ops. But I have to say I was slightly surprised when I
tried git rebase --root for the first time and it created completely
new history for me. As you say, git rebase --root is probably often
a mistyped git rebase -i --root, and if that is the case, it seems
nicer (in addition to being more consistent) if we don't do anything
rather than rewriting the history. The history rewriting might even go
unnoticed and come as an unpleasant surprise later.

When working on a new project, git rebase -i --root might even be a
convenient replacement for git rebase -i initial commit even when
one does not want to rewrite the initial commit itself, and in such a
case, the user would clearly not want a sentinel commit either.

So I'm getting more and more convinced that the sentinel commit should
only be created if --force-rebase was given. Let me know if I'm
missing something.

Martin
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [RFC PATCH] add t3420-rebase-topology

2012-09-27 Thread Chris Webb
Martin von Zweigbergk martinv...@gmail.com writes:

 On Tue, Sep 18, 2012 at 12:53 AM, Johannes Sixt j.s...@viscovery.net wrote:

  Why? Is it more like --root implies --force?
 
 It doesn't currently exactly imply --force, but the effect is the
 same. Also see my reply to Junio's email in this thread.
 
 Maybe Chris has some thoughts on this?

Hi Martin and Johannes. Sorry for the slow follow-up here.

You're right that rebase --root without --onto always creates a brand new
root as a result of the implementation using a sentinel commit. Clearly this
is what's wanted with --interactive, but rebase --root with neither --onto
nor --interactive is a slightly odd combination for which I struggle to
imagine a natural use. Perhaps you're right that for consistency it should
be a no-op unless --force-rebase is given?

If we did this, this combination would be a no-op unconditionally as by
definition we're always descended from the root of our current commit.
However, given the not-very-useful behaviour, I suspect that rebase --root
is much more likely to be a mistyped version of rebase -i --root than rebase
--root --force-rebase. (Unless I'm missing a reasonable use for this?
History linearisation perhaps?)

Best wishes,

Chris.
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html


Re: [RFC PATCH] add t3420-rebase-topology

2012-09-26 Thread Martin von Zweigbergk
[+Chris Webb regarding git rebase --root]

First of all, thanks for a meticulous review!

On Tue, Sep 18, 2012 at 12:53 AM, Johannes Sixt j.s...@viscovery.net wrote:
 Am 9/18/2012 8:31, schrieb Martin von Zweigbergk:

 Since here and in the following tests the test cases and test descriptions
 vary in the same way, wouldn't it make sense to factor the description out
 as well?

Definitely. I just couldn't think of a good way of doing it, so thanks
for great and concrete suggestions!

 (Watch your quoting, though.)

Switched to putting the test body in double quotes as you suggested in
your examples and used single quotes for strings within the test body.

 +run () {
 +echo '
 + reset 
 + git rebase '$@' --keep-empty p h 
 + test_range p.. f g h
 +'
 +}
 +test_expect_success 'rebase --keep-empty keeps empty even if already in 
 upstream' $(run)
 +test_expect_failure 'rebase -m --keep-empty keeps empty even if already in 
 upstream' $(run -m)
 +test_expect_failure 'rebase -i --keep-empty keeps empty even if already in 
 upstream' $(run -i)
 +test_expect_failure 'rebase -p --keep-empty keeps empty even if already in 
 upstream' $(run -p)

 is in upstream is decided by the patch text. If an empty commit is
 already in upstream, this adds another one with the same or a different
 commit message and authorship information. Dubious, but since it is
 opt-in, it should be OK.

Yes, it is a little dubious. See
http://thread.gmane.org/gmane.comp.version-control.git/203097/focus=203159
and Junio's answer, which I think makes sense.

 +run () {
 +echo '
 + reset 
 + git rebase '$@' j w 
 + test_range j.. E n H || test_range j.. n H E
 +'

 Chaining tests with || is dangerous: you do not know whether the first
 failed because the condition is not satisfied or because of some other
 failure.

Good point. Thanks.

 Why is this needed in the first place? Shouldn't the history be
 deterministic, provided that the commit timestamps are all distinct?

It may be deterministic, but it's not specified, I think, so I didn't
want to depend on the order. Thinking more about it, though, I think
it's good to protect the current behavior from patches that change the
order of the parents. Although it may not be incorrect to change the
order, it would at least protect against accidental changes.

It turns out that rebase -i goes through the commits in
--topo-order, while the others use default order, I think. Which
flavor should pass the test case and which should fail (and be fixed)?
I would personally prefer to say that rebase -i is correct in using
--topo-order and that the others should be fixed. Again, it's not
specified, but I would hate to have them behave differently.

 +run () {
 +echo '
 + reset 
 + git rebase '$@' --root c 
 + ! same_revision HEAD c 
 + test_range c a b c
 +'
 +}
 +test_expect_success 'rebase --root is not a no-op' $(run)
 +test_expect_success 'rebase -m --root is not a no-op' $(run -m)
 +test_expect_success 'rebase -i --root is not a no-op' $(run -i)
 +test_expect_success 'rebase -p --root is not a no-op' $(run -p)

 Why? Is it more like --root implies --force?

It doesn't currently exactly imply --force, but the effect is the
same. Also see my reply to Junio's email in this thread.

Maybe Chris has some thoughts on this?

 +run () {
 +echo '
 + reset 
 + git rebase '$@' --root --onto e y 
 + test_range e.. x y
 +'
 +}
 +test_expect_success 'rebase --root --onto' $(run)
 +test_expect_failure 'rebase -m --root --onto' $(run -m)
 +test_expect_success 'rebase -i --root --onto' $(run -i)
 +test_expect_success 'rebase -p --root --onto' $(run -p)

 Where does this rebase start? Ah, --root stands in for the upstream
 argument, hence, y is the tip to rebase. Right? Then it makes sense.

Thanks for pointing that out. I changed the order to git rebase
--onto e --root y. I hope that makes it clearer.

 +test_expect_success 'rebase -p re-creates merge from upstream' '
 + reset 
 + git rebase -p k w 
 + same_revision HEAD^ H 
 + same_revision HEAD^2 k
 +'

 IMO, this tests the wrong thing. You have this history:

  ---j---E---k
  \   \
   n---H---w

 where E is the second parent of w. What does it mean to rebase w onto k?
 IMO, it is a meaningless operation, and the outcome is irrelevant.

 It would make sense to test that this history results after the upstream
 at H moved forward:

  ---j---E---k
  \   \
   n---H   \
\   \
 z---w'

 That is, w began a topic by mergeing the sidebranch E; then upstream
 advanced to z, and now you rebase the topic to the new upstream.

Fair enough. Changed accordingly.

 +test_expect_success 'rebase -p re-creates internal merge' '
 + reset 
 + git rebase -p c w 
 + test_revisions f j n E H w HEAD~4 HEAD~3 HEAD~2 HEAD^2 HEAD^ HEAD

 You must also test for c; otherwise the test would succeed if rebase did
 nothing at all.

 This comment applies to all 

Re: [RFC PATCH] add t3420-rebase-topology

2012-09-21 Thread Martin von Zweigbergk
On Tue, Sep 18, 2012 at 12:51 AM, Junio C Hamano gits...@pobox.com wrote:
 Martin von Zweigbergk martinv...@gmail.com writes:

 do you agree
 that 'rebase --onto does not re-apply patches in onto' is desirable?

 This depends on how you look at --onto.  Recall the most typical and
 the original use case of rebase:

A'--C' your rebased work
   /
   ---o---o---o---F---B'--o---T master
  ^\
  v1.7.12   A---B---C your work

 You could view git rebase master as a short-hand for

 $ git rebase --onto $(git merge-base master HEAD) master

Exactly. I frequently consider it a short-hand for that. It might
be worth pointing out that 'git pull --rebase', which might be
one of the most frequent uses of rebase, internally often does

  git rebase --onto upstream upstream@{...} branch

where upstream@{...} is the most recent upstream that is an
ancestor of branch. For example, if your work is based on
origin/pu and you send the bottom-most patch (B in the figure
below) to the maintainer and and it gets applied to
pu. Running git pull --rebase would then lead to
git rebase --onto T A. You would want this to drop B.

   C' your rebased work
  /
  ---o---o---o---F---B'--o---T origin/pu
  \
   A origin/pu@{1}
\
 B---C your work


 The intended use case for --onto, however, is primarily to replay
 a history to older base, i.e. [...] a moral equivalent of

 $ git checkout v1.7.12
 $ git cherry-pick A B C ;# or git cherry-pick master..HEAD

Yes, this is the alternative way of looking it at and exactly why
I, too, was not sure how it should behave.

 You could argue that you can compute the patch equivalence between
 the commits in onto..master and commits in master..HEAD and
 filter out the equivalent commits

I'm not sure if you meant master..onto rather
than onto..master. Rebase (well, all flavors of rebase
but -m) currently drops patches from master..HEAD that are
also in HEAD..master. This is what the rebase --onto does not
lose patches in upstream test is about. It is also one of the
main problems that I try to fix in my long-stalled rebase-range
series. I think we should drop patches in master..HEAD that are
also in HEAD..onto (which is almost the same
as master..onto).

 The replay to an updated base case (i.e. without --onto)

Or _with_ --onto as in the above example from git pull --rebase.

 On the other hand, when the user replays to an older base, she has
 some idea what constitutes a series that she is replaying (i.e.
 $(git merge-base master HEAD)..HEAD).  It smells to go against the
 user's world model if the command silently filtered commits by patch
 equivalence.

If it's truly about rebasing onto an older base, there can't
possibly be any patches in HEAD..onto, so assuming you agree
with my reasoning above that those are the patches we should
drop, rebasing onto older history would be safe.

 Besides, the whole point of a separate onto is to allow the user
 to specify a commit that does not have a straightforward ancestry
 relationship with the bottom of the series (i.e. either master or
 F), and computation of patch equivalence is expected to be much
 higher.  Given that it is unlikely to find any match, it feels
 doubly wrong to always run git cherry equivalent in that case.

Yes, this was my only concern (apart from it possibly being
conceptually wrong to do, depending on what the user meant by
issuing the command).

 How about 'rebase --root is not a no-op'?

   ---o---o---o---F---B'--o---T master
  ^\
  v1.7.12   A---B---C your work

 If git rebase F when you are at C in the above illustration
 (reproduced only the relevant parts) is a no-op (and I think it
 should be), git rebase --root in the illustration below ought to
 be as well, no?

  F---B'--o---T master
   \
A---B---C your work

Yeah, that's what I thought as well at first. I think my test
case even started out as rebase --root _is_ a no-op.

When thinking about how to handle roots in general, I often
imagine a single virtual root commit (parent of all initial
commits), and that reasoning also implies that git rebase
--root should be a no-op. Then I saw that the test case
failed (or perhaps I remembered how it is implemented with the
clever fake root/initial commit) and started thinking about why
anyone would use git rebase --root if it was a no-op. I could
only think of using it to linearize history, but that doesn't
seem like a very likely use case. So it seems like weighing
purity/correctness against usefulness to me. I'm not sure which
way to go.

Thanks for quick and detailed feedback on an RFC patch.

Martin
--
To unsubscribe from this list: send the line unsubscribe git in
the body of a message to majord...@vger.kernel.org
More 

[RFC PATCH] add t3420-rebase-topology

2012-09-18 Thread Martin von Zweigbergk
Add more test cases to check that the topology after a rebase is as
expected. Conflicts are not considered, but patch-equivalence is.
---

Tests pass and fail as indicated by the suffix
(_success/_failure). Your input especially appreciated on whether you
agree with the intent of the test cases. For example, do you agree
that 'rebase --onto does not re-apply patches in onto' is desirable?
And if you do, then do you also agree that 'rebase --root --onto
ignores patch in onto' is desirable? How about 'rebase --root is not a
no-op'? One might think that --force would be necessary, but on the
other hand, if that was the case, the only point (AFAICT) of git
rebase --root branch without --force would be to linearize history,
so I instead made the test case confirm that --root without --onto
effectively behaves as if --force was also passed.

Feedback on the structure/setup and style is of course also
appreciated.

 t/t3420-rebase-topology.sh | 348 +
 1 file changed, 348 insertions(+)
 create mode 100755 t/t3420-rebase-topology.sh

diff --git a/t/t3420-rebase-topology.sh b/t/t3420-rebase-topology.sh
new file mode 100755
index 000..024a2b4
--- /dev/null
+++ b/t/t3420-rebase-topology.sh
@@ -0,0 +1,348 @@
+#!/bin/sh
+
+test_description='effect of rebase on topology'
+. ./test-lib.sh
+
+
+#   q---C---r
+#  /
+# a---b---c---d!--e---p
+#  \
+#   f---g!--h
+#\
+# j---E---k
+#  \   \
+#   n---H---w
+#
+# x---y---B
+#
+#
+# ! = empty
+# uppercase = cherry-picked
+# p = reverted e
+#
+# TODO:
+# prune graph to what's needed
+
+empty () {
+   git commit --allow-empty -m $1 
+   git tag $1
+}
+
+cherry_pick () {
+   git cherry-pick -n $1 
+   git commit -m $2 
+   git tag $2
+}
+
+revert () {
+   git revert -n $1 
+   git commit -m $2 
+   git tag $2
+}
+
+
+test_expect_success 'setup' '
+   test_commit a 
+   test_commit b 
+   test_commit c 
+   empty d 
+   test_commit e 
+   revert e p 
+   git checkout b 
+   test_commit f 
+   empty g 
+   test_commit h 
+   git checkout f 
+   test_commit j 
+   cherry_pick e E 
+   test_commit k 
+   git checkout j 
+   test_commit n 
+   cherry_pick h H 
+   git merge -m w E 
+   git tag w 
+   git checkout b 
+   test_commit q 
+   cherry_pick c C 
+   test_commit r 
+   git checkout --orphan disjoint 
+   git rm -rf . 
+   test_commit x 
+   test_commit y 
+   cherry_pick b B
+'
+
+reset () {
+   git rebase --abort
+   git reset --hard
+}
+
+test_range () {
+   test $(git log --reverse --topo-order --format=%s $1 | xargs) = $2
+}
+
+test_revisions () {
+   expected=$1
+   shift
+   test $(git log --format=%s --no-walk=unsorted $@ | xargs) = 
$expected
+}
+
+same_revision () {
+   test $(git rev-parse $1) = $(git rev-parse $2)
+}
+
+# the following 5 (?) tests copy t3400 tests, but check the history rather 
than status code and/or stdout
+run () {
+echo '
+   reset 
+   git rebase '$@' c j 
+   same_revision HEAD~2 c 
+   test_range c.. f j
+'
+}
+test_expect_success 'simple rebase' $(run)
+test_expect_success 'simple rebase -m' $(run -m)
+test_expect_success 'simple rebase -i' $(run -i)
+test_expect_success 'simple rebase -p' $(run -p)
+
+run () {
+echo '
+   reset 
+   git rebase '$@' b j 
+   same_revision HEAD j
+'
+}
+test_expect_success 'rebase is no-op if upstream is an ancestor' $(run)
+test_expect_success 'rebase -m is no-op if upstream is an ancestor' $(run -m)
+test_expect_success 'rebase -i is no-op if upstream is an ancestor' $(run -i)
+test_expect_success 'rebase -p is no-op if upstream is an ancestor' $(run -p)
+
+run () {
+echo '
+   reset 
+   git rebase '$@' --force b j 
+   ! same_revision HEAD j 
+   test_range b.. f j
+'
+}
+test_expect_success 'rebase --force' $(run)
+test_expect_success 'rebase -m --force' $(run -m)
+test_expect_success 'rebase -i --force' $(run -i)
+test_expect_failure 'rebase -p --force' $(run -p)
+
+run () {
+echo '
+   reset 
+   git rebase '$@' j b 
+   same_revision HEAD j
+'
+}
+test_expect_success 'rebase fast-forwards if an ancestor of upstream' $(run)
+test_expect_success 'rebase -m fast-forwards if an ancestor of upstream' 
$(run -m)
+test_expect_success 'rebase -i fast-forwards if an ancestor of upstream' 
$(run -i)
+test_expect_success 'rebase -p fast-forwards if an ancestor of upstream' 
$(run -p)
+
+run () {
+echo '
+   reset 
+   git rebase '$@' p k 
+   test_range p.. f j k
+'
+}
+test_expect_success 'rebase ignores patch in upstream' $(run)
+test_expect_failure 'rebase -m ignores patch in upstream' $(run -m)
+test_expect_success 'rebase -i ignores patch in upstream' $(run -i)
+test_expect_success 'rebase -p ignores patch in upstream' $(run -p)
+
+run () {
+echo '
+   reset 
+   

Re: [RFC PATCH] add t3420-rebase-topology

2012-09-18 Thread Johannes Sixt
Am 9/18/2012 8:31, schrieb Martin von Zweigbergk:
 Add more test cases to check that the topology after a rebase is as
 expected. Conflicts are not considered, but patch-equivalence is.
 ---
 
 Tests pass and fail as indicated by the suffix
 (_success/_failure). Your input especially appreciated on whether you
 agree with the intent of the test cases. For example, do you agree
 that 'rebase --onto does not re-apply patches in onto' is desirable?
 And if you do, then do you also agree that 'rebase --root --onto
 ignores patch in onto' is desirable? How about 'rebase --root is not a
 no-op'? One might think that --force would be necessary, but on the
 other hand, if that was the case, the only point (AFAICT) of git
 rebase --root branch without --force would be to linearize history,
 so I instead made the test case confirm that --root without --onto
 effectively behaves as if --force was also passed.
 
 Feedback on the structure/setup and style is of course also
 appreciated.
 
  t/t3420-rebase-topology.sh | 348 
 +
  1 file changed, 348 insertions(+)
  create mode 100755 t/t3420-rebase-topology.sh
 
 diff --git a/t/t3420-rebase-topology.sh b/t/t3420-rebase-topology.sh
 new file mode 100755
 index 000..024a2b4
 --- /dev/null
 +++ b/t/t3420-rebase-topology.sh
 @@ -0,0 +1,348 @@
 +#!/bin/sh
 +
 +test_description='effect of rebase on topology'
 +. ./test-lib.sh
 +
 +
 +#   q---C---r
 +#  /
 +# a---b---c---d!--e---p
 +#  \
 +#   f---g!--h
 +#\
 +# j---E---k
 +#  \   \
 +#   n---H---w
 +#
 +# x---y---B
 +#
 +#
 +# ! = empty
 +# uppercase = cherry-picked
 +# p = reverted e
 +#
 +# TODO:
 +# prune graph to what's needed
 +
 +empty () {

Is it is_empty or make_empty/empty_commit?

 + git commit --allow-empty -m $1 
 + git tag $1
 +}

Obviously the latter.

 +
 +cherry_pick () {
 + git cherry-pick -n $1 
 + git commit -m $2 
 + git tag $2
 +}
 +
 +revert () {
 + git revert -n $1 
 + git commit -m $2 
 + git tag $2
 +}
 +
 +
 +test_expect_success 'setup' '
 + test_commit a 
 + test_commit b 
 + test_commit c 
 + empty d 
 + test_commit e 
 + revert e p 
 + git checkout b 
 + test_commit f 
 + empty g 
 + test_commit h 
 + git checkout f 
 + test_commit j 
 + cherry_pick e E 
 + test_commit k 
 + git checkout j 
 + test_commit n 
 + cherry_pick h H 
 + git merge -m w E 
 + git tag w 
 + git checkout b 
 + test_commit q 
 + cherry_pick c C 
 + test_commit r 
 + git checkout --orphan disjoint 
 + git rm -rf . 
 + test_commit x 
 + test_commit y 
 + cherry_pick b B
 +'
 +
 +reset () {
 + git rebase --abort
 + git reset --hard
 +}

The 'rebase --abort' can fail, but there is no  chain. Good. Using this
function instead of the individual commands in the tests does not break
the  chain there. Good.

These following three functions should be and can be consistent with the
order of their arguments: expected actual.

 +test_range () {
 + test $(git log --reverse --topo-order --format=%s $1 | xargs) = $2

expected=$1
set -- $(git log --reverse --topo-order --format=%s $2)
test expected = $*

 +}
 +
 +test_revisions () {
 + expected=$1
 + shift
 + test $(git log --format=%s --no-walk=unsorted $@ | xargs) = 
 $expected

set -- $(git log --format=%s --no-walk=unsorted $@)
test expected = $*

 +}
 +
 +same_revision () {
 + test $(git rev-parse $1) = $(git rev-parse $2)
 +}

'test_same_revision'?

 +
 +# the following 5 (?) tests copy t3400 tests, but check the history rather 
 than status code and/or stdout
 +run () {
 +echo '
 + reset 
 + git rebase '$@' c j 
 + same_revision HEAD~2 c 
 + test_range c.. f j
 +'
 +}
 +test_expect_success 'simple rebase' $(run)
 +test_expect_success 'simple rebase -m' $(run -m)
 +test_expect_success 'simple rebase -i' $(run -i)
 +test_expect_success 'simple rebase -p' $(run -p)

Since here and in the following tests the test cases and test descriptions
vary in the same way, wouldn't it make sense to factor the description out
as well?

test_run_rebase () {
test_expect_success simple rebase $* 
reset 
git rebase $* c j 
same_revision HEAD~2 c 
test_range c.. 'f j'

}

test_run_rebase ''
test_run_rebase -m
test_run_rebase -i
test_run_rebase -p

(Watch your quoting, though.)

Oh, I see: some tests expect failure. How about:

test_run_rebase () {
result=$1
shift
test_expect_$result simple rebase $* ...
}
test_run_rebase success ''
etc.

 +
 +run () {
 +echo '
 + reset 
 + git rebase '$@' b j 
 + same_revision HEAD j
 +'
 +}
 +test_expect_success 'rebase is no-op if upstream is an ancestor' $(run)
 +test_expect_success 'rebase -m is no-op if upstream is an