Home / Alt manpages / git-read-tree(1)

  • git-read-tree(1)
  • User command
  • linux

Rebuild Git's Index Safely with git read-tree

git read-tree is plumbing: it loads a commit into Git's index and only touches your working files when you explicitly say so. This walkthrough covers inspecting a tree in a throwaway index, running a real merge, reading the three-way conflict stages, and emptying an index without wrecking the one you actually use. The examples use Git 2.43.0 from Debian package git-man 1:2.43.0-1ubuntu7.3, the version installed on the reference machine.

  • You need: Git, a test repository, and a working understanding of commits, the index and the working tree.
  • Not a replacement for git switch or git merge. Reach for those first if that is really what you want.
  • Time: about 20 minutes.

Safety boundary

Commands that write the normal index can affect later commits and staged changes. Start in a disposable clone or use a separate index with GIT_INDEX_FILE. Do not use --reset or --empty on a valuable index until you have saved what you need.

1. Check the installed command

Confirm the binary and version first. This is read-only and needs no elevated privileges:

$ command -v git
/usr/bin/git
$ git --version
git version 2.43.0
$ git read-tree -h

The basic form is git read-tree TREE. A tree-ish can be a commit, a tag that resolves to one, or a tree object. Git reads the tree contents into the index but leaves working files alone unless you add -u.

Checkpoint

If the version or help output is not what you expect, stop and read the manual installed with that Git package before copying a command from another host.

2. Inspect a tree in a separate index

Use a temporary index when your aim is to look at a commit rather than stage changes in the repository. The file just needs to sit in a directory you can write to:

$ scratch_index="$PWD/.git/read-tree-check.index"
$ GIT_INDEX_FILE="$scratch_index" git read-tree HEAD
$ GIT_INDEX_FILE="$scratch_index" git ls-files
README.md
src/main.c

Your paths will differ. The second command reads the temporary index, so it proves the tree loaded without touching the repository's usual index, and it does not write those paths' contents into the working directory.

  • Check before you commit to anything destructive. Do not point GIT_INDEX_FILE at .git/index by accident while experimenting:
$ printf 'index: %s\n' "${GIT_INDEX_FILE:-.git/index}"
index: .git/read-tree-check.index

When you have finished, remove only the disposable index you created. It is the one destructive cleanup step here, and it does not touch tracked files:

$ rm -- "$scratch_index"

Need to keep the inspection state around? Leave the file in place and note what it represents; an index file without its matching repository and object database is not a portable report on its own.

3. Load a commit into the normal index without touching files

In a repository whose working tree already matches the commit you want to inspect, a single-tree read loads the index and leaves files alone:

$ git read-tree HEAD
$ git diff --cached --quiet; printf 'cached diff status: %s\n' "$?"
cached diff status: 0

A zero status from git diff --cached --quiet means the index has no staged difference from HEAD. It does not prove the working tree is clean, check both kinds of state when it actually matters:

$ git status --short

If the command refuses because the index or working tree does not match the tree's expected state, do not reach for -f or a blind reset. Save or commit the work first, then retry from a known state.

4. Preview a reset before changing the index

--dry-run checks whether the operation would fail, without updating the index or working files, useful ahead of a reset or merge:

$ git read-tree --dry-run --reset origin/main
$ printf 'dry-run status: %s\n' "$?"
dry-run status: 0

Swap in a ref that exists locally. A zero status says Git accepted the operation under current conditions, it does not perform it, so verify the index again only after the non-dry command runs.

Warning

--reset is more forceful than a plain read. It discards unmerged index entries instead of refusing, and with -u it will not abort merely because an update could lose working-tree changes or untracked files. That combination can destroy local work. Treat it as a recovery operation, make a backup or use a disposable clone, and do not run it with elevated privileges.

5. Fast-forward the index from two trees

Two tree arguments express a fast-forward-style merge: the first is the current base, the second the target. Git carries local index and working-tree changes forward only where its rules say it is safe:

$ base=$(git rev-parse HEAD)
$ target=$(git rev-parse origin/main)
$ git read-tree -m "$base" "$target"
$ git diff --cached --name-status "$target"

The final command lists staged differences between the resulting index and the target tree. No output means the index already matches the target for the paths Git compared. A non-zero result or an "entry not up to date" error means local work needs attention, that is a protection, not a nudge to retry with --reset.

Without -u, this touches the index only. Useful when a later command will update files, but it can also surprise you: the index and working tree may briefly describe different states.

6. Update files only when you mean to

Add -u to ask a successful merge to update the working tree too:

$ git read-tree -m -u "$base" "$target"
$ git status --short

No output there means the index and files now agree. Existing local edits that would be overwritten should make the command fail rather than quietly disappear, but still treat -u as a destructive boundary: review git status --short, save important work, and run --dry-run first.

There is no undo flag for a successful index or working-tree update. Recovery is ordinary Git recovery: restore a saved copy, use git restore from a known commit, or pull committed content back from the object database. Uncommitted edits that got overwritten cannot be reconstructed by git read-tree itself.

7. Understand three-way merge entries

With three trees and -m, Git uses the arguments in a fixed order: common ancestor, current branch, then the other branch. Anything it cannot resolve trivially stays in the index at stages 1, 2 and 3:

$ ancestor=$(git merge-base HEAD topic)
$ ours=$(git rev-parse HEAD)
$ theirs=$(git rev-parse topic)
$ git read-tree -m "$ancestor" "$ours" "$theirs"
$ git ls-files -u
100644 1111111111111111111111111111111111111111 1	path/to/file
100644 2222222222222222222222222222222222222222 2	path/to/file
100644 3333333333333333333333333333333333333333 3	path/to/file

Those object IDs are illustrative placeholders, not output to copy, yours will contain real ones. Stage 1 is the ancestor, stage 2 the current branch, stage 3 the other tree. An index holding these unmerged entries cannot be written as a normal tree until a higher-level merge workflow resolves each path.

git read-tree only handles the trivial cases itself. It does not write conflict markers into files or pick a project-specific merge policy: use git merge for a normal user-facing merge, and if you are building plumbing, inspect the stages and resolve them deliberately before running git write-tree.

8. Empty an index only in a controlled workflow

--empty replaces the selected index's contents with an empty index. It does not delete tracked files from the working tree by itself:

$ isolated_index="$PWD/.git/empty-test.index"
$ GIT_INDEX_FILE="$isolated_index" git read-tree HEAD
$ GIT_INDEX_FILE="$isolated_index" git read-tree --empty
$ test -z "$(GIT_INDEX_FILE="$isolated_index" git ls-files)" && echo 'index is empty'
index is empty

Handy in scripts that build an index before writing a tree. It is also a trap against the normal index: run there by mistake and later commands can report every tracked file as untracked or missing. Keep the operation isolated unless your workflow genuinely needs an empty normal index.

Done means

  • Version checked. Installed Git 2.43.0 command and its local help match expectations.
  • Experiments isolated. A separate GIT_INDEX_FILE used, and its path double-checked.
  • Read vs write understood. A normal read changes the index, -u can change working files too.
  • Risky operations previewed. --dry-run checked first, --reset never used as a shortcut around saved work.
  • Conflict stages readable. You can read stages 1, 2 and 3 and know when a porcelain merge is the safer tool.
  • Recovery path in hand for anything that changed the normal index or working tree.