Pick both hunks, not the common ancestor, in diff3 conflicts

`b` on a merge conflict is meant to keep both sides. With the diff3
conflict style git additionally renders the common ancestor between the
two sides, and the old ALL selection kept everything between the
outermost markers, dragging that ancestor into the resolved file.

Rename the selection from ALL to BOTH and restrict it to the top and
bottom hunks so the common base is dropped. Without the diff3 style
there is no ancestor section, so the behaviour there is unchanged.

The user-facing keybinding config was already named pickBothHunks; only
the internal enum, handler, translation and log string still said "all".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stefan Haller 2026-07-01 09:30:17 +02:00
parent d79f6c0b38
commit ed3f4db4f9
13 changed files with 30 additions and 24 deletions

View file

@ -205,7 +205,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | Pick hunk | |
| `` b `` | Pick all hunks | |
| `` b `` | Pick both hunks | |
| `` <up>, k `` | Previous hunk | |
| `` <down>, j `` | Next hunk | |
| `` <left>, h `` | Previous conflict | |

View file

@ -287,7 +287,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | ハンクを選択 | |
| `` b `` | すべてのハンクを選択 | |
| `` b `` | Pick both hunks | |
| `` <up>, k `` | 前のハンク | |
| `` <down>, j `` | 次のハンク | |
| `` <left>, h `` | 前のコンフリクト | |

View file

@ -144,7 +144,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | Pick hunk | |
| `` b `` | Pick all hunks | |
| `` b `` | Pick both hunks | |
| `` <up>, k `` | 이전 hunk를 선택 | |
| `` <down>, j `` | 다음 hunk를 선택 | |
| `` <left>, h `` | 이전 충돌을 선택 | |

View file

@ -213,7 +213,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | Kies stuk | |
| `` b `` | Kies beide stukken | |
| `` b `` | Pick both hunks | |
| `` <up>, k `` | Selecteer bovenste hunk | |
| `` <down>, j `` | Selecteer onderste hunk | |
| `` <left>, h `` | Selecteer voorgaand conflict | |

View file

@ -209,7 +209,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | Wybierz fragment | |
| `` b `` | Wybierz wszystkie fragmenty | |
| `` b `` | Pick both hunks | |
| `` <up>, k `` | Poprzedni fragment | |
| `` <down>, j `` | Następny fragment | |
| `` <left>, h `` | Poprzedni konflikt | |

View file

@ -273,7 +273,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | Escolha o local | |
| `` b `` | Pegar todos os pedaços | |
| `` b `` | Pick both hunks | |
| `` <up>, k `` | Trecho anterior | |
| `` <down>, j `` | Próximo trecho | |
| `` <left>, h `` | Conflito anterior | |

View file

@ -114,7 +114,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | Выбрать эту часть | |
| `` b `` | Выбрать все части | |
| `` b `` | Pick both hunks | |
| `` <up>, k `` | Выбрать предыдущую часть | |
| `` <down>, j `` | Выбрать следующую часть | |
| `` <left>, h `` | Выбрать предыдущий конфликт | |

View file

@ -294,7 +294,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | 选中区块 | |
| `` b `` | 选中所有区块 | |
| `` b `` | Pick both hunks | |
| `` <up>, k `` | 选择顶部块 | |
| `` <down>, j `` | 选择底部块 | |
| `` <left>, h `` | 选择上一个冲突 | |

View file

@ -90,7 +90,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | 挑選程式碼片段 | |
| `` b `` | 挑選所有程式碼片段 | |
| `` b `` | Pick both hunks | |
| `` <up>, k `` | 選擇上一段 | |
| `` <down>, j `` | 選擇下一段 | |
| `` <left>, h `` | 選擇上一個衝突 | |

View file

@ -35,8 +35,8 @@ func (self *MergeConflictsController) GetKeybindings(opts types.KeybindingsOpts)
},
{
Keys: opts.GetKeys(opts.Config.Main.PickBothHunks),
Handler: self.withRenderAndFocus(self.HandlePickAllHunks),
Description: self.c.Tr.PickAllHunks,
Handler: self.withRenderAndFocus(self.HandlePickBothHunks),
Description: self.c.Tr.PickBothHunks,
DisplayOnScreen: true,
},
{
@ -247,8 +247,8 @@ func (self *MergeConflictsController) HandlePickHunk() error {
return self.pickSelection(self.context().GetState().Selection())
}
func (self *MergeConflictsController) HandlePickAllHunks() error {
return self.pickSelection(mergeconflicts.ALL)
func (self *MergeConflictsController) HandlePickBothHunks() error {
return self.pickSelection(mergeconflicts.BOTH)
}
func (self *MergeConflictsController) pickSelection(selection mergeconflicts.Selection) error {
@ -290,8 +290,8 @@ func (self *MergeConflictsController) resolveConflict(selection mergeconflicts.S
logStr = "Picking middle hunk"
case mergeconflicts.BOTTOM:
logStr = "Picking bottom hunk"
case mergeconflicts.ALL:
logStr = "Picking all hunks"
case mergeconflicts.BOTH:
logStr = "Picking both hunks"
}
self.c.LogAction("Resolve merge conflict")
self.c.LogCommand(logStr, false)

View file

@ -28,7 +28,7 @@ const (
TOP Selection = iota
MIDDLE
BOTTOM
ALL
BOTH
)
func (s Selection) isIndexToKeep(conflict *mergeConflict, i int) bool {
@ -56,14 +56,23 @@ func (s Selection) bounds(c *mergeConflict) (int, int) {
return c.ancestor, c.target
case BOTTOM:
return c.target, c.end
case ALL:
return c.start, c.end
case BOTH:
// BOTH spans two disjoint hunks, so it has no single range; callers
// go through selected() instead of asking for its bounds.
panic("BOTH has no single range")
}
panic("unexpected selection for merge conflict")
}
func (s Selection) selected(c *mergeConflict, idx int) bool {
// BOTH keeps the top and bottom hunks but drops the common ancestor in
// between (which is only present with the diff3 conflict style), so it
// isn't a single contiguous range like the other selections.
if s == BOTH {
return TOP.selected(c, idx) || BOTTOM.selected(c, idx)
}
start, end := s.bounds(c)
return start < idx && idx < end
}

View file

@ -320,7 +320,7 @@ type TranslationSet struct {
ViewConflictsMenuItem string
AbortMenuItem string
PickHunk string
PickAllHunks string
PickBothHunks string
ViewMergeRebaseOptions string
ViewMergeRebaseOptionsTooltip string
ViewMergeOptions string
@ -1338,7 +1338,7 @@ func EnglishTranslationSet() *TranslationSet {
RewordCommitEditor: "Reword with editor",
Error: "Error",
PickHunk: "Pick hunk",
PickAllHunks: "Pick all hunks",
PickBothHunks: "Pick both hunks",
Undo: "Undo",
UndoReflog: "Undo",
RedoReflog: "Redo",

View file

@ -36,9 +36,6 @@ var PickBothHunksDiff3 = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().Files().IsEmpty()
t.FileSystem().FileContent("file",
/* EXPECTED:
Equals("\nThis\nIs\nThe\nFirst Change\nSecond Change\nFile\n")
ACTUAL: */
Equals("\nThis\nIs\nThe\nFirst Change\nOriginal\nSecond Change\nFile\n"))
Equals("\nThis\nIs\nThe\nFirst Change\nSecond Change\nFile\n"))
},
})