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

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

Compare Git Trees Precisely with git-diff-tree

In about 10 minutes, you will be able to compare two committed snapshots, choose readable patch output or machine-friendly records, restrict the comparison to selected paths, and check the result in a script. The examples use Git 2.43.0 from the installed git-man package. No elevated privileges are needed.

1. Pick the two snapshots

git-diff-tree compares tree objects, but a commit name is accepted because a commit contains a tree. Give two commits for a direct before-and-after comparison. Use names that resolve in the repository you are currently in:

cd /path/to/repository
before=<older-commit>
after=<newer-commit>
git diff-tree "$before" "$after"

The default is raw output. With two ordinary commits, the command reports changed paths rather than displaying a patch. An empty result means the selected trees are identical. Check that the names resolved to the objects you intended before investigating a surprising diff:

git rev-parse --verify "$before^{commit}"
git rev-parse --verify "$after^{commit}"

Checkpoint

You have two verified commit IDs and know which one is the old side. The command reads repository objects and does not change the working tree, index or history.

2. Make the output useful to a person

Add -r to recurse into sub-trees. Add --patch (or -p) when you need line-level context. A compact review command is:

git diff-tree -r --patch "$before" "$after"

On a small change, expect headers such as diff --git a/notes.txt b/notes.txt, followed by an index line and hunks beginning with @@. New lines start with + and removed lines with -. Patch output is still a report: this command does not apply it.

For a quick summary, choose one of these deliberately different views:

  • --name-status prints a status letter and changed path, such as A extra.txt or M notes.txt.
  • --name-only prints paths without statuses.
  • --stat prints a diffstat.
  • --summary reports events such as creations, renames and mode changes.

Do not assume the default is recursive. Without -r, changes below a tree entry can be missed in the output you are reading. -t includes tree entries as well as recursing, which is useful when the tree structure itself matters.

3. Limit the comparison to paths

Put a double dash before pathspecs so Git cannot confuse a pathname with another revision argument:

git diff-tree -r --name-status "$before" "$after" -- src/config.yml 'docs/*.md'

The result is limited to matching paths. Quote shell metacharacters when you want Git, rather than the shell, to interpret the pattern. If this prints nothing, first remove the pathspec and confirm that the commits really differ.

4. Use raw output in scripts

--name-status is convenient for a terminal, but filenames with unusual characters can be quoted. Add -z when another program will parse the records:

git diff-tree -r --name-status -z "$before" "$after" |
  while IFS= read -r -d '' status && IFS= read -r -d '' path; do
    printf 'status=%s path=%s
' "$status" "$path"
  done

With -z, fields and records are terminated by NUL bytes. Do not split the output on newlines. Status letters include A for addition, D for deletion, M for content or mode changes, R for a detected rename and C for a detected copy. Rename and copy records carry extra path and similarity information, so use a parser that understands the selected format.

Safety boundary

This is read-only inspection, but a script can still cause damage if it treats a filename as an option or assumes newline-delimited data. Keep the -- separator for pathspecs and use -z with NUL-aware parsing.

5. Make exit status do the checking

For a test that should distinguish equal trees from different trees, use --exit-code:

if git diff-tree -r --quiet --exit-code "$before" "$after"; then
  echo 'trees are identical'
else
  case "$?" in
    1) echo 'trees differ' ;;
    *) echo 'git-diff-tree failed' >&2; exit 2 ;;
  esac
fi

--quiet suppresses output and implies --exit-code. Status 0 means no differences, status 1 means differences, and another non-zero status is an error worth reporting. Do not combine this check with --check without reading the exit-status rules: the manpage says --check is not compatible with --exit-code.

6. Handle roots, merge bases and standard input

For an initial commit, there is no ordinary parent tree. Add --root to show it as a creation event:

git diff-tree --root -r --name-status <initial-commit>

--merge-base needs two commits and compares their merge base with the second commit. This is useful for a branch comparison, but it is not the same as comparing the two tips directly:

git diff-tree --merge-base -r --stat <branch-a> <branch-b>

For batch work, --stdin reads lines containing two trees, one commit, or a commit followed by parent commits. It prints the input object ID before each result. Keep each input record on one line, separated by a single space as documented:

printf '%s
' <commit> | git diff-tree --stdin --root -r --name-status

By default, stdin mode does not show differences for merge commits. Use -m to compare a merge commit with all its parents. Use -c or --cc for combined merge output, remembering that combined output only lists files modified from all parents.

Common traps and recovery

  • No output: you may have omitted -r, selected equal trees, or filtered out every path. Re-run without the pathspec and with -r.
  • Unexpected rename or copy: detection is a presentation decision based on similarity. Add --no-renames for a plain add/delete view, or choose an explicit threshold with --find-renames=<n>.
  • Unreadable scripted filenames: use -z; do not attempt to repair quoted names after splitting text.
  • A patch was printed but nothing changed: that is expected. Applying a patch is a separate, state-changing operation. Stop and inspect it before using another command such as git apply.

Done means

  • You verified both revision names with git rev-parse.
  • You used -r when nested paths mattered.
  • You chose patch, summary, name-status or NUL-delimited output for the consumer.
  • You used -- before pathspecs and quoted shell patterns.
  • Your script distinguishes exit status 0, 1 and actual errors.