{"thread":{"id":"25793","subject":"How to fix “Your branch and 'origin/master' have diverged” after editing a commit that came before a pull?","startedAt":"2010-11-20T00:36:46Z","lastAt":"2010-11-22T05:38:14Z","messageCount":8,"participants":["Yang Zhang","Jeff King","Gavin Guo"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"156223","messageId":"AANLkTikr+uGrO2EB9WQk+CXeOm7jiYxdbhGzRRvgc9B9@mail.gmail.com","threadId":"25793","inReplyTo":null,"subject":"How to fix “Your branch and 'origin/master' have diverged” after editing a commit that came before a pull?","fromName":"Yang Zhang","fromEmail":"yanghatespam@gmail.com","sentAt":"2010-11-20T00:36:46Z","receivedAt":"2010-11-20T00:36:46Z","isPatch":false,"sender":{"key":"yanghatespam@gmail.com","avatar":"https://gravatar.com/avatar/e0796ebc342b90612276b87755da4abc776ddfebbc23049a79bb8b192570161d?d=mp&s=160"},"body":"In the following scenario:\n\n1. Make commits A, B, C\n2. Pull, getting commits D, E\n3. Make more commits F, G, H, ...\n4. Realize that you need to tweak B\n5. Tweak B using git rebase -i and git commit --amend\n\nNow git status says:\n\n  Your branch and 'origin/master' have diverged.\n\nHow should I fix this? Thanks.\n"},{"id":"156243","messageId":"AANLkTinRfzzG-Dc9Ervc-h0_4cwx9KBO1aTkhwBGpHHs@mail.gmail.com","threadId":"25793","inReplyTo":"AANLkTikr+uGrO2EB9WQk+CXeOm7jiYxdbhGzRRvgc9B9@mail.gmail.com","subject":"Re: How to fix “Your branch and 'origin/master' have diverged” after editing a commit that came before a pull?","fromName":"Yang Zhang","fromEmail":"yanghatespam@gmail.com","sentAt":"2010-11-20T01:59:55Z","receivedAt":"2010-11-20T01:59:55Z","isPatch":false,"sender":{"key":"yanghatespam@gmail.com","avatar":"https://gravatar.com/avatar/e0796ebc342b90612276b87755da4abc776ddfebbc23049a79bb8b192570161d?d=mp&s=160"},"body":"Clarification:\n\nSimply pulling again does not fix the problem; it just reapplies the\ncommits D, E on top of what I already have (despite the D, E already\nbeing there in the history), which also has the side-effect of\nproducing superfluous conflicts.\n\nOn Fri, Nov 19, 2010 at 4:36 PM, Yang Zhang <yanghatespam@gmail.com> wrote:\n> In the following scenario:\n>\n> 1. Make commits A, B, C\n> 2. Pull, getting commits D, E\n> 3. Make more commits F, G, H, ...\n> 4. Realize that you need to tweak B\n> 5. Tweak B using git rebase -i and git commit --amend\n>\n> Now git status says:\n>\n>  Your branch and 'origin/master' have diverged.\n>\n> How should I fix this? Thanks.\n>\n\n\n\n-- \nYang Zhang\nhttp://yz.mit.edu/\n"},{"id":"156248","messageId":"20101120043628.GB20725@sigill.intra.peff.net","threadId":"25793","inReplyTo":"AANLkTikr+uGrO2EB9WQk+CXeOm7jiYxdbhGzRRvgc9B9@mail.gmail.com","subject":"Re: How to fix “Your branch and 'origin/master' have diverged” after editing a commit that came before a pull?","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2010-11-20T04:36:28Z","receivedAt":"2010-11-20T04:36:28Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Fri, Nov 19, 2010 at 04:36:46PM -0800, Yang Zhang wrote:\n\n> In the following scenario:\n> \n> 1. Make commits A, B, C\n> 2. Pull, getting commits D, E\n> 3. Make more commits F, G, H, ...\n> 4. Realize that you need to tweak B\n> 5. Tweak B using git rebase -i and git commit --amend\n> \n> Now git status says:\n> \n>   Your branch and 'origin/master' have diverged.\n> \n> How should I fix this? Thanks.\n\nWhen you change a commit, you also change every commit after it (since\neach points to its predecessor by a parent pointer). So you create an\nalternate history. IOW, you had this:\n\n  A--B--C\n\nand pulled to get this:\n\n  A--B--C--D--E\n\nor maybe this:\n\n  A--B--C--Merge\n          /\n      D--E\n\ndepending on how those other commits relate to what you have. Then you\nmade more commits, like (let's look at the non-merge case):\n\n  A--B--C--D--E--F--G--H\n\nThen you tweaked B. The rebase replayed every commit after that, but\neach one is not exactly the same as the other. So now you have:\n\n    B--C--D--E--F--G--H\n   /\n  A--B'--C'--D'--E'--F'--G'--H'\n\nwhere the top branch of history is what you used to have (and is\naccessible via the reflog as branch@{1}). But your actual branch is at\nH'. So if you \"git pull\" again, as you tried, it will try to merge D and\nE from upstream. So:\n\n                            D--E\n                                \\\n  A--B'--C'--D'--E'--F'--G'--H'--Merge\n\nwhich is definitely not what you want. The problem is that you have\nrewritten upstream's commits, because you rebased across a set of\ncommits that contained things you had pulled.\n\nTo fix it, what you want to do is recreate the history on top of B' as\nit happened on top of B. So first you go back to C', the last commit\njust before the commits from upstream that were rewritten. (you will\nhave pick its sha1 out of the log):\n\n  git checkout -b temp B'\n\nYou should then have:\n\n  A--B'--C'\n\non a temporary branch. Now re-pull from upstream (you could also\nmanually rebase those commits, but this is probably simpler, especially\nif there actually was a merge):\n\n  git pull remote_name branch_name\n\nNote that you need to explicitly mention where you pulled from, since\nthe temp branch will not be configured to pull in the same way (if you\ndon't have any special config set up, it should be \"git pull origin\nmaster\").\n\nAnd now you have:\n\n  A--B'--C'--D--E\n\nat which point we can rebase the last bit of your branch on top:\n\n  git rebase --onto temp F'^ branch_name\n\nwhere \"branch_name\" is the name of the branch where this mess happened\n(presumably \"master\"), and F' is the first commit that is worth saving\nafter you pulled from upstream. And that gives you:\n\n  A--B'--C'--D--E--F''--G''--H''\n\nwhere F'' corresponds to the original F, but actually has a different\ncommit id (because of different parentage) than F or F'.\n\nAt that point your original branch should be in the state you want. You\ncan delete the temp branch with \"git branch -D temp\".\n\nSo that's the most general way to do it. It's a little convoluted\nbecause of the way rebase works (you can't say \"rebase those commits on\ntop of me\", but rather have to say \"rebase me on top of these commits\",\nwhich leads us to use the temporary branch).\n\nDepending on the relationship of F, G, and H to D and E, it could be\nmuch simpler to just re-order history. So from your your broken state,\nwhich remember is:\n\n  A--B'--C'--D'--E'--F'--G'--H'\n\ndo a \"git rebase -i\", and just _delete_ all of the commits you pulled\nfrom upstream (D and E in this case). Now you have:\n\n  A--B'--C'--F''--G''--H''\n\nand now repeat your pull to get:\n\n  A--B'--C'--F''--G''--H''--D--E\n\nWhich is very simple and straightforward. But it relies on it being\nacceptable for F, G, and H to come before D and E in the history.\n\n\nWhew, that turned out long. Hopefully it helps, and did not just confuse\nyou more. :)\n\n-Peff\n"},{"id":"156266","messageId":"AANLkTinFhFjxLNnu-CZiAnGpcFG8Hu-bL1N5ME89ZTku@mail.gmail.com","threadId":"25793","inReplyTo":"20101120043628.GB20725@sigill.intra.peff.net","subject":"Re: How to fix “Your branch and 'origin/master' have diverged” after editing a commit that came before a pull?","fromName":"Yang Zhang","fromEmail":"yanghatespam@gmail.com","sentAt":"2010-11-20T20:12:27Z","receivedAt":"2010-11-20T20:12:27Z","isPatch":false,"sender":{"key":"yanghatespam@gmail.com","avatar":"https://gravatar.com/avatar/e0796ebc342b90612276b87755da4abc776ddfebbc23049a79bb8b192570161d?d=mp&s=160"},"body":"On Fri, Nov 19, 2010 at 8:36 PM, Jeff King <peff@peff.net> wrote:\n> Whew, that turned out long. Hopefully it helps, and did not just confuse\n> you more. :)\n\nThat was a *fantastic* explanation. Thank you!\n\n-- \nYang Zhang\nhttp://yz.mit.edu/\n"},{"id":"156286","messageId":"AANLkTikCnX5aBrmh9+wnb9JyOiynv0zhrpdMJwbhL2ji@mail.gmail.com","threadId":"25793","inReplyTo":"20101120043628.GB20725@sigill.intra.peff.net","subject":"Re: How to fix “Your branch and 'origin/master' have diverged” after editing a commit that came before a pull?","fromName":"Gavin Guo","fromEmail":"tuffkidtt@gmail.com","sentAt":"2010-11-21T14:45:13Z","receivedAt":"2010-11-21T14:45:13Z","isPatch":false,"sender":{"key":"tuffkidtt@gmail.com","avatar":null},"body":"> To fix it, what you want to do is recreate the history on top of B' as\n> it happened on top of B. So first you go back to C', the last commit\n> just before the commits from upstream that were rewritten. (you will\n> have pick its sha1 out of the log):\n>\n>  git checkout -b temp B'\n\nI think you mean git checkout -b temp C'\n\n> You should then have:\n>\n>  A--B'--C'\n>\n> on a temporary branch. Now re-pull from upstream (you could also\n> manually rebase those commits, but this is probably simpler, especially\n> if there actually was a merge):\n>\n>  git pull remote_name branch_name\n>\n> Note that you need to explicitly mention where you pulled from, since\n> the temp branch will not be configured to pull in the same way (if you\n> don't have any special config set up, it should be \"git pull origin\n> master\").\n>\n> And now you have:\n>\n>  A--B'--C'--D--E\n>\n> at which point we can rebase the last bit of your branch on top:\n>\n>  git rebase --onto temp F'^ branch_name\n>\n> where \"branch_name\" is the name of the branch where this mess happened\n> (presumably \"master\"), and F' is the first commit that is worth saving\n> after you pulled from upstream. And that gives you:\n>\n>  A--B'--C'--D--E--F''--G''--H''\n>\n> where F'' corresponds to the original F, but actually has a different\n> commit id (because of different parentage) than F or F'.\n>\n> At that point your original branch should be in the state you want. You\n> can delete the temp branch with \"git branch -D temp\".\n\nI'm sorry that I can't understand \"your original branch should be in\nthe state you want\" ?\nYou only create a temp branch, and rebase some commits on it, right ??\nWhat does that related to original branch ??\n\n> So that's the most general way to do it. It's a little convoluted\n> because of the way rebase works (you can't say \"rebase those commits on\n> top of me\", but rather have to say \"rebase me on top of these commits\",\n> which leads us to use the temporary branch).\n\nGavin Guo\n"},{"id":"156289","messageId":"20101121163001.GA29216@sigill.intra.peff.net","threadId":"25793","inReplyTo":"AANLkTikCnX5aBrmh9+wnb9JyOiynv0zhrpdMJwbhL2ji@mail.gmail.com","subject":"Re: How to fix “Your branch and 'origin/master' have diverged” after editing a commit that came before a pull?","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2010-11-21T16:30:02Z","receivedAt":"2010-11-21T16:30:02Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Nov 21, 2010 at 10:45:13PM +0800, Gavin Guo wrote:\n\n> > To fix it, what you want to do is recreate the history on top of B' as\n> > it happened on top of B. So first you go back to C', the last commit\n> > just before the commits from upstream that were rewritten. (you will\n> > have pick its sha1 out of the log):\n> >\n> >  git checkout -b temp B'\n> \n> I think you mean git checkout -b temp C'\n\nYes, sorry. You should base your temp branch on C'.\n\n> >  git rebase --onto temp F'^ branch_name\n> [...]\n> > At that point your original branch should be in the state you want. You\n> > can delete the temp branch with \"git branch -D temp\".\n> \n> I'm sorry that I can't understand \"your original branch should be in\n> the state you want\" ?\n> You only create a temp branch, and rebase some commits on it, right ??\n> What does that related to original branch ??\n\nThe three-argument form of rebase above will switch to branch_name (your\noriginal branch), consider F'^ as the upstream, and rebase\nF'^..branch_name on top of the commits in \"temp\".\n\n-Peff\n"},{"id":"156291","messageId":"AANLkTim6h17cd6LjCt8e8HBHGCdyCjw1yx7PVUsD7piJ@mail.gmail.com","threadId":"25793","inReplyTo":"20101121163001.GA29216@sigill.intra.peff.net","subject":"Re: How to fix “Your branch and 'origin/master' have diverged” after editing a commit that came before a pull?","fromName":"Yang Zhang","fromEmail":"yanghatespam@gmail.com","sentAt":"2010-11-21T18:51:51Z","receivedAt":"2010-11-21T18:51:51Z","isPatch":false,"sender":{"key":"yanghatespam@gmail.com","avatar":"https://gravatar.com/avatar/e0796ebc342b90612276b87755da4abc776ddfebbc23049a79bb8b192570161d?d=mp&s=160"},"body":"On Sun, Nov 21, 2010 at 8:30 AM, Jeff King <peff@peff.net> wrote:\n> On Sun, Nov 21, 2010 at 10:45:13PM +0800, Gavin Guo wrote:\n>\n>> > To fix it, what you want to do is recreate the history on top of B' as\n>> > it happened on top of B. So first you go back to C', the last commit\n>> > just before the commits from upstream that were rewritten. (you will\n>> > have pick its sha1 out of the log):\n>> >\n>> >  git checkout -b temp B'\n>>\n>> I think you mean git checkout -b temp C'\n>\n> Yes, sorry. You should base your temp branch on C'.\n>\n>> >  git rebase --onto temp F'^ branch_name\n>> [...]\n>> > At that point your original branch should be in the state you want. You\n>> > can delete the temp branch with \"git branch -D temp\".\n>>\n>> I'm sorry that I can't understand \"your original branch should be in\n>> the state you want\" ?\n>> You only create a temp branch, and rebase some commits on it, right ??\n>> What does that related to original branch ??\n>\n> The three-argument form of rebase above will switch to branch_name (your\n> original branch), consider F'^ as the upstream, and rebase\n> F'^..branch_name on top of the commits in \"temp\".\n>\n> -Peff\n>\n\nActually, I missed this detail earlier, and now like Gavin I'm\nconfused. *temp* is \"in the state that you want,\" not original_branch,\nright? temp shouldn't be deleted just yet; master should be updated to\npoint to this....\n-- \nYang Zhang\nhttp://yz.mit.edu/\n"},{"id":"156304","messageId":"20101122053814.GA7568@sigill.intra.peff.net","threadId":"25793","inReplyTo":"AANLkTim6h17cd6LjCt8e8HBHGCdyCjw1yx7PVUsD7piJ@mail.gmail.com","subject":"Re: How to fix “Your branch and 'origin/master' have diverged” after editing a commit that came before a pull?","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2010-11-22T05:38:14Z","receivedAt":"2010-11-22T05:38:14Z","isPatch":false,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Sun, Nov 21, 2010 at 10:51:51AM -0800, Yang Zhang wrote:\n\n> >> >  git rebase --onto temp F'^ branch_name\n> >> [...]\n> >> > At that point your original branch should be in the state you want. You\n> >> > can delete the temp branch with \"git branch -D temp\".\n> >>\n> >> I'm sorry that I can't understand \"your original branch should be in\n> >> the state you want\" ?\n> >> You only create a temp branch, and rebase some commits on it, right ??\n> >> What does that related to original branch ??\n> >\n> > The three-argument form of rebase above will switch to branch_name (your\n> > original branch), consider F'^ as the upstream, and rebase\n> > F'^..branch_name on top of the commits in \"temp\".\n> >\n> > -Peff\n> >\n> \n> Actually, I missed this detail earlier, and now like Gavin I'm\n> confused. *temp* is \"in the state that you want,\" not original_branch,\n> right? temp shouldn't be deleted just yet; master should be updated to\n> point to this....\n\nNo, the first thing rebase will do is switch back to your\noriginal_branch, and then it will rebase the extra commits (the\nrebased versions of things that happened after your initial pull), on\ntop of the new partial history in temp.\n\nYeah, the arguments to rebase are weird. In a simpler world you would\ndo:\n\n  # assume we're on master, the broken branch; mark the point with a tag\n  git tag broken\n\n  # go back to just before the broken rewritten commits\n  git reset --hard C'\n\n  # now re-do the merge\n  git pull origin master\n\n  # and now grab the other commits from our broken state\n  git cherry-pick F'^..broken\n\nexcept that cherry-pick doesn't actually walk the commit range as you\nwant it to. I think you can do:\n\n  git cherry-pick F' G' H'\n\nthese days, so that is another option.\n\nAnyway, just for fun I put together a script which graphically shows\nyour situation at each step. You can run it all at once, but it is\nprobably more instructive to cut and paste into a terminal, reading all\nof the comments.\n\n-Peff\n\n-- >8 --\n#!/bin/sh\n\n# clean up any previous invocations\nrm -rf parent child\n\n# short helper function for making our commits\ncommit() {\n  echo $1 >$1 && git add $1 && git commit -m $1 && git tag $1\n}\n\n# short helper to show state\nshow() {\n  # or gitk \"$@\" if you prefer\n  git log --oneline --graph --decorate \"$@\"\n}\n\n# make a parent and child with some shared base\nmkdir parent && (cd parent && git init && commit base)\ngit clone parent child\n\n# now child and parent diverge. child has a-b-c,\n# parent has d-e\n(cd child && commit A && commit B && commit C) &&\n(cd parent && commit D && commit E) &&\n\n# now let's recreate the problem situation. Everything\n# now happens in the child.\ncd child\n\n# First we pull the parent's commits into the child\ngit pull origin master\n\n# And build on top of it\ncommit F && commit G && commit H\n\n# And then we \"rebase -i\", rewriting B\nGIT_EDITOR='perl -pi -e \"s/pick (.* B)/edit \\$1/\"' git rebase -i base\necho changes >>B && git commit --amend -a -m B\ngit rebase --continue\n\n# Now we have the broken state, because we rewrote parent's commits during our\n# rebase. We also failed to preserve merges, so the new history appears linear.\n# You can see the repeated commits easily by looking at the history graph of\n# our new state versus our old.\n#\n# Let's also go ahead and tag the original history and each of the new commits\n# so we can recognize and refer to them. Here H-new corresponds to H' in my\n# other explanation, and so on.\ngit branch original-history master@{1}\ngit tag H-new HEAD\ngit tag G-new HEAD~1\ngit tag F-new HEAD~2\ngit tag E-new HEAD~3\ngit tag D-new HEAD~4\ngit tag C-new HEAD~5\ngit tag B-new HEAD~6\nshow --all\n\n# So now let's look at the solution. First we make a temporary branch from the\n# rewritten commit just prior to the ones from upstream (in this case, C').\ngit checkout -b temp C-new\nshow temp\n\n# Now re-pull from upstream, recreating the merge on top of your rewritten\n# commits.\ngit pull origin master\nshow temp\n\n# And now rebase the rewritten versions of all of the commits that came after\n# the merge. We know F-new is the first such rewritten commit, so its parent\n# (F-new^) becomes the upstream. We are rebasing onto the state we have in\n# temp, and we are rebasing the branch master (and the end result will go on\n# master).\ngit rebase --onto temp F-new^ master\nshow master\n"}]}