Chapter 4
History Inspection, Restoration and Rewriting
Bachelors Program in Full Stack Development
Git, GitHub & Version Control
•
Bachelor
History Inspection, Restoration and Rewriting
Introduction
Version control is valuable not merely because it records changes, but because it enables developers to investigate, compare, restore and—when justified—rewrite those changes. These activities require a precise mental model. A command that modifies only the working tree has very different consequences from one that moves a branch reference or publishes an inverse commit.
The central professional distinction is between private history, which has not been shared and can usually be rewritten safely, and shared history, on which other developers or automated systems may depend. In shared history, additive correction with git revert is generally safer than destructive rewriting.
Learning Objectives
After completing this chapter, students should be able to:
navigate commit history using references, revision expressions and path filters;
compare commits, branches, the index and the working tree;
restore tracked files from the index or a specified revision;
distinguish the effects of restore, reset and revert;
amend commits and conduct an interactive rebase;
recover displaced commits using the reflog;
select safe correction techniques for private and shared histories;
evaluate security, governance and collaboration implications of history rewriting.
1. The Repository State Model
Git operations become easier to predict when the repository is understood as four related layers:
Commit history: immutable commit objects connected through parent references.
HEAD and branch references: names that identify commits.
Index: the proposed contents of the next commit, also called the staging area.
Working tree: files currently available for editing.
A Git commit records a root tree, author and committer information, a message, and zero or more parent references. A root commit has no parent, an ordinary commit normally has one, and a merge commit normally has two or more. A commit represents a snapshot rather than a stored textual patch, although Git can calculate differences between snapshots.
HEAD normally points symbolically to the current branch, while the branch reference points to the current commit. In a detached-HEAD state, HEAD points directly to a commit.
Git repository state layers and common data movements
Figure: Git repository state layers and common data movements
Figure: Git’s principal state layers and selected operations between them.
Before executing a corrective command, ask three questions:
Which source state will provide the replacement content?
Which destination layer will change?
Will a branch reference or published history move?
--graph draws the parent structure, --decorate displays branch and tag names, and --all includes commits reachable from all local references rather than only the current branch.
Path-limited history can conceal commits that did not affect the selected path. Git also detects renames heuristically rather than storing a permanent “rename” operation. To follow a file across likely renames, use:
git log --follow -- app/old-name.js
--follow applies to a single path and is not guaranteed to identify every complex rename or split.
2.2 Naming revisions
Git accepts full object IDs, unambiguous abbreviated IDs, branches, tags and relative expressions.
Expression
Meaning
HEAD
Current checked-out commit
HEAD~1
First parent of HEAD; equivalent to HEAD~
HEAD~3
Follow first-parent links three times
HEAD^1
First parent of HEAD
HEAD^2
Second parent of a merge commit
main@{yesterday}
Reflog-based prior value, where available
v2.0^{commit}
Commit referenced by tag v2.0
The distinction between ~ and ^ matters at merges. ~n repeatedly follows first parents, whereas ^n selects the nth immediate parent.
2.3 Revision sets and three-dot diff syntax
For revision walking, such as with git log:
A..B denotes commits reachable from B but not reachable from A.
A...B denotes the symmetric difference: commits reachable from either endpoint but not from both.
The three-dot notation has a command-specific meaning in git diff. The command:
git diff A...B
compares the merge base of A and B with B; in the ordinary single-merge-base case, it is equivalent to:
git diff $(git merge-base A B) B
It does not display a “diff of the symmetric-difference commit set.” By contrast:
git diff A B
compares the endpoint snapshots directly. git diff A..B is also interpreted as an endpoint comparison, not as the revision set used by git log.
This distinction is especially useful in pull-request review. git diff main...feature asks, in effect, “What has the feature branch changed since it diverged from main?”
2.4 Examining commits and lines
git show displays an object, normally a commit and its patch:
git show 7ac21de
git show HEAD:src/config.js
git show HEAD~2:src/config.js
The colon syntax reads a path from a tree without checking it out.
To investigate line ownership:
git blame -L 20,45 src/auth.js
git blame identifies the most recent commit affecting each selected line. It does not establish moral responsibility or prove who designed the code. Refactoring, copied code and formatting commits can distort its apparent attribution.
Untracked files are not included in ordinary diffs until added to the index, although git status reports them. Binary files generally cannot be reviewed as meaningful line-based patches without specialized diff drivers.
4. Restoring Files Precisely
git restore changes file content in the working tree, the index, or both. It does not normally move the current branch.
4.1 Restoring the working tree from the index
The default source for a working-tree-only restoration is the index:
git restore -- file.txt
This replaces the working-tree version of file.txt with the version currently staged in the index. If the index differs from HEAD, the restored working file will match the index—not HEAD.
To guarantee restoration from HEAD, specify the source explicitly:
git restore --source=HEAD -- file.txt
This changes the working-tree file but leaves any staged version in the index unchanged.
4.2 Unstaging a file
To restore the index from HEAD while retaining the working-tree content:
git restore --staged -- file.txt
When --staged is used without an explicit source, the default source is HEAD. This operation is commonly described as “unstaging” the file.
To replace both the index and working-tree file with the HEAD version, use an explicit source:
git restore principally operates on tracked paths. It should not be expected to recover an untracked file that Git never recorded. Before restoration, inspect git status and git diff; when uncertainty exists, create a temporary branch, stash or work-in-progress commit.
5. Resetting References and State
git reset has two conceptually different forms:
Commit-level reset, which moves the current branch and may update the index and working tree.
Path-level reset, which updates selected index entries without moving the branch.
moves the current branch back one first-parent commit while leaving the removed commit’s changes staged. This is useful for rebuilding a private commit.
git reset HEAD~1
moves the branch back and resets the index to the target, while preserving tracked working-tree changes as unstaged modifications.
git reset --hard HEAD~1
moves the branch and makes the index and tracked working tree match the target. Uncommitted tracked changes can be lost. Untracked files are not generally removed merely because --hard is used, although files obstructing checkout of tracked paths may be deleted. Commands such as git clean require separate caution because they can delete untracked files.
5.2 Path-level reset
The command:
git reset HEAD -- file.txt
copies the selected path from HEAD to the index without moving HEAD or changing the working-tree file. A clearer modern equivalent is:
git restore --staged -- file.txt
Because reset can change different repository layers depending on its form, it should be used only after confirming the current branch, target and status.
6. Reverting Published Changes
git revert <commit> creates a new commit that applies the inverse of the selected commit’s change. Existing commits remain reachable in their original order.
git revert a18f4c2
This is generally the preferred correction for a faulty commit already shared on a collaborative branch. It supports auditability and avoids invalidating other developers’ branch ancestry.
Reversion is not equivalent to restoring an earlier snapshot. Suppose commit C adds an API endpoint and later commit D modifies it. Reverting C attempts to reverse the patch introduced by C in the current context. Because subsequent code may overlap, conflicts can arise.
Conflict handling follows the sequencer workflow:
git status
# edit and stage resolved files
git add <resolved-files>
git revert --continue
Alternatively:
git revert --abort
Reverting merge commits
A merge commit has multiple parents, so Git must know which parent represents the mainline to preserve:
git revert -m 1 <merge-commit>
-m 1 means “treat parent 1 as the mainline and reverse the net changes introduced relative to it.” The correct parent must be verified with git show or git log --graph; choosing it by habit is unsafe. Reverting a merge also affects how Git treats later attempts to merge the same ancestry, so teams should analyze the graph before proceeding.
7. Amending and Interactive Rebase
7.1 Amending the latest commit
After modifying or staging the required files:
git commit --amend
To change only the snapshot while retaining the existing message:
git commit --amend --no-edit
Amendment creates a new commit object and moves the branch to it. The previous commit may remain recoverable through the reflog, but it is no longer the branch tip. Even a message-only amendment produces a different commit ID because commit metadata is part of the object’s content.
Amending is appropriate for an unpublished commit. If the original commit has been pushed, amendment rewrites shared history and normally requires a force update.
7.2 Interactive rebase
Interactive rebase reconstructs a sequence of commits onto a selected base:
git rebase -i HEAD~4
An editor presents the commits in replay order. Common instructions include:
Instruction
Effect
pick
Replay unchanged
reword
Replay and edit the message
edit
Pause to modify the commit
squash
Combine with the preceding commit and edit messages
fixup
Combine with the preceding commit, normally discarding this message
drop
Omit the commit
break
Pause the rebase
To split a commit, mark it edit, then reset it while retaining its changes:
If conflicts occur, resolve them, stage the resolutions and run git rebase --continue. Use git rebase --abort to return to the pre-rebase state.
Rebase does not merely “move” existing commits. It normally creates new commits with new parent references and therefore new IDs. Commit signatures on replaced commits do not carry over automatically; replacement commits must be signed again if policy requires it.
7.3 Updating a remote after rewriting
If rewriting a remote branch is authorized, prefer:
git push --force-with-lease
Unlike unconditional --force, --force-with-lease refuses the update when the remote reference no longer matches the value expected by the local repository. This reduces—but does not eliminate—the risk of overwriting another contributor’s work. Protected branches may reject both forms.
8. Recovering Work with the Reflog
The reflog records recent changes to local references and HEAD, including commits displaced by reset, amend, rebase and branch movement.
If 64ac910 is the desired commit, preserve it immediately:
git branch recovery/refresh-token 64ac910
Alternatively, inspect it first with git show 64ac910 and then cherry-pick it where appropriate.
Reflogs are local operational records, not synchronized project history. A colleague’s clone may have different reflog entries. Entries expire according to repository configuration, and unreachable objects may eventually be pruned by garbage collection. Consequently, reflog is a recovery mechanism—not a substitute for backups, remote branches or reliable release archives.
If no reference or reflog entry identifies an object, git fsck --lost-found may sometimes locate dangling objects, but the results can be incomplete and difficult to interpret.
9. Selecting the Safest Technique
The following decision framework distinguishes content correction from history rewriting.
Situation
Preferred technique
Rationale
Discard unstaged edits and match the index
git restore -- file
Working-tree-only change
Make a working file match HEAD exactly
git restore --source=HEAD -- file
Explicit source avoids index ambiguity
Unstage while keeping edits
git restore --staged -- file
Changes index, not working file
Correct the latest private commit
git commit --amend
Produces clean unpublished history
Reorganize several private commits
git rebase -i
Supports edit, reorder, squash and drop
Undo a published faulty commit
git revert
Adds an auditable inverse commit
Rebuild a private branch from an earlier point
git reset
Efficient when no collaborator depends on old IDs
Recover a displaced local commit
git reflog plus a new branch
Restores reachability before expiry
A practical safety sequence is:
Run git status.
Inspect the graph with git log --oneline --graph --decorate --all.
Decide whether the affected commits are shared.
Create a temporary recovery branch if risk exists.
Preview relevant diffs.
Apply the least destructive command that satisfies the objective.
Test and review before pushing.
10. Applied Case: Correcting a Shared Authentication Regression
Background
This is an illustrative case. A development team maintains a full-stack commerce system. Developer Noor creates three local commits on feature/session-renewal: a database migration, token-refresh logic and test cleanup. Before review, Noor uses interactive rebase to squash two noisy correction commits into the token-refresh commit.
The branch is merged into main. Production monitoring then reveals that commit b731aa2 causes valid mobile sessions to expire prematurely. Meanwhile, two other developers have pulled main and added unrelated payment and accessibility commits.
Problem
The team must restore correct session behavior without removing subsequent shared work. A developer proposes git reset --hard b731aa2^ followed by a forced push. That action would move main behind not only the faulty commit but also later commits, disrupting collaborators and deployment traceability.
Analysis
Before the feature was shared, interactive rebase was appropriate because Noor alone depended on the feature branch’s commit IDs. After merge, main became shared history. The correction should therefore be additive.
The faulty commit is an ordinary single-parent commit and can be reversed independently. No database migration reversal is required because that change belongs to a separate commit.
Approach and solution
The incident lead creates a correction branch from the current main:
A conflict occurs because a later commit renamed one validation function. The team resolves the conflict in the current codebase, stages the file and continues:
Automated integration and mobile-session tests pass. The revert commit is reviewed through a pull request and merged without rewriting main.
Outcome
The faulty behavior is removed while payment and accessibility changes remain intact. The history clearly records the original change, its reversal and the reason for the incident response. Noor later develops a corrected implementation in a new commit rather than attempting to erase the incident.
Learning points
Interactive rebase was suitable while the feature history was private.
Resetting and force-pushing shared main would have endangered collaborators’ work.
Revert preserved ancestry and auditability.
Separating the migration and application behavior into coherent commits made selective reversal easier.
A revert must be tested in the current state because later changes can alter its effect.
11. Security, Governance and Professional Practice
History rewriting has organizational consequences. Protected branches, required reviews, signed commits and deployment records may be part of legal, security or quality controls. Rewriting can invalidate commit IDs referenced by issue trackers, audit reports, software bills of materials and release documentation.
A particularly important limitation is that git revert does not remove confidential information from repository history. If a password, token or private key is committed:
revoke or rotate the credential immediately;
report the incident under organizational policy;
assess logs, forks, caches and cloned repositories;
use an approved history-filtering process, commonly with git filter-repo, if removal is required;
coordinate the forced replacement of affected references;
prevent recurrence through secret scanning and credential management.
Deleting a secret from the latest file or reverting its commit is insufficient because earlier objects may remain accessible.
Commit metadata and blame output must also be used ethically. They provide technical provenance, not proof of intent, competence or sole responsibility. Incident analysis should focus on system and process improvements rather than simplistic attribution.
AI-assisted tools can propose commit messages, conflict resolutions and rebase plans, but their output must be reviewed. A plausible automated resolution may compile while silently changing authentication or data-handling semantics. Developers remain responsible for validating diffs and tests.
12. Limitations and Contemporary Practice
git restore and git switch, introduced in Git 2.23, separate common file-restoration and branch-switching tasks that older workflows often performed through overloaded forms of git checkout. Legacy commands remain common, so professionals must recognize both styles while preferring explicit commands in documentation and teaching.
No recovery command eliminates the need for backups. Reflog retention is finite, force-with-lease depends on the accuracy of local remote-tracking information, and conflict resolution remains a semantic task requiring domain knowledge. Large binary assets, generated files and repositories using external storage systems may require additional recovery procedures.
The safest professional practice combines small coherent commits, descriptive messages, protected shared branches, automated tests, reviewed pull requests and explicit recovery checkpoints before destructive operations.
Chapter Summary
Git history consists of immutable snapshot commits connected by zero or more parent references and reached through movable references. Effective investigation uses log, show, blame, revision expressions and carefully selected diffs. For revision walking, A...B denotes a symmetric-difference commit set, whereas git diff A...B compares the merge base with B.
Repository correction depends on the layer being changed. git restore file restores the working tree from the index by default; --source=HEAD is required to guarantee content from HEAD. Commit-level reset moves the current branch and may also replace the index and working tree. Revert instead creates a new inverse commit, making it preferable for shared history.
Amendment and interactive rebase improve private history but create replacement commits with new IDs. The reflog can recover recently displaced local commits, although its records are local and temporary. Professional decisions should prioritize collaboration safety, traceability, security policy and the least destructive operation capable of achieving the required result.
Key Terms
Amend: Replace the latest commit with a newly created commit.
Branch reference: A movable name that points to a commit.
Commit: An immutable snapshot object containing a tree, metadata, a message and zero or more parent references.
Commit graph: The directed parent relationship among commits.
Detached HEAD: A state in which HEAD points directly to a commit rather than a branch.
Diff: A calculated representation of differences between repository states.
Force-with-lease: A guarded forced push that checks an expected remote-reference value.
HEAD: The reference identifying the currently checked-out commit or branch.
Index: The staged snapshot proposed for the next commit.
Interactive rebase: A process for replaying and editing a sequence of commits.
Merge base: A best common ancestor used for branch comparison and merging.
Reflog: A local record of recent changes to references and HEAD.
Reset: An operation that changes a branch position, the index, or both, with an optional working-tree update.
Restore: An operation that replaces selected index or working-tree paths from a specified source.
Revert: A new commit that inversely applies the change introduced by an earlier commit.
Revision range: An expression selecting commits through reachability rules.
Shared history: Commit ancestry already published for use by other people or systems.
Working tree: The checked-out files available for editing.
Review Questions
Short Answer Questions
What are the four principal layers in the repository state model?
What is the default source of git restore -- file.txt?
How does git diff A...B differ from the symmetric-difference revision set A...B?
What repository layers are changed by git reset --soft?
Why is a reflog not a substitute for a backup?
Long Answer Questions
Explain the effects of git restore, git reset and git revert, including suitable use cases for each.
Compare --soft, --mixed and --hard reset modes using the commit history, index and working tree.
Describe how interactive rebase reconstructs history and explain why its use on shared branches is risky.
Explain how to investigate a suspected regression using revision expressions, git log, git show and git diff.
Discuss the security and governance implications of rewriting history that contains a committed secret.
Analytical / Application Questions
A developer has staged an incorrect version of config.js but has a correct unstaged version in the working tree. Analyze why git restore config.js would be dangerous and propose a safe sequence.
A team has already pulled a faulty commit from main, and several later valid commits exist. Determine whether reset or revert is more appropriate and justify your decision.
After an accidental git reset --hard HEAD~3, design a recovery procedure using the reflog, including steps that preserve the recovered commit before further work.
Multiple Choice Questions with Answers
Question 1
What source does git restore -- file.txt use by default when restoring only the working-tree file?
A. The index
B. The HEAD commit
C. The remote-tracking branch
D. The reflog
Correct Answer: A. The index
Question 2
Which command guarantees that the working-tree version of file.txt is restored from HEAD while leaving the index unchanged?
A. git restore --staged -- file.txt
B. git restore --source=HEAD -- file.txt
C. git reset --hard HEAD -- file.txt
D. git revert HEAD -- file.txt
Correct Answer: B. git restore --source=HEAD -- file.txt
Question 3
For ordinary revision walking, what does A...B select?
A. Only commits reachable from B but not A
B. All commits reachable from both A and B
C. Commits reachable from either A or B, but not both
D. Only the merge base of A and B
Correct Answer: C. Commits reachable from either A or B, but not both
Question 4
In the ordinary single-merge-base case, what comparison is performed by git diff A...B?
A. A against B directly
B. The merge base of A and B against B
C. The merge base of A and B against A
D. Every commit in the symmetric difference against its parent
Correct Answer: B. The merge base of A and B against B
Question 5
Which reset mode moves the current branch but preserves both the index and working tree?
A. git reset --soft
B. git reset --mixed
C. git reset --hard
D. git reset --merge
Correct Answer: A. git reset --soft
Question 6
Which operation is generally safest for undoing a faulty commit already used by collaborators?
A. Delete the branch and recreate it
B. Use git reset --hard and force-push
C. Create an inverse commit with git revert
D. Amend the published commit
Correct Answer: C. Create an inverse commit with git revert
Question 7
What is a necessary characteristic of a Git commit's parent references?
A. Every commit has exactly one parent
B. Every commit has exactly two parents
C. A commit has zero or more parents
D. Only root commits have parents
Correct Answer: C. A commit has zero or more parents
Question 8
Why does git commit --amend change a commit ID?
A. It renames the current branch
B. It creates a new commit object
C. It converts the commit into a tag
D. It deletes the commit's parent
Correct Answer: B. It creates a new commit object
Question 9
What is an important limitation of the reflog?
A. It records only remote branch changes
B. It is automatically shared with every collaborator
C. It cannot record reset operations
D. It is local and its entries may expire
Correct Answer: D. It is local and its entries may expire
Question 10
When reverting a merge commit, what does the -m option identify?
A. The new commit message
B. The mainline parent to preserve
C. The remote repository to update
D. The number of commits to revert
Correct Answer: B. The mainline parent to preserve