History Inspection, Correction and RecoveryIntroductionVersion control is not only a mechanism for recording new work. It is also an information system for investigating how a project evolved, identifying the source of a defect, correcting mistakes, and recovering from accidental loss. In Git, these activities depend on understanding the difference between the current working files, the staging area, branch references, and the commit objects that form the repository’s history. A disciplined Git user should be able to answer questions such as: - What changed between two versions?
- Which commit introduced a defect?
- What exactly does a commit contain?
- How can an incorrect commit be corrected safely?
- How can a previous version be recovered?
- Which operations are safe on a shared branch?
This chapter develops these skills through Git commands including log, diff, show, blame, reflog, amend, revert, reset, restore, rebase, cherry-pick, stash, and bisect. Particular attention is given to the professional risk of rewriting history that other people may already have downloaded. Learning ObjectivesBy the end of this chapter, you should be able to: - Inspect commit history and compare repository states using
log, diff, and show. - Use
blame and bisect to investigate the origin of a change or defect. - Correct recent work with
amend, restore, and appropriate staging operations. - Distinguish
revert, reset, and restore and select the safest tool for a situation. - Apply commits selectively with
cherry-pick, temporarily save work with stash, and reorganise local commits with rebase. - Recover apparently lost commits using
reflog. - Recognise the technical and collaborative risks of rewriting shared history.
1. Git’s History ModelGit stores snapshots of a project as commits. A commit normally records a tree of files, a parent commit, author and committer information, a message, and metadata. A branch is a movable reference to a commit; it is not a separate copy of the project. HEAD identifies the commit or branch currently checked out. Three states are especially important: | State | Meaning | Typical command affecting it |
|---|
| Working tree | Files currently present in the project directory | restore, editing tools | | Index (staging area) | Proposed contents for the next commit | add, restore --staged | | Repository history | Committed snapshots and references | commit, reset, rebase |
The expression HEAD refers to the current checked-out position. HEAD~1 means the first parent of HEAD, commonly the previous commit. HEAD~3 means three first-parent steps backwards. A range such as A..B generally selects commits reachable from B that are not reachable from A; it does not itself describe file differences. A useful professional distinction is between changing content and changing history. revert creates a new commit that reverses an earlier commit, preserving the published sequence. reset moves a branch reference, potentially making commits unreachable from that branch. rebase creates replacement commits with new identities. These differences determine collaboration risk. 2. Inspecting History2.1 git logThe basic command is: git log Useful forms include: git log --oneline --decorate --graph --all
git log --stat
git log -p
git log -- path/to/file.csv
git log --author="Name"
git log --since="2025-01-01" --oneline gives a compact commit identifier and message. --graph represents branch and merge structure. --all includes commits reachable from all local references. -p displays the patch introduced by each commit, while --stat provides a summary of changed files and line counts.
History inspection is interpretive rather than merely mechanical. A commit message should be read with its affected files and surrounding commits. A large commit may contain several logically unrelated changes, making later diagnosis more difficult. Conversely, a small, focused commit is easier to review, revert, and locate with bisect. 2.2 git showshow displays one object, most commonly a commit:
git show HEAD
git show a1b2c3d
git show --stat a1b2c3d
git show a1b2c3d:path/to/file.csv The normal commit display includes metadata and a patch. The path form retrieves the file as it existed in that commit. This is useful when comparing an old configuration file with the current one without checking out another branch. 2.3 git diffdiff compares states. Common comparisons are:
git diff # working tree versus index
git diff --staged # index versus HEAD
git diff HEAD # working tree and index versus HEAD
git diff HEAD~1 HEAD # two commits
git diff main...feature # changes on feature since its merge base with main The first command answers, “What have I changed but not staged?” The second answers, “What will the next commit contain?” This distinction is essential before committing. A diff is a diagnostic representation, not merely a list of changed lines. A deleted line may be replaced elsewhere; a configuration change may affect an entire deployment; and a change shown as a rename may depend on similarity detection. Always inspect the surrounding code and test behaviour rather than assuming that a visually small diff has a small effect. 3. Identifying the Origin of Changes3.1 git blameblame assigns each line to the commit that last modified it:
git blame path/to/file.py
git blame -L 40,70 path/to/file.py
git blame REVISION -- path/to/file.py The output commonly includes a commit identifier, author, date, and line content. It is best used as a tracing tool: after finding a suspicious line, inspect the relevant commit with git show. blame does not prove who designed a feature, caused a defect, or deserves responsibility. A line may have been moved by a formatting commit, generated automatically, or copied from another file. Treat the result as historical evidence, not as a judgement about an individual.
3.2 git bisectbisect performs a binary search through commits to identify a commit associated with a regression. The basic workflow is:
git bisect start
git bisect bad # current revision fails
git bisect good known-good-tag # older revision works
# test the checked-out midpoint
git bisect good # midpoint works
git bisect bad # midpoint fails
git bisect reset At each step Git selects a midpoint. If the tested commit is good, the search continues in the later half; if bad, it continues in the earlier half. If there are N candidate commits and every test is reliable, the number of tests is approximately logarithmic, about log₂(N), rather than N. This efficiency depends on a clear, repeatable test and an ordered history. A commit is “bad” only relative to a defined test and environment. If dependencies, databases, generated files, or external services vary, the result may be misleading. Before bisecting, define the failure precisely, ensure the test can run at each candidate commit, and record any setup commands. Automation is possible: git bisect run ./test-regression.sh The script should return status 0 for good, 1– अच्छे? for bad? In practice, use a documented convention: status 0 means good, status 1–127 means bad, and another reserved status can indicate that a commit cannot be tested. Teams should verify the exact script behaviour for their environment rather than relying on an ambiguous test result. 4. Correcting Recent Work4.1 Amending a commitIf the latest commit has the wrong message or omitted a staged file, use: git add missing-file.py
git commit --amend For a message supplied directly: git commit --amend -m "Improve input validation" Amending replaces the previous commit with a new commit. Even if the file content is unchanged, the commit identity may change because its metadata or message changed. Amend only commits that have not been shared, unless the team explicitly agrees to rewrite the branch and coordinate force-pushing. 4.2 restorerestore changes files, not branch history:
git restore notes.txt # restore from index
git restore --source=HEAD~1 notes.txt
git restore --staged notes.txt # remove from index, keep working copy
git restore --staged --worktree notes.txt The last form restores both the index and working tree from the default source, usually HEAD. Because it can discard uncommitted work, inspect the file and use it deliberately. 4.3 resetreset moves HEAD and, depending on its mode, changes the index and working tree:
| Mode | Branch reference | Index | Working tree |
|---|
--soft | Moved | Unchanged | Unchanged | --mixed (default) | Moved | Reset | Unchanged | --hard | Moved | Reset | Reset |
Examples: git reset --soft HEAD~1
git reset HEAD~1
git reset --hard HEAD~1 A soft reset turns the latest commit into staged changes. A mixed reset, often used to uncommit while retaining file edits, leaves changes unstaged. A hard reset discards tracked working-tree and index changes that are inconsistent with the target. It is powerful and hazardous. Do not use reset to remove a published commit from a shared branch merely because the commit is undesirable. Prefer revert, which records the correction publicly. 5. Reversing and Reorganising History5.1 revert: public correctiongit revert a1b2c3d This creates a new commit that applies the inverse of the selected commit. The original remains visible, which preserves the audit trail and avoids forcing collaborators to reconcile rewritten references. A revert may itself produce conflicts, particularly when later commits depend on the change being reversed. For a merge commit, Git normally requires the mainline parent to be specified: git revert -m 1 merge-commit-id The correct parent depends on the branch structure and the intended reversal; it should be confirmed before execution. 5.2 rebase: local history refinementInteractive rebase can edit, reorder, combine, or remove local commits: git rebase -i HEAD~4 The editor may offer commands such as pick, reword, edit, squash, fixup, and drop. During a conflict: git status
git add resolved-file.py
git rebase --continue To abandon the operation: git rebase --abort Rebase produces new commit identities. It is appropriate for cleaning up private work before review, but risky after publication. If a rewritten branch must be pushed, a safer option than ordinary force-push is often: git push --force-with-lease This still requires care: it refuses to overwrite the remote if it has advanced unexpectedly, but it does not make history rewriting inherently safe. 5.3 cherry-pick: selective transfergit cherry-pick a1b2c3d This applies the change introduced by a selected commit to the current branch and normally creates a new commit. It is useful for transferring a bug fix to a maintenance branch. The resulting commit is not the same object as the source commit, even if the patch is identical. If conflicts occur, resolve files, stage them, and continue: git add resolved-file.py
git cherry-pick --continue Use --abort to return to the pre-operation state. Cherry-picking a commit without its prerequisite changes may compile incorrectly or pass superficial tests while violating assumptions elsewhere. 6. Temporary Work and Recovery6.1 stashA stash temporarily records uncommitted work: git stash push -m "unfinished dashboard filter"
git stash list
git stash show -p stash@{0}
git stash apply stash@{0}
git stash popapply keeps the stash entry; pop applies it and normally removes it if successful. A stash is not a substitute for a meaningful branch or commit. Important work should be committed on a private branch, because stashes can be forgotten, conflict during application, and are not automatically shared with colleagues.
6.2 reflog: recovering moved referencesThe reflog records local movements of references such as HEAD: git reflog
git show HEAD@{3}
git branch recovery HEAD@{3}If a hard reset or rebase appears to remove a commit, the commit may still be reachable through the reflog. Creating a branch at the relevant entry protects it while the situation is investigated. Reflogs are local and are not a permanent backup. They may expire according to repository maintenance settings, and they do not generally rescue objects that were never committed. The best recovery strategy is prevention: frequent commits, remote backups, protected branches, and careful review of destructive commands. 7. Shared-History Risk and Decision FrameworkBefore changing history, ask three questions: - Has the commit been pushed or used by another person?
- Is the desired result a new public correction or a private cleanup?
- Can the operation discard uncommitted work or overwrite someone else’s reference?
| Situation | Usually appropriate action |
|---|
| Wrong message in an unpublished latest commit | commit --amend | | Unstaged file should be discarded | restore | | Uncommit but retain changes | reset --mixed or --soft | | Public commit introduced a defect | revert | | Clean up private commits before review | interactive rebase | | Transfer one fix to another branch | cherry-pick | | Temporarily change branches with unfinished work | stash or a temporary branch | | Recover after reset or rebase | reflog | | Locate the first failing commit | bisect |
Force-pushing rewritten history can invalidate colleagues’ local branches, pull requests, tags, and automated deployments. Protected branches, required reviews, signed commits, and CI checks reduce these risks. A team should document whether force-pushing is prohibited, restricted to designated branches, or allowed only with --force-with-lease and explicit communication. 8. Applied Case: A Defective Data PipelineThe following is an illustrative case. BackgroundA small data-science team maintains a Python pipeline that imports customer transactions, removes invalid rows, and produces a daily report. After a deployment, the report contains substantially fewer records than expected. Problem and analysisThe team first confirms that the current branch is clean and examines recent history: git status
git log --oneline --decorate -10
git diff HEAD~3 HEAD -- pipeline/clean.py The diff shows a recent change to a date filter. git blame identifies the commit that introduced the relevant line. However, the team does not immediately blame the author or revert blindly. They define a regression test: a known input file should produce 10,000 valid rows, and the defective version produces 7,400. They identify a tag from the last successful release as good and begin a bisect: git bisect start
git bisect bad
git bisect good release-2.4
git bisect run ./tests/check_transaction_count.sh
git bisect reset The script identifies a commit that changed date parsing. The team uses git show to inspect the patch and discovers that timestamps were interpreted in local time rather than UTC. SolutionBecause the defective commit has already been deployed and shared, the team does not reset the production branch. They create a corrective commit, add a regression test, and use: git add pipeline/clean.py tests/test_transaction_count.py
git commit -m "Interpret transaction timestamps as UTC"
git push If the original change had to be removed immediately and safely, they could have used git revert while preparing the permanent correction. They communicate the incident, preserve the original commit for auditability, and verify the corrected output in CI and staging. Outcome and learning pointsThe pipeline returns the expected record count, and the regression test prevents recurrence. The case demonstrates that log, diff, blame, and bisect answer different investigative questions; that a public defect should normally be corrected with a new commit; and that tests are essential to making historical investigation reliable. 9. Best Practices and Limitations- Inspect
git status before and after any history operation. - Make a temporary safety branch before experimenting with unfamiliar recovery commands.
- Prefer small, logically coherent commits with explanatory messages.
- Use
diff --staged to review exactly what will be committed. - Treat
--hard, rebase, and force-push as high-risk operations. - Communicate before rewriting any branch others may use.
- Use tags for releases and protect important branches.
- Keep reproducible tests for
bisect; an unstable test produces unreliable conclusions. - Do not interpret
blame as a measure of personal fault. - Remember that Git history is not automatically a complete compliance record; access controls, retention policies, backups, and review systems may also be required.
Git’s commands cannot recover work that was never committed, and local reflogs cannot guarantee recovery after expiration or repository loss. Version control improves recoverability, but it does not replace backup, testing, documentation, or sound team processes. Chapter SummaryGit history can be inspected with log, show, and diff, while blame traces line-level changes and bisect efficiently locates a regression when good and bad states are testable. Recent unpublished work can often be corrected with amend, restore, reset, or interactive rebase. Public history should generally be corrected with revert rather than rewritten. Cherry-pick transfers selected changes, stash temporarily stores unfinished work, and reflog helps recover references after resets or rebases. The central professional principle is to distinguish private history refinement from public history correction and to communicate before using operations that may overwrite shared references. Key TermsCommit: A recorded snapshot with metadata and parent references. HEAD: The current checked-out commit or symbolic branch position. Branch: A movable reference to a commit. Index: The staging area used to construct the next commit. Working tree: The files currently checked out on disk. Diff: A representation of differences between repository states. Patch: The specific change represented by a diff or commit. Blame: A line-oriented attribution of the most recent modifying commit. Bisect: A binary-search procedure for locating a regression. Amend: Replacement of the latest commit with an updated commit. Revert: A new commit that inverses an earlier commit. Reset: Movement of a branch reference, optionally changing index and files. Restore: A command for restoring working-tree or staged file contents. Rebase: Reconstruction of commits on a new base. Cherry-pick: Application of one commit’s change onto another branch. Stash: A local temporary record of uncommitted changes. Reflog: A local record of reference movements. Fast-forward: Updating a reference directly to a descendant without creating a merge commit. History rewriting: Changing commit identities or branch reachability after commits exist. Regression: A previously working behaviour that becomes defective after a change. Review QuestionsShort Answer Questions- What is the difference between the working tree and the index?
- What information does
git show normally display for a commit? - Why is
git revert generally safer than git reset on a shared branch? - What is the purpose of
git reflog? - Why must a
git bisect test be repeatable?
Long Answer Questions- Explain the differences among
restore, reset, and revert, including an appropriate use case for each. - Describe how
git log, git diff, git show, and git blame can be combined to investigate a defect. - Explain interactive rebase and discuss why it is useful before code review but risky after publication.
- Describe the role of
cherry-pick and the risks of transferring a commit without its prerequisites. - Explain how reflog-based recovery works and identify its limitations.
Analytical / Application Questions- A colleague has pushed a commit containing a severe error to a shared release branch. Compare reset, revert, and a corrective commit, and recommend a course of action.
- A test fails on the current branch but passed at the last release tag. Design a complete bisect procedure, including how you would define good and bad states.
- You accidentally run
git reset --hard HEAD~2. Explain the recovery steps you would attempt and the precautions you would take before changing the recovered branch.
Multiple Choice Questions with AnswersQuestion 1Which command compares changes in the working tree with the staging area? A. git diff B. git diff --staged C. git log -p D. git show HEAD~1 Correct Answer: A. git diff Question 2Which operation creates a new commit that reverses an earlier commit? A. git reset B. git restore C. git revert D. git stash Correct Answer: C. git revert Question 3What is the principal purpose of git bisect? A. To combine multiple branches B. To locate a commit associated with a regression C. To save uncommitted changes D. To rename a branch Correct Answer: B. To locate a commit associated with a regression Question 4Which command is most appropriate for correcting the latest unpublished commit message? A. git commit --amend B. git revert HEAD C. git stash pop D. git blame HEAD Correct Answer: A. git commit --amend Question 5What does git reset --soft HEAD~1 normally do? A. Deletes all untracked files B. Moves the branch back while leaving changes staged C. Creates an inverse commit D. Downloads the previous commit Correct Answer: B. Moves the branch back while leaving changes staged Question 6Which command records local movements of references such as HEAD? A. git show B. git reflog C. git diff D. git archive Correct Answer: B. git reflog Question 7What is a major risk of rebasing commits that colleagues have already downloaded? A. It automatically encrypts the repository B. It changes commit identities and can disrupt collaborators C. It prevents all future merges D. It removes the working tree only Correct Answer: B. It changes commit identities and can disrupt collaborators Question 8Which command applies a selected commit to the current branch as a new commit? A. git cherry-pick B. git restore C. git clean D. git tag Correct Answer: A. git cherry-pick Question 9What does git blame primarily report? A. The lines changed most recently and their associated commits B. All branches containing a remote C. The size of the repository D. The files excluded by .gitignore Correct Answer: A. The lines changed most recently and their associated commits Question 10Which statement about git stash is correct? A. It permanently publishes work to the remote B. It temporarily records uncommitted changes locally C. It rewrites every commit on the current branch D. It automatically creates a release tag Correct Answer: B. It temporarily records uncommitted changes locally
|