{"record":{"id":"2e214e5c37975da2","repo":"gitbutlerapp/gitbutler","slug":"could-not-get-a-tree-of-all-applied-virtual-branch","errorCode":null,"errorMessage":"Could not get a tree of all applied virtual branches merged","messagePattern":"Could not get a tree of all applied virtual branches merged","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/gitbutler-oplog/src/oplog.rs","lineNumber":388,"sourceCode":"    if let Some(details) = details\n        && details.version == Version(3)\n    {\n        let worktree_id = get_v3_workdir_tree(snapshot_commit.tree()?)?.context(format!(\n            \"no entry at 'worktree' on sha {:?}, version: {:?}\",\n            &snapshot_commit.id(),\n            &details.version,\n        ))?;\n        return Ok(worktree_id);\n    }\n    match wd_trees_cache {\n        Some(cache) => {\n            if let Entry::Vacant(entry) = cache.entry(snapshot_commit.id)\n                && let Ok(tree_id) = tree_from_applied_vbranches(repo, snapshot_commit.id)\n            {\n                entry.insert(tree_id);\n            }\n            cache.get(&snapshot_commit.id).copied().ok_or_else(|| {\n                anyhow!(\"Could not get a tree of all applied virtual branches merged\")\n            })\n        }\n        None => tree_from_applied_vbranches(repo, snapshot_commit.id),\n    }\n}\n\nstruct IndexTrees {\n    index: gix::ObjectId,\n    conflicts: Option<gix::ObjectId>,\n}\n\nfn write_index_trees(ctx: &Context) -> Result<IndexTrees> {\n    let repo = ctx.repo.get()?;\n    let index = repo.index_or_empty()?;\n    // The detached editor writes trees without checking that each entry's blob exists\n    // locally, which it may not, e.g. for unfetched files in a partial clone with a\n    // sparse checkout.\n    let mut tree = repo.empty_tree().edit()?.detach();","sourceCodeStart":370,"sourceCodeEnd":406,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/gitbutler-oplog/src/oplog.rs#L370-L406","documentation":"Raised while materializing a snapshot's workdir tree for diffs. In the cached path, if the snapshot commit id is not yet cached and tree_from_applied_vbranches fails, nothing is inserted into the cache and the subsequent cache.get returns None, producing this error. tree_from_applied_vbranches fails when the snapshot tree lacks the entries it expects (workspace/worktree/target_tree paths), when legacy virtual-branch TOML metadata can't be read, or when fail-fast tree merges of the applied branch trees conflict.","triggerScenarios":"Rendering a diff for a very old snapshot that predates the 'virtual_branches/workspace/tree' and 'worktree' entries and has unreadable legacy metadata; a snapshot whose applied-branch trees merge with conflicts under merge_options_fail_fast; corrupted or externally modified oplog commits; snapshot trees written by a substantially older app version.","commonSituations":"Long-lived projects where the oplog timeline spans many format migrations; restoring or diffing an old timeline entry after upgrading; repositories whose .git objects were pruned/GC'd aggressively, dropping trees the oplog still references.","solutions":["Update GitButler to the latest version so all historical snapshot formats parse","Verify object availability: `git fsck` and check the snapshot commit's tree exists (aggressive `git gc --prune=now` can drop oplog trees)","When iterating the timeline, skip the unreadable snapshot and continue with the next entry rather than aborting the whole listing","If reproducible, capture the snapshot sha and report it — a format migration gap is usually fixable in tree_from_applied_vbranches"],"exampleFix":"// before\nlet tree_id = snapshot_workdir_tree(repo, snapshot_commit, cache)?; // aborts timeline walk\n\n// after: skip unreadable snapshots, keep the timeline usable\nlet tree_id = match snapshot_workdir_tree(repo, snapshot_commit, cache) {\n    Ok(tree) => tree,\n    Err(err) => {\n        tracing::warn!(\"skipping snapshot {}: {err}\", snapshot_commit.id);\n        continue;\n    }\n};","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"// when walking the timeline, degrade per-snapshot instead of aborting\nlet tree_id = match snapshot_workdir_tree(repo, &snapshot_commit, cache) {\n    Ok(tree) => tree,\n    Err(err) => {\n        tracing::warn!(\n            \"cannot build merged tree for snapshot {} ({}), skipping entry\",\n            snapshot_commit.id, err\n        );\n        continue; // timeline stays usable; entry is shown without diff\n    }\n};","preventionTips":["Skip-and-log unreadable snapshots when listing the timeline instead of failing the entire walk","Avoid aggressive `git gc --prune=now` on repos with long oplogs — oplog trees must stay reachable","Keep GitButler updated so historical snapshot format migrations apply","When changing the snapshot tree layout, keep readers for all previous layouts (workspace tree -> worktree -> legacy target_tree)"],"tags":["gitbutler","oplog","snapshot","merge","undo"],"backgroundTag":"snapshot-tree-merge-failed","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}