Search code examples
gitbitbucketatlassian-sourcetree

How to rebase pushed branches without ruining tree?


I'm the only developer working in a repository, so there's no issue with ruining peoples' flow. However when I rebase locally and then try and push it to Bitbucket, it comes back telling me I need to pull the latest changes. I do that, and the rebase has completely ruined my clean tree.

Is there a way to push a rebase to the server without then having an additional "Merge branch" commit as part of it?

Thanks!


Solution

  • Rebase is fundamentally a "copy some commits, then discard the old commits in favor of the new and supposedly improved commits" operation.

    Consider, for instance, this situation:

    ...--A--B--E--F   <-- master
             \
              C--D--G   <-- feature (HEAD)
    

    You've finished your feature feature, but for whatever reason, you had to go create two commits on master while you were working. So now feature could stand to be re-written.

    No commit can ever change, so it's not actually possible to replace C with a new-and-improved variant, but we can make a new-and-improved C' to replace C anyway, using a temporary branch or Git's "detached HEAD" mode:

                    C'  <-- HEAD
                   /
    ...--A--B--E--F   <-- master
             \
              C--D--G   <-- feature
    

    Commit C' does to commit F what commit C does to commit B. The old (and now lousy) C has B as its parent, while the new improved C' has F as its parent. So now we need to copy D to a new-and-improved D', and then do the same again for G:

                    C'--D'--G'  <-- HEAD
                   /
    ...--A--B--E--F   <-- master
             \
              C--D--G   <-- feature
    

    We're now ready for git rebase's last trick. It peels the name feature off of commit G and make feature point to G' instead:

                    C'--D'--G'  <-- feature (HEAD)
                   /
    ...--A--B--E--F   <-- master
             \
              C--D--G   [abandoned]
    

    Since we can't even see the abandoned commits, in the local repository on your laptop, it looks as though history has always been this way: that you wrote commit C' based on F, and so on. The hash IDs look random; no one but you will ever know about all this.

    Only ... you did a git push of commits C-D-G, or at least some of them, to Bitbucket. They have your old-and-lousy commits, pointed-to by their branch name feature. You send them your shiny new ones—git push origin feature—and at the end of this, your Git asks them politely to move their feature name to point to commit G' instead of G.

    This would, of course, cause them to abandon commit G in favor of the new and improved G'. That's what you want them to do. But they will say: No, if I do that, I'll lose my precious commit G!

    All you have to do is tell them—or have your Git tell them—yes, I know you could lose some commits, but do it anyway! That is, instead of a polite request, you have your Git send them a forceful command.

    The way you do this is to add --force or --force-with-lease to your git push command. That turns the polite request, Please, if it's OK, set your feature to a command: Set your feature!

    The difference between --force and --force-with-lease is that the latter adds a safety check first. Instead of just saying Set your name feature to point to commit G'!, your Git will say: I think, based on my information, that your feature points to commit G. If so, set it to point to G' instead. If not, let me know that I goofed.

    The safety check is obviously usually good, but it's also unnecessary if you're the only one who ever puts new commits into the repository over on Bitbucket.