gitbutlerapp/gitbutler · error
cannot mix mark sources
Error message
cannot mix mark sources
What it means
The status TUI's mark store (crates/but/src/command/legacy/status/tui/app/mark.rs:367) is a single-kind selection enum (Empty/Hunks/Commits/CommittedFiles/Branches). insert_mark for uncommitted hunks/files bails with 'cannot mix mark sources' when the store already holds a different kind. This is an internal invariant guarding the TUI's one-kind-at-a-time marking model, not a user configuration error.
Source
Thrown at crates/but/src/command/legacy/status/tui/app/mark.rs:367
}
}
impl MarkStore<UncommittedHunkOrFile> for Marks {
type Error = anyhow::Error;
fn contains_mark(&self, mark: &UncommittedHunkOrFile) -> bool {
self.as_hunks()
.is_some_and(|hunks| hunks.iter().any(|hunk| hunk == mark))
}
fn insert_mark(&mut self, mark: UncommittedHunkOrFile) -> Result<(), Self::Error> {
if self.contains_mark(&mark) {
return Ok(());
}
match self {
Self::Empty => *self = Self::Hunks(NonEmpty::new(mark)),
Self::Hunks(hunks) => hunks.push(mark),
_ => anyhow::bail!("cannot mix mark sources"),
}
Ok(())
}
fn remove_mark(&mut self, mark: &UncommittedHunkOrFile) {
let Self::Hunks(hunks) = self else {
return;
};
if remove_from_non_empty(hunks, |marked| marked == mark) {
*self = Self::Empty;
}
}
}
impl MarkStore<CommitId> for Marks {
type Error = anyhow::Error;
View on GitHub (pinned to caf1f223d3)
Solutions
- Clear existing marks (reset to Empty) before inserting marks of a different kind.
- At the input layer, branch on the current kind (as_hunks()) and reject or reset mixed selections before insert_mark is reached.
- Write a TUI test reproducing the mixed-kind key sequence and fix the handler that permits it.
Example fix
// before
marks.insert_mark(UncommittedHunkOrFile::File(f))?; // may hit Hunks-vs-other mix
// after
if !matches!(marks, Marks::Empty | Marks::Hunks(_)) {
marks = Marks::Empty; // one kind at a time
}
marks.insert_mark(UncommittedHunkOrFile::File(f))?; Defensive patterns
Strategy: validation
Validate before calling
if !matches!(marks, Marks::Empty | Marks::Hunks(_)) {
marks = Marks::Empty; // enforce one mark kind per session
}
marks.insert_mark(hunk_or_file)?; Type guard
fn accepts_hunk_marks(marks: &Marks) -> bool {
matches!(marks, Marks::Empty | Marks::Hunks(_))
} Try / catch
if let Err(err) = marks.insert_mark(mark) {
if err.to_string().contains("cannot mix mark sources") {
marks = Marks::Empty;
marks.insert_mark(mark)?; // retry with a fresh session
} else { return Err(err); }
} Prevention
- Reset marks to Empty on every mode transition in TUI code.
- Gate every insert_mark call with a kind check.
- Cover mixed-kind key sequences with TUI tests (see the tui-tests skill).
When it happens
Trigger: TUI code calling insert_mark(UncommittedHunkOrFile) while marks of another variant (commits, committed files, branches) are active; a key sequence or new feature that lets the user mark a hunk and then a branch without clearing marks first.
Common situations: Developers extending TUI mark handling; regressions after refactoring mark modes; keybinding handlers that switch selection context without resetting marks.
Related errors
- cannot mark files from multiple commits
- commits cannot be moved to the merge base
- File suffix '{}' is not allowed. Must be one of: {}
- Cannot compute diff specs for branch `{}`
- Login token expired. Please log in to GitButler again.
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/e9343162fec7ea3d.
Report an issue: GitHub.