SovranCode
HomeCourses Git & GitHub Git Recovery
This device
Course contentsGit Recovery · 41 topics

1. Git Fundamentals

Git IntroductionGit Version ControlGit vs GitHubGit InstallationGit Working TreeGit RepositoryGit CommitGit .gitignoreGit Project: Learning Journal

2. Git History

Git LogGit DiffGit ResetGit RevertGit StashGit Project: Messy Repository

3. Branches & History

Git BranchGit MergeGit Merge ConflictsGit RebaseGit Cherry-PickGit TagsGit Project: Feature Branch

4. Remotes and GitHub

Git RemoteGitHub AuthenticationGitHub RepositoryGitHub ForkGitHub IssuesGitHub PagesGit Project: Two Clone Sync

5. GitHub Collaboration

GitHub Pull RequestsGitHub Code ReviewGit Branching WorkflowGitHub Branch ProtectionGitHub CollaborationGitHub ContributingGit Project: Reviewed Pull Request

6. Automation and Professional Git

GitHub ActionsGit InternalsGit RecoveryGitHub SecurityGit Project: Team Repository CIProject
Learn Git & GitHub40 complete · 1 planned

1. Git Fundamentals

Git IntroductionGit Version ControlGit vs GitHubGit InstallationGit Working TreeGit RepositoryGit CommitGit .gitignoreGit Project: Learning Journal

2. Git History

Git LogGit DiffGit ResetGit RevertGit StashGit Project: Messy Repository

3. Branches & History

Git BranchGit MergeGit Merge ConflictsGit RebaseGit Cherry-PickGit TagsGit Project: Feature Branch

4. Remotes and GitHub

Git RemoteGitHub AuthenticationGitHub RepositoryGitHub ForkGitHub IssuesGitHub PagesGit Project: Two Clone Sync

5. GitHub Collaboration

GitHub Pull RequestsGitHub Code ReviewGit Branching WorkflowGitHub Branch ProtectionGitHub CollaborationGitHub ContributingGit Project: Reviewed Pull Request

6. Automation and Professional Git

GitHub ActionsGit InternalsGit RecoveryGitHub SecurityGit Project: Team Repository CIProject
PREVIOUS LESSONGit Internals
NEXT LESSONGitHub Security
6. Automation and Professional Git 85 min

Git Recovery

Find a lost commit, isolate a regression, and attribute a line of history without treating blame as a personnel tool.

What you will leave with

You will preserve a commit displaced by reset or rebase, find a first bad commit with a reproducible binary search, and use line history as evidence rather than a personnel verdict. You will not promise reflog can restore unstored text, prune objects during recovery, or push rewritten history.

Three recovery jobs

Git Reset taught how refs, index, and working files move. Git Internals showed that a commit can still exist after a branch stops naming it. Recovery starts from those models and separates three different questions.

“Where did my commit go?”

Inspect a local reflog, verify the object, and give the wanted commit a durable branch name.

“Which commit introduced this regression?”

Give git bisect one known-good boundary, one known-bad boundary, and the same test at every midpoint.

“Why is this line here?”

Use git blame to find a last-touch commit, then read that commit and neighboring history.

“How do I fix it?”

Only after diagnosis. Recovery identifies and preserves evidence; the repair may be a new commit, revert, merge, or coordinated rewrite.

Do not start with a destructive command

git reset --hard, git clean, pruning, and force-push are not diagnostic tools. Stop. Record. Verify. Preserve. Then decide.

Stop and preserve evidence

# Stop changing the repository. Record the current evidence.
git status --short --branch
git log --oneline --decorate --graph --all -12
git reflog --date=iso -12

# Verify candidates before creating any recovery name.
git show --stat <candidate>
git cat-file -t <candidate>

Status answers whether index or working-tree content is at risk. The graph shows refs that still reach commits. Reflog shows recent local movement. git show verifies a candidate before you attach a new name. Read-only commands come first because every new checkout, reset, rebase, or commit adds more history to interpret.

COMMITTED WORK

Often recoverable by name

  • Find the object in a ref or reflog.
  • Verify its tree and message.
  • Create a recovery branch.
  • Compare before integrating.
UNSTORED TEXT

Not promised by Git

  • Reflog records ref updates.
  • It is not editor history.
  • Untracked files may never be objects.
  • Hard reset can overwrite tracked edits.

Recover a displaced commit with reflog

A reflog entry says a ref used to have a value. HEAD@{1} means the previous recorded value of HEAD; main@{1} means the previous recorded value of the local main ref. Their numbers are relative and can change as more entries arrive, so copy and verify the full object id before a long investigation.

# Example: reset or rebase moved the branch away from wanted commits.
git reflog --date=iso main
git show --stat main@{1}

# Preserve first. This does not move main or change the working tree.
git branch recovery/lost-tip main@{1}
git log --oneline --decorate --graph --all

# Compare before deciding whether to merge, cherry-pick, or reset.
git log --left-right --oneline main...recovery/lost-tip
git diff main...recovery/lost-tip
QUICK CHECK

Test what you learned

Type the command that preserves the verified commit at HEAD@{1} as recovery/lost-tip.

The recovery branch is the safety result. It turns a temporary reflog clue into a normal ref. You can now inspect, diff, merge, cherry-pick, or leave the old series alone without guessing. Do not immediately reset main to the candidate: another branch may already contain wanted later work.

Use the ref-specific reflog

git reflog main answers how main moved. Plain git reflog usually shows HEAD movement, including switches between branches. Pick the log that matches the incident.

Know what reflog cannot recover

Local only

Another clone has a different reflog. A fresh clone does not inherit yours. GitHub does not host your local reflog.

Temporary

Entries expire and unreachable objects may later be pruned. A recovery branch is durable; a reflog selector is not a backup plan.

Refs, not keystrokes

A file version never staged, committed, or stashed may have no Git object. Check editor or filesystem recovery separately.

Not permission to rewrite shared work

Finding an old tip does not justify force-pushing it. Preserve locally and choose a collaboration-safe repair.

If no ref or reflog names the commit, git fsck --no-reflogs --unreachable can report unreachable objects still present locally. That is last-resort read-only inventory, not the first step. Verify every candidate with git show and name wanted commits. Do not run aggressive garbage collection during an incident.

Define the bisect contract

git bisect checks commits between a known good ancestor and a known bad descendant. Git chooses a midpoint; you run one test and mark that commit good or bad. Repeating this cuts the search space roughly in half. A thousand-commit range needs about ten classifications, not a thousand guesses.

Known bad

The bug reproduces now with exact input and expected failure. “Someone reported it” is not a test.

Known good

An older commit passes the same check in a historically valid environment.

One classification

Good means the target behavior passes. Bad means the same behavior fails. Do not change definitions halfway through.

Untestable

Use git bisect skip when a historical commit cannot build for an unrelated reason. Skips may leave several possible culprits.

Bisect finds a boundary, not moral fault

The first commit where the test turns bad may expose an older hidden assumption, depend on environment drift, or be a merge. Read the diff and context before claiming causation.

Run a manual bisect

# Start from a reproducibly bad commit.
git status --short
git bisect start
git bisect bad HEAD
git bisect good <known-good-commit>

# At each commit, run the same check.
npm test
git bisect good   # only when the check passes
git bisect bad    # only when the check fails

# After Git identifies the first bad commit:
git show --stat
git bisect reset
QUICK CHECK

Test what you learned

Type the command that ends bisect mode and restores the checkout from before the search.

Bisect temporarily checks out commits, usually with detached HEAD. Keep the working tree clean before starting. Do not edit and commit fixes during classification. When Git reports the first bad commit, record its full SHA and inspect it, then run git bisect reset before making a repair branch.

Merge-heavy history can produce more than one meaningful path. Default bisect follows ancestry between the boundaries and identifies where the test first becomes bad in that graph. If the result is a merge commit, inspect both parents and the merge diff instead of assuming one author introduced the behavior.

Automate a reliable bisect

# The script must return:
#   0   known good
#   1-127 (except 125) known bad
#   125 cannot test this commit
git bisect start HEAD <known-good-commit>
git bisect run ./scripts/repro.sh

# Read the reported first bad commit, then restore the original checkout.
git show --stat <first-bad-commit>
git bisect reset

An automated script must be executable, deterministic, and compatible with the historical range. It should create temporary output outside tracked paths or clean up after itself. It must not install unreviewed software, mutate refs, push, or depend on current uncommitted files.

GOOD TEST

One bounded behavior

  • Same input at every commit.
  • Clear pass/fail exit status.
  • Historical setup is documented.
  • Runs without network when possible.
BAD TEST

Moving target

  • Reads today’s working tree.
  • Depends on flaky services.
  • Rewrites files or refs.
  • Calls every build failure “the regression.”

Exit 125 tells Git this commit cannot be classified. Use it only for genuinely untestable points. If many commits are skipped, the result may be a range of candidates instead of one commit. Report that uncertainty honestly.

Use blame without blaming people

# Ask which commit last changed a bounded range.
git blame -L 10,25 -- src/parser.js

# Ignore whitespace-only changes when that matches the investigation.
git blame -w -L 10,25 -- src/parser.js

# Then inspect intent and surrounding history.
git show <commit> -- src/parser.js
git log --follow --oneline -- src/parser.js
QUICK CHECK

Test what you learned

Type the command that attributes lines 10 through 25 of src/parser.js.

git blame maps each selected line to the commit that last changed it. It does not prove who designed the behavior, who reviewed it, who was under an incident constraint, or which commit first made a test fail. Use the SHA as a doorway to git show, the commit message, linked discussion, and surrounding history.

Bound the question

Use -L to inspect relevant lines instead of dumping an entire file.

Ignore noise deliberately

-w can ignore whitespace. Record that choice; it changes attribution.

Follow the path

git log --follow helps with a simple rename. Copy detection options can help, but results remain heuristics.

Talk about commits

Say “commit X last changed this line.” Do not turn a history command into a label for a teammate.

Guided practice: preserve and isolate

  1. 01
    Build a disposable history

    Create eight small commits. Make commit five introduce a deterministic failing check; keep commits six through eight unrelated.

  2. 02
    Displace and recover the tip

    Create a practice branch, move it back two commits with mixed reset, find the old tip in that branch’s reflog, verify it, and create recovery/lost-tip.

  3. 03
    Bisect the regression

    Mark commit one good and the recovered tip bad. Run the same check until Git identifies commit five. Reset bisect mode.

  4. 04
    Investigate the line

    Use bounded blame on the failing behavior, then git show the last-touch commit. Compare that evidence with the bisect result.

  5. 05
    Write the report

    Record displaced tip, recovery ref, good/bad boundaries, first bad SHA, blame SHA, and why those last two may answer different questions.

Independent lab: recovery report

  1. In a disposable local repository you own, create at least ten commits. One middle commit must introduce a deterministic regression; later commits must preserve it. Record the known-good and known-bad SHAs.
  2. On a separate practice branch, move the tip backward. Use the branch-specific reflog, verify the old object with git show, and preserve it as recovery/lost-tip. Do not reset the branch forward as your first recovery action.
  3. Run a manual bisect from the known-bad tip to the known-good ancestor. Record every tested SHA and classification. Use git bisect skip for one deliberately untestable historical commit only if you can explain why.
  4. Run git bisect reset. Use bounded git blame, git show, and path history on the line involved. Do not name a person as “the cause.”
  5. Write eight evidence lines: incident symptom, displaced tip, recovery ref, known-good SHA, known-bad SHA, first-bad result (or candidate range), last-touch blame SHA, and the repair strategy you would choose without force-pushing.
  6. Finish with clean status and a graph where every wanted commit is reachable by a normal ref. Do not run pruning, do not edit .git, and do not push this exercise.
Definition of done

You preserved a displaced commit before changing history again, used one reproducible contract to narrow a regression, restored the pre-bisect checkout, and reported line history as contextual evidence rather than fault.

Common recovery mistakes

Resetting before preserving

More ref movement makes the incident harder to explain. Create a recovery branch first.

Expecting reflog on another clone

Reflogs are local. Ask what that clone fetched or recorded instead.

Bisecting a flaky symptom

Inconsistent classifications can identify the wrong boundary. Stabilize the check first.

Forgetting bisect reset

You remain detached at a historical commit. End the search before starting the fix.

Calling every build failure bad

If the target behavior cannot run for another reason, skip; do not change the question.

Using blame as accusation

It reports last-touch commits. It does not report intent, ownership, review context, or fault.

Lesson review

You can recover a displaced commit by verifying and naming it, isolate a reproducible regression with manual or automated bisect, and use blame to enter the surrounding history. GitHub Security keeps incident evidence from repeating an exposed credential. The team CI project stays planned.

  • I stop mutating, record evidence, and create a recovery branch before choosing a repair.
  • I know reflog is local, temporary ref history—not a backup of every file version.
  • I can define good, bad, and untestable results and finish with git bisect reset.
  • I use bounded blame plus show and log, and I discuss commits rather than assigning personal fault.

Related lessons

  • Git Reset — Reflog is how you recover a moved branch tip.
  • Git Internals — Lost work is often still an object Git can name.
  • Git Log — History inspection verifies bisect and blame results in context.
  • Git Revert — Diagnosis comes before choosing a shared-history-safe repair.
  • GitHub Security — Preserve safe incident evidence without copying exposed credentials.
KNOWLEDGE CHECK

Check your Git recovery model

Preserve evidence before changing refs, make bisect answer one reproducible question, and treat blame as history—not a verdict about a person.

01What should you do first after noticing a branch tip moved away from wanted commits?
02What does git branch recovery/lost-tip main@{1} do?
03What does a reflog record?
04What question does git bisect answer?
05What must an automated bisect test do?
06What is git blame evidence of?
07Why run git bisect reset when the search ends?
PREVIOUS LESSONGit Internals
NEXT LESSONGitHub Security
ON THIS PAGEGit RecoveryThree recovery jobsStop and preserve evidenceRecover a displaced commit with reflogKnow what reflog cannot recoverDefine the bisect contractRun a manual bisectAutomate a reliable bisectUse blame without blaming peopleGuided practice: preserve and isolateIndependent lab: recovery reportCommon recovery mistakesLesson reviewKnowledge checkRelated lessons
Course contents