Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Git: rebase onto development branch from upstream

Tags:

git

I have local master and develop branches. I do all my work on develop and then merge them into master for releases. There is a remote branch, upstream/master which has changes I want, but I want to rebase my changes in develop (which shares a common ancester) on top of its changes and put them back into develop. I've already done git fetch upstream.

The Git book chapter on rebasing says to do:

$ git checkout experiment
$ git rebase master

Which I (assume) in my case would mean:

$ git checkout upstream/master
$ git rebase develop

But then I'd be on the upsteam/master and in detached head state. However, if I did a merge of upstream/master, I'd do that from develop, and the changes would be on develop, e.g.

$ git checkout develop
$ git merge upstream/master

So this way to rebase seems backward to me. I'd like to rebase my changes in develop on the develop branch with the changes from upstream/master similar to how the merge would work. Am I supposed to do the rebase on upstream/master, fix any conflicts, and then add it, stash it and pop it onto develop?

like image 320
ian Avatar asked Oct 05 '13 17:10

ian


2 Answers

The rebase would actually be:

git checkout develop
git rebase upstream/master

(git rebase should read: "rebase my current branch, here develop, on top of target branch, here upstream/master")

And the end result wouldn't be a detached head, but the newly rewritten develop branch.

like image 113
VonC Avatar answered Sep 24 '22 21:09

VonC


The simplest (most obvious to everyone) method is to update your master branch first, then rebase onto the updated master which is now exactly the same as origin/master:

$ git fetch origin                    # Get updates from remote.
$ git checkout master                 # Now bring master into sync:
$ git merge --ff-only origin/master   # if this fails you have stuff
                                      # in your master that they don't
                                      # have in theirs, and you need
                                      # to decide what to do about it

At this point, if all went well, master and origin/master are the same (as you can see with graphical viewers like gitk, or with git log --graph --oneline --decorate), and it should be clear how git rebase master will work.

But you don't actually have to do that. You can just git rebase origin/master, while being on develop. (This will leave your master un-forward-ed—presumably at some point you will want it forward-ed—so it's not really saving you much. But it is easier to do now.)


The long boring "why" part: git rebase takes, in its long form, three points, which the documentation describes as newbase, upstream, and branch:

git rebase ... [--onto <newbase>] [<upstream>] [<branch>]

If you specify exactly one argument, as in git rebase master, that names the upstream and both newbase and branch are computed. The branch is the current branch (i.e., HEAD, which must not be detached). If you omit --onto, the newbase is taken as the upstream argument. So if you're on develop now, and you run git rebase X, the branch is develop and both newbase and upstream are X.

The rebase method is, in effect (there are various internal optimizations and the effect on the reflog is a bit different):

  1. check out branch
  2. save a reference (ORIG_HEAD) to the commit to which branch points
  3. reset it (a la git reset --hard) to newbase
  4. for every commit in upstream..ORIG_HEAD1 (in oldest to newest order), git cherry-pick that commit to add it to the just-reset branch.

Thus, as in the documentation:

   Assume the following history exists and the current branch is "topic":

                 A---B---C  HEAD=topic
                /
           D---E---F---G    master

when you git rebase master you get:

                   A---B---C         ORIG_HEAD
                  /
                 /       A'--B'--C'  HEAD=topic
                /       /
           D---E---F---G             master

(all I did here was take the example in the man page and add the ORIG_HEAD label and the HEAD=, to show that the original commits are "still in there", and that HEAD is a reference to topic).

So, what happens if you have your develop and master and they have their master which has a few extra changes in it? Let's draw that:

A -- B -- C                         master
          | \
          |   D                     origin/master
          |
          E -- F                    HEAD=develop

Now you git rebase origin/master:

A -- B -- C                         master
          | \
          |   D                     origin/master
          |     \
          |       E' -- F'          HEAD=develop
          |
          E -- F                    ORIG_HEAD

At some point, you eventually move your own master to point to commit D too (and you drop ORIG_HEAD) giving:

A -- B -- C -- D                    master, origin/master
                 \
                   E' - F'          HEAD=develop

which is the same thing with some of the labels moved around.

That's all that the branch labels are, they are just labels. Each label points to one (single) commit. The commits themselves point back to previous commits, which is what builds the commit tree (or "commit DAG", really).


1Git's X..Y syntax hides a lot of "deep magic". It means "all commits reachable from label Y that are not reachable from label X. Which is exactly the set of commits that need to be cherry-picked, as those are the commits that were on branch, and were not on upstream, before the "rebase" op. It "looks like" a time-based sequence at first blush, and that generally works in people's heads, but it's based on the commit graph topology. Sometimes this trips people up though: A..B, where A is not related to B at all (because of multiple commit trees in the repo), means "every revision reachable from B.

like image 14
torek Avatar answered Sep 22 '22 21:09

torek