Repository navigation
How Branches and Tags Work
The Branches panel lists local branches, remote branches, tags, stashes and worktrees of the active repository, and its branch actions start from context menus. The same actions also run from the Branches popup and the repository rows in Changes (How Repository Actions Work), for any repository. For the user side, see Branches and Tags.
Switching, creating and merging branches is the daily bread of Git. People also want to see what exists on the remote and jump to a tag. The panel must stay fast with hundreds of branches, group feature/... style names into folders, and never let a click destroy work by surprise.
The panel is Sidebar.svelte, opened from the activity bar or Shift+Cmd+E. It shows data that repoStore keeps for the active repository only: repoStore.refs and repoStore.stashes. refreshActive() reloads both whenever the active repository changes, after every mutation, and when the watcher reports a change inside .git.
Its title row is PanelHead.svelte, shared with the Scripts panel and styled like the Changes and Files panels: Refresh calls refreshRepo for the active repository, and the X hides the sidebar with settings.setLeftPanel(null), the same as Option+Cmd+B.
sequenceDiagram
participant RS as repoStore
participant API as api.ts
participant CMD as commands/branch.rs
participant REFS as git/refs.rs
RS->>API: getRefs(repoRoot)
API->>CMD: invoke get_refs
CMD->>REFS: read(repo)
REFS->>REFS: local branches, upstream, ahead and behind
REFS->>REFS: remotes, remote branches, tags
REFS-->>RS: Refs
RS->>RS: refs = result, Sidebar rebuilds rows
refs::read uses git2, as all reads do. For each local branch it finds the upstream and counts ahead and behind with graph_ahead_behind. Remote branch names are split into remote and branch by matching the longest configured remote name, because a remote can itself contain a slash. The symbolic origin/HEAD is skipped.
classDiagram
class Refs {
LocalBranch[] local
RemoteBranch[] remote
string[] tags
string[] remotes
}
class LocalBranch {
string name
bool isHead
string upstream
int ahead
int behind
string shortId
}
class RemoteBranch {
string name
string remote
string branch
}
Refs --> LocalBranch
Refs --> RemoteBranch
buildRows in src/lib/views/sidebar/tree.ts turns Refs, stashes and worktrees into a flat list of SidebarRows: sections, folder groups, branches, tags, stashes and worktrees. The Worktrees section is left out while its list is unknown (see How Worktrees Work). Names are split on / into folders. Remote branches are grouped per remote. Only rows inside expanded sections and folders are produced, so collapsed content never reaches the DOM.
collapse.svelte.ts remembers which sections and folders are collapsed (in local storage; Tags start collapsed). While you type in the filter, everything starts expanded and toggles go to a temporary set, so filtering never scrambles your saved layout.
The menus come from src/lib/views/sidebar/actions.ts: localBranchMenu, remoteBranchMenu, tagMenu and stashMenu. Each item calls repoStore.run or repoStore.runOp, and each Rust command runs the git CLI.
The branch actions (checkoutLocalBranch, newBranchFrom, renameLocalBranch, deleteLocalBranch, mergeIntoCurrent, rebaseCurrentOnto) take an optional RepoTarget: the repository root and that repository's refs, used to validate names and to pass repoPath to run. The sidebar uses activeTarget(); the Branches popup and the repository rows pass repoTarget(repoRoot, refs), so one implementation serves every repository. applyStash and dropStash take an optional repoRoot the same way.
| Action | Command | git |
|---|---|---|
| Checkout | checkout_branch |
switch <name> |
| Checkout remote | checkout_remote_branch |
switch <name> if it exists, else switch -c <name> --track <remote>
|
| New branch | create_branch |
switch -c, or branch when Checkout branch is unticked, with an optional start point |
| Rename | rename_branch |
branch -m |
| Delete | delete_branch |
branch -d, or -D when forced |
| Merge into current | merge_branch |
merge --no-edit |
| Rebase current onto | rebase_onto |
rebase |
| Checkout tag | checkout_commit |
switch --detach refs/tags/<tag>, after a confirm that HEAD will be detached |
| Create tag | create_tag |
tag <name>, or tag -a -F - <name> with the message on stdin |
| Delete tag | delete_tag |
tag -d -- <name> (local only) |
| Push tags | push_tags |
push --tags, see How Remotes Work
|
The sidebar's Merge and Rebase run with git's defaults. The Merge and Rebase dialogs of the Git menu add options (merge_with_options, rebase_with_options; see How the Git Menu Works). Tags are created from Git > New Tag... and the rows' ... > Tags, where validateTagName in repoPickers.ts checks the name first.
Merge and rebase use run_op, which returns an OpOutcome. When git stops on conflicts, that is a normal outcome: runOp shows a toast, makes that repository active and opens the Conflicts dialog. Both are disabled while any operation (merge, rebase, cherry-pick, revert) is in progress, and on the current branch itself.
Deleting asks first. If git refuses because the branch is not fully merged, a second, clearly worded dialog offers a force delete:
flowchart TD
A["Delete branch"] --> B{"Is it the current branch?"}
B -->|"yes"| X["Menu item is disabled"]
B -->|"no"| C["Confirm (danger)"]
C --> D["git branch -d"]
D --> E{"Result"}
E -->|"deleted"| F["Toast: Deleted"]
E -->|"not fully merged"| G["Confirm Force Delete (danger)"]
G -->|"yes"| H["git branch -D"]
G -->|"no"| I["Nothing changes"]
E -->|"other error"| J["Error toast"]
New and renamed branch names are checked by validateBranchName before git ever sees them: no spaces, .., ~^:?*[\, @{, //, and no leading - or /, or trailing /, . or .lock. Names that already exist are refused too.
| File | What it does |
|---|---|
src/lib/views/Sidebar.svelte |
The Branches panel: filter, a library List (role tree), Delete and Space keys, menus |
src/lib/views/PanelHead.svelte |
Title row with buttons and a close X, shared by sidebar panels |
src/lib/views/sidebar/tree.ts |
buildRows: flat row model with folder grouping |
src/lib/views/sidebar/collapse.svelte.ts |
Saved and temporary collapse state |
src/lib/views/sidebar/RowContent.svelte |
One row (a library Row): icon, label, ahead and behind, hover buttons |
src/lib/views/sidebar/actions.ts |
Menus, confirmations, RepoTarget, validateBranchName
|
src/lib/ui/menu.svelte.ts, src/lib/ui/ContextMenuHost.svelte
|
contextMenu.open and the right-click menu itself (the library's Menu) |
src/lib/ui/dialog.svelte.ts, src/lib/ui/DialogHost.svelte
|
dialogs.confirm and dialogs.prompt, used for every branch dialog |
src/lib/stores/repo.svelte.ts |
refs, stashes, refreshActive, run, runOp
|
src-tauri/src/commands/branch.rs |
Branch commands |
src-tauri/src/commands/tag.rs |
create_tag, delete_tag
|
src-tauri/src/git/refs.rs |
git2 reader for branches, remotes and tags |
Only the active repository's refs are loaded. A workspace can hold dozens of repositories. Loading every branch list would cost time and memory for panels nobody looks at. The status bar and Changes view show per-repository branch names from the cheaper status call instead.
Branch writes go through the git CLI. Hooks such as post-checkout, and the user's config, then behave exactly as in the terminal. Using git2 for switch would skip them.
Flat rows, built only for expanded parts. A nested component tree is easier to write, but a flat list keeps the DOM small and makes arrow-key navigation simple.
One branch name validator. The header once had its own, weaker copy. It now calls newBranchFrom(null), so every dialog applies the same rules.
A long branch name hid the Back and Forward buttons.
- The issue: a long branch name in the header overlapped the Back and Forward buttons, and hovering the cut-off name only said "Branches".
- Why it happened: the header's left side could not shrink, and the tooltip was a fixed word.
- The fix and why we chose it: names now shorten with an ellipsis and the tooltip shows the full branch name, so nothing is hidden and nothing overlaps. The buttons moved too; see How Navigation Works.
-
src-tauri/src/commands/tests.rs:branch_create_switch_rename_delete,checkout_remote_branch_tracks_or_switches,merge_branch_reports_conflicts_and_failures,rebase_conflict_continue_after_resolution,rebase_abort_restores_branch. -
src-tauri/src/git/tests.rs:refs_read_local_remote_and_tags,status_head_ahead_and_behind_upstream. -
src-tauri/src/commands/tag.rs:creates_lightweight_and_annotated_tags,tags_a_given_commit_and_refuses_duplicates,deletes_a_tag. -
src/lib/views/changes/repoPickers.test.ts:validateTagName.
There are no frontend tests for tree.ts or validateBranchName yet. Both are pure, so a tree.test.ts (folder grouping, filtering, collapsed sections) is the first thing to add when you change them.
- Update this page when
commands/branch.rs,commands/tag.rs,git/refs.rs,sidebar/tree.tsorsidebar/actions.tschange. - Update Branches and Tags for new menu items or dialogs.
- Retake
branches-panel.pngandbranch-context-menu.pngwhen the panel or its menu changes. - Related: How Stashes Work, How Remotes Work, Backend.
These pages are generated from docs/wiki. To correct something, open a pull request against the source file; edits made here are overwritten.
Using Nibtree
- Getting Started
- Welcome Screen
- Workspaces
- Files Panel
- File Operations
- File Icons
- Rounded Panels
- Editor and Tabs
- Pin Reorder and Wrap Tabs
- Single Tab Title
- Split Editor
- New File and Unsaved Changes
- Unload Hidden Tabs
- Editing Code
- Code Appearance
- Markdown Editor
- Image and PDF Preview
- Search Everywhere
- Recent Files
- Navigation Bar
- Find and Replace
- Changes and Commits
- Sign In Prompts
- Commit Box Layout
- Repository Actions
- Commit Options
- Ignoring Files
- Diffs
- Inline Diffs
- Staging View
- Revert a Change
- Resolving Conflicts
- Merge Tool
- Blame
- History and Log
- Merge Commits
- Branches and Tags
- Branches Popup
- Remotes
- Stashes
- Shelf
- Git Menu
- Git Dialogs
- Interactive Rebase
- Worktrees
- AI Agents
- AI Tasks
- AI Usage
- Keep Computer Awake
- Submodules
- Git LFS
- GitHub
- Git Console
- Terminal
- Terminal Features
- Scripts
- Navigation
- Menus
- Settings
- Settings Search
- Color Themes
- Settings Terminal and Automation
- Settings Files
- Updates
- Status Bar and Help
- Notifications
- Memory Use
- Clear Cache
- MCP and CLI
- Git Mergetool
- Keyboard Shortcuts
- Keyboard Shortcuts Editor
- Troubleshooting
- Troubleshooting Git
Developers
- Developer Guide
- Architecture
- Project Layout
- Backend
- How Folder Watching Works
- Folder Watching Bugs We Fixed
- How Workspace Files Work
- How the Preview Scheme Works
- Backend Services
- How Terminal Features Work
- Terminal Bugs We Fixed
- Frontend
- How the Rich Markdown Editor Works
- Rich Markdown Bugs We Fixed
- Git Menu Links
- Menu Keys and Routing
- Frontend Modules
- Blame Bugs We Fixed
- Project Color And Title Bar
- One Diff View
- Diff Bugs We Fixed
- Merge Tool Layouts
- Merge Tool Bugs We Fixed
- Color Theme Bugs We Fixed
- How the Connected Diff Works
- Rounded Panels Bugs We Fixed
- Update Bugs We Fixed
- Status Bar Bugs We Fixed
- MCP and CLI Bugs We Fixed
- Repository Actions Bugs We Fixed
- How the Changes Tab Works
- CodeMirror Patterns
- How the Cursor Works
- How Editor Features Work
- How the Path Bar Works
- Commands and Events
- Settings Reference
- Commands Workspace and Changes
- Commands Branches and Remotes
- Commands History and Extras
- Commands App and Tools
- Testing
- Test Suites
- Debugging
- Debugging with MCP
- Measuring Setting Memory
- How Terminal Memory Is Measured
- How Markdown Diagrams Work
- Releases and CI
- Publishing the Wiki
- Versioning and Changelog
- Browser Version Packages
- Browser Release Checks
- Docker Image
- Platforms and Signing
- Windows Support
- Windows Bugs We Fixed
- Windows MCP and CLI
- Docs and Screenshots
- UI Library
- UI Library MCP
- UI Library Bugs
- UI Library Migration
- Contributing
How features work
- Welcome screen
- Workspaces and folders
- Files panel
- File operations
- File icons
- Editor and tabs
- Pin, reorder and wrap tabs
- Single tab title
- Split editor
- New File and unsaved changes
- Unload hidden tabs
- Editing code
- Code appearance
- Markdown editor
- Image and PDF preview
- Search Everywhere
- Recent Files
- Navigation Bar
- Find and replace
- Changes and commits
- Sign-in prompts
- Commit box layout
- Repository actions
- Commit options
- Ignoring files
- Diffs
- Inline diffs
- Staging view
- Revert a change
- Resolving conflicts
- Merge tool
- Blame
- History and log
- Merge commits
- Commit tabs
- Branches and tags
- Branches popup
- Remotes
- Stashes
- Shelf
- Git menu
- Git dialogs
- Interactive rebase
- Worktrees
- AI agents
- AI usage
- Keep computer awake
- AI tasks
- Submodules
- Git LFS
- GitHub account
- Git Console
- Back and Forward
- Integrated terminal
- Scripts tool window
- Menu bar
- Settings
- Search in Settings
- Color themes
- Rounded panels
- Updates and release channels
- Status bar and help links
- Notifications
- Memory readout and memory log
- Clear Cache
- MCP server and command line tool
- git mergetool mode