gitbutlerapp/gitbutler · error

Cannot currently work in repositories without a worktree

Error message

Cannot currently work in repositories without a worktree

What it means

read_worktree_file() in gitbutler-repo reads a file from the repository's checked-out working tree. gix's Repository::workdir() returns None for bare repositories, and reading workspace files from a bare repo is unsupported, so this error is raised.

Solutions

  1. Open the repository from a non-bare checkout so workdir() returns Some.
  2. If you need file contents from a bare repo, read from an object/tree via gix instead of the worktree API.
  3. Re-clone as a normal working copy and register that path as the project.

Example fix

// before
let repo = gix::open("/srv/origin.git")?;          // bare
read_file_from_workspace(&repo, "src/main.rs")?;
// after
let repo = gix::open("~/work/myrepo")?;            // non-bare checkout
read_file_from_workspace(&repo, "src/main.rs")?;
Defensive patterns

Strategy: validation

Validate before calling

if repo.workdir().is_none() {
    return Err(anyhow!("repo is bare; cannot read workspace files"));
}

Type guard

fn has_worktree(repo: &gix::Repository) -> bool { repo.workdir().is_some() }

Try / catch

match read_file_from_workspace(&repo, path) {
    Err(e) if e.to_string().contains("without a worktree") => read_from_tree_fallback(&repo, path),
    other => other,
}

Prevention

When it happens

Trigger: Calling get_workspace_file_from_source or read_file_from_workspace (which call read_worktree_file) with a repository handle opened from a bare git dir — e.g. a .git directory opened directly or a bare clone.

Common situations: Tauri commands invoked against a project whose repo was opened from its git dir; pointing GitButler at a bare remote mirror; tests constructing a gix::Repository from a bare path.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


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

Appendix: source

Thrown at crates/gitbutler-repo/src/commands.rs:364

    let tree = repo.find_commit(commit_id)?.tree()?;

    Ok(match tree.lookup_entry_by_path(relative_path)? {
        Some(entry) => {
            let blob = repo.find_blob(entry.id())?;
            FileInfo::from_content(relative_path, &blob.data)
        }
        None => FileInfo::deleted(),
    })
}

/// Read `path` from the checkout of `repo`, see [`RepoCommands::read_file_from_workspace()`].
/// `path` can be relative or absolute, and is validated to be inside the worktree.
/// A linked worktree opened from disk reads the same way as the main worktree.
pub fn read_worktree_file(repo: &gix::Repository, path: &Path) -> Result<FileInfo> {
    let workdir = repo
        .workdir()
        .ok_or_else(|| anyhow!("Cannot currently work in repositories without a worktree"))?;
    let canonical_workdir = gix::path::realpath(workdir)?;
    let path = gix::path::realpath(canonical_workdir.join(path))?;
    // Double-check that the path is still in the worktree - this might not be the case
    // if it was absolute to begin with, or leads through symlinks.
    let relative_path = match path.strip_prefix(&canonical_workdir) {
        Ok(relative_path) => relative_path.to_owned(),
        Err(_) => {
            bail!(
                "Path to read from at '{}' isn't in the worktree directory '{}'",
                path.display(),
                canonical_workdir.display()
            );
        }
    };

    // Refuse `.git` in every spelling an OS can map onto it (`.GIT`,
    // `GIT~1`, `.git.`). Runs before the stat: `.git` can be a file
    // (linked worktrees) and must be refused all the same.

View on GitHub (pinned to 58e5313667)