Skip to content

Instantly share code, notes, and snippets.

@brandonsturgeon
Last active December 9, 2021 03:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save brandonsturgeon/172e356fa0247ac930ae6f8c2856ee12 to your computer and use it in GitHub Desktop.
Save brandonsturgeon/172e356fa0247ac930ae6f8c2856ee12 to your computer and use it in GitHub Desktop.
Git CLI: Automatically rewrite --force to --force-with-lease

Intro

Do you rebase often? Ever forget to --force on your subsequent push? This annoyance and impatience could tempt you into making a new alias, like:

git config --global alias.pushf 'push --force'

Yes, this will save you time. But will it save you headache? Maybe not.

--force is a destructive command by definition. It changes history.

In the context of a rebase, this makes sense. You changed history, you need the repository to reflect those changes.

However, in a team enviornment, you can't be ever 100% confident that the remote is exactly as you expect.

Maybe someone pushed to your branch and was in the process of messaging you. Maybe your local copy wasn't actually up-to-date before you made your changes.

Whatever the case, running --force could unintentionally overwrite someone else's work.

--force-with-lease

--force-with-lease is a safer alternative to --force. I'm not a Git expert, so I've included the relevant section from the git help pages below.

Click to read more

Usually, "git push" refuses to update a remote ref that is not an ancestor of the local ref used to overwrite it.

This option overrides this restriction if the current value of the remote ref is the expected value. "git push" fails otherwise.

Imagine that you have to rebase what you have already published. You will have to bypass the "must fast-forward" rule in order to replace the history you originally published with the rebased history. If somebody else built on top of your original history while you are rebasing, the tip of the branch at the remote may advance with her commit, and blindly pushing with --force will lose her work.

This option allows you to say that you expect the history you are updating is what you rebased and want to replace. If the remote ref still points at the commit you specified, you can be sure that no other people did anything to the ref. It is like taking a "lease" on the ref without explicitly locking it, and the remote ref is updated only if the "lease" is still valid.

--force-with-lease alone, without specifying the details, will protect all remote refs that are going to be updated by requiring their current value to be the same as the remote-tracking branch we have for them.

The takeaway here is that --force-with-lease offers an extra layer of protection against accidents that could cause you and your team to lose work.

Why don't I just make an alias for --force-with-lease?

This is a good question. Click here to read my ramblings on the topic. Personally, I'd argue that any forceful command should be intentional and explicit.

Aliases can make one sloppy and careless. I'd counter this question with another question:

"To save myself some time, why don't I just make an alias for rm so it never prompts me?"

rm="rm -rf"

That doesn't seem like a very good idea, does it? One careles typo and you could cause enormous damage.

In an ideal world, we'd learn to type (perhaps even with tab-compeletion) --force-with-lease every time. However, we're only human. We're bound to instinctively type -f at least once.

Instead, think of this tool as a cessation utility. It'll help you ween off of -f and --force.

About

This script will automatically replace all uses of -f and --force with --force-with-lease.

And that's it. That's all it does. Use git exactly as you would normally, but with the extra safety that comes from --force-with-lease.

If you're sure you want to use --force, you can use -ff to bypass the replacement.

Installation

Installing the wrapper script

Copy the contents of .gitwrapper.sh into ~/.gitwrapper.sh (or whatever you'd like to call it).

Then, give it execute permissions:

chmod +x ~/.gitwrapper.sh

Enabling the wrapper

Edit your ~/.bashrc / ~/.zshrc or whatever your shell runs on startup to include the git wrapper:

git=~/.gitwrapper.sh

And that's it! You're all set!

#!/bin/bash
YELLOW='\033[1;33m'
WHITE='\033[1;37m'
replaced=0
function alertReplacement {
if [ "$replaced" = 0 ]; then
echo -e "${YELLOW}Replacing '$arg' with safer '--force-with-lease' (use '-ff' to skip this check)${WHITE}"
fi
}
ARGS=()
for arg in "$@"; do
if [ "$arg" = "push" ]; then
ispush=1
elif [ "$ispush" = 1 ]; then
if [ "$arg" = '-f' ] || [ "$arg" = '--force' ]; then
alertReplacement
arg="--force-with-lease"
replaced=1
fi
fi
ARGS+=( "$arg" )
done
if [ "$replaced" = 1 ]; then
echo -e "${YELLOW}New command: 'git ${ARGS[*]}'${WHITE}"
# Gives the user a little time to cancel their command if they mean to use -ff
sleep 0.2
fi
git "${ARGS[@]}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment