How I Messed Up Our Repo and Ideas for Improving Git UX
I just found out that I was accidentally responsible for removing features from our development branch.
The problem turned out to be this (terrible!) setting in my .gitconfig:
default = matching
When push.default is set to “matching,” doing a `git push -f` without specifying a branch force-pushes ALL branches, which means lots of dangerous history-rewriting.
Instead of “matching,” I should have used “simple,” which is the default for push.default. “simple” gives a nice error message when you try to git push on a branch that isn’t tracking a remote branch:
fatal: The current branch feature/blue-dots has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin feature/blue-dots
I’m used to the “simple” behavior but had accidentally set up a new machine to use “matching,” since I skimmed this error message too quickly.
Lessons for Me
- Don’t rush through global config
- Be really careful with anything that ends in -f or — force
Could Git and Github Have Helped Avoid the Mistake?
It would be helpful to have more observability into the results of Git actions. Messages like this should have alerted me that I was doing something dumb, but I still think the feedback could have been better:
Total 0 (delta 0), reused 0 (delta 0)
+ b94d674...b21e39b dev -> dev (forced update)
+ b94d674...b21e39b feature/blue-dots -> feature/blued-dots (forced update)
The statistics at the top of the message are indicate how many objects changed, but don’t give any insight into how the history changed.
This message would have been more helpful:
+ b94d674...b21e39b dev -> dev (forced update, 2 commits orphaned on remote branch)
… b94d674 --> 0ae9347 --> b21e39b (dev)
… b94d674 (dev)
Here’s what’s different about the enhanced message:
- I added a warning about “orphaned” commits: commits are still on the remote until they are garbage-collected, The “orphaned” terminology is the best I could come up with: 0ae9347 and b21e39b will eventually be garbage-collected because “dev” ref points to an earlier commit.
- I added some ASCII art that shows how the remote changed as a result of the force push.
Most actions in Git have a way to undo them, using things like reflog, reset, and rebase. But it would be really nice if Git had an “undo” for actions that don’t affect other users. For example, in this case, if no other users have pulled from the remote, then I should be able to “git undo” to easily restore the remote dev branch to the way it was before the force push.
Awareness of Workflows
The “push.default = matching” setting may make sense for solo devs, but rarely makes sense for shared repos. Either Git or Git hosts (like Gihub and Bitbucket) could help by having more awareness of team workflows, which they could use to warn users about questionable actions. For example, if we could select “centralized repo, gitflow, rebase instead of merge” on Github, then Github could give us hints if/when weird stuff happens with regard to our intended workflows. Such as wiping out commits on a shared branch.
Ultimately, the accidental history-rewriting is my mistake, but I still like to think about how our tools could make it easier to stay safe.
The things I’m suggesting are minor tweaks: Nikita Proponov’s blog has a deep exploration of radical enhancements to the Git user experience.