gitbutlerapp/gitbutler · error

Could not reset submodule at

Error message

Could not reset submodule at '{sm_dir}' to commit {id}: {err}

What it means

Wraps a failed attempt to reset a submodule working directory to the recorded commit via a shell git command (git reset) run inside the submodule's repository directory. The subprocess exited non-zero and the raw stderr is embedded in the message. It indicates the submodule could not be moved to the commit the outer discard operation requires.

Solutions

  1. Inspect the {err} stderr in the message; run the same `git reset <commit>` manually inside the submodule to see the full failure.
  2. Commit or stash local changes inside the submodule, then retry the discard.
  3. Fetch the missing commit in the submodule (git fetch --unshallow or fetch origin) so the target commit exists.
  4. Abort any in-progress merge/rebase in the submodule (git rebase --abort / git merge --abort) before resetting.
Defensive patterns

Strategy: try-catch

Validate before calling

// preflight: is the submodule clean and at resettable state?
let ok = Command::new("git").args(["status", "--porcelain"])
    .current_dir(submodule_repo_dir).output()?.status.success();

Try / catch

// parse stderr from the error chain to branch on cause
let msg = format!("{err:#}");
if msg.contains("local changes") { stash_and_retry()?; }
else if msg.contains("not found") { fetch_submodule_then_retry()?; }
else { return Err(err); }

Prevention

When it happens

Trigger: restore_state_to_worktree encounters a submodule change whose target state commit differs from the submodule's current HEAD and runs `git reset` in the submodule dir; the command fails (dirty submodule, missing objects, merge/rebase in progress, corrupt repo).

Common situations: Submodules with local uncommitted changes or an in-progress rebase/merge; submodules cloned shallowly so the target commit is missing; read-only or CI sandboxes where git cannot write into the submodule; git hooks or config interfering.

Understand the failure class

Background: "git command failed": what it means when a tool shells out to git and git exits non-zero — this error's family across 21 libraries.

Related errors


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

Appendix: source

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

                }
                update_index(gix::index::fs::Metadata::from_path_no_follow(&link_path)?)?;
            }
            EntryKind::Commit => {
                if let RestoreMode::Update = mode {
                    // TODO(gix): actual checkout/reset functionality - it will be fine to support that fully.
                    // Since `git2` doesn't support filters, it will save us some trouble to just use Git for that.
                    let submodule_repo_dir = &file_path;
                    let out = std::process::Command::from(
                        gix::command::prepare(format!(
                            "git reset --hard {id} && git clean -fxd",
                            id = state.id
                        ))
                        .with_shell(),
                    )
                    .current_dir(submodule_repo_dir)
                    .output()?;
                    if !out.status.success() {
                        bail!(
                            "Could not reset submodule at '{sm_dir}' to commit {id}: {err}",
                            sm_dir = submodule_repo_dir.display(),
                            id = state.id,
                            err = out.stderr.as_bstr()
                        );
                    }
                } else {
                    let sm_repo = repo
                        .submodules()?
                        .into_iter()
                        .flatten()
                        .find_map(|sm| {
                            let is_active = sm.is_active().ok()?;
                            is_active.then(|| -> anyhow::Result<_> {
                                Ok(
                                    if sm.path().ok().is_some_and(|sm_path| sm_path == rela_path) {
                                        sm.open()?
                                    } else {

View on GitHub (pinned to 58e5313667)