Home / Alt manpages / git-mktree(1)

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

Build a Git Tree Object Safely with git mktree

You will finish with a repeatable way to turn git ls-tree output into a Git tree object, check that the object contains what you expect, and understand when missing objects or batch input change the command. The workflow uses Git 2.43.0, which is the installed version on this machine.

Allow about fifteen minutes. You need Git and an existing repository, or a disposable repository for the first test. The examples do not need sudo. They create objects in the repository's object database, but they do not 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. That format has a mode, object type, object ID, a tab, and a path, for example 100644 blob OBJECT_ID<tab>notes.txt. Do not remove the type field or replace the tab with guessed punctuation.

Checkpoint

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

2. Make a disposable tree to inspect

If you want a safe first run, create a temporary repository. The directory name below is only an example; choose a location you can remove after checking 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. It is used here to give git mktree a real tree to round-trip. The two files are not changed by either command 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 are placeholders for the IDs produced by your repository. The spacing in displayed ls-tree output is for readability; the input parser relies on the format generated by Git, including its path separator.

3. Rebuild the tree from ls-tree output

Pipe the listing directly to 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 is a tree object, not a commit and not a new branch. Nothing will appear 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 pass recursive output intended for a different workflow.

4. Build a tree from edited entries

To change a tree, save a listing, edit one complete entry, and feed the result back. First make a copy that can be inspected before Git 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

Changing only a path can make the new tree point at an existing blob under a different name. Changing the object ID points at different content, but the referenced object must already exist by default. A tree entry for a directory uses mode 040000 and type tree; a submodule entry uses mode 160000 and type commit. Preserve the mode and type emitted by git ls-tree 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 ID printed by the previous command. This inspection is the recovery point: if the listing is wrong, discard the untracked text file and stop. There is no need to alter the index or checkout to experiment.

5. Treat missing objects as an explicit exception

Without --missing, Git verifies that every referenced object exists. A made-up entry therefore fails:

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

--missing permits the tree to be created with that unresolved reference:

$ 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. A tree created without a ref may also become unreachable during normal repository maintenance. Keep the input and printed ID if you need to investigate it.

Gitlinks are a special case: the manual says their missing target is allowed regardless of --missing. That 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 commands:

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

Do not combine git ls-tree -z with plain git mktree, or the other way around. The terminator mode must match. The NUL form is especially useful when passing output through tools that preserve NUL records. Avoid displaying 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 because 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 malformed record is not a batch separator you should try to guess around; regenerate the input with git ls-tree and inspect it.

Common traps and recovery

git mktree does not update HEAD, a branch, the index or the working tree. If you expected a checkout change, use the appropriate higher-level Git command after verifying the tree, and make sure you understand which ref or commit it will change. Do not run a ref-update command as a follow-up just because git mktree printed an ID.

There is no destructive undo step for the examples 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 remain unreachable in the object database until Git maintenance removes it. Do not run repository cleanup merely to erase one test object; preserve the repository first if the object 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

  • You checked the installed Git version and command syntax.
  • You produced a tree from valid non-recursive ls-tree input.
  • A round-trip ID comparison verified an unchanged tree.
  • You know that ordinary entries require existing objects unless --missing is deliberate.
  • You matched -z on both sides when using NUL-terminated input.
  • You used a blank line between trees when using --batch.
  • You left branches, commits, the index and the working tree unchanged.