23

Suppose we have the following revision graph:

A-X-Z--B
     \
      \-C

with A preceding both B and C. Further suppose I rebase A from upstream, creating a new commit A*, and then rebase both B and C onto A*. The resulting revision graph is the following:

A*-X'-Z'-B
 \
  \-X"-Z"-C

Note that the shared history is no longer shared. Is there a simple way to fix this, other than, say, rebasing B and then rebasing C onto Z' explicitly. In other words is there a better way to automatically rebase multiple branches at the same time in order to preserve shared history? It just seems a little bit awkward to have to either artificially place a tag at the split point, or manually inspect the graph to find out sha1 of the commit on which to rebase C to keep the shared history, not to mention opening up the possibility of mistakes, especially since I have to do this every time I rebase until I check the changes into the upstream branch.

metamatt
  • 13,809
  • 7
  • 46
  • 56
jonderry
  • 23,013
  • 32
  • 104
  • 171
  • possible duplicate of [Git: How to rebase many branches (with the same base commit) at once?](http://stackoverflow.com/questions/866218/git-how-to-rebase-many-branches-with-the-same-base-commit-at-once) – Cascabel Apr 11 '11 at 22:23
  • 2
    Oops, that wasn't the duplicate I meant to pick, though it does look like another good candidate. Try this one: http://stackoverflow.com/questions/5600659/rebasing-a-branch-including-all-its-children/5600770#5600770 – Cascabel Apr 11 '11 at 22:25

2 Answers2

20
git rebase --committer-date-is-author-date --preserve-merges --onto A* A C
git rebase --committer-date-is-author-date --preserve-merges --onto A* A B

This should keep the common commits having the same sha1 and any merges preserved. Preserve merges is not required in this case, but will become an issue with a less trivial history.

To do this for all branches that contain A in their history do:

git branch --contains A | xargs -n 1 git rebase --committer-date-is-author-date --preserve-merges --onto A* A 

Hope this helps.

UPDATE:

This may be cleaner syntax:

for branch in $(git branch --contains A); do git rebase --committer-date-is-author-date --preserve-merges --onto A* A $branch; done
Adam Dymitruk
  • 124,556
  • 26
  • 146
  • 141
  • What version of git are you using? – Adam Dymitruk May 28 '11 at 07:45
  • Cool, didn't know about --contains. However, the same problem occurs, and strangely `git branch --contains A` prints files in addition to branches when used in a for loop, but not elsewhere. – jonderry May 28 '11 at 16:06
  • Check the man page for rebase. Perhaps there is a hyphen where one shouldn't be or there is a spelling mistake. – Adam Dymitruk May 30 '11 at 05:55
  • 3
    One day I'll have to sit and understand all this. Meanwhile this is a nightmare. – dnuske May 02 '14 at 01:04
  • For me, `--committer-date-is-author-date` has no effect if `--preserve-merges` is present. I tested with version 2.6.3 and 2.7.2. – Jon-Eric Mar 02 '16 at 20:01
  • It looks like this is because `--committer-date-is-author-date` says "Incompatible with the --interactive option.", while `--preserve-merges` says "This uses the `--interactive` machinery internally." – rkgibson2 Jan 10 '17 at 23:13
  • I'm having a hard time trying to guess what all those piped commands do. Right now the only thing I'm pretty sure is `--committer-date-is-author-date` [is only compatible with the am backend of git rebase and --preserve-merges implies the interactive rebase backend.](https://git-scm.com/docs/git-rebase#_incompatible_options) – 40detectives Sep 21 '18 at 12:38
  • Using git v2.21 on Mac, I get the error: > fatal: cannot combine am options with either interactive or merge options – Kirill Jan 03 '20 at 12:17
2

The problem in the general case

I was concerned with a similar problem: rebasing a whole subhistory -- several branches, with some links between them resulting from merge:

A--B-B2-B3 <--topicB
\   /
 \-C-C2-C3 <--topicC

If I run several git rebase sequentially (for topicB and topicC), then I doubt the merges between the branches can be preserved correctly. So I would need to rebase all the branches at once, hoping that would reconstruct the merges between them correctly.

A "solution" that worked in a specific subcase

In my case, I had luck that topicC was actually merged into topicB:

A-B-----------B2-B3 <--topicB
   \         /
    \-C-C2-C3 <--topicC

so to rebase the whole subhistory, I could just run

git rebase -p A topicB --onto A*

(where A* is the new base, instead of A, as in your question; topicB is the branch name that would initially point to the old commit B3 and to the rewritten commit B3' afterwards; -p is a short name for --preserve-merges option), obtaining a history like:

A-B-----------B2-B3
   \         /
    \-C-C2-C3 <--topicC

A*-B'-------------B2'-B3' <--topicB
    \            /
     \-C'-C2'-C3'

and then reset all remaining branch refs (and tags) to the new corresponding commits (in the new subhistory), e.g.

git branch -f topicC C3'

It worked:

A*-B'-------------B2'-B3' <--topicB
    \            /
     \-C'-C2'-C3' <--topicC

(Moving the branch refs and tags could perhaps be done with a script.)

A "solution" for the general case inspired by that specific one

If topicC was not merged into topicB, I could create a fake top commit to merge all the branches I want to rebase, e.g.:

git checkout -b fake topicB
git merge -s ours topicC

and then rebase it that way:

git rebase -p A fake --onto A*

and reset the topic branches to the new commits, delete the fake branch.

Other answers

I believe that the other answer with --committer-date-is-author-date is also good and sensible, but in my experience with Git, I hadn't had that idea and solved the problem of keeping the shared history really shared after a rebase the way I have described in my additional answer here.

imz -- Ivan Zakharyaschev
  • 4,921
  • 6
  • 53
  • 104
  • I can't follow your answer. You say _topicC_ was already merged into _topicB_, but the diagram shows otherwise. Also, why would you even want to preserve an already merged branch head after a rebase? – kynan May 27 '12 at 23:47
  • @kynan: that was just a specific subcase of the general problem where the working solution was simpler. Then this case inspired the idea of the more general solution (with a fake merge of everything). – imz -- Ivan Zakharyaschev May 28 '12 at 00:39
  • 1
    @kynan: as for preserving an already merged branch -- the branches have their intentions, for example, one was a simple bugfix prepared for a submission upstream, and the other one (the subsuming one) was an implementation of a whole new experimental feature (which however depended on the bugfix). If they haven't yet been pulled by the upstream, I'd like to preserve them after the rebase (which was for the cleanliness&clarity for the upstream). – imz -- Ivan Zakharyaschev May 28 '12 at 00:41
  • 2
    After looking at the examples in [this answer that explains git's “rebase --preserve-merges”](http://stackoverflow.com/a/15915431/3054806), this answer became easier to understand. – Strategy Thinker Jan 04 '16 at 18:48
  • @StrategyThinker, well, that answer explains that merges in the current branch (at least, those between the common ancestor and the current branch) aren't normally preserved by rebasing operations. But it doesn't explain why imz didn't use `--preserve-merges` to avoid that default behaviour and solve their problem. –  Apr 08 '18 at 00:45
  • 1
    @sampablokuper I've written the 1st part of my answer in a more explicit manner now, so that it is more ready for an exact reproduction (by a reader). As for the 2nd part, I don't know why I omitted `--preserve-merges`; I'm afraid, it's just because my answer was hasty and schematic. I should fix that. Thanks! – imz -- Ivan Zakharyaschev Apr 08 '18 at 13:06