views:

237

answers:

2

Steps Leading up to Question

Here's what I did leading up to my question:

  1. Forked a project on Github and cloned it locally
  2. Made two commits to my local repo to fix a bug
  3. Pushed those two commits to my forked Github repo
  4. Sent a pull request via Github to the original repo owner
  5. Original owner made a different commit to resolve the bug

Question

How can I merge the original owner's commits so that my local master is identical to his master (basically I want to do a Github fast forward) and have the two commits that I made on a new branch?

Steps Taken After Question Came to Mind

Here are the things that I've done so far (that I think I'll need to do to accomplish what I want):

  1. Added the original repo as a remote: git remote add original-repo git://github.com/blah/blah.git
  2. Fetched the original-repo: git fetch original-repo
  3. Fetched the original-repo master: git fetch original-repo master
  4. Went into deer in headlights mode, so posted question to Stackoverflow after googling failed me. There are some similar questions on Stackoverflow, but I didn't see anything that was trying to accomplish the same thing.
+2  A: 

It is a bit like the question "Take all my changes on the current branch and move them to a new branch in Git"

Basically, you need to reset your master branch to a state before your commits, and put those in a new branch 'myBranch' (or any other name you want):

$ checkout -b myBranch SHA1_before_your_commits
$ rebase master # to replay all your commits on top of this new branch
$ git checkout master
$ git reset --hard SHA1_before_your_commits

Then you add the commit from the original repo:

$ git pull original-repo master

Then you will need to force the push to your GitHub forked repo (since you have rewritten the history of your branch)

 git push --force origin master

Warning: this will not only destroy the history on the remote, it might also give an error message to anyone who updates from the same remote (depending on how much history you rewrite). Use this only as the very last resort.

Here, if nobody has cloned your forked repository, this could be a viable solution.

VonC
Thanks for the warning; no one had cloned my fork so it worked beautifully. In case it's not readily apparent to others, the git push --force needs two hyphens.
Matthew Rankin
@Matthew: you're welcome. Thank you for the heads-up on the hyphens. I have edited the answer accordingly.
VonC
If @Matthew did his work on separate topic (feature) branch, the solution would be to simply delete no longer needed branch.
Jakub Narębski
+1  A: 

Alternate solution to the one presented by VonC would be to use "ours" merge strategy to join histories, but to take one version:

$ git checkout -b tmp original-repo/master
$ git merge -s ours master  # take version from 'tmp', i.e. from 'original-repo/master'
$ git checkout master
$ git merge tmp                    # should be fast forward
$ git branch -d tmp              # 'tmp' branch is no longer needed

What you do above is to do in short "theirs" merge.

Caveat: this solution solves original problem only if there are no other commits on local branch since last merge (last update), that the ones which are to be replaced by version from original repository!!!

So this solution allows to keep your version of solving the problem for the history, but is more limited in application.


Below there is set of ASCII-art diagrams showing what is happening in the repository in each step:

  1. before first command:
    *---*---x---A                 <-- original-repo/master (remote-tracking branch)
                    \
                      \--a---b           <-- master <-- HEAD
    
  2. after "git checkout -b tmp original-repo/master":
                                              <-- original-repo/master,
    *---*---x---A                 <-- tmp  <-- HEAD
                    \
                      \--a---b           <-- master
    
    where both 'tmp' (local branch) and 'original-repo/master' (remote-tracking branch) point to the same commit.
  3. after "git merge -s ours master":
                            /-------------- original-repo/master
                          v
    *---*---x---A----A'       <-- tmp  <-- HEAD
                    \              /
                      \--a---b           <-- master
    
    where commit A' is a merge commit, but has the same contents (the same tree) as commit A
  4. after "git checkout master && git merge tmp" (the merge should be fast-forward):
                              /-------------- original-repo/master
                            v
    *---*---x---A----A'       <-- tmp,
                    \              /          <-- master <-- HEAD
                      \--a---b
    
    Both 'master' and 'tmp' point to the same commit.
  5. after "git branch -d tmp", and some reorganizing of the diagram
    *---*---x---A-----\            <-- original-repo/master
                    \                   \
                      \--a---b---A'      <-- master <-- HEAD
    

At the end:

  • remote-tracking branch 'original-repo/master' (i.e. 'refs/remotes/original-repo/master') points to commit A,
  • local branch 'master' (i.e. 'refs/heads/master'), which is current branch, points to commit A'.
    Commit A' has:
    • commit A as its first parent (A'^1 == A),
    • commit b as second parent (A'^2 == a),
    • and its contents (its tree) is the same as in commit A (A'^{tree} == A^{tree}).
Jakub Narębski
Very interesting, but what happens to the OP's commits on master? It they modified the same files than the GitHub original fix, wouldn't they 'disappear' due to the 'ours' merge strategy? The OP wanted to keep his commits on a separate branch, are you saying this is not needed?
VonC
@VonC: If the situation is not as diagrams above (which were added after your comment), i.e. if there are some commits on 'master' which are not present on 'original-repo/master' in different form, then of course this solution does not solve OP problem.
Jakub Narębski
The ASCII examples very pretty mangled. Could you check if they are displayed fine now (and if I did not misinterpreted them)
VonC
They were not mangled for me, and now they are mangled. It probably depends on the browser, and on the fonts used (why, oh why, `<pre>` doesn't use monospace font?!?). Also you reversed directions of arrows, which is more serious; also you need to double `\\` to display `\`... therefore I rolled back your changes. Sorry.
Jakub Narębski
That was: double ` \\ ` to display ` \ `.
Jakub Narębski
Oops Sorry about those edits. I will try to fix my Firefox font display first next time. Anyway, thank you for those extra diagrams. +1
VonC