gitbutlerapp/gitbutler · error

" " is neither a commit nor a branch. Try running 'but…

Error message

"{target}" is neither a commit nor a branch. Try running 'but status' to see what is available.

What it means

Thrown by parse_commit_or_branch when the IdMap lookup for the user-supplied target returns no matches, i.e. the string is neither recognizable as a commit nor as a branch in the current workspace. The message redirects the user to `but status` to discover valid identifiers.

Solutions

  1. Run `but status` to list valid commit/branch identifiers.
  2. Copy the exact identifier from the status output.
  3. Check for typos or truncated hashes.

Example fix

// before
but resolve feat/auhtorization
// after
but resolve feat/authorization
Defensive patterns

Strategy: validation

Validate before calling

const ids = parseStatusOutput(runButStatus());
if (!ids.commits.some(c => c.startsWith(target)) && !ids.branches.includes(target)) {
  throw new Error(`"${target}" is neither a commit nor a branch`);
}

Type guard

const isCommitOrBranch = (id: string, ids: CliId[]) => ids.some(i => (i.kind === 'commit' || i.kind === 'branch') && i.matches(id));

Try / catch

try { await butResolve(target) } catch (e) { if (String(e).includes('neither a commit nor a branch')) { print(runButStatus()); } }

Prevention

When it happens

Trigger: `but resolve <target> ...` where <target> matches zero CliIds in the context IdMap — an unknown commit hash prefix, nonexistent branch, or a reference from outside the workspace.

Common situations: Typos, stale branch names after rebases, using a full SHA that is not reachable in the workspace, or referencing an identifier only valid in another repo.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at crates/but/src/command/legacy/resolve.rs:708

        .len();
    Ok(Some(ConflictTarget {
        commit_oid,
        branch,
        other_conflicted,
    }))
}

enum CommitOrBranch {
    Commit(gix::ObjectId),
    Branch(String),
}

/// Resolve a user-provided identifier to a commit or a branch.
fn parse_commit_or_branch(ctx: &mut Context, target: &str) -> Result<CommitOrBranch> {
    let id_map = IdMap::legacy_new_from_context(ctx)?;
    let matches = id_map.parse_using_context(target, ctx)?;
    match matches.as_slice() {
        [] => bail!(
            "\"{target}\" is neither a commit nor a branch. Try running 'but status' to see what is available."
        ),
        [CliId::Commit { commit, .. }] => Ok(CommitOrBranch::Commit(commit.commit_id)),
        [CliId::Branch(branch)] => Ok(CommitOrBranch::Branch(branch.name.clone())),
        [_] => bail!("\"{target}\" does not refer to a commit or a branch"),
        _ => bail!(
            "\"{target}\" is ambiguous. Please provide more characters to uniquely identify it."
        ),
    }
}

/// List the conflicts of a conflicted commit without entering resolution mode.
fn list_conflicts(ctx: &mut Context, out: &mut OutputChannel, commit: Option<&str>) -> Result<()> {
    let t = theme::get();
    let Some(target) = target_conflicted_commit(ctx, commit)? else {
        if let Some(human_out) = out.for_human() {
            writeln!(
                human_out,

View on GitHub (pinned to 58e5313667)