# Stash

> Stash stack read/write through RepositoryStash, with option and result types in porcelain::stash.

The stash stack is Git-compatible ref and commit machinery. There is one API for it: `Repository::stash` returns a [`RepositoryStash`](https://docs.rs/grit-lib/latest/grit_lib/repo_handles/struct.RepositoryStash.html) handle with `list`, `push`, `preview`, `create`, `apply`, `apply_commit`, `pop`, `drop`, `clear`, `diff` and `branch`. The option and result types live in [`porcelain::stash`](https://docs.rs/grit-lib/latest/grit_lib/porcelain/stash/index.html) (also re-exported as [`stash`](https://docs.rs/grit-lib/latest/grit_lib/stash/index.html)). Entries are addressed by their newest-first index (`0` is `stash@{0}`), and the handle works on the repository's own work tree, so there is no separate work-tree argument.

## Push and list

[`StashPushOptions`](https://docs.rs/grit-lib/latest/grit_lib/porcelain/stash/struct.StashPushOptions.html) carries the message, whether to include untracked files, [`StashMode`](https://docs.rs/grit-lib/latest/grit_lib/porcelain/stash/enum.StashMode.html) (`All`, `StagedOnly`, `KeepIndex`), optional hunk selection and pathspecs, and the [`Signature`](https://docs.rs/grit-lib/latest/grit_lib/ident/struct.Signature.html) used for the stash commits and the `refs/stash` reflog (usually `repo.identity(IdentRole::Committer, now)`, see [Repository](https://grit-scm.com/docs/library/repository/index.md#identity)). `RepositoryStash::push` returns a [`StashPushResult`](https://docs.rs/grit-lib/latest/grit_lib/porcelain/stash/struct.StashPushResult.html) (the stash commit, its message and tree), or `Ok(None)` when there is nothing to stash. `RepositoryStash::preview` takes the same options and lists the paths that would be saved without writing objects or refs. For a partial push (a hunk selection), both the result and the [`StashPreview`](https://docs.rs/grit-lib/latest/grit_lib/porcelain/stash/struct.StashPreview.html) report what the selection resolved to: `stashed_hunks` maps each path to the hunk ids it stashes and `stashed_lines` to its line range labels, so callers do not need to resolve the selection again. `StashPushOptions::validate` rejects a selection combined with `include_untracked`, `StagedOnly` or `KeepIndex` as [`Error::StashOptionConflict`](https://docs.rs/grit-lib/latest/grit_lib/error/enum.Error.html#variant.StashOptionConflict). Stash commits are always written unsigned, whatever `commit.gpgsign` says, matching Git.

`push`, `apply`, `apply_commit` and `pop` take a [`ProgressSink`](https://docs.rs/grit-lib/latest/grit_lib/progress/trait.ProgressSink.html) and a [`Cancel`](https://docs.rs/grit-lib/latest/grit_lib/progress/trait.Cancel.html). Push reports [`StagingFiles`](https://docs.rs/grit-lib/latest/grit_lib/progress/enum.ProgressPhase.html#variant.StagingFiles) while it hashes local changes and [`CheckingOutFiles`](https://docs.rs/grit-lib/latest/grit_lib/progress/enum.ProgressPhase.html#variant.CheckingOutFiles) while it resets them. A cancel before `refs/stash` moves returns [`Error::Cancelled`](https://docs.rs/grit-lib/latest/grit_lib/error/enum.Error.html#variant.Cancelled) with the repository unchanged; once the entry is stored it stays, even if the reset that follows is cancelled. A cancelled apply or pop keeps the entry, leaves the index alone and rolls its checkout back; only files it content-merged onto a moved `HEAD` before the cancel stay written.

## Apply and pop

`RepositoryStash::apply` applies `stash@{n}` and keeps the entry. `RepositoryStash::pop` applies it and drops it only when it applied cleanly. Both take one [`StashApplyOptions`](https://docs.rs/grit-lib/latest/grit_lib/porcelain/stash/struct.StashApplyOptions.html) (`restore_index` restores the stashed index, like `git stash apply --index`) and return a [`StashApplyOutcome`](https://docs.rs/grit-lib/latest/grit_lib/porcelain/stash/struct.StashApplyOutcome.html) whose `conflicts` lists each path left with conflict stages in the index as an [`operation::Conflict`](https://docs.rs/grit-lib/latest/grit_lib/operation/struct.Conflict.html), the same paths `git ls-files -u` shows afterwards, with the same [`ConflictKind`](https://docs.rs/grit-lib/latest/grit_lib/operation/enum.ConflictKind.html) `status` reports for them. `has_conflicts()` is true exactly when a pop kept the entry. `RepositoryStash::apply_commit` applies any stash-shaped commit, such as one built by `RepositoryStash::create`, without touching `refs/stash`. An index past the end of the stack is [`Error::StashNotFound`](https://docs.rs/grit-lib/latest/grit_lib/error/enum.Error.html#variant.StashNotFound).

Dropping an entry rewrites the `refs/stash` reflog without adding a new entry, so `drop`, `pop` and `branch` take no identity.

```rust
use grit_lib::porcelain::stash::StashApplyOptions;
use grit_lib::progress::{NeverCancel, NullProgress};

let popped = repo
    .stash()
    .pop(0, &StashApplyOptions::default(), &mut NullProgress, &NeverCancel)?;
for conflict in &popped.conflicts {
    println!(
        "conflict: {} ({}; stash@{{0}} was kept)",
        conflict.path,
        conflict.kind.as_str()
    );
}
```

## Example

```rust
//! Push a stash entry and list the stack through [`RepositoryStash`](https://grit-scm.com/docs/library/stash/grit_lib::repo_handles::RepositoryStash/index.md).
//!
//! Source for the library guide "Stash" page (included in the docs site).

use grit_lib::config::environment::RepositoryOptions;
use grit_lib::porcelain::stash::{StashMode, StashPushOptions};
use grit_lib::repo::Repository;
use std::path::{Path, PathBuf};

const IDENT: &str = "Library Guide <guide@grit-scm.com> 1735689600 +0000";

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 repo = open_repo(&root)?;

    let pushed = repo.stash().push(
        &StashPushOptions {
            message: Some("library guide stash".into()),
            include_untracked: false,
            identity: IDENT.parse()?,
            selection: None,
            pathspecs: Vec::new(),
            dry_run: false,
            mode: StashMode::default(),
        },
        &mut grit_lib::progress::NullProgress,
        &grit_lib::progress::NeverCancel,
    )?;
    println!("stash_pushed={}", pushed.is_some());

    let entries = repo.stash().list()?;
    println!("stash_count={}", entries.len());
    for entry in entries {
        println!("stash {}", entry.message);
    }

    Ok(())
}
```

After a dirty file and `cargo run --bin guide_stash`, `git stash list` must show the entry. The test also runs `git fsck --strict`.
