gitbutlerapp/gitbutler · error

Change to discard at

Error message

Change to discard at '{rela_path}' didn't have a last-known tracked state - this is a bug

What it means

Thrown by hunk::restore_state_to_worktree when the ChangeState handed in has a null (all-zero) OID, meaning there is no known prior tracked blob to restore the file to. The library considers reaching this state a caller-side bug because changes that reach hunk restoration should always carry a resolved previous object id.

Solutions

  1. Resolve previous_state.id from the index or HEAD tree before calling; never pass a null OID.
  2. Check state.id.is_null() upstream and route such changes to whole-file or 'delete file' handling instead.
  3. If this arises from library-internal classification, report it as a bug with the reproduction.

Example fix

// before
hunk::restore_state_to_worktree(wt_change, state, ...)?;

// after
anyhow::ensure!(!state.id.is_null(), "no previous state for {}", wt_change.path);
hunk::restore_state_to_worktree(wt_change, state, ...)?;
Defensive patterns

Strategy: validation

Validate before calling

fn has_previous_state(state: &ChangeState) -> bool {
    !state.id.is_null()
}

Prevention

When it happens

Trigger: Calling restore_state_to_worktree with a previous_state whose id was never resolved from the index/HEAD (null hash), e.g. constructing ChangeState manually, or passing an Addition's state (no prior blob) into the hunk-restore path.

Common situations: Custom tooling that builds change descriptors without resolving OIDs, or a bug where an Addition is misclassified as a Modification before hunk restoration.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/5d6cd399b122e99a. Report an issue: GitHub.

Appendix: source

Thrown at crates/but-workspace/src/tree_manipulation/discard_worktree_changes.rs:225

        /// Assume the resource to be restored doesn't exist as it was deleted.
        Deleted,
        /// A similar resource is in its place that needs to be updated.
        Update,
    }

    /// Restore `state` by writing it into the worktree of `repo`, possibly re-adding or updating the
    /// `index` with it so that it matches the worktree.
    pub fn restore_state_to_worktree(
        pipeline: &mut gix::filter::Pipeline<'_>,
        index: &mut gix::index::State,
        rela_path: &BStr,
        state: ChangeState,
        mode: RestoreMode,
        path_check: &mut gix::status::plumbing::SymlinkCheck,
        num_sorted_entries: &mut usize,
    ) -> anyhow::Result<()> {
        if state.id.is_null() {
            bail!(
                "Change to discard at '{rela_path}' didn't have a last-known tracked state - this is a bug"
            );
        }

        let mut update_index = |md| -> anyhow::Result<()> {
            crate::commit_engine::index::upsert_index_entry(
                index,
                rela_path,
                Some(&md),
                state.id,
                state.kind.into(),
                gix::index::entry::Flags::UPDATE,
                num_sorted_entries,
            )?;
            Ok(())
        };

        let repo = pipeline.repo;

View on GitHub (pinned to 58e5313667)