Search code examples
gitgithubrebasegit-history

Github: Fetch and rebase fork on forked master


I've created a fork of a project, and added ~40 commits to our master. At some point in time I thoughtlessly rewrote history with a forced push because I couldn't push "for no reason at all" and sometimes you just want to see the world burn.

Now everything is fine, but the last ~100 commits on upstream that are also on my repo are no longer considered the same: I'm seeing "240 commits ahead" in stead of "40 commits ahead".

Is it possible to fetch the upstream master and rebase our master's commits on it, and force-push it back to our master so that ours and theirs are in sync for all previous commits except mine? If so, how? Please be specific.


Solution

  • I assume you have a clean sandbox where origin points to your fork, and you have access to the upstream repo via a different URL. I also assume origin/master and master in your sandbox are in sync.

    With these assumptions, this should work:

    git remote add upstream <upstream_url>
    git fetch upstream
    git checkout master
    git rebase upstream/master
    

    Hopefully the rebase will work and not introduce any duplicate commits. If it doesn't, it might even highlight why you had to force push in the first place.

    Before you start the rebase, git log --graph --decorate --all (or gitk -all or any other visual Git log replacement that shows the full graph) might show you why you had trouble.

    EDIT: an alternative and more conservative approach is to use git cherry-pick. The rebase solution relies on Git recognizing that the history that should be common consists of commits already present on upstream/master. But instead of rebasing, you can identify the parent of the 1st commit you want to keep, let's say origin/master~40 if you really have exactly 40 commits to keep, and add those commit at the end of upstream/master:

    git remote add upstream <upstream_url>
    git fetch upstream
    git checkout master
    git reset --hard upstream/master
    git cherry-pick origin/master~40..origin/master
    

    This gives you a new master that explicitly starts at upstream/master and adds just the new history you want.

    Note the --hard in git reset --hard upstream/master: as pointed out by OP in the comments, this is needed to make sure you start from a clean state before cherry-picking. But first make sure you didn't have anything uncommitted you wanted to save.

    Sanity check: after the cherry-pick (or the rebase), git diff master origin/master should return nothing, or again point you to other problems you need to handle. END EDIT

    Once the rebase or cherry-pick is complete and you've thoroughly convinced yourself this new history is what you want to keep:

    git push -f origin master
    

    should bring your fork back to having just 40 commits ahead of upstream.

    Caveat: I did not test the rebase solution, but I'm fairly confident it should work, based on your description of the situation. I've used the cherry-pick solution with success is similar situations, however. If you try either method, please report back on your success or anything wrong that would need adjustment.