Commit graph

507 commits

Author SHA1 Message Date
Stefan Haller f032ee8b0f Extract BranchesHelper.PostFetchRefresh to unify the two fetch paths
The post-fetch logic was duplicated in `backgroundFetch` and the manual
fetch handler: refresh a fixed set of views, then auto-forward branches
if the fetch succeeded. The two had already drifted on the refresh
scope; folding them into a single helper makes the duplication go
away and prevents it from drifting again.

Pass the fetch error through so we preserve the previous behaviour of
refreshing unconditionally but only auto-forwarding on success.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 13:57:46 +02:00
Stefan Haller 7d1d90ae4d Preserve empty Worktrees slice when worktree list fails to load
If `git worktree list` fails, we want the Worktrees model to fall back
to an empty slice so callers iterating over it stay correct. The error
branch was setting it to `[]`, but the line below unconditionally
overwrote it with the nil `worktrees` value from the failed call.

Use an else branch so the empty-slice fallback actually sticks.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-21 13:57:46 +02:00
Stefan Haller 58abf24862 Log errors from fetching GitHub PRs to the debug log, not to the Command Log
In the Command Log we only want to see errors for user-initiated actions, not
from background activity.
2026-05-10 15:47:02 +02:00
Stefan Haller d9aceaf0da Keep GitHub PR refresh error logging in one place 2026-05-10 15:40:09 +02:00
Stefan Haller 1c79fe24d0 Support GitHub Enterprise for the pull-requests feature
The branches-panel PR icons only worked for github.com remotes. There was no
fundamental reason — the auth library we already vendor (cli/go-gh) supports
enterprise tokens out of the box (GH_ENTERPRISE_TOKEN, gh auth's keyring),
and the user-facing 'services' config has long been the documented way to
tell lazygit "this domain is a github service" for the View-PR-URL feature.

The fetcher just hardcoded github.com in three places:
- a substring check on the remote URL to decide we're "in a github repo",
- the GraphQL endpoint (always api.github.com/graphql), and
- the auth lookup (always against the default host).

Plumb the resolved web domain through instead. Detection now goes through
the hosting_service ("is this remote's provider 'github'?"), which means a
user with services: { 'git.acme.com': 'github:git.acme.com' } configured
gets PR icons on their GHE remotes too.

Replacing the substring check with a provider check also tightens a latent
bug in getGithubRemotes: it previously accepted any remote whose URL parsed
with the default regex, including gitlab and bitbucket — masked today only
by the InGithubRepo gate, but exposed once the gate goes away.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 15:28:14 +02:00
Stefan Haller 7a2d1ab544 Clear cherry-pick buffer when copying after a paste
After a successful paste DidPaste is true, hiding the "X commits copied"
indicator but leaving the buffer populated. From the user's perspective
this looks like a clean slate, so a new shift+C should start fresh. It is
important to reset DidPaste first, before populating the buffer with the new
commits, because otherwise each loop iteration would overwrite the previous one
since Add() rebuilds the set via SelectedHashSet() which returns empty while
DidPaste is set.
2026-05-09 14:02:42 +02:00
Stefan Haller 4734bab896 Improve performance of inline status spinner
Now that HandleRender no longer does an implicit Render(), we can use it inside
OnUIThreadContentOnly to save performance, on the assumption that rerendering a
view that contains an inline spinner never changes the layout.
2026-05-09 13:58:33 +02:00
Antoine Gaudreau Simard 8675862228 Bounce setGithubPullRequests' Branches render to UI thread
Fixes an issue mostly noticeable using the go -race mode, this resolves
them.
2026-05-09 13:58:33 +02:00
Stefan Haller a364a8d75c Bounce explicit LocalCommits render in refreshBranches to UI thread
refreshBranches runs on a worker goroutine and re-renders the commits view
directly to refresh the branch-head visualization. As with refreshView, this
currently only flushes because HandleRender ends with self.c.Render(). Wrap the
explicit HandleRender call (along with the LocalCommitsMutex pair around it) in
OnUIThread so it keeps working once Render() is removed.
2026-05-09 13:58:33 +02:00
Stefan Haller 0d1caf5c22 Bounce refreshView to the UI thread
An async refresh dispatches refreshXyz on a worker goroutine, which then calls
refreshView -> PostRefreshUpdate -> HandleRender. Today the final
self.c.Render() inside ListContextTrait.HandleRender is what triggers a UI flush
from the worker. We're going to remove that Render() call, so prepare by
wrapping refreshView's body in OnUIThread.

This moves the entire rendering of the view (and the ReApplyFilter/ReApplySearch
stuff) to the UI thread, not just the layout. I don't expect this to make a
difference in practice, and it is already one step towards my long-term goal of
moving all view rendering to the UI thread (see
https://github.com/jesseduffield/lazygit/issues/2974#issuecomment-1729154768).
2026-05-09 13:58:33 +02:00
Stefan Haller 79440c0945 Set the view color on the UI thread too
Unrelated to this branch, just because we're touching this code: there's little
reason to set the color on the background thread but the text on the UI thread.
Set them both together on the UI thread. Avoids a data race (unlikely to be a
problem in practice, we're talking about a 64-bit int, but still).
2026-05-09 13:58:33 +02:00
Stefan Haller 670565c175 Have renderAppStatus trigger a full layout when the appStatus width changes
In 0d195077e4 we improved the performance of the status bar spinner by
avoiding a layout. This is fine from one spinner tick to the next, but it's a
problem when spinning starts or ends (or in the hypothetical case that the
status text changes in the middle of the operation, which we never do in
lazygit, but theoretically could). In this case a layout is needed so that the
rest of the status bar gets pushed over appropriately (or moves back to the left
when the spinner ends), and also so that the bottom line is shown or hidden
properly for users who set gui.showBottomLine to false.

To fix this, keep track of the status string width and force a layout whenever
it changes. This includes the beginning and end of an operation when it changes
from empty to non-empty or vice versa.

There is currently no observable misbehavior from this bug, but that's only
because we must have a HandleRender call somewhere that forces a full layout
when an operation starts or ends. We will remove the Render() call from
HandleRender at the end of this branch, at which point the misbehavior would be
visible if we didn't fix it here.
2026-05-09 13:58:33 +02:00
Antoine Gaudreau Simard b7edcbad3a Improve performance of spinner in Synchronized events
Instead of redrawing the two views on each tick, only redraw the spinner
2026-05-06 20:38:53 +02:00
Antoine Gaudreau Simard 0d195077e4 Improve performance when drawing the spinner on background events 2026-05-06 18:53:32 +02:00
Antoine Gaudreau Simard 79727f1780 Honor the spinner rate configurate with sync spinner 2026-05-06 18:53:32 +02:00
Stefan Haller 64996d12d9 Add Key type
This bundles the keyName and a rune, so that we don't have to pass these around
separately everywhere. This should make it easier to swap out the rune for a
string when we upgrade to tcell v3.
2026-04-30 22:14:26 +02:00
Stefan Haller ff58687b68 Cleanup: remove unnecessary function Label()
It's a no-op. (I think this might not have been the case in the past, where
Label() was needed to "normalize" a keybinding or something.)
2026-04-30 22:14:26 +02:00
Stefan Haller 196e0a3c17 Copy gocui files into lazygit repo under pkg/gocui
I copied all files except dot files (.github and .gitignore), the _examples
folder, and go.mod/go.sum.

At some point we may want to copy the files back to the gocui repo when other
clients (e.g. lazydocker) want to use the newer versions of them.
2026-04-30 14:29:08 +02:00
Stefan Haller cef1f8fc2c If multiple remotes exist but only one is a Github remote, pick it without prompting
If the repo has multiple remotes, but only one of them is on Github (the others
might for example point to a self-hosted Critic server or something like that),
lazygit would still present a menu to choose the remote for pull requests, but
it would contain only that single entry. That's pointless, pick it automatically
without prompting.

We add some tests while we're at it; these wouldn't have caught the problem,
because they only test getGithubBaseRemote which already takes the filtered
github remotes. It's still better than not having any tests; the real issue
could only have been caught with an integration test, which we don't bother
adding.
2026-04-26 16:55:51 +02:00
Stefan Haller 5b596bb62b Stop showing cached PRs when we no longer have a base remote
If we used to have a base remote (e.g. because there was only one), but now we
don't (e.g. because the user has added a second one, and we don't know yet which
one is the base), stop showing the cached PRs. This is especially important in
the case that the user cancels the prompt, in which case we don't have a base
remote for the rest of the session, but would keep showing the (potentially
stale) cached PRs forever.
2026-04-25 14:23:43 +02:00
Stefan Haller 227081f1d1 Preserve whitespace when remembering a commit message
This is useful when cancelling out of the commit panel mid-sentence (after
having typed the space for the next word); when entering the commit message
panel again, the space was gone and you had to type it again. Small thing, but
it just seems better to resume the panel in exactly the state that you left it
in. (Which we actually don't do; we don't remember the cursor position, or which
of the subject/description panels was active. That would be a separate
improvement.)

The save path and the load path used to be asymmetric. On save, the textarea
getters applied strings.TrimSpace, which stripped any leading blank lines, a
trailing newline after the cursor, or indentation on the very first line of the
description — all of which are legitimate user content. On load,
SplitCommitMessageAndDescription did TrimSpace on the description as well, and
the preserved message was routed through that same git-format split because
HandleCommitPress passed it as OpenCommitMessagePanel's InitialMessage. The
result: every round-trip through "escape and reopen" silently mutated the
message.

The fix is to treat our own preservation file as its own format, distinct from
git's canonical "summary\n\nbody" format:

- The textarea getters return raw content. strings.TrimSpace moves to the one
  place that still needs it: the empty-summary check in HandleCommitConfirm (git
  itself strips trailing whitespace and blank lines, so no pre-trim is needed
  before -m).
- SplitPreservedCommitMessage / SetPreservedMessageInView split on the single
  "\n" our Join uses, without any trimming — truly lossless.
- SplitCommitMessageAndDescription keeps its git-format behavior but replaces
  TrimSpace with TrimPrefix("\n"), so it strips only the blank-line separator
  and leaves body indentation intact.
- HandleCommitPress now mirrors HandleWIPCommitPress: it no longer passes the
  preserved message as InitialMessage. OpenCommitMessagePanel resolves the
  preserved content itself, uses it for display via the preservation-format
  setter, and stores it as the initial message so the close-time "did the user
  change anything?" check still correctly detects a cleared panel.
- GetInitialMessage no longer trims. With raw getters on both sides of the
  comparison, trimming here caused spurious non-matches (e.g. for preserved
  content with trailing whitespace). The original motivation — matching a
  "WIP: " prefix with trailing space — works unchanged.
- UpdateCommitPanelView becomes dead code and is removed; its one remaining
  caller (history cycling, always git-format) goes directly through
  SetMessageAndDescriptionInView.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 18:51:17 +02:00
Stefan Haller 3de2a2d96c Extract PreserveCommitMessage from CloseCommitMessagePanel
We'll want to call just the preservation step when the app is quitting
with the commit message panel open: at that point the view-hiding and
context-popping in CloseCommitMessagePanel are pointless (the app is
about to exit), and some of the focus-lost side effects could be
surprising during teardown.
2026-04-21 11:25:29 +02:00
Stefan Haller 5c61126c4b Remove unnecessary clearing of the commit message panel on close
There's no reason for this; when opening the panel next time, it will be
populated again. Also, it doesn't make sense to do this only when not preserving
the message.
2026-04-21 11:23:37 +02:00
Stefan Haller 4218f97a21 Don't default the base repo for pull requests to "origin"
When no base repository is configured using gh, we have several heuristics for
choosing a likely base repo before prompting the user. One of these was that if
the origin remote exists, we'd choose that. This may be the right choice in some
cases, but in many others it's not; for example, in the common scenario of a
forking setup where the upstream repo's remote is called after its owner, and
the fork remote is called origin, we would choose origin as the base repo, which
results in not showing any PRs. Don't do that, and instead prompt the user for
the base repo in this case.
2026-04-13 22:00:54 +02:00
Stefan Haller e4309f101c Log hashes of local branches when deleting them
This makes it easier to recover them if they were deleted accidentally.

We only do this for local branches for now, we don't bother for remote branches;
it would be trickier there, because the branch on the server might not actually
point to the same hash as our local remote tracking branch does.
2026-04-06 19:06:25 +02:00
Stefan Haller e92b04e1dc Don't refresh pull requests when checking out a local branch
For esthetic reasons, checking out a branch (or other ref) blocks the UI until
the refresh is done, so it's important that the refresh doesn't do unnecessary
work. Refreshing pull requests is unnecessary (but costly, when waiting for it)
when a branch is checked out that already existed locally. However, it is
required when checking out a remote branch for the first time, so that the PR
icon appears immediately when there is one.
2026-04-01 09:13:55 +02:00
Stefan Haller 6145eaf939 Prompt only once per session for each repo
If the user hits escape in the "Select base repository for pull requests"
prompt, don't bother them again for this repo at the next refresh.
2026-04-01 09:13:55 +02:00
Jesse Duffield fff6003044 Cache PRs in AppState so that they appear immediately at startup
Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-04-01 09:13:55 +02:00
Jesse Duffield d33fa5bb05 Add pull requests to lazygit's model and refresh them
Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2026-04-01 09:13:55 +02:00
Stefan Haller 968c44b02f Add missing entries to scopeNameMap 2026-03-31 15:48:28 +02:00
Stefan Haller fde4bc1fb2 Allow customizing the window width/height thresholds for when to use portrait mode 2026-03-31 13:08:46 +02:00
Stefan Haller f8fd8d04bc Fix typo 2026-03-31 13:08:46 +02:00
Stefan Haller 9f38d02a4e Don't stage out-of-date submodules when asking user to auto-stage after resolving conflicts
When you rebase a branch and there are conflicts, lazygit asks you to continue
the rebase when it detects that all conflicts have been resolved. However, it is
common to make additional changes beyond just fixing the conflicts (e.g. to fix
build failures), and when doing that while the "Continue the rebase?" prompt is
showing, lazygit detects that too and asks you if you want to stage those newly
modified files too. This is all well and good (and can be disabled for those who
don't like it); however, lazygit would treat out-of-date submodules as unstaged
changes and would offer to stage those as well, and this is pretty bad and
almost never what you want. Fix this by excluding submodules from that second
check.
2026-03-30 17:41:53 +02:00
Stefan Haller 781105c49b Scroll to top when showing subcommits
Previously, when showing subcommits of a branch or tag, scrolling down, and then
going out and entering subcommits again, it would select the first commit but
keep the previous scroll position, so that the selection was not visible. Always
scroll to the top to fix this.
2026-03-27 14:55:02 +01:00
Stefan Haller da065b5b63 Add comments
I got confused when I investigated the bug that was fixed in the previous
commit, so hopefully these will be helpful for the next person reading this
code.
2026-03-08 17:24:26 +01:00
Stefan Haller bcfb4e11c0 Fix off-by-one error when calculating popup panel dimensions
The symptom of the bug was that confirmation panels with wrapped text were one
character too wide, which could sometimes result in them being one line too
heigh. One concrete example is the "Discard file changes" confirmation that
appears when pressing `d` in the commit files panel.

I got very confused when investigating this bug, and the reason is that
getPopupPanelDimensionsAux took two parameters panelWidth and panelHeight, but
the second was actually contentHeight. Rename it to contentHeight, and change
the first one to also be the content width rather than the panel width. This is
easier for clients, and less confusing.
2026-03-08 17:24:26 +01:00
Jesse Duffield 8728da7985 Only reset selection in ReApplyFilter when search prompt is active
Without this check, the selection was being reset to 0 whenever
ReApplyFilter was called for the current filter context, even when
the user wasn't actively typing in the search prompt (e.g. when the
model updates in the background). This was causing unexpected cursor
jumps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:04:31 +01:00
Jesse Duffield ed9693ebcc Reset PrevSearchIndex when opening filter prompt
When opening a filter prompt, reset PrevSearchIndex to -1 to avoid
stale search state from a previous search/filter session.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:04:31 +01:00
Jesse Duffield 60316388c0 Rename GetFilter to GetStatusFilter on IFileTree
This avoids a naming collision with GetFilter from the
IFilterableContext interface, which will be implemented by
FileTreeViewModel in the next commit.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 20:04:31 +01:00
Stefan Haller 35d06c32d7 Turn error checks in discard handler into DisabledReasons
That's how we usually do this; it's useful to see in the keybindings menu that
the command is disabled.
2026-03-07 20:47:19 +11:00
Stefan Haller 1ede4e6eb9 Limit popup panel widths to a maximum width
On very large screens they would get ridiculously wide.

The limits we chose here are a little arbitrary:
- 80 for confirmations and prompts
- 90 for menus
- auto-wrap width plus 25 for the commit message editor (or 100 if auto-wrap is
  off)
2026-01-28 09:02:27 +01:00
Stefan Haller 268d1934f8 Ignore fixup commits for a found base commit when doing ctrl-f 2026-01-24 16:04:35 +01:00
Stefan Haller 4bcf3e181c Extract a helper method getCommitsForHashes 2026-01-24 16:04:35 +01:00
Stefan Haller df1e5512da Make getHashesAndSubjects a free-standing function
There's no reason for this to be a method.
2026-01-24 16:04:35 +01:00
Stefan Haller 2ba3c1dc3f Move isFixupCommit to FixupHelper 2026-01-24 16:04:35 +01:00
Stefan Haller 1f7e5d7767 When doing ctrl-f, and the resulting commit is not visible, scroll it into view
This used to work in the last version, but broke with efd4298b5e (#5134).
2026-01-06 16:17:33 +01:00
Stefan Haller 98bf8f86d1 Bump gocui and adapt code accordingly 2026-01-04 18:48:47 +01:00
Stefan Haller 3ccd33b388 Show an error when checking out a file would overwrite local modifications 2026-01-03 20:09:55 +01:00
Stefan Haller fec7e9ce61 Bump gocui (and tcell)
and adapt lazygit's client code accordingly.
2025-12-23 16:49:16 +01:00
Stefan Haller efd4298b5e Avoid scrolling the selection into view on refresh
It is possible to scroll the selection out of view using the mouse wheel; after
doing this, it would sometimes scroll into view by itself again, for example
when a background fetch occurred. In the files panel this would even happen
every 10s with every regular files refresh.

Fix this by adding a scrollIntoView parameter to HandleFocus, which is false by
default, and is only set to true from controllers that change the selection.
2025-12-23 15:34:38 +01:00