Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is git push --force-with-lease always safe?

Tags:

git

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.

like image 785
Pluffee Avatar asked Dec 12 '19 16:12

Pluffee


People also ask

Is force with lease safe?

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.

Is git push force safe?

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.

What does git push force with lease do?

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.

Why should you never force push to a branch that you share with other developers?

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.


3 Answers

I would like to describe a plausible case where the --force-with-lease does not save you from overwriting your colleagues work.

It all begins with Bob

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 continues

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 continues

Bob is tasked to rebase Alices work on the current master branch, and does the following

  1. 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.

  2. Bob checks out the branch with git checkout feature/one

  3. Bob forgets to do a git pull
  4. 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
    
  5. 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

like image 51
Human Avatar answered Nov 15 '22 00:11

Human


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".

like image 42
VonC Avatar answered Nov 15 '22 01:11

VonC


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.

like image 33
dan1st Avatar answered Nov 14 '22 23:11

dan1st