Search code examples
gitgit-push

Git forced push: how to prevent overriding other user's changes


I often have to use git reset and git rebase to change a few recent commits (rearrange, merge multiple commits to one, ...). That requires to use the force option when pushing. Unfortunately, this also might overwrite other user's changes in the remote repository.

Why I need to rewrite the history? Because we are doing a lot of work in a feature-branch (convert from using Swing to SWT, to be specific) and have to keep it as short as possible. To achieve this, we often find, that some commits should go to the master before the feature-branch is forked.

How to make the forced git push fail, if I haven't fetched the latest changes from the remote repository?


Solution

  • If other people have seen the historical commits in the repository, and you later go and rewrite them, you're going to have problems. This is just part of the design of git.

    git can't really distinguish between your having rewritten commits but having an up-to-date repository and your not having rewritten commits but having commits that must be merged before pushing. The way an up-to-date push is detected is simply by checking that every commit in the destination ref is in the history of the ref you're pushing; this is known as a fast-forward. When you rebase, you delete commits from your local branch, and create new, similar commits. git cannot determine for sure that these new commits are equal to the old commits in the history, sees it as a pending merge, and requires a force to override this and overwrite whatever's already in the destination repository.

    So if you force, you overwrite whatever's in the destination. This is by design. Moreover, other people pulling from your repository will see problems because of this too. Let's say you flip the order of commits in your repository:

    A-------B-------C oldmaster
     \
      ------C'------B' master
    

    If some other user has a copy of oldmaster locally, and pulls from master, git will attempt to merge these two branches. This will double-apply the patches in C' and B'. Not good. Git can sometimes detect these problems at merge time, but even if it merges correctly, you will be left with:

    A-------B-------C------D master
     \                    /
      ------C'------B'----
    

    And now your rebased commits have come back from the dead.

    Using rebase there doesn't help either; you could just as well end up with:

    A------B------C------C'------B'
    

    In a simple case like this git may be able to detect the double-applied patches and drop them, but it is not guaranteed.

    So in conclusion: Don't rebase commits that have been made visible to others. Do all the rebasing you want when it's still a private branch you keep locally, or when it's a public branch with big warnings saying "DEVELOPMENT BRANCH - MAY BE REBASED - DO NOT BASE WORK ON THIS BRANCH". But the moment someone else is building on top of it, stop messing around with history.

    If you absolutely must be messing with history all the time with multiple users, you may find it better to work with the feature branch as a bag of patches, using StGIT, guilt, topgit, or similar. You can then work with them as patches, reorder the patches all you want (but not change the commits themselves!), then when it comes time to push changes upstream, linearize the patches on top of the original upstream branch.