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

  1. 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
  2. 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)
  3. Add `debug_assert!(self.branch.is_some() || !self.visible)` in draw to catch invariant breaks in dev builds while shipping the graceful fallback
  4. 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

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.