Home / Alt manpages / git-mktree(1)

  • git-mktree(1)
  • User command
  • linux

Build a Tree Object Safely with git mktree

git mktree turns git ls-tree output back into a real Git tree object, byte for byte if you want it to. Most people never touch it until they need to assemble or edit a tree by hand, and that output is the only sane input for doing so. You will finish with a repeatable way to build a tree, check it contains what you expect, and understand how missing objects or batch input change the command's behaviour. The workflow uses Git 2.43.0, the version installed on this machine.

  • Time: about fifteen minutes.
  • You need: Git and an existing repository, or a disposable one for the first test. No sudo required.
  • What it will not touch: the examples create objects in the repository's object database, but they never change files in the working tree, the index, a branch or a commit.

1. Check the installed command

Confirm the version and the options available on your host. This is a read-only check:

$ git --version
git version 2.43.0
$ git mktree -h
usage: git mktree [-z] [--missing] [--batch]

The command reads standard input and prints the object name of each tree it creates. It accepts non-recursive ls-tree format: a mode, object type, object ID, a tab, then a path, for example 100644 blob OBJECT_ID<tab>notes.txt. Do not drop the type field or swap the tab for guessed punctuation.

Checkpoint

The version is known, and your input will come from git ls-tree or from text that follows its exact format.

2. Make a disposable tree to inspect

For a safe first run, create a temporary repository. The directory name below is only an example; pick a location you can remove once you have checked it:

$ mkdir git-mktree-demo
$ cd git-mktree-demo
$ git init
$ printf 'alpha\n' > alpha.txt
$ printf 'beta\n' > beta.txt
$ git add alpha.txt beta.txt
$ tree_id=$(git write-tree)
$ printf 'tree: %s\n' "$tree_id"
tree: 40-hexadecimal-object-id

git write-tree records the current index as a tree and prints its ID, which gives git mktree a real tree to round-trip. Neither command touches the two files themselves after they are created. Inspect the tree without recursion:

$ git ls-tree "$tree_id"
100644 blob OBJECT_ID_1    alpha.txt
100644 blob OBJECT_ID_2    beta.txt

The object IDs above stand in for whatever your repository actually produces. The spacing in displayed ls-tree output is only for readability; the input parser depends on the format Git generates, including its path separator.

3. Rebuild the tree from ls-tree output

Pipe the listing straight into git mktree:

$ git ls-tree "$tree_id" | git mktree
OBJECT_ID_OF_REBUILT_TREE

With the same entries, the printed ID should match $tree_id. Capture and compare it when scripting:

$ rebuilt_id=$(git ls-tree "$tree_id" | git mktree)
$ test "$rebuilt_id" = "$tree_id" && echo 'tree round-trip verified'
tree round-trip verified

Git normalises the order of tree entries, so pre-sorting the input is not required. The output here is a tree object, not a commit and not a new branch: nothing shows up in git log unless a later operation puts the tree into a commit.

Checkpoint

The round-trip comparison succeeds. If it does not, check that the input was not edited, that the object IDs belong to this repository, and that you did not accidentally feed in recursive output meant for a different workflow.

4. Build a tree from edited entries

To change a tree, save a listing, edit exactly one complete entry, and feed the result back. Make a copy you can inspect before Git ever reads it:

$ git ls-tree "$tree_id" > tree.txt
$ sed -n '1,5p' tree.txt
100644 blob OBJECT_ID_1    alpha.txt
100644 blob OBJECT_ID_2    beta.txt
$ git mktree < tree.txt
OBJECT_ID_OF_EDITED_TREE
  • Change only the path and the new tree points an existing blob at a different name.
  • Change the object ID and it points at different content, but by default that object must already exist.
  • Directory entries use mode 040000 and type tree; submodule entries use mode 160000 and type commit. Preserve the mode and type git ls-tree emits rather than inventing combinations.

Before using a hand-edited file in a real repository, review it and compare the result:

$ git ls-tree OBJECT_ID_OF_EDITED_TREE
100644 blob OBJECT_ID_1    alpha.txt
100644 blob OBJECT_ID_2    beta.txt

Replace the example ID with the one your previous command printed. This inspection is the recovery point: if the listing looks wrong, discard the untracked text file and stop. There is no need to touch the index or checkout to run this experiment.

5. Treat missing objects as a deliberate exception

Without --missing, Git verifies that every referenced object actually exists, so a made-up entry fails:

$ printf '100644 blob deadbeefdeadbeefdeadbeefdeadbeefdeadbeef\tblank\n' | git mktree
fatal: entry 'blank' object deadbeefdeadbeefdeadbeefdeadbeefdeadbeef is unavailable

--missing lets the tree be created with that unresolved reference anyway:

$ printf '100644 blob deadbeefdeadbeefdeadbeefdeadbeefdeadbeef\tblank\n' | git mktree --missing
NEW_TREE_OBJECT_ID

This is useful for low-level object assembly and diagnostics, not for hiding a broken repository: commands that later need the missing blob can still fail, and a tree written without a ref can become unreachable during normal maintenance. Keep the input and the printed ID if you need to investigate it. Gitlinks are a special case: the manual says a missing gitlink target is allowed regardless of --missing, which does not make an ordinary missing blob safe to ignore.

6. Use NUL input for unusual paths

When paths contain characters that make line-oriented processing awkward, use the matching NUL mode on both sides:

$ git ls-tree -z "$tree_id" | git mktree -z
OBJECT_ID_OF_REBUILT_TREE

Do not mix git ls-tree -z with a plain git mktree, or the reverse: the terminator mode must match on both ends. The NUL form is especially handy when piping through tools that preserve NUL records. Do not display the raw stream in a terminal; NUL characters are control data, not readable separators.

7. Process more than one tree with batch mode

--batch reads multiple trees separated by one blank line and prints one ID per tree. This example sends the same listing twice:

$ { git ls-tree "$tree_id"; printf '\n'; git ls-tree "$tree_id"; } | git mktree --batch
OBJECT_ID_OF_FIRST_TREE
OBJECT_ID_OF_SECOND_TREE

The two IDs should be equal, since the input trees are equal. The final newline is optional. With -z, use NUL-terminated records and the corresponding NUL batch format instead. A blank line inside a path, or a malformed record, is not a separator you should try to guess your way around: regenerate the input with git ls-tree and inspect it properly.

Common traps and recovery

git mktree never updates HEAD, a branch, the index or the working tree. If you expected a checkout change, use the appropriate higher-level command once you have verified the tree, and make sure you know exactly which ref or commit it will change. Do not run a ref-update command as a reflex just because git mktree printed an ID.

There is no destructive undo step for anything shown here. To abandon an experiment, stop using the printed tree ID and remove only any scratch input file you created; the newly written object may stay unreachable in the object database until Git's own maintenance clears it. Do not run repository cleanup just to erase one test object; preserve the repository first if the object actually matters. If parsing fails, check the mode, type, full object ID, tab separator and path. If object verification fails, locate the object in the same repository or stop and reassess whether --missing is appropriate. If a batch run produces fewer IDs than expected, count the blank-line separators and validate each tree independently.

Done means

  • Version checked: you confirmed the installed Git version and command syntax.
  • Tree produced: you built a tree from valid non-recursive ls-tree input.
  • Round-trip verified: an ID comparison confirmed an unchanged tree.
  • Missing objects understood: ordinary entries need existing objects unless --missing is deliberate.
  • NUL mode matched: you used -z on both sides when handling NUL-terminated input.
  • Batch format respected: you separated trees with a blank line when using --batch.
  • Nothing else touched: branches, commits, the index and the working tree stayed unchanged.