Git push is not what you think

tldr;

  • git’s default configuration with regards to push is potentially very dangerous.
  • make sure you’ve run git config --global push.default current
  • There are other options for push.default but make sure you read the docs before setting them.
  • setting current as your default behavior means no more complaints about setting upstream when pushing.

Perception vs. Reality vis-à-vis git push

When it comes to git push most people think “It pushes my current branch’s updates up to the remote server” but that’s only a small part of what’s happening, and ignorance about the rest can leave you with very upset coworkers. I know, because that’s exactly what happened to me today when I ran git push -f on a coworker’s computer that happened to have the default configuration.

I mistakenly believed that “Hey, git push just pushes the current branch up, so git push -f will just force push this current branch up. I want to wipe out history on this topic branch, so no worries.”

git push

“Updates remote refs using local refs, while sending objects necessary to complete the given refs.”

Note that nothing in that statement is singular: refs… plural.

push (in its default configuration) is effectively the opposite of fetch. Fetch pulls down all the changes from branches you’re tracking. push uploads all your changes to branches you’re tracking. Now, normally this won’t get you in trouble. The branches you’re not currently working are either: older (or current) versions of what’s on the remote repo (in which case nothing happens), or incompatible versions of what’s up there (in which case they get rejected), or possible new commits that can safely be updated. The latter case can be annoying, but it’s rarely that bad, and can be easily backed out.

The problem is when you add -f to push in a default configuration and don’t specify what repo and branch you’re pushing to. This essentially says “Hey remote repo. All these branches I’m tracking? Yeah, make yours match. No. I don’t care that I’m blowing away work. You can take those rejections and shove them where the sun don’t shine.”

The more remote branches you’re tracking, the more damage it can do. And, you’re probably tracking master, which is usually the most important branch in any repo, which means there’s a good chance you’ll be doing some notable damage.

But, if you change your default configuration by running git config --global push.default current then the default behavior stops being “everything I can find a matching name for” and starts being “the current branch” which is what everyone generally assumes is happening anyway. People don’t want, or expect this “reverse fetch” behavior. They expect something akin to “my branch will go up”.

Making this change to push’s default behavior also means that when you do do git push on a brand new branch it won’t complain about you not having set upstream. Instead it will just create a new remote branch for you that matches the current (new) branch.

What to do when you’ve run push -f

In the default configuration?

  1. pray
  2. apologize profusely to everyone who’s working on your repo.
  3. ask very nicely if every could please do a push to all the branches they’re tracking.
  4. thank the deit(y/ies) that someone came up with a distributed version control system where everyone’s got a legitimate copy of the repo.

The only circumstance under which 3 will fail to recover the data is if you did the most recent work that was pushed to a remote branch, then reset or rebased your local copy of it to some prior state, then force pushed before anyone else had a chance to download your changes. Alternately, everyone hates you and refuses to push their copy in order to force you to recreate all the work you blew away. If that’s the case you should probably go look for another team.