- -force considered harmful; understanding git’s –force-with-lease
- By Steve Smith
- The Nuggets translation Project
- Permanent link to this article: github.com/xitu/gold-m…
- Translator: LeviDing
- Proofread by: Yifili09
Git’s push –force is destructive because it overwrites remote repositories unconditionally, no matter what you have locally. With this command, it is possible to override any changes pushed by team members during this period. However, there is a better way to do this. The -force-with-lease option can help you when you need to force push, but you still need to make sure you don’t overwrite other people’s work.
Git’s push-force directive is notoriously not recommended because it can break other content already committed to a shared library. This is not always fatal (if the changes are still in some colleague’s local workspace, they can be remerged later), but it is ill-advised and, at worst, disastrous. This is because the –force command option forces the branch’s head pointer to point to your personal modification record, ignoring other changes made at the same time as yours.
One of the most common reasons for force pushing is when we are forced to rebase a branch. To illustrate this point, let’s look at an example. We have a project that has a functional branch that Alice and Bob are working on at the same time. They both git clone… Made this warehouse and began to work.
Initially, Alice completes the functionality she is responsible for and pushes it to the main repository. There’s nothing wrong with that.
Bob also finished his work, but before pushing, he noticed that some changes had been merged into the Master branch. To keep a clean working tree, he performs a rebase on the main branch. Of course, when he pushes the branch through Rebase it will be rejected. However, Bob does not realize that Alice has pushed her work. Bob executes push –force. Unfortunately, this clears all changes and records that Alice made in the remote main repository.
The problem here is that Bob, who is doing the forced push, doesn’t know why his push is being rejected, so he thinks it’s due to Rebase, not a change in Alice. This is why –force should be eliminated when collaborating on the same branch; And through the workflow of the remote main warehouse, any branch can be shared.
But — Force has a little-known relative that partly prevents structural damage from being forced to update; It is force-with-lease.
Force-with-lease is used to refuse to update a branch unless it is in the desired state. Nobody updates the branch upstream. In fact, checking the upstream references is what we would expect, since the references are hashes, and implicitly encoding the paternity chains to their values.
You can tell –force-with-lease exactly what to check, and by default the current remote reference is checked. What this means in practice is that when Alice updates her branch and pushes it to the remote repository, the reference pointer to the branch will be updated. Now, unless Bob pulls from the remote repository, local references to the remote repository will expire. When he pushes with –force-with-lease, Git checks for local and remote references and rejects Bob’s force-push. –force-with-lease effectively allows you to force push only when no one else is updating the branch upstream. It’s like a –force with a seat belt. A quick demonstration of it might help illustrate this point:
Alice has made some changes to the branch and has pushed it to the remote main repository. Bob now rebases the master branch of the remote repository:
ssmith$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: Dev commit # 1
Applying: Dev commit # 2
Applying: Dev commit # 3Copy the code
After rebase, he tried to push his changes, but the server refused because it would overwrite Alice’s work:
ssmith$ git push
To /tmp/repo
! [rejected] dev -> dev (fetch first)
error: failed to push some refs to '/tmp/repo'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ... ') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.Copy the code
But Bob thinks this is due to the Rebase operation and decides to force push:
ssmith$ git push --force To /tmp/repo + f82f59e... c27aff1 dev -> dev (forced update)Copy the code
However, if he uses –force-with-lease, he gets a different result, because Git checks the remote branch and finds that the fetch hasn’t actually been updated since the last time Bob used it:
ssmith$ git push -n --force-with-lease
To /tmp/repo
! [rejected] dev -> dev (stale info)
error: failed to push some refs to '/tmp/repo'Copy the code
Of course, there are a few caveats about Git here. As shown above, it only works if Alice has already pushed her changes to the remote repository. This is not a serious problem, but if she wants to change something she committed, she will be prompted to merge when she goes to the pull branch.
A more subtle problem is that there are ways to fool Git into thinking that the branch hasn’t been modified. In normal use, the most common case where this happens is when Bob uses Git fetch instead of Git pull to update his local copy. Fetch pulls objects and references from the remote repository, but no matching merge updates the working tree. This will make it look as if the local repository has been updated synchronously with the remote repository, but the local repository has not, and trick the –force-with-lease ‘command into successfully overwriting the remote branch, as in this example:
ssmith$ git push --force-with-lease
To /tmp/repo
! [rejected] dev -> dev (stale info)
error: failed to push some refs to '/tmp/repo'ssmith$ git fetch remote: Counting objects: 3, done. remote: Compressing objects: 100% (3/3), done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From /tmp/repo 1a3a03f.. d7cda55 dev -> origin/dev ssmith$ git push --force-with-lease Counting objects: 9, done. Delta compression using up to 8 threads. Compressing objects: 100% (6/6), done. Writing objects: 100% (9/9), 845 bytes | 0 bytes/s, done. Total 9 (delta 0), reused 0 (delta 0) To /tmp/repo d7cda55.. b57fc84 dev -> devCopy the code
The simplest answer to this question is simply to say “don’t fetch the branch without merging” (or more commonly pull, which includes the first two), But if for some reason you want to fetch before uploading code with –force-with-lease, there is a safer way to do it. Like so many git properties, references are just Pointers to objects, so we can create our own. In this case, we can create a copy of the “savepoint” for the remote warehouse reference before the fetch. We can then tell –force-with-lease to use this as a reference value instead of the remote reference that has been updated.
To do this, we use Git’s update-ref feature to create a new reference to hold the state of the remote repository prior to any rebase or FETCH operation. This effectively marks the start of the push to the remote work node. Here we save the state of the remote branch dev to a new reference named dev-pre-rebase:
ssmith$ git update-ref refs/dev-pre-rebase refs/remotes/origin/devCopy the code
In this case, we can do rebase and fetch, and then use the saved ref to protect the remote repository in case someone makes changes while working:
ssmith$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: Dev commit # 1
Applying: Dev commit # 2
Applying: Dev commit # 3ssmith$ git fetch remote: Counting objects: 3, done. remote: Compressing objects: 100% (3/3), done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From /tmp/repo 2203121.. a9a35b3 dev -> origin/dev ssmith$ git push --force-with-lease=dev:refs/dev-pre-rebase To /tmp/repo ! [rejected] dev -> dev (stale info) error: failed to push some refs to'/tmp/repo'Copy the code
–force-with-lease is a useful tool for git users who sometimes need to force push. However, this is not a panacea for all the risks of the –force operation, and should not be used without understanding its inner workings and its caveats.
In the most common use cases, however, developers simply pull and push in the normal way. With occasional use of rebase, this command provides some much needed protection against forced push damage. Therefore, I expect that in a future version of Git (but probably not until 3.0), it will become the default behavior of –force, and the current behavior will be relegated to options that show its actual behavior, such as — force-reaction-remote.
The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. Android, iOS, React, front end, back end, product, design, etc. Keep an eye on the Nuggets Translation project for more quality translations.