Lesson 32 · Recovery
Recover mistakes after pushing
Repair a mistake that others may already have: prefer a new commit, and when you must rewrite a branch you own, force with a lease pinned to the exact remote tip you reviewed.
- Level: Advanced
- 8 min read
- 4 min video
- Updated
- Before this lesson: Push commits and publish a branch, Revert a change in shared history, Find a lost commit with reflog
Progress saved in this browser.
Playing loads the video from YouTube (privacy-enhanced mode).
What you will learn
- Choose between an additive repair and a rewrite for a pushed mistake.
- Explain what a lease checks and why the expected commit matters.
- Predict a stale-lease rejection and keep a teammate's work.
- Know what GitTree keeps before it force pushes.
Before you start
Two situations on a local rehearsal server. On shared, a bad commit is already pushed and others may have it. On lease, you pushed a draft and then rewrote it locally, but Leo pushed a newer commit to the same branch in the meantime.
Everything is local and on the Free plan (GitTree asks you to sign in): no hosting account, no provider, no internet. Every identity is fictional (example.com). Never run this lesson in a real project.
Make the changes in GitTree or with the native Git commands, not both in the same copy. Reading the result is always safe. The menu labels come from GitTree 2.0's own strings and the native commands were run with Git 2.43 in disposable copies. The GitTree steps were not run on macOS, Windows or Linux.
Concept and state effects
If teammates may have the bad commit, repair it with a new commit (a revert, lesson 28) and push normally: history keeps the mistake and nobody's clone is invalidated. Rewrite a published branch only when you own it.
A lease makes a force push conditional: Git replaces the remote branch only if it still points where you expect. --force-with-lease=<ref>:<commit> names the expected commit. A bare --force-with-lease uses your remote-tracking ref, and a git fetch moves that ref, so fetching and then pushing can overwrite what you just downloaded. GitTree pins the commit last fetched, shows it in its dialog and keeps the replaced tip under refs/ogt/trash.
A lease does not decide whether you may overwrite a teammate's work. It only protects you from overwriting something you have not seen.
Prepare the exact project state
Start from a separate ordinary copy of lesson 18's result with a new local bare repository as its only remote. RECOVERY_BAD is the bad stylesheet; LEASE_A, LEASE_B and LEASE_REWRITE are the three versions of a note.
Complete file inputs
Save each file below exactly as shown: UTF-8, LF line endings and one final newline, without the Markdown fence lines. Files not listed keep their contents from M.
styles.css — RECOVERY_BAD
:root {
color-scheme: light;
--page: #f7f8fa;
--surface: #ffffff;
--text: #f7f8fa;
--link: #154ab5;
--line: #bac4d0;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
background: var(--page);
color: var(--text);
font: 1rem/1.6 system-ui, sans-serif;
}
a {
color: var(--link);
}
.site-header,
main,
footer {
width: min(68rem, 100% - 2rem);
margin-inline: auto;
}
.site-header {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 1rem;
padding-block: 1rem;
}
.hero {
max-width: 48rem;
padding-block: 3rem;
}
h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
line-height: 1.2;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(16rem, 100%), 1fr));
gap: 1rem;
}
.feature-card {
padding: 1rem;
border: 1px solid var(--line);
border-radius: 0.75rem;
background: var(--surface);
}
section {
margin-block-end: 2rem;
}
button,
input {
font: inherit;
}
button,
input {
padding: 0.5rem 0.75rem;
}
:focus-visible {
outline: 3px solid var(--link);
outline-offset: 3px;
}
form {
display: grid;
max-width: 28rem;
gap: 0.5rem;
}docs/lease-note.md — LEASE_A
# Private release rehearsal
The reviewed draft is still private.docs/lease-note.md — LEASE_B
# Private release rehearsal
Leo added a review that must not be overwritten.docs/lease-note.md — LEASE_REWRITE
# Private release rehearsal
This alternative draft has not seen Leo's review.In GitTree
Open the palette with Command+Shift+P (Ctrl+Shift+P on Ubuntu).
- Build the rehearsal as the next section shows: the bad commit pushed to
shared, and theleasebranch with Leo's newer commit on the server. - Additive repair. On
shared, right-click the bad commit and choose Revert Commit, then click Push. This is a normal push; do not choose Force Push (With Lease)…. - Stale lease. On
private/lease, rewrite the draft locally so the branch no longer contains the pushed commit, and try to push it with the toolbar Push. GitTree first asks where to push, becauseprivate/leasetracksorigin/leaseunder another name: choose Push to origin/lease. A dialog titled "The remote refused private/lease" then offers Fetch Only, Rebase and Push, Merge and Push and Force Push…. - Choose Fetch Only first and read the graph: the remote branch now has Leo's commit, which your rewrite does not contain. That is a teammate's work.
- Open Force Push…. The dialog "Force push private/lease to origin/lease?" says commits that your branch lacks will be removed from the remote, that the push goes ahead only while the remote is still at the commit shown, and that the replaced tip is kept under
refs/ogt/trash. Cancel; Leo's work is not yours to replace. - Reconcile instead: Rebase and Push puts your draft on top of Leo's, or ask him first. If a real force push ran, the notice says "Force pushed. The replaced remote tip is kept at …", and Undo treats a force push as a change to a remote that you push again to change back.
In the terminal
After the revert, on shared:
git revert --no-edit "$BAD_SHA"
git push origin HEAD:shared
git merge-base --is-ancestor "$BAD_SHA" HEAD; echo $?The answer is 0: the mistake stays in history and nobody was forced to repair a clone.
On private/lease, with Leo's commit already on the server, test the pinned lease with the commit you reviewed:
git push --force-with-lease=refs/heads/lease:"$EXPECTED_SHA" origin HEAD:lease; echo $?
git ls-remote origin refs/heads/lease
git status --short ! [rejected] HEAD -> lease (stale info)
error: failed to push some refs to '../origin.git'
1Git refuses ("stale info"), the server still names Leo's commit and the status is empty. A bare git push --force-with-lease origin HEAD:lease is refused too while your remote-tracking ref is old, but after git fetch origin lease the same command replaces Leo's commit, because the fetch moved the ref it compares with. Name the commit you reviewed, or reconcile with a rebase.
Result
Where you end: the bad commit is still in the history of shared and a revert on top of it has been pushed normally. On lease the pinned push was refused, the server still holds Leo's commit, and your local rewrite is intact. No force push was made.
Common mistakes
- Rewriting a branch other people already pulled when a revert would do.
- Fetching and then force pushing with a bare lease, which now trusts what you just downloaded.
- Treating a lease as permission to replace a teammate's commit.
- Assuming Undo can reverse a force push on the server.
If something goes wrong
If a force push replaced the wrong tip, the replaced commit is kept under refs/ogt/trash (and, for native pushes, in your reflog and the teammate's clone). Tell the teammate, restore the tip as a branch and push it again with your agreement. A refused push changed nothing.
Check your understanding
Answer each question, then read why. Questions marked “Required” count toward completing the lesson.
0 of 6 correct
Answer the required questions correctly to complete the lesson.
Checked in this browser.
Practice
Report the ancestor check on shared, the exit code and message of the pinned push, the commit ls-remote shows for lease, and whether a bare lease would have been refused before and after a fetch.
Your own confirmation: the site cannot see your computer.
To complete the lesson, answer the required questions correctly and confirm the practical exercise.
Recap
- Prefer a new commit for anything others may have.
- Pin the lease to the commit you reviewed.
- A lease protects you, not your teammate's intent.
Related
Glossary terms introduced
Lease — قفل; stale info — معلومات قديمة; replaced tip — الرأس المستبدَل.
Video transcript
The narration of the video, cue by cue.
Show the transcript
- You pushed a mistake. What now?
- If others may have the commit, repair it by adding a commit.
- A lease says: replace the remote only if it is still where I expect.
- But a fetch moves what a bare lease expects.
- Two branches on a local server: shared, and lease.
- On shared, revert the bad commit.
- Push normally. No force.
- The mistake stays in history, and nobody must repair a clone.
- On lease, a rewritten branch is refused.
- Fetch only, and look. Leo's commit is there.
- It proceeds only while the remote is at this commit, and keeps the old tip.
- Leo's work is not yours to replace. Rebase onto it, or ask him.
- In the terminal, the ancestor check on shared is zero.
- Name the commit you reviewed. Git says stale info.
- The server still names Leo's commit.
- A bare lease after a fetch would have replaced it.
- Undo does not reverse a force push on a server.
- Practice: report the exit code, the server commit, and the status.
- Next, work on two branches at once with worktrees.