# Tree SHA-256

> Content digest of a revision's tree for signed tags and verification.

The [tree-sha256](https://github.com/schacon/tree-sha256) digest hashes file and submodule **contents** with SHA-256 (not Git's SHA-1 object names). Grit exposes it on [`Repository::tree_sha256`](https://docs.rs/grit-lib/latest/grit_lib/repo/struct.Repository.html#method.tree_sha256).

## Compute a digest

```rust
use grit_lib::repo::{InitOptions, Repository};
use grit_lib::tree_sha256::TreeSha256Options;

# fn main() -> grit_lib::error::Result<()> {
let dir = tempfile::tempdir()?;
let repo = Repository::init(dir.path(), &InitOptions::default())?;
# let head = repo.odb().write_loose_materialize(
#     grit_lib::objects::ObjectKind::Commit,
#     b"tree 0000000000000000000000000000000000000000\n\n",
# )?;
let report = repo.tree_sha256(head, &TreeSha256Options::default())?;
println!("{}", report.digest);
# Ok(())
# }
```

[`TreeSha256Report`](https://docs.rs/grit-lib/latest/grit_lib/tree_sha256/struct.TreeSha256Report.html) includes counts and any submodule paths recorded as `cycle:` or `excluded:` (see the upstream spec).

## Example program

```rust
//! Print the tree-sha256 digest for `HEAD` (or the first argument).
//!
//! Source for the library guide "Tree SHA-256" page.

use grit_lib::error::Error;
use grit_lib::revwalk::rev_parse::resolve_revision_as_commit;
use grit_lib::tree_sha256::TreeSha256Options;

fn main() -> Result<(), Error> {
    let cwd = std::env::current_dir().map_err(Error::Io)?;
    let env = grit_lib::config::environment::Environment::from_vars(std::env::vars_os(), cwd);
    let options = grit_lib::config::environment::RepositoryOptions::with_environment(env);
    let repo = grit_lib::repo::Repository::discover(None, &options)?;
    let rev = std::env::args().nth(1).unwrap_or_else(|| "HEAD".into());
    let oid = resolve_revision_as_commit(&repo, &rev)?;
    let report = repo.tree_sha256(oid, &TreeSha256Options::default())?;
    println!("{}", report.digest);
    println!("files={} submodules={}", report.files, report.submodules);
    Ok(())
}
```

## Missing submodules

When a gitlink submodule is not checked out or its commit is absent, hashing returns [`Error::TreeSha256MissingSubmodules`](https://docs.rs/grit-lib/latest/grit_lib/error/enum.Error.html#variant.TreeSha256MissingSubmodules) listing every path.

## Tag header claim

Annotated tags can carry a [`TreeSha256Claim`](https://docs.rs/grit-lib/latest/grit_lib/tree_sha256/struct.TreeSha256Claim.html) in extra headers (immediately after `tagger`, before any other custom headers):

- one `tree-sha256-excluded <path>` line per excluded submodule (sorted when written)
- one `tree-sha256 <64-hex>` line with **exactly** 64 hex digits (no surrounding whitespace)

Read a claim with [`TreeSha256Claim::from_tag`](https://docs.rs/grit-lib/latest/grit_lib/tree_sha256/struct.TreeSha256Claim.html#method.from_tag) on [`TagData`](https://docs.rs/grit-lib/latest/grit_lib/objects/struct.TagData.html), or attach one with [`TreeSha256Claim::apply_to_tag`](https://docs.rs/grit-lib/latest/grit_lib/tree_sha256/struct.TreeSha256Claim.html#method.apply_to_tag). Header names are [`TREE_SHA256_HEADER`](https://docs.rs/grit-lib/latest/grit_lib/tree_sha256/constant.TREE_SHA256_HEADER.html) and [`TREE_SHA256_EXCLUDED_HEADER`](https://docs.rs/grit-lib/latest/grit_lib/tree_sha256/constant.TREE_SHA256_EXCLUDED_HEADER.html).

Excluded paths in [`TreeSha256Claim::excluded`](https://docs.rs/grit-lib/latest/grit_lib/tree_sha256/struct.TreeSha256Claim.html#structfield.excluded) must be non-empty and must not contain NUL or newline when calling `apply_to_tag` (the same rules enforced when reading tags).

## Verify a signed tag

[`Repository::verify_tag`](https://docs.rs/grit-lib/latest/grit_lib/repo/struct.Repository.html#method.verify_tag) reads an annotated tag object by id (not via a ref), checks its appended signature with [`TagVerifyOptions::gpg`](https://docs.rs/grit-lib/latest/grit_lib/tree_sha256/struct.TagVerifyOptions.html#structfield.gpg), and recomputes the `tree-sha256` claim when the header is present. Signature verification and digest hashing run on separate threads.

Excluded submodule paths recorded in `tree-sha256-excluded` headers are hashed with [`TreeSha256Options::exclude_exact`](https://docs.rs/grit-lib/latest/grit_lib/tree_sha256/struct.TreeSha256Options.html#structfield.exclude_exact) only (no globs). Paths that are not gitlinks in the tagged tree are reported in [`TreeSha256Verdict::bogus_excluded`](https://docs.rs/grit-lib/latest/grit_lib/tree_sha256/struct.TreeSha256Verdict.html#structfield.bogus_excluded) and fail [`TagVerification::is_ok`](https://docs.rs/grit-lib/latest/grit_lib/tree_sha256/struct.TagVerification.html#method.is_ok). Real excluded submodules are listed in [`TreeSha256Verdict::excluded`](https://docs.rs/grit-lib/latest/grit_lib/tree_sha256/struct.TreeSha256Verdict.html#structfield.excluded) but do not fail verification.

```rust
use grit_lib::commit::signing::GpgConfig;
use grit_lib::repo::Repository;
use grit_lib::tree_sha256::TagVerifyOptions;

# fn main() -> grit_lib::error::Result<()> {
# let repo: Repository = todo!();
# let tag_oid = todo!();
let gpg = GpgConfig::from_config(repo.config()?.as_ref())?
    .with_command_runner(repo.command_runner());
let report = repo.verify_tag(tag_oid, &TagVerifyOptions {
    check_signature: true,
    gpg,
})?;
assert!(report.is_ok());
# Ok(())
# }
```

To verify by tag name, as `grit tag --verify` does, call [`verify_tag_by_name`](https://docs.rs/grit-lib/latest/grit_lib/porcelain/tag/fn.verify_tag_by_name.html). It resolves `refs/tags/<name>` through the repository's ref store (loose, packed or reftable), reads the tag object once, and returns a [`VerifiedTag`](https://docs.rs/grit-lib/latest/grit_lib/porcelain/tag/struct.VerifiedTag.html) with the tag object id, the `object` header target and the [`TagVerification`](https://docs.rs/grit-lib/latest/grit_lib/tree_sha256/struct.TagVerification.html). A bad name, a missing tag, and a lightweight tag (one with no tag object to verify) are the typed errors `TagError::InvalidName`, `TagError::NotFound` and [`TagError::Lightweight`](https://docs.rs/grit-lib/latest/grit_lib/porcelain/tag/enum.TagError.html#variant.Lightweight).

## Example program (verify)

```rust
//! Verify a signed tag's SSH/OpenPGP signature and optional `tree-sha256` header.
//!
//! Source for the library guide "Tree SHA-256" verification section.

use grit_lib::commit::signing::GpgConfig;
use grit_lib::error::Error;
use grit_lib::tree_sha256::TagVerifyOptions;

fn main() -> Result<(), Error> {
    let cwd = std::env::current_dir().map_err(Error::Io)?;
    let env = grit_lib::config::environment::Environment::from_vars(std::env::vars_os(), cwd);
    let options = grit_lib::config::environment::RepositoryOptions::with_environment(env);
    let repo = grit_lib::repo::Repository::discover(None, &options)?;
    let tag = std::env::args()
        .nth(1)
        .ok_or_else(|| Error::Message("usage: guide_tree_sha256_verify <tag>".to_owned()))?;
    let oid = grit_lib::revwalk::rev_parse::resolve_revision(&repo, &tag)?;
    let gpg =
        GpgConfig::from_config(repo.config()?.as_ref())?.with_command_runner(repo.command_runner());
    let report = repo.verify_tag(
        oid,
        &TagVerifyOptions {
            check_signature: true,
            gpg,
        },
    )?;
    println!("signature: {:?}", report.signature);
    if let Some(v) = &report.tree_sha256 {
        println!("claimed: {}", v.claimed);
        println!("computed: {}", v.computed);
    }
    if report.is_ok() {
        println!("OK");
    } else {
        std::process::exit(1);
    }
    Ok(())
}
```
