gitui-org/gitui · error
No branch
Error message
No branch
What it means
CheckoutOptionPopup keeps the target branch in `self.branch: Option<BranchInfo>` (initialized to None in `new`, src/popups/checkout_option.rs:43) and only ever fills it in `open()` (line 80-86). `get_text()` renders the 'Switch to: <branch>' line by calling `self.branch.as_ref().expect("No branch")` at line 60, so if the popup is ever drawn while visible but `open(branch)` has not run, this `expect` panics and takes down the whole TUI process. It is an unstated invariant 'visible implies branch is Some' enforced by panic instead of by the type system.
Source
Thrown at src/popups/checkout_option.rs:60
repo: env.repo.borrow().clone(),
branch: None,
option: CheckoutOptions::KeepLocalChanges,
visible: false,
key_config: env.key_config.clone(),
theme: env.theme.clone(),
}
}
fn get_text(&self, _width: u16) -> Vec<Line<'_>> {
let mut txt: Vec<Line> = Vec::with_capacity(10);
txt.push(Line::from(vec![
Span::styled(
String::from("Switch to: "),
self.theme.text(true, false),
),
Span::styled(
self.branch.as_ref().expect("No branch").name.clone(),
self.theme.commit_hash(false),
),
]));
let (kind_name, kind_desc) = self.option.to_string_pair();
txt.push(Line::from(vec![
Span::styled(
String::from("How: "),
self.theme.text(true, false),
),
Span::styled(kind_name, self.theme.text(true, true)),
Span::styled(kind_desc, self.theme.text(true, false)),
]));
txt
}
View on GitHub (pinned to 2fa693cb6e)
Solutions
- Make the renderer total: replace the `expect` with a graceful fallback (render 'no branch selected' or skip the line) so a missing branch degrades to UI text instead of a crash
- Enforce the invariant where it is created: set `self.branch = Some(branch)` before `self.show()` in `open()`, and make `show()` unreachable without a branch (or store branch+visibility together)
- Add `debug_assert!(self.branch.is_some() || !self.visible)` in draw to catch invariant breaks in dev builds while shipping the graceful fallback
- Audit callers: grep for `.show()`/visibility manipulation on CheckoutOptionPopup and ensure every path goes through `open(branch)`
Example fix
// before
Span::styled(
self.branch.as_ref().expect("No branch").name.clone(),
self.theme.commit_hash(false),
),
// after
let branch_name = self
.branch
.as_ref()
.map(|b| b.name.clone())
.unwrap_or_else(|| String::from("no branch selected"));
Span::styled(branch_name, self.theme.commit_hash(false)), Defensive patterns
Strategy: type-guard
Validate before calling
// Before showing or drawing the popup, verify it holds a branch:
if popup_branch_name(&popup).is_none() {
log::warn!("checkout popup has no branch selected; not drawing");
return Ok(()); // or popup.hide();
} Type guard
fn popup_branch_name(p: &CheckoutOptionPopup) -> Option<&str> {
p.branch.as_ref().map(|b| b.name.as_str())
} Prevention
- Treat Option fields in UI components as render-time data that must always be checked, never expect-unwrapped
- Set required data before flipping visibility flags in open()-style methods so show() and Some(data) cannot diverge
- Write a unit test that draws every popup freshly constructed (branch: None) to catch panics in render paths
- Prefer making illegal states unrepresentable: store `branch: BranchInfo` and construct the popup per-open instead of keeping a long-lived Option
When it happens
Trigger: Component::draw() running while `visible == true` but `open(branch)` was never called (e.g. a state-restore path, test harness, or refactored caller that invokes `show()` directly); the window between `self.show()?` at line 81 and `self.branch = Some(branch)` at line 83 if a draw could ever interleave; any future code path that unhides the popup generically after it was constructed with `branch: None`. Note `checkout()` (line 89) already handles the None case gracefully — only the render path panics.
Common situations: Adding a new entry point that shows the checkout-options popup without a selected branch (command palette, key-replay, integration tests that instantiate components and force-draw them); refactoring `open()` so `show()` happens before the branch is stored; persisting/restoring popup visibility across tab switches.
Related errors
AI-assisted analysis of gitui-org/gitui@2fa693cb6e (2026-08-16).
Data as JSON: /api/errors/98493a72f0b5400b.
Report an issue: GitHub.