Skip to content

Conversation

@lmnek
Copy link
Contributor

@lmnek lmnek commented Sep 12, 2025

Implements #2026. I also tried to address issues mentioned in the #3477 PR.

Previously, pressing M opened an external merge tool. Now it opens a merge options menu that allows selecting all conflicts in chosen files as ours (HEAD), theirs (incoming), or union (both), while still providing access to the external merge tool.

This uses git-merge-file for a 3-way merge with the --ours, --theirs, and --union flags. This approach avoids the issue mentioned in #1608 (reply in thread), and correctly applies the chosen conflict resolutions while preserving changes from other branches. The command is executed with --object-id, which requires object IDs obtained via rev-parse, instead of relying on the standard version that works with full saved files.

Disclaimer: On my machine, the tests pass inconsistently. Sometimes they succeed, sometimes fail with errors such as POTENTIAL DEADLOCK: Recursive locking or Inconsistent locking. I haven’t yet identified the root cause of this issue.

PR Description

Please check if the PR fulfills these requirements

  • Cheatsheets are up-to-date (run go generate ./...)
  • Code has been formatted (see here)
  • Tests have been added/updated (see here for the integration test guide)
  • Text is internationalised (see here)
  • If a new UserConfig entry was added, make sure it can be hot-reloaded (see here)
  • Docs have been updated if necessary
  • You've read through your own file changes for silly mistakes etc

@stefanhaller
Copy link
Collaborator

O wow, this looks really nice at first glance. It might take me a while to review this, as I'm rather busy right now. Feel free to ping me if you haven't heard back after a week or two.

Disclaimer: On my machine, the tests pass inconsistently. Sometimes they succeed, sometimes fail with errors such as POTENTIAL DEADLOCK: Recursive locking or Inconsistent locking.

My guess is that you have go 1.25 installed locally. This is a known problem, I didn't have time to look into this yet. If you can use 1.24 to run the tests, it should be more reliable.

@bllendev
Copy link

yoo thanks for doing this i just ran into this prob, had a big rebase and i was really thinking this should exist lol, thanks again

Copy link
Collaborator

@stefanhaller stefanhaller left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really great work. Don't let the large number of review comments discourage you, many of them are only nitpicks.

@lmnek lmnek force-pushed the merge-file-options branch 2 times, most recently from 6f1a2b2 to deca5fa Compare September 26, 2025 08:22
@lmnek
Copy link
Contributor Author

lmnek commented Sep 26, 2025

Thank you for the feedback! I addressed all of the comments and responded when I wasn't 100% sure about my solution.

@stefanhaller
Copy link
Collaborator

Excellent work! All my complaints were addressed to my satisfaction. 😄

I pushed three more fixup commits, the first two for very minor things, the third is a bit more important though: it makes it so that the command to open the menu is disabled when there are no files in the selection that have merge conflicts. This is important because otherwise the command would always show up in the status bar (even when you don't have any conflicts), which is confusing. Feel free to squash all fixups if you are happy with them.

However, while working on this I noticed a problem that I didn't realize before: using normalisedSelectedNodes is wrong here. What this does is that when you have a multiselection of a directory and a file inside, it removes the file from the selection, leaving only the directory. (Except that the function is buggy right now, see #4920.) But this is not useful, because the new merge conflicts menu can't deal with directories. What we need is the opposite: when a directory is selected, it should remove the directory from the selection and instead add all files inside of it (no matter whether they are selected or not).

I guess I would also be content with a simpler version that requires you to select the files to operate on; in that case I would propose to disable the command unless all selected items are files with merge conflicts.

Let me know if you have any thoughts on this.

@lmnek lmnek force-pushed the merge-file-options branch from 1667254 to 25307a6 Compare September 29, 2025 18:33
@lmnek
Copy link
Contributor Author

lmnek commented Sep 29, 2025

I've squashed all previous commits and fixed the linter warning.

Thank you for pointing out the incorrect behavior of the node selection. I would prefer this to be solved completely rather than the simple restricting version. Therefore, I added one more fixup commit with a scratch implementation of a BFS algorithm that's somewhat a reverse of normalisedSelectedNodes. Let me know what you think 😄.

I also just noticed that the CI tests are failing on older Git versions, since the --object-id flag was only introduced in Git v2.43. I’m not sure what the preferred way to handle version compatibility is in this codebase, so I’d appreciate your input.

One possible fallback (without --object-id) would be to use git merge-file with three temporary files, one for each of the previously referenced objects; similar to this script: https://github.com/jakub-g/git-resolve-conflict/blob/base/lib/git-resolve-conflict.sh. That was actually my initial implementation, but I replaced it with the object ID approach since it seemed more elegant and lightweight.

@lmnek lmnek force-pushed the merge-file-options branch from 25307a6 to 371df2f Compare September 29, 2025 18:52
@stefanhaller
Copy link
Collaborator

Therefore, I added one more fixup commit with a scratch implementation of a BFS algorithm that's somewhat a reverse of normalisedSelectedNodes.

This is very nice, I like the name too. I just wonder if it needs to be breadth first; wouldn't depth first be sufficient, and be easier? (No need for a queue.) I'm not much of an algorithm guru, so maybe I'm missing something.

I also just noticed that the CI tests are failing on older Git versions, since the --object-id flag was only introduced in Git v2.43. I’m not sure what the preferred way to handle version compatibility is in this codebase, so I’d appreciate your input.

Ah damn. Normally I'd be tempted to say "disable the feature on older versions", but 2.43 isn't that old yet (Apple still ships 2.39 with their devtools it seems), so this might be annoying for too many people. But I'm also not excited about maintaining an alternative code path (especially when it's more complex) for older versions.

So: I'm unsure too. Another option could be to do the simpler "checkout --theirs"/"checkout --ours", but the behavior would be slightly different; not sure that's good.

That was actually my initial implementation,

Ok, if you already had that, then maybe you are the best one to judge how bad it would be to maintain the extra code path?

@lmnek lmnek force-pushed the merge-file-options branch from 6b1872a to fd3a29d Compare October 5, 2025 22:11
@lmnek
Copy link
Contributor Author

lmnek commented Oct 5, 2025

I just wonder if it needs to be breadth first; wouldn't depth first be sufficient, and be easier? (No need for a queue.)

Switching to DFS would require either changing the queue to a stack or rewriting the algorithm recursively. I don’t see a clear advantage in doing that as the current BFS implementation seems equally readable and comparable in terms of memory usage and performance.

But I'm also not excited about maintaining an alternative code path (especially when it's more complex) for older versions.

I implemented both code paths in the latest fixup commit. This included adding a git show command, a helper function to create temporary files for the older merge-file approach, and splitting behavior based on the detected Git version. The legacy path doesn’t feel particularly complex, so I don’t expect it to be difficult to maintain. Let me know what you think! :))

@stefanhaller
Copy link
Collaborator

Switching to DFS would require either changing the queue to a stack or rewriting the algorithm recursively.

Yeah, I would have expected a recursive implementation, but I'm also happy to keep the current one. You are right, it is straightforward enough.

I implemented both code paths in the latest fixup commit. This included adding a git show command, a helper function to create temporary files for the older merge-file approach, and splitting behavior based on the detected Git version. The legacy path doesn’t feel particularly complex, so I don’t expect it to be difficult to maintain. Let me know what you think! :))

Happy to keep this! You are right, it's not so much code, and the integration tests cover both implementations.

I pushed a few more fixups, let me know if you have questions about any of those.

I'm happy with the current state, and would be ready to merge it as is.

I did notice one cosmetic problem where after invoking the menu you get the "All conflicts resolved, continue the merge?" popup, but behind it you still see the conflict markers. I find this rather ugly, but it's not a new problem, so I decided to open a separate PR to address it. If you want to have a look: #4945.

@lmnek lmnek force-pushed the merge-file-options branch from 028107b to ffb898d Compare October 8, 2025 20:45
@lmnek
Copy link
Contributor Author

lmnek commented Oct 8, 2025

Thank you for the review and all the helpful comments throughout! I'm quite glad that I chose this issue as my first open-source contribution, as it was a very smooth and fun experience. I hope to contribute some more in the future :))

(I have no problem with any of the latest changes, so I went ahead and squashed all the commits.)

@stefanhaller stefanhaller added the enhancement New feature or request label Oct 9, 2025
Replace merge-tool with merge options menu that allows resolving all
conflicts for selected files as ours, theirs, or union, while still
providing access to the merge tool.
@stefanhaller
Copy link
Collaborator

Excellent. Looking forward to your next contribution!

@stefanhaller stefanhaller merged commit 3f30008 into jesseduffield:master Oct 9, 2025
12 checks passed
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Nov 4, 2025
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [jesseduffield/lazygit](https://github.com/jesseduffield/lazygit) | minor | `v0.55.1` -> `v0.56.0` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>jesseduffield/lazygit (jesseduffield/lazygit)</summary>

### [`v0.56.0`](https://github.com/jesseduffield/lazygit/releases/tag/v0.56.0)

[Compare Source](jesseduffield/lazygit@v0.55.1...v0.56.0)

<!-- Release notes generated using configuration in .github/release.yml at v0.56.0 -->

#### What's Changed

##### Enhancements 🔥

- Don't break line after footnote symbol in commit messages by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4912](jesseduffield/lazygit#4912)
- Give better visual feedback when checking out the previous branch by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4929](jesseduffield/lazygit#4929)
- Add merge menu with conflict resolver by [@&#8203;lmnek](https://github.com/lmnek) in [#&#8203;4889](jesseduffield/lazygit#4889)
- When entering a commit in path filtering mode, select the filtered path by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4942](jesseduffield/lazygit#4942)
- Document a workaround for using custom pagers on Windows by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4941](jesseduffield/lazygit#4941)
- Show "Log (x of y)" in the title bar when there is more than one branch log command by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4943](jesseduffield/lazygit#4943)
- Support multiple pagers by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4953](jesseduffield/lazygit#4953)
- Add config option gui.skipSwitchWorktreeOnCheckoutWarning by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4964](jesseduffield/lazygit#4964)
- Add no-ff merge option by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4966](jesseduffield/lazygit#4966)

##### Fixes 🔧

- Don't log the git rev-list command that we use for IsBranchMerged by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4896](jesseduffield/lazygit#4896)
- Hide the cursor when the password prompt is showing by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4878](jesseduffield/lazygit#4878)
- Update Merge conflicts menu keybinding to use p instead of k by [@&#8203;zingazzi](https://github.com/zingazzi) in [#&#8203;4934](jesseduffield/lazygit#4934)
- Update diff of conflicted file in the main view after conflicts have been resolved by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4945](jesseduffield/lazygit#4945)
- Don't depend on en\_US locale to be installed by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4949](jesseduffield/lazygit#4949)
- Fix dropping submodule changes from a commit by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4937](jesseduffield/lazygit#4937)
- Fix support for Git copy status when status.renames=copies by [@&#8203;kapral18](https://github.com/kapral18) in [#&#8203;4892](jesseduffield/lazygit#4892)
- When pasting multi-line text into a prompt, don't treat the line feeds as "confirm" by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4955](jesseduffield/lazygit#4955)
- Offer to force-delete a worktree if it contains submodules by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4959](jesseduffield/lazygit#4959)
- Fix window arrangement when a popup or the search prompt is open by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4961](jesseduffield/lazygit#4961)
- Fix lazygit getting unresponsive when pasting commits that become empty by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4958](jesseduffield/lazygit#4958)
- Show a better error message if the temp directory is not writeable by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4968](jesseduffield/lazygit#4968)
- Avoid auto-stashing when only submodules are out of date by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4969](jesseduffield/lazygit#4969)
- Use a PTY when using external diff command from git config by [@&#8203;brandondong](https://github.com/brandondong) in [#&#8203;4983](jesseduffield/lazygit#4983)
- Fix fixup's false filename detection in hunks containing dashed lines by [@&#8203;abobov](https://github.com/abobov) in [#&#8203;5004](jesseduffield/lazygit#5004)

##### Maintenance ⚙️

- Make running with --debug and running integration tests work when built with go 1.25 by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4910](jesseduffield/lazygit#4910)
- Update go to 1.25 by [@&#8203;kyu08](https://github.com/kyu08) in [#&#8203;4844](jesseduffield/lazygit#4844)
- feat(nix): add comprehensive Nix flake by [@&#8203;doprz](https://github.com/doprz) in [#&#8203;4826](jesseduffield/lazygit#4826)
- Fix normalisedSelectedNodes function by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4920](jesseduffield/lazygit#4920)
- Add `synchronize` event to the hooks of "Check Required Labels" by [@&#8203;kyu08](https://github.com/kyu08) in [#&#8203;4974](jesseduffield/lazygit#4974)
- Use ignore directive to ignore test files not to be passes to gofumpt by [@&#8203;kyu08](https://github.com/kyu08) in [#&#8203;4936](jesseduffield/lazygit#4936)

##### Docs 📖

- Fix MoveCommitsToNewBranch description typo by [@&#8203;deventon](https://github.com/deventon) in [#&#8203;4867](jesseduffield/lazygit#4867)
- Wrap long lines in comments in Config.md by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;4951](jesseduffield/lazygit#4951)
- Fix keybinding cheatsheets with regard to pipe characters in key or description by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;5007](jesseduffield/lazygit#5007)

##### I18n 🌎

- Update translations from Crowdin by [@&#8203;stefanhaller](https://github.com/stefanhaller) in [#&#8203;5005](jesseduffield/lazygit#5005)

#### New Contributors

- [@&#8203;deventon](https://github.com/deventon) made their first contribution in [#&#8203;4867](jesseduffield/lazygit#4867)
- [@&#8203;zingazzi](https://github.com/zingazzi) made their first contribution in [#&#8203;4934](jesseduffield/lazygit#4934)
- [@&#8203;doprz](https://github.com/doprz) made their first contribution in [#&#8203;4826](jesseduffield/lazygit#4826)
- [@&#8203;lmnek](https://github.com/lmnek) made their first contribution in [#&#8203;4889](jesseduffield/lazygit#4889)
- [@&#8203;kapral18](https://github.com/kapral18) made their first contribution in [#&#8203;4892](jesseduffield/lazygit#4892)
- [@&#8203;abobov](https://github.com/abobov) made their first contribution in [#&#8203;5004](jesseduffield/lazygit#5004)

**Full Changelog**: <jesseduffield/lazygit@v0.55.1...v0.56.0>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNjkuMSIsInVwZGF0ZWRJblZlciI6IjQxLjE2OS4xIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiXX0=-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants