Git’s behind-the-scenes mechanics: index, stash, refs, and merge diffs
Digging into Git’s internals often surfaces details that are obvious in hindsight but rarely considered. A few of these stand out when you start explaining how the tool really works—particularly around the index, the stash, references, and merge commits.
The index, staging area, and --cached are one and the same
Running git add file.txt followed by git status shows the familiar output:
$ git add content/post/2023-10-20-some-miscellaneous-git-facts.markdown
$ git status
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: content/post/2023-10-20-some-miscellaneous-git-facts.markdown
That state is commonly called the “staging area,” but Git itself uses three interchangeable names for it: git diff --cached, git diff --staged, and the file .git/index. When you stage a file, Git writes it into the object database under .git/objects and updates .git/index to point to that new object. All three terms refer to that same underlying index file.
The stash is just a set of commits
It’s easy to wonder where changes go when you run git stash. The answer: Git creates commits for your changes and labels them with a reference named stash, stored at .git/refs/stash. Viewing the log for that reference shows what happened:
$ git log stash --oneline
6cb983fe (refs/stash) WIP on main: c6ee55ed wip
2ff2c273 index on main: c6ee55ed wip
... some more stuff
Inspecting the commit it points to confirms the stash contains your work-in-progress:
$ git show 2ff2c273 --stat
commit 2ff2c273357c94a0087104f776a8dd28ee467769
Author: Julia Evans <[email protected]>
Date: Fri Oct 20 14:49:20 2023 -0400
index on main: c6ee55ed wip
content/post/2023-10-20-some-miscellaneous-git-facts.markdown | 40 ++++++++++++++++++++++++++++++++++++++++
Notably, git stash doesn’t create a single commit—it creates two: one for the index and one for unstaged changes. Older stash entries are kept in the reflog.
References extend beyond branches and tags
Most of the time, a Git “reference” means a branch, HEAD, or a tag. But the stash is a concrete example of a reference that is none of those. In a typical repository, the full list of refs (excluding HEAD) can look like this:
$ find .git/refs -type f
.git/refs/heads/main
.git/refs/remotes/origin/HEAD
.git/refs/remotes/origin/main
.git/refs/stash
Other useful references that aren’t branches or tags include:
refs/notes/*, used bygit notesrefs/pull/123/merge, for GitHub pull requests (fetchable withgit fetch origin refs/pull/123/merge)refs/bisect/*, used bygit bisect
Merge commits aren’t really empty
In a toy repository with two branches, each containing a single file (x.txt and y.txt), merging them produces a commit that looks like this:
$ git log --oneline
96a8afb (HEAD -> y) Merge branch 'x' into y
0931e45 y
1d8bd2d (x) x
Running git show on that merge commit shows no diff:
git show 96a8afb
commit 96a8afbf776c2cebccf8ec0dba7c6c765ea5d987 (HEAD -> y)
Merge: 0931e45 1d8bd2d
Author: Julia Evans <[email protected]>
Date: Fri Oct 20 14:07:00 2023 -0400
Merge branch 'x' into y
But diffing the merge commit against each parent individually reveals the expected changes:
$ git diff 0931e45 96a8afb --stat
x.txt | 1 +
1 file changed, 1 insertion(+)
$ git diff 1d8bd2d 96a8afb --stat
y.txt | 1 +
1 file changed, 1 insertion(+)
The reason merge commits appear empty is that Git’s default merge diff only shows conflicts. In a repo where two branches add different content to the same file and the merge is resolved manually, the merge commit diff shows exactly that resolution:
$ git show HEAD
commit 3bfe8311afa4da867426c0bf6343420217486594
Merge: 782b3d5 ac7046d
Author: Julia Evans <[email protected]>
Date: Fri Oct 20 15:29:06 2023 -0400
Merge branch 'x' into y
diff --cc file.txt
index 975fbec,587be6b..b680253
--- a/file.txt
+++ b/file.txt
@@@ -1,1 -1,1 +1,1 @@@
- y
-x
++z
If there is no conflict, Git simply shows nothing. The merge commit is still a full snapshot of the repository—it just doesn’t display a diff unless there was something to resolve.



