Search code examples
gitmergerebase

Git Rebase with BitBucket


I am running into an issue that is throwing me for a spin. I have code in my branch that conflicts with master (in my case develop). It is not a big issue since it is literally 7 lines of code easy to identify.

BitBucket is kind enough to let me know that in order to fix the merge conflict I need to do the following steps.

  • git checkout <branch in question>
  • git pull <master> (in my case develop)
    • fix conflicts
    • at this point the command line also provides this guidance
    • "Resolve all conflicts manually, mark them as resolved with "git add/rm <conflicted_files>", then run "git rebase --continue"."
    • Once I run "git rebase --continue", a new document shows up with the opportunity to change the original commit message. I only close it and the merge continues
  • git commit
    • git result into "Successfully rebased and updated <current branch>
  • git push origin HEAD
    • I would expect to have a successful message that everything is running great and as expected however I keep getting this message:

On branch <current branch> Your branch and '<current branch>' have diverged, and have 4 and 3 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)

nothing to commit, working tree clean

Ok no problem, lets run git pull... and we have to start all over again as if I had never fixed the merge conflicts.

I was reading other comments with similar issues and found someone pushing code with the -f flag (force). I tried and of course it worked, however my 7 line changes turned into multiple file changes. The merge conflict is gone but I am concern about bloating the main (develop) branch with unnecessary commits.


Solution

  • TL;DR: you need git push --force-with-lease origin HEAD.

    You're running into this issue because you are using rebase. (Bitbucket is not relevant here; I snipped that tag.)

    A rebase operation is, at its core, a command to Git of the form:

    • I have some old commits. They're... okay-ish, but not good enough.
    • Please remove the old commits, creating some new and improved replacement commits to use instead.

    When you are done, the old commits are gone(ish1), and you are now using the new-and-improved replacement commits.

    When you run git push origin HEAD, your Git resolves your name HEAD to your current branch name—whatever that name is—and acts as if you ran git push origin branch. This has your Git call up their GIt, over at origin, send over your new commits, and then ask them—politely—if they would please, if it's OK, set their branch name to identify the same last commit that your branch name identifies.

    Because you used rebase, this please set branch name ______ (fill in with name) to _______ (fill in with hash ID) amounts to instructions to their Git of the form: Throw away the same old commits I threw away, using these same replacement commits. But they don't know that your new replacement commits are replacements, at this point. They've quite forgotten anything they told you earlier. They see only that you're asking them to throw away some commits. So they say No! If I did that, I'd lose some commits.

    To overcome their reluctance to discard commits, you must send them a more-forceful command, rather than a polite request. You must tell them: Yes, I know this may discard some commits. Do it anyway! To do this, you must use the --force or --force-with-lease option on your git push command.

    The --force-with-lease option is "safer", in a sense: when you use this option, your command has the form: I'd like you to update _____ (fill in the blank with branch name). I believe your latest commit is _______ (fill in the blank with hash ID). If so, throw away some commits; use ______ (fill in the blank with hash ID). Your Git fills in all the blanks and sends this over. If their Git hasn't accumulated any new commits since the last time your Git talked with their Git, your replacement commits are the right replacements, and if they obey this forceful command, that does the trick.

    The --force option skips the safety check: Update ______ (fill in the blank with name) to _____ (fill in the blank with hash ID)! As long as nobody else added new commits since you started your work, this forceful command achieves the same result as the more-cautious --force-with-lease.

    When you use git pull instead of git push with a forceful option, you get their latest commits—which are the ones you threw out earlier while doing your replacements—and now you have the same problem. You would get this with any kind of pull, whether using a fetch-and-rebase pull, or a fetch-and-merge pull. (I personally recommend avoiding git pull: run git fetch yourself, then run either git merge or git rebase yourself, based on which one you intend. Then when it goes wrong, you know which command actually failed. When you use git pull to run two Git commands for you, and something fails, you may not even know which Git command failed. But some people find typing a few extra characters difficult. As you can see based on the length of my answer, I don't have this particular problem. 😀)


    1Git is quite greedy for commits, and reluctant to give any up. For this reason, and as a backup in case you change your mind, the old commits aren't really gone yet. You have, by default, at least 30 days before they will go away for real. Recovering them in that 30-day window is mildly painful, though.