Add --revisions flag to rebase, so that it can be used to apply an arbitrary range of commits on top of a current branch. Signed-off-by: Michael S. Tsirkin <mst@redhat.com> --- I've been wishing for this functionality for a while now, so here goes. This isn't yet properly documented and I didn't write a test, but the patch seems to work fine for me. Any early flames/feedback? git-rebase.sh | 36 ++++++++++++++++++++++++------------ 1 files changed, 24 insertions(+), 12 deletions(-) diff --git a/git-rebase.sh b/git-rebase.sh index b121f45..d99d04b 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -3,12 +3,13 @@ # Copyright (c) 2005 Junio C Hamano. # -USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--onto <newbase>] [<upstream>|--root] [<branch>] [--quiet | -q]' +USAGE='[--interactive | -i] [-v] [--force-rebase | -f] [--onto <newbase>] [--revisions <revision range>] [<upstream>|--root] [<branch>] [--quiet | -q]' LONG_USAGE='git-rebase replaces <branch> with a new branch of the same name. When the --onto option is provided the new branch starts out with a HEAD equal to <newbase>, otherwise it is equal to <upstream> It then attempts to create a new commit for each commit from the original -<branch> that does not exist in the <upstream> branch. +<branch> that does not exist in the <upstream> branch, or for +each commit matching <revision range> when the --revisions options is provided. It is possible that a merge failure will prevent this process from being completely automatic. You will have to resolve any such merge failure @@ -41,6 +42,7 @@ If you would prefer to skip this patch, instead run \"git rebase --skip\". To restore the original branch and stop rebasing run \"git rebase --abort\". " unset newbase +unset revisions strategy=recursive do_merge= dotest="$GIT_DIR"/rebase-merge @@ -291,6 +293,11 @@ do newbase="$2" shift ;; + --revisions) + test 2 -le "$#" || usage + revisions="$2" + shift + ;; ...
This pretty much reverses what rebase normally does. Instead of "rebase this onto that" it's "'rebase' that onto this". And instead of updating the branch head that got rebased, the, uhm, "upstream" gets updated. Also, AFAICT this needs to be called like this: git rebase --revisions foo..bar HEAD Changing the meaning of the <upstream> argument and relying on the fact that <newbase> defaults to <upstream>. If such a thing gets added, it should rather work like --root, not using <upstream> at all, but --onto <newbase> only. Maybe defaulting to HEAD for <newbase> and making --onto optional, as it's reversed WRT what it does compared to the usual rebase. But generally, I'd say it would be better to add such a range feature to cherry-pick than abusing rebase for that. Björn --
Sorry, I had trouble parsing the above. Could you suggest e.g. how the The reason to use rebase is that I often want to combine this with -i flag, editing patches as they are applied. -- MST --
Current: git rebase [-i | --interactive] [options] [--onto <newbase>] <upstream> [<branch>] git rebase [-i | --interactive] [options] --onto <newbase> --root [<branch>] Add: git rebase [-i | --interactive] [options] --revisions <range> [<branch>] (Thinking about it, I guess an explicit --onto makes no sense with the Hm, well, your patch didn't touch git-rebase--interactive.sh ;-) Björn --
I agree. So this is different from what I implemented basically only in that Ah, I was wondering why it doesn't work :) --
It also drops <upstream>, because that makes no sense with --revisions. So the only mandatory argument is the revision range. Björn --
The last sentence is wrong I think - it is still the branch head that is updated. --
Yes, that's the who point of the patch. The above applies a single So this 1. won't be very useful, as you show it is easy to achieve with existing commands. 2. interprets "foo" as branch name as opposed to revision range. OTOH, rebase --revisions as I implemented is a "smarter cherry-pick" which can't easily be achieved with existing commands, especially if you add "-i". -- MST --
Yes, and it's "backwards" compared to the existing "rebase" modes, but
Hm, no. I expected it to turn all commits reachable from foo into
patches and applying them to bar. But actually, that should hit the
special <since> mode of format-patch. So
git rebase --revisions foo bar
is (with your patch) actually the same as
git rebase foo bar
So actually the example should have been:
git rebase --root --revisions foo bar
Both invocations probably mess up the diff-stat as that becomes:
git diff --stat --summary foo
So it creates a diffstat of the diff from the working tree to "foo",
One can "almost" achieve it.
git rebase --revision A..B foo
is about the same as:
git checkout foo
git reset --hard B
git rebase --onto ORIG_HEAD A
But:
a) The "reset --hard" obviously lacks the safety checks for clean
index/working tree.
b) "git rebase --abort" won't take you back to your initial state, but
to B.
c) It's not really obvious that you can do it and how to do it.
Another possibility would be:
git checkout B^0 # detach HEAD at B
git rebase foo # rebase onto foo
git checkout foo
git merge HEAD@{1} # Fast-forwards foo to the rebased stuff
That fixes a), avoid b) [because you don't mess up any branch head
early] but is still subject to c).
And for both methods, the ORIG_HEAD and HEAD@{1} arguments are somewhat
"unstable", e.g. checking out the wrong branch head first, and only then
the correct one, you'd have to use HEAD@{2} instead of HEAD@{1} (because
the reflog for HEAD got a new entry).
So you can already do what you want to do, but wrapping it in a single
porcelain might still be useful because it's obviously a lot easier and
safer that way. That said, I wonder what kind of workflow you're using
though, and why you require that feature. I've never needed something
Well, a single committish is a "range" as far as the range-based
commands are concerned, e.g. "git log master" treats "master" to mean
all commits reachable it. If "rebase --revisions master" ...I need this often for many reasons: - Imagine developing a patchset with a complex bugfix on master branch. Then I decide to also apply (backport) this patchset to stable branch. - Imagine developing a bugfix/feature patchset on master branch. Then I decide the patchset is too large/unsafe and want to switch it to staging branch. - I have a large queue of patches on staging branch, I decide that a range of patches is mature enough for master. And I often need -i to inspec/edit patches while doing this, even though I can rebase -i later, but that would mean Well, implemenation-wise, teaching cherry-pick about multiple commits seems very hard to me. We would need to teach it about all the flags that rebase has to patch queue management. So I can't implement it. Can you? -- MST --
Hm, I'd also imagine that you want a separate branch then, and not directly mess up the stable branch, so I'd do: git branch foo-stable foo # Create a branch for the backport git rebase --onto stable master foo-stable # Backport Now you got your backported version and can merge it to "stable". Common wisdom is do things the other way around though. Create the bugfix for the oldest branch that it applies to, then merge it forward, either doing: "bugfix -> stable" and "stable -> master" merges, or "bugfix -> stable" and "bugfix -> master" merges. That approach has the advantage that you don't get multiple commits doing the same thing, which you get with rebasing/cherry-picking. Hm, so you have a topic branch "foo" based upon master, but it's too experimental so you don't want to merge it to master, but "staging". I don't see why you even have to rebase it then. "staging" is likely ahead of master, so the merge base of "foo" and "master" is also reachable through "staging", and simply merging "foo" to "staging" should work Basically, same deal as with the first two cases. If the series is directly on "staging" (i.e. you didn't create a topic branch), you can create one now: git branch foo $last_commit_for_foo git rebase --onto master $first_commit_for_foo^ foo And you got your backported topic branch for "foo". Or you already have a topic branch "foo-staging", but it's based upon some commit only in "staging" but not in "master", so a plain merge would mess things up. Same deal as with backporting from "master" to "stable" And in this case it's also true that basing the topic branches on "master" instead of "staging" makes things easier. That way, you can merge to either "staging" or "master" without any ill-effects. Björn --
Well, directly working with a stable branch is easier, so yes, I want to mess it up: this is just my local tree, if anything The annoying thing is that merge step. I can create a merge commit if I mistype things, and I do not want any I know. The advantage of making all changes to master first is that this way a change gets more review and testing before being applied to stable. Further, often different people Yes but I want my development history to be linear, Yes, I understand that creating a temporary branch and checking it out then merging it will make rebase do what I want. The only disadvantage is that I need to remember where I am in the As above, I do not want merges. --
It also allows making use of rebase's rather extensive machinery for dealing with conflicts (e.g., rebase --continue / --skip / --abort). But it would make more sense to have it in cherry-pick... (cherry-pick --continue / --skip / --abort...) -Miles -- Consult, v.i. To seek another's disapproval of a course already decided on. --
But you don't rebase the branch head. Before the rebase, the branch head doesn't reference the commits that get rebased. For example: git checkout bar git rebase --revisions foo bar You "rebase" the commits in foo's history, but you update bar. WRT the result, the above command should be equivalent to: git checkout bar git reset --hard foo git rebase --root --onto ORIG_HEAD; And here, the commits currently reachable through "bar" are rebased, and "bar" also gets updated. Björn --
Many people wanted to have "pick many commits onto the current HEAD" and I think it would be a natural, uncontroversial and welcome addition to allow "git cherry-pick A..B". In fact, historically, people who wanted to have "pick many commits" complained that the "rebase" interface was backwards, because it works in the _wrong_ direction for _their_ usecase. Of course, when you _are_ rebasing a branch on top of some other branch, the way "rebase" currently works is the _right_ direction. But I think it is a reasonable thing to _implement_ the feature to range-pick commits reusing the sequencing logic already in "rebase" and "rebase -i". That essentially is what we wanted to do with "git sequencer" that would be a sequencing logic backend shared among rebase, cherry-pick, and perhaps am. So perhaps a good way to move forward is to teach "git cherry-pick A..B" to be a thin wrapper that invokes a new hidden mode of operation added to "rebase" that is not advertised to the end user. I would suggest calling the option to invoke that hidden mode not "--revisions", but "--reverse" or "--opposite" or something of that nature, though. It makes "rebase" work in different direction. --
Heya, Speaking of which, what's the status of git sequencer? I seem to remember some activity recently to slowly rewrite git rebase in c, but I haven't seen anything since then. Is it still moving forward? Is anyone interested in doing so? Just curious... -- Cheers, Sverre Rabbelier --
Hi, Last June and July, I sent some patch series to port "rebase -i" to C using code from the sequencer project. My goal was to save some interesting code from the sequencer GSoC 2008 project and at the same time to move forward "rebase -i" code toward a sequencer. But Dscho and Junio didn't like the fact that the code from the sequencer I added was duplicating existing code and was not properly refactored, though it also added things that would be needed later for the sequencer. My plan was to refactor later, once I had a sequencer, but Junio and Dscho did not like that plan. They said it would be a too big maintenance burden. So I agreed to not duplicate any existing code and to properly refactor everything. And I have been trying to take interesting and useful code from the sequencer project and to integrate it into existing commands. And this is why I sent yesterday the 4th version of my '"git reset --merge" related improvements' patch series. Best regards, Christian. --
After that I plan to work on cherry-pick and that could be useful to implement something like "git cherry-pick A..B". See patch 11/15 from Stephan Beyer in my last "port rebase -i to C" series: http://thread.gmane.org/gmane.comp.version-control.git/127256/focus=127259 Regards, Christian. --
Heya, On Wed, Dec 9, 2009 at 07:52, Christian Couder <chriscool@tuxfamily.org> wrote: Ah, okay, thanks for the update! -- Cheers, Sverre Rabbelier --
Or even "git cherry-pick branch", as I naïvely tried doing before I understood what it did. This is definitely a feature that would help me. The question of where it goes is actually a bit difficult, it is the same mode of operation as "git rebase", only the other way around. It is the same as "git cherry-pick", but called multiple times. And it is the same as "git merge --squash", but without squashing the commits into one. So does this new mode go into rebase, cherry-pick or merge, or into all three? No matter which, proper documentation is needed. Maybe this could also be used to implement a "git merge --squash A..B", a.k.a a "partial merge". (And if it could be implemented to allow a "git merge A..B" and later do a "git merge B" to merge the rest of the side-branch, that would be interesting). -- \\// Peter - http://www.softwolves.pp.se/ --
rebase already tries to detect previously applied commits. --
The same thing, apply a set of changes on top of the current branch, just using the "merge" name, and not "rebase" or "cherry-pick". "merge --squash" is just "cherry-pick" with a different name. -- \\// Peter - http://www.softwolves.pp.se/ --
Err, no. "git merge --squash foo" merges all changes from the merge base
of HEAD and foo up to foo. "git cherry-pick foo" takes just the changes
from foo^ to foo. For example:
A---B---C (master)
\
D---E---F (foo)
git cherry-pick foo # Tries to create a new commit with the changes from
# "git diff D F"
git merge --squash foo # Tries to create a new commit with the changes
# from "git diff A F"
Björn
--
Did you mean "git diff E F"? Andreas. -- Andreas Schwab, schwab@linux-m68k.org GPG Key fingerprint = 58CA 54C7 6D53 942B 1756 01D3 44D5 214B 8276 4ED5 "And now for something completely different." --
Ugh, yes, of course. Thanks. Björn --
So this will be best written as git cherry-pick ..foo -- MST --
No, "git cherry-pick ..foo" should pick the individual commits, and not
create a single big commit like "git merge --squash". So such a command
should make you end up with:
A---B---C---D'--E'--F' (master)
\
D---E---F
Not:
A---B---C---M (master)
\
D---E---F (foo)
[M being the "sqash-merge"]
"merge --squash" is one of the things I really dislike, because it turns
off the "history" part of the merge. You can say "Merging in git is about
histories, merging in svn is about changes only" to describe the major
difference for the merge commands in the two systems... "But then
there's --squash which turns git into svn".
I think a "cherry-pick --squash <range>" command would be nicer from a
conceptual point of view, but it's way too late for merge --squash to be
dropped. And I guess it wouldn't be trivial to add such a flag, and not
worth the effort, as you could as well use the interactive mode and
replace "pick" with "squash" manually. (An el cheapo implementation that
automatically replaces it would likely confuse the user, because he
asked for a single commit, but might get to fix conflicts for all the
individual commits).
Björn
--
I agree with this to some degree, but I do not offhand think of a better
alternative.
At the first sight, it looks as if what "merge --squash" does was
implemented as a new option "--squash" to the "merge" command merely
because the way _how_ it internally needs to compute the result was
already available in the implementation of "merge" command, and not
necessarily because _what_ it does was conceptually consistent with the
way "merge" works.
But at the conceptual level, "merge --squash" is a short-hand for this
command sequence:
git rebase -i HEAD that-branch
... make everything except the first one into "squash"
git checkout - ;# come back to the original branch
git merge that-branch ;# fast forward to it
So after all, it is "merge it after squashing them".
--
To me, that approach looks backwards, just like the "rebase --revisions"
proposal. "rebase" just happens to already provide the necessary
operations, but if cherry-pick would accepts ranges, this looks a lot
more logical to me:
git cherry-pick HEAD..that_branch
git reset --soft this_branch@{1} # [1]
git commit
[1] I assume that like "rebase", such a cherry-pick command would
already add a single reflog entry for the current branch
I cherry-pick all changes, and then use reset + commit to squash them
together to a single commit. To me, it's "I want to get all the changes
and squash them into a single commit", not "I want to squash the other
side's history in the background, without actually affecting the other
side and then merge that squashed version of the history".
So "cherry-pick --squash ..that_branch" seems more logical at the
conceptual level. Internally, it could of course just do a three-way
merge, instead of being stupid and repeating the "apply, commit --amend"
sequence over and over again.
Björn
--
Yes, of course, but what you are missing (and I am at blame for forgetting
to mention the history behind this in the message you are responding to)
is that "merge --squash" to support a particular need/use case was done
way before "rebase -i" came into existence.
Here is how "merge --squash" is explained in the log message:
git-merge --squash
Some people tend to do many little commits on a topic branch,
recording all the trials and errors, and when the topic is
reasonably cooked well, would want to record the net effect of
the series as one commit on top of the mainline, removing the
cruft from the history. The topic is then abandoned or forked
off again from that point at the mainline.
A nicer workflow may be to use "rebase -i" to clean up the history before
even contemplating to integrate the topic to the mainline, instead of the
above "abandoning or forking off again", if you know today's git.
But interactive was not available back then. It was introduced at 1b1dce4
(Teach rebase an interactive mode, 2007-06-25), which is 1 year after
7d0c688 (git-merge --squash, 2006-06-23).
--
Hm? You started explaining that "merge --squash" would be right because you can do it via some command sequence that involves rebase -i and then merge. I said that using that rebase+merge sequence as an argument for the choice of the name is wrong. It would even have made more sense to me if you said: git merge that-branch git reset --soft HEAD^ git commit -C ORIG_HEAD Which is "merge, but then drop the extra parents", which pretty close to what "merge --squash" does (and that sequence even gets it right not to rewrite that-branch). I'm not arguing that you shouldn't have chosen "merge --squash" to do that. You couldn't possibly foresee the future and that git might get rebase -i or maybe at some day cherry-pick -i <range>. I'm just saying that in retrospective, it's sad that merge doesn't always mean "merge histories", but that --squash makes it degenerate to "merge changes". I don't see why you're trying to defend the choice of "merge --squash" using a IMHO rather weird command sequence that happens to involve "merge", using commands that weren't present when "merge --squash" was added, but at the same ignore the "cherry-pick -i <range>" command git might learn in the near future, which allows for a much saner explanation: git cherry-pick -i ..that-branch ... make everything except the first one into "squash" And given that, one could add a --squash flag to cherry-pick that makes it do the "squash everything" itself, allowing it to be a bit smarter about the whole thing, because it could use a three-way merge internally, instead of cherry-picking all the individual commits. Making "git cherry-pick --squash ..that-branch" the same as "git merge --squash Well, I'm not saying that git should completely lose the abilitiy to do something like "merge --squash", just that if it learns "git cherry-pick <range>", it might as well get the --squash thing for cherry-pick, maybe allowing for "merge --squash" to be phased out. And heck, having it as an option to ...
Exactly! What I meant to say in the original message was that conceptually, the difference between a "git rebase --reverse A..B", a "git cherry-pick A..B" and a "git merge --squash A..B" is minute. And when continuing the thought experiment, the step from "git merge --squash A..B" to "git merge A..B" is not very large either, just (a lot) more difficult to implement. -- \\// Peter - http://www.softwolves.pp.se/ --
"git merge" is about merging histories. --squash and the A..B you suggest make it degenerate into merging changes (and you can't record that using the commit DAG). So that messes things up conceptually. Implementing probably wouldn't be that hard, IIRC it should be a matter of messing with the fake merge base that cherry-pick uses to get its job done. While "git cherry-pick foo" uses foo^ as the merge base, you'd just make "git merge --squash A..B" work like "git cherry-pick B" but use A as the fake merge base. It's been a while since I looked at the cherry-pick code though, so I might be off here. (Kind of ironic though that I didn't think of that when I said that "cherry-pick --squash" would be hard to do...) Anyway, "git merge" with a range simply makes no sense at all given how git's merge works (opposed to svn's idea of merging, which is about changes, not about histories). If you want a squash flag, tell cherry-pick to handle ranges and teach such a flag to it. Björn --
I know, this is the one "feature" of CVS that I sometimes miss in Git, that I cannot "merge" just parts of a history, and have that recorded in the history tree. I know it's wrong, I know I could do it better, but sometimes it's the shortcut that would make life easier for me. :-) But the reason I mentioned it was because of the discussion on whether the "reverse rebase" should be an option to "cherry-pick" or not, and I mentioned that it could just as well be "merge" since it can be used to And tell "merge" to tell me that if I try, so that if I try $ git merge A..B I would get a message saying something like Cannot merge a range of commits. Try "git cherry-pick A..B" or "git rebase --reverse A..B". And perhaps we could also in the same way retire --squash? $ git merge --squash B The "--squash" option is obsolete. Please use "git cherry-pick --squash B". (with a transition period where it would just call the other). Or whatever the options to simulate the old behaviour would be. This would make it clearer that "merge" preserves history while "cherry-pick" and "rebase" do not. -- \\// Peter - http://www.softwolves.pp.se/ --
Hm, does CVS really record the fact that things were merged? I've never seriously used CVS, so I have no idea... And if it does, is it just the OK, and I disagreed because I think that "merge --squash" is already wrong. And given your comment below about retiring "merge --squash", I Hm, for an error message that "range of commits" is probably on the edge of being confusing. After all "git merge B" will create a new commit M that "says" that M^1..M^2 was merged to M^1. But I can't come up with a I'd certainly like that. Bjoern --
cherry-pick is a binary though while rebase is a shell script. Should I just exec git rebase? git-rebase? -- MST --
See run-command.h : #define RUN_GIT_CMD 2 /*If this is to be git sub-command */ int run_command_v_opt(const char **argv, int opt); That should do the trick (grep 'run_command_v_opt.*GIT_CMD' *.c for some example of uses). -- Matthieu Moy http://www-verimag.imag.fr/~moy/ --
Intuitively, git rebase --reverse A..B would mean "take the range A..B, and start applying the patches from B, going in reverse order up to A", like "git log --reverse". So, I'd find it misleading. Perhaps "git rebase --cherry-pick A..B" would be a better name. No objection for --opposite either. -- Matthieu Moy http://www-verimag.imag.fr/~moy/ --
Hm, there's also (the probably not so well known) "git rev-list --cherry-pick A...B", which excludes commits that appear on both A and B and have the same patch id. I'd rather call the rev-list option a misnomer than the suggested hidden option for rebase, but I'd call it --cherry-pick-mode or --cherry-picking (like am's hidden "git am --rebasing"), just to make sure... Of course, it's not _that_ important, as it's going to be a hidden option, so user confusion is probably not that much of a concern. Björn --
OK, --cherry-picking looks fine as well. --
As somebody mentioned, "--cherry-picking", similar to "am --rebasing" that is also an unadvertised internal implementation detail, is a good name. In the very old days, the original "rebase" command was a command to "set-up and run 'am -3' command in order to transplant the current branch onto somewhere else." That was obviously too long for a command name and description, and I shorten it to "rebase", to name the command after what it is _for_ (not "what it does", nor "how it does it"). Because the command was to "set up and run am-3", it was natural at the conceptual level that the way to continue after a failed/conflicted patch application was "am --resolved". But since then, the concept of "rebasing" got established more firmly and how "rebase" can be done has become much less relevant. The original name "rebase" stopped being short for "set up and run am-3 for rebasing", but about what it _does_ (i.e. "it rebases"). And "rebase --continue" has become a natural way to drive "am --continue" at that point, to accomodate for the change in the end-user conception. These days, "set up and run am-3 for rebasing" is not even _how_ it does the "rebase", as "rebase -i" does not even use am-3. So "rebase --continue" was a logical conclusion of the command's evolution. The lesson to be learned from this history is that "cherry-pick A..B" that internally runs "rebase --cherry-picking" will need a similar "--continue" support that delegates to "rebase". "running am", which was originally the whole point of "rebase", later became an implementation detail, and we needed to teach "--continue" to "rebase" at that point. Because from day one "running rebase -i" will be an implementation detail of "cherry-pick A..B", we need to teach "cherry-pick" to pass "--abort", "--continue", etc. to underlying "rebase" for the same reason. --
And there are no "revisions" in git. So using that term for anything would only be confusing. Git has "commits" and various kinds of references to them. -- David Kågedal --
