Terminal Git conflict resolver and diff viewer


ec (easy-conflict) helps you resolve Git merge conflicts and review changes without leaving the terminal. 3-way conflict resolver with diff3 base comparison, or browse working tree and commit changes in the diff viewer.
brew install ec
npm install -g @chojs23/ec
Or run without installing:
npx @chojs23/ec
The npm package is a thin installer that downloads the matching prebuilt
binary from GitHub Releases at install time. Set EC_SKIP_DOWNLOAD=1 to
skip the postinstall download in restricted environments.
Install the packaged release from nixpkgs:
nix profile add nixpkgs#ec
Run the latest repo state without installing:
nix run github:chojs23/ec
Install the latest repo state into your profile:
nix profile add github:chojs23/ec
The nixpkgs package can lag behind the latest upstream release. Use the direct repo commands above if you want the newest branch or tag immediately.
You can run the installer through Make:
make install
scripts/install.ps1.scripts/install.sh.go install github.com/chojs23/ec/cmd/ec@latest
curl -fsSL https://raw.githubusercontent.com/chojs23/ec/main/scripts/install.sh | sh
build from source:
pikaur -S easy-conflict
binary:
pikaur -S easy-conflict-bin
Download a Windows binary from GitHub Releases and add it to your PATH.
PowerShell installer (default: latest release, install to $HOME\\.local\\bin):
irm https://raw.githubusercontent.com/chojs23/ec/main/scripts/install.ps1 | iex
Available release artifacts:
ec-windows-amd64.exeec-windows-arm64.exemake build
Run with no args inside a Git repo
ec
The selector shows conflicted files first, followed by a View Diff section.
Choose working tree to inspect staged, unstaged, and untracked changes, or
choose one of the latest 100 commits. Each commit row shows its total deletions
in red and additions in green. The diff viewer lists changed files in the left
pane and shows the selected file patch in an old/new split layout by default.
Press s to toggle between split and unified diff layouts.
Diff viewer keys:
h / l: focus the file explorer or diff viewj / k: move or scroll in the focused paneH / L, left / right, or horizontal mouse wheel: scroll the focused pane horizontallyctrl+u / ctrl+d: scroll half a pagegg / G: go to the top or bottome: open or close the file explorers: toggle unified or split diffoption+h / option+l or option+left / option+right: resize the file explorerq: return to the selectorec does not run git add after you write
Git will still decide whether the merge is resolved based on the file contents
You can set ec as your git mergetool by adding this to your git config
git config --global merge.tool ec
git config --global mergetool.ec.cmd 'ec "$BASE" "$LOCAL" "$REMOTE" "$MERGED"'
git config --global mergetool.ec.trustExitCode true
You can set ec as your jujutsu merge-editor by adding this to your jujutsu config
jj config set --user ui.merge-editor 'ec'
jj config set --user merge-tools.ec.merge-args '["$base", "$left", "$right", "$output"]'
Interactive
ec <BASE> <LOCAL> <REMOTE> <MERGED>
ec --base <path> --local <path> --remote <path> --merged <path>
No args mode
ec
Non interactive
ec --check --merged <path>
ec --apply-all ours --base <path> --local <path> --remote <path> --merged <path>
This repo includes a minimal Neovim plugin that opens ec in a terminal buffer.
Install with your plugin manager and ensure ec is on your PATH.
require("ec").setup()
Using lazy.nvim
{
"chojs23/ec",
keys = {
{ "<leader>gr", ":Ec<CR>", desc = "Open ec" },
},
}
{
cmd = "ec",
open_cmd = "tabnew",
cwd = nil,
float = true,
close_on_exit = true,
}
Option notes:
cmd: executable name or list with default args; :Ec args are appended.open_cmd: Vim command used when float is disabled or unavailable.cwd: working directory for ec; string path or function; defaults to getcwd().float: enable floating window; table merges with float defaults.close_on_exit: close terminal on successful exit code 0.Float defaults when float = true:
{
width = 0.92,
height = 0.86,
border = "rounded",
title = "ec",
title_pos = "center",
zindex = 50,
}
Float option notes:
width and height: fractions of editor when <= 1, otherwise absolute size.border: floating window border style.title: title text for the float.title_pos: title alignment.zindex: float stacking order.:Ec
:Ec --base <path> --local <path> --remote <path> --merged <path>
The resolver shows three panes in one view.
The header shows the current conflict and how many remain unresolved. A fixed state strip separates selection from the applied result, even when pane titles are narrow:
The center pane shows the result, with an explicit state for the current block:
Applied does not mean saved. Press w or ctrl+s to write the result.
Use e to open $EDITOR with the current result. When you exit the editor, the resolver reloads the merged file and keeps manual edits.
Keybindings are vim-like by default.
The TUI can load colors from a theme config file.
Config path:
$XDG_CONFIG_HOME/ec/themes.json (when XDG_CONFIG_HOME is set)
If XDG_CONFIG_HOME is not set, ec uses os.UserConfigDir()/ec/themes.json.
Typical defaults are ~/.config/ec/themes.json on Linux,
~/Library/Application Support/ec/themes.json on macOS,
and %AppData%\ec\themes.json on Windows.
Example:
{
"default": "warm",
"themes": {
"warm": {
"header_bg": "94",
"header_fg": "230",
"added_bg": "58",
"removed_bg": "88",
"result_fg": "#f1f1f1",
"selected_side_border": "#79c0ff"
}
}
}
Missing keys fall back to the built-in defaults.
The diff viewer uses selected_side_border for its focused pane so selection
matches the conflict resolver.
Both screens use added_bg and removed_bg behind syntax-colored code.
The resolver uses selected_hunk_marker_fg and selected_hunk_marker_bg for
the current block labels and pane_border for the result border.
Unresolved block labels use selector_unresolved_fg, sharing the selector's
red unresolved color. Pending preview text keeps status_unresolved_fg.
Legacy line-highlight, per-line conflict, dimming, result-border, and connector keys remain accepted for old theme files, but no longer control resolver code rows or its result border. Use the keys above for the current resolver layout.
Hex colors require a TrueColor-capable terminal to avoid 256-color downsampling.
Supported keys:
title_fg, pane_border, selected_pane_border, side_pane_border, selected_side_border,
header_bg, header_fg, footer_bg, footer_fg, line_number, ours_highlight_bg,
ours_highlight_fg, theirs_highlight_bg, theirs_highlight_fg, result_fg,
result_highlight_bg, result_highlight_fg, modified_bg, modified_fg, added_bg,
added_fg, removed_bg, removed_fg, diff_hunk_bg, diff_hunk_fg,
conflicted_bg, conflicted_fg,
insert_marker_fg, selected_hunk_marker_fg, selected_hunk_marker_bg, selected_hunk_bg,
status_resolved_fg, status_unresolved_fg, result_resolved_marker_fg,
result_resolved_border, result_unresolved_border, toast_bg, toast_fg,
selector_resolved_fg, selector_unresolved_fg, dim_foreground_light,
file_status_modified_fg, file_status_untracked_fg, file_status_added_fg,
file_status_deleted_fg, file_status_renamed_fg, file_status_conflicted_fg,
dim_foreground_dark, dim_foreground_muted.
| Key | Default |
|---|---|
title_fg |
#c9d1d9 |
pane_border |
245 |
selected_pane_border |
117 |
side_pane_border |
245 |
selected_side_border |
117 |
header_bg |
#161b22 |
header_fg |
#f0f6fc |
footer_bg |
#161b22 |
footer_fg |
#8b949e |
line_number |
#6e7681 |
ours_highlight_bg |
24 |
ours_highlight_fg |
230 |
theirs_highlight_bg |
52 |
theirs_highlight_fg |
230 |
result_fg |
231 |
result_highlight_bg |
60 |
result_highlight_fg |
230 |
modified_bg |
24 |
modified_fg |
231 |
added_bg |
#0d4429 |
added_fg |
#7ee787 |
removed_bg |
#4c1c1c |
removed_fg |
#ff7b72 |
diff_hunk_bg |
#162a46 |
diff_hunk_fg |
#79c0ff |
conflicted_bg |
131 |
conflicted_fg |
231 |
insert_marker_fg |
196 |
selected_hunk_marker_fg |
117 |
selected_hunk_marker_bg |
#161b22 |
selected_hunk_bg |
236 |
status_resolved_fg |
42 |
status_unresolved_fg |
#d29922 |
result_resolved_marker_fg |
42 |
result_resolved_border |
245 |
result_unresolved_border |
245 |
toast_bg |
22 |
toast_fg |
230 |
selector_resolved_fg |
42 |
selector_unresolved_fg |
196 |
file_status_modified_fg |
#d29922 |
file_status_untracked_fg |
#a371f7 |
file_status_added_fg |
#3fb950 |
file_status_deleted_fg |
#f85149 |
file_status_renamed_fg |
#58a6ff |
file_status_conflicted_fg |
#ff7b72 |
dim_foreground_light |
231 |
dim_foreground_dark |
16 |
dim_foreground_muted |
244 |
Backups are off by default. Use --backup to write a sibling file named .ec.bak before writing the result.
Base chunks come from git merge-file --diff3 output. If the base stage is missing for a file, the tool continues without a base view and prints a warning.
New features and bug reports are welcome.
Feel free to open an issue or a pull request.
MIT