Git stores immutable objects and moves small references between them. Daily work should use porcelain commands, while the storage model explains why rewrites create new history and why recovery remains possible for a period.
Inspect the Repository Database #
The .git directory contains repository-local state:
| Path | Purpose |
|---|---|
HEAD | Symbolic name of the checked-out branch, or a detached object ID |
config | Repository configuration |
index | Proposed snapshot for the next commit |
objects/ | Loose objects and packfiles |
refs/ and packed-refs | Branch, tag, and remote-tracking names |
logs/ | Local reflogs for references |
hooks/ | Client-side hook samples and installed hooks |
modules/ | Git directories for initialized submodules |
A worktree holds checked-out files. The index records a candidate tree. A commit points to a tree together with parent IDs and metadata. A branch is a movable reference to a commit; it is not a mutable container of commits.
Inspect names and object types without decoding files manually:
git rev-parse --git-dir
git show-ref --head
git cat-file -t HEAD
git cat-file -p HEAD
git count-objects -vHModel Objects and Content Addressing #
Git identifies an object by hashing a canonical byte sequence:
<type><space><decimal-size><NUL><content>SHA-1 repositories use a 160-bit object ID. New repositories can use SHA-256. The object ID identifies content and structure; it does not encode a time or a sequence number.
Git has four core object types:
- A blob stores file bytes without a filename.
- A tree maps path names and modes to blobs and subtrees.
- A commit names a root tree, zero or more parents, author and committer identities and times, and a message.
- An annotated tag stores tag metadata and points to another object.
A tree entry mode of 100644 is a normal non-executable file, 100755 is an
executable file, 120000 is a symbolic link, and 160000 is a submodule
gitlink. A gitlink points at a commit in another repository rather than
embedding that repository’s files.
Create and inspect objects in a disposable repository:
printf 'hello\n' | git hash-object -w --stdin
git cat-file -p <object-id>
git ls-tree HEAD
git ls-tree -r HEADLoose objects live under .git/objects/aa/bb..., where aa is the first byte
of the hexadecimal ID. Git zlib-compresses their canonical representation.
git cat-file is safer than manually adding compression headers or using
ad-hoc decompression pipelines.
Understand Packs, Deltas, and Reachability #
Many loose objects cost filesystem metadata operations. git gc can collect
reachable objects into packfiles in .git/objects/pack/. A .pack file holds
object representations, an .idx file maps object IDs to pack offsets, and a
.rev file can speed operations that need pack order.
Packfiles may delta-compress objects against other objects. Deltas are a storage optimization, not Git’s history model: every commit still names a complete root tree, and Git can reconstruct any referenced blob through its object graph. A commit does not store only the textual diff from its parent.
Reachability starts at references such as branches, tags, remote-tracking
branches, and reflogs. Garbage collection retains reachable objects and can
prune unreachable objects after configured expiration. git fsck reports
connectivity and can locate unreachable objects:
git fsck --full --no-reflogs --unreachable
git cat-file -p <unreachable-object-id>Read References and the Index #
HEAD commonly contains ref: refs/heads/main. A detached checkout contains
an object ID directly. Loose branch names are files under refs/heads/, while
many references can be consolidated in packed-refs.
The index is not a list of changed files. It stores the path, mode, object ID, and stat cache for the next tree. During a merge conflict, the index can carry multiple stages for one path:
| Stage | Meaning |
|---|---|
| 1 | Merge base |
| 2 | Current branch version |
| 3 | Other branch version |
Inspect index entries with:
git ls-files --stage
git diff --cachedResolving a conflict and running git add replaces stages 1–3 with one
stage-0 entry.
Explain Why Rewriting Changes IDs #
A commit includes its parent object IDs. Changing its message, tree, author, committer, timestamp, or parent produces a different commit ID. Every descendant must be recreated because its recorded parent changes.
Rewrite local, unpublished work freely when it improves history. Coordinate
before rewriting a shared branch. A forced update can discard a collaborator’s
new remote commit; use --force-with-lease when a force push is necessary:
git push --force-with-lease origin feature/topicAmend the Latest Commit #
git commit --amend creates a replacement for HEAD and moves the current
branch:
git add README.md
git commit --amend --no-editRun git commit --amend without --no-edit to replace the message. The
original commit remains recoverable through the reflog until expiry and
pruning.
Edit Older Commits Interactively #
Interactive rebase rewrites the commits after the selected endpoint:
git rebase -i HEAD~3The command presents the last three commits. Its todo commands include:
| Command | Effect |
|---|---|
pick | Keep a commit |
reword | Replace its message |
edit | Stop after applying it |
squash | Combine it with the preceding commit and edit the message |
fixup | Combine it and discard its message |
drop | Omit it |
To change an older commit’s contents, mark it edit, amend when the rebase
stops, and continue:
git add path/to/file
git commit --amend --no-edit
git rebase --continueResolve conflicts, stage the resolved paths, and continue. git rebase --abort
returns the branch to its pre-rebase state. git rebase --edit-todo changes
remaining instructions while a rebase is paused.
Recover with Reflogs #
Every local reference movement can have a reflog entry. Create an explicit backup name before a risky rewrite:
git branch backup/before-rewriteThen locate and name an earlier state:
git reflog show --date=iso HEAD
git switch -c recovered-work HEAD@{1}Reflogs are local implementation history, not a distributed backup. Publish important work, preserve remote backups, and avoid running destructive cleanup before recovery is complete.
Comments
Read or comment on GitHub (sign-in required to comment)