gitbutlerapp/gitbutler · error
cannot mark files from multiple commits
Error message
cannot mark files from multiple commits
What it means
At crates/but/src/command/legacy/status/tui/app/mark.rs:430, the CommittedFiles variant enforces a second invariant: all marked CommittedFileIds must come from the same commit (files.head.commit_id != mark.commit_id bails 'cannot mark files from multiple commits'). Marking files across commits in one operation is not supported because the resulting operation would be ambiguous.
Source
Thrown at crates/but/src/command/legacy/status/tui/app/mark.rs:430
}
impl MarkStore<CommittedFileId> for Marks {
type Error = anyhow::Error;
fn contains_mark(&self, mark: &CommittedFileId) -> bool {
self.as_committed_files()
.is_some_and(|files| files.iter().any(|file| file == mark))
}
fn insert_mark(&mut self, mark: CommittedFileId) -> Result<(), Self::Error> {
if self.contains_mark(&mark) {
return Ok(());
}
match self {
Self::Empty => *self = Self::CommittedFiles(NonEmpty::new(mark)),
Self::CommittedFiles(files) => {
if files.head.commit_id != mark.commit_id {
anyhow::bail!("cannot mark files from multiple commits");
}
files.push(mark);
}
_ => anyhow::bail!("cannot mix mark sources"),
}
Ok(())
}
fn remove_mark(&mut self, mark: &CommittedFileId) {
let Self::CommittedFiles(files) = self else {
return;
};
if remove_from_non_empty(files, |marked| marked == mark) {
*self = Self::Empty;
}
}
}View on GitHub (pinned to caf1f223d3)
Solutions
- Complete the operation with files from one commit, then repeat for the next commit.
- If multi-commit file moves are genuinely needed, first move/squash the commits so the files share one commit.
- Fix the TUI selection so files from other commits are not selectable while a committed-file mark session is active.
Example fix
// before: marks hold files from commit A, user marks file from commit B
marks.insert_mark(CommittedFileId { commit_id: b, .. })?; // cannot mark files from multiple commits
// after: scope the session to one commit
assert_eq!(files.head.commit_id, mark.commit_id, "single-commit mark session"); Defensive patterns
Strategy: validation
Validate before calling
if let Marks::CommittedFiles(files) = &marks {
if files.head.commit_id != mark.commit_id {
anyhow::bail!("finish the current commit's file marks before marking another commit");
}
}
marks.insert_mark(mark)?; Type guard
fn same_commit_as_marks(marks: &Marks, commit_id: &CommitId) -> bool {
match marks {
Marks::Empty => true,
Marks::CommittedFiles(files) => &files.head.commit_id == commit_id,
_ => false,
}
} Try / catch
if let Err(err) = marks.insert_mark(file_id) {
if err.to_string().contains("multiple commits") {
// flush the current operation, reset marks, restart from the new commit
} else { return Err(err); }
} Prevention
- Treat each commit's file marks as one session; complete or clear before switching commits.
- Make files under other commits non-selectable while a committed-file session is active.
- Add tests for cross-commit marking attempts.
When it happens
Trigger: Marking a file from commit B while files from commit A are already marked (e.g. marking a file shown under a different commit's section in the TUI).
Common situations: Users trying to gather files from several commits for one action; UI lists where commit boundaries are not obvious; new operations that would legitimately need multi-commit file selections.
Related errors
- cannot mix mark sources
- 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/2afed5bdb4af8cd9.
Report an issue: GitHub.