Compare commits

..
212 Commits
Author SHA1 Message Date
Stefan Haller 80223c7fbb Fix keybinding cheatsheets with regard to pipe characters in key or description (#5007)
The "Cycle pagers" command wasn't rendered correctly, because it's bound
to '|' by default, but this was taked as a table column delimiter. Fix
this by escaping the pipe character.

A similar thing could happen for the description or tooltip (and did in
fact, for a tooltip in the russion translation), so escape those too
just to be sure.
2025-11-01 09:56:57 +01:00
Stefan Haller ff0a16f809 Fix keybinding cheatsheets with regard to pipe characters in key or description
The "Cycle pagers" command wasn't rendered correctly, because it's bound to '|'
by default, but this was taked as a table column delimiter. Fix this by escaping
the pipe character.

A similar thing could happen for the description or tooltip (and did in fact,
for a tooltip in the russion translation), so escape those too just to be sure.
2025-11-01 09:54:32 +01:00
Stefan Haller 48c1db6fe4 Fix fixup's false filename detection in hunks containing dashed lines (#5004)
### PR Description

The existing diff parser incorrectly treated subsequent lines beginning
with `---` as filename headers while processing hunks. This caused
corruption when dashed lines appeared within diffs themselves.

Restrict filename detection to only occur between hunks.

This repository https://github.com/abobov/lazygit-fixup-issue
demonstrates the issue:

- clone repository
- run `git reset --soft HEAD~`
- try `<c-f>` Find base commit for fixup 
- `fatal: no such path ...` error
2025-11-01 09:42:45 +01:00
Anton Bobov 4c4929acad Fix fixup's false filename detection in hunks containing dashed lines
The existing diff parser incorrectly treated subsequent lines beginning
with "---" as filename headers while processing hunks.  This caused
corruption when dashed lines appeared within diffs themselves.

Restrict filename detection to only occur between hunks.
2025-11-01 09:40:04 +01:00
Stefan Haller f4920e8efa Use a PTY when using external diff command from git config (#4983)
### PR Description

I hit the same issue as
https://github.com/jesseduffield/lazygit/issues/3119 when setting
`useExtDiffGitConfig` to true and using `difftastic` side-by-side
comparison in my git config.

The same fix in https://github.com/jesseduffield/lazygit/pull/3120 needs
to be applied for the newer config option.
2025-11-01 09:35:45 +01:00
Brandon 263ad11b3b Use a PTY when using external diff command from git config
Same logic as https://github.com/jesseduffield/lazygit/pull/3120 but for new external diff option
2025-11-01 09:33:52 +01:00
Stefan Haller 6f0f1e7995 Update translations from Crowdin (#5005) 2025-10-31 19:58:37 +01:00
Stefan Haller 3967edd113 Update translations from Crowdin 2025-10-31 19:56:01 +01:00
Stefan Haller 32a701cb9c Use ignore directive to ignore test files not to be passes to gofumpt (#4936)
### Motivation
To replace the workaround introduced in
https://github.com/jesseduffield/lazygit/pull/4809 with `ignore`
directive to make the code simpler.

### Overview
This is a follow-up PR to
https://github.com/jesseduffield/lazygit/pull/4809.
I have added 4 changes due to updating gofumpt to remove `git ls-files`
workaround.

Please take a look at each commit messages for details.
2025-10-20 07:59:40 +02:00
kyu08 64bcc72e45 Specify return value where named return value is used
After [v0.9.0](https://github.com/mvdan/gofumpt/releases/tag/v0.9.0),
gofumpt prohibits "naked return" for the sake of clarity. This makes
more readable when "named return value" is used.
For more infomation for "prohibition of naked return":
https://github.com/mvdan/gofumpt/issues/285.
2025-10-20 07:56:56 +02:00
kyu08andStefan Haller beb05d4a61 Fix makeAtomic in branches_test
When replacing the naked return with a `return result`, the linter starts to
complain about "return copies lock value: sync/atomic.Int32 contains
sync/atomic.noCopy". I suspect this is also a problem when using a naked return,
and the linter just doesn't catch it in that case. Either way, it's better to
use a pointer to ensure that the atomic is not copied.

Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2025-10-20 07:56:56 +02:00
kyu08andStefan Haller 65c27dd8ce Remove workaround for "make format"
When a new enough gofumpt version is used (v0.9.0 or later), it suports the
`ignore` directive that we just added, so the workaround is no longer needed.

Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2025-10-20 07:56:56 +02:00
kyu08 76452a0bcc Add ignore directive in go.mod
This can be used by go tools such as gofumpt.
2025-10-20 07:56:56 +02:00
Stefan Haller 6782f04e8c Add no-ff merge option (#4966)
This will put whatever git's default merge variant is as the first menu
item, and add a second item which is the opposite (no-ff if the default
is ff, and vice versa). Which one is the default depends on whether a
fast-forward merge is possible, and whether users have set git's
`merge.ff` config or lazygit's `git.merging.args` config.

If users prefer to always have the same option first no matter whether
it's applicable, they can make ff always appear first by setting git's
`merge.ff` config to "true" or "only", or by setting lazygit's
`git.merging.args` config to "--ff" or "--ff-only"; if they want no-ff
to appear first, they can do that by setting git's `merge.ff` config to
"false", or by setting lazygit's `git.merging.args` config to "--no-ff".
Which of these they choose depends on whether they want the config to
also apply to other git clients including the cli, or only to lazygit.

Closes #4091.
2025-10-19 21:26:31 +02:00
Stefan Haller 62854026a3 Add no-ff merge option
This will put whatever git's default merge variant is as the first menu item,
and add a second item which is the opposite (no-ff if the default is ff, and
vice versa).

If users prefer to always have the same option first no matter whether it's
applicable, they can make ff always appear first by setting git's "merge.ff"
config to "true" or "only", or by setting lazygit's "git.merging.args" config to
"--ff" or "--ff-only"; if they want no-ff to appear first, they can do that by
setting git's "merge.ff" config to "false", or by setting lazygit's
"git.merging.args" config to "--no-ff". Which of these they choose depends on
whether they want the config to also apply to other git clients including the
cli, or only to lazygit.
2025-10-19 21:24:28 +02:00
Stefan Haller c88381b9ce Rename texts to be consistent with our conventions 2025-10-19 21:24:28 +02:00
Stefan Haller afbef40258 Reorder texts so that they are grouped together, and ordered in a sensible way 2025-10-19 21:24:28 +02:00
Stefan Haller 4e0194d8f7 Replace MergeOpts struct with MergeVariant enum
- Squash and FastForwardOnly are mutually exclusive, and instead of asserting
  this at runtime, model the API so that they can't be passed together.
- FastForwardOnly is unused, so remove it; however, we are going to need --ff
  and --no-ff in the next commit, so add those instead.
- Instead of putting the enum into the MergeOpts struct, replace the struct by
  the enum. We can reintroduce the struct when we add more arguments, but for
  now it's an unnecessary indirection.
2025-10-19 21:24:28 +02:00
Stefan Haller d334bae0a0 Add synchronize event to the hooks of "Check Required Labels" (#4974)
I found that PR status becomes green even if no label added when we use
`Update branch` on GitHub PR page.

So I added `synchronize` event to the hooks of `Check Required Labels`.
2025-10-19 17:29:17 +02:00
kyu08 76948f82c1 Add synchronize event to the hooks of "Check Required Labels" 2025-10-19 22:12:23 +09:00
Stefan Haller a22aced1bb Avoid auto-stashing when only submodules are out of date (#4969)
Stashing doesn't affect submodules, so if you have a working copy that
has out-of-date submodules but no other changes, and then you revert or
paste a commit (or invoke one of the many other lazygit commands that
auto-stash, e.g. undo), lazygit would previously try to stash changes
(which did nothing, but also didn't return an error), perform the
operation, and then pop the stash again. If no stashes existed before,
then this would only cause a confusing error popup ("error:
refs/stash@{0} is not a valid reference"), but if there were stashes,
this would try to pop the newest one of these, which is very undesirable
and confusing.
2025-10-16 12:08:48 +02:00
Stefan Haller 40cc008a5a Avoid auto-stashing when only submodules are out of date
Stashing doesn't affect submodules, so if you have a working copy that has
out-of-date submodules but no other changes, and then you revert or paste a
commit (or invoke one of the many other lazygit commands that auto-stash, e.g.
undo), lazygit would previously try to stash changes (which did nothing, but
also didn't return an error), perform the operation, and then pop the stash
again. If no stashes existed before, then this would only cause a confusing
error popup ("error: refs/stash@{0} is not a valid reference"), but if there
were stashes, this would try to pop the newest one of these, which is very
undesirable and confusing.
2025-10-16 12:00:31 +02:00
Stefan Haller 7282159f78 Show a better error message if the temp directory is not writeable (#4968)
This happened to a few users after updating macOS to version 15.7.1, and
apparently a reboot fixes it, so suggest this in the error message.

Closes #4924
2025-10-16 10:10:30 +02:00
Stefan Haller e02f073cd0 Show a better error message if the temp directory is not writeable
This happened to a few users after updating macOS to version 15.7.1, and
apparently a reboot fixes it, so suggest this in the error message.
2025-10-16 10:04:47 +02:00
Stefan Haller 853b54b069 Add config option gui.skipSwitchWorktreeOnCheckoutWarning (#4964)
Adds a config option that disables the confirmation for switching to a
worktree when trying to check out a branch that is checked out in that
other worktree.

Fixes #4938.
2025-10-14 14:24:36 +02:00
Stefan Haller 80f860beb7 Add config option gui.skipSwitchWorktreeOnCheckoutWarning 2025-10-14 14:19:54 +02:00
Stefan Haller d3fb22a673 Fix lazygit getting unresponsive when pasting commits that become empty (#4958)
Lazygit would become unresponsive when cherry-picking a commit that
becomes empty at the destination. There are two ways this can happen:
one where a cherry-picked commit simply becomes empty "by itself",
because the change is already present on the destination branch (this
was only a problem with git versions older than 2.45), and another where
the cherry-pick stops with conflicts, and the user resolves them such
that no changes are left, and then continues the cherry-pick. This would
still fail even with git 2.45 or later. The fix is the same for both
cases though.

Fixes #4763.
2025-10-14 12:30:15 +02:00
Stefan Haller 3ac9003298 Add tests
I can't do my usual "add the tests first, with EXPECTED/ACTUAL sections that
document the bug" method here, because the tests would hang without the bug
being fixed.

We need two different tests here: one where a cherry-picked commit simply
becomes empty "by itself", because the change is already present on the
destination branch (this was only a problem with git versions older than 2.45),
and the other where the cherry-pick stops with conflicts, and the user resolves
them such that no changes are left, and then continues the cherry-pick. This
would still fail even with git 2.45 or later. The fix is the same for both cases
though.

The tests show that the selection behavior after skipping an empty cherry-picked
commit is not ideal, but since that's only a minor cosmetic problem we don't
bother fixing it here.
2025-10-14 12:27:32 +02:00
Stefan Haller e858bad3f8 Fix lazygit getting unresponsive when pasting commits that become empty
Whenever git returns the error "The previous cherry-pick is now empty", we would
previously continue the rebase; this works for rebase because it behaves the
same as "git rebase --skip" in this case. That's not true for cherry-pick
though; if you continue a cherry-pick where the current commit is empty, it will
return the same error again, causing lazygit to be stuck in an endless loop.

Fix this by skipping instead of continuing; this shouldn't make a difference for
rebase, but works for cherry-pick.

Theoretically we could have a similar problem for revert (if you are trying to
revert a commit that has already been undone through some other means); this
should then be fixed in the same way with this change. However, the change is
not relevant for revert because git returns a different error in this case.
2025-10-14 12:27:32 +02:00
Stefan Haller 9adcc8ec79 Fix window arrangement when a popup or the search prompt is open (#4961)
When focusing the main view, going into full screen mode by pressing '+'
twice, and then opening the search prompt ('/') or a menu (e.g. '?' or
':'), the full screen display would switch to the focused side panel.

Fix this by always excluding popups from the window arrangement logic.
No popup should ever have any influence on how the views beneath it are
laid out.

Addresses [this detail
aspect](https://github.com/jesseduffield/lazygit/issues/1113#issuecomment-3206883422)
of #1113.
2025-10-14 12:27:16 +02:00
Stefan Haller 8eeb16c8da Fix window arrangement when a popup or the search prompt is open
When focusing the main view, going into full screen mode by pressing '+' twice,
and then opening the search prompt ('/') or a menu (e.g. '?' or ':'), the full
screen display would switch to the focused side panel.

Fix this by always excluding popups from the window arrangement logic. No popup
should ever have any influence on how the views beneath it are laid out.
2025-10-14 12:22:58 +02:00
Stefan Haller 442ede95c2 Offer to force-delete a worktree if it contains submodules (#4959)
For a worktree that contains submodules, trying to delete it would show
an error instead of offering to force-remove it.

Fixes #4125.
2025-10-14 12:22:38 +02:00
Stefan Haller 3415ed975b Offer to force-delete a worktree if it contains submodules 2025-10-14 12:19:49 +02:00
Stefan Haller 7568f5bf05 Add test demonstrating the problem
For a worktree that contains submodules, trying to delete it shows an error and
doesn't offer to force-remove it.
2025-10-14 12:19:49 +02:00
Stefan Haller 5812466145 Support multiple pagers (#4953)
Allow configuring an array of pagers, and cycling through them with the
`|` key (mnemonic: we are piping the output through something). I find
this useful for switching between delta (which I prefer most of the
time) and difftastic (which occasionally produces better output for
certain kinds of changes). It could also be used to switch between delta
in inline mode and in side-by-side mode.
2025-10-14 12:19:32 +02:00
Stefan Haller 32bf6d685c Add config migration of paging section to pagers array 2025-10-14 12:17:13 +02:00
Stefan Haller 823b2e9aab Update cheatsheets 2025-10-14 12:17:13 +02:00
Stefan Haller e44d6ec330 Replace paging config with an array of pagers 2025-10-14 12:17:13 +02:00
Stefan Haller 765c9eb85c Add PagerConfig
This is an object that is owned by Gui, is accessible through GuiCommon.State(),
and also passed down to GitCommand, where it is mostly needed. Right now it
simply wraps access to the Git.Paging config, which isn't very exciting, but
we'll extend it in the next commit to handle a slice of pagers (and maintain the
currently selected pager index), and doing this refactoring up front allows us
to make that change without having to touch clients.
2025-10-14 12:17:13 +02:00
Stefan Haller ed05470732 Cleanup: remove unused method ConfiguredPager
This could have been removed in commit 9657b4346f.
2025-10-14 12:17:13 +02:00
Stefan Haller 1c87776470 When pasting multi-line text into a prompt, don't treat the line feeds as "confirm" (#4955)
This is likely to do bad things; for example, if the prompt is the shell
command prompt, then we would run into what looks like a deadlock bug in
tcell. In other cases, the characters in the following lines might be
treated as random keybindings after the prompt is confirmed; this is
very similar to #4234.

In this case, it seems the best we can do is to simply swallow the line
feeds. The entire pasted text will then appear as a single long line in
the prompt, and hopefully the user knows that they can use ctrl-u to
delete it again. If not, they will probably just hit esc to close the
prompt.

Fixes #4954.
2025-10-14 12:15:44 +02:00
Stefan Haller fbaea583a5 When pasting multi-line text into a prompt, don't treat the line feeds as "confirm"
This is likely to do bad things; for example, if the prompt is the shell command
prompt, then we would run into what looks like a deadlock bug in tcell. In other
cases, the characters in the following lines might be treated as random commands
after the prompt is confirmed.
2025-10-14 12:10:51 +02:00
Stefan Haller 6b5b493237 Fix support for Git copy status when status.renames=copies (#4892)
Fixes: #4890

- Fix file status parsing to handle Git copy operations when
`status.renames=copies` is configured
- Extend status parser to recognize both "R" (rename) and "C" (copy)
prefixes
- Add comprehensive test coverage for copy status codes

The same issue from https://github.com/jesseduffield/lazygit/issues/4890
now is fixed

<img width="602" height="453" alt="image"
src="https://github.com/user-attachments/assets/c5b0dc4b-8f77-4867-b601-65faa91d6607"
/>
2025-10-14 12:07:04 +02:00
Karen Grigoryan e6f3313d9a Fix support for Git copy status when status.renames=copies
Fixes #4890

When Git is configured with status.renames=copies, it can produce
status codes starting with "C" (copy) in addition to "R" (rename).
The file loader was only checking for "R" prefixes, causing copy
operations to be parsed incorrectly and breaking file staging.

This fix extends the status parser to handle both "R" and "C"
prefixes, ensuring proper support for Git's copy detection feature.

Fixes file staging issues when using status.renames=copies configuration.
2025-10-14 11:57:12 +02:00
Jesse Duffield 1fd771f818 Update README.md
Updated image source for Warp logo in README.
2025-10-11 15:50:08 +11:00
Stefan Haller f7a4092a29 Wrap long lines in comments in Config.md (#4951)
Since these are displayed in a fenced code block on github, they aren't
auto-wrapped on display, so users have to scroll horizontally to read
longer paragraphs.

Addresses
https://github.com/jesseduffield/lazygit/issues/800#issuecomment-3209372957.
2025-10-10 21:27:56 +02:00
Stefan Haller db9fb216fd Wrap long comment lines in Config.md 2025-10-10 21:25:02 +02:00
Stefan Haller c9eef8079e Add missing Portuguese language 2025-10-10 21:25:02 +02:00
Stefan Haller 396f87b46b Unwrap paragraphs in user_config comments
We want to switch to have paragraphs consistently on one line, and auto-wrap
them automatically when generating Config.md.

The changes to Config.md in this commit are temporary.
2025-10-10 21:25:02 +02:00
Stefan Haller 30169bd258 Fix dropping submodule changes from a commit (#4937)
Our logic to decide if a file needs to be checked out from the previous
commit or deleted because it didn't exist in the previous commit didn't
work for submodules. We were using `git cat-file -e` to ask whether the
file existed, but this returns an error for submodules, so we were
always deleting those instead of reverting them back to their previous
state.

Switch to using `git ls-tree -- file` instead, which works for both
files and submodules.

Fixes #4932.
2025-10-10 12:58:06 +02:00
Stefan Haller c1e52fc807 Fix dropping submodule changes from a commit
Our logic to decide if a file needs to be checked out from the previous commit
or deleted because it didn't exist in the previous commit didn't work for
submodules. We were using `git cat-file -e` to ask whether the file existed, but
this returns an error for submodules, so we were always deleting those instead
of reverting them back to their previous state.

Switch to using `git ls-tree -- file` instead, which works for both files and
submodules.
2025-10-10 12:55:58 +02:00
Stefan Haller 0904bf9969 Add test demonstrating the problem
When dropping changes to the submodule, we expect it to get rolled back to the
previous version; however, it is removed entirely instead.
2025-10-10 12:55:58 +02:00
Stefan Haller 21483b259c Rename AddFileInWorktree to AddFileInWorktreeOrSubmodule
It works for submodules too.

Also, pass file name and file content explicitly; the existing tests don't care
about these, but when writing tests that do, it makes them easier to understand.
2025-10-10 12:55:58 +02:00
Stefan Haller 8e00bbd939 Show "Log (x of y)" in the title bar when there is more than one branch log command (#4943)
Addresses #4939.
2025-10-10 12:51:54 +02:00
Stefan Haller 5d02cba721 Show "Log (x of y)" in the title bar when there is more than one branch log command 2025-10-10 12:49:42 +02:00
Stefan Haller bf56a61b71 Change condition that determines whether we are showing the dashboard
Doesn't make a difference currently, since the title is either StatusTitle when
the dashboard is showing, or LogTitle when one of the branch logs is showing.
This is going to change in the next commit, though.
2025-10-10 12:49:42 +02:00
Stefan Haller 220cde1376 Fix potential crash when reloading the config after removing a branch log command
When cycling to the last branch log command, and then editing the config to
remove one or more log commands, lazygit would crash with an out of bounds panic
when returning to it. Fix this by resetting the log index to 0 when it is out of
bounds. (I think resetting to 0 is better than clamping, although it doesn't
matter much.)
2025-10-10 12:49:42 +02:00
Stefan Haller 19f88290a2 Change allBranchesLogCmdIndex to an int
I know that uint was chosen to document that it can't be negative (not sure why
the "8" was chosen, though). That's pointless in languages that don't enforce
this though: you can subtract 1 from uint8(0) and you'll get something that
doesn't make sense; that's not any better than getting -1. I'm not a fan of
using unsigned types in general (at least in languages like go or C++), and they
usually just make the code more cumbersome without real benefits.
2025-10-10 12:49:42 +02:00
Stefan Haller 47ca08240b Don't depend on en_US locale to be installed (#4949)
For some of the git commands that lazygit uses, it needs to force the
language to English because it parses git's output and needs to react to
certain things. It used to do this by setting the locale to
`en_US.UTF-8`; some users reported that this locale isn't available on
their systems. Use the "C" locale instead, which achieves the same
result and is guaranteed to be available everywhere.

Fixes #4731.
2025-10-09 13:19:58 +02:00
Stefan Haller 99a83efc34 Set LC_MESSAGES too
I'm not sure this is necessary, but it doesn't hurt.
2025-10-09 13:10:58 +02:00
Stefan Haller b755f52754 Use C locale instead of en_US.UTF-8 to force English language
Some users reported that en_US.UTF-8 is not available on their systems, leading
to warnings in the command log. "C" also forces the language to English, and is
guaranteed to be available everywhere.
2025-10-09 13:10:02 +02:00
Stefan Haller c31b604c9d Document a workaround for using custom pagers on Windows (#4941)
Custom pagers are not supported on Windows, because using a pager in git
requires the git command to run in a PTY, but the PTY package we are
using doesn't support Windows.

However, there's a workaround to use a custom pager by using a
Powershell script configured as an external diff command. Document this
in the `Custom_Pagers.md` document, and export a `LAZYGIT_COLUMNS`
environment variable to allow such scripts to work better.
2025-10-09 10:06:44 +02:00
stk 2abd76eaa3 Add documentation for pager workaround on Windows 2025-10-09 10:04:29 +02:00
stk 43811cdad8 Export a LAZYGIT_COLUMNS variable to "pty" tasks on Windows
This makes it possible to pass it to an external diff command that is
used like a pager. An example for this can be seen in the added
documentation in the next commit.
2025-10-09 10:04:29 +02:00
Stefan Haller 4b59f9855e When entering a commit in path filtering mode, select the filtered path (#4942)
This can be useful if you want to do some operation on the filtered
file, e.g. check it out using `c`, or enter the file to make a custom
patch from it.

Addresses #4935.
2025-10-09 10:00:38 +02:00
Stefan Haller 7fe73c1ee2 When entering a commit in path filtering mode, select the filtered path 2025-10-09 09:58:15 +02:00
Stefan Haller 5811f2945c Extract a InternalTreePathForFilePath helper function 2025-10-09 09:58:10 +02:00
Stefan Haller fb00584f4d Update diff of conflicted file in the main view after conflicts have been resolved (#4945)
When lazygit detects that all conflicts have been resolved, it puts up a
popup asking whether the user wants to continue the merge or rebase.
However, it didn't rerender the main view of the conflicted file, so it
would still show the conflict markers, which is confusing. Add some
logic that updates the main view in this situation, behind the popup.
2025-10-09 09:04:32 +02:00
Stefan Haller 5fad3ab047 Refresh the main view for the current side panel when a popup is showing
The logic in postRefreshUpdate would only rerender the main view if the context
being updated is the current view. This is not the case when a popup is showing;
but we still want to render the main view in that case, behind the popup. This
happens for example when we refresh the Files scope, we determine that all
conflicts have been resolved and show a popup asking to continue the merge or
rebase, but the postRefreshUpdate of the Files context only happens when the
popup is already showing, so we would still see the conflict markers behind the
popup, which is rather confusing.
2025-10-09 09:02:03 +02:00
Stefan Haller 3f30008224 Add merge menu with conflict resolver (#4889)
Implements https://github.com/jesseduffield/lazygit/issues/2026. I also
tried to address issues mentioned in the
https://github.com/jesseduffield/lazygit/pull/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](https://git-scm.com/docs/git-merge-file) for
a 3-way merge with the `--ours`, `--theirs`, and `--union` flags. This
approach avoids the issue mentioned in
https://github.com/jesseduffield/lazygit/discussions/1608#discussioncomment-13002595,
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.
2025-10-09 08:51:04 +02:00
Krystof Gartner 703f053a7e Add merge options menu
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.
2025-10-09 08:45:58 +02:00
Stefan Haller 1f002af06b fix(nix): use nixos-unstable (#4948)
Fixed jesseduffield/lazygit#4947
2025-10-09 08:43:26 +02:00
doprz 610ac68635 fix(nix): use nixos-unstable
Fixed jesseduffield/lazygit#4947
2025-10-08 11:39:07 -05:00
Stefan Haller 60a7d6ac57 Fix normalisedSelectedNodes function (#4920)
The `normalisedSelectedNodes` function in `files_controller.go` had a
bug where it didn't work correctly for the root item (`/`). This PR
fixes that.

We don't have any tests for this bug. Apparently, the functions that use
this (staging and discarding files) also work correctly when they work
on a directory and then on the contained file again, that's why nobody
has noticed yet. I briefly looked into adding unit tests for the
function, but it was more work than I was willing to put into this right
now.
2025-10-08 15:12:03 +02:00
Stefan Haller 302b621b68 Fix: make isDescendentOfSelectedNodes work for the root item
The root item's path is ".", and the path of a file at top level is "./file".
When using GetPath, this gives us "." and "file", respectively, and
isDescendentOfSelectedNodes would return false for these.

Working with the internal paths (i.e. without stripping the leading "./") fixes
this.
2025-10-08 15:09:35 +02:00
Stefan Haller d0c6e27fee Cleanup: move variable assignment out of the loop
It never changes inside this function, so there's no need to recompute it with
every loop iteration.
2025-10-08 15:09:35 +02:00
Stefan Haller 6478bf456b feat(nix): add comprehensive Nix flake (#4826)
## PR Description

This PR adds Nix flake support to provide a reproducible development
environment and build process for `lazygit` contributors, devs, and
users.

Features:
- Nix Flake Configuration: Complete flake setup supporting multiple
architectures
- Development Shell: Pre-configured environment with Go toolchain,
linting tools, and development utilities
- Multiple Entry Points: Support for `nix develop`, `nix run`, and `nix
build`

Closes #3474.
2025-10-08 15:08:44 +02:00
doprzandEveeifyeve 6b8853c027 feat(nix): add comprehensive nix flake
Co-authored-by: Eveeifyeve
2025-10-08 15:05:54 +02:00
Stefan Haller 97762f484f Update Merge conflicts menu keybinding to use p instead of k (#4934) 2025-10-05 10:26:33 +02:00
Marco Zingoni b32b55201e Update resolve conflict using p instead of k 2025-10-05 10:23:03 +02:00
Stefan Haller f45e7ca705 Update go to 1.25 (#4844)
### PR Description
This PR includes 2 changes:
1. Bump golangci-lint version to `v2.4.0` from  `v.2.2.1`
1. Bump Go version to `1.25.0`:
	- Bump Go version to `1.25` in `go.mod`
	- Bump Go version to  `1.25` in `ci.yml`
	- Bump base image to `golang:1.25`  in `Dockerfile`

### Reference
- [Go 1.25 release note](https://tip.golang.org/doc/go1.25)
2025-10-05 10:21:49 +02:00
kyu08 93b8d70209 Update go to 1.25 2025-10-05 10:17:03 +02:00
kyu08 47b1ededf3 Bump golangci-lint to v.2.4.0 from v2.2.1 2025-10-05 10:17:03 +02:00
Stefan Haller 7544de03c0 Give better visual feedback when checking out the previous branch (#4929)
I find the command "Checkout previous branch" quite useful, and I use it
a lot. However, I'm unhappy with the visual feedback, so this PR
improves that a bit.

Previously, the feedback you got when pressing "-" was just a "Checking
out..." status in the bottom line. This was both easy to miss if you are
used to looking for an inline status in the branches panel, and it
didn't provide information about which branch was being checked out,
which can be annoying in very large repos where checking out takes a
while, and you only see at the end if you are now on the right branch.

Improve this by trying to figure out which branch was the previously
checked out one, and then checking it out normally so that you get an
inline status next to it (as if you had pressed space on it). There are
cases where this fails, e.g. when the previously checked out ref was a
detached head, in which case we fall back to the previous behavior.
2025-10-05 10:04:06 +02:00
Stefan Haller e585accd37 Give better feedback when checking out the previous branch
Previously, the feedback you got when pressing "-" was just a "Checking out..."
status in the bottom line. This was both easy to miss if you are used to looking
for an inline status in the branches panel, and it didn't provide information
about which branch was being checked out, which can be annoying in very large
repos where checking out takes a while, and you only see at the end if you are
now on the right branch.

Improve this by trying to figure out which branch was the previously checked out
one, and then checking it out normally so that you get an inline status next to
it (as if you had pressed space on it). There are cases where this fails, e.g.
when the previously checked out ref was a detached head, in which case we fall
back to the previous behavior.
2025-10-02 18:28:17 +02:00
Stefan Haller ba0e2d12da README.md: Update Sponsors (#4897)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action
2025-10-02 17:32:53 +02:00
github-actions[bot] 385f796c90 README.md: Update Sponsors 2025-10-02 15:30:45 +00:00
Stefan Haller 05b6588e39 Don't break line after footnote symbol in commit messages (#4912)
Addresses #4900.
2025-10-02 17:30:32 +02:00
Stefan Haller ef92e30315 Bump gocui
The main change here is https://github.com/jesseduffield/gocui/pull/85, which
avoids breaking lines after footnote symbols in commit messages (e.g. [1]).

[1]: https://www.example.com/this-is-a-really-long-url-that-lazy-git-automatically-wraps-and-is-an-issue
2025-10-02 17:28:13 +02:00
Stefan Haller cb2b5c3738 Hide the cursor when the password prompt is showing (#4878)
The cursor is a bit of a security concern, because it reveals the length
(and typing pattern) of the password.

Resolves #4877.
2025-09-20 11:14:05 +02:00
Stefan Haller 851afe94f5 Hide the cursor when the password prompt is showing
The cursor is a bit of a security concern, because it reveals the length (and
typing pattern) of the password.
2025-09-20 11:11:46 +02:00
Stefan Haller 6941b81992 Don't log the git rev-list command that we use for IsBranchMerged (#4896)
When you delete a branch, we call this function to determine whether we
need to ask for confirmation of not. We don't want to log this, because
it's not a command that a user would normally use as part of this
operation.
2025-09-20 11:11:00 +02:00
Stefan Haller 2b152dcf5e Don't log the git rev-list command that we use for IsBranchMerged
When you delete a branch, we call this function to determine whether we need to
ask for confirmation of not. We don't want to log this, because it's not a
command that a user would normally use as part of this operation.
2025-09-20 11:07:43 +02:00
Stefan Haller bbef570225 Make running with --debug and running integration tests work when built with go 1.25 (#4910)
The go-deadlock package needed to be updated to be compatible with go
1.25.
2025-09-20 11:07:18 +02:00
Stefan Haller f689b32c33 Bump the go-deadlock dependency to v0.3.6
This is needed for compatibility with go 1.25.
2025-09-20 10:51:37 +02:00
Stefan Haller d56a9cf73e Fix MoveCommitsToNewBranch description typo (#4867) 2025-09-18 12:29:23 +02:00
Torben Koch 308a4d5c93 fix command description typo 2025-09-18 12:24:25 +02:00
Stefan Haller 3de12b7fde Fix staging when using the new useExternalDiffGitConfig config (#4895)
This new config was introduced in 0.55, but it made it impossible to
enter the staging view or custom patch building view.

Fixes
https://github.com/jesseduffield/lazygit/pull/4832#issuecomment-3289371491.
2025-09-17 20:39:08 +02:00
Stefan Haller c35e3776dd Fix staging when using the new useExternalDiffGitConfig config
This new config was introduced in 0.55, but it didn't work in the staging view
or custom patch building view.
2025-09-17 20:37:14 +02:00
Stefan Haller b4a38a368a Don't hide keybindings that match the confirmMenu key in the keybindings menu (#4880)
In all other menus besides the keybindings menu it makes sense to hide
keybindings that match the confirmMenu binding. This is important to
make it clear which action will be triggered when you press the key.

In the keybindings menu this is different; the main purpose of that menu
is not to allow triggering commands by their key while the menu is open,
but to serve as a reference for what the keybindings are when it is not
open. Because of this, it is more important to show all bindings in this
menu, even if they conflict with the confirmMenu key.

This fixes a regression introduced in b3a3410a1a.

Fixes #4879.
2025-09-15 17:02:50 +02:00
Stefan Haller 26096d9dd9 Don't hide keybindings that match the confirmMenu key in the keybindings menu
In all other menus besides the keybindings menu it makes sense to hide
keybindings that match the confirmMenu binding. This is important to make it
clear which action will be triggered when you press the key.

In the keybindings menu this is different; the main purpose of that menu is not
to allow triggering commands by their key while the menu is open, but to serve
as a reference for what the keybindings are when it is not open. Because of
this, it is more important to show all bindings in this menu, even if they
conflict with the confirmMenu key.

This fixes a regression introduced in b3a3410a1a.
2025-09-07 12:43:54 +02:00
Jesse Duffield 0d5a410114 Update README.md 2025-09-06 17:11:27 +10:00
Jesse Duffield 0553c00f61 Update README.md 2025-09-06 16:56:50 +10:00
Jesse Duffield d65f638869 Update README.md 2025-09-06 16:51:31 +10:00
Jesse Duffield 2602426d43 README.md: Update Sponsors (#4795)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action
2025-09-06 09:42:07 +10:00
github-actions[bot] a0af7403cc README.md: Update Sponsors 2025-09-05 17:00:11 +00:00
Stefan Haller 900d8be359 Add dev container feature as installation method to README (#4876)
### PR Description

Implements #4874
2025-09-05 18:59:58 +02:00
Henning Lorenzen 4c686fc66e Update README.md 2025-09-05 18:47:51 +02:00
Stefan Haller 2469420efd Update translations from Crowdin (#4873) 2025-09-05 10:50:09 +02:00
Stefan Haller d52ff052bb Update translations from crowdin 2025-09-05 10:47:51 +02:00
Stefan Haller c0805f117e Make it possible to rebind the Confirm keybinding (#4860)
### PR Description

Remapping `keybinding.universal.confirm` from `<enter>` to something
like `y` is currently impossible because the same keybinding is also
used to confirm prompts (e.g. "New branch") and the search prompt. Fix
this by hard-coding enter for those; it doesn't really make sense to use
any other key for prompts.

While at it, add separate bindings for `confirmMenu` and
`confirmSuggestion` for those who would like to have different keys for
these. Of these, `confirmMenu` could be a little tricky because menus
are sometimes used purely as a choice (e.g. in "Amend commit attribute"
or the global keybindings menu), in which case you might want to use
`<enter>`, but other times as a substitute for a confirmation (e.g. for
"Delete branch"), in which case you might want to remap to `y`. I don't
have a great idea what to do about that, to be honest. Feedback welcome.

In this PR we only take care of Confirm, which many people seem to be
concerned about. We might consider doing something similar for Esc, but
it seems less urgent, and I'm out of time now. 😄

This seemingly simple change required some serious refactoring under the
hood, so thorough testing would be good to ensure we didn't break
anything.

Closes #2611
Closes #2767
Closes #3471

Related: #2768
2025-09-05 10:46:33 +02:00
Stefan Haller b3a3410a1a Remove keybindings for menu items that are the same as the menu confirm key
This is needed when remapping the confirmMenu key to, say, "y", and there's a
menu that has an item with a "y" binding. This already worked correctly (confirm
takes precedence, as desired), but it's still confusing to see the item binding.
2025-09-05 10:42:04 +02:00
Stefan Haller b413710d8c Add separate keybindings for confirmMenu and confirmSuggestion
It seems useful to have the flexibility to remap "enter" in confirmations to
"y", but keep "enter" for menus and suggestions (even though we sometimes use
menus as confirmations, but it's still good to give users the choice).
2025-09-05 10:42:04 +02:00
Stefan Haller 81868de264 Hard-code "enter" for adding lines in integration tests
This one doesn't make a difference in practice because we don't remap the key in
tests, but if we would, then this would no longer work correctly. It's just more
correct this way.
2025-09-05 10:42:04 +02:00
Stefan Haller ea7050437d Fix <enter> keybinding for switching to a worktree
The universal.confirm keybinding is the wrong one to use for this, we want
universal.goInto instead. They are both bound to "enter" by default, but when
remapping confirm to "y" we don't want to use that for entering worktrees.
2025-09-05 10:42:04 +02:00
Stefan Haller 6303c64232 Hard-code "enter" for search prompt
Like with the previous commit, it doesn't make sense to use any other key than
enter for the search prompt.
2025-09-05 10:42:04 +02:00
Stefan Haller b006c83181 Hard-code "enter" for editable prompts
Rebinding the universal.confirm keybinding currently doesn't make sense, because
the rebound key would also be used for editable prompts, which means you would
only be able to bind it to a ctrl key (not "y", which is desirable for some
people), and also it would allow you to enter a line feed in a branch name.

Fix this by always using enter for editable prompts.
2025-09-05 10:42:04 +02:00
Stefan Haller 0a64e1abb3 Update cheatsheets for the previous commit
Done in a separate commit because the diff is already so long.
2025-09-05 10:42:03 +02:00
Stefan Haller 5a630aeda1 Refactor: add a separate Prompt view
So far, confirmations and prompts were handled by the same view, context, and
controller, with a bunch of conditional code based on whether the view is
editable. This was more or less ok so far, since it does save a little bit of
code duplication; however, now we need separate views, because we don't have
dynamic keybindings, but we want to map "confirm" to different keys in
confirmations (the "universal.confirm" user config) and prompts (hard-coded to
enter, because it doesn't make sense to customize it there).

It also allows us to get rid of the conditional code, which is a nice benefit;
and the code duplication is actually not *that* bad.
2025-09-05 10:42:03 +02:00
Stefan Haller 94aa1101c9 Fix rare crash in interactive rebase (merge command without comment) (#4872)
This fixes a crash in an interactive rebase when there's a merge command
in the rebase-todo file that doesn't have a comment. I don't know under
what circumstances this can happen; git itself doesn't produce these,
but it is theoretically possible for the user to do this manually by
doing `git rebase --edit-todo`, or third-party tools could do it too.

We had one user report a crash because of this, so it seems worth fixing
it.

Closes #4858.
2025-09-05 10:41:23 +02:00
Stefan Haller ffccfd6e42 Bump git-todo-parser
This fixes a crash in an interactive rebase when there's a merge command in the
rebase-todo file that doesn't have a comment. I don't know under what
circumstances this can happen; git itself doesn't produce these, but it is
theoretically possible for the user to do this manually by doing `git rebase
--edit-todo`, or third-party tools could do it too.

We had one user report a crash because of this, so it seems worth fixing it.
2025-09-05 10:37:20 +02:00
Stefan Haller 2bf2c38e9b docs(VISION): fix "Dicoverability" typo (#4866) 2025-09-03 09:29:49 +02:00
Ricardo Fernández Serrata 83f95bf8fe docs(VISION): fix "Dicoverability" typo 2025-09-03 00:52:38 -04:00
Stefan Haller be0d7a6e73 Log the hash of dropped stashes (#4850)
### PR Description

If you dropped/popped a stash accidentally, the logged hash can help
recover it more easily.

Supersedes #4847.
2025-08-26 14:02:43 +02:00
Stefan Haller 438f5c0eda Log hashes when dropping/popping stashes
If you dropped/popped a stash accidentally, the logged hash can help recover it
more easily.
2025-08-25 19:23:30 +02:00
Stefan Haller 5cb80b8635 Fix command log for stash commands
With the exception of Rename Stash, they would all just log "Stash".
2025-08-25 19:23:30 +02:00
Stefan Haller ee81a8e3c2 Add hash field to models.StashEntry 2025-08-25 19:23:30 +02:00
Stefan Haller 61d5b1646d Cleanup: bring stash loader test up to date
It didn't actually test how it parses the unix time stamps, and the test only
succeeded because the code is lenient against it missing.
2025-08-25 19:23:30 +02:00
Stefan Haller 5a6de1248f Fix dropping range selection of filtered stashes (#4849)
### PR Description

When filtering by file path, dropping a range selection of stashes would
drop the wrong ones if those stashes would be noncontiguous in the
unfiltered list.
2025-08-25 19:21:38 +02:00
Stefan Haller bf419abb8e Fix dropping a range of stashes in filtered mode
To fix the problem described in the previous commit, iterate backwards over the
stashes that we want to delete. This allows us to use their Index field.
2025-08-25 18:41:06 +02:00
Stefan Haller ef3e899f5b Add test demonstrating problem with dropping stashes in filtering mode
As can be seen from the test, it deletes the wrong stashes in this case, because
it assumes the selection is contiguous.
2025-08-25 15:56:39 +02:00
Stefan Haller a0f4614fdc Cleanup: remove unnecessary ToggleRangeSelect
The next RangeSelectDown actually cancels the sticky range select and turns it
into a non-sticky one.
2025-08-25 14:58:48 +02:00
Stefan Haller 1d65196136 Dont auto-forward branches that are checked out in another worktree (#4833)
### PR Description

This is similar to #2957, except that in that case it was about an
explicit fast-forward command, and we solved it by doing the
fast-forward in the other work-tree (so that it would fail if that
worktree had modified files, for example). In this case it is about the
relatively new auto-forward feature (added in v0.50, see #4493), and in
this case we fix it by not even trying to auto-forward any branches that
are checked out by other worktrees.
2025-08-20 10:45:24 +02:00
Stefan Haller 235ab15d7b Don't auto-forward branches that are checked out by another worktree 2025-08-20 10:43:19 +02:00
Stefan Haller 7130cb2947 Add test for auto-forwarding branches that are checked out by another worktree
The test shows that we are currently auto-forwarding branches even if they are
checked out by another worktree; this is quite bad, because when you switch to
that other worktree you'll see that the files that are touched by the fetched
commits are all modified (which we don't test here).
2025-08-20 10:43:19 +02:00
Stefan Haller 8beec9aacc Add a user config for using git's external diff command for paging (#4832)
### PR Description

This is similar to using lazygit's `Git.Paging.ExternalDiffCommand`
config, except that the command is configured in git. This can be done
either with git's `diff.external` config, or through `.gitattributes`,
so it gives a bit more flexibility.

We could consider removing the `Git.Paging.ExternalDiffCommand` config
now, because its functionality is covered by the new config. I decided
to keep it though, because I don't want to make this a breaking change,
and also because some users might want to have the external diff command
only in lazygit but not on the command line.
2025-08-20 10:42:37 +02:00
Stefan Haller 40e989467f Add a user config for using git's external diff command for paging
This is similar to using lazygit's Git.Paging.ExternalDiffCommand config, except
that the command is configured in git. This can be done either with git's
`diff.external` config, or through .gitattributes, so it gives a bit more
flexibility.
2025-08-20 10:39:55 +02:00
Stefan Haller 86934ce1af Remove the git.paging.useConfig option (#4837)
### PR Description

Many people don't understand what this means, which is apparent from the
amount of issues that got filed because of this. Let's get rid of it to
avoid this confusion. People will have to configure their pager twice if
they want to use it both on the command line and in lazygit, which I
think is not a big deal.

See
[here](https://github.com/jesseduffield/lazygit/issues/3704#issuecomment-2330772344)
for more rationale.

Closes #3704.
2025-08-20 10:39:21 +02:00
Stefan Haller 548244c0b7 Add breaking changes notice about git.paging.useConfig option 2025-08-19 11:54:54 +02:00
Stefan Haller 9657b4346f Remove the git.paging.useConfig option
Many people don't understand what this means, which is apparent from the amount
of issues that got filed because of this. Let's get rid of it to avoid this
confusion. People will have to configure their pager twice if they want to use
it both on the command line and in lazygit, which I think is not a big deal.
2025-08-19 10:43:28 +02:00
Stefan Haller 13f97fb259 Use external diff command in stashes panel (#4836)
### PR Description

When we added support for using external diff commands as pagers (mainly
to support difftastic as a pager) in #2868, we only supported this in
the files and commits panels, but not in the stashes panel. This was
forgotten in 6266e19623.
2025-08-19 09:53:50 +02:00
Stefan Haller 89d50fa229 Use external diff command in stashes panel
This was forgotten in 6266e19623.
2025-08-19 09:48:11 +02:00
Stefan Haller 0f785c434a Run label check workflow only on label events and open pr event (#4830)
## **PR Description**
- Currently, `check-required-label` only runs when commits are added to
a PR, even if a maintainer has added labels, so the CI status remains
red until a commit is pushed after labels are added by a maintainer.
- I updated the workflows to run `check-required-label` only on label
events(add/remove) or open PR event.
- This will make PR status updates more accurate, so authors will be
able to see that tests and lint are passing by just checking the PR
status, and maintainers will easily know if the PR is ready for review.
2025-08-18 07:44:43 +02:00
kyu08 e932ea24f2 Run label check workflow only on label events and open pr event 2025-08-18 07:40:38 +02:00
Stefan Haller 1639e17d4e Enhance PR/Issue templates readability (#4829) 2025-08-18 07:39:53 +02:00
kyu08 7853df0f25 Enhance PR/Issue templates readability 2025-08-18 07:37:46 +02:00
Stefan Haller 011ff853b8 Add "CopyToClipboard" command to ConfirmationController (#4810)
## **PR Description**

Sometimes, I want to copy the error message to clipboard to search
google or ask to LLM about the error message.

So I added CopyToClipboard command to `ConfirmationController` and I
have confirmed that this command copies the content of the window to the
clipboard.
2025-08-15 17:20:16 +02:00
kyu08 fc84b77db8 Run go generate ./... 2025-08-15 17:17:06 +02:00
kyu08 3fa5a8eddd Add "CopyToClipboard" command to ConfirmationController 2025-08-15 17:17:06 +02:00
Stefan Haller 0573529f8a Cleanup: remove unnecessary code in test
This was needed in an earlier version of the test, when we asserted the file
content in a more complicated way. It should have been removed in caca62b89e.
2025-08-15 17:17:06 +02:00
Stefan Haller deaa701d41 Update donation wording so that it's clear there's no strings attached (#4827) 2025-08-15 12:05:40 +02:00
Jesse Duffield 7ed946951f Update donation wording so that it's clear there's no strings attached 2025-08-15 19:51:50 +10:00
Stefan Haller 82daedb96d Add support for suspending LazyGit with Ctrl+Z on Unix systems (#4757)
## ✨ Add Ctrl+Z suspend support for LazyGit on Unix-like systems

### 📝 Summary

This PR adds support for suspending LazyGit when the user presses
`Ctrl+Z`, making it behave like common CLI tools (e.g., Vim, less,
htop):

* Pressing `Ctrl+Z` sends a `SIGTSTP` signal to suspend LazyGit.
* After resuming with `fg`, LazyGit redraws and continues working
without hanging.
* During suspension, background routines are paused to avoid running
while LazyGit is stopped.

### ✅ Motivation

Make LazyGit feel more native on Unix-like systems by supporting
standard terminal suspend/resume (Ctrl+Z / fg) behavior.

Closes #3906
2025-08-14 20:47:19 +02:00
Stefan Haller 4bc1ba22c1 Add a breaking changes notice for the changed keybinding 2025-08-14 20:42:21 +02:00
Stefan Haller 6322359392 Update cheatsheets, config docs, and schema 2025-08-14 20:42:20 +02:00
cowboy8625andStefan Haller 0f38d2d61e Implement suspending the app using ctrl-z
Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2025-08-14 20:41:46 +02:00
Stefan Haller af190ad280 Extract methods suspend/resume on Gui struct
These suspend/resume the gocui layer and pause/unpause background refreshes.
2025-08-14 20:40:44 +02:00
cowboy8625andStefan Haller 376ca65807 Rebind redo from <c-z> to Z
This frees up ctrl-z for suspend. Hopefully, redo is not such a frequently used
operation that the change annoys people.

Co-authored-by: Stefan Haller <stefan@haller-berlin.de>
2025-08-14 20:40:44 +02:00
Stefan Haller 5490e559a3 Cleanup: remove stale comment
This should have been removed in 8c574f888c, where I renamed it back to 'gui'
to fix a linter warning.
2025-08-14 20:40:44 +02:00
Stefan Haller bb17072ed8 Clean up build tags
- Remove old-style build tags (the +build syntax has become obsolete with 1.17)
- Remove redundant build tags from '*_windows.go' files
2025-08-14 20:40:44 +02:00
Stefan Haller 5b86716446 Improve display of "esc" keybinding in the keybindings status bar (#4819)
### PR Description

- For popup windows, the status bar would show `Confirm: <enter> |
Close/Cancel: <esc> | Cancel: <esc>`. Omit the second `<esc>`.
- Don't show the `<esc>` binding in other views when it doesn't do
anything.
- Change the text of the `<esc>` label to show what it does, based on
context. This is very helpful because esc can cancel all sorts of
things, and if several of these things are active at once, it is not
obvious which one will be cancelled first.

Supersedes #4808.
2025-08-14 18:02:07 +02:00
Stefan Haller da7121fa87 Don't append "Disabled: " to menu tooltip if disabled reason is empty
In some cases we set a disabled reason but leave the text empty, so that we
don't get an error toast when the item is invoked. In such a case it looks
awkward if there is a tooltip showing "Disabled: " with no following text.
2025-08-14 17:59:38 +02:00
Stefan Haller 0c0c32aec4 Show "Close/Cancel: <esc>" for menus like we do for confirmations
For many menus, just "Close" is fine, but some menus act more like confirmations
(e.g. the menu that appears when you cherry-pick and get conflicts); in this
case, it's good to make it more obvious that hitting esc cancels the whole
thing.
2025-08-14 17:59:38 +02:00
Stefan Haller aeff986450 Show context-specific labels for <esc> in staging and patch building view
Dismissing a range selection is handled by the global escape handler for all
list views, but not for the staging and patch building views, so we need to make
the esc description dynamic for these, too.
2025-08-14 17:59:38 +02:00
Stefan Haller 51066b14b1 Show escape binding for staging and patch building in the status bar 2025-08-14 17:59:38 +02:00
Stefan Haller 5f91b1b48e Move global escape handler to before the keybindings menu handler
The main reason for this is that sometimes the escape key is handled by a local
binding, in which case it appears before the '?' binding in the options status
bar, and other times it is handled by the global controller, in which case it
appeared after. Moving it to before the keybindings menu handler makes it appear
before '?' in both cases.

Also, if the window is too narrow to show all keybindings, the ones that don't
fit will be truncated, and in this case it is more important to show the esc
binding because of its context sensitivity.

This also moves the esc entry up a few positions in the keybindings menu, but I
don't think this matters much.
2025-08-14 17:59:38 +02:00
Stefan Haller a8e44dcea6 Show context-specific labels for the global <esc> binding
WHen several modes are active at the same time, it isn't totally obvious which
one will be cancelled first, so show this in the status bar.
2025-08-14 17:59:38 +02:00
Stefan Haller d8ea83704f Rename ModeStatus.Description to InfoLabel
It is styled and includes a "(Reset)" button, so it's really not a
general-purpose description, but very specific to the Information view.
2025-08-14 17:59:38 +02:00
Stefan Haller 7bf05dfca4 Avoid showing <esc> in options map when it doesn't do anything
The code duplication between Escape and EscapeEnabled is unfortunate, but I
don't see a better way to solve this.
2025-08-14 17:59:38 +02:00
Stefan Haller 0af439ddf5 Cleanup: remove dead code
Cancelling searching (as opposed to filtering) is handled by gocui.
2025-08-14 17:59:38 +02:00
Stefan Haller 4961c4b678 Avoid duplicate key bindings in options map
Sometimes there is a local and a global keybinding for the same key; if both are
configured to be shown in the options map, we should only show the local one
because it takes precedence. This happens for example for <esc> in a popup, or
for <esc> in the focused main view.

Note that this is also a problem in the keybindings menu, and we don't solve
that here.
2025-08-14 17:59:38 +02:00
Stefan Haller f2c2e80a5c Show "Exit back to side panel" in options status bar for focused main view
It's maybe not totally obvious, so let's show it.

Esc now appears twice in the status bar, because the global controller also
appends its generic "Cancel". We'll fix this in the next commit.
2025-08-14 17:59:38 +02:00
Stefan Haller d50e58559e Support Azure DevOps vs-ssh.visualstudio.com SSH remotes as hosting provider (#4822)
**PR Description**  
This PR adds support for parsing **legacy Azure DevOps SSH remotes**
that use the
`vs-ssh.visualstudio.com` host, which is still common in older
repositories or corporate setups.

This PR fixes issue #2667.

Previously, even if users mapped this host to the Azure DevOps provider
via
`configServiceDomains`, URL generation failed with:
```
Failed to parse repo information from url
```
because the Azure DevOps `ServiceDefinition` regex set did not include
this SSH format.

### Changes
- Added a new regex to `azdoServiceDef.regexStrings` to match:
  ```
  git@vs-ssh.visualstudio.com:v3/<org>/<project>/<repo>
  ```
- This allows `getRepoURLFromRemoteURL` to correctly extract `org`,
`project`, and `repo`
  for these remotes.
- Added a test case to `TestGetPullRequestURL` verifying that mapping  
`vs-ssh.visualstudio.com` → `azuredevops:dev.azure.com` produces the
correct PR URL.

### Example
With this config:
```yaml
services:
  'vs-ssh.visualstudio.com': 'azuredevops:dev.azure.com'
```
and remote:
```
git@vs-ssh.visualstudio.com:v3/myorg/myproject/myrepo
```
`GetPullRequestURL("feature/new", "")` now returns:
```
https://dev.azure.com/myorg/myproject/_git/myrepo/pullrequestcreate?sourceRef=feature%2Fnew
```

### Why
- Many users still have remotes in this legacy format.
- This change is **backward-compatible** and does not affect existing
supported remotes.
- It unblocks URL generation for PRs and commits without requiring users
to change their remotes.
- Fixes #2667
2025-08-14 15:23:38 +02:00
🚀 Niklas Arens a44af0685c fix(hosting_service): support Azure DevOps vs-ssh.visualstudio.com SSH remotes 2025-08-14 15:20:56 +02:00
🚀 Niklas Arens 8276d4a50a test: TDD - support Azure DevOps vs-ssh.visualstudio.com SSH remotes 2025-08-14 15:20:56 +02:00
Stefan Haller 0c0f53fa31 Allow filtering the keybindings menu by keybinding (#4821)
- **PR Description**

When filtering the keybindings menu using `/`, typing `@` filters by
keybinding.

Closes #4739.
2025-08-14 15:04:56 +02:00
Stefan Haller a08799ac15 Allow filterable contexts to customize the filter label 2025-08-14 15:02:48 +02:00
Stefan Haller 2ed11336b5 Allow filtering for keybindings by prepending filter string with '@' 2025-08-14 15:02:48 +02:00
Stefan Haller d52d5e6a9e Allow filtered lists to preprocess the filter string
An example for this can be seen in the next commit.

We make this a setter rather than an added constructor argument so that we don't
have to change all those contexts that don't want to make use of this.
2025-08-14 15:02:48 +02:00
Stefan Haller f1c07b3aed Strengthen text expectation
This assertion didn't test anything useful, given that the filtered view already
has two lines. This should have been adapted in 9f0b4d0000 when we added
section headers while filtering.
2025-08-14 15:02:48 +02:00
Stefan Haller 389ff29871 Fix right-alignment of divergence from base branch for branch checked out in a worktree (#4824)
- **PR Description**

We didn't take the worktree icon into account properly, so the
divergence indication was offset to the left by two characters in this
case.

I haven't used worktrees myself so far, so I didn't notice this before.
2025-08-14 14:30:51 +02:00
Stefan Haller e54a8b2ee7 Fix right-alignment of divergence from base branch for branch checked out in a worktree
We didn't take the worktree icon into account properly, so the divergence
indication was offset to the left by two characters in this case.
2025-08-14 14:28:11 +02:00
Stefan Haller 1e75aee37e Add installation with gah (#4820)
- **PR Description**

This PR adds to the documentation a way of installation using
[gah](https://github.com/marverix/gah/).

> gah is an GitHub Releases app installer, that does not require sudo.
It is a simple bash script that downloads the latest release of an app
from GitHub and installs it in `~/.local/bin`. It is designed to be used
with apps that are distributed as a single binary file.
2025-08-13 18:30:47 +02:00
Marek Sierociński 08db9b43be docs: Add installation with gah 2025-08-13 11:35:24 +02:00
Stefan Haller 28ef4a13d2 Pass only Git-tracked Go files to gofumpt (#4809)
## **PR Description**
The below error message is shown when executing `make format` when
`test/_results/demo/worktree_create_from_branches/actual/repo/src/shims.go`
exists (maybe executing integration test produces this file?).

```sh
$ make format
gofumpt -l -w .
test/_results/demo/worktree_create_from_branches/actual/repo/src/shims.go:1:20: expected 'package', found 'EOF'
make: *** [format] Error 2
```

I have confirmed it works without above error when I run `make format`
on this PR's branch.
2025-08-12 11:46:02 +02:00
kyu08 e9cc07d4d3 Pass only Git-tracked Go files to gofumpt 2025-08-12 09:05:08 +02:00
Stefan Haller b1382da9ec Fix delay with flicking through files or commits when git diff is very slow (#4803)
One reason why git diff can be very slow is when "diff.algorithm =
histogram" is being used. In this case, showing a very long single-file
diff can take seconds to load, and you'll see the "loading..." message
in the main view until we got the first lines of the diff to show.
There's nothing really we can do about this delay; however, when
switching to another, shorter file (or commit) while the "loading..."
message is still showing, this switch should be instantaneous. And it
was before 0.54.0, but we broke this in 0.54.0 with 8d7740a5ac
(#4782); now users have to wait for the slow git diff command to output
more text before the switch occurs. To fix this, don't block waiting for
the process to terminate if we just stopped it.

In addition, improve CPU usage by terminating git processes gracefully
(by sending them a TERM signal); this helps keep CPU usage low when
flicking through several of these huge diffs with "diff.algorithm =
histogram", because it avoids having several git processes running in
the background, calculating expensive diffs that we are never going to
show. Unfortunately this is only possible on Linux and Mac, so Windows
users will have to live with the higher CPU usage. The recommended
workaround is to not use "diff.algorithm = histogram".

Fixes #4798.
2025-08-11 18:13:42 +02:00
Stefan Haller e056e33da5 Improve CPU usage when flicking through large diffs
The previous commit already fixed the user-visible lag, but there's still a
problem with multiple background git processes consuming resources calculating
diffs that we are never going to show. Improve this by terminating those
processes (by sending them a TERM signal).

Unfortunately this is only possible on Linux and Mac, so Windows users will have
to live with the higher CPU usage. The recommended workaround is to not use
"diff.algorithm = histogram".
2025-08-11 18:07:21 +02:00
Stefan Haller 898f565116 Fix delay with flicking through files or commits when git diff is very slow
One reason why git diff can be very slow is when "diff.algorithm = histogram" is
being used. In this case, showing a very long single-file diff can take seconds
to load, and you'll see the "loading..." message in the main view until we got
the first lines of the diff to show. There's nothing really we can do about this
delay; however, when switching to another, shorter file (or commit) while the
"loading..." message is still showing, this switch should be instantaneous. And
it was before 0.54.0, but we broke this in 0.54.0 with 8d7740a5ac (#4782); now
users have to wait for the slow git diff command to output more text before the
switch occurs.

To fix this, don't block waiting for the process to terminate if we just stopped
it.
2025-08-11 18:07:21 +02:00
Stefan Haller e5acbed848 Cleanup: move UpdateWindowTitle to platform-specific source files
No need to do a runtime check if we already have the platform-specific files.
2025-08-11 18:07:21 +02:00
Stefan Haller 77a9207f9e Update the badges of golangci-lint and homebrew in README.md (#4807) 2025-08-11 13:56:20 +02:00
Stefan Haller 985013718d Change color of github tag and homebrew badges to blue
Both of these badges have the coloring logic to show stable releases in blue,
and prereleases in orange. The criterion for a prerelease is that the major
version is 0. Now it is debatable whether we consider lazygit "stable", but I
don't feel we are in a preliminary state of the project, working towards the
first final 1.0 release, so I feel blue expresses the state of the project
better.
2025-08-11 13:42:19 +02:00
Stefan Haller 39b4f16e1a Update github tag badge
It doesn't really seem to make a difference, but this is what I get from
shields.io's builder (no idea where the previous .svg extension came from).
2025-08-11 13:37:53 +02:00
Stefan Haller 2864eecd37 Update homebrew link
The old link was broken, it would have to be "Formula/l/lazygit.rb" instead of
"Formula/lazygit.rb".

However, I don't find it very useful to link to the raw formula file, so I
decided to change the link to point to the homebrew web page for lazygit.
2025-08-11 13:36:05 +02:00
kyu08 649f9cd56f Update the badge of golangci-lint in README.md 2025-08-10 10:35:33 +02:00
Stefan Haller 0da27604e4 Update CONTRIBUTING.md to clarify translation contribution process (#4806) 2025-08-10 10:01:43 +02:00
kyu08 c4a684ce53 Update CONTRIBUTING.md to clarify translation contribution process 2025-08-10 09:59:54 +02:00
Stefan Haller 26db25be34 Fix scrollbar in certain popup panels (e.g. the intro message for new users) (#4804)
When showing a confirmation whose text ends with a line feed, we would
make the popup panel one line less tall than it needs to be, so it would
show a scroll bar. One example where this occurred is the very first
popup that users ever see (the "seriously you rock" message for new
users); that's a pretty bad first impression.

This happens because our code to suppress trailing newlines in views
doesn't work for styled text, and the text in confirmations is bold.
This code checks if the last character of the text is a line feed, and
in this case it isn't; the escape sequence for turning bold off comes
after it.

We should really fix this by improving that mechanism, but this would
require some tricky logic, so as a quick fix, trim trailing (and
leading) linefeeds from the text that we display in a confirmation,
before making it bold.
2025-08-09 11:52:04 +02:00
Stefan Haller 09e2bfaf99 Trim trailing newlines when showing confirmations
When showing a confirmation whose text ended with a line feed, we would make the
popup panel one line less tall than it needs to be, so it would show a scroll
bar. One example where this occurred is the very first popup that users ever see
(the "seriously you rock" message for new users); that's a pretty bad first
impression.

This happens because our code to suppress trailing newlines in views doesn't
work for styled text, and the text in confirmations is bold. This code checks if
the last character of the text is a line feed, and in this case it isn't; the
escape sequence for turning bold off comes after it.

We should really fix this by improving that mechanism, but this would require
some tricky logic, so as a quick fix, trim trailing (and leading) linefeeds from
the text that we display in a confirmation, before making it bold.
2025-08-08 20:37:44 +02:00
Stefan Haller a364ee53a8 Cleanup: remove duplicate test case
This is identical to the one right before.
2025-08-08 11:24:43 +02:00
Stefan Haller c0dcf7d0ad Fix the useHunkModeInStagingView hint in the breaking changes message (#4800)
`useHunkSelectionMode` must have been a left-over from an earlier
iteration of the branch where the config had a different name.
2025-08-06 19:31:18 +02:00
Stefan Haller d869c23c1a Fix the useHunkModeInStagingView hint in the breaking changes message 2025-08-06 19:26:00 +02:00
Stefan Haller 0262a8de69 Stop bumping our homebrew formula (#4799)
It is being auto-bumped by homebrew, like most formulae these days, so
no reason for us to do that explicitly; and it actually fails with an
error message, so stop trying.
2025-08-06 19:24:46 +02:00
Stefan Haller 04c2be826b Stop bumping our homebrew formula
It is being auto-bumped by homebrew, like most formulae these days, so no reason
for us to do that explicitly; and it actually fails with an error message, so
stop trying.
2025-08-06 14:02:23 +02:00
Stefan Haller c08903e3ad Stop updating Jesse's homebrew tap (#4797)
The tap is being retired, see
https://github.com/jesseduffield/homebrew-lazygit/pull/3.
2025-08-06 13:30:43 +02:00
Stefan Haller 5a654b1d56 Stop updating Jesse's homebrew tap
The tap is being retired, see
https://github.com/jesseduffield/homebrew-lazygit/pull/3.
2025-08-06 13:28:07 +02:00
Stefan Haller 525edfbc4d Fix temp dir permission problem on multi-user machines (#4796)
In 3a9dbf7341 we created a global /tmp/lazygit/ folder that contains
the temp directories of each running instance, to avoid polluting /tmp
with multiple folders. The problem with that approach was that the
folder was created with 700 permissions, so if multiple users were using
lazygit on the same machine (e.g. a server), all users except the first
one would get fatal errors on startup.

Fix this by creating temp folders containing the user's uid.

Fixes #4793.
2025-08-06 09:48:14 +02:00
Stefan Haller 71813fdda5 Create a user-specific temp dir to avoid permission problems on multi-user machines
In 3a9dbf7341 we created a global /tmp/lazygit/ folder that contains the temp
directories of each running instance, to avoid polluting /tmp with multiple
folders. The problem with that approach was that the folder was created with 700
permissions, so if multiple users were using lazygit on the same machine (e.g. a
server), all users except the first one would get fatal errors on startup.

Fix this by creating temp folders containing the user's uid.
2025-08-05 22:50:22 +02:00
Stefan Haller bcb95bd211 Update Fedora section of the README.md file (#4792)
After discussion from the following PR (
https://github.com/jesseduffield/lazygit/pull/4362 ) I have finally
found time to make a PR to update the Fedora section of the README.md
file. I actually use those instructions myself from few different Fedora
and Amazon Linux 2023 machines.

Here is an example of installing lazygit from the Copr:

```
» sudo dnf install lazygit
Updating and loading repositories:
Repositories loaded.
Package                                                              Arch        Version                                                              Repository                                                  Size
Installing:
 lazygit                                                             x86_64      0.48.0-1.fc41                                                        copr:copr.fedorainfracloud.org:dejan:lazygit            21.5 MiB

Transaction Summary:
 Installing:         1 package

Total size of inbound packages is 6 MiB. Need to download 6 MiB.
After this operation, 22 MiB extra will be used (install 22 MiB, remove 0 B).
Is this ok [y/N]: y
[1/1] lazygit-0:0.48.0-1.fc41.x86_64                                                                                                                                          100% |   3.7 MiB/s |   5.7 MiB |  00m02s
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
[1/1] Total                                                                                                                                                                   100% |   3.7 MiB/s |   5.7 MiB |  00m02s
Running transaction
[1/3] Verify package files                                                                                                                                                    100% |  52.0   B/s |   1.0   B |  00m00s
[2/3] Prepare transaction                                                                                                                                                     100% |   3.0   B/s |   1.0   B |  00m00s
[3/3] Installing lazygit-0:0.48.0-1.fc41.x86_64                                                                                                                               100% |  44.9 MiB/s |  21.5 MiB |  00m00s
Complete!
```
2025-08-05 12:39:03 +02:00
Dejan Lekić 2e0d66ca24 Updated Fedora section in the README.md file. 2025-08-03 16:58:37 +01:00
Stefan Haller 5175798cb1 README.md: Update Sponsors (#4710)
Automated changes by
[create-pull-request](https://github.com/peter-evans/create-pull-request)
GitHub action
2025-08-02 10:34:01 +02:00
github-actions[bot] 84b057c4a1 README.md: Update Sponsors 2025-08-01 09:10:14 +00:00
303 changed files with 7975 additions and 18342 deletions
+11 -9
View File
@@ -6,10 +6,10 @@ labels: bug
assignees: ''
---
**Describe the bug**
### Describe the bug
A clear and concise description of what the bug is.
**To Reproduce**
### To Reproduce
Steps to reproduce the behavior:
1. Go to '...'
@@ -17,20 +17,22 @@ Steps to reproduce the behavior:
3. Scroll down to '....'
4. See error
**Expected behavior**
### Expected behavior
A clear and concise description of what you expected to happen.
**Screenshots**
### Screenshots
If applicable, add screenshots to help explain your problem.
**Version info:**
_Run `lazygit --version` and paste the result here_
_Run `git --version` and paste the result here_
### Version info:
**Additional context**
* _Run `lazygit --version` and paste the result here_
* _Run `git --version` and paste the result here_
### Additional context
Add any other context about the problem here.
**Note:** please try updating to the latest version or [manually building](https://github.com/jesseduffield/lazygit/#manual) the latest `master` to see if the issue still occurs.
> [!NOTE]
> Please try updating to the latest version or [manually building](https://github.com/jesseduffield/lazygit/#manual) the latest `master` to see if the issue still occurs.
<!--
If you want to try and debug this issue yourself, you can run `lazygit --debug` in one terminal panel and `lazygit --logs` in another to view the logs.
+4 -4
View File
@@ -7,8 +7,8 @@ assignees: ''
---
**Topic**
A clear and concise description of what you want to discuss
### Topic
A clear and concise description of what you want to discuss.
**Your thoughts**
What you have to say about the topic
### Your thoughts
What you have to say about the topic.
+4 -4
View File
@@ -6,16 +6,16 @@ labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
### Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
### Describe the solution you'd like
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
### Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
### Additional context
Add any other context or screenshots about the feature request here.
<!--
+2 -2
View File
@@ -1,6 +1,6 @@
- **PR Description**
### PR Description
- **Please check if the PR fulfills these requirements**
### Please check if the PR fulfills these requirements
* [ ] Cheatsheets are up-to-date (run `go generate ./...`)
* [ ] Code has been formatted (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
@@ -0,0 +1,15 @@
name: Check Required Labels
on:
pull_request:
types: [opened, labeled, unlabeled, synchronize]
jobs:
check-required-label:
runs-on: ubuntu-latest
steps:
- uses: mheap/github-action-required-labels@v5
with:
mode: exactly
count: 1
labels: "ignore-for-release, feature, enhancement, bug, maintenance, docs, i18n, performance"
+8 -17
View File
@@ -1,7 +1,7 @@
name: Continuous Integration
env:
GO_VERSION: 1.24
GO_VERSION: 1.25
on:
push:
@@ -32,7 +32,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.24.x
go-version: 1.25.x
- name: Test code
# we're passing -short so that we skip the integration tests, which will be run in parallel below
run: |
@@ -87,7 +87,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.24.x
go-version: 1.25.x
- name: Print git version
run: git --version
- name: Test code
@@ -113,7 +113,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.24.x
go-version: 1.25.x
- name: Build linux binary
run: |
GOOS=linux go build
@@ -140,7 +140,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.24.x
go-version: 1.25.x
- name: Check Vendor Directory
# ensure our vendor directory matches up with our go modules
run: |
@@ -166,24 +166,15 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.24.x
go-version: 1.25.x
- name: Lint
uses: golangci/golangci-lint-action@v8
with:
# If you change this, make sure to also update scripts/golangci-lint-shim.sh
version: v2.2.1
version: v2.4.0
- name: errors
run: golangci-lint run
if: ${{ failure() }}
check-required-label:
runs-on: ubuntu-latest
if: github.ref != 'refs/heads/master'
steps:
- uses: mheap/github-action-required-labels@v5
with:
mode: exactly
count: 1
labels: "ignore-for-release, feature, enhancement, bug, maintenance, docs, i18n, performance"
upload-coverage:
# List all jobs that produce coverage files
needs: [unit-tests, integration-tests]
@@ -196,7 +187,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.24.x
go-version: 1.25.x
- name: Download all coverage artifacts
uses: actions/download-artifact@v4
+1 -8
View File
@@ -139,7 +139,7 @@ jobs:
- name: Setup Go
uses: actions/setup-go@v5
with:
go-version: 1.24.x
go-version: 1.25.x
- name: Run goreleaser
uses: goreleaser/goreleaser-action@v6
@@ -149,10 +149,3 @@ jobs:
args: release --clean
env:
GITHUB_TOKEN: ${{secrets.LAZYGIT_RELEASE_PAT}}
- name: Bump Homebrew formula
uses: dawidd6/action-homebrew-bump-formula@v3
with:
token: ${{secrets.LAZYGIT_RELEASE_PAT}}
formula: lazygit
tag: ${{env.new_tag}}
+6
View File
@@ -28,3 +28,9 @@ __debug_bin*
demo/output/*
coverage.out
# Nix
result
result-*
.direnv
.envrc
+1 -1
View File
@@ -1,6 +1,6 @@
version: "2"
run:
go: "1.24"
go: "1.25"
linters:
enable:
- copyloopvar
-23
View File
@@ -36,26 +36,3 @@ snapshot:
changelog:
use: github-native
sort: asc
brews:
-
# Repository to push the tap to.
repository:
owner: jesseduffield
name: homebrew-lazygit
# Your app's homepage.
# Default is empty.
homepage: 'https://github.com/jesseduffield/lazygit/'
# Your app's description.
# Default is empty.
description: 'A simple terminal UI for git commands, written in Go'
# # Packages your package depends on.
# dependencies:
# - git
# - zsh
# # Packages that conflict with your package.
# conflicts:
# - svn
# - bash
+40 -1
View File
@@ -60,6 +60,31 @@ To run lazygit from within the integrated terminal just go `go run main.go`
This allows you to contribute to Lazygit without needing to install anything on your local machine. The Codespace has all the necessary tools and extensions pre-installed.
## Using Nix for development
If you use Nix, you can leverage the included flake to set up a complete development environment with all necessary dependencies:
```sh
nix develop
```
This will drop you into a development shell that includes:
* Latest Go toolchain
* golangci-lint for code linting
* git and make
You can also build and run lazygit using nix:
```sh
# Build lazygit
nix build
# Run lazygit directly
nix run
```
The nix flake supports multiple architectures (x86_64-linux, aarch64-linux, x86_64-darwin, aarch64-darwin) and provides a consistent development environment across different systems.
## Code of conduct
Please note by participating in this project, you agree to abide by the [code of conduct].
@@ -118,10 +143,24 @@ Lazygit supports [Nerd Fonts](https://www.nerdfonts.com) to render certain icons
## Internationalisation
Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package. If you need to render text to the user, you should add a new field to the TranslationSet struct in `pkg/i18n/english.go` and add the actual content within the `EnglishTranslationSet()` method in the same file. Then you can access via `gui.Tr.YourNewText` (or `self.c.Tr.YourNewText`, etc). Although it is appreciated if you translate the text into other languages, it's not expected of you (google translate will likely do a bad job anyway!).
Boy that's a hard word to spell. Anyway, lazygit is translated into several languages within the pkg/i18n package.
### For developers adding new text
If you need to render text to the user, you should add a new field to the TranslationSet struct in `pkg/i18n/english.go` and add the actual content within the `EnglishTranslationSet()` method in the same file. Then you can access via `gui.Tr.YourNewText` (or `self.c.Tr.YourNewText`, etc).
Note, we use 'Sentence case' for everything (so no 'Title Case' or 'whatever-it's-called-when-there's-no-capital-letters-case')
### For translators
Lazygit translations are managed through [Crowdin](https://crowdin.com/project/lazygit/). If you'd like to contribute translations:
1. Join the Crowdin project at https://crowdin.com/project/lazygit/
2. Select your target language and help translate missing strings
3. The translation files in `pkg/i18n/translations/` are managed by the maintainers - please don't edit them directly
For detailed information about the translation process, including how maintainers sync translations, see `pkg/i18n/translations/README.md`.
## Debugging
The easiest way to debug lazygit is to have two terminal tabs open at once: one for running lazygit (via `go run main.go -debug` in the project root) and one for viewing lazygit's logs (which can be done via `go run main.go --logs` or just `lazygit --logs`).
+1 -1
View File
@@ -2,7 +2,7 @@
# docker build -t lazygit .
# docker run -it lazygit:latest /bin/sh
FROM golang:1.24 as build
FROM golang:1.25 as build
WORKDIR /go/src/github.com/jesseduffield/lazygit/
COPY go.mod go.sum ./
RUN go mod download
+86 -13
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -8,7 +8,7 @@ Lazygit's vision is to be the most enjoyable UI for git.
There are seven (sometimes contradictory) design principles we follow:
- Dicoverability
- Discoverability
- Simplicity
- Safety
- Power
+12
View File
@@ -0,0 +1,12 @@
(import (
let
lock = builtins.fromJSON (builtins.readFile ./flake.lock);
nodeName = lock.nodes.root.inputs.flake-compat;
in
fetchTarball {
url =
lock.nodes.${nodeName}.locked.url
or "https://github.com/edolstra/flake-compat/archive/${lock.nodes.${nodeName}.locked.rev}.tar.gz";
sha256 = lock.nodes.${nodeName}.locked.narHash;
}
) { src = ./.; }).defaultNix
+138 -73
View File
@@ -64,12 +64,15 @@ gui:
# One of: 'margin' (default) | 'jump'
scrollOffBehavior: margin
# The number of spaces per tab; used for everything that's shown in the main view, but probably mostly relevant for diffs.
# Note that when using a pager, the pager has its own tab width setting, so you need to pass it separately in the pager command.
# The number of spaces per tab; used for everything that's shown in the main
# view, but probably mostly relevant for diffs.
# Note that when using a pager, the pager has its own tab width setting, so you
# need to pass it separately in the pager command.
tabWidth: 4
# If true, capture mouse events.
# When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.
# When mouse events are captured, it's a little harder to select text: e.g.
# requiring you to hold the option key when on macOS.
mouseEvents: true
# If true, do not show a warning when amending a commit.
@@ -81,45 +84,61 @@ gui:
# If true, do not show warning when applying/popping the stash
skipStashWarning: false
# If true, do not show a warning when attempting to commit without any staged files; instead stage all unstaged files.
# If true, do not show a warning when attempting to commit without any staged
# files; instead stage all unstaged files.
skipNoStagedFilesWarning: false
# If true, do not show a warning when rewording a commit via an external editor
skipRewordInEditorWarning: false
# Fraction of the total screen width to use for the left side section. You may want to pick a small number (e.g. 0.2) if you're using a narrow screen, so that you can see more of the main section.
# If true, switch to a different worktree without confirmation when checking out
# a branch that is checked out in that worktree
skipSwitchWorktreeOnCheckoutWarning: false
# Fraction of the total screen width to use for the left side section. You may
# want to pick a small number (e.g. 0.2) if you're using a narrow screen, so
# that you can see more of the main section.
# Number from 0 to 1.0.
sidePanelWidth: 0.3333
# If true, increase the height of the focused side window; creating an accordion effect.
# If true, increase the height of the focused side window; creating an accordion
# effect.
expandFocusedSidePanel: false
# The weight of the expanded side panel, relative to the other panels. 2 means
# twice as tall as the other panels. Only relevant if `expandFocusedSidePanel` is true.
# twice as tall as the other panels. Only relevant if `expandFocusedSidePanel`
# is true.
expandedSidePanelWeight: 2
# Sometimes the main window is split in two (e.g. when the selected file has both staged and unstaged changes). This setting controls how the two sections are split.
# Sometimes the main window is split in two (e.g. when the selected file has
# both staged and unstaged changes). This setting controls how the two sections
# are split.
# Options are:
# - 'horizontal': split the window horizontally
# - 'vertical': split the window vertically
# - 'flexible': (default) split the window horizontally if the window is wide enough, otherwise split vertically
# - 'flexible': (default) split the window horizontally if the window is wide
# enough, otherwise split vertically
mainPanelSplitMode: flexible
# How the window is split when in half screen mode (i.e. after hitting '+' once).
# How the window is split when in half screen mode (i.e. after hitting '+'
# once).
# Possible values:
# - 'left': split the window horizontally (side panel on the left, main view on the right)
# - 'left': split the window horizontally (side panel on the left, main view on
# the right)
# - 'top': split the window vertically (side panel on top, main view below)
enlargedSideViewLocation: left
# If true, wrap lines in the staging view to the width of the view. This
# makes it much easier to work with diffs that have long lines, e.g.
# paragraphs of markdown text.
# If true, wrap lines in the staging view to the width of the view. This makes
# it much easier to work with diffs that have long lines, e.g. paragraphs of
# markdown text.
wrapLinesInStagingView: true
# If true, hunk selection mode will be enabled by default when entering the staging view.
# If true, hunk selection mode will be enabled by default when entering the
# staging view.
useHunkModeInStagingView: true
# One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
# One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko'
# | 'ru' | 'pt'
language: auto
# Format used when displaying time e.g. commit time.
@@ -192,11 +211,15 @@ gui:
# If true, show the '5 of 20' footer at the bottom of list views
showListFooter: true
# If true, display the files in the file views as a tree. If false, display the files as a flat list.
# This can be toggled from within Lazygit with the '`' key, but that will not change the default.
# If true, display the files in the file views as a tree. If false, display the
# files as a flat list.
# This can be toggled from within Lazygit with the '`' key, but that will not
# change the default.
showFileTree: true
# If true, add a "/" root item in the file tree representing the root of the repository. It is only added when necessary, i.e. when there is more than one item at top level.
# If true, add a "/" root item in the file tree representing the root of the
# repository. It is only added when necessary, i.e. when there is more than one
# item at top level.
showRootItemInFileTree: true
# If true, show the number of lines changed per file in the Files view
@@ -208,7 +231,9 @@ gui:
# If true, show the command log
showCommandLog: true
# If true, show the bottom line that contains keybinding info and useful buttons. If false, this line will be hidden except to display a loader for an in-progress action.
# If true, show the bottom line that contains keybinding info and useful
# buttons. If false, this line will be hidden except to display a loader for an
# in-progress action.
showBottomLine: true
# If true, show jump-to-window keybindings in window titles.
@@ -219,10 +244,12 @@ gui:
# If empty, do not show icons.
nerdFontsVersion: ""
# If true (default), file icons are shown in the file views. Only relevant if NerdFontsVersion is not empty.
# If true (default), file icons are shown in the file views. Only relevant if
# NerdFontsVersion is not empty.
showFileIcons: true
# Length of author name in (non-expanded) commits view. 2 means show initials only.
# Length of author name in (non-expanded) commits view. 2 means show initials
# only.
commitAuthorShortLength: 2
# Length of author name in expanded commits view. 2 means show initials only.
@@ -243,10 +270,12 @@ gui:
# Whether to split the main window when viewing file changes.
# One of: 'auto' | 'always'
# If 'auto', only split the main window when a file has both staged and unstaged changes
# If 'auto', only split the main window when a file has both staged and unstaged
# changes
splitDiff: auto
# Default size for focused window. Can be changed from within Lazygit with '+' and '_' (but this won't change the default).
# Default size for focused window. Can be changed from within Lazygit with '+'
# and '_' (but this won't change the default).
# One of: 'normal' (default) | 'half' | 'full'
screenMode: normal
@@ -254,7 +283,8 @@ gui:
# One of 'rounded' (default) | 'single' | 'double' | 'hidden' | 'bold'
border: rounded
# If true, show a seriously epic explosion animation when nuking the working tree.
# If true, show a seriously epic explosion animation when nuking the working
# tree.
animateExplosion: true
# Whether to stack UI components on top of each other.
@@ -287,27 +317,36 @@ gui:
# If true, jump to the Files panel after applying a stash
switchToFilesAfterStashApply: true
# If true, when using the panel jump keys (default 1 through 5) and target panel is already active, go to next tab instead
# If true, when using the panel jump keys (default 1 through 5) and target panel
# is already active, go to next tab instead
switchTabsWithPanelJumpKeys: false
# Config relating to git
git:
# Array of pagers. Each entry has the following format:
#
# # Value of the --color arg in the git diff command. Some pagers want
# # this to be set to 'always' and some want it set to 'never'
# colorArg: "always"
#
# # e.g.
# # diff-so-fancy
# # delta --dark --paging=never
# # ydiff -p cat -s --wrap --width={{columnWidth}}
# pager: ""
#
# # e.g. 'difft --color=always'
# externalDiffCommand: ""
#
# # If true, Lazygit will use git's `diff.external` config for paging.
# # The advantage over `externalDiffCommand` is that this can be
# # configured per file type in .gitattributes; see
# # https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver.
# useExternalDiffGitConfig: false
#
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md
paging:
# Value of the --color arg in the git diff command. Some pagers want this to be set to 'always' and some want it set to 'never'
colorArg: always
# e.g.
# diff-so-fancy
# delta --dark --paging=never
# ydiff -p cat -s --wrap --width={{columnWidth}}
pager: ""
# If true, Lazygit will use whatever pager is specified in `$GIT_PAGER`, `$PAGER`, or your *git config*. If the pager ends with something like ` | less` we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager).
useConfig: false
# e.g. 'difft --color=always'
externalDiffCommand: ""
# for more information.
pagers: []
# Config relating to committing
commit:
@@ -322,22 +361,26 @@ git:
# Config relating to merging
merging:
# If true, run merges in a subprocess so that if a commit message is required, Lazygit will not hang
# If true, run merges in a subprocess so that if a commit message is required,
# Lazygit will not hang
# Only applicable to unix users.
manualCommit: false
# Extra args passed to `git merge`, e.g. --no-ff
args: ""
# The commit message to use for a squash merge commit. Can contain "{{selectedRef}}" and "{{currentBranch}}" placeholders.
# The commit message to use for a squash merge commit. Can contain
# "{{selectedRef}}" and "{{currentBranch}}" placeholders.
squashMergeMessage: Squash merge {{selectedRef}} into {{currentBranch}}
# list of branches that are considered 'main' branches, used when displaying commits
# list of branches that are considered 'main' branches, used when displaying
# commits
mainBranches:
- master
- main
# Prefix to use when skipping hooks. E.g. if set to 'WIP', then pre-commit hooks will be skipped when the commit message starts with 'WIP'
# Prefix to use when skipping hooks. E.g. if set to 'WIP', then pre-commit hooks
# will be skipped when the commit message starts with 'WIP'
skipHookPrefix: WIP
# If true, periodically fetch from remote
@@ -346,7 +389,10 @@ git:
# If true, periodically refresh files and submodules
autoRefresh: true
# If not "none", lazygit will automatically fast-forward local branches to match their upstream after fetching. Applies to branches that are not the currently checked out branch, and only to those that are strictly behind their upstream (as opposed to diverged).
# If not "none", lazygit will automatically fast-forward local branches to match
# their upstream after fetching. Applies to branches that are not the currently
# checked out branch, and only to those that are strictly behind their upstream
# (as opposed to diverged).
# Possible values: 'none' | 'onlyMainBranches' | 'allBranches'
autoForwardBranches: onlyMainBranches
@@ -354,25 +400,29 @@ git:
fetchAll: true
# If true, lazygit will automatically stage files that used to have merge
# conflicts but no longer do; and it will also ask you if you want to
# continue a merge or rebase if you've resolved all conflicts. If false, it
# won't do either of these things.
# conflicts but no longer do; and it will also ask you if you want to continue a
# merge or rebase if you've resolved all conflicts. If false, it won't do either
# of these things.
autoStageResolvedConflicts: true
# Command used when displaying the current branch git log in the main window
branchLogCmd: git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --
# Commands used to display git log of all branches in the main window, they will be cycled in order of appearance (array of strings)
# Commands used to display git log of all branches in the main window, they will
# be cycled in order of appearance (array of strings)
allBranchesLogCmds:
- git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium
# If true, git diffs are rendered with the `--ignore-all-space` flag, which ignores whitespace changes. Can be toggled from within Lazygit with `<c-w>`.
# If true, git diffs are rendered with the `--ignore-all-space` flag, which
# ignores whitespace changes. Can be toggled from within Lazygit with `<c-w>`.
ignoreWhitespaceInDiffView: false
# The number of lines of context to show around each diff hunk. Can be changed from within Lazygit with the `{` and `}` keys.
# The number of lines of context to show around each diff hunk. Can be changed
# from within Lazygit with the `{` and `}` keys.
diffContextSize: 3
# The threshold for considering a file to be renamed, in percent. Can be changed from within Lazygit with the `(` and `)` keys.
# The threshold for considering a file to be renamed, in percent. Can be changed
# from within Lazygit with the `(` and `)` keys.
renameSimilarityThreshold: 50
# If true, do not spawn a separate process when using GPG
@@ -400,30 +450,35 @@ git:
# 'topo-order' makes it easier to read the git log graph, but commits may not
# appear chronologically. See https://git-scm.com/docs/
#
# Can be changed from within Lazygit with `Log menu -> Commit sort order` (`<c-l>` in the commits window by default).
# Can be changed from within Lazygit with `Log menu -> Commit sort order`
# (`<c-l>` in the commits window by default).
order: topo-order
# This determines whether the git graph is rendered in the commits panel
# One of 'always' | 'never' | 'when-maximised'
#
# Can be toggled from within lazygit with `Log menu -> Show git graph` (`<c-l>` in the commits window by default).
# Can be toggled from within lazygit with `Log menu -> Show git graph` (`<c-l>`
# in the commits window by default).
showGraph: always
# displays the whole git graph by default in the commits view (equivalent to passing the `--all` argument to `git log`)
# displays the whole git graph by default in the commits view (equivalent to
# passing the `--all` argument to `git log`)
showWholeGraph: false
# How branches are sorted in the local branches view.
# One of: 'date' (default) | 'recency' | 'alphabetical'
# Can be changed from within Lazygit with the Sort Order menu (`s`) in the branches panel.
# Can be changed from within Lazygit with the Sort Order menu (`s`) in the
# branches panel.
localBranchSortOrder: date
# How branches are sorted in the remote branches view.
# One of: 'date' (default) | 'alphabetical'
# Can be changed from within Lazygit with the Sort Order menu (`s`) in the remote branches panel.
# Can be changed from within Lazygit with the Sort Order menu (`s`) in the
# remote branches panel.
remoteBranchSortOrder: date
# When copying commit hashes to the clipboard, truncate them to this
# length. Set to 40 to disable truncation.
# When copying commit hashes to the clipboard, truncate them to this length. Set
# to 40 to disable truncation.
truncateCopiedCommitHashesTo: 12
# Periodic update checks
@@ -447,10 +502,12 @@ refresher:
# If true, show a confirmation popup before quitting Lazygit
confirmOnQuit: false
# If true, exit Lazygit when the user presses escape in a context where there is nothing to cancel/close
# If true, exit Lazygit when the user presses escape in a context where there is
# nothing to cancel/close
quitOnTopLevelReturn: false
# Config relating to things outside of Lazygit like how files are opened, copying to clipboard, etc
# Config relating to things outside of Lazygit like how files are opened,
# copying to clipboard, etc
os:
# Command for editing a file. Should contain "{{filename}}".
edit: ""
@@ -459,8 +516,8 @@ os:
# "{{filename}}", and may optionally contain "{{line}}".
editAtLine: ""
# Same as EditAtLine, except that the command needs to wait until the
# window is closed.
# Same as EditAtLine, except that the command needs to wait until the window is
# closed.
editAtLineAndWait: ""
# Whether lazygit suspends until an edit process returns
@@ -469,12 +526,12 @@ os:
# For opening a directory in an editor
openDirInEditor: ""
# A built-in preset that sets all of the above settings. Supported presets
# are defined in the getPreset function in editor_presets.go.
# A built-in preset that sets all of the above settings. Supported presets are
# defined in the getPreset function in editor_presets.go.
editPreset: ""
# Command for opening a file, as if the file is double-clicked. Should
# contain "{{filename}}", but doesn't support "{{line}}".
# Command for opening a file, as if the file is double-clicked. Should contain
# "{{filename}}", but doesn't support "{{line}}".
open: ""
# Command for opening a link. Should contain "{{link}}".
@@ -488,7 +545,9 @@ os:
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-command-for-copying-to-and-pasting-from-clipboard
readFromClipboardCmd: ""
# A shell startup file containing shell aliases or shell functions. This will be sourced before running any shell commands, so that shell functions are available in the `:` command prompt or even in custom commands.
# A shell startup file containing shell aliases or shell functions. This will be
# sourced before running any shell commands, so that shell functions are
# available in the `:` command prompt or even in custom commands.
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#using-aliases-or-functions-in-shell-commands
shellFunctionsFile: ""
@@ -503,13 +562,15 @@ customCommands: []
services: {}
# What to do when opening Lazygit outside of a git repo.
# - 'prompt': (default) ask whether to initialize a new repo or open in the most recent repo
# - 'prompt': (default) ask whether to initialize a new repo or open in the most
# recent repo
# - 'create': initialize a new repo
# - 'skip': open most recent repo
# - 'quit': exit Lazygit
notARepository: prompt
# If true, display a confirmation when subprocess terminates. This allows you to view the output of the subprocess before returning to Lazygit.
# If true, display a confirmation when subprocess terminates. This allows you to
# view the output of the subprocess before returning to Lazygit.
promptToReturnFromSubprocess: true
# Keybindings
@@ -517,6 +578,7 @@ keybinding:
universal:
quit: q
quit-alt1: <c-c>
suspendApp: <c-z>
return: <esc>
quitWithoutChangingDirectory: Q
togglePanel: <tab>
@@ -556,6 +618,8 @@ keybinding:
select: <space>
goInto: <enter>
confirm: <enter>
confirmMenu: <enter>
confirmSuggestion: <enter>
confirmInEditor: <a-enter>
confirmInEditor-alt: <c-s>
remove: d
@@ -582,8 +646,9 @@ keybinding:
prevTab: '['
nextScreenMode: +
prevScreenMode: _
cyclePagers: '|'
undo: z
redo: <c-z>
redo: Z
filteringMenu: <c-s>
diffingMenu: W
diffingMenu-alt: <c-e>
@@ -616,7 +681,7 @@ keybinding:
viewResetOptions: D
fetch: f
toggleTreeView: '`'
openMergeTool: M
openMergeOptions: M
openStatusFilter: <c-b>
copyFileInfoToClipboard: "y"
collapseAll: '-'
+59 -31
View File
@@ -2,26 +2,29 @@
Lazygit supports custom pagers, [configured](/docs/Config.md) in the config.yml file (which can be opened by pressing `e` in the Status panel).
Support does not extend to Windows users, because we're making use of a package which doesn't have Windows support.
Support does not extend to Windows users, because we're making use of a package which doesn't have Windows support. However, see [below](#emulating-custom-pagers-on-windows) for a workaround.
## Default:
Multiple pagers are supported; you can cycle through them with the `|` key. This can be useful if you usually prefer a particular pager, but want to use a different one for certain kinds of diffs.
Pagers are configured with the `pagers` array in the git section; here's an example for a multi-pager setup:
```yaml
git:
paging:
colorArg: always
useConfig: false
pagers:
- pager: delta --dark --paging=never
- pager: ydiff -p cat -s --wrap --width={{columnWidth}}
colorArg: never
- externalDiffCommand: difft --color=always
```
the `colorArg` key is for whether you want the `--color=always` arg in your `git diff` command. Some pagers want it set to `always`, others want it set to `never`.
The `colorArg` key is for whether you want the `--color=always` arg in your `git diff` command. Some pagers want it set to `always`, others want it set to `never`. The default is `always`, since that's what most pagers need.
## Delta:
```yaml
git:
paging:
colorArg: always
pager: delta --dark --paging=never
pagers:
- pager: delta --dark --paging=never
```
![](https://i.imgur.com/QJpQkF3.png)
@@ -32,9 +35,8 @@ A cool feature of delta is --hyperlinks, which renders clickable links for the l
```yaml
git:
paging:
colorArg: always
pager: diff-so-fancy
pagers:
- pager: diff-so-fancy
```
![](https://i.imgur.com/rjH1TpT.png)
@@ -45,26 +47,15 @@ git:
gui:
sidePanelWidth: 0.2 # gives you more space to show things side-by-side
git:
paging:
colorArg: never
pager: ydiff -p cat -s --wrap --width={{columnWidth}}
pagers:
- colorArg: never
pager: ydiff -p cat -s --wrap --width={{columnWidth}}
```
![](https://i.imgur.com/vaa8z0H.png)
Be careful with this one, I think the homebrew and pip versions are behind master. I needed to directly download the ydiff script to get the no-pager functionality working.
## Using git config
```yaml
git:
paging:
colorArg: always
useConfig: true
```
If you set `useConfig: true`, lazygit will use whatever pager is specified in `$GIT_PAGER`, `$PAGER`, or your *git config*. If the pager ends with something like ` | less` we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager).
## Using external diff commands
Some diff tools can't work as a simple pager like the ones above do, because they need access to the entire diff, so just post-processing git's diff is not enough for them. The most notable example is probably [difftastic](https://difftastic.wilfred.me.uk).
@@ -73,16 +64,53 @@ These can be used in lazygit by using the `externalDiffCommand` config; in the c
```yaml
git:
paging:
externalDiffCommand: difft --color=always
pagers:
- externalDiffCommand: difft --color=always
```
The `colorArg`, `pager`, and `useConfig` options are not used in this case.
The `colorArg` and `pager` options are not used in this case.
You can add whatever extra arguments you prefer for your difftool; for instance
```yaml
git:
paging:
externalDiffCommand: difft --color=always --display=inline --syntax-highlight=off
pagers:
- externalDiffCommand: difft --color=always --display=inline --syntax-highlight=off
```
Instead of setting this command in lazygit's `externalDiffCommand` config, you can also tell lazygit to use the external diff command that is configured in git itself (`diff.external`), by using
```yaml
git:
pagers:
- useExternalDiffGitConfig: true
```
This can be useful if you also want to use it for diffs on the command line, and it also has the advantage that you can configure it per file type in `.gitattributes`; see https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver.
## Emulating custom pagers on Windows
There is a trick to emulate custom pagers on Windows using a Powershell script configured as an external diff command. It's not perfect, but certainly better than nothing. To do this, save the following script as `lazygit-pager.ps1` at a convenient place on your disk:
```pwsh
#!/usr/bin/env pwsh
$old = $args[1].Replace('\', '/')
$new = $args[4].Replace('\', '/')
$path = $args[0]
git diff --no-index --no-ext-diff $old $new
| %{ $_.Replace($old, $path).Replace($new, $path) }
| delta --width=$env:LAZYGIT_COLUMNS
```
Use the pager of your choice with the arguments you like in the last line of the script. Personally I wouldn't want to use lazygit anymore without delta's `--hyperlinks --hyperlinks-file-link-format="lazygit-edit://{path}:{line}"` args, see [above](#delta).
In your lazygit config, use
```yml
git:
pagers:
- externalDiffCommand: "C:/wherever/lazygit-pager.ps1"
```
The main limitation of this approach compared to a "real" pager is that renames are not displayed correctly; they are shown as if they were modifications of the old file. (This affects only the hunk headers; the diff itself is always correct.)
+15 -5
View File
@@ -24,15 +24,17 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` R `` | Refresh | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
| `` + `` | Next screen mode (normal/half/fullscreen) | |
| `` _ `` | Prev screen mode | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
| `` <esc> `` | Cancel | |
| `` ? `` | Open keybindings menu | |
| `` <c-s> `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. |
| `` W `` | View diffing options | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` <c-e> `` | View diffing options | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` q `` | Quit | |
| `` <esc> `` | Cancel | |
| `` <c-z> `` | Suspend the application | |
| `` <c-w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` z `` | Undo | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` <c-z> `` | Redo | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` Z `` | Redo | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
## List panel navigation
@@ -124,6 +126,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|-----|--------|-------------|
| `` <enter> `` | Confirm | |
| `` <esc> `` | Close/Cancel | |
| `` <c-o> `` | Copy to clipboard | |
## Files
@@ -151,13 +154,20 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). |
| `` ` `` | Toggle file tree view | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | Open external merge tool | Run `git mergetool`. |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` f `` | Fetch | Fetch changes from remote. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` 0 `` | Focus main view | |
| `` / `` | Search the current view by text | |
## Input prompt
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | Confirm | |
| `` <esc> `` | Close/Cancel | |
## Local branches
| Key | Action | Info |
@@ -201,7 +211,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` z `` | Undo | Undo last merge conflict resolution. |
| `` e `` | Edit file | Open file in external editor. |
| `` o `` | Open file | Open file in default application. |
| `` M `` | Open external merge tool | Run `git mergetool`. |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` <esc> `` | Return to files panel | |
## Main panel (normal)
@@ -256,7 +266,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | Execute | |
| `` <esc> `` | Close | |
| `` <esc> `` | Close/Cancel | |
| `` / `` | Filter the current view by text | |
## Reflog
+35 -25
View File
@@ -24,15 +24,17 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` R `` | 更新 | Gitの状態を更新します(`git status`、`git branch`などをバックグラウンドで実行してパネルの内容を更新します)。これは`git fetch`を実行しません。 |
| `` + `` | 次の画面モード(通常/半分/全画面) | |
| `` _ `` | 前の画面モード | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
| `` <esc> `` | キャンセル | |
| `` ? `` | キーバインディングメニューを開く | |
| `` <c-s> `` | フィルターオプションを表示 | コミットログのフィルタリングオプションを表示し、フィルタに一致するコミットのみを表示します。 |
| `` W `` | 差分オプションを表示 | 2つのrefの差分に関連するオプションを表示します(例:選択したrefとの差分表示、差分を取るrefの入力、差分方向の反転など)。 |
| `` <c-e> `` | 差分オプションを表示 | 2つのrefの差分に関連するオプションを表示します(例:選択したrefとの差分表示、差分を取るrefの入力、差分方向の反転など)。 |
| `` q `` | 終了 | |
| `` <esc> `` | キャンセル | |
| `` <c-z> `` | Suspend the application | |
| `` <c-w> `` | 空白表示の切り替え | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` z `` | 元に戻す | 最後のgitコマンドを元に戻すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 |
| `` <c-z> `` | やり直す | 最後のgitコマンドをやり直すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 |
| `` Z `` | やり直す | 最後のgitコマンドをやり直すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 |
## リストパネルのナビゲーション
@@ -51,6 +53,13 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` ] `` | 次のタブ | |
| `` [ `` | 前のタブ | |
## Input prompt
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 確認 | |
| `` <esc> `` | 閉じる/キャンセル | |
## コミット
| Key | Action | Info |
@@ -74,19 +83,19 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` B `` | リベース用のベースコミットとしてマーク | 次のリベース用のベースコミットを選択します。ブランチにリベースするとき、ベースコミットより上のコミットのみが持ち込まれます。これは `git rebase --onto` コマンドを使用します。 |
| `` A `` | 修正 | ステージされた変更でコミットを修正します。選択したコミットがHEADコミットの場合、これは `git commit --amend` を実行します。それ以外の場合、コミットはリベースを通じて修正されます。 |
| `` a `` | コミット属性を修正 | コミット作者の設定/リセットまたは共同作者の設定を行います。 |
| `` t `` | 元に戻す | 選択したコミットの変更を逆に適用する、リバートコミットを作成します。 |
| `` t `` | リバート | 選択したコミットの変更を逆に適用する、リバートコミットを作成します。 |
| `` T `` | コミットにタグを付ける | 選択したコミットを指すタグを新規作成します。タグ名とオプションの説明を入力するよう促されます。 |
| `` <c-l> `` | ログオプションを表示 | コミットログのオプションを表示します(例:並び順の変更、Gitグラフの非表示、Gitグラフ全体の表示)。 |
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したコミットをデタッチドヘッド(特定のブランチに属さない状態)としてチェックアウトします。 |
| `` y `` | コミット属性をクリップボードにコピー | コミット属性をクリップボードにコピーします(例:ハッシュ、URL、差分、メッセージ、作者)。 |
| `` o `` | ブラウザでコミットを開く | |
| `` n `` | コミットから新しいブランチを作成 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
| `` * `` | 現在のブランチのコミットを選択 | |
| `` 0 `` | Focus main view | |
| `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | ファイルを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストで検索 | |
@@ -108,7 +117,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` ` `` | ファイルツリービューを切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` - `` | すべてのファイルを折りたたむ | ファイルツリー内のすべてのディレクトリを折りたたみます |
| `` = `` | すべてのファイルを展開 | ファイルツリー内のすべてのディレクトリを展開します |
| `` 0 `` | Focus main view | |
| `` 0 `` | メインビューにフォーカス | |
| `` / `` | 現在のビューをテキストで検索 | |
## コミット概要
@@ -127,13 +136,13 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` y `` | コミット属性をクリップボードにコピー | コミット属性をクリップボードにコピーします(例:ハッシュ、URL、差分、メッセージ、作者)。 |
| `` o `` | ブラウザでコミットを開く | |
| `` n `` | コミットから新しいブランチを作成 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
| `` <c-r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | |
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
| `` * `` | 現在のブランチのコミットを選択 | |
| `` 0 `` | Focus main view | |
| `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | ファイルを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストで検索 | |
@@ -161,7 +170,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` d `` | 削除 | スタッシュリストからスタッシュエントリを削除します。 |
| `` n `` | 新しいブランチ | 選択したスタッシュエントリから新しいブランチを作成します。これは、スタッシュエントリが作成されたコミットをgitがチェックアウトし、そのコミットから新しいブランチを作成した後、スタッシュエントリを追加のコミットとして新しいブランチに適用することで機能します。 |
| `` r `` | スタッシュの名前を変更 | |
| `` 0 `` | Focus main view | |
| `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | ファイルを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | |
@@ -175,14 +184,14 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` u `` | 更新を確認 | |
| `` <enter> `` | 最近のリポジトリをチェックアウト | |
| `` a `` | ブランチログの表示モードを順に切り替え | |
| `` 0 `` | Focus main view | |
| `` 0 `` | メインビューにフォーカス | |
## セカンダリ
| Key | Action | Info |
|-----|--------|-------------|
| `` <tab> `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 |
| `` <esc> `` | Exit back to side panel | |
| `` <esc> `` | サイドパネルに戻る | |
| `` / `` | 現在のビューをテキストで検索 | |
## タグ
@@ -196,7 +205,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` P `` | タグをプッシュ | 選択したタグをリモートにプッシュします。リモートを選択するよう促されます。 |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
| `` 0 `` | Focus main view | |
| `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | コミットを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | |
@@ -227,11 +236,11 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` D `` | リセット | 作業ツリーのリセットオプション(例:作業ツリーの完全破棄)を表示します。 |
| `` ` `` | ファイルツリービューを切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
| `` M `` | 外部マージツールを開く | `git mergetool`を実行します。 |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` f `` | フェッチ | リモートから変更をフェッチします。 |
| `` - `` | すべてのファイルを折りたたむ | ファイルツリー内のすべてのディレクトリを折りたたみます |
| `` = `` | すべてのファイルを展開 | ファイルツリー内のすべてのディレクトリを展開します |
| `` 0 `` | Focus main view | |
| `` 0 `` | メインビューにフォーカス | |
| `` / `` | 現在のビューをテキストで検索 | |
## メインパネル(ステージング)
@@ -241,7 +250,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` <left> `` | 前のハンクに移動 | |
| `` <right> `` | 次のハンクに移動 | |
| `` v `` | 範囲選択を切り替え | |
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. |
| `` a `` | ハンクの選択を切り替える | Toggle line-by-line vs. hunk selection mode. |
| `` <c-o> `` | 選択したテキストをクリップボードにコピー | |
| `` <space> `` | ステージ | 選択された部分のステージ / アンステージを切り替えます。 |
| `` d `` | 破棄 | ステージされていない変更が選択されている場合、`git reset`を使用して変更を破棄します。ステージされた変更が選択されている場合、変更をアンステージします。 |
@@ -263,7 +272,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` <left> `` | 前のハンクに移動 | |
| `` <right> `` | 次のハンクに移動 | |
| `` v `` | 範囲選択を切り替え | |
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. |
| `` a `` | ハンクの選択を切り替える | Toggle line-by-line vs. hunk selection mode. |
| `` <c-o> `` | 選択したテキストをクリップボードにコピー | |
| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
| `` e `` | ファイルを編集 | 外部エディタでファイルを開きます。 |
@@ -284,7 +293,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` z `` | 元に戻す | 最後のマージコンフリクト解決を元に戻します。 |
| `` e `` | ファイルを編集 | 外部エディタでファイルを開きます。 |
| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
| `` M `` | 外部マージツールを開く | `git mergetool`を実行します。 |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` <esc> `` | ファイルパネルに戻る | |
## メインパネル(通常)
@@ -294,7 +303,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` mouse wheel down (fn+up) `` | 下にスクロール | |
| `` mouse wheel up (fn+down) `` | 上にスクロール | |
| `` <tab> `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 |
| `` <esc> `` | Exit back to side panel | |
| `` <esc> `` | サイドパネルに戻る | |
| `` / `` | 現在のビューをテキストで検索 | |
## メニュー
@@ -302,7 +311,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 実行 | |
| `` <esc> `` | 閉じる | |
| `` <esc> `` | 閉じる/キャンセル | |
| `` / `` | 現在のビューをテキストでフィルタリング | |
## リフログ
@@ -314,13 +323,13 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` y `` | コミット属性をクリップボードにコピー | コミット属性をクリップボードにコピーします(例:ハッシュ、URL、差分、メッセージ、作者)。 |
| `` o `` | ブラウザでコミットを開く | |
| `` n `` | コミットから新しいブランチを作成 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
| `` <c-r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | |
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
| `` * `` | 現在のブランチのコミットを選択 | |
| `` 0 `` | Focus main view | |
| `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | コミットを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | |
@@ -350,7 +359,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` s `` | 並び順 | |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
| `` 0 `` | Focus main view | |
| `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | コミットを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | |
@@ -363,12 +372,12 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` i `` | git-flowオプションを表示 | |
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択した項目をチェックアウトします。 |
| `` n `` | 新しいブランチ | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` o `` | プルリクエストを作成 | |
| `` O `` | プルリクエスト作成オプションを表示 | |
| `` <c-y> `` | プルリクエストURLをクリップボードにコピー | |
| `` c `` | 名前でチェックアウト | 名前でチェックアウトします。入力ボックスに「-」を入力すると、最後のブランチをチェックアウトすることができます。 |
| `` - `` | Checkout previous branch | |
| `` - `` | 直前のブランチにチェックアウト | |
| `` F `` | 強制チェックアウト | 選択したブランチを強制的にチェックアウトします。これにより、選択したブランチをチェックアウトする前にワーキングディレクトリ内のすべてのローカル変更が破棄されます。 |
| `` d `` | 削除 | ローカル/リモートブランチの削除オプションを表示します。 |
| `` r `` | リベース | チェックアウトしたブランチを選択したブランチ上にリベースします。 |
@@ -380,7 +389,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
| `` R `` | ブランチ名を変更 | |
| `` u `` | アップストリームオプションを表示 | ブランチのアップストリームに関連するオプションを表示します(例:アップストリームの設定/解除やアップストリームへのリセット)。 |
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
| `` 0 `` | Focus main view | |
| `` 0 `` | メインビューにフォーカス | |
| `` <enter> `` | コミットを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | |
@@ -401,3 +410,4 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
|-----|--------|-------------|
| `` <enter> `` | 確認 | |
| `` <esc> `` | 閉じる/キャンセル | |
| `` <c-o> `` | クリップボードにコピー | |
+17 -7
View File
@@ -24,15 +24,17 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` R `` | 새로고침 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
| `` + `` | 다음 스크린 모드 (normal/half/fullscreen) | |
| `` _ `` | 이전 스크린 모드 | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
| `` <esc> `` | 취소 | |
| `` ? `` | 매뉴 열기 | |
| `` <c-s> `` | View filter-by-path options | View options for filtering the commit log, so that only commits matching the filter are shown. |
| `` W `` | Diff 메뉴 열기 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` <c-e> `` | Diff 메뉴 열기 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` q `` | 종료 | |
| `` <esc> `` | 취소 | |
| `` <c-z> `` | Suspend the application | |
| `` <c-w> `` | 공백문자를 Diff 뷰에서 표시 여부 전환 | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` z `` | 되돌리기 (reflog) (실험적) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` <c-z> `` | 다시 실행 (reflog) (실험적) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` Z `` | 다시 실행 (reflog) (실험적) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
## List panel navigation
@@ -51,6 +53,13 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` ] `` | 이전 탭 | |
| `` [ `` | 다음 탭 | |
## Input prompt
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 확인 | |
| `` <esc> `` | 닫기/취소 | |
## Reflog
| Key | Action | Info |
@@ -128,7 +137,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 실행 | |
| `` <esc> `` | 닫기 | |
| `` <esc> `` | 닫기/취소 | |
| `` / `` | Filter the current view by text | |
## 메인 패널 (Merging)
@@ -144,7 +153,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` z `` | 되돌리기 | Undo last merge conflict resolution. |
| `` e `` | 파일 편집 | Open file in external editor. |
| `` o `` | 파일 닫기 | Open file in default application. |
| `` M `` | Git mergetool를 열기 | Run `git mergetool`. |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` <esc> `` | 파일 목록으로 돌아가기 | |
## 메인 패널 (Normal)
@@ -188,7 +197,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` <esc> `` | 파일 목록으로 돌아가기 | |
| `` <tab> `` | 패널 전환 | Switch to other view (staged/unstaged changes). |
| `` E `` | Edit hunk | Edit selected hunk in external editor. |
| `` c `` | 커밋 변경내용 | Commit staged changes. |
| `` c `` | 커밋 변경내용 | 스테이징된 변경 사항 커밋. |
| `` w `` | Commit changes without pre-commit hook | |
| `` C `` | Git 편집기를 사용하여 변경 내용을 커밋합니다. | |
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
@@ -370,7 +379,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` <space> `` | Staged 전환 | Toggle staged for selected file. |
| `` <c-b> `` | 파일을 필터하기 (Staged/unstaged) | |
| `` y `` | 클립보드에 복사 | |
| `` c `` | 커밋 변경내용 | Commit staged changes. |
| `` c `` | 커밋 변경내용 | 스테이징된 변경 사항 커밋. |
| `` w `` | Commit changes without pre-commit hook | |
| `` A `` | 마지맛 커밋 수정 | |
| `` C `` | Git 편집기를 사용하여 변경 내용을 커밋합니다. | |
@@ -388,7 +397,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` D `` | 초기화 | View reset options for working tree (e.g. nuking the working tree). |
| `` ` `` | 파일 트리뷰로 전환 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | Git mergetool를 열기 | Run `git mergetool`. |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` f `` | Fetch | Fetch changes from remote. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
@@ -401,3 +410,4 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|-----|--------|-------------|
| `` <enter> `` | 확인 | |
| `` <esc> `` | 닫기/취소 | |
| `` <c-o> `` | 클립보드에 복사 | |
+14 -4
View File
@@ -24,15 +24,17 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` R `` | Verversen | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
| `` + `` | Volgende scherm modus (normaal/half/groot) | |
| `` _ `` | Vorige scherm modus | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
| `` <esc> `` | Annuleren | |
| `` ? `` | Open menu | |
| `` <c-s> `` | Bekijk scoping opties | View options for filtering the commit log, so that only commits matching the filter are shown. |
| `` W `` | Open diff menu | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` <c-e> `` | Open diff menu | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` q `` | Quit | |
| `` <esc> `` | Annuleren | |
| `` <c-z> `` | Suspend the application | |
| `` <c-w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` z `` | Ongedaan maken (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` <c-z> `` | Redo (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` Z `` | Redo (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
## Lijstpaneel navigatie
@@ -77,7 +79,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). |
| `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | Open external merge tool | Run `git mergetool`. |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` f `` | Fetch | Fetch changes from remote. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
@@ -90,6 +92,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|-----|--------|-------------|
| `` <enter> `` | Bevestig | |
| `` <esc> `` | Sluiten | |
| `` <c-o> `` | Copy to clipboard | |
## Branches
@@ -188,6 +191,13 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` w `` | View worktree options | |
| `` / `` | Start met zoeken | |
## Input prompt
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | Bevestig | |
| `` <esc> `` | Sluiten | |
## Menu
| Key | Action | Info |
@@ -209,7 +219,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` z `` | Ongedaan maken | Undo last merge conflict resolution. |
| `` e `` | Verander bestand | Open file in external editor. |
| `` o `` | Open bestand | Open file in default application. |
| `` M `` | Open external merge tool | Run `git mergetool`. |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` <esc> `` | Ga terug naar het bestanden paneel | |
## Normaal
+15 -5
View File
@@ -24,15 +24,17 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
| `` R `` | Odśwież | Odśwież stan git (tj. uruchom `git status`, `git branch`, itp. w tle, aby zaktualizować zawartość paneli). To nie uruchamia `git fetch`. |
| `` + `` | Następny tryb ekranu (normalny/półpełny/pełnoekranowy) | |
| `` _ `` | Poprzedni tryb ekranu | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
| `` <esc> `` | Anuluj | |
| `` ? `` | Otwórz menu przypisań klawiszy | |
| `` <c-s> `` | Pokaż opcje filtrowania | Pokaż opcje filtrowania dziennika commitów, tak aby pokazywane były tylko commity pasujące do filtra. |
| `` W `` | Pokaż opcje różnicowania | Pokaż opcje dotyczące różnicowania dwóch refów, np. różnicowanie względem wybranego refa, wprowadzanie refa do różnicowania i odwracanie kierunku różnic. |
| `` <c-e> `` | Pokaż opcje różnicowania | Pokaż opcje dotyczące różnicowania dwóch refów, np. różnicowanie względem wybranego refa, wprowadzanie refa do różnicowania i odwracanie kierunku różnic. |
| `` q `` | Wyjdź | |
| `` <esc> `` | Anuluj | |
| `` <c-z> `` | Suspend the application | |
| `` <c-w> `` | Przełącz białe znaki | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` z `` | Cofnij | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby cofnąć ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. |
| `` <c-z> `` | Ponów | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby ponowić ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. |
| `` Z `` | Ponów | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby ponowić ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. |
## Nawigacja panelu listy
@@ -124,6 +126,13 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
| `` <esc> `` | Wyjdź z budowniczego niestandardowej łatki | |
| `` / `` | Szukaj w bieżącym widoku po tekście | |
## Input prompt
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | Potwierdź | |
| `` <esc> `` | Zamknij/Anuluj | |
## Lokalne gałęzie
| Key | Action | Info |
@@ -159,7 +168,7 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | Wykonaj | |
| `` <esc> `` | Zamknij | |
| `` <esc> `` | Zamknij/Anuluj | |
| `` / `` | Filtruj bieżący widok po tekście | |
## Panel główny (normalny)
@@ -185,7 +194,7 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
| `` z `` | Cofnij | Cofnij ostatnie rozwiązanie konfliktu scalania. |
| `` e `` | Edytuj plik | Otwórz plik w zewnętrznym edytorze. |
| `` o `` | Otwórz plik | Otwórz plik w domyślnej aplikacji. |
| `` M `` | Otwórz zewnętrzne narzędzie scalania | Uruchom `git mergetool`. |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` <esc> `` | Wróć do panelu plików | |
## Panel główny (zatwierdzanie)
@@ -216,6 +225,7 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
|-----|--------|-------------|
| `` <enter> `` | Potwierdź | |
| `` <esc> `` | Zamknij/Anuluj | |
| `` <c-o> `` | Kopiuj do schowka | |
## Pliki
@@ -243,7 +253,7 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
| `` D `` | Reset | Wyświetl opcje resetu dla drzewa roboczego (np. zniszczenie drzewa roboczego). |
| `` ` `` | Przełącz widok drzewa plików | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` <c-t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
| `` M `` | Otwórz zewnętrzne narzędzie scalania | Uruchom `git mergetool`. |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` f `` | Pobierz | Pobierz zmiany ze zdalnego serwera. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
+20 -10
View File
@@ -24,15 +24,17 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` R `` | Atualizar | Atualize o estado do git (ou seja, execute `git status`, `git branch`, etc em segundo plano para atualizar o conteúdo de painéis). Isso não executa `git fetch`. |
| `` + `` | Next screen mode (normal/half/fullscreen) | |
| `` _ `` | Prev screen mode | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
| `` <esc> `` | Cancelar | |
| `` ? `` | Open keybindings menu | |
| `` <c-s> `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. |
| `` W `` | View diffing options | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` <c-e> `` | View diffing options | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` q `` | Sair | |
| `` <esc> `` | Cancelar | |
| `` <c-z> `` | Suspend the application | |
| `` <c-w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` z `` | Desfazer | O reflog será usado para determinar qual comando git para executar para desfazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. |
| `` <c-z> `` | Refazer | O reflog será usado para determinar qual comando git para executar para refazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. |
| `` Z `` | Refazer | O reflog será usado para determinar qual comando git para executar para refazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. |
## List panel navigation
@@ -77,7 +79,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` D `` | Restaurar | Opções de redefinição de exibição para árvore de trabalho (por exemplo, nukando a árvore de trabalho). |
| `` ` `` | Alternar exibição de árvore de arquivo | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` <c-t> `` | Abrir ferramenta de diff externa (git difftool) | |
| `` M `` | Abrir ferramenta de merge externa | Execute `git mergetool`. |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` f `` | Buscar | Buscar alterações do controle remoto. |
| `` - `` | Recolher todos os arquivos | Recolher todos os diretórios na árvore de arquivos |
| `` = `` | Expandir todos os arquivos | Expandir todos os diretórios na árvore do arquivo |
@@ -92,12 +94,12 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` i `` | Exibir opções do git-flow | |
| `` <space> `` | Verificar | Checar item selecionado |
| `` n `` | Nova branch | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` o `` | Create pull request | |
| `` O `` | View create pull request options | |
| `` <c-y> `` | Copiar URL do pull request para área de transferência | |
| `` c `` | Checar por nome | Checar por nome. Na caixa de entrada você pode inserir '-' para trocar para a última branch |
| `` - `` | Checkout previous branch | |
| `` - `` | Checkout da branch anterior | |
| `` F `` | Forçar checagem | Forçar checagem da branch selecionada. Isso irá descartar todas as mudanças no seu diretório de trabalho antes cheque a branch selecionada |
| `` d `` | Apagar | Ver opções de exclusão para a branch local/remoto. |
| `` r `` | Refazer | Refazer a branch checada na branch selecionada |
@@ -183,7 +185,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` o `` | Open commit in browser | |
| `` n `` | Create new branch off of commit | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. |
| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. |
| `` <c-t> `` | Abrir ferramenta de diff externa (git difftool) | |
@@ -199,6 +201,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|-----|--------|-------------|
| `` <enter> `` | Confirmar | |
| `` <esc> `` | Fechar/Cancelar | |
| `` <c-o> `` | Copy to clipboard | |
## Etiquetas
@@ -216,12 +219,19 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## Input prompt
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | Confirmar | |
| `` <esc> `` | Fechar/Cancelar | |
## Menu
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | Executar | |
| `` <esc> `` | Fechar | |
| `` <esc> `` | Fechar/Cancelar | |
| `` / `` | Filter the current view by text | |
## Painel Principal (Normal)
@@ -269,7 +279,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` z `` | Desfazer | Desfazer resolução de conflitos de última mesclagem. |
| `` e `` | Editar arquivo | Abrir arquivo no editor externo. |
| `` o `` | Abrir arquivo | Abrir arquivo no aplicativo padrão. |
| `` M `` | Abrir ferramenta de merge externa | Execute `git mergetool`. |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` <esc> `` | Retornar ao painel de arquivos | |
## Painel principal (patch build)
@@ -296,7 +306,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` o `` | Open commit in browser | |
| `` n `` | Create new branch off of commit | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. |
| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. |
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | |
@@ -360,7 +370,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` o `` | Open commit in browser | |
| `` n `` | Create new branch off of commit | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. |
| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. |
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | |
+18 -8
View File
@@ -24,15 +24,17 @@ _Связки клавиш_
| `` R `` | Обновить | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
| `` + `` | Следующий режим экрана (нормальный/полуэкранный/полноэкранный) | |
| `` _ `` | Предыдущий режим экрана | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
| `` <esc> `` | Отменить | |
| `` ? `` | Открыть меню | |
| `` <c-s> `` | Просмотреть параметры фильтрации по пути | View options for filtering the commit log, so that only commits matching the filter are shown. |
| `` W `` | Открыть меню сравнении | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` <c-e> `` | Открыть меню сравнении | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` q `` | Выйти | |
| `` <esc> `` | Отменить | |
| `` <c-z> `` | Suspend the application | |
| `` <c-w> `` | Переключить отображение изменении пробелов в просмотрщике сравнении | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` z `` | Отменить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git запустить, чтобы отменить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. |
| `` <c-z> `` | Повторить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git нужно запустить, чтобы повторить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. |
| `` Z `` | Повторить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git нужно запустить, чтобы повторить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. |
## Навигация по панели списка
@@ -51,6 +53,13 @@ _Связки клавиш_
| `` ] `` | Следующая вкладка | |
| `` [ `` | Предыдущая вкладка | |
## Input prompt
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | Подтвердить | |
| `` <esc> `` | Закрыть/отменить | |
## Worktrees
| Key | Action | Info |
@@ -114,7 +123,7 @@ _Связки клавиш_
| `` z `` | Отменить | Undo last merge conflict resolution. |
| `` e `` | Редактировать файл | Open file in external editor. |
| `` o `` | Открыть файл | Open file in default application. |
| `` M `` | Открыть внешний инструмент слияния (git mergetool) | Run `git mergetool`. |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` <esc> `` | Вернуться к панели файлов | |
## Главная панель (сборка патчей)
@@ -144,7 +153,7 @@ _Связки клавиш_
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-r> `` | Сбросить отобранную (скопированную | cherry-picked) выборку коммитов | |
| `` <c-r> `` | Сбросить отобранную (скопированную \| cherry-picked) выборку коммитов | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | |
@@ -157,7 +166,7 @@ _Связки клавиш_
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | Скопировать hash коммита в буфер обмена | |
| `` <c-r> `` | Сбросить отобранную (скопированную | cherry-picked) выборку коммитов | |
| `` <c-r> `` | Сбросить отобранную (скопированную \| cherry-picked) выборку коммитов | |
| `` b `` | Просмотреть параметры бинарного поиска | |
| `` s `` | Объединить коммиты (Squash) | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
| `` f `` | Объединить несколько коммитов в один отбросив сообщение коммита (Fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
@@ -227,7 +236,7 @@ _Связки клавиш_
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | Выполнить | |
| `` <esc> `` | Закрыть | |
| `` <esc> `` | Закрыть/отменить | |
| `` / `` | Filter the current view by text | |
## Панель Подтверждения
@@ -236,6 +245,7 @@ _Связки клавиш_
|-----|--------|-------------|
| `` <enter> `` | Подтвердить | |
| `` <esc> `` | Закрыть/отменить | |
| `` <c-o> `` | Copy to clipboard | |
## Подкоммиты
@@ -249,7 +259,7 @@ _Связки клавиш_
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-r> `` | Сбросить отобранную (скопированную | cherry-picked) выборку коммитов | |
| `` <c-r> `` | Сбросить отобранную (скопированную \| cherry-picked) выборку коммитов | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | |
@@ -381,7 +391,7 @@ _Связки клавиш_
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). |
| `` ` `` | Переключить вид дерева файлов | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | Открыть внешний инструмент слияния (git mergetool) | Run `git mergetool`. |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` f `` | Получить изменения | Fetch changes from remote. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
+14 -4
View File
@@ -24,15 +24,17 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` R `` | 刷新 | 刷新git状态(即在后台上运行`git status`,`git branch`等命令以更新面板内容) 不会运行`git fetch` |
| `` + `` | 下一屏模式(正常/半屏/全屏) | |
| `` _ `` | 上一屏模式 | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
| `` <esc> `` | 取消 | |
| `` ? `` | 打开菜单 | |
| `` <c-s> `` | 查看按路径过滤选项 | 查看用于过滤提交日志的选项,以便仅显示与过滤器匹配的提交。 |
| `` W `` | 打开 diff 菜单 | 查看与比较两个引用相关的选项,例如与选定的 ref 进行比较,输入要比较的 ref,然后反转比较方向。 |
| `` <c-e> `` | 打开 diff 菜单 | 查看与比较两个引用相关的选项,例如与选定的 ref 进行比较,输入要比较的 ref,然后反转比较方向。 |
| `` q `` | 退出 | |
| `` <esc> `` | 取消 | |
| `` <c-z> `` | Suspend the application | |
| `` <c-w> `` | 切换是否在差异视图中显示空白字符差异 | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` z `` | 撤销 | Reflog将用于确定运行哪个git命令来撤消最后一个git命令。这并不包括对工作树的更改,只考虑提交。 |
| `` <c-z> `` | 重做 | Reflog将用于确定运行哪个git命令来重做上一个git命令。这并不包括对工作树的更改,只考虑提交。 |
| `` Z `` | 重做 | Reflog将用于确定运行哪个git命令来重做上一个git命令。这并不包括对工作树的更改,只考虑提交。 |
## 列表面板导航
@@ -51,6 +53,13 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` ] `` | 下一个标签 | |
| `` [ `` | 上一个标签 | |
## Input prompt
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 确认 | |
| `` <esc> `` | 关闭 | |
## Reflog
| Key | Action | Info |
@@ -208,7 +217,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` D `` | 重置 | 查看工作树的重置选项(例如:清除工作树)。 |
| `` ` `` | 切换文件树视图 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` M `` | 打开外部合并工具(git mergetool) | 执行 `git mergetool`. |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` f `` | 抓取 | 从远程获取变更 |
| `` - `` | 折叠全部文件 | 折叠文件树中的全部目录 |
| `` = `` | 展开全部文件 | 展开文件树中的全部目录 |
@@ -297,7 +306,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` z `` | 撤销 | 撤消上次合并冲突解决 |
| `` e `` | 编辑文件 | 使用外部编辑器打开文件 |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` M `` | 打开外部合并工具(git mergetool) | 执行 `git mergetool`. |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` <esc> `` | 返回文件面板 | |
## 正在暂存
@@ -349,6 +358,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|-----|--------|-------------|
| `` <enter> `` | 确认 | |
| `` <esc> `` | 关闭 | |
| `` <c-o> `` | 复制到剪贴板 | |
## 菜单
+15 -5
View File
@@ -24,15 +24,17 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
| `` R `` | 重新整理 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
| `` + `` | 下一個螢幕模式(常規/半螢幕/全螢幕) | |
| `` _ `` | 上一個螢幕模式 | |
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
| `` <esc> `` | 取消 | |
| `` ? `` | 開啟選單 | |
| `` <c-s> `` | 檢視篩選路徑選項 | View options for filtering the commit log, so that only commits matching the filter are shown. |
| `` W `` | 開啟差異比較選單 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` <c-e> `` | 開啟差異比較選單 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` q `` | 結束 | |
| `` <esc> `` | 取消 | |
| `` <c-z> `` | Suspend the application | |
| `` <c-w> `` | 切換是否在差異檢視中顯示空格變更 | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
| `` z `` | 復原 | 將使用 reflog 確任 git 指令以復原。這不包括工作區更改;只考慮提交。 |
| `` <c-z> `` | 取消復原 | 將使用 reflog 確任 git 指令以重作。這不包括工作區更改;只考慮提交。 |
| `` Z `` | 取消復原 | 將使用 reflog 確任 git 指令以重作。這不包括工作區更改;只考慮提交。 |
## 移動
@@ -51,6 +53,13 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
| `` ] `` | 下一個索引標籤 | |
| `` [ `` | 上一個索引標籤 | |
## Input prompt
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 確認 | |
| `` <esc> `` | 關閉/取消 | |
## 主面板 (補丁生成)
| Key | Action | Info |
@@ -89,7 +98,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
| `` z `` | 復原 | Undo last merge conflict resolution. |
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` M `` | 開啟外部合併工具 | 執行 `git mergetool`。 |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` <esc> `` | 返回檔案面板 | |
## 主面板(預存)
@@ -119,7 +128,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 執行 | |
| `` <esc> `` | 關閉 | |
| `` <esc> `` | 關閉/取消 | |
| `` / `` | 搜尋 | |
## 子提交
@@ -339,7 +348,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
| `` D `` | 重設 | View reset options for working tree (e.g. nuking the working tree). |
| `` ` `` | 顯示檔案樹狀視圖 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` M `` | 開啟外部合併工具 | 執行 `git mergetool`。 |
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
| `` f `` | 擷取 | 同步遠端異動 |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
@@ -371,6 +380,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
|-----|--------|-------------|
| `` <enter> `` | 確認 | |
| `` <esc> `` | 關閉/取消 | |
| `` <c-o> `` | 複製到剪貼簿 | |
## 遠端
Generated
+127
View File
@@ -0,0 +1,127 @@
{
"nodes": {
"flake-compat": {
"locked": {
"lastModified": 1733328505,
"narHash": "sha256-NeCCThCEP3eCl2l/+27kNNK7QrwZB1IJCrXfrbv5oqU=",
"rev": "ff81ac966bb2cae68946d5ed5fc4994f96d0ffec",
"revCount": 69,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.1.0/01948eb7-9cba-704f-bbf3-3fa956735b52/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": "nixpkgs-lib"
},
"locked": {
"lastModified": 1759362264,
"narHash": "sha256-wfG0S7pltlYyZTM+qqlhJ7GMw2fTF4mLKCIVhLii/4M=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "758cf7296bee11f1706a574c77d072b8a7baa881",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1759831965,
"narHash": "sha256-vgPm2xjOmKdZ0xKA6yLXPJpjOtQPHfaZDRtH+47XEBo=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "c9b6fb798541223bbb396d287d16f43520250518",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixpkgs-lib": {
"locked": {
"lastModified": 1754788789,
"narHash": "sha256-x2rJ+Ovzq0sCMpgfgGaaqgBSwY+LST+WbZ6TytnT9Rk=",
"owner": "nix-community",
"repo": "nixpkgs.lib",
"rev": "a73b9c743612e4244d865a2fdee11865283c04e6",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixpkgs.lib",
"type": "github"
}
},
"nixpkgs_2": {
"locked": {
"lastModified": 1754340878,
"narHash": "sha256-lgmUyVQL9tSnvvIvBp7x1euhkkCho7n3TMzgjdvgPoU=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "cab778239e705082fe97bb4990e0d24c50924c04",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"flake-compat": "flake-compat",
"flake-parts": "flake-parts",
"nixpkgs": "nixpkgs",
"systems": "systems",
"treefmt-nix": "treefmt-nix"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": "nixpkgs_2"
},
"locked": {
"lastModified": 1758728421,
"narHash": "sha256-ySNJ008muQAds2JemiyrWYbwbG+V7S5wg3ZVKGHSFu8=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "5eda4ee8121f97b218f7cc73f5172098d458f1d1",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
+125
View File
@@ -0,0 +1,125 @@
{
description = "A simple terminal UI for git commands";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
systems.url = "github:nix-systems/default";
flake-parts.url = "github:hercules-ci/flake-parts";
flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz";
treefmt-nix.url = "github:numtide/treefmt-nix";
};
outputs =
inputs@{ flake-parts, systems, ... }:
flake-parts.lib.mkFlake { inherit inputs; } {
systems = import systems;
imports = [
inputs.treefmt-nix.flakeModule
];
perSystem =
{
pkgs,
system,
...
}:
let
goMod = builtins.readFile ./go.mod;
versionMatch = builtins.match ".*go[[:space:]]([0-9]+\\.[0-9]+)(\\.[0-9]+)?.*" goMod;
goVersion =
if versionMatch != null then
builtins.head versionMatch
else
throw "Could not extract Go version from go.mod";
goOverlay = final: prev: {
go = prev."go_${builtins.replaceStrings [ "." ] [ "_" ] goVersion}";
};
lazygit = pkgs.buildGoModule rec {
pname = "lazygit";
version = "dev";
gitCommit = inputs.self.rev or inputs.self.dirtyRev or "dev";
src = ./.;
vendorHash = null;
# Disable integration tests that require specific environment
doCheck = false;
nativeBuildInputs = with pkgs; [
git
makeWrapper
];
buildInputs = [ pkgs.git ];
ldflags = [
"-s"
"-w"
"-X main.commit=${gitCommit}"
"-X main.version=${version}"
"-X main.buildSource=nix"
];
postInstall = ''
wrapProgram $out/bin/lazygit \
--prefix PATH : ${pkgs.lib.makeBinPath [ pkgs.git ]}
'';
meta = {
description = "A simple terminal UI for git commands";
homepage = "https://github.com/jesseduffield/lazygit";
license = pkgs.lib.licenses.mit;
maintainers = [ "jesseduffield" ];
platforms = pkgs.lib.platforms.unix;
mainProgram = "lazygit";
};
};
in
{
_module.args.pkgs = import inputs.nixpkgs {
inherit system;
overlays = [ goOverlay ];
config = { };
};
packages = {
default = lazygit;
inherit lazygit;
};
devShells.default = pkgs.mkShell {
name = "lazygit-dev";
buildInputs = with pkgs; [
# Go toolchain
go
gotools
# Development tools
git
gnumake
];
# Environment variables for development
CGO_ENABLED = "0";
};
treefmt = {
programs.nixfmt.enable = pkgs.lib.meta.availableOn pkgs.stdenv.buildPlatform pkgs.nixfmt-rfc-style.compiler;
programs.nixfmt.package = pkgs.nixfmt-rfc-style;
programs.gofmt.enable = true;
};
checks.build = lazygit;
};
flake = {
overlays.default = final: prev: {
lazygit = inputs.self.packages.${final.system}.lazygit;
};
};
};
}
+16 -13
View File
@@ -1,6 +1,9 @@
module github.com/jesseduffield/lazygit
go 1.24.0
go 1.25.0
// This is necessary to ignore test files when executing gofumpt.
ignore ./test
require (
dario.cat/mergo v1.0.1
@@ -9,34 +12,35 @@ require (
github.com/aybabtme/humanlog v0.4.1
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
github.com/creack/pty v1.1.11
github.com/gdamore/tcell/v2 v2.8.1
github.com/gdamore/tcell/v2 v2.9.0
github.com/go-errors/errors v1.5.1
github.com/gookit/color v1.4.2
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd
github.com/jesseduffield/gocui v0.3.1-0.20250711082438-4aa4fd0b4d22
github.com/jesseduffield/gocui v0.3.1-0.20251002151855-67e0e55ff42a
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
github.com/karimkhaleel/jsonschema v0.0.0-20231001195015-d933f0d94ea3
github.com/kyokomi/emoji/v2 v2.2.8
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/mattn/go-runewidth v0.0.16
github.com/lucasb-eyer/go-colorful v1.3.0
github.com/mattn/go-runewidth v0.0.19
github.com/mgutz/str v1.2.0
github.com/mitchellh/go-ps v1.0.0
github.com/sahilm/fuzzy v0.1.0
github.com/samber/lo v1.31.0
github.com/sanity-io/litter v1.5.2
github.com/sasha-s/go-deadlock v0.3.5
github.com/sasha-s/go-deadlock v0.3.6
github.com/sirupsen/logrus v1.9.3
github.com/spf13/afero v1.9.5
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
github.com/stefanhaller/git-todo-parser v0.0.7-0.20250429125209-dcf39e4641f5
github.com/stefanhaller/git-todo-parser v0.0.7-0.20250905083220-c50528f08304
github.com/stretchr/testify v1.10.0
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
golang.org/x/sync v0.16.0
golang.org/x/sync v0.17.0
golang.org/x/sys v0.36.0
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -46,6 +50,7 @@ require (
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
@@ -67,19 +72,17 @@ require (
github.com/mattn/go-colorable v0.1.11 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/onsi/ginkgo v1.10.3 // indirect
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.37.0 // indirect
golang.org/x/net v0.39.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/term v0.33.0 // indirect
golang.org/x/text v0.27.0 // indirect
golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect
gopkg.in/fsnotify.v1 v1.4.7 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
+24 -21
View File
@@ -65,6 +65,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do=
@@ -96,8 +98,8 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
github.com/gdamore/tcell/v2 v2.8.0/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
github.com/gdamore/tcell/v2 v2.8.1/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
github.com/gdamore/tcell/v2 v2.9.0 h1:N6t+eqK7/xwtRPwxzs1PXeRWnm0H9l02CrgJ7DLn1ys=
github.com/gdamore/tcell/v2 v2.9.0/go.mod h1:8/ZoqM9rxzYphT9tH/9LnunhV9oPBqwS8WHGYm5nrmo=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
@@ -194,8 +196,8 @@ github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c h1:tC2Paiis
github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c/go.mod h1:F2fEBk0ddf6ixrBrJjY7phfQ3hL9rXG0uSjvwYe50bE=
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd h1:ViKj6qth8FgcIWizn9KiACWwPemWSymx62OPN0tHT+Q=
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd/go.mod h1:lRhCiBr6XjQrvcQVa+UYsy/99d3wMXn/a0nSQlhnhlA=
github.com/jesseduffield/gocui v0.3.1-0.20250711082438-4aa4fd0b4d22 h1:vhMwEsLlMtuKKo9/z3Qcggycgad8oV7+siwOZEnJDOs=
github.com/jesseduffield/gocui v0.3.1-0.20250711082438-4aa4fd0b4d22/go.mod h1:sLIyZ2J42R6idGdtemZzsiR3xY5EF0KsvYEGh3dQv3s=
github.com/jesseduffield/gocui v0.3.1-0.20251002151855-67e0e55ff42a h1:z3NQvFXAWGT4B/MwQBZc+1ej8WJ/Nv35xngQRvwzPuI=
github.com/jesseduffield/gocui v0.3.1-0.20251002151855-67e0e55ff42a/go.mod h1:sLIyZ2J42R6idGdtemZzsiR3xY5EF0KsvYEGh3dQv3s=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5/go.mod h1:qxN4mHOAyeIDLP7IK7defgPClM/z1Kze8VVQiaEjzsQ=
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e h1:uw/oo+kg7t/oeMs6sqlAwr85ND/9cpO3up3VxphxY0U=
@@ -224,8 +226,9 @@ github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/kyokomi/emoji/v2 v2.2.8 h1:jcofPxjHWEkJtkIbcLHvZhxKgCPl6C7MyjTrD4KDqUE=
github.com/kyokomi/emoji/v2 v2.2.8/go.mod h1:JUcn42DTdsXJo1SWanHh4HKDEyPaR5CqkmoirZZP9qE=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@@ -237,8 +240,9 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw=
github.com/mgutz/str v1.2.0/go.mod h1:w1v0ofgLaJdoD0HpQ3fycxKD1WtxpjSo151pK/31q6w=
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
@@ -247,8 +251,8 @@ github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw=
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe h1:vHpqOnPlnkba8iSxU4j/CvDSS9J4+F4473esQsYLGoE=
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -260,7 +264,6 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.3/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
@@ -271,8 +274,8 @@ github.com/samber/lo v1.31.0 h1:Sfa+/064Tdo4SvlohQUQzBhgSer9v/coGvKQI/XLWAM=
github.com/samber/lo v1.31.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8=
github.com/sanity-io/litter v1.5.2 h1:AnC8s9BMORWH5a4atZ4D6FPVvKGzHcnc5/IVTa87myw=
github.com/sanity-io/litter v1.5.2/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0=
github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU=
github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U=
github.com/sasha-s/go-deadlock v0.3.6 h1:TR7sfOnZ7x00tWPfD397Peodt57KzMDo+9Ae9rMiUmw=
github.com/sasha-s/go-deadlock v0.3.6/go.mod h1:CUqNyyvMxTyjFqDT7MRg9mb4Dv/btmGTqSR+rky/UXo=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
@@ -284,8 +287,8 @@ github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad h1:fiWzISvDn0Csy5H0iwgAuJGQTUpVfEMJJd4nRFXogbc=
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
github.com/stefanhaller/git-todo-parser v0.0.7-0.20250429125209-dcf39e4641f5 h1:ZCI0NPs0xXd00Ej9lX+wwbHjQDkstJa3kUbX7WCOF8I=
github.com/stefanhaller/git-todo-parser v0.0.7-0.20250429125209-dcf39e4641f5/go.mod h1:HFt9hGqMzgQ+gVxMKcvTvGaFz4Y0yYycqqAp2V3wcJY=
github.com/stefanhaller/git-todo-parser v0.0.7-0.20250905083220-c50528f08304 h1:bg+K3E0GYuqwTGaEfNrsZ0rH0Bw4p3EmPjk9Zjnua+w=
github.com/stefanhaller/git-todo-parser v0.0.7-0.20250905083220-c50528f08304/go.mod h1:HFt9hGqMzgQ+gVxMKcvTvGaFz4Y0yYycqqAp2V3wcJY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@@ -434,8 +437,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -488,8 +491,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -499,8 +502,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -515,8 +518,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+23 -5
View File
@@ -8,6 +8,7 @@ import (
_ "net/http/pprof"
"os"
"os/exec"
"os/user"
"path/filepath"
"runtime"
"runtime/debug"
@@ -124,12 +125,13 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
os.Exit(0)
}
tmpDirBase := filepath.Join(os.TempDir(), "lazygit")
if err := os.MkdirAll(tmpDirBase, 0o700); err != nil {
log.Fatal(err.Error())
}
tempDir, err := os.MkdirTemp(tmpDirBase, "")
tempDirBase := getTempDirBase()
tempDir, err := os.MkdirTemp(tempDirBase, "lazygit-*")
if err != nil {
if os.IsPermission(err) {
log.Fatalf("Your temp directory (%s) is not writeable. Try if rebooting your machine fixes this.", tempDirBase)
}
log.Fatal(err.Error())
}
defer os.RemoveAll(tempDir)
@@ -314,3 +316,19 @@ func getGitVersionInfo() string {
gitVersion := strings.Trim(strings.TrimPrefix(string(stdout), "git version "), " \r\n")
return gitVersion
}
func getTempDirBase() string {
tempDir := os.TempDir()
user, err := user.Current()
if err != nil || user.Uid == "" {
return tempDir
}
tmpDirBase := filepath.Join(tempDir, "lazygit-"+user.Uid)
if err := os.MkdirAll(tmpDirBase, 0o700); err != nil {
return tempDir
}
return tmpDirBase
}
+6
View File
@@ -116,6 +116,7 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
"commitDescription": tr.CommitDescriptionTitle,
"commits": tr.CommitsTitle,
"confirmation": tr.ConfirmationTitle,
"prompt": tr.PromptTitle,
"information": tr.InformationTitle,
"main": tr.NormalTitle,
"patchBuilding": tr.PatchBuildingTitle,
@@ -224,6 +225,11 @@ func formatBinding(binding *types.Binding) string {
// Replace newlines with <br> tags for proper markdown table formatting
tooltip := strings.ReplaceAll(binding.Tooltip, "\n", "<br>")
// Escape pipe characters to avoid breaking the table format
action = strings.ReplaceAll(action, `|`, `\|`)
description = strings.ReplaceAll(description, `|`, `\|`)
tooltip = strings.ReplaceAll(tooltip, `|`, `\|`)
// Use backticks for keyboard keys. Two backticks are needed with an inner space
// to escape a key that is itself a backtick.
return fmt.Sprintf("| `` %s `` | %s | %s |\n", action, description, tooltip)
+5 -1
View File
@@ -12,6 +12,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/commands/patch"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -59,6 +60,7 @@ func NewGitCommand(
version *git_commands.GitVersion,
osCommand *oscommands.OSCommand,
gitConfig git_config.IGitConfig,
pagerConfig *config.PagerConfig,
) (*GitCommand, error) {
repoPaths, err := git_commands.GetRepoPaths(osCommand.Cmd, version)
if err != nil {
@@ -88,6 +90,7 @@ func NewGitCommand(
gitConfig,
repoPaths,
repository,
pagerConfig,
), nil
}
@@ -98,6 +101,7 @@ func NewGitCommandAux(
gitConfig git_config.IGitConfig,
repoPaths *git_commands.RepoPaths,
repo *gogit.Repository,
pagerConfig *config.PagerConfig,
) *GitCommand {
cmd := NewGitCmdObjBuilder(cmn.Log, osCommand.Cmd)
@@ -108,7 +112,7 @@ func NewGitCommandAux(
// common ones are: cmn, osCommand, dotGitDir, configCommands
configCommands := git_commands.NewConfigCommands(cmn, gitConfig, repo)
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands)
gitCommon := git_commands.NewGitCommon(cmn, version, cmd, osCommand, repoPaths, repo, configCommands, pagerConfig)
fileLoader := git_commands.NewFileLoader(gitCommon, cmd, configCommands)
statusCommands := git_commands.NewStatusCommands(gitCommon)
+64 -13
View File
@@ -13,7 +13,7 @@ import (
type BranchCommands struct {
*GitCommon
allBranchesLogCmdIndex uint8 // keeps track of current all branches log command
allBranchesLogCmdIndex int // keeps track of current all branches log command
}
func NewBranchCommands(gitCommon *GitCommon) *BranchCommands {
@@ -116,6 +116,22 @@ func (self *BranchCommands) CurrentBranchName() (string, error) {
return strings.TrimSpace(output), nil
}
// Gets the full ref name of the previously checked out branch. Can return an empty string (but no
// error) e.g. when the previously checked out thing was a detached head.
func (self *BranchCommands) PreviousRef() (string, error) {
cmdArgs := NewGitCmd("rev-parse").
Arg("--symbolic-full-name").
Arg("@{-1}").
ToArgv()
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil {
return "", err
}
return strings.TrimSpace(output), nil
}
// LocalDelete delete branch locally
func (self *BranchCommands) LocalDelete(branches []string, force bool) error {
cmdArgs := NewGitCmd("branch").
@@ -234,26 +250,51 @@ func (self *BranchCommands) Rename(oldName string, newName string) error {
return self.cmd.New(cmdArgs).Run()
}
type MergeOpts struct {
FastForwardOnly bool
Squash bool
}
type MergeVariant int
const (
MERGE_VARIANT_REGULAR MergeVariant = iota
MERGE_VARIANT_FAST_FORWARD
MERGE_VARIANT_NON_FAST_FORWARD
MERGE_VARIANT_SQUASH
)
func (self *BranchCommands) Merge(branchName string, variant MergeVariant) error {
extraArgs := func() []string {
switch variant {
case MERGE_VARIANT_REGULAR:
return []string{}
case MERGE_VARIANT_FAST_FORWARD:
return []string{"--ff"}
case MERGE_VARIANT_NON_FAST_FORWARD:
return []string{"--no-ff"}
case MERGE_VARIANT_SQUASH:
return []string{"--squash", "--ff"}
}
panic("shouldn't get here")
}()
func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
if opts.Squash && opts.FastForwardOnly {
panic("Squash and FastForwardOnly can't both be true")
}
cmdArgs := NewGitCmd("merge").
Arg("--no-edit").
Arg(strings.Fields(self.UserConfig().Git.Merging.Args)...).
ArgIf(opts.FastForwardOnly, "--ff-only").
ArgIf(opts.Squash, "--squash", "--ff").
Arg(extraArgs...).
Arg(branchName).
ToArgv()
return self.cmd.New(cmdArgs).Run()
}
// Returns whether refName can be fast-forward merged into the current branch
func (self *BranchCommands) CanDoFastForwardMerge(refName string) bool {
cmdArgs := NewGitCmd("merge-base").
Arg("--is-ancestor").
Arg("HEAD", refName).
ToArgv()
err := self.cmd.New(cmdArgs).DontLog().Run()
return err == nil
}
// Only choose between non-empty, non-identical commands
func (self *BranchCommands) allBranchesLogCandidates() []string {
return lo.Uniq(lo.WithoutEmpty(self.UserConfig().Git.AllBranchesLogCmds))
@@ -262,6 +303,10 @@ func (self *BranchCommands) allBranchesLogCandidates() []string {
func (self *BranchCommands) AllBranchesLogCmdObj() *oscommands.CmdObj {
candidates := self.allBranchesLogCandidates()
if self.allBranchesLogCmdIndex >= len(candidates) {
self.allBranchesLogCmdIndex = 0
}
i := self.allBranchesLogCmdIndex
return self.cmd.New(str.ToArgv(candidates[i])).DontLog()
}
@@ -269,7 +314,13 @@ func (self *BranchCommands) AllBranchesLogCmdObj() *oscommands.CmdObj {
func (self *BranchCommands) RotateAllBranchesLogIdx() {
n := len(self.allBranchesLogCandidates())
i := self.allBranchesLogCmdIndex
self.allBranchesLogCmdIndex = uint8((int(i) + 1) % n)
self.allBranchesLogCmdIndex = (i + 1) % n
}
func (self *BranchCommands) GetAllBranchesLogIdxAndCount() (int, int) {
n := len(self.allBranchesLogCandidates())
i := self.allBranchesLogCmdIndex
return i, n
}
func (self *BranchCommands) IsBranchMerged(branch *models.Branch, mainBranches *MainBranches) (bool, error) {
@@ -288,7 +339,7 @@ func (self *BranchCommands) IsBranchMerged(branch *models.Branch, mainBranches *
Arg("--").
ToArgv()
stdout, _, err := self.cmd.New(cmdArgs).RunWithOutputs()
stdout, _, err := self.cmd.New(cmdArgs).DontLog().RunWithOutputs()
if err != nil {
return false, err
}
+22 -8
View File
@@ -122,14 +122,14 @@ func TestBranchMerge(t *testing.T) {
scenarios := []struct {
testName string
userConfig *config.UserConfig
opts MergeOpts
variant MergeVariant
branchName string
expected []string
}{
{
testName: "basic",
userConfig: &config.UserConfig{},
opts: MergeOpts{},
variant: MERGE_VARIANT_REGULAR,
branchName: "mybranch",
expected: []string{"merge", "--no-edit", "mybranch"},
},
@@ -142,7 +142,7 @@ func TestBranchMerge(t *testing.T) {
},
},
},
opts: MergeOpts{},
variant: MERGE_VARIANT_REGULAR,
branchName: "mybranch",
expected: []string{"merge", "--no-edit", "--merging-args", "mybranch"},
},
@@ -155,16 +155,30 @@ func TestBranchMerge(t *testing.T) {
},
},
},
opts: MergeOpts{},
variant: MERGE_VARIANT_REGULAR,
branchName: "mybranch",
expected: []string{"merge", "--no-edit", "--arg1", "--arg2", "mybranch"},
},
{
testName: "fast forward only",
testName: "fast-forward merge",
userConfig: &config.UserConfig{},
opts: MergeOpts{FastForwardOnly: true},
variant: MERGE_VARIANT_FAST_FORWARD,
branchName: "mybranch",
expected: []string{"merge", "--no-edit", "--ff-only", "mybranch"},
expected: []string{"merge", "--no-edit", "--ff", "mybranch"},
},
{
testName: "non-fast-forward merge",
userConfig: &config.UserConfig{},
variant: MERGE_VARIANT_NON_FAST_FORWARD,
branchName: "mybranch",
expected: []string{"merge", "--no-edit", "--no-ff", "mybranch"},
},
{
testName: "squash merge",
userConfig: &config.UserConfig{},
variant: MERGE_VARIANT_SQUASH,
branchName: "mybranch",
expected: []string{"merge", "--no-edit", "--squash", "--ff", "mybranch"},
},
}
@@ -174,7 +188,7 @@ func TestBranchMerge(t *testing.T) {
ExpectGitArgs(s.expected, "", nil)
instance := buildBranchCommands(commonDeps{runner: runner, userConfig: s.userConfig})
assert.NoError(t, instance.Merge(s.branchName, s.opts))
assert.NoError(t, instance.Merge(s.branchName, s.variant))
runner.CheckForMissingCalls()
})
}
+4 -3
View File
@@ -256,13 +256,14 @@ func (self *CommitCommands) AmendHeadCmdObj() *oscommands.CmdObj {
func (self *CommitCommands) ShowCmdObj(hash string, filterPaths []string) *oscommands.CmdObj {
contextSize := self.UserConfig().Git.DiffContextSize
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
extDiffCmd := self.pagerConfig.GetExternalDiffCommand()
useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig()
cmdArgs := NewGitCmd("show").
Config("diff.noprefix=false").
ConfigIf(extDiffCmd != "", "diff.external="+extDiffCmd).
ArgIfElse(extDiffCmd != "", "--ext-diff", "--no-ext-diff").
ArgIfElse(extDiffCmd != "" || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff").
Arg("--submodule").
Arg("--color="+self.UserConfig().Git.Paging.ColorArg).
Arg("--color="+self.pagerConfig.GetColorArg()).
Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg("--stat").
Arg("--decorate").
+19 -8
View File
@@ -255,7 +255,7 @@ func TestCommitShowCmdObj(t *testing.T) {
contextSize uint64
similarityThreshold int
ignoreWhitespace bool
extDiffCmd string
pagerConfig *config.PagingConfig
expected []string
}
@@ -266,7 +266,7 @@ func TestCommitShowCmdObj(t *testing.T) {
contextSize: 3,
similarityThreshold: 50,
ignoreWhitespace: false,
extDiffCmd: "",
pagerConfig: nil,
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%", "--"},
},
{
@@ -275,7 +275,7 @@ func TestCommitShowCmdObj(t *testing.T) {
contextSize: 3,
similarityThreshold: 50,
ignoreWhitespace: false,
extDiffCmd: "",
pagerConfig: nil,
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%", "--", "file.txt"},
},
{
@@ -284,7 +284,7 @@ func TestCommitShowCmdObj(t *testing.T) {
contextSize: 77,
similarityThreshold: 50,
ignoreWhitespace: false,
extDiffCmd: "",
pagerConfig: nil,
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%", "--"},
},
{
@@ -293,7 +293,7 @@ func TestCommitShowCmdObj(t *testing.T) {
contextSize: 3,
similarityThreshold: 33,
ignoreWhitespace: false,
extDiffCmd: "",
pagerConfig: nil,
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=33%", "--"},
},
{
@@ -302,7 +302,7 @@ func TestCommitShowCmdObj(t *testing.T) {
contextSize: 77,
similarityThreshold: 50,
ignoreWhitespace: true,
extDiffCmd: "",
pagerConfig: nil,
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--no-ext-diff", "--submodule", "--color=always", "--unified=77", "--stat", "--decorate", "-p", "1234567890", "--ignore-all-space", "--find-renames=50%", "--"},
},
{
@@ -311,15 +311,26 @@ func TestCommitShowCmdObj(t *testing.T) {
contextSize: 3,
similarityThreshold: 50,
ignoreWhitespace: false,
extDiffCmd: "difft --color=always",
pagerConfig: &config.PagingConfig{ExternalDiffCommand: "difft --color=always"},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.external=difft --color=always", "-c", "diff.noprefix=false", "show", "--ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%", "--"},
},
{
testName: "Show diff using git's external diff config",
filterPaths: []string{},
contextSize: 3,
similarityThreshold: 50,
ignoreWhitespace: false,
pagerConfig: &config.PagingConfig{UseExternalDiffGitConfig: true},
expected: []string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "show", "--ext-diff", "--submodule", "--color=always", "--unified=3", "--stat", "--decorate", "-p", "1234567890", "--find-renames=50%", "--"},
},
}
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
userConfig := config.GetDefaultConfig()
userConfig.Git.Paging.ExternalDiffCommand = s.extDiffCmd
if s.pagerConfig != nil {
userConfig.Git.Pagers = []config.PagingConfig{*s.pagerConfig}
}
userConfig.Git.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
userConfig.Git.DiffContextSize = s.contextSize
userConfig.Git.RenameSimilarityThreshold = s.similarityThreshold
+17 -13
View File
@@ -4,16 +4,18 @@ import (
gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/config"
)
type GitCommon struct {
*common.Common
version *GitVersion
cmd oscommands.ICmdObjBuilder
os *oscommands.OSCommand
repoPaths *RepoPaths
repo *gogit.Repository
config *ConfigCommands
version *GitVersion
cmd oscommands.ICmdObjBuilder
os *oscommands.OSCommand
repoPaths *RepoPaths
repo *gogit.Repository
config *ConfigCommands
pagerConfig *config.PagerConfig
}
func NewGitCommon(
@@ -24,14 +26,16 @@ func NewGitCommon(
repoPaths *RepoPaths,
repo *gogit.Repository,
config *ConfigCommands,
pagerConfig *config.PagerConfig,
) *GitCommon {
return &GitCommon{
Common: cmn,
version: version,
cmd: cmd,
os: osCommand,
repoPaths: repoPaths,
repo: repo,
config: config,
Common: cmn,
version: version,
cmd: cmd,
os: osCommand,
repoPaths: repoPaths,
repo: repo,
config: config,
pagerConfig: pagerConfig,
}
}
+4 -31
View File
@@ -1,15 +1,10 @@
package git_commands
import (
"os"
"strconv"
"strings"
gogit "github.com/jesseduffield/go-git/v5"
"github.com/jesseduffield/go-git/v5/config"
"github.com/jesseduffield/lazygit/pkg/commands/git_config"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type ConfigCommands struct {
@@ -31,32 +26,6 @@ func NewConfigCommands(
}
}
func (self *ConfigCommands) ConfiguredPager() string {
if os.Getenv("GIT_PAGER") != "" {
return os.Getenv("GIT_PAGER")
}
if os.Getenv("PAGER") != "" {
return os.Getenv("PAGER")
}
output := self.gitConfig.Get("core.pager")
return strings.Split(output, "\n")[0]
}
func (self *ConfigCommands) GetPager(width int) string {
useConfig := self.UserConfig().Git.Paging.UseConfig
if useConfig {
pager := self.ConfiguredPager()
return strings.Split(pager, "| less")[0]
}
templateValues := map[string]string{
"columnWidth": strconv.Itoa(width/2 - 6),
}
pagerTemplate := string(self.UserConfig().Git.Paging.Pager)
return utils.ResolvePlaceholderString(pagerTemplate, templateValues)
}
type GpgConfigKey string
const (
@@ -128,6 +97,10 @@ func (self *ConfigCommands) GetRebaseUpdateRefs() bool {
return self.gitConfig.GetBool("rebase.updateRefs")
}
func (self *ConfigCommands) GetMergeFF() string {
return self.gitConfig.Get("merge.ff")
}
func (self *ConfigCommands) DropConfigCache() {
self.gitConfig.DropCache()
}
+4
View File
@@ -61,6 +61,10 @@ func buildGitCommon(deps commonDeps) *GitCommon {
gitCommon.Common.SetUserConfig(config.GetDefaultConfig())
}
gitCommon.pagerConfig = config.NewPagerConfig(func() *config.UserConfig {
return gitCommon.Common.UserConfig()
})
gitCommon.version = deps.gitVersion
if gitCommon.version == nil {
gitCommon.version = &GitVersion{2, 0, 0, ""}
+4 -3
View File
@@ -19,17 +19,18 @@ func NewDiffCommands(gitCommon *GitCommon) *DiffCommands {
// This is for generating diffs to be shown in the UI (e.g. rendering a range
// diff to the main view). It uses a custom pager if one is configured.
func (self *DiffCommands) DiffCmdObj(diffArgs []string) *oscommands.CmdObj {
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
extDiffCmd := self.pagerConfig.GetExternalDiffCommand()
useExtDiff := extDiffCmd != ""
useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig()
ignoreWhitespace := self.UserConfig().Git.IgnoreWhitespaceInDiffView
return self.cmd.New(
NewGitCmd("diff").
Config("diff.noprefix=false").
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
ArgIfElse(useExtDiff || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff").
Arg("--submodule").
Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
Arg(fmt.Sprintf("--color=%s", self.pagerConfig.GetColorArg())).
ArgIf(ignoreWhitespace, "--ignore-all-space").
Arg(fmt.Sprintf("--unified=%d", self.UserConfig().Git.DiffContextSize)).
Arg(diffArgs...).
+2 -2
View File
@@ -201,8 +201,8 @@ func (self *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) {
PreviousPath: "",
}
if strings.HasPrefix(status.Change, "R") {
// if a line starts with 'R' then the next line is the original file.
if strings.HasPrefix(status.Change, "R") || strings.HasPrefix(status.Change, "C") {
// if a line starts with 'R' (rename) or 'C' (copy) then the next line is the original file.
status.PreviousPath = splitLines[i+1]
status.StatusString = fmt.Sprintf("%s %s -> %s", status.Change, status.PreviousPath, status.Path)
i++
@@ -192,6 +192,43 @@ func TestFileGetStatusFiles(t *testing.T) {
},
},
},
{
testName: "Copied files",
similarityThreshold: 50,
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
"C copy1.txt\x00original.txt\x00CM copy2.txt\x00original.txt",
nil,
),
expectedFiles: []*models.File{
{
Path: "copy1.txt",
PreviousPath: "original.txt",
HasStagedChanges: true,
HasUnstagedChanges: false,
Tracked: true,
Added: false,
Deleted: false,
HasMergeConflicts: false,
HasInlineMergeConflicts: false,
DisplayString: "C original.txt -> copy1.txt",
ShortStatus: "C ",
},
{
Path: "copy2.txt",
PreviousPath: "original.txt",
HasStagedChanges: true,
HasUnstagedChanges: true,
Tracked: true,
Added: false,
Deleted: false,
HasMergeConflicts: false,
HasInlineMergeConflicts: false,
DisplayString: "CM original.txt -> copy2.txt",
ShortStatus: "CM",
},
},
},
}
for _, s := range scenarios {
+17 -8
View File
@@ -244,8 +244,9 @@ func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteract
cmdObj.AddEnvVars(
"DEBUG="+debug,
"LANG=en_US.UTF-8", // Force using EN as language
"LC_ALL=en_US.UTF-8", // Force using EN as language
"LANG=C", // Force using English language
"LC_ALL=C", // Force using English language
"LC_MESSAGES=C", // Force using English language
"GIT_SEQUENCE_EDITOR="+gitSequenceEditor,
)
@@ -277,8 +278,9 @@ func (self *RebaseCommands) GitRebaseEditTodo(todosFileContent []byte) error {
cmdObj.AddEnvVars(
"DEBUG="+debug,
"LANG=en_US.UTF-8", // Force using EN as language
"LC_ALL=en_US.UTF-8", // Force using EN as language
"LANG=C", // Force using English language
"LC_ALL=C", // Force using English language
"LC_MESSAGES=C", // Force using English language
"GIT_EDITOR="+ex,
"GIT_SEQUENCE_EDITOR="+ex,
)
@@ -519,10 +521,17 @@ func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, comm
}
for _, filePath := range filePaths {
// check if file exists in previous commit (this command returns an error if the file doesn't exist)
cmdArgs := NewGitCmd("cat-file").Arg("-e", "HEAD^:"+filePath).ToArgv()
if err := self.cmd.New(cmdArgs).Run(); err != nil {
doesFileExistInPreviousCommit := false
if commitIndex < len(commits)-1 {
// check if file exists in previous commit (this command returns an empty string if the file doesn't exist)
cmdArgs := NewGitCmd("ls-tree").Arg("--name-only", "HEAD^", "--", filePath).ToArgv()
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil {
return err
}
doesFileExistInPreviousCommit = strings.TrimRight(output, "\n") == filePath
}
if !doesFileExistInPreviousCommit {
if err := self.os.Remove(filePath); err != nil {
return err
}
+1 -1
View File
@@ -139,7 +139,7 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
fileName: []string{"test999.txt"},
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"rebase", "--interactive", "--autostash", "--keep-empty", "--no-autosquash", "--rebase-merges", "abcdef"}, "", nil).
ExpectGitArgs([]string{"cat-file", "-e", "HEAD^:test999.txt"}, "", nil).
ExpectGitArgs([]string{"ls-tree", "--name-only", "HEAD^", "--", "test999.txt"}, "test999.txt\n", nil).
ExpectGitArgs([]string{"checkout", "HEAD^", "--", "test999.txt"}, "", nil).
ExpectGitArgs([]string{"commit", "--amend", "--no-edit", "--allow-empty", "--allow-empty-message"}, "", nil).
ExpectGitArgs([]string{"rebase", "--continue"}, "", nil),
+6 -1
View File
@@ -81,12 +81,17 @@ func (self *StashCommands) Hash(index int) (string, error) {
}
func (self *StashCommands) ShowStashEntryCmdObj(index int) *oscommands.CmdObj {
extDiffCmd := self.pagerConfig.GetExternalDiffCommand()
useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig()
// "-u" is the same as "--include-untracked", but the latter fails in older git versions for some reason
cmdArgs := NewGitCmd("stash").Arg("show").
Arg("-p").
Arg("--stat").
Arg("-u").
Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
ConfigIf(extDiffCmd != "", "diff.external="+extDiffCmd).
ArgIfElse(extDiffCmd != "" || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff").
Arg(fmt.Sprintf("--color=%s", self.pagerConfig.GetColorArg())).
Arg(fmt.Sprintf("--unified=%d", self.UserConfig().Git.DiffContextSize)).
ArgIf(self.UserConfig().Git.IgnoreWhitespaceInDiffView, "--ignore-all-space").
Arg(fmt.Sprintf("--find-renames=%d%%", self.UserConfig().Git.RenameSimilarityThreshold)).
+8 -2
View File
@@ -32,7 +32,7 @@ func (self *StashLoader) GetStashEntries(filterPath string) []*models.StashEntry
return self.getUnfilteredStashEntries()
}
cmdArgs := NewGitCmd("stash").Arg("list", "--name-only", "--pretty=%gd:%ct|%gs").ToArgv()
cmdArgs := NewGitCmd("stash").Arg("list", "--name-only", "--pretty=%gd:%H|%ct|%gs").ToArgv()
rawString, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil {
return self.getUnfilteredStashEntries()
@@ -66,7 +66,7 @@ outer:
}
func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--pretty=%ct|%gs").ToArgv()
cmdArgs := NewGitCmd("stash").Arg("list", "-z", "--pretty=%H|%ct|%gs").ToArgv()
rawString, _ := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
return lo.Map(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
@@ -80,6 +80,12 @@ func stashEntryFromLine(line string, index int) *models.StashEntry {
Index: index,
}
hash, line, ok := strings.Cut(line, "|")
if !ok {
return model
}
model.Hash = hash
tstr, msg, ok := strings.Cut(line, "|")
if !ok {
return model
+19 -9
View File
@@ -1,7 +1,9 @@
package git_commands
import (
"fmt"
"testing"
"time"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
@@ -17,30 +19,38 @@ func TestGetStashEntries(t *testing.T) {
expectedStashEntries []*models.StashEntry
}
hoursAgo := time.Now().Unix() - 3*3600 - 1800
daysAgo := time.Now().Unix() - 3*3600*24 - 3600*12
scenarios := []scenario{
{
"No stash entries found",
"",
oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%ct|%gs"}, "", nil),
ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%H|%ct|%gs"}, "", nil),
[]*models.StashEntry{},
},
{
"Several stash entries found",
"",
oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%ct|%gs"},
"WIP on add-pkg-commands-test: 55c6af2 increase parallel build\x00WIP on master: bb86a3f update github template\x00",
nil,
),
ExpectGitArgs([]string{"stash", "list", "-z", "--pretty=%H|%ct|%gs"},
fmt.Sprintf("fa1afe1|%d|WIP on add-pkg-commands-test: 55c6af2 increase parallel build\x00deadbeef|%d|WIP on master: bb86a3f update github template\x00",
hoursAgo,
daysAgo,
), nil),
[]*models.StashEntry{
{
Index: 0,
Name: "WIP on add-pkg-commands-test: 55c6af2 increase parallel build",
Index: 0,
Name: "WIP on add-pkg-commands-test: 55c6af2 increase parallel build",
Recency: "3h",
Hash: "fa1afe1",
},
{
Index: 1,
Name: "WIP on master: bb86a3f update github template",
Index: 1,
Name: "WIP on master: bb86a3f update github template",
Recency: "3d",
Hash: "deadbeef",
},
},
},
+26 -4
View File
@@ -103,6 +103,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize uint64
similarityThreshold int
ignoreWhitespace bool
pagerConfig *config.PagingConfig
expected []string
}
@@ -113,7 +114,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 3,
similarityThreshold: 50,
ignoreWhitespace: false,
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--find-renames=50%", "refs/stash@{5}"},
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--no-ext-diff", "--color=always", "--unified=3", "--find-renames=50%", "refs/stash@{5}"},
},
{
testName: "Show diff with custom context size",
@@ -121,7 +122,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 77,
similarityThreshold: 50,
ignoreWhitespace: false,
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=77", "--find-renames=50%", "refs/stash@{5}"},
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--no-ext-diff", "--color=always", "--unified=77", "--find-renames=50%", "refs/stash@{5}"},
},
{
testName: "Show diff with custom similarity threshold",
@@ -129,7 +130,25 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 3,
similarityThreshold: 33,
ignoreWhitespace: false,
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--find-renames=33%", "refs/stash@{5}"},
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--no-ext-diff", "--color=always", "--unified=3", "--find-renames=33%", "refs/stash@{5}"},
},
{
testName: "Show diff with external diff command",
index: 5,
contextSize: 3,
similarityThreshold: 50,
ignoreWhitespace: false,
pagerConfig: &config.PagingConfig{ExternalDiffCommand: "difft --color=always"},
expected: []string{"git", "-C", "/path/to/worktree", "-c", "diff.external=difft --color=always", "stash", "show", "-p", "--stat", "-u", "--ext-diff", "--color=always", "--unified=3", "--find-renames=50%", "refs/stash@{5}"},
},
{
testName: "Show diff using git's external diff config",
index: 5,
contextSize: 3,
similarityThreshold: 50,
ignoreWhitespace: false,
pagerConfig: &config.PagingConfig{UseExternalDiffGitConfig: true},
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--ext-diff", "--color=always", "--unified=3", "--find-renames=50%", "refs/stash@{5}"},
},
{
testName: "Default case",
@@ -137,7 +156,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 3,
similarityThreshold: 50,
ignoreWhitespace: true,
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--ignore-all-space", "--find-renames=50%", "refs/stash@{5}"},
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--no-ext-diff", "--color=always", "--unified=3", "--ignore-all-space", "--find-renames=50%", "refs/stash@{5}"},
},
}
@@ -147,6 +166,9 @@ func TestStashStashEntryCmdObj(t *testing.T) {
userConfig.Git.IgnoreWhitespaceInDiffView = s.ignoreWhitespace
userConfig.Git.DiffContextSize = s.contextSize
userConfig.Git.RenameSimilarityThreshold = s.similarityThreshold
if s.pagerConfig != nil {
userConfig.Git.Pagers = []config.PagingConfig{*s.pagerConfig}
}
repoPaths := RepoPaths{
worktreePath: "/path/to/worktree",
}
+52 -6
View File
@@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"regexp"
"strings"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/models"
@@ -257,7 +258,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool,
}
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool) *oscommands.CmdObj {
colorArg := self.UserConfig().Git.Paging.ColorArg
colorArg := self.pagerConfig.GetColorArg()
if plain {
colorArg = "never"
}
@@ -265,12 +266,13 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
contextSize := self.UserConfig().Git.DiffContextSize
prevPath := node.GetPreviousPath()
noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile()
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
extDiffCmd := self.pagerConfig.GetExternalDiffCommand()
useExtDiff := extDiffCmd != "" && !plain
useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig() && !plain
cmdArgs := NewGitCmd("diff").
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
ArgIfElse(useExtDiff || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff").
Arg("--submodule").
Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg(fmt.Sprintf("--color=%s", colorArg)).
@@ -297,18 +299,19 @@ func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bo
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) *oscommands.CmdObj {
contextSize := self.UserConfig().Git.DiffContextSize
colorArg := self.UserConfig().Git.Paging.ColorArg
colorArg := self.pagerConfig.GetColorArg()
if plain {
colorArg = "never"
}
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
extDiffCmd := self.pagerConfig.GetExternalDiffCommand()
useExtDiff := extDiffCmd != "" && !plain
useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig() && !plain
cmdArgs := NewGitCmd("diff").
Config("diff.noprefix=false").
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
ArgIfElse(useExtDiff || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff").
Arg("--submodule").
Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg("--no-renames").
@@ -405,3 +408,46 @@ func (self *WorkingTreeCommands) ResetMixed(ref string) error {
return self.cmd.New(cmdArgs).Run()
}
func (self *WorkingTreeCommands) ShowFileAtStage(path string, stage int) (string, error) {
cmdArgs := NewGitCmd("show").
Arg(fmt.Sprintf(":%d:%s", stage, path)).
ToArgv()
return self.cmd.New(cmdArgs).RunWithOutput()
}
func (self *WorkingTreeCommands) ObjectIDAtStage(path string, stage int) (string, error) {
cmdArgs := NewGitCmd("rev-parse").
Arg(fmt.Sprintf(":%d:%s", stage, path)).
ToArgv()
output, err := self.cmd.New(cmdArgs).RunWithOutput()
if err != nil {
return "", err
}
return strings.TrimSpace(output), nil
}
func (self *WorkingTreeCommands) MergeFileForFiles(strategy string, oursFilepath string, baseFilepath string, theirsFilepath string) (string, error) {
cmdArgs := NewGitCmd("merge-file").
Arg(strategy).
Arg("--stdout").
Arg(oursFilepath, baseFilepath, theirsFilepath).
ToArgv()
return self.cmd.New(cmdArgs).RunWithOutput()
}
// OIDs mode (Git 2.43+)
func (self *WorkingTreeCommands) MergeFileForObjectIDs(strategy string, oursID string, baseID string, theirsID string) (string, error) {
cmdArgs := NewGitCmd("merge-file").
Arg(strategy).
Arg("--stdout").
Arg("--object-id").
Arg(oursID, baseID, theirsID).
ToArgv()
return self.cmd.New(cmdArgs).RunWithOutput()
}
@@ -46,6 +46,7 @@ var azdoServiceDef = ServiceDefinition{
pullRequestURLIntoTargetBranch: "/pullrequestcreate?sourceRef={{.From}}&targetRef={{.To}}",
commitURL: "/commit/{{.CommitHash}}",
regexStrings: []string{
`^.+@vs-ssh\.visualstudio\.com[:/](?:v3/)?(?P<org>[^/]+)/(?P<project>[^/]+)/(?P<repo>[^/]+?)(?:\.git)?$`,
`^git@ssh.dev.azure.com.*/(?P<org>.*)/(?P<project>.*)/(?P<repo>.*?)(?:\.git)?$`,
`^https://.*@dev.azure.com/(?P<org>.*?)/(?P<project>.*?)/_git/(?P<repo>.*?)(?:\.git)?$`,
`^https://.*/(?P<org>.*?)/(?P<project>.*?)/_git/(?P<repo>.*?)(?:\.git)?$`,
@@ -223,6 +223,18 @@ func TestGetPullRequestURL(t *testing.T) {
assert.Equal(t, "https://mycompany.azuredevops.com/collection/myproject/_git/myrepo/pullrequestcreate?sourceRef=feature%2Fnew", url)
},
},
{
testName: "Opens a link to new pull request on Azure DevOps (legacy vs-ssh.visualstudio.com SSH) with mapping to dev.azure.com",
from: "feature/new",
remoteUrl: "git@vs-ssh.visualstudio.com:v3/myorg/myproject/myrepo",
configServiceDomains: map[string]string{
"vs-ssh.visualstudio.com": "azuredevops:dev.azure.com",
},
test: func(url string, err error) {
assert.NoError(t, err)
assert.Equal(t, "https://dev.azure.com/myorg/myproject/_git/myrepo/pullrequestcreate?sourceRef=feature%2Fnew", url)
},
},
{
testName: "Opens a link to new pull request on Bitbucket Server (SSH)",
from: "feature/new",
+1
View File
@@ -7,6 +7,7 @@ type StashEntry struct {
Index int
Recency string
Name string
Hash string
}
func (s *StashEntry) FullRefName() string {
+1 -1
View File
@@ -330,7 +330,7 @@ func (self *cmdObjRunner) runAndDetectCredentialRequest(
promptUserForCredential func(CredentialType) <-chan string,
) error {
// setting the output to english so we can parse it for a username/password request
cmdObj.AddEnvVars("LANG=en_US.UTF-8", "LC_ALL=en_US.UTF-8")
cmdObj.AddEnvVars("LANG=C", "LC_ALL=C", "LC_MESSAGES=C")
return self.runAndStreamAux(cmdObj, func(handler *cmdHandler, cmdWriter io.Writer) {
tr := io.TeeReader(handler.stdoutPipe, cmdWriter)
@@ -1,5 +1,4 @@
//go:build !windows
// +build !windows
package oscommands
@@ -1,6 +1,3 @@
//go:build windows
// +build windows
package oscommands
import (
+16 -16
View File
@@ -35,16 +35,16 @@ import (
// destination file exists, all it's contents will be replaced by the contents
// of the source file. The file mode will be copied from the source and
// the copied data is synced/flushed to stable storage.
func CopyFile(src, dst string) (err error) {
func CopyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return //nolint: nakedret
return err
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return //nolint: nakedret
return err
}
defer func() {
if e := out.Close(); e != nil {
@@ -54,30 +54,30 @@ func CopyFile(src, dst string) (err error) {
_, err = io.Copy(out, in)
if err != nil {
return //nolint: nakedret
return err
}
err = out.Sync()
if err != nil {
return //nolint: nakedret
return err
}
si, err := os.Stat(src)
if err != nil {
return //nolint: nakedret
return err
}
err = os.Chmod(dst, si.Mode())
if err != nil {
return //nolint: nakedret
return err
}
return //nolint: nakedret
return err
}
// CopyDir recursively copies a directory tree, attempting to preserve permissions.
// Source directory must exist. If destination already exists we'll clobber it.
// Symlinks are ignored and skipped.
func CopyDir(src string, dst string) (err error) {
func CopyDir(src string, dst string) error {
src = filepath.Clean(src)
dst = filepath.Clean(dst)
@@ -91,7 +91,7 @@ func CopyDir(src string, dst string) (err error) {
_, err = os.Stat(dst)
if err != nil && !os.IsNotExist(err) {
return //nolint: nakedret
return err
}
if err == nil {
// it exists so let's remove it
@@ -102,12 +102,12 @@ func CopyDir(src string, dst string) (err error) {
err = os.MkdirAll(dst, si.Mode())
if err != nil {
return //nolint: nakedret
return err
}
entries, err := os.ReadDir(src)
if err != nil {
return //nolint: nakedret
return err
}
for _, entry := range entries {
@@ -117,13 +117,13 @@ func CopyDir(src string, dst string) (err error) {
if entry.IsDir() {
err = CopyDir(srcPath, dstPath)
if err != nil {
return //nolint: nakedret
return err
}
} else {
var info os.FileInfo
info, err = entry.Info()
if err != nil {
return //nolint: nakedret
return err
}
// Skip symlinks.
@@ -133,10 +133,10 @@ func CopyDir(src string, dst string) (err error) {
err = CopyFile(srcPath, dstPath)
if err != nil {
return //nolint: nakedret
return err
}
}
}
return //nolint: nakedret
return err
}
-13
View File
@@ -1,7 +1,6 @@
package oscommands
import (
"fmt"
"io"
"os"
"os/exec"
@@ -329,15 +328,3 @@ func GetLazygitPath() string {
}
return `"` + filepath.ToSlash(ex) + `"`
}
func (c *OSCommand) UpdateWindowTitle() error {
if c.Platform.OS != "windows" {
return nil
}
path, getWdErr := os.Getwd()
if getWdErr != nil {
return getWdErr
}
argString := fmt.Sprint("title ", filepath.Base(path), " - Lazygit")
return c.Cmd.NewShell(argString, c.UserConfig().OS.ShellFunctionsFile).Run()
}
+14 -1
View File
@@ -1,12 +1,13 @@
//go:build !windows
// +build !windows
package oscommands
import (
"os"
"os/exec"
"runtime"
"strings"
"syscall"
)
func GetPlatform() *Platform {
@@ -34,3 +35,15 @@ func getUserShell() string {
return "bash"
}
func (c *OSCommand) UpdateWindowTitle() error {
return nil
}
func TerminateProcessGracefully(cmd *exec.Cmd) error {
if cmd.Process == nil {
return nil
}
return cmd.Process.Signal(syscall.SIGTERM)
}
@@ -1,5 +1,4 @@
//go:build !windows
// +build !windows
package oscommands
+21
View File
@@ -1,5 +1,12 @@
package oscommands
import (
"fmt"
"os"
"os/exec"
"path/filepath"
)
func GetPlatform() *Platform {
return &Platform{
OS: "windows",
@@ -7,3 +14,17 @@ func GetPlatform() *Platform {
ShellArg: "/c",
}
}
func (c *OSCommand) UpdateWindowTitle() error {
path, getWdErr := os.Getwd()
if getWdErr != nil {
return getWdErr
}
argString := fmt.Sprint("title ", filepath.Base(path), " - Lazygit")
return c.Cmd.NewShell(argString, c.UserConfig().OS.ShellFunctionsFile).Run()
}
func TerminateProcessGracefully(cmd *exec.Cmd) error {
// Signals other than SIGKILL are not supported on Windows
return nil
}
@@ -1,6 +1,3 @@
//go:build windows
// +build windows
package oscommands
import (
+37
View File
@@ -272,6 +272,7 @@ func computeMigratedConfig(path string, content []byte, changes *ChangesSet) ([]
{[]string{"gui", "skipUnstageLineWarning"}, "skipDiscardChangeWarning"},
{[]string{"keybinding", "universal", "executeCustomCommand"}, "executeShellCommand"},
{[]string{"gui", "windowSize"}, "screenMode"},
{[]string{"keybinding", "files", "openMergeTool"}, "openMergeOptions"},
}
for _, pathToReplace := range pathsToReplace {
@@ -309,6 +310,11 @@ func computeMigratedConfig(path string, content []byte, changes *ChangesSet) ([]
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err)
}
err = migratePagers(&rootNode, changes)
if err != nil {
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err)
}
// Add more migrations here...
if reflect.DeepEqual(rootNode, originalCopy) {
@@ -468,6 +474,37 @@ func migrateAllBranchesLogCmd(rootNode *yaml.Node, changes *ChangesSet) error {
})
}
func migratePagers(rootNode *yaml.Node, changes *ChangesSet) error {
return yaml_utils.TransformNode(rootNode, []string{"git"}, func(gitNode *yaml.Node) error {
pagingKeyNode, pagingValueNode := yaml_utils.LookupKey(gitNode, "paging")
if pagingKeyNode == nil || pagingValueNode.Kind != yaml.MappingNode {
// If there's no "paging" section (or it's not an object), there's nothing to do
return nil
}
pagersKeyNode, _ := yaml_utils.LookupKey(gitNode, "pagers")
if pagersKeyNode != nil {
// Conversely, if there *is* already a "pagers" array, we also have nothing to do.
// This covers the case where the user keeps both the "paging" section and the "pagers"
// array for the sake of easier testing of old versions.
return nil
}
pagingKeyNode.Value = "pagers"
pagingContentCopy := pagingValueNode.Content
pagingValueNode.Kind = yaml.SequenceNode
pagingValueNode.Tag = "!!seq"
pagingValueNode.Content = []*yaml.Node{{
Kind: yaml.MappingNode,
Content: pagingContentCopy,
}}
changes.Add("Moved git.paging object to git.pagers array")
return nil
})
}
func (c *AppConfig) GetDebug() bool {
return c.debug
}
+97 -2
View File
@@ -865,7 +865,7 @@ keybinding:
nextScreenMode: +
prevScreenMode: _
undo: z
redo: <c-z>
redo: Z
filteringMenu: <c-s>
diffingMenu: W
diffingMenu-alt: <c-e>
@@ -897,7 +897,7 @@ keybinding:
toggleStagedAll: a
viewResetOptions: D
fetch: f
openMergeTool: M
openMergeOptions: M
openStatusFilter: <c-b>
copyFileInfoToClipboard: "y"
collapseAll: '-'
@@ -1089,3 +1089,98 @@ func TestAllBranchesLogCmdMigrations(t *testing.T) {
})
}
}
func TestPagerMigration(t *testing.T) {
scenarios := []struct {
name string
input string
expected string
expectedDidChange bool
expectedChanges []string
}{
{
name: "Incomplete Configuration Passes uneventfully",
input: "git:",
expectedDidChange: false,
expectedChanges: []string{},
},
{
name: "No paging section",
input: `git:
autoFetch: true
`,
expected: `git:
autoFetch: true
`,
expectedDidChange: false,
expectedChanges: []string{},
},
{
name: "Both paging and pagers exist",
input: `git:
paging:
pager: delta --dark --paging=never
pagers:
- diff: diff-so-fancy
`,
expected: `git:
paging:
pager: delta --dark --paging=never
pagers:
- diff: diff-so-fancy
`,
expectedDidChange: false,
expectedChanges: []string{},
},
{
name: "paging is not an object",
input: `git:
paging: 5
`,
expected: `git:
paging: 5
`,
expectedDidChange: false,
expectedChanges: []string{},
},
{
name: "paging is moved to pagers array (keeping the order)",
input: `git:
paging:
pager: delta --dark --paging=never
autoFetch: true
`,
expected: `git:
pagers:
- pager: delta --dark --paging=never
autoFetch: true
`,
expectedDidChange: true,
expectedChanges: []string{"Moved git.paging object to git.pagers array"},
},
{
name: "paging is moved to pagers array even if empty",
input: `git:
paging: {}
`,
expected: `git:
pagers: [{}]
`,
expectedDidChange: true,
expectedChanges: []string{"Moved git.paging object to git.pagers array"},
},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
changes := NewChangesSet()
actual, didChange, err := computeMigratedConfig("path doesn't matter", []byte(s.input), changes)
assert.NoError(t, err)
assert.Equal(t, s.expectedDidChange, didChange)
if didChange {
assert.Equal(t, s.expected, string(actual))
}
assert.Equal(t, s.expectedChanges, changes.ToSliceFromOldest())
})
}
}
-1
View File
@@ -1,5 +1,4 @@
//go:build !windows && !linux
// +build !windows,!linux
package config
+82
View File
@@ -0,0 +1,82 @@
package config
import (
"strconv"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type PagerConfig struct {
getUserConfig func() *UserConfig
pagerIndex int
}
func NewPagerConfig(getUserConfig func() *UserConfig) *PagerConfig {
return &PagerConfig{getUserConfig: getUserConfig}
}
func (self *PagerConfig) currentPagerConfig() *PagingConfig {
pagers := self.getUserConfig().Git.Pagers
if len(pagers) == 0 {
return nil
}
// Guard against the pager index being out of range, which can happen if the user
// has removed pagers from their config file while lazygit is running.
if self.pagerIndex >= len(pagers) {
self.pagerIndex = 0
}
return &pagers[self.pagerIndex]
}
func (self *PagerConfig) GetPagerCommand(width int) string {
currentPagerConfig := self.currentPagerConfig()
if currentPagerConfig == nil {
return ""
}
templateValues := map[string]string{
"columnWidth": strconv.Itoa(width/2 - 6),
}
pagerTemplate := string(currentPagerConfig.Pager)
return utils.ResolvePlaceholderString(pagerTemplate, templateValues)
}
func (self *PagerConfig) GetColorArg() string {
currentPagerConfig := self.currentPagerConfig()
if currentPagerConfig == nil {
return "always"
}
colorArg := currentPagerConfig.ColorArg
if colorArg == "" {
return "always"
}
return colorArg
}
func (self *PagerConfig) GetExternalDiffCommand() string {
currentPagerConfig := self.currentPagerConfig()
if currentPagerConfig == nil {
return ""
}
return currentPagerConfig.ExternalDiffCommand
}
func (self *PagerConfig) GetUseExternalDiffGitConfig() bool {
currentPagerConfig := self.currentPagerConfig()
if currentPagerConfig == nil {
return false
}
return currentPagerConfig.UseExternalDiffGitConfig
}
func (self *PagerConfig) CyclePagers() {
self.pagerIndex = (self.pagerIndex + 1) % len(self.getUserConfig().Git.Pagers)
}
func (self *PagerConfig) CurrentPagerIndex() (int, int) {
return self.pagerIndex, len(self.getUserConfig().Git.Pagers)
}
+77 -61
View File
@@ -84,13 +84,14 @@ type GuiConfig struct {
SkipNoStagedFilesWarning bool `yaml:"skipNoStagedFilesWarning"`
// If true, do not show a warning when rewording a commit via an external editor
SkipRewordInEditorWarning bool `yaml:"skipRewordInEditorWarning"`
// If true, switch to a different worktree without confirmation when checking out a branch that is checked out in that worktree
SkipSwitchWorktreeOnCheckoutWarning bool `yaml:"skipSwitchWorktreeOnCheckoutWarning"`
// Fraction of the total screen width to use for the left side section. You may want to pick a small number (e.g. 0.2) if you're using a narrow screen, so that you can see more of the main section.
// Number from 0 to 1.0.
SidePanelWidth float64 `yaml:"sidePanelWidth" jsonschema:"maximum=1,minimum=0"`
// If true, increase the height of the focused side window; creating an accordion effect.
ExpandFocusedSidePanel bool `yaml:"expandFocusedSidePanel"`
// The weight of the expanded side panel, relative to the other panels. 2 means
// twice as tall as the other panels. Only relevant if `expandFocusedSidePanel` is true.
// The weight of the expanded side panel, relative to the other panels. 2 means twice as tall as the other panels. Only relevant if `expandFocusedSidePanel` is true.
ExpandedSidePanelWeight int `yaml:"expandedSidePanelWeight"`
// Sometimes the main window is split in two (e.g. when the selected file has both staged and unstaged changes). This setting controls how the two sections are split.
// Options are:
@@ -103,13 +104,11 @@ type GuiConfig struct {
// - 'left': split the window horizontally (side panel on the left, main view on the right)
// - 'top': split the window vertically (side panel on top, main view below)
EnlargedSideViewLocation string `yaml:"enlargedSideViewLocation"`
// If true, wrap lines in the staging view to the width of the view. This
// makes it much easier to work with diffs that have long lines, e.g.
// paragraphs of markdown text.
// If true, wrap lines in the staging view to the width of the view. This makes it much easier to work with diffs that have long lines, e.g. paragraphs of markdown text.
WrapLinesInStagingView bool `yaml:"wrapLinesInStagingView"`
// If true, hunk selection mode will be enabled by default when entering the staging view.
UseHunkModeInStagingView bool `yaml:"useHunkModeInStagingView"`
// One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
// One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru' | 'pt'
Language string `yaml:"language" jsonschema:"enum=auto,enum=en,enum=zh-TW,enum=zh-CN,enum=pl,enum=nl,enum=ja,enum=ko,enum=ru"`
// Format used when displaying time e.g. commit time.
// Uses Go's time format syntax: https://pkg.go.dev/time#Time.Format
@@ -236,8 +235,30 @@ type SpinnerConfig struct {
}
type GitConfig struct {
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md
Paging PagingConfig `yaml:"paging"`
// Array of pagers. Each entry has the following format:
// [dev] The following documentation is duplicated from the PagingConfig struct below.
//
// # Value of the --color arg in the git diff command. Some pagers want
// # this to be set to 'always' and some want it set to 'never'
// colorArg: "always"
//
// # e.g.
// # diff-so-fancy
// # delta --dark --paging=never
// # ydiff -p cat -s --wrap --width={{columnWidth}}
// pager: ""
//
// # e.g. 'difft --color=always'
// externalDiffCommand: ""
//
// # If true, Lazygit will use git's `diff.external` config for paging.
// # The advantage over `externalDiffCommand` is that this can be
// # configured per file type in .gitattributes; see
// # https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver.
// useExternalDiffGitConfig: false
//
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md for more information.
Pagers []PagingConfig `yaml:"pagers"`
// Config relating to committing
Commit CommitConfig `yaml:"commit"`
// Config relating to merging
@@ -255,10 +276,7 @@ type GitConfig struct {
AutoForwardBranches string `yaml:"autoForwardBranches" jsonschema:"enum=none,enum=onlyMainBranches,enum=allBranches"`
// If true, pass the --all arg to git fetch
FetchAll bool `yaml:"fetchAll"`
// If true, lazygit will automatically stage files that used to have merge
// conflicts but no longer do; and it will also ask you if you want to
// continue a merge or rebase if you've resolved all conflicts. If false, it
// won't do either of these things.
// If true, lazygit will automatically stage files that used to have merge conflicts but no longer do; and it will also ask you if you want to continue a merge or rebase if you've resolved all conflicts. If false, it won't do either of these things.
AutoStageResolvedConflicts bool `yaml:"autoStageResolvedConflicts"`
// Command used when displaying the current branch git log in the main window
BranchLogCmd string `yaml:"branchLogCmd"`
@@ -293,8 +311,7 @@ type GitConfig struct {
// One of: 'date' (default) | 'alphabetical'
// Can be changed from within Lazygit with the Sort Order menu (`s`) in the remote branches panel.
RemoteBranchSortOrder string `yaml:"remoteBranchSortOrder" jsonschema:"enum=date,enum=alphabetical"`
// When copying commit hashes to the clipboard, truncate them to this
// length. Set to 40 to disable truncation.
// When copying commit hashes to the clipboard, truncate them to this length. Set to 40 to disable truncation.
TruncateCopiedCommitHashesTo int `yaml:"truncateCopiedCommitHashesTo"`
}
@@ -308,6 +325,7 @@ func (PagerType) JSONSchemaExtend(schema *jsonschema.Schema) {
}
}
// [dev] This documentation is duplicated in the GitConfig struct. If you make changes here, make them there too.
type PagingConfig struct {
// Value of the --color arg in the git diff command. Some pagers want this to be set to 'always' and some want it set to 'never'
ColorArg string `yaml:"colorArg" jsonschema:"enum=always,enum=never"`
@@ -316,10 +334,10 @@ type PagingConfig struct {
// delta --dark --paging=never
// ydiff -p cat -s --wrap --width={{columnWidth}}
Pager PagerType `yaml:"pager"`
// If true, Lazygit will use whatever pager is specified in `$GIT_PAGER`, `$PAGER`, or your *git config*. If the pager ends with something like ` | less` we will strip that part out, because less doesn't play nice with our rendering approach. If the custom pager uses less under the hood, that will also break rendering (hence the `--paging=never` flag for the `delta` pager).
UseConfig bool `yaml:"useConfig"`
// e.g. 'difft --color=always'
ExternalDiffCommand string `yaml:"externalDiffCommand"`
// If true, Lazygit will use git's `diff.external` config for paging. The advantage over `externalDiffCommand` is that this can be configured per file type in .gitattributes; see https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver.
UseExternalDiffGitConfig bool `yaml:"useExternalDiffGitConfig"`
}
type CommitConfig struct {
@@ -343,8 +361,7 @@ type MergingConfig struct {
type LogConfig struct {
// One of: 'date-order' | 'author-date-order' | 'topo-order' | 'default'
// 'topo-order' makes it easier to read the git log graph, but commits may not
// appear chronologically. See https://git-scm.com/docs/
// 'topo-order' makes it easier to read the git log graph, but commits may not appear chronologically. See https://git-scm.com/docs/
//
// Can be changed from within Lazygit with `Log menu -> Commit sort order` (`<c-l>` in the commits window by default).
Order string `yaml:"order" jsonschema:"enum=date-order,enum=author-date-order,enum=topo-order,enum=default"`
@@ -390,6 +407,7 @@ type KeybindingConfig struct {
type KeybindingUniversalConfig struct {
Quit string `yaml:"quit"`
QuitAlt1 string `yaml:"quit-alt1"`
SuspendApp string `yaml:"suspendApp"`
Return string `yaml:"return"`
QuitWithoutChangingDirectory string `yaml:"quitWithoutChangingDirectory"`
TogglePanel string `yaml:"togglePanel"`
@@ -424,6 +442,8 @@ type KeybindingUniversalConfig struct {
Select string `yaml:"select"`
GoInto string `yaml:"goInto"`
Confirm string `yaml:"confirm"`
ConfirmMenu string `yaml:"confirmMenu"`
ConfirmSuggestion string `yaml:"confirmSuggestion"`
ConfirmInEditor string `yaml:"confirmInEditor"`
ConfirmInEditorAlt string `yaml:"confirmInEditor-alt"`
Remove string `yaml:"remove"`
@@ -446,6 +466,7 @@ type KeybindingUniversalConfig struct {
PrevTab string `yaml:"prevTab"`
NextScreenMode string `yaml:"nextScreenMode"`
PrevScreenMode string `yaml:"prevScreenMode"`
CyclePagers string `yaml:"cyclePagers"`
Undo string `yaml:"undo"`
Redo string `yaml:"redo"`
FilteringMenu string `yaml:"filteringMenu"`
@@ -484,7 +505,7 @@ type KeybindingFilesConfig struct {
ViewResetOptions string `yaml:"viewResetOptions"`
Fetch string `yaml:"fetch"`
ToggleTreeView string `yaml:"toggleTreeView"`
OpenMergeTool string `yaml:"openMergeTool"`
OpenMergeOptions string `yaml:"openMergeOptions"`
OpenStatusFilter string `yaml:"openStatusFilter"`
CopyFileInfoToClipboard string `yaml:"copyFileInfoToClipboard"`
CollapseAll string `yaml:"collapseAll"`
@@ -579,12 +600,10 @@ type OSConfig struct {
// Command for editing a file. Should contain "{{filename}}".
Edit string `yaml:"edit,omitempty"`
// Command for editing a file at a given line number. Should contain
// "{{filename}}", and may optionally contain "{{line}}".
// Command for editing a file at a given line number. Should contain "{{filename}}", and may optionally contain "{{line}}".
EditAtLine string `yaml:"editAtLine,omitempty"`
// Same as EditAtLine, except that the command needs to wait until the
// window is closed.
// Same as EditAtLine, except that the command needs to wait until the window is closed.
EditAtLineAndWait string `yaml:"editAtLineAndWait,omitempty"`
// Whether lazygit suspends until an edit process returns
@@ -595,12 +614,10 @@ type OSConfig struct {
// For opening a directory in an editor
OpenDirInEditor string `yaml:"openDirInEditor,omitempty"`
// A built-in preset that sets all of the above settings. Supported presets
// are defined in the getPreset function in editor_presets.go.
// A built-in preset that sets all of the above settings. Supported presets are defined in the getPreset function in editor_presets.go.
EditPreset string `yaml:"editPreset,omitempty" jsonschema:"example=vim,example=nvim,example=emacs,example=nano,example=vscode,example=sublime,example=kakoune,example=helix,example=xcode,example=zed,example=acme"`
// Command for opening a file, as if the file is double-clicked. Should
// contain "{{filename}}", but doesn't support "{{line}}".
// Command for opening a file, as if the file is double-clicked. Should contain "{{filename}}", but doesn't support "{{line}}".
Open string `yaml:"open,omitempty"`
// Command for opening a link. Should contain "{{link}}".
@@ -757,32 +774,33 @@ func GetDefaultConfig() *UserConfig {
UnstagedChangesColor: []string{"red"},
DefaultFgColor: []string{"default"},
},
CommitLength: CommitLengthConfig{Show: true},
SkipNoStagedFilesWarning: false,
ShowListFooter: true,
ShowCommandLog: true,
ShowBottomLine: true,
ShowPanelJumps: true,
ShowFileTree: true,
ShowRootItemInFileTree: true,
ShowNumstatInFilesView: false,
ShowRandomTip: true,
ShowIcons: false,
NerdFontsVersion: "",
ShowFileIcons: true,
CommitAuthorShortLength: 2,
CommitAuthorLongLength: 17,
CommitHashLength: 8,
ShowBranchCommitHash: false,
ShowDivergenceFromBaseBranch: "none",
CommandLogSize: 8,
SplitDiff: "auto",
SkipRewordInEditorWarning: false,
ScreenMode: "normal",
Border: "rounded",
AnimateExplosion: true,
PortraitMode: "auto",
FilterMode: "substring",
CommitLength: CommitLengthConfig{Show: true},
SkipNoStagedFilesWarning: false,
ShowListFooter: true,
ShowCommandLog: true,
ShowBottomLine: true,
ShowPanelJumps: true,
ShowFileTree: true,
ShowRootItemInFileTree: true,
ShowNumstatInFilesView: false,
ShowRandomTip: true,
ShowIcons: false,
NerdFontsVersion: "",
ShowFileIcons: true,
CommitAuthorShortLength: 2,
CommitAuthorLongLength: 17,
CommitHashLength: 8,
ShowBranchCommitHash: false,
ShowDivergenceFromBaseBranch: "none",
CommandLogSize: 8,
SplitDiff: "auto",
SkipRewordInEditorWarning: false,
SkipSwitchWorktreeOnCheckoutWarning: false,
ScreenMode: "normal",
Border: "rounded",
AnimateExplosion: true,
PortraitMode: "auto",
FilterMode: "substring",
Spinner: SpinnerConfig{
Frames: []string{"|", "/", "-", "\\"},
Rate: 50,
@@ -793,12 +811,6 @@ func GetDefaultConfig() *UserConfig {
SwitchTabsWithPanelJumpKeys: false,
},
Git: GitConfig{
Paging: PagingConfig{
ColorArg: "always",
Pager: "",
UseConfig: false,
ExternalDiffCommand: "",
},
Commit: CommitConfig{
SignOff: false,
AutoWrapCommitMessage: true,
@@ -854,6 +866,7 @@ func GetDefaultConfig() *UserConfig {
Universal: KeybindingUniversalConfig{
Quit: "q",
QuitAlt1: "<c-c>",
SuspendApp: "<c-z>",
Return: "<esc>",
QuitWithoutChangingDirectory: "Q",
TogglePanel: "<tab>",
@@ -888,6 +901,8 @@ func GetDefaultConfig() *UserConfig {
Select: "<space>",
GoInto: "<enter>",
Confirm: "<enter>",
ConfirmMenu: "<enter>",
ConfirmSuggestion: "<enter>",
ConfirmInEditor: "<a-enter>",
ConfirmInEditorAlt: "<c-s>",
Remove: "d",
@@ -911,8 +926,9 @@ func GetDefaultConfig() *UserConfig {
PrevTab: "[",
NextScreenMode: "+",
PrevScreenMode: "_",
CyclePagers: "|",
Undo: "z",
Redo: "<c-z>",
Redo: "Z",
FilteringMenu: "<c-s>",
DiffingMenu: "W",
DiffingMenuAlt: "<c-e>",
@@ -945,7 +961,7 @@ func GetDefaultConfig() *UserConfig {
ViewResetOptions: "D",
Fetch: "f",
ToggleTreeView: "`",
OpenMergeTool: "M",
OpenMergeOptions: "M",
OpenStatusFilter: "<c-b>",
ConfirmDiscard: "x",
CopyFileInfoToClipboard: "y",
+2 -2
View File
@@ -124,8 +124,8 @@ func (gui *Gui) getRandomTip() string {
formattedKey(config.Universal.Remove),
),
fmt.Sprintf(
"If you need to pull out the big guns to resolve merge conflicts, you can press '%s' in the files panel to open 'git mergetool'",
formattedKey(config.Files.OpenMergeTool),
"If you need to pull out the big guns to resolve merge conflicts, you can press '%s' in the files panel to open merge options",
formattedKey(config.Files.OpenMergeOptions),
),
fmt.Sprintf(
"To revert a commit, press '%s' on that commit",
+1 -1
View File
@@ -196,7 +196,7 @@ func (self *ContextMgr) Activate(c types.Context, opts types.OnFocusOpts) {
v.Visible = true
self.gui.c.GocuiGui().Cursor = v.Editable
self.gui.c.GocuiGui().Cursor = v.Editable && v.Mask == 0
c.HandleFocus(opts)
}
+4
View File
@@ -41,6 +41,7 @@ const (
MENU_CONTEXT_KEY types.ContextKey = "menu"
CONFIRMATION_CONTEXT_KEY types.ContextKey = "confirmation"
PROMPT_CONTEXT_KEY types.ContextKey = "prompt"
SEARCH_CONTEXT_KEY types.ContextKey = "search"
COMMIT_MESSAGE_CONTEXT_KEY types.ContextKey = "commitMessage"
COMMIT_DESCRIPTION_CONTEXT_KEY types.ContextKey = "commitDescription"
@@ -73,6 +74,7 @@ var AllContextKeys = []types.ContextKey{
MENU_CONTEXT_KEY,
CONFIRMATION_CONTEXT_KEY,
PROMPT_CONTEXT_KEY,
SEARCH_CONTEXT_KEY,
COMMIT_MESSAGE_CONTEXT_KEY,
SUBMODULES_CONTEXT_KEY,
@@ -106,6 +108,7 @@ type ContextTree struct {
CustomPatchBuilderSecondary types.Context
MergeConflicts *MergeConflictsContext
Confirmation *ConfirmationContext
Prompt *PromptContext
CommitMessage *CommitMessageContext
CommitDescription types.Context
CommandLog types.Context
@@ -141,6 +144,7 @@ func (self *ContextTree) Flatten() []types.Context {
self.Stash,
self.Menu,
self.Confirmation,
self.Prompt,
self.CommitMessage,
self.CommitDescription,
+15 -5
View File
@@ -12,9 +12,10 @@ import (
type FilteredList[T any] struct {
filteredIndices []int // if nil, we are not filtering
getList func() []T
getFilterFields func(T) []string
filter string
getList func() []T
getFilterFields func(T) []string
preprocessFilter func(string) string
filter string
mutex deadlock.Mutex
}
@@ -26,6 +27,10 @@ func NewFilteredList[T any](getList func() []T, getFilterFields func(T) []string
}
}
func (self *FilteredList[T]) SetPreprocessFilterFunc(preprocessFilter func(string) string) {
self.preprocessFilter = preprocessFilter
}
func (self *FilteredList[T]) GetFilter() string {
return self.filter
}
@@ -79,7 +84,12 @@ func (self *FilteredList[T]) applyFilter(useFuzzySearch bool) {
self.mutex.Lock()
defer self.mutex.Unlock()
if self.filter == "" {
filter := self.filter
if self.preprocessFilter != nil {
filter = self.preprocessFilter(filter)
}
if filter == "" {
self.filteredIndices = nil
} else {
source := &fuzzySource[T]{
@@ -87,7 +97,7 @@ func (self *FilteredList[T]) applyFilter(useFuzzySearch bool) {
getFilterFields: self.getFilterFields,
}
matches := utils.FindFrom(self.filter, source, useFuzzySearch)
matches := utils.FindFrom(filter, source, useFuzzySearch)
self.filteredIndices = lo.Map(matches, func(match fuzzy.Match, _ int) int {
return match.Index
})
@@ -1,5 +1,7 @@
package context
import "github.com/jesseduffield/lazygit/pkg/i18n"
type FilteredListViewModel[T HasID] struct {
*FilteredList[T]
*ListViewModel[T]
@@ -33,3 +35,8 @@ func (self *FilteredListViewModel[T]) ClearFilter() {
self.SetSelection(unfilteredIndex)
}
// Default implementation of most filterable contexts. Can be overridden if needed.
func (self *FilteredListViewModel[T]) FilterPrefix(tr *i18n.TranslationSet) string {
return tr.FilterPrefix
}
+39 -6
View File
@@ -2,10 +2,12 @@ package context
import (
"errors"
"strings"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
@@ -48,11 +50,12 @@ func NewMenuContext(
}
type MenuViewModel struct {
c *ContextCommon
menuItems []*types.MenuItem
prompt string
promptLines []string
columnAlignment []utils.Alignment
c *ContextCommon
menuItems []*types.MenuItem
prompt string
promptLines []string
columnAlignment []utils.Alignment
allowFilteringKeybindings bool
*FilteredListViewModel[*types.MenuItem]
}
@@ -62,11 +65,29 @@ func NewMenuViewModel(c *ContextCommon) *MenuViewModel {
c: c,
}
filterKeybindings := false
self.FilteredListViewModel = NewFilteredListViewModel(
func() []*types.MenuItem { return self.menuItems },
func(item *types.MenuItem) []string { return item.LabelColumns },
func(item *types.MenuItem) []string {
if filterKeybindings {
return []string{keybindings.LabelFromKey(item.Key)}
}
return item.LabelColumns
},
)
self.FilteredListViewModel.SetPreprocessFilterFunc(func(filter string) string {
if self.allowFilteringKeybindings && strings.HasPrefix(filter, "@") {
filterKeybindings = true
return filter[1:]
}
filterKeybindings = false
return filter
})
return self
}
@@ -92,6 +113,10 @@ func (self *MenuViewModel) SetPromptLines(promptLines []string) {
self.promptLines = promptLines
}
func (self *MenuViewModel) SetAllowFilteringKeybindings(allow bool) {
self.allowFilteringKeybindings = allow
}
// TODO: move into presentation package
func (self *MenuViewModel) GetDisplayStrings(_ int, _ int) [][]string {
menuItems := self.FilteredListViewModel.GetItems()
@@ -214,3 +239,11 @@ func (self *MenuContext) OnMenuPress(selectedItem *types.MenuItem) error {
func (self *MenuContext) RangeSelectEnabled() bool {
return false
}
func (self *MenuContext) FilterPrefix(tr *i18n.TranslationSet) string {
if self.allowFilteringKeybindings {
return tr.FilterPrefixMenu
}
return self.FilteredListViewModel.FilterPrefix(tr)
}
+30
View File
@@ -0,0 +1,30 @@
package context
import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type PromptContext struct {
*SimpleContext
c *ContextCommon
State ConfirmationContextState
}
var _ types.Context = (*PromptContext)(nil)
func NewPromptContext(
c *ContextCommon,
) *PromptContext {
return &PromptContext{
c: c,
SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: c.Views().Prompt,
WindowName: "prompt",
Key: PROMPT_CONTEXT_KEY,
Kind: types.TEMPORARY_POPUP,
Focusable: true,
HasUncontrolledBounds: true,
})),
}
}
+1
View File
@@ -84,6 +84,7 @@ func NewContextTree(c *ContextCommon) *ContextTree {
c,
),
Confirmation: NewConfirmationContext(c),
Prompt: NewPromptContext(c),
CommitMessage: NewCommitMessageContext(c),
CommitDescription: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
+7 -1
View File
@@ -98,7 +98,7 @@ func (gui *Gui) resetHelpersAndControllers() {
Bisect: bisectHelper,
Suggestions: suggestionsHelper,
Files: helpers.NewFilesHelper(helperCommon),
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper),
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper, rebaseHelper),
Tags: helpers.NewTagsHelper(helperCommon, commitsHelper, gpgHelper),
BranchesHelper: helpers.NewBranchesHelper(helperCommon, worktreeHelper),
GPG: helpers.NewGpgHelper(helperCommon),
@@ -109,6 +109,7 @@ func (gui *Gui) resetHelpersAndControllers() {
AmendHelper: helpers.NewAmendHelper(helperCommon, gpgHelper),
FixupHelper: helpers.NewFixupHelper(helperCommon),
Commits: commitsHelper,
SuspendResume: helpers.NewSuspendResumeHelper(helperCommon),
Snake: helpers.NewSnakeHelper(helperCommon),
Diff: diffHelper,
Repos: reposHelper,
@@ -192,6 +193,7 @@ func (gui *Gui) resetHelpersAndControllers() {
statusController := controllers.NewStatusController(common)
commandLogController := controllers.NewCommandLogController(common)
confirmationController := controllers.NewConfirmationController(common)
promptController := controllers.NewPromptController(common)
suggestionsController := controllers.NewSuggestionsController(common)
jumpToSideWindowController := controllers.NewJumpToSideWindowController(common, gui.handleNextTab)
@@ -398,6 +400,10 @@ func (gui *Gui) resetHelpersAndControllers() {
confirmationController,
)
controllers.AttachControllers(gui.State.Contexts.Prompt,
promptController,
)
controllers.AttachControllers(gui.State.Contexts.Suggestions,
suggestionsController,
)
+2 -4
View File
@@ -420,15 +420,13 @@ func (self *BranchesController) promptToCheckoutWorktree(worktree *models.Worktr
"worktreeName": worktree.Name,
})
self.c.Confirm(types.ConfirmOpts{
return self.c.ConfirmIf(!self.c.UserConfig().Gui.SkipSwitchWorktreeOnCheckoutWarning, types.ConfirmOpts{
Title: self.c.Tr.SwitchToWorktree,
Prompt: prompt,
HandleConfirm: func() error {
return self.c.Helpers().Worktree.Switch(worktree, context.LOCAL_BRANCHES_CONTEXT_KEY)
},
})
return nil
}
func (self *BranchesController) handleCreatePullRequest(selectedBranch *models.Branch) error {
@@ -490,7 +488,7 @@ func (self *BranchesController) forceCheckout() error {
func (self *BranchesController) checkoutPreviousBranch() error {
self.c.LogAction(self.c.Tr.Actions.CheckoutBranch)
return self.c.Helpers().Refs.CheckoutRef("-", types.CheckoutRefOptions{})
return self.c.Helpers().Refs.CheckoutPreviousRef()
}
func (self *BranchesController) checkoutByName() error {
+13 -36
View File
@@ -1,9 +1,6 @@
package controllers
import (
"fmt"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -39,38 +36,19 @@ func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) [
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
Handler: func() error {
if len(self.c.Contexts().Suggestions.State.Suggestions) > 0 {
self.switchToSuggestions()
}
return nil
},
Key: opts.GetKey(opts.Config.Universal.CopyToClipboard),
Handler: self.handleCopyToClipboard,
Description: self.c.Tr.CopyToClipboardMenu,
DisplayOnScreen: true,
},
}
return bindings
}
func (self *ConfirmationController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{
{
ViewName: self.c.Contexts().Suggestions.GetViewName(),
FocusedView: self.c.Contexts().Confirmation.GetViewName(),
Key: gocui.MouseLeft,
Handler: func(gocui.ViewMouseBindingOpts) error {
self.switchToSuggestions()
// Let it fall through to the ListController's click handler so that
// the clicked line gets selected:
return gocui.ErrKeybindingNotHandled
},
},
}
}
func (self *ConfirmationController) GetOnFocusLost() func(types.OnFocusLostOpts) {
return func(types.OnFocusLostOpts) {
self.c.Helpers().Confirmation.DeactivateConfirmationPrompt()
self.c.Helpers().Confirmation.DeactivateConfirmation()
}
}
@@ -82,14 +60,13 @@ func (self *ConfirmationController) context() *context.ConfirmationContext {
return self.c.Contexts().Confirmation
}
func (self *ConfirmationController) switchToSuggestions() {
subtitle := ""
if self.c.State().GetRepoState().GetCurrentPopupOpts().HandleDeleteSuggestion != nil {
// We assume that whenever things are deletable, they
// are also editable, so we show both keybindings
subtitle = fmt.Sprintf(self.c.Tr.SuggestionsSubtitle,
self.c.UserConfig().Keybinding.Universal.Remove, self.c.UserConfig().Keybinding.Universal.Edit)
func (self *ConfirmationController) handleCopyToClipboard() error {
confirmationView := self.c.Views().Confirmation
text := confirmationView.Buffer()
if err := self.c.OS().CopyToClipboard(text); err != nil {
return err
}
self.c.Views().Suggestions.Subtitle = subtitle
self.c.Context().Replace(self.c.Contexts().Suggestions)
self.c.Toast(self.c.Tr.MessageCopiedToClipboard)
return nil
}
@@ -173,7 +173,7 @@ func (self *CustomPatchOptionsMenuAction) handleMovePatchIntoWorkingTree() error
self.returnFocusFromPatchExplorerIfNecessary()
mustStash := self.c.Helpers().WorkingTree.IsWorkingTreeDirty()
mustStash := self.c.Helpers().WorkingTree.IsWorkingTreeDirtyExceptSubmodules()
return self.c.ConfirmIf(mustStash, types.ConfirmOpts{
Title: self.c.Tr.MustStashTitle,
Prompt: self.c.Tr.MustStashWarning,
+76 -12
View File
@@ -178,10 +178,13 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
Description: self.c.Tr.OpenDiffTool,
},
{
Key: opts.GetKey(opts.Config.Files.OpenMergeTool),
Handler: self.c.Helpers().WorkingTree.OpenMergeTool,
Description: self.c.Tr.OpenMergeTool,
Tooltip: self.c.Tr.OpenMergeToolTooltip,
Key: opts.GetKey(opts.Config.Files.OpenMergeOptions),
Handler: self.withItems(self.openMergeConflictMenu),
Description: self.c.Tr.ViewMergeConflictOptions,
Tooltip: self.c.Tr.ViewMergeConflictOptionsTooltip,
GetDisabledReason: self.require(self.itemsSelected(self.canOpenMergeConflictMenu)),
OpensMenu: true,
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Files.Fetch),
@@ -573,7 +576,7 @@ func (self *FilesController) handleNonInlineConflict(file *models.File) error {
OnPress: func() error {
return handle(self.c.Git().WorkingTree.StageFile, self.c.Tr.Actions.ResolveConflictByKeepingFile)
},
Key: 'k',
Key: 'p',
}
deleteItem := &types.MenuItem{
Label: self.c.Tr.MergeConflictDeleteFile,
@@ -970,7 +973,7 @@ func (self *FilesController) createStashMenu() error {
{
Label: self.c.Tr.StashAllChanges,
OnPress: func() error {
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirtyExceptSubmodules() {
return errors.New(self.c.Tr.NoFilesToStash)
}
return self.handleStashSave(self.c.Git().Stash.Push, self.c.Tr.Actions.StashAllChanges)
@@ -980,7 +983,7 @@ func (self *FilesController) createStashMenu() error {
{
Label: self.c.Tr.StashAllChangesKeepIndex,
OnPress: func() error {
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirtyExceptSubmodules() {
return errors.New(self.c.Tr.NoFilesToStash)
}
// if there are no staged files it behaves the same as Stash.Save
@@ -999,7 +1002,7 @@ func (self *FilesController) createStashMenu() error {
Label: self.c.Tr.StashStagedChanges,
OnPress: func() error {
// there must be something in staging otherwise the current implementation mucks the stash up
if !self.c.Helpers().WorkingTree.AnyStagedFiles() {
if !self.c.Helpers().WorkingTree.AnyStagedFilesExceptSubmodules() {
return errors.New(self.c.Tr.NoTrackedStagedFilesStash)
}
return self.handleStashSave(self.c.Git().Stash.SaveStagedChanges, self.c.Tr.Actions.StashStagedChanges)
@@ -1009,10 +1012,10 @@ func (self *FilesController) createStashMenu() error {
{
Label: self.c.Tr.StashUnstagedChanges,
OnPress: func() error {
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirty() {
if !self.c.Helpers().WorkingTree.IsWorkingTreeDirtyExceptSubmodules() {
return errors.New(self.c.Tr.NoFilesToStash)
}
if self.c.Helpers().WorkingTree.AnyStagedFiles() {
if self.c.Helpers().WorkingTree.AnyStagedFilesExceptSubmodules() {
return self.handleStashSave(self.c.Git().Stash.StashUnstagedChanges, self.c.Tr.Actions.StashUnstagedChanges)
}
// ordinary stash
@@ -1024,6 +1027,34 @@ func (self *FilesController) createStashMenu() error {
})
}
func (self *FilesController) openMergeConflictMenu(nodes []*filetree.FileNode) error {
normalizedNodes := flattenSelectedNodesToFiles(nodes)
fileNodesWithConflicts := lo.Filter(normalizedNodes, func(node *filetree.FileNode, _ int) bool {
return node.File != nil && node.File.HasInlineMergeConflicts
})
filepaths := lo.Map(fileNodesWithConflicts, func(node *filetree.FileNode, _ int) string {
return node.GetPath()
})
return self.c.Helpers().WorkingTree.CreateMergeConflictMenu(filepaths)
}
func (self *FilesController) canOpenMergeConflictMenu(nodes []*filetree.FileNode) *types.DisabledReason {
normalizedNodes := flattenSelectedNodesToFiles(nodes)
hasFileNodesWithConflicts := lo.SomeBy(normalizedNodes, func(node *filetree.FileNode) bool {
return node.File != nil && node.File.HasInlineMergeConflicts
})
if !hasFileNodesWithConflicts {
return &types.DisabledReason{Text: self.c.Tr.NoFilesWithMergeConflicts}
}
return nil
}
func (self *FilesController) openCopyMenu() error {
node := self.context().GetSelected()
@@ -1221,13 +1252,14 @@ func normalisedSelectedNodes(selectedNodes []*filetree.FileNode) []*filetree.Fil
}
func isDescendentOfSelectedNodes(node *filetree.FileNode, selectedNodes []*filetree.FileNode) bool {
nodePath := node.GetInternalPath()
for _, selectedNode := range selectedNodes {
if selectedNode.IsFile() {
continue
}
selectedNodePath := selectedNode.GetPath()
nodePath := node.GetPath()
selectedNodePath := selectedNode.GetInternalPath()
if strings.HasPrefix(nodePath, selectedNodePath+"/") {
return true
@@ -1236,6 +1268,38 @@ func isDescendentOfSelectedNodes(node *filetree.FileNode, selectedNodes []*filet
return false
}
// BFS algorithm for expanding directories into their children,
// and for collecting the unique file nodes
func flattenSelectedNodesToFiles(selectedNodes []*filetree.FileNode) []*filetree.FileNode {
queue := append(make([]*filetree.FileNode, 0, len(selectedNodes)), selectedNodes...)
visited := set.New[string]()
var files []*filetree.FileNode
for len(queue) > 0 {
// pop node from queue
node := queue[0]
queue = queue[1:]
nodeID := node.ID()
if visited.Includes(nodeID) {
continue
}
visited.Add(nodeID)
if node.File != nil {
// unique file node -> collect it
files = append(files, node)
continue
}
// directory node -> enqueue children
for _, ch := range node.Children {
queue = append(queue, &filetree.FileNode{Node: ch})
}
}
return files
}
func someNodesHaveUnstagedChanges(nodes []*filetree.FileNode) bool {
return lo.SomeBy(nodes, (*filetree.FileNode).GetHasUnstagedChanges)
}
+65 -5
View File
@@ -1,6 +1,8 @@
package controllers
import (
"fmt"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -58,6 +60,22 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type
Handler: opts.Guards.NoPopupPanel(self.prevScreenMode),
Description: self.c.Tr.PrevScreenMode,
},
{
Key: opts.GetKey(opts.Config.Universal.CyclePagers),
Handler: opts.Guards.NoPopupPanel(self.cyclePagers),
GetDisabledReason: self.canCyclePagers,
Description: self.c.Tr.CyclePagers,
Tooltip: self.c.Tr.CyclePagersTooltip,
},
{
Key: opts.GetKey(opts.Config.Universal.Return),
Modifier: gocui.ModNone,
Handler: self.escape,
Description: self.c.Tr.Cancel,
DescriptionFunc: self.escapeDescription,
GetDisabledReason: self.escapeEnabled,
DisplayOnScreen: true,
},
{
ViewName: "",
Key: opts.GetKey(opts.Config.Universal.OptionMenu),
@@ -115,11 +133,18 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type
Handler: self.quitWithoutChangingDirectory,
},
{
Key: opts.GetKey(opts.Config.Universal.Return),
Modifier: gocui.ModNone,
Handler: self.escape,
Description: self.c.Tr.Cancel,
DisplayOnScreen: true,
Key: opts.GetKey(opts.Config.Universal.SuspendApp),
Modifier: gocui.ModNone,
Handler: self.c.Helpers().SuspendResume.SuspendApp,
Description: self.c.Tr.SuspendApp,
GetDisabledReason: func() *types.DisabledReason {
if !self.c.Helpers().SuspendResume.CanSuspendApp() {
return &types.DisabledReason{
Text: self.c.Tr.CannotSuspendApp,
}
}
return nil
},
},
{
Key: opts.GetKey(opts.Config.Universal.ToggleWhitespaceInDiffView),
@@ -155,6 +180,27 @@ func (self *GlobalController) prevScreenMode() error {
return (&ScreenModeActions{c: self.c}).Prev()
}
func (self *GlobalController) cyclePagers() error {
self.c.State().GetPagerConfig().CyclePagers()
if self.c.Context().CurrentSide().GetKey() == self.c.Context().Current().GetKey() {
self.c.Context().CurrentSide().HandleFocus(types.OnFocusOpts{})
}
current, total := self.c.State().GetPagerConfig().CurrentPagerIndex()
self.c.Toast(fmt.Sprintf("Selected pager %d of %d", current+1, total))
return nil
}
func (self *GlobalController) canCyclePagers() *types.DisabledReason {
_, total := self.c.State().GetPagerConfig().CurrentPagerIndex()
if total <= 1 {
return &types.DisabledReason{
Text: self.c.Tr.CyclePagersDisabledReason,
}
}
return nil
}
func (self *GlobalController) createOptionsMenu() error {
return (&OptionsMenuAction{c: self.c}).Call()
}
@@ -190,6 +236,20 @@ func (self *GlobalController) escape() error {
return (&QuitActions{c: self.c}).Escape()
}
func (self *GlobalController) escapeDescription() string {
return (&QuitActions{c: self.c}).EscapeDescription()
}
func (self *GlobalController) escapeEnabled() *types.DisabledReason {
if (&QuitActions{c: self.c}).EscapeEnabled() {
return nil
}
// The empty error text is intentional. We don't want to show an error
// toast for this, but only hide it from the options map.
return &types.DisabledReason{Text: ""}
}
func (self *GlobalController) toggleWhitespace() error {
return (&ToggleWhitespaceAction{c: self.c}).Call()
}
@@ -283,7 +283,9 @@ func (self *BranchesHelper) AutoForwardBranches() error {
updateCommands := ""
// The first branch is the currently checked out branch; skip it
for _, branch := range branches[1:] {
if branch.RemoteBranchStoredLocally() && (allBranches || lo.Contains(self.c.UserConfig().Git.MainBranches, branch.Name)) {
if branch.RemoteBranchStoredLocally() &&
!self.checkedOutByOtherWorktree(branch) &&
(allBranches || lo.Contains(self.c.UserConfig().Git.MainBranches, branch.Name)) {
isStrictlyBehind := branch.IsBehindForPull() && !branch.IsAheadForPull()
if isStrictlyBehind {
updateCommands += fmt.Sprintf("update %s %s %s\n", branch.FullRefName(), branch.FullUpstreamRefName(), branch.CommitHash)
@@ -77,7 +77,7 @@ func (self *CherryPickHelper) Paste() error {
}),
HandleConfirm: func() error {
return self.c.WithWaitingStatusSync(self.c.Tr.CherryPickingStatus, func() error {
mustStash := IsWorkingTreeDirty(self.c.Model().Files)
mustStash := IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules)
self.c.LogAction(self.c.Tr.Actions.CherryPick)
@@ -3,6 +3,7 @@ package helpers
import (
goContext "context"
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
@@ -25,6 +26,14 @@ func NewConfirmationHelper(c *HelperCommon) *ConfirmationHelper {
func (self *ConfirmationHelper) wrappedConfirmationFunction(cancel goContext.CancelFunc, function func() error) func() error {
return func() error {
if self.c.GocuiGui().IsPasting {
// The user is pasting multi-line text into a prompt; we don't want to handle the
// line feeds as "confirm" keybindings. Simply ignoring them is the best we can do; this
// will cause the entire pasted text to appear as a single line in the prompt. Hopefully
// the user knows that ctrl-u allows them to delete it again...
return nil
}
cancel()
self.c.Context().Pop()
@@ -45,17 +54,27 @@ func (self *ConfirmationHelper) wrappedPromptConfirmationFunction(cancel goConte
})
}
func (self *ConfirmationHelper) DeactivateConfirmationPrompt() {
func (self *ConfirmationHelper) DeactivateConfirmation() {
self.c.Mutexes().PopupMutex.Lock()
self.c.State().GetRepoState().SetCurrentPopupOpts(nil)
self.c.Mutexes().PopupMutex.Unlock()
self.c.Views().Confirmation.Visible = false
self.c.Views().Suggestions.Visible = false
self.clearConfirmationViewKeyBindings()
}
func (self *ConfirmationHelper) DeactivatePrompt() {
self.c.Mutexes().PopupMutex.Lock()
self.c.State().GetRepoState().SetCurrentPopupOpts(nil)
self.c.Mutexes().PopupMutex.Unlock()
self.c.Views().Prompt.Visible = false
self.c.Views().Suggestions.Visible = false
self.clearPromptViewKeyBindings()
}
func getMessageHeight(wrap bool, editable bool, message string, width int, tabWidth int) int {
wrappedLines, _, _ := utils.WrapViewLinesToWidth(wrap, editable, message, width, tabWidth)
return len(wrappedLines)
@@ -104,15 +123,28 @@ func (self *ConfirmationHelper) prepareConfirmationPanel(
opts types.ConfirmOpts,
) {
self.c.Views().Confirmation.Title = opts.Title
// for now we do not support wrapping in our editor
self.c.Views().Confirmation.Wrap = !opts.Editable
self.c.Views().Confirmation.FgColor = theme.GocuiDefaultTextColor
self.c.Views().Confirmation.Mask = runeForMask(opts.Mask)
self.c.Views().Confirmation.SetOrigin(0, 0)
suggestionsContext := self.c.Contexts().Suggestions
suggestionsContext.State.FindSuggestions = opts.FindSuggestionsFunc
self.c.ResetViewOrigin(self.c.Views().Confirmation)
self.c.SetViewContent(self.c.Views().Confirmation, style.AttrBold.Sprint(strings.TrimSpace(opts.Prompt)))
}
func (self *ConfirmationHelper) preparePromptPanel(
opts types.ConfirmOpts,
) {
self.c.Views().Prompt.Title = opts.Title
self.c.Views().Prompt.FgColor = theme.GocuiDefaultTextColor
self.c.Views().Prompt.Mask = runeForMask(opts.Mask)
self.c.Views().Prompt.SetOrigin(0, 0)
textArea := self.c.Views().Prompt.TextArea
textArea.Clear()
textArea.TypeString(opts.Prompt)
self.c.Views().Prompt.RenderTextArea()
if opts.FindSuggestionsFunc != nil {
suggestionsContext := self.c.Contexts().Suggestions
suggestionsContext.State.FindSuggestions = opts.FindSuggestionsFunc
suggestionsView := self.c.Views().Suggestions
suggestionsView.Wrap = false
suggestionsView.FgColor = theme.GocuiDefaultTextColor
@@ -149,44 +181,59 @@ func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts typ
// remove any previous keybindings
self.clearConfirmationViewKeyBindings()
self.clearPromptViewKeyBindings()
self.prepareConfirmationPanel(
types.ConfirmOpts{
Title: opts.Title,
Prompt: opts.Prompt,
FindSuggestionsFunc: opts.FindSuggestionsFunc,
Editable: opts.Editable,
Mask: opts.Mask,
})
confirmationView := self.c.Views().Confirmation
confirmationView.Editable = opts.Editable
var context types.Context
if opts.Editable {
textArea := confirmationView.TextArea
textArea.Clear()
textArea.TypeString(opts.Prompt)
confirmationView.RenderTextArea()
} else {
self.c.ResetViewOrigin(confirmationView)
self.c.SetViewContent(confirmationView, style.AttrBold.Sprint(opts.Prompt))
}
self.c.Contexts().Suggestions.State.FindSuggestions = opts.FindSuggestionsFunc
self.setKeyBindings(cancel, opts)
self.preparePromptPanel(
types.ConfirmOpts{
Title: opts.Title,
Prompt: opts.Prompt,
FindSuggestionsFunc: opts.FindSuggestionsFunc,
Mask: opts.Mask,
})
context = self.c.Contexts().Prompt
self.setPromptKeyBindings(cancel, opts)
} else {
if opts.FindSuggestionsFunc != nil {
panic("non-editable confirmation views do not support suggestions")
}
self.c.Contexts().Suggestions.State.FindSuggestions = nil
self.prepareConfirmationPanel(
types.ConfirmOpts{
Title: opts.Title,
Prompt: opts.Prompt,
})
context = self.c.Contexts().Confirmation
self.setConfirmationKeyBindings(cancel, opts)
}
self.c.Contexts().Suggestions.State.AllowEditSuggestion = opts.AllowEditSuggestion
self.c.State().GetRepoState().SetCurrentPopupOpts(&opts)
self.c.Context().Push(self.c.Contexts().Confirmation, types.OnFocusOpts{})
self.c.Context().Push(context, types.OnFocusOpts{})
}
func (self *ConfirmationHelper) setKeyBindings(cancel goContext.CancelFunc, opts types.CreatePopupPanelOpts) {
var onConfirm func() error
if opts.HandleConfirmPrompt != nil {
onConfirm = self.wrappedPromptConfirmationFunction(cancel, opts.HandleConfirmPrompt, func() string { return self.c.Views().Confirmation.TextArea.GetContent() })
} else {
onConfirm = self.wrappedConfirmationFunction(cancel, opts.HandleConfirm)
}
func (self *ConfirmationHelper) setConfirmationKeyBindings(cancel goContext.CancelFunc, opts types.CreatePopupPanelOpts) {
onConfirm := self.wrappedConfirmationFunction(cancel, opts.HandleConfirm)
onClose := self.wrappedConfirmationFunction(cancel, opts.HandleClose)
self.c.Contexts().Confirmation.State.OnConfirm = onConfirm
self.c.Contexts().Confirmation.State.OnClose = onClose
}
func (self *ConfirmationHelper) setPromptKeyBindings(cancel goContext.CancelFunc, opts types.CreatePopupPanelOpts) {
onConfirm := self.wrappedPromptConfirmationFunction(cancel, opts.HandleConfirmPrompt,
func() string { return self.c.Views().Prompt.TextArea.GetContent() })
onSuggestionConfirm := self.wrappedPromptConfirmationFunction(
cancel,
@@ -205,8 +252,8 @@ func (self *ConfirmationHelper) setKeyBindings(cancel goContext.CancelFunc, opts
return opts.HandleDeleteSuggestion(idx)
}
self.c.Contexts().Confirmation.State.OnConfirm = onConfirm
self.c.Contexts().Confirmation.State.OnClose = onClose
self.c.Contexts().Prompt.State.OnConfirm = onConfirm
self.c.Contexts().Prompt.State.OnClose = onClose
self.c.Contexts().Suggestions.State.OnConfirm = onSuggestionConfirm
self.c.Contexts().Suggestions.State.OnClose = onClose
self.c.Contexts().Suggestions.State.OnDeleteSuggestion = onDeleteSuggestion
@@ -216,6 +263,12 @@ func (self *ConfirmationHelper) clearConfirmationViewKeyBindings() {
noop := func() error { return nil }
self.c.Contexts().Confirmation.State.OnConfirm = noop
self.c.Contexts().Confirmation.State.OnClose = noop
}
func (self *ConfirmationHelper) clearPromptViewKeyBindings() {
noop := func() error { return nil }
self.c.Contexts().Prompt.State.OnConfirm = noop
self.c.Contexts().Prompt.State.OnClose = noop
self.c.Contexts().Suggestions.State.OnConfirm = noop
self.c.Contexts().Suggestions.State.OnClose = noop
self.c.Contexts().Suggestions.State.OnDeleteSuggestion = noop
@@ -237,8 +290,10 @@ func (self *ConfirmationHelper) ResizeCurrentPopupPanels() {
switch c {
case self.c.Contexts().Menu:
self.resizeMenu(parentPopupContext)
case self.c.Contexts().Confirmation, self.c.Contexts().Suggestions:
case self.c.Contexts().Confirmation:
self.resizeConfirmationPanel(parentPopupContext)
case self.c.Contexts().Prompt, self.c.Contexts().Suggestions:
self.resizePromptPanel(parentPopupContext)
case self.c.Contexts().CommitMessage, self.c.Contexts().CommitDescription:
self.ResizeCommitMessagePanels(parentPopupContext)
}
@@ -299,26 +354,30 @@ func (self *ConfirmationHelper) layoutMenuPrompt(contentWidth int) int {
}
func (self *ConfirmationHelper) resizeConfirmationPanel(parentPopupContext types.Context) {
panelWidth := self.getPopupPanelWidth()
contentWidth := panelWidth - 2 // minus 2 for the frame
confirmationView := self.c.Views().Confirmation
prompt := confirmationView.Buffer()
panelHeight := getMessageHeight(true, false, prompt, contentWidth, confirmationView.TabWidth)
x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight, parentPopupContext)
_, _ = self.c.GocuiGui().SetView(confirmationView.Name(), x0, y0, x1, y1, 0)
}
func (self *ConfirmationHelper) resizePromptPanel(parentPopupContext types.Context) {
suggestionsViewHeight := 0
if self.c.Views().Suggestions.Visible {
suggestionsViewHeight = 11
}
panelWidth := self.getPopupPanelWidth()
contentWidth := panelWidth - 2 // minus 2 for the frame
confirmationView := self.c.Views().Confirmation
prompt := confirmationView.Buffer()
wrap := true
editable := confirmationView.Editable
if editable {
prompt = confirmationView.TextArea.GetContent()
wrap = false
}
panelHeight := getMessageHeight(wrap, editable, prompt, contentWidth, confirmationView.TabWidth) + suggestionsViewHeight
promptView := self.c.Views().Prompt
prompt := promptView.TextArea.GetContent()
panelHeight := getMessageHeight(false, true, prompt, contentWidth, promptView.TabWidth) + suggestionsViewHeight
x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight, parentPopupContext)
confirmationViewBottom := y1 - suggestionsViewHeight
_, _ = self.c.GocuiGui().SetView(confirmationView.Name(), x0, y0, x1, confirmationViewBottom, 0)
promptViewBottom := y1 - suggestionsViewHeight
_, _ = self.c.GocuiGui().SetView(promptView.Name(), x0, y0, x1, promptViewBottom, 0)
suggestionsViewTop := confirmationViewBottom + 1
suggestionsViewTop := promptViewBottom + 1
_, _ = self.c.GocuiGui().SetView(self.c.Views().Suggestions.Name(), x0, suggestionsViewTop, x1, suggestionsViewTop+suggestionsViewHeight, 0)
}
@@ -347,7 +406,7 @@ func (self *ConfirmationHelper) IsPopupPanelFocused() bool {
func (self *ConfirmationHelper) TooltipForMenuItem(menuItem *types.MenuItem) string {
tooltip := menuItem.Tooltip
if menuItem.DisabledReason != nil {
if menuItem.DisabledReason != nil && menuItem.DisabledReason.Text != "" {
if tooltip != "" {
tooltip += "\n\n"
}
+1 -1
View File
@@ -194,7 +194,7 @@ func parseDiff(diff string) ([]*hunk, []*hunk) {
if strings.HasPrefix(line, "diff --git") {
finishHunk()
currentHunk = nil
} else if strings.HasPrefix(line, "--- ") {
} else if currentHunk == nil && strings.HasPrefix(line, "--- ") {
// For some reason, the line ends with a tab character if the file
// name contains spaces
filename = strings.TrimRight(line[6:], "\t")
@@ -78,6 +78,26 @@ index 9ce8efb33..fb5e469e7 100644
},
},
},
{
name: "hunk with dashed lines",
diff: `
diff --git a/file1.txt b/file1.txt
index 9ce8efb33..fb5e469e7 100644
--- a/file1.txt
+++ b/file1.txt
@@ -3,1 +3,1 @@
--- xxx
+-- yyy
`,
expectedDeletedLineHunks: []*hunk{
{
filename: "file1.txt",
startLineIdx: 3,
numLines: 1,
},
},
expectedAddedLineHunks: []*hunk{},
},
{
name: "several hunks in different files",
diff: `
+1
View File
@@ -35,6 +35,7 @@ type Helpers struct {
AmendHelper *AmendHelper
FixupHelper *FixupHelper
Commits *CommitsHelper
SuspendResume *SuspendResumeHelper
Snake *SnakeHelper
// lives in context package because our contexts need it to render to main
Diff *DiffHelper
@@ -157,7 +157,7 @@ func (self *MergeAndRebaseHelper) CheckMergeOrRebaseWithRefreshOptions(result er
} else if strings.Contains(result.Error(), "No changes - did you forget to use") {
return self.genericMergeCommand(REBASE_OPTION_SKIP)
} else if strings.Contains(result.Error(), "The previous cherry-pick is now empty") {
return self.genericMergeCommand(REBASE_OPTION_CONTINUE)
return self.genericMergeCommand(REBASE_OPTION_SKIP)
} else if strings.Contains(result.Error(), "No rebase in progress?") {
// assume in this case that we're already done
return nil
@@ -381,38 +381,103 @@ func (self *MergeAndRebaseHelper) MergeRefIntoCheckedOutBranch(refName string) e
return errors.New(self.c.Tr.CantMergeBranchIntoItself)
}
wantFastForward, wantNonFastForward := self.fastForwardMergeUserPreference()
canFastForward := self.c.Git().Branch.CanDoFastForwardMerge(refName)
var firstRegularMergeItem *types.MenuItem
var secondRegularMergeItem *types.MenuItem
var fastForwardMergeItem *types.MenuItem
if !wantNonFastForward && (wantFastForward || canFastForward) {
firstRegularMergeItem = &types.MenuItem{
Label: self.c.Tr.RegularMergeFastForward,
OnPress: self.RegularMerge(refName, git_commands.MERGE_VARIANT_REGULAR),
Key: 'm',
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.RegularMergeFastForwardTooltip,
map[string]string{
"checkedOutBranch": checkedOutBranchName,
"selectedBranch": refName,
},
),
}
fastForwardMergeItem = firstRegularMergeItem
secondRegularMergeItem = &types.MenuItem{
Label: self.c.Tr.RegularMergeNonFastForward,
OnPress: self.RegularMerge(refName, git_commands.MERGE_VARIANT_NON_FAST_FORWARD),
Key: 'n',
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.RegularMergeNonFastForwardTooltip,
map[string]string{
"checkedOutBranch": checkedOutBranchName,
"selectedBranch": refName,
},
),
}
} else {
firstRegularMergeItem = &types.MenuItem{
Label: self.c.Tr.RegularMergeNonFastForward,
OnPress: self.RegularMerge(refName, git_commands.MERGE_VARIANT_REGULAR),
Key: 'm',
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.RegularMergeNonFastForwardTooltip,
map[string]string{
"checkedOutBranch": checkedOutBranchName,
"selectedBranch": refName,
},
),
}
secondRegularMergeItem = &types.MenuItem{
Label: self.c.Tr.RegularMergeFastForward,
OnPress: self.RegularMerge(refName, git_commands.MERGE_VARIANT_FAST_FORWARD),
Key: 'f',
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.RegularMergeFastForwardTooltip,
map[string]string{
"checkedOutBranch": checkedOutBranchName,
"selectedBranch": refName,
},
),
}
fastForwardMergeItem = secondRegularMergeItem
}
if !canFastForward {
fastForwardMergeItem.DisabledReason = &types.DisabledReason{
Text: utils.ResolvePlaceholderString(
self.c.Tr.CannotFastForwardMerge,
map[string]string{
"checkedOutBranch": checkedOutBranchName,
"selectedBranch": refName,
},
),
}
}
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.Merge,
Items: []*types.MenuItem{
firstRegularMergeItem,
secondRegularMergeItem,
{
Label: self.c.Tr.RegularMerge,
OnPress: self.RegularMerge(refName),
Key: 'm',
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.RegularMergeTooltip,
map[string]string{
"checkedOutBranch": checkedOutBranchName,
"selectedBranch": refName,
},
),
},
{
Label: self.c.Tr.SquashMergeUncommittedTitle,
Label: self.c.Tr.SquashMergeUncommitted,
OnPress: self.SquashMergeUncommitted(refName),
Key: 's',
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.SquashMergeUncommitted,
self.c.Tr.SquashMergeUncommittedTooltip,
map[string]string{
"selectedBranch": refName,
},
),
},
{
Label: self.c.Tr.SquashMergeCommittedTitle,
Label: self.c.Tr.SquashMergeCommitted,
OnPress: self.SquashMergeCommitted(refName, checkedOutBranchName),
Key: 'S',
Tooltip: utils.ResolvePlaceholderString(
self.c.Tr.SquashMergeCommitted,
self.c.Tr.SquashMergeCommittedTooltip,
map[string]string{
"checkedOutBranch": checkedOutBranchName,
"selectedBranch": refName,
@@ -423,10 +488,10 @@ func (self *MergeAndRebaseHelper) MergeRefIntoCheckedOutBranch(refName string) e
})
}
func (self *MergeAndRebaseHelper) RegularMerge(refName string) func() error {
func (self *MergeAndRebaseHelper) RegularMerge(refName string, variant git_commands.MergeVariant) func() error {
return func() error {
self.c.LogAction(self.c.Tr.Actions.Merge)
err := self.c.Git().Branch.Merge(refName, git_commands.MergeOpts{})
err := self.c.Git().Branch.Merge(refName, variant)
return self.CheckMergeOrRebase(err)
}
}
@@ -434,7 +499,7 @@ func (self *MergeAndRebaseHelper) RegularMerge(refName string) func() error {
func (self *MergeAndRebaseHelper) SquashMergeUncommitted(refName string) func() error {
return func() error {
self.c.LogAction(self.c.Tr.Actions.SquashMerge)
err := self.c.Git().Branch.Merge(refName, git_commands.MergeOpts{Squash: true})
err := self.c.Git().Branch.Merge(refName, git_commands.MERGE_VARIANT_SQUASH)
return self.CheckMergeOrRebase(err)
}
}
@@ -442,7 +507,7 @@ func (self *MergeAndRebaseHelper) SquashMergeUncommitted(refName string) func()
func (self *MergeAndRebaseHelper) SquashMergeCommitted(refName, checkedOutBranchName string) func() error {
return func() error {
self.c.LogAction(self.c.Tr.Actions.SquashMerge)
err := self.c.Git().Branch.Merge(refName, git_commands.MergeOpts{Squash: true})
err := self.c.Git().Branch.Merge(refName, git_commands.MERGE_VARIANT_SQUASH)
if err = self.CheckMergeOrRebase(err); err != nil {
return err
}
@@ -459,6 +524,31 @@ func (self *MergeAndRebaseHelper) SquashMergeCommitted(refName, checkedOutBranch
}
}
// Returns wantsFastForward, wantsNonFastForward. These will never both be true, but they can both be false.
func (self *MergeAndRebaseHelper) fastForwardMergeUserPreference() (bool, bool) {
// Check user config first, because it takes precedence over git config
mergingArgs := self.c.UserConfig().Git.Merging.Args
if strings.Contains(mergingArgs, "--ff") { // also covers "--ff-only"
return true, false
}
if strings.Contains(mergingArgs, "--no-ff") {
return false, true
}
// Then check git config
mergeFfConfig := self.c.Git().Config.GetMergeFF()
if mergeFfConfig == "true" || mergeFfConfig == "only" {
return true, false
}
if mergeFfConfig == "false" {
return false, true
}
return false, false
}
func (self *MergeAndRebaseHelper) ResetMarkedBaseCommit() error {
self.c.Modes().MarkedBaseCommit.Reset()
self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
+30 -8
View File
@@ -40,7 +40,8 @@ func NewModeHelper(
type ModeStatus struct {
IsActive func() bool
Description func() string
InfoLabel func() string
CancelLabel func() string
Reset func() error
}
@@ -48,7 +49,7 @@ func (self *ModeHelper) Statuses() []ModeStatus {
return []ModeStatus{
{
IsActive: self.c.Modes().Diffing.Active,
Description: func() string {
InfoLabel: func() string {
return self.withResetButton(
fmt.Sprintf(
"%s %s",
@@ -58,18 +59,24 @@ func (self *ModeHelper) Statuses() []ModeStatus {
style.FgMagenta,
)
},
CancelLabel: func() string {
return self.c.Tr.CancelDiffingMode
},
Reset: self.diffHelper.ExitDiffMode,
},
{
IsActive: self.c.Git().Patch.PatchBuilder.Active,
Description: func() string {
InfoLabel: func() string {
return self.withResetButton(self.c.Tr.BuildingPatch, style.FgYellow.SetBold())
},
CancelLabel: func() string {
return self.c.Tr.ExitCustomPatchBuilder
},
Reset: self.patchBuildingHelper.Reset,
},
{
IsActive: self.c.Modes().Filtering.Active,
Description: func() string {
InfoLabel: func() string {
filterContent := lo.Ternary(self.c.Modes().Filtering.GetPath() != "", self.c.Modes().Filtering.GetPath(), self.c.Modes().Filtering.GetAuthor())
return self.withResetButton(
fmt.Sprintf(
@@ -80,21 +87,27 @@ func (self *ModeHelper) Statuses() []ModeStatus {
style.FgRed,
)
},
CancelLabel: func() string {
return self.c.Tr.ExitFilterMode
},
Reset: self.ExitFilterMode,
},
{
IsActive: self.c.Modes().MarkedBaseCommit.Active,
Description: func() string {
InfoLabel: func() string {
return self.withResetButton(
self.c.Tr.MarkedBaseCommitStatus,
style.FgCyan,
)
},
CancelLabel: func() string {
return self.c.Tr.CancelMarkedBaseCommit
},
Reset: self.mergeAndRebaseHelper.ResetMarkedBaseCommit,
},
{
IsActive: self.c.Modes().CherryPicking.Active,
Description: func() string {
InfoLabel: func() string {
copiedCount := len(self.c.Modes().CherryPicking.CherryPickedCommits)
text := self.c.Tr.CommitsCopied
if copiedCount == 1 {
@@ -110,27 +123,36 @@ func (self *ModeHelper) Statuses() []ModeStatus {
style.FgCyan,
)
},
CancelLabel: func() string {
return self.c.Tr.ResetCherryPickShort
},
Reset: self.cherryPickHelper.Reset,
},
{
IsActive: func() bool {
return !self.suppressRebasingMode && self.c.Git().Status.WorkingTreeState().Any()
},
Description: func() string {
InfoLabel: func() string {
workingTreeState := self.c.Git().Status.WorkingTreeState()
return self.withResetButton(
workingTreeState.Title(self.c.Tr), style.FgYellow,
)
},
CancelLabel: func() string {
return fmt.Sprintf(self.c.Tr.AbortTitle, self.c.Git().Status.WorkingTreeState().CommandName())
},
Reset: self.mergeAndRebaseHelper.AbortMergeOrRebaseWithConfirm,
},
{
IsActive: func() bool {
return self.c.Model().BisectInfo.Started()
},
Description: func() string {
InfoLabel: func() string {
return self.withResetButton(self.c.Tr.Bisect.Bisecting, style.FgGreen)
},
CancelLabel: func() string {
return self.c.Tr.Actions.ResetBisect
},
Reset: self.bisectHelper.Reset,
},
}
+12 -3
View File
@@ -163,6 +163,15 @@ func (self *RefsHelper) CheckoutRemoteBranch(fullBranchName string, localBranchN
})
}
func (self *RefsHelper) CheckoutPreviousRef() error {
previousRef, err := self.c.Git().Branch.PreviousRef()
if err == nil && strings.HasPrefix(previousRef, "refs/heads/") {
return self.CheckoutRef(strings.TrimPrefix(previousRef, "refs/heads/"), types.CheckoutRefOptions{})
}
return self.CheckoutRef("-", types.CheckoutRefOptions{})
}
func (self *RefsHelper) GetCheckedOutRef() *models.Branch {
if len(self.c.Model().Branches) == 0 {
return nil
@@ -249,7 +258,7 @@ func (self *RefsHelper) CreateGitResetMenu(name string, ref string) error {
style.FgRed.Sprintf("reset --%s %s", row.strength, name),
},
OnPress: func() error {
return self.c.ConfirmIf(row.strength == "hard" && IsWorkingTreeDirty(self.c.Model().Files),
return self.c.ConfirmIf(row.strength == "hard" && IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules),
types.ConfirmOpts{
Title: self.c.Tr.Actions.HardReset,
Prompt: self.c.Tr.ResetHardConfirmation,
@@ -475,7 +484,7 @@ func (self *RefsHelper) moveCommitsToNewBranchStackedOnCurrentBranch(newBranchNa
return err
}
mustStash := IsWorkingTreeDirty(self.c.Model().Files)
mustStash := IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules)
if mustStash {
if err := self.c.Git().Stash.Push(fmt.Sprintf(self.c.Tr.AutoStashForNewBranch, newBranchName)); err != nil {
return err
@@ -508,7 +517,7 @@ func (self *RefsHelper) moveCommitsToNewBranchOffOfMainBranch(newBranchName stri
return commit.Status == models.StatusUnpushed
})
mustStash := IsWorkingTreeDirty(self.c.Model().Files)
mustStash := IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules)
if mustStash {
if err := self.c.Git().Stash.Push(fmt.Sprintf(self.c.Tr.AutoStashForNewBranch, newBranchName)); err != nil {
return err
+2 -2
View File
@@ -35,7 +35,7 @@ func (self *SearchHelper) OpenFilterPrompt(context types.IFilterableContext) err
state.Context = context
self.searchPrefixView().SetContent(self.c.Tr.FilterPrefix)
self.searchPrefixView().SetContent(context.FilterPrefix(self.c.Tr))
promptView := self.promptView()
promptView.ClearTextArea()
self.OnPromptContentChanged("")
@@ -69,7 +69,7 @@ func (self *SearchHelper) DisplayFilterStatus(context types.IFilterableContext)
state.Context = context
searchString := context.GetFilter()
self.searchPrefixView().SetContent(self.c.Tr.FilterPrefix)
self.searchPrefixView().SetContent(context.FilterPrefix(self.c.Tr))
promptView := self.promptView()
keybindingConfig := self.c.UserConfig().Keybinding
@@ -0,0 +1,59 @@
//go:build !windows
package helpers
import (
"os"
"os/signal"
"syscall"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
func canSuspendApp() bool {
return true
}
func sendStopSignal() error {
return syscall.Kill(0, syscall.SIGSTOP)
}
// setForegroundPgrp sets the current process group as the foreground process group
// for the terminal, allowing the program to read input after resuming from suspension.
func setForegroundPgrp() error {
fd, err := unix.Open("/dev/tty", unix.O_RDWR, 0)
if err != nil {
return err
}
defer unix.Close(fd)
pgid := syscall.Getpgrp()
return unix.IoctlSetPointerInt(fd, unix.TIOCSPGRP, pgid)
}
func handleResumeSignal(log *logrus.Entry, onResume func() error) {
if err := setForegroundPgrp(); err != nil {
log.Warning(err)
return
}
if err := onResume(); err != nil {
log.Warning(err)
}
}
func installResumeSignalHandler(log *logrus.Entry, onResume func() error) {
go func() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGCONT)
for sig := range sigs {
switch sig {
case syscall.SIGCONT:
handleResumeSignal(log, onResume)
}
}
}()
}
@@ -0,0 +1,16 @@
package helpers
import (
"github.com/sirupsen/logrus"
)
func canSuspendApp() bool {
return false
}
func sendStopSignal() error {
return nil
}
func installResumeSignalHandler(log *logrus.Entry, onResume func() error) {
}
@@ -0,0 +1,31 @@
package helpers
type SuspendResumeHelper struct {
c *HelperCommon
}
func NewSuspendResumeHelper(c *HelperCommon) *SuspendResumeHelper {
return &SuspendResumeHelper{
c: c,
}
}
func (s *SuspendResumeHelper) CanSuspendApp() bool {
return canSuspendApp()
}
func (s *SuspendResumeHelper) SuspendApp() error {
if !canSuspendApp() {
return nil
}
if err := s.c.Suspend(); err != nil {
return err
}
return sendStopSignal()
}
func (s *SuspendResumeHelper) InstallResumeSignalHandler() {
installResumeSignalHandler(s.c.Log, s.c.Resume)
}
@@ -43,10 +43,9 @@ type WindowArrangementArgs struct {
Height int
// User config
UserConfig *config.UserConfig
// Name of the currently focused window
// Name of the currently focused window. (It's actually the current static window, meaning
// popups are ignored)
CurrentWindow string
// Name of the current static window (meaning popups are ignored)
CurrentStaticWindow string
// Name of the current side window (i.e. the current window in the left
// section of the UI)
CurrentSideWindow string
@@ -79,28 +78,27 @@ func (self *WindowArrangementHelper) GetWindowDimensions(informationStr string,
repoState := self.c.State().GetRepoState()
var searchPrefix string
if repoState.GetSearchState().SearchType() == types.SearchTypeSearch {
searchPrefix = self.c.Tr.SearchPrefix
if filterableContext, ok := repoState.GetSearchState().Context.(types.IFilterableContext); ok {
searchPrefix = filterableContext.FilterPrefix(self.c.Tr)
} else {
searchPrefix = self.c.Tr.FilterPrefix
searchPrefix = self.c.Tr.SearchPrefix
}
args := WindowArrangementArgs{
Width: width,
Height: height,
UserConfig: self.c.UserConfig(),
CurrentWindow: self.windowHelper.CurrentWindow(),
CurrentSideWindow: self.c.Context().CurrentSide().GetWindowName(),
CurrentStaticWindow: self.c.Context().CurrentStatic().GetWindowName(),
SplitMainPanel: repoState.GetSplitMainPanel(),
ScreenMode: repoState.GetScreenMode(),
AppStatus: appStatus,
InformationStr: informationStr,
ShowExtrasWindow: self.c.State().GetShowExtrasWindow(),
InDemo: self.c.InDemo(),
IsAnyModeActive: self.modeHelper.IsAnyModeActive(),
InSearchPrompt: repoState.InSearchPrompt(),
SearchPrefix: searchPrefix,
Width: width,
Height: height,
UserConfig: self.c.UserConfig(),
CurrentWindow: self.c.Context().CurrentStatic().GetWindowName(),
CurrentSideWindow: self.c.Context().CurrentSide().GetWindowName(),
SplitMainPanel: repoState.GetSplitMainPanel(),
ScreenMode: repoState.GetScreenMode(),
AppStatus: appStatus,
InformationStr: informationStr,
ShowExtrasWindow: self.c.State().GetShowExtrasWindow(),
InDemo: self.c.InDemo(),
IsAnyModeActive: self.modeHelper.IsAnyModeActive(),
InSearchPrompt: repoState.InSearchPrompt(),
SearchPrefix: searchPrefix,
}
return GetWindowDimensions(args)
@@ -393,7 +391,7 @@ func splitMainPanelSideBySide(args WindowArrangementArgs) bool {
func getExtrasWindowSize(args WindowArrangementArgs) int {
var baseSize int
// The 'extras' window contains the command log context
if args.CurrentStaticWindow == "extras" {
if args.CurrentWindow == "extras" {
baseSize = 1000 // my way of saying 'fill the available space'
} else if args.Height < 40 {
baseSize = 1
@@ -19,21 +19,20 @@ import (
func TestGetWindowDimensions(t *testing.T) {
getDefaultArgs := func() WindowArrangementArgs {
return WindowArrangementArgs{
Width: 75,
Height: 30,
UserConfig: config.GetDefaultConfig(),
CurrentWindow: "files",
CurrentSideWindow: "files",
CurrentStaticWindow: "files",
SplitMainPanel: false,
ScreenMode: types.SCREEN_NORMAL,
AppStatus: "",
InformationStr: "information",
ShowExtrasWindow: false,
InDemo: false,
IsAnyModeActive: false,
InSearchPrompt: false,
SearchPrefix: "",
Width: 75,
Height: 30,
UserConfig: config.GetDefaultConfig(),
CurrentWindow: "files",
CurrentSideWindow: "files",
SplitMainPanel: false,
ScreenMode: types.SCREEN_NORMAL,
AppStatus: "",
InformationStr: "information",
ShowExtrasWindow: false,
InDemo: false,
IsAnyModeActive: false,
InSearchPrompt: false,
SearchPrefix: "",
}
}
@@ -3,21 +3,24 @@ package helpers
import (
"errors"
"fmt"
"os"
"regexp"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/samber/lo"
)
type WorkingTreeHelper struct {
c *HelperCommon
refHelper *RefsHelper
commitsHelper *CommitsHelper
gpgHelper *GpgHelper
c *HelperCommon
refHelper *RefsHelper
commitsHelper *CommitsHelper
gpgHelper *GpgHelper
mergeAndRebaseHelper *MergeAndRebaseHelper
}
func NewWorkingTreeHelper(
@@ -25,12 +28,14 @@ func NewWorkingTreeHelper(
refHelper *RefsHelper,
commitsHelper *CommitsHelper,
gpgHelper *GpgHelper,
mergeAndRebaseHelper *MergeAndRebaseHelper,
) *WorkingTreeHelper {
return &WorkingTreeHelper{
c: c,
refHelper: refHelper,
commitsHelper: commitsHelper,
gpgHelper: gpgHelper,
c: c,
refHelper: refHelper,
commitsHelper: commitsHelper,
gpgHelper: gpgHelper,
mergeAndRebaseHelper: mergeAndRebaseHelper,
}
}
@@ -42,6 +47,14 @@ func AnyStagedFiles(files []*models.File) bool {
return lo.SomeBy(files, func(f *models.File) bool { return f.HasStagedChanges })
}
func (self *WorkingTreeHelper) AnyStagedFilesExceptSubmodules() bool {
return AnyStagedFilesExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules)
}
func AnyStagedFilesExceptSubmodules(files []*models.File, submoduleConfigs []*models.SubmoduleConfig) bool {
return lo.SomeBy(files, func(f *models.File) bool { return f.HasStagedChanges && !f.IsSubmodule(submoduleConfigs) })
}
func (self *WorkingTreeHelper) AnyTrackedFiles() bool {
return AnyTrackedFiles(self.c.Model().Files)
}
@@ -50,12 +63,20 @@ func AnyTrackedFiles(files []*models.File) bool {
return lo.SomeBy(files, func(f *models.File) bool { return f.Tracked })
}
func (self *WorkingTreeHelper) IsWorkingTreeDirty() bool {
return IsWorkingTreeDirty(self.c.Model().Files)
func (self *WorkingTreeHelper) AnyTrackedFilesExceptSubmodules() bool {
return AnyTrackedFilesExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules)
}
func IsWorkingTreeDirty(files []*models.File) bool {
return AnyStagedFiles(files) || AnyTrackedFiles(files)
func AnyTrackedFilesExceptSubmodules(files []*models.File, submoduleConfigs []*models.SubmoduleConfig) bool {
return lo.SomeBy(files, func(f *models.File) bool { return f.Tracked && !f.IsSubmodule(submoduleConfigs) })
}
func (self *WorkingTreeHelper) IsWorkingTreeDirtyExceptSubmodules() bool {
return IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules)
}
func IsWorkingTreeDirtyExceptSubmodules(files []*models.File, submoduleConfigs []*models.SubmoduleConfig) bool {
return AnyStagedFilesExceptSubmodules(files, submoduleConfigs) || AnyTrackedFilesExceptSubmodules(files, submoduleConfigs)
}
func (self *WorkingTreeHelper) FileForSubmodule(submodule *models.SubmoduleConfig) *models.File {
@@ -247,3 +268,135 @@ func (self *WorkingTreeHelper) commitPrefixConfigsForRepo() []config.CommitPrefi
return self.c.UserConfig().Git.CommitPrefix
}
func (self *WorkingTreeHelper) mergeFile(filepath string, strategy string) (string, error) {
if self.c.Git().Version.IsOlderThan(2, 43, 0) {
return self.mergeFileWithTempFiles(filepath, strategy)
}
return self.mergeFileWithObjectIDs(filepath, strategy)
}
func (self *WorkingTreeHelper) mergeFileWithTempFiles(filepath string, strategy string) (string, error) {
showToTempFile := func(stage int, label string) (string, error) {
output, err := self.c.Git().WorkingTree.ShowFileAtStage(filepath, stage)
if err != nil {
return "", err
}
f, err := os.CreateTemp(self.c.GetConfig().GetTempDir(), "mergefile-"+label+"-*")
if err != nil {
return "", err
}
defer f.Close()
if _, err := f.Write([]byte(output)); err != nil {
return "", err
}
return f.Name(), nil
}
baseFilepath, err := showToTempFile(1, "base")
if err != nil {
return "", err
}
defer os.Remove(baseFilepath)
oursFilepath, err := showToTempFile(2, "ours")
if err != nil {
return "", err
}
defer os.Remove(oursFilepath)
theirsFilepath, err := showToTempFile(3, "theirs")
if err != nil {
return "", err
}
defer os.Remove(theirsFilepath)
return self.c.Git().WorkingTree.MergeFileForFiles(strategy, oursFilepath, baseFilepath, theirsFilepath)
}
func (self *WorkingTreeHelper) mergeFileWithObjectIDs(filepath, strategy string) (string, error) {
baseID, err := self.c.Git().WorkingTree.ObjectIDAtStage(filepath, 1)
if err != nil {
return "", err
}
oursID, err := self.c.Git().WorkingTree.ObjectIDAtStage(filepath, 2)
if err != nil {
return "", err
}
theirsID, err := self.c.Git().WorkingTree.ObjectIDAtStage(filepath, 3)
if err != nil {
return "", err
}
return self.c.Git().WorkingTree.MergeFileForObjectIDs(strategy, oursID, baseID, theirsID)
}
func (self *WorkingTreeHelper) CreateMergeConflictMenu(selectedFilepaths []string) error {
onMergeStrategySelected := func(strategy string) error {
for _, filepath := range selectedFilepaths {
output, err := self.mergeFile(filepath, strategy)
if err != nil {
return err
}
if err = os.WriteFile(filepath, []byte(output), 0o644); err != nil {
return err
}
}
err := self.c.Git().WorkingTree.StageFiles(selectedFilepaths, nil)
self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}})
return err
}
cmdColor := style.FgBlue
return self.c.Menu(types.CreateMenuOptions{
Title: self.c.Tr.MergeConflictOptionsTitle,
Items: []*types.MenuItem{
{
LabelColumns: []string{
self.c.Tr.UseCurrentChanges,
cmdColor.Sprint("git merge-file --ours"),
},
OnPress: func() error {
return onMergeStrategySelected("--ours")
},
Key: 'c',
},
{
LabelColumns: []string{
self.c.Tr.UseIncomingChanges,
cmdColor.Sprint("git merge-file --theirs"),
},
OnPress: func() error {
return onMergeStrategySelected("--theirs")
},
Key: 'i',
},
{
LabelColumns: []string{
self.c.Tr.UseBothChanges,
cmdColor.Sprint("git merge-file --union"),
},
OnPress: func() error {
return onMergeStrategySelected("--union")
},
Key: 'b',
},
{
LabelColumns: []string{
self.c.Tr.OpenMergeTool,
cmdColor.Sprint("git mergetool"),
},
OnPress: self.OpenMergeTool,
Key: 'm',
},
},
})
}
@@ -189,7 +189,8 @@ func (self *WorktreeHelper) Remove(worktree *models.Worktree, force bool) error
self.c.LogAction(self.c.Tr.RemoveWorktree)
if err := self.c.Git().Worktree.Delete(worktree.Path, force); err != nil {
errMessage := err.Error()
if !strings.Contains(errMessage, "--force") {
if !strings.Contains(errMessage, "--force") &&
!strings.Contains(errMessage, "fatal: working trees containing submodules cannot be moved or removed") {
return err
}
@@ -856,7 +856,7 @@ func (self *LocalCommitsController) revert(commits []*models.Commit, start, end
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.RevertCommit)
return self.c.WithWaitingStatusSync(self.c.Tr.RevertingStatus, func() error {
mustStash := helpers.IsWorkingTreeDirty(self.c.Model().Files)
mustStash := helpers.IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules)
if mustStash {
if err := self.c.Git().Stash.Push(self.c.Tr.AutoStashForReverting); err != nil {
+4 -3
View File
@@ -39,9 +39,10 @@ func (self *MainViewController) GetKeybindings(opts types.KeybindingsOpts) []*ty
DisplayOnScreen: true,
},
{
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.escape,
Description: self.c.Tr.ExitFocusedMainView,
Key: opts.GetKey(opts.Config.Universal.Return),
Handler: self.escape,
Description: self.c.Tr.ExitFocusedMainView,
DisplayOnScreen: true,
},
{
// overriding this because we want to read all of the task's output before we start searching

Some files were not shown because too many files have changed in this diff Show More