I've always followed the rule not to modify the git history once it has been pushed to a remote repository.
But I am wondering if interactive rebasing into a push --force-with-lease bypasses this rule?
Is it perfectly safe for other users if the force-with-lease succeeds or are there any caveats to this strategy?
Thank you in advance for any input.
Git push --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.
The --force option for git push allows you to override this rule: the commit history on the remote will be forcefully overwritten with your own local history. This is a rather dangerous process, because it's very easy to overwrite (and thereby lose) commits from your colleagues.
Introducing Force with Lease Using this flag, git checks if the remote version of the branch is the same as the one you rebase, i.e. did someone push new commits when we were rebasing. The push is then rejected if the remotes branch is changed. It's like taking a lease on the version of the branch you started changing.
Force pushing changes the repository history and can corrupt your pull request. If other collaborators branch the project before a force push, the force push may overwrite commits that collaborators based their work on.
I would like to describe a plausible case where the --force-with-lease
does not save you from overwriting your colleagues work.
doing the following while having an up-to-date master branch checked out:
# Creating a new branch called feature/one
$ git checkout -b feature/one
# Do some changes and git add ...
$ git commit
# Push for the first time
$ git push --set-upstream origin feature/one
# Checkout another branch to work on something else
Situation on Bob's machine
...--F--G--H <-- master (HEAD)
\
o--o <-- feature/one
Alice picks up the work on feature/one and commits stuff on top of Bob's work and pushes her changes, in the mean time some unrelated pull requests are merged to the master branch. How Alice's working tree looks like
...--F--G--H--I--J <-- master (HEAD)
\
o--o--x--x <-- feature/one
Bob is tasked to rebase Alices work on the current master branch, and does the following
git pull
while he is on the master branch, which basically is a git fetch
and a git merge
The consequences of this step are important later.
Situation on Bob's machine:
...--F--G--H--I--J <-- master (HEAD)
\
o--o <-- feature/one
...--F--G--H--I--J <-- origin/master (HEAD)
\
o--o--x--x <-- origin/feature/one
Bob's machine now contains an up-to-date remote but changes in origin/feature/one are not yet merged to feature/one.
Bob checks out the branch with git checkout feature/one
git pull
Bob rebases his local branch on the master with git rebase -i origin/master
the situation on bobs machine looks like this:
...--F--G--H--I--J <-- master (HEAD)
\
o--o <-- feature/one
Bob thinks that he succsessfully rebased his branch and force pushes feature/one
to origin/feature/one
, because
Bob is a nice guy, he pushes with git push --force-with-lease origin feature/one
and expects that the option
--force-with-lease
will prevent his push operation, if he is about to overwrite other peoples work.
But the option will not save him, if I understand
this blog post correctly, --force-with-lease
sees no
difference between origin/feature/one on Bob's machine and the the actual origin/feature/one and therefore assumes
that Bob's working tree will not overwrite anything on the remote if forced pushed to it. The reason for the lack
of difference, lies in the excution of an implicit git fetch
as part of git pull
earlier (in step 1 of this
section) on a different branch.
After the push, the remote will look like this
...--F--G--H--I--J <-- master (HEAD)
\
o--o <-- feature/one
instead of
...--F--G--H--I--J <-- master (HEAD)
\
o--o--x--x <-- feature/one
Here is the relevant part of the blog postl linked above:
The fetch will pull the objects and refs from the remote, but without a matching merge does not update the working tree. This will make it look as if the working copy of the remote is up to date with the remote without actually including the new work, and trick
--force-with-lease
into overwriting the remote branch
It can be made safer with Git 2.30 (Q1 2021): "git push --force-with-lease[=<ref>]
(man)" 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.
It rejects a forced update of a branch when its remote-tracking ref has updates that we do not have locally.
See commit 3b5bf96, commit 3b990aa, commit 99a1f9a (03 Oct 2020) by Srinidhi Kaushik (clickyotomy
).
See commit aed0800 (02 Oct 2020) by Junio C Hamano (gitster
).
(Merged by Junio C Hamano -- gitster
-- in commit de0a7ef, 27 Oct 2020)
push
: add reflog check for "--force-if-includes
"Signed-off-by: Srinidhi Kaushik
Add a check to verify if the remote-tracking ref of the local branch is reachable from one of its "reflog" entries.
The check iterates through the local ref's reflog to see if there is an entry for the remote-tracking ref and collecting any commits that are seen, into a list; the iteration stops if an entry in the reflog matches the remote ref or if the entry timestamp is older the latest entry of the remote ref's "reflog". If there wasn't an entry found for the remote ref,
"in_merge_bases_many()
" is called to check if it is reachable from the list of collected commits.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.If the new option is passed without specifying "
--force-with-lease
", or specified along with "--force-with-lease=<refname>:<expect>
" it is a "no-op".
It is not safe.
See this atlassian blog post, which describes that git push --force-with-lease
is safer than git push -f
. However, it partically overwrites the remote making it not safe.
But --force has a lesser-known sibling that partially protects against damaging forced updates; this is --force-with-lease.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With