git push --force-with-lease

It is usually not recommended to use git --force on a shared branch. Let's see how git push --force-with-lease can help.

Let’s suppose that Alina and Blake are both working on a branch called b1.

The starting point for Alina is as follows:

alina

ba34ea099d7dabe74c965b1mainorigin/b1origin/mainHEAD

Blake makes a change on the main branch and pushes it.

Her commit graph then looks like this:

blake

deebac7ba34ea099d7dabe74c965mainorigin/b1origin/mainHEAD

Alina is aware of the change and decides to rebase her branch on top of main.

alina

alina git:( b1 ) git fetch --all

remote: Enumerating objects: 5, done.

remote: Counting objects: 100% (5/5), done.

remote: Compressing objects: 100% (2/2), done.

remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0

Unpacking objects: 100% (3/3), 248 bytes | 124.00 KiB/s, done.

From ssh://remote.mygit.com/gitpowerup/arenas

   99d7dab..deebac7  main       -> origin/main

alina git:( b1 )

This updates her origin/main branch:

alina

deebac7ba34ea099d7dabe74c965b1mainorigin/b1origin/mainHEAD

She performs the rebase:

alina

alina git:( b1 ) git rebase origin/main

Rebasing (1/1)

Auto-merging file.txt

CONFLICT (content): Merge conflict in file.txt

error: could not apply ba34ea0... update

hint: Resolve all conflicts manually, mark them as resolved with

hint: "git add/rm <conflicted_files>", then run "git rebase --continue".

hint: You can instead skip this commit: run "git rebase --skip".

hint: To abort and get back to the state before "git rebase", run "git rebase --abort".

Could not apply ba34ea0... update

alina

Alina resolves the conflicts and completes the rebase.

alina

alina git rebase --continue

Successfully rebased and updated refs/heads/b1.

alina git:( b1 )

This gives Alina a new commit graph:

alina

37c9f1cdeebac7ba34ea099d7dabe74c965b1mainorigin/b1origin/mainHEAD

When she tries to git push, Git indicates that the history has been rewritten and that extra steps are required:

alina

alina git:( b1 ) git push

To ssh://remote.mygit.com/gitpowerup/arenas.git

 ! [rejected]        b1 -> b1 (non-fast-forward)

error: failed to push some refs to 'ssh://remote.mygit.com/gitpowerup/arenas.git'

hint: Updates were rejected because the tip of your current branch 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.

alina git:( b1 )

Meanwhile, Blake switches to the old b1 branch.

blake

blake git:( main ) git checkout b1

branch 'b1' set up to track 'origin/b1'.

Switched to a new branch 'b1'

blake git:( b1 )

Blake has this commit graph:

blake

deebac7ba34ea099d7dabe74c965b1mainorigin/b1origin/mainHEAD

She creates a commit and pushes it. Blake’s commit graph is now:

blake

63d0b3ddeebac7ba34ea099d7dabe74c965b1mainorigin/b1origin/mainHEAD

At this stage, the remote server has that commit graph, which is aligned with Blake’s:

arenas.git

63d0b3ddeebac7ba34ea099d7dabe74c965b1mainHEAD

Blake has added the line C to the file file.txt.

The content of file.txt for Blake is:

blake

blake git:( b1 ) cat file.txt

A

B

C

blake git:( b1 )

Meanwhile, for Alina, the content of file.txt is:

alina

alina git:( b1 ) cat file.txt

A

A.1

B

alina git:( b1 )

Alina decides to git push --force her rebased b1 branch:

alina

alina git:( b1 ) git push --force

Enumerating objects: 5, done.

Counting objects: 100% (5/5), done.

Delta compression using up to 8 threads

Compressing objects: 100% (2/2), done.

Writing objects: 100% (3/3), 270 bytes | 270.00 KiB/s, done.

Total 3 (delta 0), reused 0 (delta 0), pack-reused 0

remote: 

remote: Create a new pull request for 'b1':

remote:   http://localhost:3030/gitpowerup/arenas/compare/main...b1

remote: 

remote: . Processing 1 references

remote: Processed 1 references in total

To ssh://remote.mygit.com/gitpowerup/arenas.git

 + 63d0b3d...37c9f1c b1 -> b1 (forced update)

alina git:( b1 )

The remote server now has this commit graph:

arenas.git

63d0b3d37c9f1cdeebac7ba34ea099d7dabe74c965b1mainHEAD

Blake does a git fetch --all to get the latest changes from the remote server:

blake

blake git:( b1 ) git fetch --all

remote: Enumerating objects: 5, done.

remote: Counting objects: 100% (5/5), done.

remote: Compressing objects: 100% (2/2), done.

remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0

Unpacking objects: 100% (3/3), 250 bytes | 125.00 KiB/s, done.

From ssh://remote.mygit.com/gitpowerup/arenas

 + 63d0b3d...37c9f1c b1         -> origin/b1  (forced update)

blake git:( b1 )

Her commit tree shows that a divergence occurred:

blake

63d0b3d37c9f1cdeebac7ba34ea099d7dabe74c965b1mainorigin/b1origin/mainHEAD

And a git status confirms that:

blake

blake git:( b1 ) git status

On branch b1

Your branch and 'origin/b1' have diverged,

and have 2 and 2 different commits each, respectively.

  (use "git pull" to merge the remote branch into yours)

nothing to commit, working tree clean

blake git:( b1 )

In that case, to resync her b1 branch with the remote, a git reset --hard is in order:

blake

blake git:( b1 ) git reset --hard origin/b1

HEAD is now at 37c9f1c update

blake git:( b1 )

Blake’s commit graph is back in order:

blake

63d0b3d37c9f1cdeebac7ba34ea099d7dabe74c965b1mainorigin/b1origin/mainHEAD

But the content of file.txt shows that her change is lost (no more C line):

blake

blake git:( b1 ) cat file.txt

A

A.1

B

blake git:( b1 )

Let’s repeat with a safer push

We repeat the process:

  • Blake makes a change on the main branch and pushes it.
  • Then she switches back to the b1 branch.
  • Alina does a git fetch --all and then a git rebase of her b1 branch.

This is the final step of the rebase:

alina

alina git rebase origin/main--continue

file.txt: needs merge

You must edit all merge conflicts and then

mark them as resolved using git add

alina

And Alina’s commit graph is:

alina

ab97fab0dba9e137c9f1cdeebac7ba34ea099d7dabe74c965b1mainorigin/b1origin/mainHEAD

Alina added the line A.2 to file.txt.

Alina’s file.txt content is now:

alina

alina git:( b1 ) cat file.txt

A

A.1

A.2

B

alina git:( b1 )

Back on Blake’s machine, she is on the old b1 branch and makes a change that she pushes:

That gives her this commit tree:

blake

106c5730dba9e163d0b3d37c9f1cdeebac7ba34ea099d7dabe74c965b1mainorigin/b1origin/mainHEAD

And on the remote server, the commit graph is now:

arenas.git

106c5730dba9e163d0b3d37c9f1cdeebac7ba34ea099d7dabe74c965b1mainHEAD

On Blake’s machine, the content of file.txt is (she added the C line):

blake

blake git:( b1 ) cat file.txt

A

A.1

B

C

blake git:( b1 )

This time, Alina uses git push --force-with-lease to update the b1 branch on the remote:

alina

alina git:( b1 ) git push --force-with-leasegit push --force-with-lease

To ssh://remote.mygit.com/gitpowerup/arenas.git

 ! [rejected]        b1 -> b1 (stale info)

error: failed to push some refs to 'ssh://remote.mygit.com/gitpowerup/arenas.git'

alina git:( b1 )

Git this time indicates that her local state of b1 is not what is currently on the remote and aborts the git push.

To fix that situation, Alina must first retrieve the commits from the remote with a git fetch:

alina

alina git:( b1 ) git fetch --all

remote: Enumerating objects: 5, done.

remote: Counting objects: 100% (5/5), done.

remote: Compressing objects: 100% (2/2), done.

remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0

Unpacking objects: 100% (3/3), 257 bytes | 128.00 KiB/s, done.

From ssh://remote.mygit.com/gitpowerup/arenas

   37c9f1c..106c573  b1         -> origin/b1

alina git:( b1 )

That updates Alina’s commit graph:

alina

106c573ab97fab0dba9e137c9f1cdeebac7ba34ea099d7dabe74c965b1mainorigin/b1origin/mainHEAD

Alina can rebase her local b1 on top of the remote b1 with git rebase origin/b1:

alina

alina git:( b1 ) git rebase origin/b1

Rebasing (1/2)

Auto-merging file.txt

CONFLICT (content): Merge conflict in file.txt

error: could not apply 0dba9e1... update

hint: Resolve all conflicts manually, mark them as resolved with

hint: "git add/rm <conflicted_files>", then run "git rebase --continue".

hint: You can instead skip this commit: run "git rebase --skip".

hint: To abort and get back to the state before "git rebase", run "git rebase --abort".

Could not apply 0dba9e1... update

alina

Alina resolves the conflicts:

alina

alina git rebase --continue

Successfully rebased and updated refs/heads/b1.

alina git:( b1 )

This leads to the updated commit graph:

alina

da4d1e8106c573ab97fab0dba9e137c9f1cdeebac7ba34ea099d7dabe74c965b1mainorigin/b1origin/mainHEAD

And she can push again with git push --force-with-lease:

alina

alina git:( b1 ) git push --force-with-lease

Enumerating objects: 5, done.

Counting objects: 100% (5/5), done.

Delta compression using up to 8 threads

Compressing objects: 100% (2/2), done.

Writing objects: 100% (3/3), 279 bytes | 279.00 KiB/s, done.

Total 3 (delta 0), reused 0 (delta 0), pack-reused 0

remote: 

remote: Create a new pull request for 'b1':

remote:   http://localhost:3030/gitpowerup/arenas/compare/main...b1

remote: 

remote: . Processing 1 references

remote: Processed 1 references in total

To ssh://remote.mygit.com/gitpowerup/arenas.git

   106c573..da4d1e8  b1 -> b1

alina git:( b1 )

Alina’s commit graph is now:

alina

da4d1e8106c573ab97fab0dba9e137c9f1cdeebac7ba34ea099d7dabe74c965b1mainorigin/b1origin/mainHEAD

And the remote commit graph is now:

arenas.git

da4d1e8106c5730dba9e163d0b3d37c9f1cdeebac7ba34ea099d7dabe74c965b1mainHEAD

On Alina’s machine, the content of file.txt is:

alina

alina git:( b1 ) cat file.txt

A

A.1

A.2

B

C

alina git:( b1 )

On Blake’s machine, the content of that file is:

blake

blake git:( b1 ) cat file.txt

A

A.1

B

C

blake git:( b1 )

The line C is present on both machines, meaning that the changes made by Blake were not lost this time.