vossad01
vossad01

Reputation: 11948

Allow merging unrelated histories in git rebase

When you want to rebase a branch keeping merge commits you pass the --preserve-merges flag. When you are merging unrelated history in git you need to pass the --allow-unrelated-histories flag.

If you are doing git rebase --preserve-merges when an existing merge comes from an unrelated history, it fails:

fatal: refusing to merge unrelated histories

If you try git rebase --preserve-merges --allow-unrelated-histories it fails with:

error: unknown option 'allow-unrelated-histories'

Is there some other way to tell rebase to allow the merge?


Edit: here is a minimal reproduction: https://github.com/vossad01/rebase-unrelated-merge-reproduction

To reproduce checkout master then execute:

git rebase --preserve-merges --onto origin/a-prime HEAD~2

Upvotes: 25

Views: 18690

Answers (5)

Rujul Walvekar
Rujul Walvekar

Reputation: 11

What worked for me is

$ git checkout master
$ git rebase --rebase-merges --onto origin HEAD~2

Successfully rebased and updated refs/heads/master

This was my error

$ git status

on branch master

Your branch and 'origin/master' have diverged, and have 82 and 24391 different commits each, respectively. (use "git pull" to merge the remote branch into yours)

$ git pull

fatal: refusing to merge unrelated histories

Upvotes: 1

Wenfang Du
Wenfang Du

Reputation: 11327

The only way to synchronize the two diverged branches is to merge them back together, resulting in an extra merge commit and two sets of commits that contain the same changes (the original ones, and the ones from your rebased branch). Needless to say, this is a very confusing situation.

So, before you run git rebase, always ask yourself, “Is anyone else looking at this branch?” If the answer is yes, take your hands off the keyboard and start thinking about a non-destructive way to make your changes (e.g., the git revert command). Otherwise, you’re safe to re-write history as much as you like.

Reference: https://www.atlassian.com/git/tutorials/merging-vs-rebasing#the-golden-rule-of-rebasing

Upvotes: 0

Schwern
Schwern

Reputation: 164769

To reproduce checkout master then execute:

git rebase --preserve-merges --onto origin/a-prime HEAD~2 -i

The git-rebase docs say to not combine -i and --preserve-merges.

[--preserve-merges] uses the --interactive machinery internally, but combining it with the --interactive option explicitly is generally not a good idea unless you know what you are doing (see BUGS below).

But even without the -i it still fails with fatal: refusing to merge unrelated histories.

Part of the problem is HEAD~2 is a direct ancestor of origin/a-prime. Your test repo looks like this:

1 [master]
|
2
|\
| |  3 [origin/a-prime]
| |  |
| 4 / [origin/b]
|  /
| /
|/
5 [origin/a]

HEAD~2 of master is 5. origin/a-prime is 3. Your command is equivalent to:

git rebase -p --onto 3 5

5 is a direct ancestor of 3, so that command doesn't make much sense. If that works at all, it's going to do something weird.


the cases I have encountered a couple of times recently have been in moving a project's documentation from GitHub Wiki to GitHub Pages (when the website already exists).

This is an inappropriate use of rebase. Rebase turns parallel histories into linear histories, basically pretending that one set of changes was done on top of another set all along. This is good for things like keeping feature branches up to date while they're being worked on, the bookkeeping and review is easier if you don't have a bunch of intermediate merge commits that do nothing but update the branch. Those are just noise to anyone reading the code and commit history in the future.

But when you have two truly divergent histories, it's best to leave them as divergent histories. Merging them tells the correct story: the web site and docs were developed separately, but then come together into one unit.

1 - 3 - 5
         \
  2 - 4 - 6 - 7 - 8 [master]

You can look at them separately in topological order (8, 7, 6, 5, 3, 1, 4, 2) using git log --topo-order or you can look at them interleaved in date order (8, 7, 6, 5, 4, 3, 2, 1), the git log default. A history visualizer like gitk or GitX will show both orders simultaneously.

Rebasing one on top of the other tells a lie: we worked on the site, and then we worked on the documentation, and then at some point (a point you'll have to find) and for some reason we worked on the site and documentation together.

1 - 3 - 5 - 2 - 4 - 6 - 7 - 8 [master]

That loses vital information and makes puzzling out why certain changes were made more difficult in the future.

Do a merge, it's the correct thing.

Upvotes: 4

vossad01
vossad01

Reputation: 11948

When git rebase fails on the merge it does not abort the rebase, so you have the opportunity to manually intervene.

If you are willing to to resolve this by hand, you can complete the merge as follows:

git merge --allow-unrelated ORIGINAL_BRANCH_THAT_WAS_MERGED --no-commit
git commit -C ORIGINAL_MERGE_COMMIT
git rebase --continue

Ideally, there would be a way for Git to handle this without manual intervention.

Upvotes: 18

jthill
jthill

Reputation: 60265

The brute-force method is to force a common root -- since you're trying to rebase roots, with no content history, make a nonce empty commit and tell git that's the parent of the histories you're merging:

git rev-list --all --max-parents=0 \
| awk '{print $0,empty}' empty=`:|git mktree|xargs git commit-tree` \
> .git/info/grafts
git rebase here
rm .git/info/grafts

Upvotes: 10

Related Questions