Turn a Git Index into a Tree with git write-tree
git write-tree freezes whatever is sitting in your Git index into a tree object, no commit required. The examples here were checked against Git 2.43.0 from the git-man package, version 1:2.43.0-1ubuntu7.3.
The route
Jump straight to the step you need, or tick off Done means at the end.
Allow about fifteen minutes. You need Git and a repository where you can make a temporary, commit-free change. This is ordinary user-level plumbing: no sudo, nothing published, no branch moved.
1. Check the installed command
Start in the repository whose index you want to snapshot. Confirm the executable and version before you trust its output in a script:
$ command -v git
/usr/bin/git
$ git --version
git version 2.43.0
$ git write-tree -h
usage: git write-tree [--missing-ok] [--prefix=<prefix>/]
The command reads the index, not the working directory. It prints one tree object name to standard output. A tree is Git's directory-shaped object: paths, modes and references to blobs or nested trees. It has no author, no message and no parent, because it is not a commit.
2. Put the content you want in the index
Stage things the normal way first. In this example only tracked.txt goes in:
$ printf 'one\n' > tracked.txt
$ git add -- tracked.txt
$ git status --short
A tracked.txt
Checkpoint
The path must show in the index column of git status --short. A path shown only in the working-tree column is not part of the tree git write-tree is about to build. On a real repository, do not run this blind over valuable unstaged edits; stage only what you actually want included.
3. Write the tree and capture its ID
Run the command with no options:
$ tree_id=$(git write-tree)
$ printf 'tree: %s\n' "$tree_id"
tree: f46a6a9d2d0dfeb46eba9132c1a874fd01cd776f
Your object ID will differ. It is a 40-character SHA-1 name in this repository's format. The command writes the tree object into Git's object database, but it leaves the index, working files, current branch and commit history untouched.
Check what actually got written:
$ git ls-tree "$tree_id"
100644 blob 7f... tracked.txt
The blob ID and spacing vary. What matters is that the expected path and file mode are there. To confirm the object type on its own:
$ git cat-file -t "$tree_id"
tree
4. Know where the index boundary bites
git write-tree has no idea a file changed until you stage it. Change the working file, then write another tree without touching the index:
$ printf 'two\n' > tracked.txt
$ stale_id=$(git write-tree)
$ printf '%s\n' "$stale_id"
f46a6a9d2d0dfeb46eba9132c1a874fd01cd776f
$ git diff -- tracked.txt
diff --git a/tracked.txt b/tracked.txt
...
Same ID, because the index still holds one. This is the classic distraction: the working directory looks current, while the index is the actual input. Update the index and run the command again if the tree should carry the new content:
$ git add -- tracked.txt
$ current_id=$(git write-tree)
$ test "$current_id" != "$stale_id" && echo "tree changed"
tree changed
Recovery
There is no undo for writing a tree object, which is normally harmless since unreachable objects get collected later. To undo the example's file edit instead, restore it from the index, and only when you are certain the edit is disposable:
$ git restore --worktree -- tracked.txt
Warning
That restore destroys the unstaged edit. Do not make it a routine verification step; copy or commit anything valuable first.
5. Stop dead on an unmerged index
The index must be fully merged. Mid-conflict it can hold stage 1, 2 and 3 entries for one path, and in that state git write-tree refuses to build a normal tree and exits non-zero:
$ git write-tree
conflict.txt: unmerged (...)
fatal: git-write-tree: error building trees
The object IDs in that message vary. Check the state before chasing anything else:
$ git status
$ git ls-files --unmerged
Resolve the conflict your usual way, then stage the resolved file. Confirm git ls-files --unmerged prints nothing before you try again. Do not sidestep a conflict just to get an object ID out of it: the resulting tree would not represent a settled index.
6. Write just a subtree with --prefix
Use --prefix=SUBDIRECTORY/ when you want the tree for one directory rather than the repository root. The prefix has to name a subdirectory that actually exists in the index:
$ git add -- sub/value
$ subtree_id=$(git write-tree --prefix=sub/)
$ git ls-tree "$subtree_id"
100644 blob ... value
Handy when a larger process only needs the contents of a subproject or directory. It is not a new checkout, and it will not strip the prefix from the index for you. A path missing from the indexed data gets you an error, not an invented empty directory.
7. Treat --missing-ok as an expert-only switch
By default Git checks that every object the index references actually exists in the object database. --missing-ok turns that check off. It exists for specialised plumbing work where an incomplete object database is intentional, not as a repair switch for an ordinary repository.
Do not reach for --missing-ok just because the plain command failed. Check the repository and the index first, and keep the default integrity check when the tree is heading into a commit or another tool. A tree that names missing objects can be unusable to whatever reads it later.
Done means
- Staged the right paths and know the index, not the working tree, is the input.
- Got a tree ID back from
git write-treewith no elevated privileges involved. - Confirmed the contents:
git ls-treeshows the expected paths andgit cat-file -treportstree. - Checked for unmerged entries before trusting the result.
- Used --prefix only for an indexed subdirectory and left
--missing-okalone unless an expert workflow needs it. - Left everything else untouched: no commit, branch, working file or remote changed by the tree-writing step.