# `Exgit.Object.Tree`
[🔗](https://github.com/ivarvong/exgit/blob/v0.1.0/lib/exgit/object/tree.ex#L1)

A git tree object.

`decode/1` is **byte-exact**: it preserves the mode string verbatim so that
`decode |> encode` reproduces the original tree bytes (and thus the same
SHA). Historical git repositories occasionally contain legacy modes such
as `100664`; normalizing those during decode would silently change the
tree's SHA and corrupt verification.

`new/1` applies git's canonical ordering (dirs sort as if they had a
trailing `/`) and — by default — normalizes modes via
`canonical_mode/1`. Pass `strict: true` to raise on non-canonical
modes instead of silently coercing, or build the struct directly for
a raw, unvalidated tree.

> #### Round-trip note {: .warning}
>
> `Tree.new(decoded.entries)` is NOT always equivalent to `decoded` —
> `new/1` canonicalizes modes, so a decoded tree with legacy `100664`
> mode will have its mode rewritten by `new/1` and its SHA will
> change. If you need byte-exact round-trip, preserve the decoded
> struct and don't pass it through `new/1`.

## Path traversal

`decode/1` validates each entry's name against the rules in `Exgit.RefName`
for path components — rejecting `/`, `..`, `.`, empty names, and
case-insensitive `.git`/`.gitmodules` entries. Hostile trees from a
pack never reach a materialize/checkout/write path.

# `entry`

```elixir
@type entry() :: {mode(), name :: String.t(), sha :: binary()}
```

# `mode`

```elixir
@type mode() :: String.t()
```

# `t`

```elixir
@type t() :: %Exgit.Object.Tree{entries: [entry()]}
```

# `canonical_mode`

```elixir
@spec canonical_mode(String.t()) :: String.t()
```

Normalize a mode string to the canonical git spelling. Used by `new/1`
but NOT by `decode/1`. Octal modes canonicalize by their file-type
bits: directories (`"040000"` → `"40000"`), symlinks (`"0120000"` →
`"120000"`) and gitlinks (`"0160000"` → `"160000"`) keep their type;
everything else coerces to `100644` / `100755` based on the
executable bit. Non-octal strings are returned unchanged by the
one-arg form; the two-arg form with `strict: true` raises
`ArgumentError` on any non-canonical input.

# `canonical_mode`

```elixir
@spec canonical_mode(String.t(), boolean()) :: String.t() | no_return()
```

# `decode`

```elixir
@spec decode(binary()) :: {:ok, t()} | {:error, term()}
```

# `encode`

```elixir
@spec encode(t()) :: iolist()
```

# `new`

```elixir
@spec new(
  [entry()],
  keyword()
) :: t()
```

Build a canonical tree from a list of `{mode, name, sha}` entries.

Options:

  * `:strict` — when `true`, raise `ArgumentError` on any mode that
    is not one of the five canonical git modes (`40000`, `100644`,
    `100755`, `120000`, `160000`). Default: `false`, which
    canonicalizes via `canonical_mode/1`: zero-padded modes normalize
    to their canonical form (`"040000"` → `"40000"`, `"0120000"` →
    `"120000"`), other octal modes coerce to `100644` / `100755`
    based on the executable bit, and non-octal strings pass through
    unchanged.

# `sha`

```elixir
@spec sha(t()) :: Exgit.Object.sha()
```

# `sha_hex`

```elixir
@spec sha_hex(t()) :: String.t()
```

---

*Consult [api-reference.md](api-reference.md) for complete listing*
