Repository navigation
How the Merge Tool Works
The merge tool is a three pane view (Yours, Result, Theirs) with connectors between the panes and apply and ignore buttons beside each change's line numbers. The user side is in Merge Tool.
Conflict markers are hard to read. A merge dialog shows both sides next to the result and takes changes with one click; building that, light on memory, was the original goal of Nibtree. Rust computes the chunks once, and pure, tested TypeScript tracks what you did with them.
conflicts::load reads the three index stages (base, ours, theirs), and build_document makes a MergeDocument, normalized to LF with the original eol kept. A stage that is not UTF-8 marks the document binary, with no chunks. For a deleted side (empty text), MergeView first offers the per-file choices, with Merge Text Anyway.
compute_chunks in merge/engine.rs diffs base to ours and base to theirs (imara-diff, histogram), then walks both hunk lists together. Hunks that overlap or touch in the base form one chunk, as in git's xdiff. Each chunk is oursOnly, theirsOnly, bothSame or conflict. Ignore whitespace diffs whitespace-normalized lines, so ranges still address the original lines.
classDiagram
class MergeDocument {
path
kind
binary
base
ours
theirs
oursLabel
theirsLabel
eol
ignoreWhitespace
}
class MergeChunk {
id
kind
base
ours
theirs
}
class ChunkState {
result
oursDone
theirsDone
applied
edited
}
class LineRange {
start
end
}
MergeDocument "1" --> "*" MergeChunk : chunks
MergeChunk <|-- ChunkState : UI adds state
MergeChunk --> LineRange : base, ours, theirs
Lines split on \n as in CodeMirror, so a Rust LineRange matches the editor lines.
MergeView.svelte, a WindowLayer over the main window, loads the document for MergeEditor.svelte. MergeSession (mergeSession.svelte.ts) builds three editors (sides read-only) into the panes of the UI library's MergeFrame. The Result starts from the base text and holds the chunk state in chunkField, a StateField in extensions.ts.
initialChunks marks the unchanged side done. Each button calls a pure function in model.ts and dispatches the result as one transaction:
-
applySidereplaces the chunk's result lines with one side. On a conflict, a second apply appends the other side. -
ignoreSidemarks a side handled without touching the text. -
applyNonConflictingapplies every unresolved, unedited non-conflict chunk, optionally from one side. -
acceptWholeSidereplaces the whole result (Accept Left and Accept Right). -
mapChunksmoves chunk ranges through your typing and marks chunks you type in asedited.
stateDiagram-v2
[*] --> Open
Open --> Resolved: apply or ignore a one-sided or identical change
Open --> HalfDone: apply or ignore one side of a conflict
HalfDone --> Resolved: apply (appends) or ignore the other side
Open --> Open: typing inside marks it edited
Resolved --> Open: undo
HalfDone --> Open: undo
Resolved --> [*]
dispatchAction sends the text edit with setChunks and isolateHistory.of("full"), so each click is one undo step, and chunkHistory (invertedEffects) makes undo restore text and chunk state together.
draw builds SVG ribbons for the visible chunks. The library's editor/mergeGutters.ts draws the rest of each band: chunkGutterClasses colors a chunk's gutter rows, and paneGutters puts a side's numbers next to the result with a button column beside them (sideGutters wires Apply and Ignore). Yours keeps its ruler at its outer edge, the result draws its ruler over its lines' ends, and only Theirs keeps a vertical scrollbar, so a band runs unbroken from pane to pane. The panes use baseExtensions, so each has the find bar and the whitespace setting; opening a find bar moves the text, so onSideGeometry redraws on geometry changes. Scrolling one pane scrolls the others through mapLine, a piecewise linear mapping between chunk edges. changedSpans in inline.ts adds word highlights. F7 and Shift+F7 use findUnresolved.
Cmd+Enter saves from any pane (a high-priority keymap from applyKeymap). Escape, Cancel and the title-bar close button all run cancel, which asks first when there is undo history. Ignore whitespace recomputes the chunks, after a confirm. save warns about unresolved changes and leftover markers, then saveResolution writes and stages the file. The same editor runs as git mergetool, see How mergetool mode works.
| File | What it does |
|---|---|
src-tauri/src/merge/engine.rs |
compute_chunks, the git comparison test |
src-tauri/src/merge/model.rs |
LineRange, MergeChunk, MergeDocument, Eol
|
src-tauri/src/git/conflicts.rs |
load and build_document
|
src-tauri/src/commands/merge.rs |
load_conflict, save_resolution
|
src/lib/merge/model.ts |
Pure chunk actions, line edits, scroll mapping |
src/lib/merge/extensions.ts |
chunkField, undo support, line decorations, the Cmd+Enter keymap |
src/lib/merge/inline.ts |
Word-level highlights |
src/lib/merge/MergeEditor.svelte |
Toolbar, MergeFrame, footer, save and cancel |
src/lib/merge/mergeSession.svelte.ts |
The three editors, chunk actions, undo, navigation |
src/lib/merge/mergeRibbons.ts, mergeScroll.ts
|
Connector ribbons, synced scrolling |
ui/web/src/lib/editor/mergeGutters.ts |
Gutter colors, numbers on either side, the chunk buttons |
src/lib/merge/MergeView.svelte |
The overlay, reload and file-level fallbacks |
Our own engine, checked against git. git merge-file gives marker text, not chunk ranges for a UI, so a property test compares our clean merges with it.
Chunk state inside the editor. In a StateField, undo never leaves text and buttons out of step.
Buttons beside the numbers, not over them. They get their own gutter column, so every line number stays readable, and the gaps between panes only carry the ribbons. The 3-pane diff and the library's MergeView draw the same way.
One overlay, not a second window. Every window starts another web view process, which costs memory.
-
src-tauri/src/merge/engine.rs: chunk kinds, touching changes, offsets, final newlines, ignore whitespace and the comparison withgit merge-file. -
src-tauri/src/git/tests.rs: theload_*tests (chunks, CRLF, both added, deleted, binary, ignore whitespace). -
src/lib/merge/model.test.ts:replaceLineswith random edits, chunk actions,mapChunksand scroll mapping. -
src/lib/merge/inline.test.ts: word spans. -
src/lib/merge/extensions.test.ts:applyKeymapbeats the default Cmd+Enter, also in the search bar. -
ui/web/src/lib/editor/mergeGutters.test.ts: gutter row colors, edges, gaps and tones.
Change the engine only with a new engine test; keep UI rules in model.ts with a Vitest case. Connectors and scrolling need a visual check. See Testing.
- Update this page when chunk grouping, chunk actions, undo, keys, saving or cancelling change.
- Update Merge Tool for visible changes.
- Retake
merge-tool.pngandmerge-tool-resolved.png. See Docs and Screenshots.
The window buttons covered the merge title.
- The issue: on macOS the close, minimize and zoom buttons sat on top of the merge icon and "Merge Revisions for".
- Why it happened: the window hides the system title strip and puts its buttons in the app's top bar, but the merge view covers that bar with its own toolbar, which left no room for them.
-
The fix and why we chose it: the merge view's top bar is now an
AppHeaderwithwindowControls, like thegit mergetoolwindow, so it leaves room for the buttons, moves the window and loses the room in full screen.
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