# Bundle

> Offline pack bundles — create, verify, list heads, and unbundle.

Git bundle files combine a header and thin pack for offline transfer. The [`bundle`](https://docs.rs/grit-lib/latest/grit_lib/bundle/index.html) domain implements read/write/verify compatible with system `git bundle`. `Repository::bundles` returns `RepositoryBundles` scoped to one open repository.

| Method | Role |
| --- | --- |
| `create` | Write a bundle from `BundleSpec` tips |
| `verify` | Check prerequisites and connectivity (`BundleVerifyReport`) |
| `list_heads` | Parse ref tips from the header |
| `unbundle` | Ingest the pack without updating refs |

With a files-backed repository `unbundle` installs the pack under `objects/pack/`. With a custom primary object store it writes every object of the pack into that store instead (see [Object stores](https://grit-scm.com/docs/library/bundle/object-stores.md/index.md#receiving-packs-into-a-custom-store)).

[`write_bundle`](https://docs.rs/grit-lib/latest/grit_lib/bundle/fn.write_bundle.html) streams the pack into the `Write` you pass (wrap files in a `BufWriter`), so the whole pack is never held in memory. When the repository has local packs, deltas and whole objects already stored there are copied verbatim instead of being recomputed, so bundling a packed repository costs about as much as copying its pack.

For fetch/clone semantics over a bundle path, see `bundle::bundle_remote`. Network transport is covered on the [Network](https://grit-scm.com/docs/library/network/index.md) page.

## Example

```rust
//! Create a bundle file and verify ref tips with [`RepositoryBundles`](https://grit-scm.com/docs/library/bundle/grit_lib::repo_handles::RepositoryBundles/index.md).
//!
//! Source for the library guide "Bundle" page (included in the docs site).

use grit_lib::bundle::BundleSpec;
use grit_lib::config::environment::RepositoryOptions;
use grit_lib::objects::ObjectId;
use grit_lib::repo::Repository;
use grit_lib::state::resolve_head;
use std::path::{Path, PathBuf};

fn open_repo(root: &Path) -> Result<Repository, grit_lib::error::Error> {
    let git_dir = if root.join(".git").is_dir() {
        root.join(".git")
    } else {
        root.to_path_buf()
    };
    let work_tree = if root.join(".git").is_dir() {
        Some(root)
    } else {
        None
    };
    let mut opts = RepositoryOptions::from_process();
    if let Some(wt) = work_tree {
        opts = opts.with_work_tree(wt);
    }
    Repository::open(&git_dir, &opts)
}

fn main() -> Result<(), grit_lib::error::Error> {
    let root = std::env::args()
        .nth(1)
        .map(PathBuf::from)
        .or_else(|| std::env::current_dir().ok())
        .ok_or_else(|| grit_lib::error::Error::Message("missing repository path".into()))?;
    let bundle_path = std::env::args()
        .nth(2)
        .map(PathBuf::from)
        .unwrap_or_else(|| root.join("guide.bundle"));

    let repo = open_repo(&root)?;
    let head = resolve_head(repo.git_dir())?;
    let tip: ObjectId = head
        .oid()
        .cloned()
        .ok_or_else(|| grit_lib::error::Error::Message("HEAD has no commit".into()))?;

    repo.bundles()
        .create(
            &BundleSpec {
                include: vec![(tip, "refs/heads/main".into())],
                ..Default::default()
            },
            &bundle_path,
        )
        .map_err(|e| grit_lib::error::Error::Message(e.to_string()))?;

    let report = repo
        .bundles()
        .verify(&bundle_path)
        .map_err(|e| grit_lib::error::Error::Message(e.to_string()))?;
    println!("bundle_ok={}", report.is_ok());

    let heads = repo
        .bundles()
        .list_heads(&bundle_path, None)
        .map_err(|e| grit_lib::error::Error::Message(e.to_string()))?
        .items;
    for head in heads {
        println!("bundle_head {} {}", head.name, head.oid.to_hex());
    }

    Ok(())
}
```

```bash
cargo run --bin guide_bundle . /tmp/demo.bundle
git bundle verify /tmp/demo.bundle
```

The test runs `git bundle verify` on the file grit wrote.
