Recover work
- Lesson 12
- 40 min
- Module 4: Resolve and recover
Lesson 12 of 15 · Git and Team Development EssentialsRecover work
1. Understand a repository
2. Work with branches and remotes
3. Collaborate through reviews
4. Resolve and recover
5. Release and hotfix practice
What you will be able to do
Get your work back after you moved a branch to the wrong commit or deleted a branch. After this lesson, you can find a lost commit, recover it, and keep your teammate’s work at the same time.
Before you startWhat to know and to have ready
What you need to know
- What
git resetdoes, and why it is only for commits that nobody else has, from Undo safely: Reset: only for commits that nobody has. - In this module, you commit, merge and revert on
main, so that the commands stay short. On a team, you make each change on a branch and merge it through a pull request, as in Pull-request sequence.
What you need on your computer
- The lab
~/projects/c14-labfrom this module, withticket-notes,sam-notesandteam-remote.git. Inticket-notes,git statusshowsYour branch is up to date with 'origin/main'andnothing to commit, working tree clean. - The reverts that you pushed in Undo safely: Guided practice: revert, then reset in a clone and Undo safely: Your turn: revert one commit, keep the next. In this lesson, the newest commit on the remote is the revert from that “Your turn” task. If you did not do the task, your newest commit is different. The steps work the same.
- No
scratchfolder inc14-lab. You deleted it in the last step of Undo safely: Guided practice: revert, then reset in a clone. If it is there, go to~/projects/c14-lab, check withpwd, and runrm -rf scratch. - If the lab is missing or broken, run
sh nextia-learning-labs/C14/setup.sh m04 --resetin~/projects. Then do the worked examples in Conflicts step by step and Undo safely again. - You do every reset in this lesson in a disposable clone called
scratch. You make it withgit clone, and delete it before you make it again. The commits that you make have different hashes from this lesson.
Git remembers where your branches were
Section titled “Git remembers where your branches were”When you reset a branch, the commits that leave it are not deleted at once. Git keeps them for some time. The problem is to find their hashes, because no branch and no git log shows them now.
The reflog (reference log) is Git’s record of where HEAD and each branch pointed, newest first. Git adds an entry each time HEAD moves: when you commit, switch, reset, merge, pull or clone. git reflog shows the entries for HEAD:
2bf80ca HEAD@{0}: reset: moving to HEAD~241faf11 HEAD@{1}: commit: Add ticket 107121ec42 HEAD@{2}: commit: Add ticket 1062bf80ca HEAD@{3}: clone: from /Users/amira/projects/c14-lab/team-remote.gitRead each line from left to right:
| Part | Example | Meaning |
|---|---|---|
| Hash | 41faf11 |
The commit where HEAD was after this action. |
| Name | HEAD@{1} |
“Where HEAD was one move ago”. HEAD@{0} is now. |
| Action | commit: Add ticket 107 |
What moved HEAD, and the commit message or target. |
Three limits are important:
- The reflog is local. It is only in your clone. It is not pushed, and a new clone starts with an empty reflog. Your teammate cannot use your reflog.
- Entries expire. By default, Git keeps an entry for 90 days. For a commit that no branch contains, it keeps the entry for 30 days. After that, Git can delete the commit.
- It records only commits. If you never committed a change, or never stashed it, the reflog has nothing to bring back.
git restoreandgit reset --harddiscard such changes for good.
Worked example: undo an accidental reset
Section titled “Worked example: undo an accidental reset”Amira makes a disposable clone, then two local commits in it:
cd ~/projects/c14-labgit clone team-remote.git scratchcd scratchShe adds a line for ticket 106 to tickets.txt and commits it with git commit -am "Add ticket 106". Then she does the same for ticket 107:
41faf11 (HEAD -> main) Add ticket 107121ec42 Add ticket 1062bf80ca (origin/main, origin/HEAD) Revert "Update the accounts team extension"She wants to remove only the last commit, but she types HEAD~2 instead of HEAD~1:
git reset --hard HEAD~2HEAD is now at 2bf80ca Revert "Update the accounts team extension"Both commits are gone from main, and from git log. She looks in the reflog. -5 shows the newest five entries:
git reflog -52bf80ca HEAD@{0}: reset: moving to HEAD~241faf11 HEAD@{1}: commit: Add ticket 107121ec42 HEAD@{2}: commit: Add ticket 1062bf80ca HEAD@{3}: clone: from /Users/amira/projects/c14-lab/team-remote.gitThe entry before the reset is 41faf11, her ticket 107 commit. Its parent is the ticket 106 commit. So 41faf11 has both of her commits.
She could run git reset --hard 41faf11 to move main back. But if she copies the wrong hash, she makes a second mistake. A safer way is to put a new branch on the commit first. A branch changes no files, and she can inspect it:
git branch rescue 41faf11git log --oneline -3 rescue41faf11 (rescue) Add ticket 107121ec42 Add ticket 1062bf80ca (HEAD -> main, origin/main, origin/HEAD) Revert "Update the accounts team extension"These are the correct commits. main is behind rescue, so she moves main forward with a fast-forward merge. --ff-only means “only if this is a fast-forward”. If it is not, Git stops and changes nothing. Then she deletes the rescue branch:
git merge --ff-only rescuegit branch -d rescueUpdating 2bf80ca..41faf11Fast-forward tickets.txt | 2 ++ 1 file changed, 2 insertions(+)Deleted branch rescue (was 41faf11).main points to 41faf11 again, and both commits are back.
Worked example: bring back a deleted branch
Section titled “Worked example: bring back a deleted branch”Amira tries an idea on a branch, try-priorities, with one commit. She switches back to main and deletes the branch. git branch -d refuses, because the branch has a commit that is on no other branch:
error: the branch 'try-priorities' is not fully mergedhint: If you are sure you want to delete it, run 'git branch -D try-priorities'This warning protects work. She uses -D, which deletes the branch anyway:
git branch -D try-prioritiesDeleted branch try-priorities (was edc37a8).Then she understands that she needs the idea after all. The output above shows the hash: (was edc37a8). If that output is gone from the screen, use the reflog. The hash is in the entry for her commit on the branch:
41faf11 HEAD@{0}: checkout: moving from try-priorities to mainedc37a8 HEAD@{1}: commit: Add priority levels41faf11 HEAD@{2}: checkout: moving from main to try-prioritiesA branch is only a label on a commit, so she makes the label again:
git branch try-priorities edc37a8git log --oneline -2 try-prioritiesedc37a8 (try-priorities) Add priority levels41faf11 (HEAD -> main) Add ticket 107Stash: put changes aside for a short time
Section titled “Stash: put changes aside for a short time”A stash is a place where Git keeps changes that you did not commit, so that you can work on something else. git stash push saves the changes and makes your files match the last commit again. git stash pop puts the changes back.
Amira is halfway through ticket 108 in tickets.txt. She also has a new file, draft.txt, which Git does not track. A teammate asks her to check something on a clean main:
git status --shortgit stash push -m "Half-done ticket 108"git status --shortgit stash list M tickets.txt?? draft.txtSaved working directory and index state On main: Half-done ticket 108?? draft.txtstash@{0}: On main: Half-done ticket 108-m gives the stash a message, so she can recognise it in git stash list. The tickets.txt change is in the stash. But draft.txt is still in the folder: by default, stash does not take untracked files. git stash push -u takes them too.
When she is ready, she brings the change back:
git stash popDropped refs/stash@{0} (e42f7c918e3b194e6e2218fcf006ec4e10a15bed)The change is back in tickets.txt, and the stash is removed from the list.
What stash does not do well
Section titled “What stash does not do well”- Untracked files stay behind unless you use
-u. - A stash is local.
git pushdoes not send it. If your computer breaks, the stash is lost. popcan conflict. If the same lines changed after you stashed,popstops with a conflict. The stash then stays in the list. See the common mistake below.- Stashes are easy to forget. After a week,
stash@{0}: On main: WIPtells you very little.
For work that must wait more than a few minutes, a commit on its own branch is safer. The reflog records it, you can push it, and its message says what it is:
git switch -c wip-ticket-108git commit -am "WIP: ticket 108, cause not found yet"git switch mainGuided practice: lose commits and get them back
Section titled “Guided practice: lose commits and get them back”-
If you did the worked examples in your own
scratchclone, delete that clone. Otherwise,git clonein the next step stops, because the folder exists.rm -rfdeletes a folder with no undo, so check the name before you press Enter:Terminal window cd ~/projects/c14-labrm -rf scratchlsno longer showsscratch. -
Make a new disposable clone:
Terminal window cd ~/projects/c14-labgit clone team-remote.git scratchcd scratch -
Add a line for ticket 106 to
tickets.txt, save, and commit:Terminal window git commit -am "Add ticket 106" -
Add a line for ticket 107, save, and commit:
Terminal window git commit -am "Add ticket 107" -
Run
pwd. Then remove both commits by mistake:Terminal window git reset --hard HEAD~2git log --oneline -1shows the commit from the remote. Your two commits are not in the log. -
Find them:
Terminal window git reflog -5The entry
HEAD@{1}showscommit: Add ticket 107. Write down its hash. -
Put a branch on that hash, and check it. Use your hash instead of
41faf11:Terminal window git branch rescue 41faf11git log --oneline -3 rescueThe output shows both of your commits.
-
Move
mainforward, and delete the rescue branch:Terminal window git merge --ff-only rescuegit branch -d rescueThe output includes
Fast-forward.git log --oneline -2shows both commits again. -
Make a branch with one commit, and go back to
main:Terminal window git switch -c try-prioritiesecho "Priority levels: low, normal, urgent." > priorities.txtgit add priorities.txtgit commit -m "Add priority levels"git switch main -
Run
pwd. Then delete the branch:Terminal window git branch -D try-prioritiesThe output is
Deleted branch try-priorities (was …), with a hash. -
Make the branch again with that hash. Use your hash instead of
edc37a8. Then check it:Terminal window git branch try-priorities edc37a8git log --oneline -1 try-prioritiesThe output shows
Add priority levels. -
Add a line for ticket 108 to
tickets.txt, but do not commit. Stash it, and check:Terminal window git stash push -m "Half-done ticket 108"git stash listgit status --shortgit stash listshows your message.git status --shortprints nothing. -
Bring the change back with
git stash pop.git status --shortshowsM tickets.txt.
Your turn: recover your commit and keep Sam’s
Section titled “Your turn: recover your commit and keep Sam’s”This is the completion check for Module 4. You recover your own lost commit, and your teammate’s work must stay.
- Delete the old
scratch, and make a new one. In it, commit a line for ticket 106. - In
sam-notes, rungit pull. AddNetwork printer: ext. 4520tocontacts.txt, commit with the messageAdd the network printer contact, and push. - In
scratch, rungit fetch. Then make this mistake on purpose:git reset --hard origin/main. You wanted to “update to the remote”, and your ticket 106 commit leftmain. - Get your commit back so that
maininscratchhas both your ticket 106 commit and Sam’s printer contact. Do not push fromscratch.
Then show the result with git log --oneline --graph -4, tail -1 contacts.txt, tail -1 tickets.txt and sh check.sh.
Show a model answer
After the mistake, the reflog shows the lost commit at HEAD@{1}:
6a64a93 HEAD@{0}: reset: moving to origin/maina89b80c HEAD@{1}: commit: Add ticket 1062bf80ca HEAD@{2}: clone: from /Users/amira/projects/c14-lab/team-remote.gitPut a branch on it. Then merge it into main. This is not a fast-forward, because main now has Sam’s commit and rescue has yours. So Git makes a merge commit that keeps both:
git branch rescue a89b80cgit merge --no-edit rescuegit log --oneline --graph -4tail -1 contacts.txttail -1 tickets.txtsh check.shMerge made by the 'ort' strategy. tickets.txt | 1 + 1 file changed, 1 insertion(+)* 50d5765 (HEAD -> main) Merge branch 'rescue'|\| * a89b80c (rescue) Add ticket 106* | 6a64a93 (origin/main, origin/HEAD) Add the network printer contact|/* 2bf80ca Revert "Update the accounts team extension"Network printer: ext. 4520Ticket 106: keyboard missing keys. Ordered a new keyboard.PASS: 6 ticket lines checkedThe graph shows both lines of work: your recovered commit and Sam’s commit. You can now delete the label with git branch -d rescue. Sam’s commit was never at risk on the remote. The danger was in the next step: if you had moved main back to a89b80c with reset, your main would not contain Sam’s commit.
Another correct answer: git reset --hard a89b80c, then git pull --no-rebase --no-edit. The files are the same. Only the message of the merge commit is different.
Common mistake
git stash pop stops with a conflict
Symptom: you stashed a change to tickets.txt, and then committed a change to the same lines. git stash pop shows:
Auto-merging tickets.txtCONFLICT (content): Merge conflict in tickets.txt...The stash entry is kept in case you need it again.git stash list still shows the stash. tickets.txt has conflict markers. Here, the names of the sides are different from a merge:
<<<<<<< Updated upstreamTicket 107: cannot print in colour. Installed the colour driver. Fixed.||||||| Stash baseTicket 107: cannot print in colour. Installed the colour driver.=======Ticket 107: cannot print in colour. Installed the colour driver.Ticket 108: VPN drops every hour.>>>>>>> Stashed changesCause: Updated upstream is your current commit. Stashed changes is the stash. Both changed lines next to each other since you stashed, so Git cannot apply the stash alone. It keeps the stash so that the change is not lost.
Fix:
-
Resolve the conflict as in Conflicts step by step. Here, keep the
Fixed.line and add the ticket 108 line after it. -
Run
git diff --checkandsh check.sh. -
Run
git add tickets.txtto mark the file as resolved. -
Remove the stash, because its change is now in your file:
Terminal window git stash dropOutput Dropped refs/stash@{0} (1b54611302688949f349718abca555a35e38fcb9)
Common mistake
The reflog does not have my change
Symptom: you ran git restore tickets.txt or git reset --hard, and lost an edit. git reflog shows no entry for it.
Cause: the edit was never in a commit or a stash. The reflog records only where HEAD and branches pointed, and they point to commits. Git never saved the edit, so Git cannot bring it back.
Fix: Git cannot help here. Look for another copy: your editor’s undo history, or its local history feature. To prevent this, commit or stash before any command that discards changes, and read git status before you run it.
Check your understanding
Predict: Amira runs git reset --hard HEAD~2 in her clone and loses two local commits. Sam clones the remote on his computer. Can Sam find Amira's two commits with git reflog?
Show the answer
No. The reflog is local to each clone, and it is never pushed. Amira never pushed the two commits, so they exist only in her clone. Sam’s new clone has its own reflog, which starts with his clone. Only Amira can recover the commits, from her own reflog, and only before the entries expire.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Check your understanding
Completion check: in your scratch clone, show that you recovered your lost commit and kept Sam's commit. Which output proves each part?
Show the answer
git log --oneline --graph -4shows a merge commit with two parents: yourAdd ticket 106and Sam’sAdd the network printer contact.tail -1 tickets.txtshows your ticket 106 line.tail -1 contacts.txtshows Sam’s printer line.sh check.shprintsPASS.- You did not push from
scratch, and you did not rewriteorigin/main:git statussays that you are ahead oforigin/main, not that you diverged or are behind.
Does your answer have the main points of ours?
Sign in and enroll in the course to save your answers. Every check counts toward the certificate.
Recap
- The reflog records where HEAD pointed after each move. Use it to find the hash of a commit that left a branch.
- Recover by putting a branch on the hash first,
git branch rescue <hash>. Inspect it, then merge it or reset to it. - A deleted branch is a lost label. Make it again with
git branch <name> <hash>, using the hash from(was …)or from the reflog. - The reflog is local, and its entries expire. It cannot recover changes that you never committed or stashed.
- A stash keeps uncommitted changes for a short time. It skips untracked files without
-u, stays on your computer, and keeps the entry ifpopconflicts. For longer work, commit on a branch.
This is the end of Module 4. You can now resolve a conflict, undo a shared commit with revert, and recover lost local work. In Module 5, you use these skills to prepare a release and fix it.