If a search has many matches, scrolling the view becomes laggy because
every draw takes long. Add a benchmark for this, so that later commits
can show how much faster they make drawing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`sync/lock_error_partway_through_pull_with_rebase` failed on CI: every
attempt to continue the rebase ran into index.lock, as if the lock was
never released.
The test hook that holds index.lock starts a background process that
removes the lock once the git command that ran the hook has exited. If
lazygit runs the command in a pty, as it does for a pull, the command's
exit sends SIGHUP to that process. The process ignored SIGHUP, but only
once it got to run. On a busy machine the command can exit before that;
SIGHUP then kills the process and leaves the lock behind. Delaying the
start of the process by 0.3s makes the test fail every time.
Ignore SIGHUP in the hook before starting the background process, so
that the process inherits it. With this, the test passes even with the
delay.
sync/lock_error_partway_through_pull_with_rebase failed on CI: every
attempt to continue the rebase ran into index.lock, as if the lock was
never released.
The test hook that holds index.lock starts a background process that
removes the lock once the git command that ran the hook has exited. If
lazygit runs the command in a pty, as it does for a pull, the command's
exit sends SIGHUP to that process. The process ignored SIGHUP, but only
once it got to run. On a busy machine the command can exit before that;
SIGHUP then kills the process and leaves the lock behind. Delaying the
start of the process by 0.3s makes the test fail every time.
Ignore SIGHUP in the hook before starting the background process, so
that the process inherits it. With this, the test passes even with the
delay.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When pasting a commit message into the commit message editor (either via
the `ctrl+o` menu or with the terminal's Command-V), we now remove the
hard line breaks so that the text reflows again when you edit it, like
we already do when rewording a message.
When pasting with Command-V, we now avoid the extra blank line at the
beginning of the description; this wasn't really a problem because it
got stripped when committing, but it looked ugly.
Along the way, fix a few minor issues when pasting text into various
views, e.g. the search prompt or a "New branch name" text field.
Every view that takes text has an OnPaste handler now, and views that
don't take text already ignored pasted keys. Make this a single rule.
While a paste is in progress, keys go to the focused view's OnPaste
handler or nowhere. This replaces the check in execKeybindings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Treat pasted text like in the other views that take a single line of
text. Before, a pasted newline confirmed the search, and the rest of
the pasted text was lost.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Like a menu's filter, a prompt takes a single line of text, so treat
pasted text the same way. Before, the prompt ignored pasted newlines.
This joined the lines without a space. It also ran the keybindings for
other keys in the text, so a pasted Esc closed the prompt, and a pasted
tab switched to the suggestions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The filter takes a single line of text. Drop the newlines at the start
and end of the pasted text, and replace the remaining newlines and tabs
with spaces. A pasted newline therefore no longer runs the selected
menu item.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The keybindings menu filters as you type. If you paste text that ends
with a newline into it, the text filters the menu, and the newline then
runs the selected item as if you had pressed Enter. The same happens in
the other menus that filter as you type.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Do the same as when pasting a commit message with the commit menu, so
that the paragraphs of the body reflow when you edit them. This also
covers a commit message pasted into the summary, since everything after
its first line goes into the description.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If you paste a commit message into the summary with the terminal's
paste command, the body keeps its hard line breaks. This happens even
where auto-wrapping would break the line at the same place. If you
then edit a paragraph of the body, it doesn't reflow. Pasting with the
commit menu already removes these line breaks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It returned a driver for the summary. This made it impossible to use
the description's driver methods after a test step that moves the
focus to the description, such as pasting a multi-line text into the
summary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If you paste text into an empty description, drop the newlines at its
start. This happens when you paste a whole commit message into the
summary with the terminal's paste command. The part after the first
line goes into the description, and it starts with the blank line
between subject and body.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Use the new OnPaste handler for the summary and the description. This
replaces the IsPasting checks in the keybindings for confirming and for
switching between summary and description. These checks only worked for
the default keys of those keybindings. If a user had bound Enter or Tab
to something else, pasting a newline or a tab ran that command.
Otherwise the behavior stays the same, except that the panel now drops
Esc and control characters in pasted text. Before, it executed them;
for example, a pasted Esc closed the panel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Editable views receive pasted text as one key press per character, so a
newline or a tab in the text triggers the keybinding for Enter or Tab.
The commit message panel works around this by checking IsPasting in
these keybindings. This is fragile, and it gives the panel no way to
look at the pasted text as a whole.
Add an OnPaste handler to View. For a view that has one, collect the
pasted characters, newlines and tabs while the paste lasts, and pass
them to the handler when it ends. Drop all other keys of the paste. They
are unlikely to occur in pasted text, and dispatching keys like Esc in
the middle of a paste would run them as commands.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If you paste a commit message into the summary with the terminal's
paste command, the description starts with an empty line. The first
newline of the pasted text moves to the description, and the second
one (the blank line between subject and body) is typed into the
description. Git strips the empty line when committing, but it looks
broken in the panel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It checks that the text goes in at the cursor, and that a tab turns
into spaces instead of switching to the summary.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The commit message panel handles pasted text differently from typed
text (see ba6cfc1f85), but integration tests couldn't simulate a
paste, so this had no tests. Let tests replay the events that a
terminal sends for a bracketed paste: the start of the paste, a key
press for each character, and the end of the paste.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Do the same as when rewording a commit, so that the paragraphs of the
body reflow when you edit them.
This also matters for the "Copy commit message to clipboard" entry of
the commit menu. It copies the message with the line breaks that
auto-wrapping inserted. Without this change, pasting the copied
message back into the panel would turn these into hard line breaks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If you copy a commit's message with `y` in the commits panel and paste
it into the commit message panel with the commit menu, the body keeps
the hard line breaks that git committed. This happens even where
auto-wrapping would break the line at the same place. If you then edit
a paragraph of the body, it doesn't reflow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All three callers of TryRemoveHardLineBreaks check the
AutoWrapCommitMessage config first and pass the AutoWrapWidth config.
We are about to add a fourth caller, so move both into a CommitsHelper
method. This also makes it impossible to call it with the wrong width.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The commit menu (`ctrl+o` in the commit message panel) can paste a
message from the clipboard, but it can't copy the message to it. Copying
is useful for keeping a message around while committing something else
first. For example, if a pre-commit hook fails, users may want to commit
a fix for it before they come back to the original change. Copying the
preserved commit message gives them a chance to park it in an untitled
editor buffer and reuse it later.
Also, add "Clear commit message" to the menu; this is for those users
who don't know that you can quickly clear the message by holding down
`ctrl+u` with auto-repeat. I've seen users hold down backspace with
auto-repeat instead, which takes forever.
This changes the keybinding for the "Paste commit message from
clipboard" menu item from `p` to `V`, so that we can use `C` for
copying. I hope this isn't a problem for anybody's muscle memory, but
the new keys are consistent with copying and pasting commits in the
commits panel.
Many users don't know that they can clear the commit message panel
quickly by holding down ctrl+u. They hold down backspace instead, and
for a long message this takes a long time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The commit menu (ctrl+o in the commit message panel) can paste a message
from the clipboard, but it can't copy the message to it. Copying is
useful for keeping a message around while committing something else
first. For example, if a pre-commit hook fails, users may want to
commit a fix for it before they come back to the original change. If
they commit the fix through the commit message panel, lazygit deletes
the preserved message of the original change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The next commit needs to build a commit message from the commit panel
in git's canonical format too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
We are about to add an entry for copying the commit message to the
clipboard. Its obvious key `c` is already taken by "Add co-author", so
use `C` for copying and `V` for pasting. This matches the keys for
copying and pasting commits in the commits panel.
This means changing the key of an existing entry. We change the one for
pasting and not the one for adding a co-author, because users are
likely to paste a commit message less often than they add a co-author.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While a long-running operation blocks input, lazygit buffers key events
and replays them when the operation ends. Paste events weren't buffered,
so they took effect right away. If the user pasted text during the
block, the paste has ended by the time its keys are replayed, and
lazygit treated them as typed keys. In a list panel, every character of
the text then ran the command bound to it, rather than ignoring them as
it would when the UI is not blocked.
To fix this, buffer the paste start/stop events in the same way as the
key events themselves, and replay them after unblocking along with
those.
Buffer the start and end of a paste along with the keys, and replay them
in the same order. This way lazygit still recognizes the replayed keys
as pasted.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While a long-running operation blocks input, lazygit buffers key events
and replays them when the operation ends. Paste events aren't buffered,
so they take effect right away. If the user pastes text during the
block, the paste has ended by the time its keys are replayed, and
lazygit treats them as typed keys. In a list panel, every character of
the text then runs the command bound to it. Without the block, lazygit
ignores keys pasted into a list panel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If you commit with `shift-C` ("Commit changes using git editor") while
no files are staged, lazygit asks whether to stage all files. Confirming
this often fails with "fatal: Unable to create '.../.git/index.lock':
File exists". This is caused by the refresh of the Files view after the
stage-all, which runs `git status` on a worker. Fix this by waiting for
that refresh to be finished before calling `git commit`.
Committing with `c` didn't run into this in practice, because the
refresh is done long before the user has typed a commit message.
If you commit with shift-C ("Commit changes using git editor") while no
files are staged, lazygit asks whether to stage all files. Confirming
this often fails with "fatal: Unable to create '.../.git/index.lock':
File exists".
After staging the files, we refresh the files view and then retry the
commit right away. The refresh runs `git status` on a worker. Since it
is a foreground refresh, git takes index.lock to write its refreshed
stat cache back to the index. So `git commit` can start while the status
command still holds the lock. Committing with `c` doesn't run into this
in practice, because the refresh is done long before the user has typed
a commit message.
Retry the commit in the refresh's Then instead. We already do this when
gui.skipNoStagedFilesWarning is on.
Without the fix, the new test failed in two of six runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If a squash merge stops with conflicts, lazygit didn't show a merge in
progress, because "git merge --squash" doesn't write MERGE_HEAD. After
resolving the conflicts, users had to commit the changes themselves, and
type the message that "Squash merge and commit" would have used. To give
up, they had to discard the changes. The "Abort the merge" item of the
conflicts menu failed with "You are currently neither rebasing nor
merging".
Improve this by showing a merge in progress when a "Squash merge and
commit" fails with conflicts, so that users can continue or abort it
like they would with a regular merge. We even enter this state after
"Squash merge and leave uncommitted", so that users can use the `m` menu
to continue and create a commit with the right message. This is a change
in behavior that I hope won't disturb anyone's workflows; please file an
issue if it does.
Discarding a file or directory in the files panel runs git commands on
these paths only, and these leave SQUASH_MSG alone. If users discard
all changes of a squash merge this way, lazygit keeps showing a merge
in progress, and the next commit gets the squash merge message.
Abort the squash merge after "Discard all changes" if no changes are
left. Untracked files don't count, because a squash merge stages the
files that it adds.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After "Squash merge and leave uncommitted", the commit panel opens
empty, or with the commit prefix of the branch. Users have to type a
message for the squash merge themselves. "git commit" from the terminal
suggests the message in SQUASH_MSG instead.
Show the message in SQUASH_MSG in the commit panel, unless there is a
preserved message. It takes precedence over the commit prefix, like the
message of "Squash merge and commit" does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If a squash merge stops with conflicts, lazygit doesn't show a merge in
progress, because "git merge --squash" doesn't write MERGE_HEAD. After
resolving the conflicts, users have to commit the changes themselves,
and type the message that "Squash merge and commit" would have used. To
give up, they have to discard the changes. The "Abort the merge" item
of the conflicts menu fails with "You are currently neither rebasing
nor merging".
"git merge --squash" writes SQUASH_MSG, a message for committing the
changes. Git removes it when the changes are committed, reset or
stashed, and when switching branches. Show a merge in progress while
the file exists. Continue it with "git commit", and abort it with "git
reset --merge", like "git merge --abort" does.
Write the configured squashMergeMessage into SQUASH_MSG, so that
continuing uses it. Do this for "Squash merge and leave uncommitted"
too, so that both variants make the same commit. A merge in progress is
now shown after this variant until the user commits the changes. We
don't offer to continue it when its conflicts are resolved, though,
because the user asked us not to commit.
Select the new commit after "Squash merge and commit", like after other
merges.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GenericMergeOrRebaseAction takes the name of the git command whose
--continue, --skip or --abort option it runs. We are about to show a
squash merge as an operation in progress, and it has no such command.
"git merge --continue" and "git merge --abort" refuse to run without
MERGE_HEAD, and a squash merge doesn't write it. Pass the working tree
state instead, so that GenericMergeOrRebaseAction can decide which
commands to run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If a merge, cherry-pick, revert or "git am" stops with conflicts, users
often edit the files that git changed, for example to fix the build. If
they then decide to give up and abort, git fails with "Entry 'file' not
uptodate. Cannot merge." It keeps unstaged changes when aborting, but it
can't reset the staged changes of a file underneath them. "git am
--abort" even stops halfway, after it has reset the conflicted files.
Git refuses to start any of these operations if there are staged
changes, or if a file that it has to change has unstaged changes. So
unstaged changes on top of staged ones were made after git stopped.
Discard them before aborting, but ask the user first, listing the
files. A rebase doesn't need this, because "git rebase --abort"
discards all changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Git refuses to merge a branch while there are staged changes, unless
the merge is a fast-forward. A fast-forward keeps the staged changes as
they are. A squash merge that git can fast-forward adds the changes of
the branch to them, though, and "Squash merge and commit" then commits
them all.
Disable both squash merge items in the merge menu while there are
staged changes, so that squash merges behave like other merges. We will
rely on this when we let the user abort a squash merge. Aborting resets
the staged changes, so they must all come from the merge.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
If the checked-out branch can be fast-forwarded to the selected branch,
"git merge --squash" doesn't refuse to run while there are staged
changes, unlike a merge that can't be fast-forwarded. It adds the
changes of the branch to the ones that are already staged. "Squash
merge and commit" then commits the staged changes along with the
squashed ones, without the user noticing.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Users can copy commits from one repo to another with "git format-patch"
and "git am", for example with custom commands. If "git am" stopped with
a conflict, lazygit showed this as a rebase, because git am uses the
rebase-apply directory too. The rebase options in the m menu didn't work
either; git refuses to run "git rebase --continue" during an am.
Recognize an am by the rebase-apply/applying file, and call this state
"applying patches". The `m` menu then offers "git am --continue",
"--abort" and "--skip".
Show them in the commits panel under a "Pending patches" header, like
the todos of a rebase or cherry-pick. The patches usually come from
another repository, so we can't look up their commits. Instead, take
the subject, author and date from the patch files in .git/rebase-apply,
and show the patch itself in the main view.
Git has no way of editing these todos, so disable the actions that
would edit them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All callers turned the result of isCherryPickingOrReverting into the
same disabled reason. The next commit adds a second reason for "git
am", so return the reason itself.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Users can copy commits from one repo to another with "git format-patch"
and "git am", for example with custom commands. If "git am" stopped
with a conflict, lazygit showed this as a rebase, because git am uses
the rebase-apply directory too. The rebase options in the m menu didn't
work either; git refuses to run "git rebase --continue" during an am.
Recognize an am by the rebase-apply/applying file, and call this state
"applying patches". The m menu then offers "git am --continue",
"--abort" and "--skip".
If "git am --continue" finds nothing to commit, it says "No changes -
did you forget to use 'git add'?". Unlike for a rebase, don't skip the
patch in that case. Without --3way, git am leaves the files alone if a
patch doesn't apply, and skipping would lose the patch.
If a lock error stops "git am --continue" or "--skip" after it has moved
on to the next patch, don't run it again; this could skip that patch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The texts for aborting or continuing a merge, rebase, cherry-pick or
revert insert the git command name into the sentence, as in "Abort the
rebase". We are going to support "git am" next, and "Abort the am"
makes no sense.
Give each operation a translated phrase like "the rebase" instead, and
use it as a placeholder in these texts. Rename the texts too. The
existing translations still use %s, and this way lazygit ignores them
until they are updated.
This changes the wording of the abort confirmation slightly, from
"Abort rebase" to "Abort the rebase", and from "abort the current
rebase" to "abort the rebase".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>