Safely force pushing with Git

DateReadtime 2 minutes Tags

I want to modify the most recent commit but its already been pushed to origin. Using the --force-with-lease flag I can "more safely" push to origin and overwrite the existing data.

The --force-with-lease flag checks to make sure the remote repo matches the local cache. In other words, it ensures that another commit hasn't been pushed to the remote that is missing locally.

Setup

Two commits, I want to change the most recent commit:

$ git logg -n 2
* f3c2993 (HEAD -> master, origin/master) commit 1
* ef1318d initial commit

Change most recent:

$ git commit --amend -m "commit 2"

Current status after amending commit, notice origin and local have diverged:

$ git logga -n 3
* 5e00821 (HEAD -> master) commit 2
| * f3c2993 (origin/master) commit 1
|/
* ef1318d initial commit

Regular push fails:

$ git push origin master
To git@example.com:user/repo.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'git@example.com:user/repo.git'
hint: Updates were rejected because the tip of your current master is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Since, the local copy doesn't linearly follow the remote copy a plain push fails.

Need to force push

The normal --force flag only allows for one outcome because is doesn't check the state of the remote before overwriting it.

The --force-with-lease allows two outcomes: a "happy path" where the force occurs and a "happier path" where the force push fails (saving you from potentially losing commits) because the repository doesn't match our local expectation of the remote (likely a push from another clone of the repository)

Happy Path

Push with lease succeeds:

$ git push origin master --force-with-lease
Counting objects: 8, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (7/7), done.
Writing objects: 100% (8/8), 779 bytes | 0 bytes/s, done.
Total 8 (delta 5), reused 0 (delta 0)
To git@example.com:user/repo.git
   f3c2993..5e00821  master -> master

State after push:

$ git logga -n 2
* 5e00821 (HEAD -> master, origin/master) commit 2
* ef1318d initial commit

Happier Path

Push with lease fails:

$ git push --force-with-lease
To git@example.com:user/repo.git
 ! [rejected]        master -> master (stale info)
error: failed to push some refs to 'git@example.com:user/repo.git'

State after push (same as before):

$ git logga -n 3
* 5e00821 (HEAD -> master) commit 2
| * f3c2993 (origin/master) commit 1
|/
* ef1318d initial commit

Fetching the remote will pull down the changes and allow you to inspect the commits:

$ git fetch --all
Fetching origin
remote: Counting objects: 1, done.
remote: Total 1 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (1/1), done.
From git@example.com:user/repo.git
   5e00821..850e9c0  master     -> origin/master

However be careful with fetching, as it will update our knowledge of the remote so the next --force-with-lease would likely succeed.

Note

I use two git aliases in this post for git log:

logg   = log --color --date-order --graph --oneline --decorate
logga  = log --color --date-order --graph --oneline --decorate --all