git_ui: Add multi-select to git panel for bulk stage, unstage, and discard (#60340)

Closes #26430
Complements: #52935 by Jacob Binnie

Enables multi-select in the Git Panel, following the Project Panel
rules.



https://github.com/user-attachments/assets/2892c1e2-3071-47f9-af1a-48e1c69dbb8a



**Core selection gestures:**

1. Click one file, shift+click a file deeper in the list: all files
between highlight.
2. Click one file, then ctrl/cmd+click another file: it joins the
selection; or removes it when selected.
3. Shift+down extends the selection from the current row; shift+up
shrinks it back and re-extends past the anchor.
4. Escape with a selection clears it and keeps focus; Escape again
toggles focus as before.
5. Plain click collapses selection to file and opens its diff;
double-click triggers the alternate open action.
6. Shift+click on a checkbox still performs the pre-existing range
staging.

**Bulk operation gestures:**

1. Select multiple files, press space: if any member is unstaged, all
become staged; press space again: all unstaged.
2. Right-click a file inside the selection: menu reads
Stage/Discard/Trash N files, depending on context.
3. Select multiple files, press delete: confirmation prompt. Mixed
selections: untracked = trashed, staged = unstaged.

**Folders, in tree view:**

1. Ctrl/cmd+click a folder row behaves like its checkbox does: selects
it as a unit, expands operations to descendants.
2. Folder rows inside a shift range join the selection. A collapsed
folder is selected including its hidden files.
3. The same directory under Tracked and Untracked is two independent
selections (so marks are section-qualified).
5. Plain click on a folder still expands/collapses it and clears the
selection.

**Implementation notes:**

1. Selection is stored as repo paths plus section-qualified tree keys to
survive status refreshes.*
2. Stale entries are pruned on rebuild, selection is cleared on active
repository change.
3. Range selection follows visual order in both flat and tree view.
4. Bulk operations batch into a single git operation.

\* Not sure if this is the best option for storing it.


**Why this is necessary**

- Multi-select has been an ongoing feature wish since 2025 with various
upvotes
- Current state of the art IDEs offer this (VSCode, IntelliJ, VS)
- It greatly increases efficiency using the diff panel


**Why is the PR from #52935 not enough?**

This PR builds on its approach. Thanks to @jacobbinnie. But I noticed a
few things:

- never clears the selection when the active repository changes
- a failed checkout or trash disappears silently
- hardcodes single-click = project diff and double-click = file diff,
ignoring the entry_primary_click_action setting
- its context menu still reads "Stage File" / "Discard Changes"
(singular)
- discard prompt always says "Discard" even for an all-untracked
selection that will actually trash files
- no shift+arrow range extension, no Escape to clear
- it skips directory rows entirely, in tree view you can't act on a
folder's subtree at all
- its range_select only ever accumulates, consecutive shift+clicks grow
the set and nothing shrinks it
- Uses Vec<RepoPath> on marks, linear contains scans -> This PR uses
HashSets, relevant for huge repos.
- doesn't ship any tests -> This PR covers the whole range with tests
too*

\* Tests dominate this PR (~850 lines). The actual feature sits around
400.

Release Notes:

- Added multi-select to the git panel

---------

Co-authored-by: Christopher Biscardi <chris@christopherbiscardi.com>
Co-authored-by: Jacob Binnie <jacob.b@linktr.ee>
Co-authored-by: Andrea Leone <andrealeone@hey.com>
f8c278352fjustinschmitz97 committed on 9/1/2026, 12:59:39 PM· committed by GitHubparenta66fb6a
4 files changedLine totals unavailable