TL;DR: M in lazygit's branches panel opens a four-item menu. Each option runs the merge, but the commit graph the merge leaves behind is different.

The lazygit tool's M key is one keystroke for one operation, but it has four parameter choices hidden inside it. Pick the same one twice and you get the same history. Pick a different one and the history is different, in ways that don't show up in the next push, but do show up in git log six months from now.

The newsletter's issue 06 covered staging, reset, and branch merging in lazygit. The merging point's explanation stopped at "press M, pick the option, lazygit runs the merge" and did not show what each option does to history. Here is all four, in the order the menu shows them, with the commit-graph shape each one leaves.

What the M menu looks like

In lazygit's branches panel, with your cursor on the branch you want to merge in (the source), press M. A panel titled Merge opens with four options. Press the highlighted letter to pick your option and enter. You can use esc or the Cancel row to return to the branches panel with no merge run.

The four options, in the order the menu shows them, are: Regular merge (with merge commit), Regular merge (fast-forward), Squash merge and leave uncommitted, Squash merge and commit. If you don't see exactly those labels, no worries, the help screen (?) lists the active keymap.

1. Regular merge (with merge commit) — m

The target branch gets a merge commit with two parents: the previous tip and the source tip. The branches panel refreshes and the new merge commit appears on the target's log line. Also, drilling into the target branch shows the two-parent structure.

This is the right pick when the team uses merge commits as audit markers (git log --first-parent main walks each PR as one entry).

2. Regular merge (fast-forward) — f

Pick this when the source branch is a direct descendant of the target — common with a short-lived feature branch rebased against main. The target pointer moves to the source tip; no new commit is created.

This is the right pick when the team treats branch history as a single line and does not need a "this PR landed here" marker.

3. Squash merge (and leave uncommitted / and commit) — s / S

The source branch's commits squash (collapse) into one commit on the target regardless of which of these two you pick. The difference is what happens to the source branch's working tree afterwards:

  • s (and leave uncommitted): the source's working tree is preserved untouched, ready for more work.

  • S (and commit): the source branch is updated to the same new commit, and any uncommitted edits on the source branch land as part of the squash.

Either choice gives you one commit on the target with a message like Squash merge <source> into <target>. The trade-off both choices share: the intermediate commits on the source branch do not appear on the target — only the one squashed commit does, so git log on the target cannot show how the source branch evolved.

Where you land after each option

All four modes return you to the branches panel. The panel differs:

  • With merge commit: new merge commit on the target.

  • Fast-forward: source and target aligned on the same commit; nothing visibly new.

  • Squash (s or S): one new commit on the target.

If you do not see any new commit on the target after the merge, either it was a no-op (the source was already in the target's history) or you fast-forwarded and the pointer moved without adding a commit.

Aborting a merge

Two paths, depending on whether the merge has run yet.

  • Before it runs: esc from the M menu, or pick the Cancel row. You return to the branches panel with the merge not started.

  • After it has run: switch to the commits panel, find the merge commit, press z to soft-reset. Same workflow, as discussed in issue 06, to undo the last commit.

A merge that produced a conflict has a forward path, not an abort path: the merge view shows the conflict markers, you resolve each hunk, esc returns to the Files panel, and the rest of the merge flow proceeds from there.

What the team actually uses

Open any of your team's repos and run:

git log --oneline --merges -20
git log --oneline --first-parent -20

The shape tells you which mode the team has been picking:

  • Merge commits in --merges, single-commit entries in --first-parent means: squash merge.

  • Merge commits in both views means: with-merge-commit merge.

  • Linear list, no merge commits means: fast-forward or squash indistinguishable in --oneline.

"lazygit's default" is the answer of a team that has not picked one; "the team picked this on a Wednesday in March 2025" is the answer of a team that has, even if the picking was casual. The latter is the better answer, because at least the team can be asked.

Try it

Run brew install lazygit or apt install lazygit. Open a git repo. In the branches panel, pick a short-lived feature branch, press M, and read the four menu rows. Pick the mode whose history matches what your team uses. Press enter. Watch the branches panel to see what landed.

One CLI trick

# Same diagnostic as the "which mode your team uses" section, but
# in the shell after the merge, not in lazygit
git log --oneline --merges -5     # recent merge commits
git log --oneline --first-parent -20  # main's view of recent changes

The two lists line up when your team uses squash or fast-forward; the first list grows faster than the second when your team uses with-merge-commit. The shape tells you the policy the team has, even when nobody wrote it down.

If you want the full toolkit, The Modern CLI Stack is a free ~50-page PDF + EPUB covering mise, starship, zoxide, fzf, broot, ripgrep, fd, bat, eza, delta, tldr, atuin, lazygit.

Reply

Avatar

or to participate