Compare two git trees and return a list of changes.
Changes are returned as maps with explicit keys so callers can pattern match on the operation, path, modes, and SHAs:
%{op: :added, path: String.t(), new_mode: String.t(), new_sha: binary()}
%{op: :removed, path: String.t(), old_mode: String.t(), old_sha: binary()}
%{op: :modified, path: String.t(), old_mode: String.t(), new_mode: String.t(),
old_sha: binary(), new_sha: binary()}
%{op: :mode_changed, path: String.t(), old_mode: String.t(), new_mode: String.t(),
old_sha: binary(), new_sha: binary()}
%{op: :submodule_change, path: String.t(), old_sha: binary(), new_sha: binary()}
%{op: :type_changed, path: String.t(), old_mode: String.t(), new_mode: String.t(),
old_sha: binary(), new_sha: binary()}Options
:prefix— path prefix for the produced change entries. Default"".:max_depth— maximum tree recursion depth. Protects against a hostile tree with a circular reference or a pathological nesting that would overflow the stack. Default 256 (git itself caps around 4096).:max_changes— cap the number of change entries. Prevents a singleDiff.treescall from producing millions of entries on a hostile input. Defaultnil(unbounded; caller takes responsibility).
Defense in depth
Diff.trees/4 is the one path that walks arbitrary (possibly
remote-sourced) tree graphs recursively. It must not overflow the
stack or loop forever on a tree object that references itself
(directly or indirectly). The :max_depth cap is the guard; a
tree that exceeds it returns {:error, {:max_depth_exceeded, n}}
so callers can distinguish "legitimately deep" from "hostile".