826

I am trying to understand the difference between

git push --force

and

git push --force-with-lease

My guess is that the latter only pushes to the remote if the remote has commits that the local branch doesn't have?

5
  • 2
    the "local remote tracking branch". Basically meaning the remote has to look like what your client expects it to look like. git help push has use-cases explaining its purpose (basically to keep you from trashing a change someone just pushed up). What's a little unclear to me is how the remote tracking branch works. But presumably typically it's going to need to look exactly how it looked last time you did a fetch or pull with no new commits.
    – zzxyz
    Commented Oct 15, 2018 at 19:51
  • 29
    @zzxyz: the actual implementation of --force-with-lease is similar to that of compare-and-swap instructions on modern CPUs: the one who wants the swap to occur supplies the expected value and the new value. The system doing the swap compares the expected value with the true current value, and does the swap if and only if the two are equal. With git push, the expected value is whatever is in the remote-tracking name, e.g., git push --force-with-lease origin X sends your own origin/X along with the new desired value; origin's Git tells you if it did the exchange, or not.
    – torek
    Commented Oct 15, 2018 at 20:28
  • 2
    If the Git at origin did the exchange, you are done. If not, you can run git fetch origin to pick up the new current value, rework your changes if needed, and run another force-with-lease compare-and-swap to try again.
    – torek
    Commented Oct 15, 2018 at 20:29
  • 4
    from v2.30 Release Notes: "git push --force-with-lease[=<ref>]" can easily be misused to lose commits unless the user takes good care of their own "git fetch". A new option "--force-if-includes" attempts to ensure that what is being force-pushed was created after examining the commit at the tip of the remote ref that is about to be force-replaced.
    – caduceus
    Commented Jan 7, 2021 at 13:13
  • I would name force-with-lease or at least think of it as --> safe-with-lease Commented Oct 9, 2022 at 5:36

5 Answers 5

858

force overwrites a remote branch with your local branch.

--force-with-lease is a safer option that will not overwrite any work on the remote branch if more commits were added to the remote branch (by another team-member or coworker or what have you). It ensures you do not overwrite someone elses work by force pushing.

I think your general idea surrounding the command is correct. If the remote branch has the same value as the remote branch on your local machine- you will overwrite remote. If it doesn't have the same value- it indicates a change that someone else made to the remote branch while you were working on your code and thus will not overwrite any code. Obviously if there are additional commits in remote then the values won't be the same.

I just think of --force-with-lease as the option to use when I want to make sure I don't overwrite any teammates code. A lot of teams at my company use --force-with-lease as the default option for a fail-safe. Its unnecessary in most circumstances but will save you lots of headache if you happen to overwrite something that another person contributed to remote.

I'm sure you looked at the docs but there might be some more wordy explanation contained in here:

https://git-scm.com/docs/git-push

5
  • 7
    This answer makes no sense because you can't "overwrite code" in Git. Perhaps you meant it doesn't make other people's commits unreachable? But it doesn't even do that.
    – Timmmm
    Commented Jun 6, 2023 at 16:52
  • This comment does not make sense, but for another reason: it's not obvious from this answer how this differs from just git push. It appears that git push does exactly the same thing described here. And from what I've picked up, --force-with-lease has a disadvantage in a form of possibility to overwrite remote if you did fetch, but did not do merge of a remote/pull. What's the --force-with-lease is for then? Commented Aug 15, 2023 at 22:17
  • i just rewrote remote branch by my version that was behind using --force-with-lease :(
    – sKopheK
    Commented Sep 26, 2023 at 11:58
  • @VitaliiLebediev the difference is that with normal push (i.e., no force) the push won't get through at all if for example the remote is ahead of your local branch. You can still force push (with and without lease) in that case. Similarly when the push cannot be fast-forwarded on the remote. Regarding force-with-lease after you already fetched: If you fetched, you are expected to look into the commit graph (or use other means) to make sure you really want the remote branch to point to that force pushed commit and drop any commits that your local repo knows about. Commented Jan 24 at 10:05
  • 4
    @sKopheK probably too late for your situation, but the advantage of --force-with-lease is that even if you dropped commits (intentionally or accidentally) from the remote branch, they are already in your local repository and you can reset the branch again via reflog. That means even if something goes wrong, it can be rolled back much more easily and force-pushed again within seconds, likely before anyone is affected (if you realize and fix it immediately). Commented Jan 24 at 10:07
135
+25

Looking for an answer drawing from credible and/or official sources.

The "compare and swap" mentioned by torek in the comments and in his other answer is further illustrated by the sources of Git itself.

the latter only pushes to the remote if the remote does not have commits that the local branch doesn't have?

That feature was introduced in this commit (Dec. 2013, Git v1.8.5-rc0)

--force-with-lease will protect all remote refs that are going to be updated by requiring their current value to be the same as some reasonable default, unless otherwise specified;

For now, "some reasonable default" is tentatively defined as "the value of the remote-tracking branch we have for the ref of the remote being updated", and it is an error if we do not have such a remote-tracking branch.

So "lease" means:

"force-with-lease": You assume you took the lease on the ref when you fetched to decide what the rebased history should be, and you can push back only if the lease has not been broken.

"You assume you took the lease on the ref": So "lease" is used to describe a safety mechanism for force pushing changes to a remote repository.

When you fetch from a remote repository, you essentially get the latest state of the repository at that time. Think of this as taking a "snapshot" or a temporary "lease" on the repository's state.
The "lease" implies that you are aware of the repository's state as of the last fetch and are operating under the assumption that this state has not changed since.

When you use git push --force-with-lease, you are telling Git: "I want to force push my changes, but only if the remote repository is in the same state as my last fetch."
In other words, you can push your changes as long as no one else has pushed any updates to the remote repository since your last fetch.
If someone else has introduced changes to the remote repository (thereby "breaking the lease"), the push will be rejected. That helps prevent accidental overwriting of others' work.

The sources still mention "cas" ("compare and swap"):

  • This option was originally called "cas" (for "compare and swap"), the name which nobody liked because it was too technical.
  • The second attempt called it "lockref" (because it is conceptually like pushing after taking a lock) but the word "lock" was hated because it implied that it may reject push by others, which is not the way this option works.
  • This round calls it "force-with-lease".
    You assume you took the lease on the ref when you fetched to decide what the rebased history should be, and you can push back only if the lease has not been broken.

So: "git push --force-with-lease vs. --force"

As I mentioned in "push --force-with-lease by default", Git 2.13 (Q2 2017) confirms that the option --force-with-lease can be ignored if a background process (like the ones you find in an IDE with a Git plugin) runs git fetch origin.
In that case, --force prevails.

As Pavlus adds in the comments:

it is not ignored per se, it is just now you have identical refs for local remote head and remote head, so --force-with-lease will behave correctly -- compare these two, and if in that interval of time between fetch and push, someone updated remote, it won't behave as --force, it will still fail.


Another difference: before Git 2.29 (Q4 2020), pushing a ref whose name contains a non-ASCII character with the "--force-with-lease" option did not work over smart HTTP protocol.
It would work with git push --force.

See commit cd85b44 (21 Jul 2020) by Brian m. Carlson (bk2204).
(Merged by Junio C Hamano -- gitster -- in commit c2796ac, 30 Jul 2020)

remote-curl: make --force-with-lease work with non-ASCII ref names

Reported-by: Frej Bjon
Signed-off-by: brian m. carlson

When we invoke a remote transport helper and pass an option with an argument, we quote the argument as a C-style string if necessary.
This is the case for the cas option, which implements the --force-with-lease command-line flag, when we're passing a non-ASCII refname.

However, the remote curl helper isn't designed to parse such an argument, meaning that if we try to use --force-with-lease with an HTTP push and a non-ASCII refname, we get an error like this:

error: cannot parse expected object name '0000000000000000000000000000000000000000"'  

Note the double quote, which get_oid has reminded us is not valid in an hex object ID.

Even if we had been able to parse it, we would send the wrong data to the server: we'd send an escaped ref, which would not behave as the user wanted and might accidentally result in updating or deleting a ref we hadn't intended.

Since we need to expect a quoted C-style string here, just check if the first argument is a double quote, and if so, unquote it.
Note that if the refname contains a double quote, then we will have double-quoted it already, so there is no ambiguity.

We test for this case only in the smart protocol, since the DAV-based protocol is not capable of handling this capability.
We use UTF-8 because this is nicer in our tests and friendlier to Windows, but the code should work for all non-ASCII refs.

While we're at it, since the name of the option is now well established and isn't going to change, let's inline it instead of using the #define constant.


Git 2.30 (Q1 2021) adds git push --force-if-includes

When a local branch that is based on a remote ref, has been rewound and is to be force pushed on the remote, "--force-if-includes" runs a check that ensures any updates to the remote-tracking ref that may have happened (by push from another repository) in-between the time of the last update to the local branch (via "git pull", for instance) and right before the time of push, have been integrated locally before allowing a forced update.


Does it mean that if I do fetch before pushing, then "force-with-lease" will happily overwrite the remote branch, even with other people's work in it? I wonder how well it plays with automatic fetching many IDEs do.

The --force-with-lease option is designed to protect against overwriting changes in the remote repository that you have not seen. When you use this option, Git checks that the remote branch has not changed since your last fetch. If the remote branch has been updated (e.g., by another collaborator's push), the --force-with-lease push will fail.

Automatic fetching by IDEs can lead to scenarios where you unintentionally overwrite changes made by others without reviewing them. This happens because your local view of the remote branch is kept up-to-date automatically, and --force-with-lease checks against this view.

So I would either deactivate the automatic fetch, or at least review the two branches (the current one, and its remote tracking counterpart) before making any force push (with lease or not).

7
  • 2
    Correction about last paragraph: it is not ignored per se, it is just now you have identical refs for local remote head and remote head, so --force-with-lease will behave correctly -- compare these two, and if in that interval of time between fetch and push, someone updated remote, it won't behave as --force, it will still fail.
    – Pavlus
    Commented Jul 28, 2020 at 9:50
  • @Pavlus Good point. I have included your comment in the answer for more visibility.
    – VonC
    Commented Jul 28, 2020 at 9:58
  • 1
    @Clockwork I have edited the answer to define lease. Think of it like renting a car (fetching the repository). You have it for a set period (until your next fetch), and you assume it is in the same condition as when you rented it. If you return the car (push your changes) and it is in a different condition than expected (someone else has made changes), there is a conflict that needs resolution.
    – VonC
    Commented Jan 2 at 16:07
  • 2
    Funny: 10 years later, Git 2.44 finally removes any "CAS" (Compare And Swap) reference from the code. See commit a762af3
    – VonC
    Commented Jan 4 at 21:23
  • 1
    @Osman-pasha True, that can be risky. I have edited the answer to include your comment and make that risky scenario more visible.
    – VonC
    Commented Jun 6 at 20:25
64

git push --force is destructive because it unconditionally overwrites the remote repository with whatever one have locally. git's push --force is strongly discouraged as it can destroy other commits already pushed to a shared repository. One of the most common causes of force pushes is when we're forced to rebase a branch.

For example. We have a project with a feature branch that both Alice and Bob are going to work on. They both clone this repository and start work. Alice initially completes her part of the feature, and pushes this up to the main repository. This is all well and good. Bob also finishes his work, but before pushing it up he notices some changes had been merged into master. Wanting to keep a clean tree, he performs a rebase against the master branch. Of-course, when he goes to push this rebased branch it will be rejected. However not realising that Alice has already pushed her work, he performs a push --force. Unfortunately, this will erase all record of Alice's changes in the central repository.

What --force-with-lease does is refuse to update a branch unless it is the state that we expect; i.e. nobody has updated the branch upstream. In practice this works by checking that the upstream ref is what we expect, because refs are hashes, and implicitly encode the chain of parents into their value.

Here is a good post regarding git push --force and git push --force-with-lease: –force considered harmful; understanding git’s –force-with-lease

5
  • 3
    what I don't understand - if you rebase with master, then you should be able to push without --force-with-lease? Why is --force-with-lease necessary after rebasing with master? Commented Oct 31, 2018 at 19:24
  • 3
    @AlexanderMills there may have some possibility that cause problem. If you are the last person that push this to master then no problem but there is another person who working with another task then it may cause serious problem. Say at first A - B - C was in master , At first Alice make it A - B - C - D - E and same time Bob make it A - B - C - F - G. If Alice is the the last person push in master after rebasing A - B - C - D - E - F - G will not cause any problem but another person Tom try to push A - B - C - D - E - R at same time in master disaster will happen !!!
    – Shakil
    Commented Nov 1, 2018 at 9:59
  • 7
    --force does not lose commits, it just detaches them. They can be resurrected by their hashes, as in git checkout <SHA> or git reset --hard <SHA>, assuming the maintainter of the remote repo does not do any GC or pruning operations. Commented Sep 27, 2019 at 15:06
  • 2
    "git's push --force is strongly discouraged as it can destroy other commits already pushed to a shared repository" this statement is strongly opinion-based. Doing a force push is part of a normal git rebase code review workflow. Also like mentioned already you can retrieve the commits even after doing this.
    – Étienne
    Commented Feb 14, 2020 at 10:15
  • 1
    Both this answer and the linked article make it sound like it's normal for a push to be rejected after rebasing a feature branch onto the main branch. That's just not true. After a successful rebase to main, a normal push should succeed.
    – Fritz
    Commented Aug 28, 2023 at 14:42
43

Assuming any pre-receive hooks on the server accept the push, this will always succeed:

git push --force

Whereas this runs a specific client-side check before proceeding:

git push --force-with-lease

You can run the specific check yourself manually. Here's the "lease-checking" algorithm:

  1. Figure out your current branch.

  2. Run git for-each-ref refs/remotes. Take note of the commit-id your git client thinks corresponds to the upstream state of your current branch.

E.g., if you are on branch "foo", take note of the commit-id associated with "refs/remotes/origin/foo".

  1. Determine the actual commit-id of the remote branch on the upstream git server right now.

  2. Only let the "git push" proceed if the commit-ids you extracted from step 2 and step 3 agree. In other words, only proceed if your local git clone's notion of upstream agrees with actual upstream.

There's a sad implication here: since git fetch updates all refs under "refs/remotes/origin/*" to their latest versions, this combination of commands is essentially identical to git push --force:

git fetch

# The command below behaves identically to "git push --force"
# if a "git fetch" just happened!

git push --force-with-lease

To work around this inherent weakness in git push --force-with-lease I try to never run git fetch. Instead I always run git pull --rebase whenever I need to sync with upstream, since git pull only updates a single ref under refs/remotes, keeping the "lease" of --force-with-lease useful.

3
  • 2
    if the check is client-side, is there potential for a race condition? i.e. somebody else pushes changes after the check but before the forced push?
    – craq
    Commented Dec 23, 2019 at 21:45
  • 9
    I think this answer is significantly easier to understand and more useful than the higher rated ones. Thank you for explaining this so well! Commented Sep 30, 2021 at 10:16
  • 1
    Good explanation, but I disagree with the conclusion. IMHO you should never use either --force or --force-with-lease if there is any chance that another person would push to the same branch (unless you personally warned all other contributors). If you rebased a feature branch onto a "shared" branch, then a normal git push should succeed (no need for any --force). If not, someone else pushed before you, and you need to rebase and push again. If you push to a "private" branch (i.e. no one else pushes to it), then it does not make a difference which --force you use.
    – Fritz
    Commented Aug 28, 2023 at 14:49
5

Force-with-lease is not necessarily safe. It just works as Sylvie said.

One note: In git a branch is just a pointer on a commit. And commits point to zero or more parent commits. Even if you changed the branch entirely with a hard git reset and a forced push or a push with - - force-with-lease without wanting it, that's not necessarily a big problem.

You can use your local git reflog to see how your local tip on the branches (Where was HEAD at that time?) has changed and reset and push the branch again. Then you only lose new commits on the remote branch, but even they might be restored by the team members.

1
  • 2
    Just a reminder for most people do not know. git list (and all its alias variants) can take the --reflog parameter which then lists also all commits that are normally invisible. Much more useful than the git reflog command alone. Commented Jul 23, 2023 at 16:21

Not the answer you're looking for? Browse other questions tagged or ask your own question.