Compare commits

...

242 Commits

Author SHA1 Message Date
Stefan Haller
cbd23107ef WIP Make it searchable
Doesn't work very well yet, but it gives you a taste of what it could look like.
2024-10-15 16:59:50 +02:00
Stefan Haller
50426cda3a Select line that is in the middle of the screen
TODO: doesn't work the very first time
2024-10-15 16:36:19 +02:00
Stefan Haller
e031f437e9 Add navigation keybindings 2024-10-15 15:35:52 +02:00
Stefan Haller
9bd212aab1 DROPME: change window title for testing 2024-10-15 15:35:52 +02:00
Stefan Haller
9df81067e9 Make Files diff focusable 2024-10-15 15:35:52 +02:00
Stefan Haller
f31037864e Extract code to DiffHelper
TODO: handle the WithDiffModeCheck thing properly
2024-10-15 15:35:52 +02:00
Stefan Haller
03e060d82c Extract helper method mainViews 2024-10-15 15:35:52 +02:00
Stefan Haller
f88b1942ae Remove utils.Clamp, use lo.Clamp instead 2024-10-15 15:35:51 +02:00
Stefan Haller
2e05ea57dc [gocui] Make highlight overwrite the background color
It seems that highlighting has so far only been used in cases where there wasn't
a background color. Or'ing the bits of the color with the existing background
color doesn't make sense.
2024-10-15 15:06:11 +02:00
Stefan Haller
052974be65 Allow pasting commits multiple times (#3983)
- **PR Description**

After pasting commits, hide the cherry-pick status (i.e. remove the "x
commits copied" status in the lower right corner, and hide the blue
selection of the copied commits). However, keep the copied commits
around so that it's possible to paste them again. This can be useful
e.g. to backport a bugfix to multiple major version release branches.

Discussed in #3198.

- **Please check if the PR fulfills these requirements**

* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [x] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [ ] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [x] You've read through your own file changes for silly mistakes etc
2024-10-13 16:59:04 +02:00
Stefan Haller
85523402d6 Allow pasting commits more than once
After pasting commits once, we hide the cherry-picking status (as if it had been
reset), and no longer paint the copied commits with blue hashes; however, we
still allow pasting them again. This can be useful e.g. to backport a bugfix to
multiple major version release branches.
2024-10-13 16:55:54 +02:00
Stefan Haller
f473d23d65 Wrap an overly long line 2024-10-13 16:55:54 +02:00
Stefan Haller
59a937ee7a Get rid of error return value of PostRefreshUpdate and a few related ones
I missed these in https://github.com/jesseduffield/lazygit/pull/3890.
2024-10-13 16:55:54 +02:00
Stefan Haller
53f8249ee1 Fix file icons (#3975)
- **PR Description**

Some file icons weren't drawn correctly, e.g. the ones for
`tailwind.config.ts` or `nuxt.config.ts`, but also many others.

Fixes #3747.

- **Please check if the PR fulfills these requirements**

* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [x] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [ ] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [x] You've read through your own file changes for silly mistakes etc
2024-10-13 16:55:18 +02:00
Stefan Haller
f71274b601 Add test to ensure that file icons are one rune
This should prevent errors like that from happening again.
2024-10-13 16:51:27 +02:00
Stefan Haller
f2fd435c05 Fix many file icons
The string literal "\uf0868" does *not* create a single rune with the code point
f0868, as was intended; instead, it creates two runes, one with the code point
f086, followed by the character '8'.
2024-10-13 16:51:27 +02:00
Stefan Haller
4e361e1a87 Fix merge conflict resolution when file doesn't end with a LF (#3976)
- **PR Description**

When resolving conflicts using lazygit's merge conflicts view in a file
that doesn't end with a trailing line feed, the last line would be lost.

Fixes #3444.

- **Please check if the PR fulfills these requirements**

* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [x] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [ ] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [x] You've read through your own file changes for silly mistakes etc
2024-10-13 16:50:53 +02:00
Stefan Haller
696e78fcc8 Fix ForEachLineInFile to not lose the last line if it doesn't end with a LF 2024-10-09 15:37:08 +02:00
Stefan Haller
b71aa5e23b Add regression test for resolving conflicts in a file without a trailing LF
The test shows that the last line of the file is lost.
2024-10-09 15:36:02 +02:00
Stefan Haller
72cf3efb1d Add test demonstrating problem with ForEachLineInFile
The function drops the last line if it doesn't end with a line feed.
2024-10-09 15:36:02 +02:00
Stefan Haller
ae610dcbb7 Extract helper function for easier testing 2024-10-09 15:08:01 +02:00
Stefan Haller
d11e11d179 Auto-render hyperlinks (#3914)
- **PR Description**

Add a facility to gocui.View to enable auto-rendering of https
hyperlinks. Then, use it for the Command Log panel (as an alternative
approach to #3911), and also in the status view and in confirmation
popups to get rid of some code that used to do this manually.

- **Please check if the PR fulfills these requirements**

* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [ ] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [ ] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [x] You've read through your own file changes for silly mistakes etc
2024-09-28 12:07:28 +02:00
Stefan Haller
825f5c0a91 Use AutoRenderHyperLinks in confirmation view
This allows us to get rid of the underlineLinks function.
2024-09-28 12:04:51 +02:00
Stefan Haller
26e3a93fc3 Use AutoRenderHyperLinks in main views
This allows clicking on links in commit messages, for examples. It also affects
the status view, so we can get rid of the manual hyperlinking there.
2024-09-28 12:04:51 +02:00
Stefan Haller
1ceb5a6b37 Turn on AutoRenderHyperLinks in the Command Log panel
Some commands output hyperlinks, and it's useful to be able to click them.
2024-09-28 12:04:51 +02:00
Stefan Haller
65b731f484 Bump gocui 2024-09-28 12:04:51 +02:00
Stefan Haller
c6a7722066 Add a menu item to delete both local and remote branch at once (#3916)
- **PR Description**

Add a menu item to delete both local and remote branch at once.

- **Please check if the PR fulfills these requirements**

* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [x] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [x] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [x] You've read through your own file changes for silly mistakes etc
2024-09-28 11:26:20 +02:00
Stefan Haller
1ab70ec645 Add a menu item to delete both local and remote branch at once 2024-09-28 11:23:21 +02:00
Stefan Haller
e181de1180 Better branch delete confirmation (#3915)
- **PR Description**

When deleting a local branch, put up the "This branch is not fully
merged, do you want to force delete it" confirmation only when the
branch is not merged into any of the main branches.

- **Please check if the PR fulfills these requirements**

* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [x] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [x] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [x] You've read through your own file changes for silly mistakes etc
2024-09-28 11:22:54 +02:00
Stefan Haller
c712b1d0fe Better local branch delete confirmation
Currently we try to delete a branch normally, and if git returns an error and
its output contains the text "branch -D", then we prompt the user to force
delete, and try again using -D. Besides just being ugly, this has the
disadvantage that git's logic to decide whether a branch is merged is not very
good; it only considers a branch merged if it is either reachable from the
current head, or from its own upstream. In many cases I want to delete a branch
that has been merged to master, but I don't have master checked out, so the
current branch is really irrelevant, and it should rather (or in addition) check
whether the branch is reachable from one of the main branches. The problem is
that git doesn't know what those are.

But lazygit does, so make the check on our side, prompt the user if necessary,
and always use -D. This is both cleaner, and works better.

See this mailing list discussion for more:
https://lore.kernel.org/git/bf6308ce-3914-4b85-a04b-4a9716bac538@haller-berlin.de/
2024-09-28 11:19:32 +02:00
Stefan Haller
c4e5995cb9 Fix bug with deleting remote branch whose name doesn't match local branch 2024-09-28 11:19:32 +02:00
Stefan Haller
be3683ccc8 Add test that demonstrates bug with deleting remote branch with different name
It's maybe not very common, but it's totally possible for a remote branch to
have a different name than the local branch. This test shows that we don't
support this properly when deleting the remote branch.
2024-09-28 11:19:32 +02:00
Stefan Haller
7e7309f97e Add question marks to questions 2024-09-28 11:19:32 +02:00
Stefan Haller
d2b6f93858 Remove unused texts 2024-09-28 11:19:32 +02:00
Stefan Haller
a91fe517d3 Remove obsolete TODO comment
Looks perfectly internationalized to me.
2024-09-28 11:19:32 +02:00
Stefan Haller
8a328a553a Fix copying commit author to clipboard (#3936)
- **PR Description**

This included single quotes in strange places in the copied text.

Fixes #3933.

- **Please check if the PR fulfills these requirements**

* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [x] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [ ] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [x] You've read through your own file changes for silly mistakes etc
2024-09-28 11:15:03 +02:00
Stefan Haller
3d56357294 Fix copying commit author to clipboard
This was a regression introduced with the GitCommandBuilder in 25f8b0337.
2024-09-23 09:47:14 +02:00
Stefan Haller
9b2a0c4538 Add test for copying a commit author to the clipboard
The test shows that we are including single quotes in strange places.
2024-09-23 09:45:03 +02:00
Jesse Duffield
a04ad24a60 Add performance improvements section to release notes (#3922)
- **PR Description**

- **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))
* [ ] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [ ] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [ ] You've read through your own file changes for silly mistakes etc

<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for
examples
-->
2024-09-18 21:26:47 +10:00
Jesse Duffield
0d633896ae Add performance improvements section to release notes 2024-09-18 21:06:02 +10:00
Stefan Haller
611fabde11 Fix crash when viewing the divergence of a branch which is up to date with its upstream (#3918)
This was introduced by #3838, specifically by commit e675025411.

Fixes #3900 and #3917.
2024-09-18 09:27:11 +02:00
Stefan Haller
f8073c7188 Fix crash when viewing the divergence of a branch which is up to date with its upstream
This was introduced by #3838, specifically by commit e675025411.

Add a regression test that would have crashed without the fix.
2024-09-18 09:24:10 +02:00
Stefan Haller
4dadcd2ace Improve performance with large numbers of untracked or modified files (#3919)
- **PR Description**
**BuildTreeFromFiles** used a linear complexity lookup to find if the
children has already been added. Use maps to get constant time lookup
for children.

For the test scenario in #3798 (90000 untracked files) this reduces the
time of BuildTreeFromFiles from something like 10s down to about 30ms on
my machine.

Fixes #3798.

- **Please check if the PR fulfills these requirements**

* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [ ] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [ ] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [x] You've read through your own file changes for silly mistakes etc

<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for
examples
-->
2024-09-18 09:23:21 +02:00
partho.kunda
b18f12ca0f Use map to quickly find children in BuildTreeFromFiles 2024-09-18 09:16:58 +02:00
Stefan Haller
c67979abbb Add options for disabling switching to the Files panel after popping or applying a stash (#3913)
- **PR Description**

In v0.44.0 we added a small QoL improvement to auto-switch to the Files
panel after popping or applying a stash. While this should be an
improvement for most people, it turns out to be in the way of some
people's workflows, so make it configurable. See
[here](https://github.com/jesseduffield/lazygit/pull/3888#issuecomment-2350853602)
for more discussion.
2024-09-15 14:19:08 +02:00
Stefan Haller
0e489bb5cc Add options for disabling switching to the Files panel after popping or applying a stash 2024-09-15 11:59:59 +02:00
Stefan Haller
647f533e71 With stacked branches, create fixup commit at the end of the branch it belongs to (#3892)
- **PR Description**

When working with stacked branches, and creating a fixup commit for a
commit in one of the lower branches of the stack, the fixup was created
at the top of the stack and the user needed to move it down to the right
branch manually. This is unnecessary extra work; create it at the end of
the right branch automatically.
2024-09-15 11:21:34 +02:00
Stefan Haller
b22149d832 Create fixup commit at end of its branch when there's a stack of branches 2024-09-15 11:19:39 +02:00
Stefan Haller
396215a5c9 Extract helper function for getting the hash of the last commit made 2024-09-15 11:19:39 +02:00
Stefan Haller
42c157a5e6 Add changeToFixup field to MoveFixupCommitDown 2024-09-15 11:19:39 +02:00
Stefan Haller
a793f709b6 Update language files from Crowdin (#3898)
- **PR Description**

Pull down the latest translations from Crowdin. Unfortunately I forgot
to do this in time for the 0.44 release, bummer.

I'm not sure how much testing/proof-reading/peer review we want to do on
these.
2024-09-15 09:43:15 +02:00
Stefan Haller
f74551e464 Generate keybindings 2024-09-08 15:20:52 +02:00
Stefan Haller
e06b1cef60 Update language files from Crowdin 2024-09-08 15:19:26 +02:00
Stefan Haller
2d0c7cb0fc Switch to Files panel after popping a stash (#3888)
#### PR Description

I find myself always switching to the Files panel after popping a stash,
100% of the time, so it makes sense that lazygit does this for me. Do it
for apply as well, for consistency.

#### Please check if the PR fulfills these requirements

* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [x] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [ ] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [x] You've read through your own file changes for silly mistakes etc
2024-09-06 13:21:49 +02:00
Stefan Haller
f5b8619ded Switch to Files panel after popping a stash 2024-09-06 13:15:37 +02:00
Stefan Haller
4c6c915a77 Get rid of a lot of error return values (#3890)
- **PR Description**

Change many functions in the gui package (and some in gocui) to no
longer return errors.

There might be more that could be changed in this way, but I feel these
are the main ones.

Fixes #3887.
2024-09-06 08:48:44 +02:00
Stefan Haller
064fae41e7 Remove return value of OpenCommitMessagePanel
Similar to the previous commit: in 100% of the call sites we now need an extra
`return nil`. Nevertheless, I still prefer it this way.
2024-09-06 08:45:48 +02:00
Stefan Haller
d4ef8e53d5 Remove return value of Alert/Confirm/Prompt
This might seem controversial; in many cases the client code gets longer,
because it needs an extra line for an explicit `return nil`. I still prefer
this, because it makes it clearer which calls can return errors.
2024-09-06 08:45:48 +02:00
Stefan Haller
b15a1c7ae7 Remove return value of CreatePopupPanel 2024-09-06 08:45:48 +02:00
Stefan Haller
6f0182f11c Remove return value of RefreshPatchBuildingPanel 2024-09-06 08:45:48 +02:00
Stefan Haller
371998e635 Remove return value of IContextMgr.Push/Pop et. al. 2024-09-06 08:45:48 +02:00
Stefan Haller
072b465fa6 Fix a lock that is held too long
I can only guess, but I think this was a typo (or a copy-paste-o) when this code
was written. It was introduced in 55af07a1bb, and I think the defer was kept by
accident; if it had been on purpose, then the statement would have been put
right after the Lock call.
2024-09-06 08:45:48 +02:00
Stefan Haller
8302575078 Remove return value of Focus-related functions 2024-09-06 08:45:48 +02:00
Stefan Haller
8edcd71234 Remove return value of IPatchExplorerContext.Render, RenderAndFocus, and NavigateTo 2024-09-06 08:45:48 +02:00
Stefan Haller
5446683881 Remove return value of RenderToMainViews and some related functions 2024-09-06 08:45:48 +02:00
Stefan Haller
b91beb68e1 Remove return value of HandleRender 2024-09-06 08:45:48 +02:00
Stefan Haller
5659f1f3e9 Bump gocui
And adapt client code.
2024-09-06 08:45:48 +02:00
Stefan Haller
753b16b697 Add Zed editor support to editorPreset (#3886)
- **PR Description**

- **Please check if the PR fulfills these requirements**

* [X] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [ ] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [X] Docs have been updated if necessary
* [X] You've read through your own file changes for silly mistakes etc
2024-09-03 19:27:29 +02:00
Dmytro Suvorov
da7a28c117 Add Zed to user_config.go and schema 2024-09-03 19:22:56 +02:00
Dmytro Suvorov
c35743d7ad Add Zed to docs/Config.md 2024-09-03 19:22:56 +02:00
Dmytro Suvorov
f0eafabd6d Add Zed support to editor_presets.go 2024-09-03 17:30:56 +03:00
Stefan Haller
fc4cf5d196 Offer autostash option when creating new branch (#3871)
- **PR Description**

Resolves https://github.com/jesseduffield/lazygit/issues/3866

My attempt at creating a shared abstraction felt like it was more
indirection than it was worth so I ended up going with the code
duplication approach.
2024-09-03 09:08:56 +02:00
Brandon
4e880e56c4 Add integration tests for checkout/new branch with autostash 2024-09-03 09:05:53 +02:00
Brandon
e8e39f5ce2 Offer autostash option when creating new branch 2024-09-03 09:05:53 +02:00
Brandon
370ab2d19c Simplify CheckoutRef 2024-09-03 09:05:53 +02:00
Stefan Haller
fa8cd47227 Don't allow opening a menu while the search or filter prompt is open (#3878)
- **PR Description**

This solves several problems that arise from opening a menu while the
prompt is open. We might try to solve these in a different way, e.g. by
dismissing the search prompt before opening a menu, but restricting what
you can do while the prompt is open seems like the more robust fix.

To achieve this, we
- call resetKeyBindings both when opening and when closing the
search/filter prompt
- change the keybindings to only contain the ones for the search prompt
when that context is active.

Fixes #3875.
2024-09-02 18:34:28 +02:00
Stefan Haller
9ec77bba91 Don't allow opening a menu while the search or filter prompt is open
This solves several problems that arise from opening a menu while the prompt is
open. We might try to solve these in a different way, e.g. by dismissing the
search prompt before opening a menu, but restricting what you can do while the
prompt is open seems like the more robust fix.

To achieve this, we
- call resetKeyBindings both when opening and when closing the search/filter
  prompt
- change the keybindings to only contain the ones for the search prompt when
  that context is active.
2024-09-02 18:31:30 +02:00
Stefan Haller
4ec9262ff6 Ask to auto-stage unstaged files when continuing a rebase after resolving conflicts (#3879)
- **PR Description**

When lazygit sees that all conflicts were resolved, it auto-stages all
previously conflicted files and asks to continue the rebase. (There is
[a PR](https://github.com/jesseduffield/lazygit/pull/3870) open to make
this optional.)

It is a common situation for this popup to be opened in the background,
while the user is still busy fixing build errors in their editor. In
this case, coming back to lazygit and confirming the continue prompt
would result in an error because not all files are staged.

Improve this by opening another popup in this case, asking to stage the
newly modified files too and continue.

See
https://github.com/jesseduffield/lazygit/issues/3111#issuecomment-1801751982
and the following discussion further down in that issue.
2024-09-02 18:27:08 +02:00
Stefan Haller
ba21d4e651 Ask to auto-stage unstaged files when continuing a rebase after resolving conflicts 2024-09-02 18:24:36 +02:00
Stefan Haller
3cffed9412 Make auto-staging resolved conflicts optional (#3870)
- **PR Description**

Add user config `git.autoStageResolvedConflicts` (default true). When
set to false, users need to stage their conflicted files manually after
resolving conflicts, and also continue a merge/rebase manually when all
conflicted files are resolved.

Fixes #3111.
2024-09-02 18:20:20 +02:00
Stefan Haller
90b8fd242d Add config git.autoStageResolvedConflicts 2024-09-02 18:12:47 +02:00
Stefan Haller
1191aca60f Actually look for conflict markers in GetHasInlineMergeConflicts
So far, lazygit has always auto-staged files as soon as the conflict markers
disappeared from them, which means that we could rely on any file that still had
a status of "UU" to still contain conflict markers.

We are going to make the auto-staging optional in the next commit, and in that
case the user will want to manually stage "UU" files; so we must now check
whether the file contains conflict markers, and disallow the staging in that
case.
2024-09-02 18:12:47 +02:00
Stefan Haller
2f01af49e8 Non-sticky range selection diff (#3869)
- **PR Description**

When selecting a range of commits, show the combined diff for them.

Along the way, fix a few minor issues with the current implementation of
diffing mode; see the individual commit messages for details.

Fixes #3862.

- **Please check if the PR fulfills these requirements**

* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [x] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [x] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [x] You've read through your own file changes for silly mistakes etc
2024-08-31 08:16:32 +02:00
Stefan Haller
32fef9aadb Add a simple integration test for non-sticky range diff 2024-08-28 19:51:15 +02:00
Stefan Haller
717cb40f05 Cleanup: remove now unused ListControllerTrait from SwitchToDiffFilesController 2024-08-28 19:51:15 +02:00
Stefan Haller
ef7d1a8602 Use non-sticky range diff when entering commit files panel
We make the name of the GetSelectedRefRangeForDiffFiles very specific on purpose
to make it clear that this is only for switching to diff files, so the
implementations can make assumptions about that (unlike GetSelectedRef, which is
used for different purposes and needs to stay more generic).
2024-08-28 19:51:15 +02:00
Stefan Haller
a6656e307c Extract a method CommitFilesContext.ReInit
Right now it doesn't do very much yet, but it's still worth it even in this
state, I'd say. The function is going to become a bit longer in the next commit,
though.
2024-08-28 19:51:15 +02:00
Stefan Haller
442592a149 Show diff for range selection in commits and sub-commits panel
In other views that show lists of commits (reflog and stash) it doesn't make
sense to show a range diff of selected entries because they don't form a linear
sequence, so we keep the previous behavior of showing the diff for the free end
of the selection range in those view.

The same applies to the commits view if the selection range includes rebasing
todos; these can have an arbitrary order, and a range diff doesn't make sense
for those.
2024-08-28 19:35:23 +02:00
Stefan Haller
ac335907ae Add ShortRefName to Ref interface 2024-08-28 18:27:52 +02:00
Stefan Haller
b07ce19b9a Add --stat -p to diff args in diffing mode
This is consistent with what we do for showing single commits (with git show),
and I find it very useful.
2024-08-28 18:27:52 +02:00
Stefan Haller
079c5a9905 Add prefix to main view diff when in diffing mode
Hopefully this will help alleviate the problem that diffing mode is sticky, and
you often forget that it's still on.

Make it magenta like the mode text in the information view.
2024-08-28 18:27:52 +02:00
Stefan Haller
a2c4fad410 Bugfix: properly set title of subcommits panel when refreshing
This fixes two problems:
- Set the title ref to the commit description (like
  SwitchToDiffFilesController.enter does), rather than just the hash
- SetTitleRef only sets the title of the DynamicTitleBuilder, but doesn't set it
  on the view; this only happens in ContextMgr.Activate, so you'd have to switch
  to a different side panel and then back to see the new title.
2024-08-28 18:27:52 +02:00
Stefan Haller
567e898e22 Bugfix: don't allow dropping patches from a custom patch that was made in diffing mode
The three nested `if` statements may looks strange, and you might wonder why we
don't have single one with &&. The answer is that later in this branch we will
add an `else` block to the middle one.
2024-08-28 18:27:52 +02:00
Stefan Haller
14a29d6d6f Bugfix: more comprehensive check whether custom patch must be reset
We need to compare more than just the "To" ref. The NewPatchRequired function
existed already for this purpose, it just wasn't used.
2024-08-28 18:27:52 +02:00
Stefan Haller
8e2ed8c538 Extract helper function currentFromToReverseForPatchBuilding
In the next commit it will be reused for checking whether we need to reset the
patch; and extracting it makes it easier to extend it for non-sticky diff ranges
later in the branch.
2024-08-28 18:27:52 +02:00
Stefan Haller
6ad4ffea3b Cleanup: remove unnecessary viewFiles indirection
viewFiles is only called from enter; it doesn't make much sense to fill in a
SwitchToCommitFilesContextOpts struct to pass it to viewFiles for this one call.
Simply inline viewFiles into enter and get rid of all that.
2024-08-28 18:27:52 +02:00
Stefan Haller
770d51634c Cleanup: remove diffFilesContext field of SwitchToDiffFilesController
I found this indirection confusing when reading the code. It looks like
SwitchToDiffFilesController is instantiated with different such contexts, but
it's always Contexts.CommitFiles, so just use that directly.
2024-08-28 18:27:52 +02:00
Stefan Haller
c51e13941c Cleanup: remove unused translated text 2024-08-28 16:54:38 +02:00
Stefan Haller
05ae0800c3 Add codespell support (config, workflow to detect/not fix) and make it fix few typos (#3751)
More about codespell: https://github.com/codespell-project/codespell .

I personally introduced it to dozens if not hundreds of projects already
and so far only positive feedback.

CI workflow has 'permissions' set only to 'read' so also should be safe.
2024-08-27 18:05:45 +02:00
Yaroslav Halchenko
4bbfe7b3cc [DATALAD RUNCMD] run codespell throughout fixing typos automagically
=== Do not change lines below ===
{
 "chain": [],
 "cmd": "codespell -w",
 "exit": 0,
 "extra_inputs": [],
 "inputs": [],
 "outputs": [],
 "pwd": "."
}
^^^ Do not change lines above ^^^

Signed-off-by: Yaroslav Halchenko <debian@onerussian.com>
2024-08-27 18:03:00 +02:00
Yaroslav Halchenko
00530477c9 [DATALAD RUNCMD] Do interactive fixing of some ambigous typos
=== Do not change lines below ===
{
 "chain": [],
 "cmd": "codespell -w -i 3 -C 2 ./pkg/commands/oscommands/cmd_obj_runner.go ./pkg/integration/tests/branch/rebase_and_drop.go",
 "exit": 0,
 "extra_inputs": [],
 "inputs": [],
 "outputs": [],
 "pwd": "."
}
^^^ Do not change lines above ^^^

Signed-off-by: Yaroslav Halchenko <debian@onerussian.com>
2024-08-27 18:03:00 +02:00
Yaroslav Halchenko
90c1334535 Skip also for \nd
Signed-off-by: Yaroslav Halchenko <debian@onerussian.com>
2024-08-27 18:03:00 +02:00
Yaroslav Halchenko
83ef031922 [DATALAD RUNCMD] Do interactive fixing of some ambigous typos
=== Do not change lines below ===
{
 "chain": [],
 "cmd": "codespell -w -i 3 -C 2",
 "exit": 0,
 "extra_inputs": [],
 "inputs": [],
 "outputs": [],
 "pwd": "."
}
^^^ Do not change lines above ^^^

Signed-off-by: Yaroslav Halchenko <debian@onerussian.com>
2024-08-27 18:03:00 +02:00
Yaroslav Halchenko
820c2bc0fd custom skips for codespell
Signed-off-by: Yaroslav Halchenko <debian@onerussian.com>
2024-08-27 18:03:00 +02:00
Yaroslav Halchenko
a484954d74 Add rudimentary codespell config
Signed-off-by: Yaroslav Halchenko <debian@onerussian.com>
2024-08-27 18:03:00 +02:00
Yaroslav Halchenko
56be1a4950 Add github action to codespell master on push and PRs
Signed-off-by: Yaroslav Halchenko <debian@onerussian.com>
2024-08-27 18:03:00 +02:00
Yaroslav Halchenko
1e8eb72b2f Do not git ignore .codespellrc
Signed-off-by: Yaroslav Halchenko <debian@onerussian.com>
2024-08-27 18:03:00 +02:00
Stefan Haller
18935bf7f0 Fix linter warnings (#3854)
- **PR Description**

Fix some linter warnings that I keep getting locally because I'm running
a newer version of golangci-lint than the pinned version we use on CI.

Also, update the pinned version in CI to 1.60 so that we are in sync
again.

Finally, upgrade the golangci-lint-action version to the latest,
although I'm not sure what difference this makes. The linting step runs
faster now, maybe because of better caching -- I noticed that
previously, loading and saving the cache took a very long time, which
often made the lint step the slowest of all.
2024-08-27 13:12:40 +02:00
Stefan Haller
457c4c248d Upgrade golangci-lint-action to latest version 2024-08-27 10:33:06 +02:00
Stefan Haller
8c553dcde9 Upgrade golang-ci to 1.60 2024-08-27 10:33:06 +02:00
Stefan Haller
a709caf138 Remove error return value from functions that always return nil 2024-08-27 10:33:06 +02:00
Stefan Haller
8dea2dab88 Ignore return values of fmt.Scanln 2024-08-27 10:33:06 +02:00
Stefan Haller
63aa32c521 Remove "double" formatting 2024-08-27 10:33:06 +02:00
Stefan Haller
24841f22f1 Use print instead of printf when there are no arguments 2024-08-27 10:33:06 +02:00
Stefan Haller
d712c2f199 Use format arguments instead of concatenating strings
That's what they are for.
2024-08-27 10:33:06 +02:00
Stefan Haller
4525216d26 Fix cancelled autostash resulting in stuck inline status (#3860)
- **PR Description**

When switching branches, there is a "Checking out" inline status
displayed next to the branch (but only sometimes? I think if the action
completes too quickly there is no status). If it does get displayed and
the checkout results in an autostash confirmation prompt, pressing
escape to cancel the action will cancel the checkout and close the
prompt. However, the inline status will still be displayed next to the
branch and doesn't go away by itself. Performing a manual UI refresh
(`R`) fixes the state.

If the prompt was confirmed instead, then this issue would not happen.

Reproduction:
```bash
git init
echo -e "a\n\nb" > file
git add .
git commit -m "add file"
echo -e "a\n\nc" > file
git add .
git commit -m "edit last line"
git checkout -b dev HEAD~
echo -e "b\n\nb" > file
lazygit
```

Switch to the other branch in the branches panel and press escape on the
prompt. The "Checking out" inline status should be stuck.
2024-08-27 10:30:31 +02:00
Brandon
ae61da7485 Fix cancelled autostash resulting in stuck inline status 2024-08-27 10:28:22 +02:00
Stefan Haller
fb9f6153fc Add missing closing quote in pager docs (#3864)
- **PR Description**

Add missing closing quote in pager docs.
2024-08-27 10:22:47 +02:00
Stefan Haller
a0c808842b Add missing closing quote 2024-08-27 10:16:53 +02:00
Stefan Haller
8a8490d97d Underline hyperlinks only on mouse hover (#3856)
- **PR Description**

Followup to #3825: we decided there that we don't want to underline
links in delta diffs by default, but only on mouse hover. This PR does
that; it makes it possible to decide per view whether links should be
underlined always, or only on hover. We set this to only on hover for
the main views, so that links in diffs are not underlined (also affects
the status view though), but all other links we want to underline always
for better discoverability.
2024-08-24 17:49:10 +02:00
Stefan Haller
68c7f9840a Set main views to underline hyperlinks only on mouse hover
Note that this doesn't only affect the diff views, which are the ones where we
want this, but also the status view, where the plan was to keep them underlined
always for better discoverability. We could make this configurable dynamically
(by adding another flag to ViewUpdateOpts), but actually I think it's fine this
way.
2024-08-24 17:45:54 +02:00
Stefan Haller
8d37f48744 Bump gocui 2024-08-24 17:45:51 +02:00
Stefan Haller
37f32755b5 Fix rendering regression introduced in #3839 (#3855)
- **PR Description**

Fix broken rendering introduced in #3839: the status view would often render only partially.
2024-08-24 11:51:53 +02:00
Stefan Haller
5380fe2483 Fix rendering regression introduced in #3839 2024-08-24 11:46:53 +02:00
Stefan Haller
8e71df3e53 Fix loading customCommands from per-repo config file (#3835)
- **PR Description**

Any newly loaded custom command coming from the per-repo config file should add
to the global ones (or override an existing one in the global one), rather than
replace all global ones.
2024-08-24 11:04:56 +02:00
Stefan Haller
30f43a245b Fix loading customCommands from per-repo config file
Any newly loaded custom command coming from the per-repo config file should add
to the global ones (or override an existing one in the global one), rather than
replace all global ones.

We can achieve this by simply prepending the newly loaded commands to the
existing ones. We don't have to take care of removing duplicate key assignments;
it is already possible to add two custom commands with the same key to the
global config file, the first one wins.
2024-08-24 11:01:25 +02:00
Stefan Haller
283ed29f10 Add a test that shows how per-repo config file replaces customCommands
We want to add to the global customCommands instead of replacing them.
2024-08-24 11:01:25 +02:00
Stefan Haller
ccd39bb8ae Fix wrong test assertion text
If a `t.FileSystem().FileContent("file.txt", Equals("bla"))` assertion fails
because the file doesn't exist, the error would say

   Expected path 'file.txt' to not exist, but it does

which is very confusing.
2024-08-24 11:01:25 +02:00
Stefan Haller
db40653202 Allow using </> and ,/. in sticky range select mode in patch explorer (#3837)
- **PR Description**

Don't cancel sticky range select when pressing `<`/`>`, `,`/`.` in the
patch explorer view. This was already working correctly in list views.

Fixes #3823.
2024-08-24 10:59:48 +02:00
Stefan Haller
d3940729eb Allow using </> and ,/. in sticky range select mode in patch explorer
They still cancel hunk selection mode, setting it to line selection mode, but if
range selection mode is on, we keep it on.
2024-08-24 10:56:20 +02:00
Stefan Haller
56a6ee6afb Cleanup: move SetLineSelectMode into AdjustSelectedLineIdx 2024-08-24 10:56:20 +02:00
Stefan Haller
a37a3fc4a1 Fix crash when filtering commits (#3838)
- **PR Description**

First we fix the crash reported in #3812 (see there for reproduction
recipe), and then we add a more general fix for avoiding crashes in
similar situations that we don't know about yet.

I'm not really happy with the brittleness of all this (the
interdependency between rendering and updating search results, that is),
but I haven't found a good way to untangle this yet.

Fixes #3812.
2024-08-24 10:55:12 +02:00
Stefan Haller
af87cd1dd6 Don't return model search results for commits when we don't have columnPositions
We fixed one specific scenario where this happened ealier in this branch, but in
case there are more that we don't know about yet, at least make sure we don't
crash.
2024-08-24 10:51:25 +02:00
Stefan Haller
e675025411 Return nil columnPositions when not rendering anything
... instead of returning a slice with a single [0] element. This makes it easier
to check whether we have columnPositions.
2024-08-24 10:51:25 +02:00
Stefan Haller
c3d5798c6c Fix early exit condition
I don't know what this condition is supposed to guard against, or whether we
really need it (it was added in 06ca71e955, and the commit message of that
commit only says "fix bug"). But if we do need it, then it seems that `>=` is
more correct than `>`.
2024-08-24 10:51:25 +02:00
Stefan Haller
15d17e16dd Call ReApplySearch after layout
This fixes a possible crash when exiting filter mode in the commits panel.
2024-08-24 10:51:25 +02:00
Stefan Haller
926061557b Make searching available in the filtered commits list
It is already possible to search a filtered list by searching first, and then
enabling a filter, so I found it inconsistent to not allow searching when you
are already filtering. One reason for not allowing this might be that the search
status (on the left) hides the filter status (on the right), but if we think
that's enough reason to not allow both at the same time, then we should cancel a
search when we enter filtering.
2024-08-24 10:51:25 +02:00
Stefan Haller
8522337f32 Scroll views up if needed to show all their content (#3839)
- **PR Description**

There are many situations where this can arise. Some examples are:
- the terminal window is small, and you are showing a view that shows more
content than fits into the view port, and the view is scrolled all the way
down; now you resize the terminal window to a taller size. Previously, the
scroll position of the view would stay the same, so it would add blank space
at the bottom; now it will scroll to fill that blank space with content
- expandFocusedSidePanel is on, you go to the bottom of a list view, now switch
to a different panel, then scroll that (now unfocused) panel all the way down
with the scroll wheel; now you focus that panel again. It becomes larger
because of the accordion behavior, but would show blank space at the
bottom.

And probably others that I can't remember right now. I only remember that I
always found it confusing to look at a view that had blank space at the bottom
even though it had more content to scroll into view.
2024-08-24 10:50:50 +02:00
Stefan Haller
6114f69ee5 Scroll views up if needed to show all their content
There are many situations where this can arise. Some examples are:
- the terminal window is small, and you are showing a view that shows more
  content than fits into the view port, and the view is scrolled all the way
  down; now you resize the terminal window to a taller size. Previously, the
  scroll position of the view would stay the same, so it would add blank space
  at the bottom; now it will scroll to fill that blank space with content
- expandFocusedSidePanel is on, you go to the bottom of a list view, now switch
  to a different panel, then scroll that (now unfocused) panel all the way down
  with the scroll wheel; now you focus that panel again. It becomes larger
  because of the accordion behavior, but would show blank space at the bottom.

And probably others that I can't remember right now. I only remember that I
always found it confusing to look at a view that had blank space at the bottom
even though it had more content to scroll into view.
2024-08-24 10:47:27 +02:00
Stefan Haller
0aa351443f Bump gocui 2024-08-24 10:47:25 +02:00
Stefan Haller
c28ecabfd8 Support hyperlinks from pagers (#3825)
- **PR Description**

Allows to use `delta --hyperlinks` as a pager, which turns line numbers
in the diff into clickable links that take you to the respective file.
For VS Code users, I recommend to combine this with
`--hyperlinks-file-link-format="vscode://file/{path}:{line}"`
so that it jumps to the right line.

In addition, I added a few commits that replaces our old, manual ad-hoc
handling of links in various places (status view, confirmation panels,
information view) with the new hyperlinks feature, which cleans up the
code a bit.

Fixes #3817.
2024-08-24 10:39:41 +02:00
Stefan Haller
bbd779b437 Use our new hyperlink support in the information view 2024-08-24 10:36:01 +02:00
Stefan Haller
b411897a5a Fix Decolorise to also strip hyperlinks
This is needed so that the information view is correctly aligned when we add
hyperlinks to it.
2024-08-24 10:36:01 +02:00
Stefan Haller
fb97c30080 Remove now unused function handleGenericClick 2024-08-24 10:36:01 +02:00
Stefan Haller
61b59837bb Use our new hyperlink support in confirmations 2024-08-24 10:36:01 +02:00
Stefan Haller
e65c0a9d5e Use our new hyperlink support in the status panel 2024-08-24 10:36:01 +02:00
Stefan Haller
3f7674a2e9 Add function to render a hyperlink
It might seem cleaner to integrate this into the text style system, so that you
could say `ts := ts.Url("some link")` and then `ts.Sprint("my text")`. However,
this would require adding a new field to TextStyle, which I didn't want to do.
2024-08-24 10:36:01 +02:00
Stefan Haller
fb81fc6057 Add documentation about delta --hyperlinks 2024-08-24 10:36:01 +02:00
Stefan Haller
e1acb6a547 Set an openHyperlink function on gocui 2024-08-24 10:36:01 +02:00
Stefan Haller
250eb14de1 Bump gocui 2024-08-24 10:35:59 +02:00
Stefan Haller
61ae5e16ae Improve mouse support for commit message panel (#3836)
#### PR Description

* Fix some minor problems related to cursor movement in an auto-wrapped
  commit message
* Allow clicking in an editable view to set the cursor (most useful in
  longer commit descriptions)
* Allow switching between commit message and description by clicking
2024-08-24 10:29:44 +02:00
Stefan Haller
e1efbfc842 Make it possible to scroll the commit description with the mouse wheel
It is just unexpected and confusing when it isn't.

There's something weird about the cursor position when scrolling it out of view;
it will be shown clamped to the visible area of the view (as if it had moved in
the opposite direction than the scroll direction), but then when you type
something again, or just move the cursor with the arrow keys, the view will jump
back to where the cursor really was. This looks confusing, and it might be
reason enough not to allow scrolling the view at all.
2024-08-24 10:21:30 +02:00
Stefan Haller
7d486cabeb Allow switching between commit message and description by clicking
It is annoying to have to tab to the description first before you can set the
cursor there by clicking.
2024-08-24 10:21:30 +02:00
Stefan Haller
c2e953a09f Cleanup: use the right context for CommitDescriptionController.Context()
It doesn't seem to be used by anything, it looks like we only need to implement
the method so that the IController interface is satisfied. But still, it doesn't
hurt to be correct here, and might avoid confusion in the future when somebody
does try to use the method.
2024-08-24 10:21:30 +02:00
Stefan Haller
59450c7d12 Bump gocui 2024-08-24 10:21:25 +02:00
Jesse Duffield
ca9e006cca Fix range select -> stage failure when deleted file is already staged (#3631)
- **PR Description**

Fix range select -> stage failure when deleted file is already staged.

Fixes https://github.com/jesseduffield/lazygit/issues/3603

- **Please check if the PR fulfills these requirements**

* [ ] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [x] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [ ] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] Docs have been updated if necessary
* [x] You've read through your own file changes for silly mistakes etc

<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for
examples
-->
2024-08-24 10:31:46 +10:00
Brandon
387bdb1b84 Don't stage already staged deleted items 2024-08-24 09:36:44 +10:00
Jesse Duffield
5fb98655b3 Improve fixup commits script (#3853)
This script is failing currently on
https://github.com/jesseduffield/lazygit/pull/3631 because that fork's
master branch is 300 commits behind our own, but the feature branch is
up to date.

The thing is, we don't actually need to involve the master branch. All
we care about is the feature branch's own commits, so this commit simply
fetches those commits and checks them.

- **PR Description**

- **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))
* [ ] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [ ] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [x] You've read through your own file changes for silly mistakes etc

<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for
examples
-->
2024-08-24 09:36:30 +10:00
Jesse Duffield
addfa2f961 Improve fixup commits script
This script is failing currently on
https://github.com/jesseduffield/lazygit/pull/3631 because that fork's
master branch is 300 commits behind our own, but the feature branch is
up to date.

The thing is, we don't actually need to involve the master branch. All
we care about is the feature branch's own commits, so this commit simply
fetches those commits and checks them.
2024-08-24 09:27:02 +10:00
Stefan Haller
7679b109cb Specifying branch name source from refs/heads for fast forwarding (#3807)
- **PR Description**
This fixes #2625
2024-08-18 10:45:42 +02:00
Neko Box Coder
6a418c65ca Specifying branch name source from refs/heads for fast forwarding 2024-08-18 10:42:57 +02:00
Stefan Haller
aa55995924 Per-repo config files (and reloading of edited config files) (#3787)
- **PR Description**

Support per-repo user config files. For now we only support
`.git/lazygit.yml`; in the future we would also like to support
`./.lazygit.yml`, but that one will need a trust prompt as it could be
versioned, which adds quite a bit of complexity, so we leave that for
later.

We do, however, support config files in parent directories (all the way
up to the root directory). This makes it possible to add a config file
that applies to multiple repos at once. Useful if you want to set
different options for all your work repos vs. all your open-source
repos, for instance.

In addition, we support re-loading edited config files. This makes it
much easier to experiment with config settings, especially the ones that
affect the layout or color scheme, because you see the effect
immediately without having to restart lazygit.
2024-08-18 10:28:33 +02:00
Stefan Haller
5431c269d3 Add checkbox to PR template to make sure UserConfig entries can be hot-reloaded 2024-08-18 10:24:53 +02:00
Stefan Haller
73696629d9 Add dev documentation about using UserConfig 2024-08-18 10:24:53 +02:00
Stefan Haller
38b1255119 Add information about per-repo config files to Config.md 2024-08-18 10:24:53 +02:00
Stefan Haller
57de11b709 Show a confirmation when changing a config that can't be auto-reloaded 2024-08-18 10:24:53 +02:00
Stefan Haller
ce50533689 Reload changed user config files on terminal focus 2024-08-18 10:24:53 +02:00
Stefan Haller
18ad975573 Make custom commands reload when switching repos
Since onNewRepo calls resetKeybindings, which reinitializes the keybindings for
custom commands, all we have to do for this is store a pointer to a config
instead of storing the customCommands, so we get the up-to-date ones every time.
2024-08-18 10:24:52 +02:00
Stefan Haller
fd8e480363 Re-determine existing main branches if mainBranches config changed 2024-08-18 10:24:52 +02:00
Stefan Haller
3d6d677453 Store Common instead of just the list of configured main branches in MainBranches
This will make it possible to change the configured main branches at runtime.
We'll support this in the next commit.
2024-08-18 10:24:52 +02:00
Stefan Haller
aef8e71b82 Make gui.commitLength hot-reloadable 2024-08-18 10:24:52 +02:00
Stefan Haller
5872779faa Make gui.statusPanelView config hot-reloadable
It still doesn't update the view immediately when reloading the config, only the
next time the status panel is focused. But at least it does it then; writing the
code to update the panel when reloading the config is probably not justifiable.
2024-08-18 10:24:52 +02:00
Stefan Haller
04d7907864 Move initialization of global gocui properties to onUserConfigLoaded 2024-08-18 10:24:52 +02:00
Stefan Haller
65a2eccfdb Set custom author and branch colors and nerd font version after loading user config 2024-08-18 10:24:52 +02:00
Stefan Haller
4a272afc67 Reinitialize gui.ShowExtrasWindow after loading user config 2024-08-18 10:24:52 +02:00
Stefan Haller
2499a6c8a3 Initialize translation set after reading user config
This allows having per-repo config files with different languages per repo. Now
granted, this is not an important use case that we need to support; however, the
goal is to eventually make all configs hot-reloadable (as opposed to loading
them only once at startup), so this is one step in that direction.
2024-08-18 10:24:52 +02:00
Stefan Haller
74ed1ac584 Support per-repo config files
For now we only support .git/lazygit.yml; in the future we would also like to
support ./.lazygit.yml, but that one will need a trust prompt as it could be
versioned, which adds quite a bit of complexity, so we leave that for later.

We do, however, support config files in parent directories (all the way up to
the root directory). This makes it possible to add a config file that applies to
multiple repos at once. Useful if you want to set different options for all your
work repos vs. all your open-source repos, for instance.
2024-08-18 10:24:52 +02:00
Stefan Haller
d6d48f2866 Make common.UserConfig an atomic.Pointer for safe concurrent access
Currently, userConfig is only read once at startup and then never changes. Later
in this branch, we will add the possibility to reload the user config; this can
happen either when switching repositories, or when the user has edited the
config file. In both cases, reloading happens on the main thread, but the user
config could be accessed concurrently from background threads, so we need to
make this safe.
2024-08-18 10:24:52 +02:00
Stefan Haller
f98b57aa5e Change direct access to Common.UserConfig to a getter
This will allow us to turn the field into an atomic.Value for safe concurrent
access.
2024-08-18 10:24:52 +02:00
Stefan Haller
f114321322 Save changed user config back to disk in integration tests
At the moment, the user config is only read once at startup, so there's no point
in writing it back to disk. However, later in this branch we will add code that
reloads the user config when switching repos, which does happen quite a bit in
integration tests; this would undo the changes that a test made in its
SetupConfig function, so write those changes to disk to prevent that from
happening.
2024-08-18 10:24:52 +02:00
Stefan Haller
940700dc56 Introduce ConfigFile struct
This makes it more explicit how to deal with the different types of config
files: a user-supplied config file (via the LG_CONFIG_FILE env var) is required
to exist, whereas the default config file will be created if it is missing.

We will later extend this with repo-specific config files, which will be skipped
if missing.
2024-08-18 10:24:52 +02:00
Stefan Haller
be0fa98d11 Split createAllViews
Split it so createAllViews instanciates the views, and sets those properties
that are independent of the user config, and configureViewProperties which sets
those things that do depend on the user config. For now we call the second right
after the first, but later we'll call configureViewProperties after reloading
the user config.
2024-08-18 10:24:52 +02:00
Stefan Haller
e71fc43952 Remove return value of Gui.setColorScheme
It always returns nil.
2024-08-18 10:24:52 +02:00
Stefan Haller
cd0f72d66a Remove pointless reloading of UserConfig
It was added in 043cb2ea44, and the commit message was "reload config whenever
returning to gui". I don't understand what this means; Run() is called exactly
once after startup, so it would just reload the config again for no reason.

We will add a real way of reloading the config whenever it has changed later in
this branch.
2024-08-18 10:24:52 +02:00
Stefan Haller
48bb3a9dce Make fields of AppConfig private
We are going to make a few changes to the fields in this branch, and we can make
them with more peace of mind when we can be sure they are not accessed from
outside this package.
2024-08-18 10:24:52 +02:00
Stefan Haller
55d8e801f1 Use getters for AppState and UserConfig instead of accessing the fields directly
This will allow us to make them private.
2024-08-18 10:24:52 +02:00
Stefan Haller
54765d2236 Cleanup: remove unused method AppConfig.ConfigFilename 2024-08-18 10:24:52 +02:00
Stefan Haller
dbf6716b9b Cleanup: remove unused field IsNewRepo 2024-08-18 10:24:52 +02:00
Stefan Haller
a19bb0bbfe Cleanup: remove unused field DeafultConfFiles
It was also embarrassingly misspelled, so it's good that it's gone. :-)
2024-08-18 10:24:52 +02:00
Stefan Haller
75a95865ff Use filepath instead of path for file path operations
In practice, using path seems to work too, since Windows seems to be capable of
dealing with a path like C:/x/y instead of C:\x\y; but it's cleaner to do this
properly.
2024-08-18 10:24:52 +02:00
Stefan Haller
07dd8a2b07 Bump gocui 2024-08-18 10:24:52 +02:00
Stefan Haller
8bcfa3660a Fix pressing escape after clicking in diff view (#3828)
- **PR Description**

When clicking in a single-file diff view to enter staging (or custom
patch editing, when coming from the commit files panel), you needed to
press escape twice to exit, where the first press would seemingly do
nothing.
2024-08-17 11:34:10 +02:00
Stefan Haller
0e4d266a52 Fix pressing escape after clicking in diff view
When clicking in a single-file diff view to enter staging (or custom patch
editing, when coming from the commit files panel), you needed to press escape
twice to exit, where the first press would seemingly do nothing.

The reason for this was that after clicking in the diff we end up in non-sticky
range select mode, but only with a single line selected, which is basically
indistinguishable from line select mode.
2024-08-17 11:30:38 +02:00
Stefan Haller
7676572358 Improve template placeholders for custom commands (#3809)
- **PR Description**

Improve the template placeholders that are available for custom
commands:
- `SelectedCommit` replaces `SelectedLocalCommit`,
`SelectedReflogCommit`, and `SelectedSubCommit`
- `SelectedPath` is set to `SelectedCommitFilePath` when the CommitFiles
context is active

It still slightly bothers me that we don't make a similar unification
for `SelectedLocalBranch` and `SelectedRemoteBranch` (and others), but
it would be a bigger change to do that, and we decided in #3663 not to.

Fixes #3663.
2024-08-17 11:29:49 +02:00
Stefan Haller
7fb758cc1d Set SelectedPath to SelectedCommitFilePath in CommitFiles context 2024-08-17 11:26:31 +02:00
Stefan Haller
22f0d9cdd3 Expose SelectedCommit to custom commands, deprecate Selected{Local,Reflog,Sub}Commit
SelectedCommit is context-dependent and points to SelectedLocalCommit,
SelectedReflogCommit, or SelectedSubCommit depending on which panel is active.

If none of these panels is active, it returns the selected local commit, which
is probably the most useful default (e.g. when defining custom commands for the
Files panel).
2024-08-17 11:26:31 +02:00
Stefan Haller
1cb29cea15 Some cleanups for APIs related to contexts (#3808)
- **PR Description**

Some cleanups for APIs related to contexts. Most of these were triggered
by a TODO comment in the code.
2024-08-17 11:24:15 +02:00
Stefan Haller
1eb5d89f1d Remove bool return value of GetParentContext()
The comments that I'm deleting here explain why we need the bool; however, in
our case that's a theoretical issue. It would only arise if we ever were to pass
a nil context to SetParentContext, which we never do.
2024-08-17 11:14:51 +02:00
Stefan Haller
d570552206 Replace ActivateContext() with Context().Activate() 2024-08-17 11:14:51 +02:00
Stefan Haller
41f41ee4ee Rename ActivateContext/deactivateContext to Activate/deactivate
"Context" is redundant in the method names here.
2024-08-17 11:14:51 +02:00
Stefan Haller
94d6f4dae7 Replace IsCurrentContext() with Context().IsCurrent() 2024-08-17 11:14:51 +02:00
Stefan Haller
f30387e7f5 Replace CurrentPopupContexts() with Context().CurrentPopup() 2024-08-17 11:14:51 +02:00
Stefan Haller
3a8b97841f Rename PopupContexts() to CurrentPopup()
... for consistency with CurrentSide().
2024-08-17 11:14:51 +02:00
Stefan Haller
df3afb1b89 Replace CurrentSideContext() with Context().CurrentSide() 2024-08-17 11:14:51 +02:00
Stefan Haller
7f935c1ea8 Replace CurrentStaticContext() with Context().CurrentStatic() 2024-08-17 11:14:51 +02:00
Stefan Haller
7ed94c0410 Replace CurrentContext() with Context().Current() 2024-08-17 11:14:51 +02:00
Stefan Haller
8e15451117 Remove unused method RemoveContexts() 2024-08-17 11:14:51 +02:00
Stefan Haller
111d10fe5c Replace ReplaceContext() with Context().Replace() 2024-08-17 11:14:50 +02:00
Stefan Haller
98335361fd Replace PopContext() with Context().Pop() 2024-08-17 11:14:50 +02:00
Stefan Haller
bd36b8a95e Replace PushContext() with Context().Push() 2024-08-17 11:14:50 +02:00
Stefan Haller
d89dc967b8 Rename "Custom Command" to "Shell Command" (#3800)
- **PR Description**

The double use of the term "Custom Command" for both shell commands and
user-configured keybindings was confusing.
2024-08-17 10:59:23 +02:00
Stefan Haller
dbca9306de Rename "Custom Command" to "Shell Command"
The double use of the term "Custom Command" for both shell commands and
user-configured keybindings was confusing.
2024-08-17 10:56:03 +02:00
Stefan Haller
0cbe08b105 Add new integration tests folder "shell_commands"
The folder custom_commands contained tests for both custom commands (the ones
you configure in config.yml) and shell commands (the ones you execute at the ":"
prompt). I always found this confusing, so separate these into two different
folders.
2024-08-17 10:56:03 +02:00
Stefan Haller
af5b19a9da Fix line coloring when using the delta pager (#3820)
- **PR Description**

The green/red bars for added/deleted lines in delta wouldn't extend to
the right edge of the view, but end after the last printable character.
This was broken before, and fixed in #1386 two years ago, but then it
broke again in v0.42 because of #3687.
2024-08-17 10:53:47 +02:00
Stefan Haller
da8e4e44b7 Bump gocui 2024-08-17 10:50:32 +02:00
Stefan Haller
69666d1089 Switch tabs with panel jump keys (#3794)
- **PR Description**

When using the panel jump keybindings (`1` through `5` by default), and
the target panel is already the active one, go to the next tab instead.
2024-08-17 10:39:57 +02:00
Stefan Haller
b37d6dcd1c When using the panel jump keys and the target panel is already active, switch tabs 2024-08-17 10:37:00 +02:00
Stefan Haller
c72be6cbf3 Allow using shell aliases in interactive custom commands (#3793)
- **PR Description**

When executing an interactive custom command, use the user's shell
rather than "bash", and pass the -i flag. This makes it possible to use
shell aliases or shell functions which are not available in
non-interactive shells.

In previous attempts to solve this, concerns were brought up: [this
one](https://github.com/jesseduffield/lazygit/pull/2096#issuecomment-1257072541)
is addressed by using the interactive shell only for custom commands but
not anything else. [This
one](https://github.com/jesseduffield/lazygit/pull/2096#issuecomment-1343341795)
is a little dubious and unconfirmed, so I'm not very worried about it.

Supersedes #2096 and #3299. Fixes #770, #899, and #1642.
2024-08-17 10:34:40 +02:00
Stefan Haller
5a3049485c Use an interactive shell for running custom commands
Also, use the user's shell (from the SHELL env variable) instead of bash. Both
of these together allow users to use their shell aliases or shell functions in
the interactive command prompt.
2024-08-17 10:32:18 +02:00
Stefan Haller
39e77d1823 Extract helper function quotedCommandString
Will be reused in the next commit.
2024-08-17 10:32:18 +02:00
Stefan Haller
da94ee7a9a Fix redraw bug (stale content) in commits view (#3783)
- **PR Description**

When switching from a branch with fewer commits than fit in the commits
panel to another branch with even fewer commits, the last few commits of
the old branch were still drawn at the bottom.

Fixes #3778.
2024-08-17 10:29:39 +02:00
Stefan Haller
62ca873ddd Bump gocui 2024-08-17 10:25:16 +02:00
Stefan Haller
9404c2309c Allow GPG reword for last commit (#3815)
- **PR Description**
This PR fixes #3806, which is only for the last commit.
Would be good if this could be extended to commits older than head.
2024-08-16 13:11:17 +02:00
Neko Box Coder
4a2508e960 Allow rewording for last commit using GPG 2024-08-16 13:08:31 +02:00
Neko Box Coder
ce6388bdfa Adding guard to not do reword under git_commands when using gpg 2024-08-15 08:54:23 +02:00
Stefan Haller
58d7467180 Fix lack of icon assignation when extension don't match capitalization (lowercase) (#3810)
- **PR Description**
The extension icon map contain all extensions on lowercase, when a file
don't have extension on lowercase the string don't match and icon is not
assigned.
2024-08-09 12:19:46 +02:00
hasecilu
cb53e377a8 Fix lack of icon assignation when extension don't match capitalization 2024-08-08 15:18:02 -06:00
Stefan Haller
a3560eb451 Don't exit app when GetRepoPaths call fails during startup (#3779)
- **PR Description**
Fixes #3740 

As explained in the issue, 7a67096 moved some code around that caused a
call to `GetRepoPaths` to occur before `setupApp`, which usually handles
the scenario where we are not in a git directory. `GetRepoPaths` returns
an error if the path isn't a git repository, which caused the app to
exit before we reached `setupApp`.

When starting up lazygit, we ignore (and log) the error returned by
`GetRepoPaths`, and continue instead of exiting early. This allows us to
reach the step where we follow the user's `notARepository` config entry.
2024-08-03 18:37:53 +02:00
ppoum
ef4fd70f9c Ignore GetRepoPaths error when launching 2024-08-03 10:02:47 -04:00
Stefan Haller
74fe069da9 feat(custom command): support multiple contexts within one command (#3784)
- **PR Description**

For some custom commands, they can be used in multiple contexts. But for
now, if we want to do this, we should copy and paste the same config
times and times with just a different **context**.

Related issue: #3759 

This PR makes it possible to use multiple contexts in the `context` field of 
`customCommand`, separated by comma.
2024-08-02 11:59:39 +02:00
Yam Liu
542030f190 Support multiple contexts within one command, add tests, update doc 2024-08-02 11:55:29 +02:00
Yam Liu
206b2c6f0b Add a unit test case for global context 2024-08-02 11:55:29 +02:00
Stefan Haller
dc87592876 Add a readme file for the JSON files in pkg/i18n/translations (#3781) 2024-07-26 11:04:18 +02:00
Stefan Haller
37f35da436 Add a readme file for the JSON files in pkg/i18n/translations
People have started sending PRs that change these files.
2024-07-26 11:00:10 +02:00
Stefan Haller
f598da0df2 Reapply "Check for fixup commits on CI" (#3745)
Re-enable the check for fixup commits (see #3742), make it work for
forks, and extend it to "amend!" and "WIP" commits (and a few others).
2024-07-13 15:17:50 +02:00
Stefan Haller
20ccb03a45 Extend check for fixups
Also check for squash! and amend! (these are all anchored to the beginning of
the subject), and WIP and DROPME for good measure (but only if they occur in the
first line, otherwise it wouldn't let me merge this very commit :)
2024-07-13 15:15:01 +02:00
Stefan Haller
891362dfb2 Use extended regex rather than perl regex in the git call
My local git is not compiled with PCRE support, so using -E makes it easier for
me to test the script locally. And -E is good enough for the simple matching we
want to do here.
2024-07-13 15:02:34 +02:00
Stefan Haller
463cf35e64 Make checkout action work with forks 2024-07-13 14:39:28 +02:00
Stefan Haller
1919a2d2d6 Reapply "Check for fixup commits on CI"
This reverts commit f2db9fa3f9.
2024-07-13 14:08:44 +02:00
Stefan Haller
71ad3fac63 Fix language auto detection (#3744)
- **PR Description**

Fix a regression (introduced with #3649) that broke language
auto-detection. When starting lazygit with the `gui.language` config set
to "auto" (which is the default), lazygit would fail to start if the
LANG environment is set to one of our supported languages.

For example:
```
$ export LANG=nl_NL
$ lazygit
2024/07/13 11:43:03 open translations/nl-NL.json: file does not exist
```

Fixes #3743
2024-07-13 12:14:39 +02:00
Stefan Haller
ae4a579153 Fix language auto-detection
Starting lazygit with an environment containing LANG=ko_KO or LANG=nl_NL would
result in an error at startup.
2024-07-13 12:07:59 +02:00
Stefan Haller
da86096e19 Add test that demonstrates bug with language auto-detection 2024-07-13 12:07:59 +02:00
419 changed files with 7272 additions and 2795 deletions

7
.codespellrc Normal file
View File

@@ -0,0 +1,7 @@
[codespell]
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
skip = .git*,go.sum,*.lock,.codespellrc,vendor,translations,Keybindings_*.md
check-hidden = true
# camel-cased
ignore-regex = (\b[A-Za-z][a-z]*[A-Z]\S+\b|\.edn\b|\S+…|\\nd\b)
ignore-words-list = fomrat,inbetween

View File

@@ -6,6 +6,7 @@
* [ ] Code has been formatted (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [ ] Tests have been added/updated (see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) for the integration test guide)
* [ ] Text is internationalised (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be hot-reloaded (see [here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [ ] You've read through your own file changes for silly mistakes etc

3
.github/release.yml vendored
View File

@@ -21,6 +21,9 @@ changelog:
- title: I18n 🌎
labels:
- i18n
- title: Performance Improvements 📊
labels:
- performance
- title: Other Changes
labels:
- "*"

View File

@@ -170,9 +170,9 @@ jobs:
with:
go-version: 1.22.x
- name: Lint
uses: golangci/golangci-lint-action@v3.7.0
uses: golangci/golangci-lint-action@v6.1.0
with:
version: v1.58
version: v1.60
- name: errors
run: golangci-lint run
if: ${{ failure() }}
@@ -184,7 +184,7 @@ jobs:
with:
mode: exactly
count: 1
labels: "ignore-for-release, feature, enhancement, bug, maintenance, docs, i18n"
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]
@@ -219,3 +219,22 @@ jobs:
CODACY_PROJECT_TOKEN=${{ secrets.CODACY_PROJECT_TOKEN }} \
bash <(curl -Ls https://coverage.codacy.com/get.sh) report \
--force-coverage-parser go -r coverage.out
check-for-fixups:
runs-on: ubuntu-latest
if: github.ref != 'refs/heads/master'
steps:
# See https://github.com/actions/checkout/issues/552#issuecomment-1167086216
- name: "PR commits"
run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} ))" >> "${GITHUB_ENV}"
- name: "Checkout PR branch and all PR commits"
uses: actions/checkout@v4
with:
repository: ${{ github.event.pull_request.head.repo.full_name }}
ref: ${{ github.event.pull_request.head.ref }}
fetch-depth: ${{ env.PR_FETCH_DEPTH }}
- name: Check for fixups
run: |
./scripts/check_for_fixups.sh ${{ github.event.pull_request.base.ref }}

25
.github/workflows/codespell.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
# Codespell configuration is within .codespellrc
---
name: Codespell
on:
push:
branches: [master]
pull_request:
branches: [master]
permissions:
contents: read
jobs:
codespell:
name: Check for spelling errors
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Annotate locations with typos
uses: codespell-project/codespell-problem-matcher@v1
- name: Codespell
uses: codespell-project/actions-codespell@v2

1
.gitignore vendored
View File

@@ -5,6 +5,7 @@
# Hidden
.*
!.codespellrc
# Notes
*.notes

View File

@@ -1,13 +1,10 @@
linters:
disable:
- structcheck # gives false positives
enable:
- gofumpt
- thelper
- goimports
- tparallel
- wastedassign
- exportloopref
- unparam
- prealloc
- unconvert

View File

@@ -1,6 +1,6 @@
# User Config
Default path for the config file:
Default path for the global config file:
- Linux: `~/.config/lazygit/config.yml`
- MacOS: `~/Library/Application\ Support/lazygit/config.yml`
@@ -16,6 +16,8 @@ If you want to change the config directory:
- MacOS: `export XDG_CONFIG_HOME="$HOME/.config"`
In addition to the global config file you can create repo-specific config files in `<repo>/.git/lazygit.yml`. Settings in these files override settings in the global config file. In addition, files called `.lazygit.yml` in any of the parent directories of a repo will also be loaded; this can be useful if you have settings that you want to apply to a group of repositories.
JSON schema is available for `config.yml` so that IntelliSense in Visual Studio Code (completion and error checking) is automatically enabled when the [YAML Red Hat][yaml] extension is installed. However, note that automatic schema detection only works if your config file is in one of the standard paths mentioned above. If you override the path to the file, you can still make IntelliSense work by adding
```yaml
@@ -244,6 +246,12 @@ gui:
# One of 'dashboard' (default) | 'allBranchesLog'
statusPanelView: dashboard
# If true, jump to the Files panel after popping a stash
switchToFilesAfterStashPop: true
# If true, jump to the Files panel after applying a stash
switchToFilesAfterStashApply: true
# Config relating to git
git:
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md
@@ -303,6 +311,12 @@ git:
# If true, pass the --all arg to git fetch
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.
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}} --
@@ -495,7 +509,7 @@ keybinding:
scrollDownMain-alt1: J
scrollUpMain-alt2: <c-u>
scrollDownMain-alt2: <c-d>
executeCustomCommand: ':'
executeShellCommand: ':'
createRebaseOptionsMenu: m
# 'Files' appended for legacy reasons
@@ -665,7 +679,7 @@ os:
editPreset: 'vscode'
```
Supported presets are `vim`, `nvim`, `nvim-remote`, `lvim`, `emacs`, `nano`, `micro`, `vscode`, `sublime`, `bbedit`, `kakoune`, `helix`, and `xcode`. In many cases lazygit will be able to guess the right preset from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
Supported presets are `vim`, `nvim`, `nvim-remote`, `lvim`, `emacs`, `nano`, `micro`, `vscode`, `sublime`, `bbedit`, `kakoune`, `helix`, `xcode`, and `zed`. In many cases lazygit will be able to guess the right preset from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
`nvim-remote` is an experimental preset for when you have invoked lazygit from within a neovim process, allowing lazygit to open the file from within the parent process rather than spawning a new one.

View File

@@ -87,6 +87,11 @@ The permitted contexts are:
| stash | The 'Stash' tab |
| global | This keybinding will take affect everywhere |
> **Bonus**
>
> You can use a comma-separated string, such as `context: 'commits, subCommits'`, to make it effective in multiple contexts.
## Prompts
### Common fields
@@ -291,9 +296,7 @@ Here's an example using a command but not specifying anything else: so each line
Your commands can contain placeholder strings using Go's [template syntax](https://jan.newmarch.name/golang/template/chapter-template.html). The template syntax is pretty powerful, letting you do things like conditionals if you want, but for the most part you'll simply want to be accessing the fields on the following objects:
```
SelectedLocalCommit
SelectedReflogCommit
SelectedSubCommit
SelectedCommit
SelectedFile
SelectedPath
SelectedLocalBranch
@@ -306,6 +309,9 @@ SelectedWorktree
CheckedOutBranch
```
(For legacy reasons, `SelectedLocalCommit`, `SelectedReflogCommit`, and `SelectedSubCommit` are also available, but they are deprecated.)
To see what fields are available on e.g. the `SelectedFile`, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/services/custom_commands/models.go) (all the modelling lives in the same file).
## Keybinding collisions

View File

@@ -26,6 +26,8 @@ git:
![](https://i.imgur.com/QJpQkF3.png)
A cool feature of delta is --hyperlinks, which renders clickable links for the line numbers in the left margin, and lazygit supports these. To use them, set the `pager:` config to `delta --dark --paging=never --line-numbers --hyperlinks --hyperlinks-file-link-format="lazygit-edit://{path}:{line}"`; this allows you to click on an underlined line number in the diff to jump right to that same line in your editor.
## Diff-so-fancy
```yaml

View File

@@ -2,7 +2,7 @@
## Packages
* `pkg/app`: Contains startup code, inititalises a bunch of stuff like logging, the user config, etc, before starting the gui. Catches and handles some errors that the gui raises.
* `pkg/app`: Contains startup code, initialises a bunch of stuff like logging, the user config, etc, before starting the gui. Catches and handles some errors that the gui raises.
* `pkg/app/daemon`: Contains code relating to the lazygit daemon. This could be better named: it's is not a daemon in the sense that it's a long-running background process; rather it's a short-lived background process that we pass to git for certain tasks, like GIT_EDITOR for when we want to set the TODO file for an interactive rebase.
* `pkg/cheatsheet`: Generates the keybinding cheatsheets in `docs/keybindings`.
* `pkg/commands/git_commands`: All communication to the git binary happens here. So for example there's a `Checkout` method which calls `git checkout`.
@@ -12,7 +12,7 @@
* `pkg/commands/models`: Contains model structs that represent commits, branches, files, etc.
* `pkg/commands/patch`: Contains code for parsing and working with git patches
* `pkg/common`: Contains the `Common` struct which holds common dependencies like the logger, i18n, and the user config. Most structs in the code will have a field named `c` which holds a common struct (or a derivative of the common struct).
* `pkg/config`: Contains code relating to the Lazygit user config. Specifically `pkg/config/user_config/go` defines the user config struct and its default values.
* `pkg/config`: Contains code relating to the Lazygit user config. Specifically `pkg/config/user_config/go` defines the user config struct and its default values. See [below](#using-userconfig) for some important information about using it.
* `pkg/constants`: Contains some constant strings (e.g. links to docs)
* `pkg/env`: Contains code relating to setting/getting environment variables
* `pkg/i18n`: Contains internationalised strings
@@ -86,6 +86,12 @@ The event loop is managed in the `MainLoop` function of `vendor/github.com/jesse
Often, as part of handling a keypress, we'll want to run some code asynchronously so that it doesn't block the UI thread. For this we'll typically run `self.c.OnWorker(myFunc)`. If the worker wants to then do something on the UI thread again it can call `self.c.OnUIThread(myOtherFunc)`.
## Using UserConfig
The UserConfig struct is loaded from lazygit's global config file (and possibly repo-specific config files). It can be re-loaded while lazygit is running, e.g. when the user edits one of the config files. In this case we should make sure that any new or changed config values take effect immediately. The easiest way to achieve this is what we do in most controllers or helpers: these have a pointer to the `common.Common` struct, which contains the UserConfig, and access it from there. Since the UserConfig instance in `common.Common` is updated whenever we reload the config, the code can be sure that it always uses an up-to-date value, and there's nothing else to do.
If that's not possible for some reason, see if you can add code to `Gui.onUserConfigLoaded` to update things from the new config; there are some examples in that function to use as a guide. If that's too hard to do too, add the config to the list in `Gui.checkForChangedConfigsThatDontAutoReload` so that the user is asked to quit and restart lazygit.
## Legacy code structure
Before we had controllers and contexts, all the code lived directly in the gui package under a gui God Struct. This was fairly bloated and so we split things out to have a better separation of concerns. Nonetheless, it's a big effort to migrate all the code so we still have some logic in the gui struct that ought to live somewhere else. Likewise, we have some keybindings defined in `pkg/gui/keybindings.go` that ought to live on a controller (all keybindings used to be defined in that one file).

View File

@@ -122,7 +122,7 @@ is this:
"github.com/jesseduffield/lazygit/pkg/utils"
+ "github.com/samber/lo"
"golang.org/x/sync/errgroup"
@@ -308,9 +309,5 @@ func (self *FixupHelper) blameAddedLines(addedLineHunks []*hunk) ([]string, erro
@@ -308,9 +309,5 @@ func (self *FixupHelper) blameAddedLines(addedLineHunks []*hunk) ([]string, error
func findCommit(hash string) (*models.Commit, int, bool) {
- for i, commit := range self.c.Model().Commits {
- if commit.Hash == hash {

View File

@@ -18,7 +18,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view. |
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view. |
| `` : `` | Execute custom command | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` <c-p> `` | View custom patch options | |
| `` m `` | View merge/rebase options | View options to abort/continue/skip the current merge/rebase. |
| `` 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`. |

View File

@@ -18,7 +18,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view. |
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view. |
| `` : `` | カスタムコマンドを実行 | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` <c-p> `` | View custom patch options | |
| `` m `` | View merge/rebase options | View options to abort/continue/skip the current merge/rebase. |
| `` 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`. |

View File

@@ -18,7 +18,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | Diff 보기의 변경 사항 주위에 표시되는 컨텍스트의 크기를 늘리기 | Increase the amount of the context shown around changes in the diff view. |
| `` { `` | Diff 보기의 변경 사항 주위에 표시되는 컨텍스트 크기 줄이기 | Decrease the amount of the context shown around changes in the diff view. |
| `` : `` | Execute custom command | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` <c-p> `` | 커스텀 Patch 옵션 보기 | |
| `` m `` | View merge/rebase options | View options to abort/continue/skip the current merge/rebase. |
| `` 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`. |

View File

@@ -18,7 +18,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view. |
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view. |
| `` : `` | Voer aangepaste commando uit | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` <c-p> `` | Bekijk aangepaste patch opties | |
| `` m `` | Bekijk merge/rebase opties | View options to abort/continue/skip the current merge/rebase. |
| `` 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`. |

View File

@@ -18,7 +18,7 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | Zwiększ rozmiar kontekstu w widoku różnic | Zwiększ ilość kontekstu pokazywanego wokół zmian w widoku różnic. |
| `` { `` | Zmniejsz rozmiar kontekstu w widoku różnic | Zmniejsz ilość kontekstu pokazywanego wokół zmian w widoku różnic. |
| `` : `` | Wykonaj polecenie niestandardowe | Wyświetl monit, w którym możesz wprowadzić polecenie powłoki do wykonania. Nie należy mylić z wcześniej skonfigurowanymi poleceniami niestandardowymi. |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` <c-p> `` | Wyświetl opcje niestandardowej łatki | |
| `` m `` | Pokaż opcje scalania/rebase | Pokaż opcje do przerwania/kontynuowania/pominięcia bieżącego scalania/rebase. |
| `` 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`. |

View File

@@ -18,7 +18,7 @@ _Связки клавиш_
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | Увеличить размер контекста, отображаемого вокруг изменений в просмотрщике сравнении | Increase the amount of the context shown around changes in the diff view. |
| `` { `` | Уменьшите размер контекста, отображаемого вокруг изменений в просмотрщике сравнении | Decrease the amount of the context shown around changes in the diff view. |
| `` : `` | Выполнить пользовательскую команду | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` <c-p> `` | Просмотреть пользовательские параметры патча | |
| `` m `` | Просмотреть параметры слияния/перебазирования | View options to abort/continue/skip the current merge/rebase. |
| `` 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`. |

View File

@@ -2,7 +2,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
# Lazygit 按键绑定
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
_图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
## 全局键绑定
@@ -11,28 +11,28 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` <c-r> `` | 切换到最近的仓库 | |
| `` <pgup> (fn+up/shift+k) `` | 向上滚动主面板 | |
| `` <pgdown> (fn+down/shift+j) `` | 向下滚动主面板 | |
| `` @ `` | 打开命令日志菜单 | View options for the command log e.g. show/hide the command log and focus the command log. |
| `` P `` | 推送 | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` p `` | 拉取 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` @ `` | 打开命令日志菜单 | 查看命令日志的选项,例如显示/隐藏命令日志以及聚焦命令日志 |
| `` P `` | 推送 | 推送当前分支到它的上游。如果上游为配置,你可以在弹窗中配置上游分支。 |
| `` p `` | 拉取 | 从当前分支的远程分支获取改动。如果上游为配置,你可以在弹窗中配置上游分支。 |
| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | 扩大差异视图中显示的上下文范围 | Increase the amount of the context shown around changes in the diff view. |
| `` { `` | 缩小差异视图中显示的上下文范围 | Decrease the amount of the context shown around changes in the diff view. |
| `` : `` | 执行自定义命令 | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
| `` } `` | 扩大差异视图中显示的上下文范围 | 增加diff视图中围绕更改显示的上下文数量 |
| `` { `` | 缩小差异视图中显示的上下文范围 | 减少diff视图中围绕更改显示的上下文数量 |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` <c-p> `` | 查看自定义补丁选项 | |
| `` m `` | 查看 合并/变基 选项 | View options to abort/continue/skip the current merge/rebase. |
| `` 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`. |
| `` + `` | 下一屏模式正常/半屏/全屏 | |
| `` m `` | 查看 合并/变基 选项 | 查看当前合并或变基的中止、继续、跳过选项 |
| `` R `` | 刷新 | 刷新git状态(即在后台上运行`git status`,`git branch`等命令以更新面板内容) 不会运行`git fetch` |
| `` + `` | 下一屏模式(正常/半屏/全屏) | |
| `` _ `` | 上一屏模式 | |
| `` ? `` | 打开菜单 | |
| `` <c-s> `` | 查看按路径过滤选项 | 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. |
| `` <c-s> `` | 查看按路径过滤选项 | 查看用于过滤提交日志的选项,以便仅显示与过滤器匹配的提交。 |
| `` W `` | 打开 diff 菜单 | 查看与比较两个引用相关的选项,例如与选定的 ref 进行比较,输入要比较的 ref然后反转比较方向。 |
| `` <c-e> `` | 打开 diff 菜单 | 查看与比较两个引用相关的选项,例如与选定的 ref 进行比较,输入要比较的 ref然后反转比较方向。 |
| `` q `` | 退出 | |
| `` <esc> `` | 取消 | |
| `` <c-w> `` | 切换是否在差异视图中显示空白字符差异 | Toggle whether or not whitespace changes are shown in the diff view. |
| `` 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. |
| `` <c-w> `` | 切换是否在差异视图中显示空白字符差异 | 切换是否在diff视图中显示空白更改 |
| `` z `` | (通过 reflog)撤销「实验功能」 | Reflog将用于确定运行哪个git命令来撤消最后一个git命令。这并不包括对工作树的更改只考虑提交。 |
| `` <c-z> `` | (通过 reflog)重做「实验功能」 | Reflog将用于确定运行哪个git命令来重做上一个git命令。这并不包括对工作树的更改只考虑提交。 |
## 列表面板导航
@@ -43,8 +43,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` < `` | 滚动到顶部 | |
| `` > `` | 滚动到底部 | |
| `` v `` | 切换拖动选择 | |
| `` <s-down> `` | Range select down | |
| `` <s-up> `` | Range select up | |
| `` <s-down> `` | 向下扩展选择范围 | |
| `` <s-up> `` | 向上扩展选择范围 | |
| `` / `` | 开始搜索 | |
| `` H `` | 向左滚动 | |
| `` L `` | 向右滚动 | |
@@ -56,27 +56,17 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. |
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD |
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hashURLdiff、消息、作者)。 |
| `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | |
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 复制提交拣选 | 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> `` | 重置已拣选复制的提交 | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` <enter> `` | 查看提交 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## Worktrees
| Key | Action | Info |
|-----|--------|-------------|
| `` n `` | New worktree | |
| `` <space> `` | Switch | Switch to the selected worktree. |
| `` o `` | Open in editor | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
| `` / `` | Filter the current view by text | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | |
## 分支页面
@@ -84,42 +74,42 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|-----|--------|-------------|
| `` <c-o> `` | 将分支名称复制到剪贴板 | |
| `` i `` | 显示 git-flow 选项 | |
| `` <space> `` | 检出 | Checkout selected item. |
| `` <space> `` | 检出 | 检出选中的项目 |
| `` n `` | 新分支 | |
| `` o `` | 创建抓取请求 | |
| `` O `` | 创建抓取请求选项 | |
| `` <c-y> `` | 将抓取请求 URL 复制到剪贴板 | |
| `` c `` | 按名称检出 | Checkout by name. In the input box you can enter '-' to switch to the last branch. |
| `` F `` | 强制检出 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
| `` d `` | Delete | View delete options for local/remote branch. |
| `` r `` | 将已检出的分支变基到该分支 | Rebase the checked-out branch onto the selected branch. |
| `` M `` | 合并到当前检出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` f `` | 从上游快进此分支 | Fast-forward selected branch from its upstream. |
| `` c `` | 按名称检出 | 按名称检出。在输入框中,您可以输入'-' 来切换到最后一个分支。 |
| `` F `` | 强制检出 | 强制检出所选分支。这将在检出所选分支之前放弃工作目录中的所有本地更改。 |
| `` d `` | 删除 | 查看本地/远程分支的删除选项 |
| `` r `` | 将已检出的分支变基到该分支 | 将检出的分支变基到所选的分支上。 |
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
| `` f `` | 从上游快进此分支 | 将当前分支直接移动到远程追踪分支的最新提交 |
| `` T `` | 创建标签 | |
| `` s `` | Sort order | |
| `` s `` | 排序 | |
| `` g `` | 查看重置选项 | |
| `` R `` | 重命名分支 | |
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` u `` | 查看上游选项 | 查看与分支上游相关的选项,例如设置/取消设置上游和重置为上游。 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` <enter> `` | 查看提交 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | |
## 子提交
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. |
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD |
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hashURLdiff、消息、作者)。 |
| `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | |
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 复制提交拣选 | 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> `` | 重置已拣选复制的提交 | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` <enter> `` | 查看提交的文件 | |
| `` w `` | View worktree options | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 开始搜索 | |
## 子模块
@@ -127,104 +117,114 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将子模块名称复制到剪贴板 | |
| `` <enter> `` | Enter | 输入子模块 |
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. |
| `` u `` | Update | 更新子模块 |
| `` <enter> `` | 进入 | 输入子模块 |
| `` d `` | 删除 | 删除选定的子模块及其相应的目录 |
| `` u `` | 更新 | 更新子模块 |
| `` n `` | 添加新的子模块 | |
| `` e `` | 更新子模块 URL | |
| `` i `` | Initialize | 初始化子模块 |
| `` i `` | 初始化 | 初始化子模块 |
| `` b `` | 查看批量子模块选项 | |
| `` / `` | Filter the current view by text | |
| `` / `` | 通过文本过滤当前视图 | |
## 工作区
| Key | Action | Info |
|-----|--------|-------------|
| `` n `` | 新建工作树 | |
| `` <space> `` | 切换 | 切换到选中的工作树 |
| `` o `` | 在编辑器中编写 | |
| `` d `` | 删除 | 删除选定的工作树。这将删除工作树的目录以及 .git 目录中有关工作树的元数据。 |
| `` / `` | 通过文本过滤当前视图 | |
## 提交
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
| `` <c-r> `` | 重置已拣选复制的提交 | |
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
| `` b `` | 查看二分查找选项 | |
| `` s `` | 压缩 | 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. |
| `` r `` | 改写提交 | Reword the selected commit's message. |
| `` s `` | 压缩(Squash) | 将已选提交压缩到该提交之下。这些选定的提交的消息会附加到该提交的消息之下。 |
| `` f `` | 修正(fixup) | 将选定的提交合并到其下面的提交中。与压缩类似,但所选提交的消息将被丢弃。 |
| `` r `` | 改写提交 | 重写所选提交的消息。 |
| `` R `` | 使用编辑器重命名提交 | |
| `` d `` | 删除提交 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
| `` e `` | Edit (start interactive rebase) | 编辑提交 |
| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.
If you would instead like to start an interactive rebase from the selected commit, press `e`. |
| `` p `` | Pick | 选择提交变基过程中 |
| `` d `` | 删除提交 | 删除选中的提交。这将通过变基从分支中删除该提交,如果该提交修改的内容依赖于后续的提交,则需要解决合并冲突。 |
| `` e `` | 编辑(开始交互式变基) | 编辑提交 |
| `` i `` | 开始交互式变基 | 为分支上的提交启动交互式变基。这将包括从 HEAD 提交到第一个合并提交或主分支提交的所有提交。
如果您想从所选提交启动交互式变基,请按 `e` |
| `` p `` | 拣选(Pick) | 选择提交(变基过程中) |
| `` F `` | 为此提交创建修正 | 创建修正提交 |
| `` S `` | Apply fixup commits | 压缩在所选提交之上的所有“fixup!”提交自动压缩 |
| `` S `` | 应用该修复提交 | 压缩在所选提交之上的所有“fixup!”提交(自动压缩) |
| `` <c-j> `` | 下移提交 | |
| `` <c-k> `` | 上移提交 | |
| `` V `` | 粘贴提交拣选 | |
| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. |
| `` A `` | Amend | 用已暂存的更来修补提交 |
| `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. |
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. |
| `` T `` | 标签提交 | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. |
| `` <c-l> `` | 打开日志菜单 | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. |
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. |
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` V `` | 粘贴提交(拣选) | |
| `` B `` | 标记一个主提交用于变基 | 选择下一次变基的主提交。当您变基到一个分支时,只有高于主提交的提交才会被引入。这使用“git rebase --onto”命令。 |
| `` A `` | 修补(Amend) | 用已暂存的更来修补提交 |
| `` a `` | 修补提交属性 | 设置或重置提交的作者,或添加其他作者。 |
| `` t `` | 撤销(Revert) | 为所选提交创建还原提交,这会反向应用所选提交的更改。 |
| `` T `` | 标签提交 | 创建一个新标签指向所选提交。你可以在弹窗中输入标签名称和描述(可选)。 |
| `` <c-l> `` | 打开日志菜单 | 查看提交日志的选项,例如更改排序顺序、隐藏 git graph、显示整个 git graph |
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD |
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hashURLdiff、消息、作者)。 |
| `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | |
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 复制提交拣选 | 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-t> `` | Open external diff tool (git difftool) | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` <enter> `` | 查看提交的文件 | |
| `` w `` | View worktree options | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 开始搜索 | |
## 提交信息
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 确认 | |
| `` <esc> `` | 关闭 | |
## 提交文件
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将文件名复制到剪贴板 | |
| `` c `` | 检出 | 检出文件 |
| `` d `` | Remove | 放弃对此文件的提交更 |
| `` o `` | 打开文件 | Open file in default application. |
| `` e `` | Edit | Open file in external editor. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <space> `` | 补丁中包含的切换文件 | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` <enter> `` | 输入文件以将所选行添加到补丁中或切换目录折叠 | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
| `` ` `` | 切换文件树视图 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
| `` d `` | 删除 | 放弃对此文件的提交更 |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` e `` | 编辑 | 使用外部编辑器打开文件 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` <space> `` | 补丁中包含的切换文件 | 切换文件是否包含在自定义补丁中。请参阅 https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches |
| `` a `` | 操作所有文件 | 添加或删除所有提交中的文件到自定义的补丁中。请参阅 https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches |
| `` <enter> `` | 输入文件以将所选行添加到补丁中(或切换目录折叠) | 如果已选择一个文件则Enter进入该文件以便您可以向自定义补丁添加/删除单独的行。如果选择了目录,则切换目录。 |
| `` ` `` | 切换文件树视图 | 在平铺部署与树布局之间切换文件视图。平铺布局在一个列表中展示所有文件路径,树布局则根据目录分组展示。 |
| `` / `` | 开始搜索 | |
## 提交讯息
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 确认 | |
| `` <esc> `` | 关闭 | |
## 文件
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将文件名复制到剪贴板 | |
| `` <space> `` | 切换暂存状态 | Toggle staged for selected file. |
| `` <c-b> `` | Filter files by status | |
| `` y `` | Copy to clipboard | |
| `` c `` | 提交更 | Commit staged changes. |
| `` w `` | 提交更而无需预先提交钩子 | |
| `` <space> `` | 切换暂存状态 | 为选定的文件切换暂存状态 |
| `` <c-b> `` | 通过状态过滤文件 | |
| `` y `` | 复制到剪贴板 | |
| `` c `` | 提交更 | 提交暂存文件 |
| `` w `` | 提交更而无需预先提交钩子 | |
| `` A `` | 修补最后一次提交 | |
| `` C `` | 提交更改(使用编辑器编辑提交信息 | |
| `` <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> |
| `` e `` | Edit | Open file in external editor. |
| `` o `` | 打开文件 | Open file in default application. |
| `` C `` | 提交变更(使用编辑器编辑提交信息) | |
| `` <c-f> `` | 找到用于修复的基准提交 | 找到您当前变更所基于的提交,以便于修正/改进该提交。这样做可以省去您逐一查看分支提交来确定应该修正/改进哪个提交的麻烦。请参阅文档: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` e `` | 编辑 | 使用外部编辑器打开文件 |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` i `` | 忽略文件 | |
| `` r `` | 刷新文件 | |
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
| `` S `` | 查看贮藏选项 | View stash options (e.g. stash all, stash staged, stash unstaged). |
| `` a `` | 切换所有文件的暂存状态 | Toggle staged/unstaged for all files in working tree. |
| `` <enter> `` | 暂存单个 块/行 用于文件, 或 折叠/展开 目录 | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. |
| `` d `` | 查看'放弃更'选项 | View options for discarding changes to the selected file. |
| `` s `` | 贮藏 | 贮藏所有变更.若要使用其他贮藏变体,请使用查看贮藏选项快捷键 |
| `` S `` | 查看贮藏选项 | 查看贮藏选项(例如:贮藏所有、贮藏已暂存变更、贮藏未暂存变更) |
| `` a `` | 切换所有文件的暂存状态 | 切换工作区中所有文件的已暂存/未暂存状态 |
| `` <enter> `` | 暂存单个 块/行 用于文件, 或 折叠/展开 目录 | 如果选中的是一个文件,则会进入到暂存视图,以便可以暂存单个代码块/行。如果选中的是一个目录,则会折叠/展开这个目录 |
| `` d `` | 查看'放弃更'选项 | 查看选中文件的放弃变更选项 |
| `` g `` | 查看上游重置选项 | |
| `` 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | 打开外部合并工具 (git mergetool) | Run `git mergetool`. |
| `` f `` | 抓取 | Fetch changes from remote. |
| `` D `` | 重置 | 查看工作树的重置选项(例如:清除工作树)。 |
| `` ` `` | 切换文件树视图 | 在平铺部署与树布局之间切换文件视图。平铺布局在一个列表中展示所有文件路径,树布局则根据目录分组展示。 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` M `` | 打开外部合并工具(git mergetool) | 执行 `git mergetool`. |
| `` f `` | 抓取 | 从远程获取变更 |
| `` / `` | 开始搜索 | |
## 构建补丁中
@@ -234,10 +234,10 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <left> `` | 选择上一个区块 | |
| `` <right> `` | 选择下一个区块 | |
| `` v `` | 切换拖动选择 | |
| `` a `` | 切换选择块 | Toggle hunk selection mode. |
| `` a `` | 切换选择代码块 | 切换代码块选择模式 |
| `` <c-o> `` | 将选中文本复制到剪贴板 | |
| `` o `` | 打开文件 | Open file in default application. |
| `` e `` | 编辑文件 | Open file in external editor. |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` e `` | 编辑文件 | 使用外部编辑器打开文件 |
| `` <space> `` | 添加/移除 行到补丁 | |
| `` <esc> `` | 退出逐行模式 | |
| `` / `` | 开始搜索 | |
@@ -246,15 +246,15 @@ If you would instead like to start an interactive rebase from the selected commi
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | 检出 | Checkout the selected tag tag as a detached HEAD. |
| `` n `` | 创建标签 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
| `` d `` | Delete | View delete options for local/remote tag. |
| `` P `` | 推送标签 | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <space> `` | 检出 | 检出选择的标签作为分离的HEAD |
| `` n `` | 创建标签 | 基于当前提交创建一个新标签。你将在弹窗中输入标签名称和描述(可选)。 |
| `` d `` | 删除 | 查看本地/远程标签的删除选项 |
| `` P `` | 推送标签 | 推送选择的标签到远端。你将在弹窗中选择一个远端。 |
| `` g `` | 重置 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` <enter> `` | 查看提交 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | |
## 正在合并
@@ -266,10 +266,10 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <down> `` | 选择底部块 | |
| `` <left> `` | 选择上一个冲突 | |
| `` <right> `` | 选择下一个冲突 | |
| `` z `` | 撤销 | Undo last merge conflict resolution. |
| `` e `` | 编辑文件 | Open file in external editor. |
| `` o `` | 打开文件 | Open file in default application. |
| `` M `` | 打开外部合并工具 (git mergetool) | Run `git mergetool`. |
| `` z `` | 撤销 | 撤消上次合并冲突解决 |
| `` e `` | 编辑文件 | 使用外部编辑器打开文件 |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` M `` | 打开外部合并工具(git mergetool) | 执行 `git mergetool`. |
| `` <esc> `` | 返回文件面板 | |
## 正在暂存
@@ -279,19 +279,19 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <left> `` | 选择上一个区块 | |
| `` <right> `` | 选择下一个区块 | |
| `` v `` | 切换拖动选择 | |
| `` a `` | 切换选择块 | Toggle hunk selection mode. |
| `` a `` | 切换选择代码块 | 切换代码块选择模式 |
| `` <c-o> `` | 将选中文本复制到剪贴板 | |
| `` <space> `` | 切换暂存状态 | 切换行暂存状态 |
| `` d `` | 取消变更 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. |
| `` o `` | 打开文件 | Open file in default application. |
| `` e `` | 编辑文件 | Open file in external editor. |
| `` d `` | 取消变更(git reset) | 当选择未暂存的变更时使用git reset丢弃该变更。当选择已暂存的变更时取消暂存该变更 |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` e `` | 编辑文件 | 使用外部编辑器打开文件 |
| `` <esc> `` | 返回文件面板 | |
| `` <tab> `` | 切换到其他面板 | Switch to other view (staged/unstaged changes). |
| `` E `` | Edit hunk | Edit selected hunk in external editor. |
| `` c `` | 提交更 | Commit staged changes. |
| `` w `` | 提交更而无需预先提交钩子 | |
| `` C `` | 提交更改(使用编辑器编辑提交信息 | |
| `` <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> |
| `` <tab> `` | 切换到其他面板 | 切换到其他视图(已暂存/未暂存的变更) |
| `` E `` | 编辑代码块 | 在外部编辑器中编辑选中的代码块 |
| `` c `` | 提交更 | 提交暂存文件 |
| `` w `` | 提交更而无需预先提交钩子 | |
| `` C `` | 提交变更(使用编辑器编辑提交信息) | |
| `` <c-f> `` | 找到用于修复的基准提交 | 找到您当前变更所基于的提交,以便于修正/改进该提交。这样做可以省去您逐一查看分支提交来确定应该修正/改进哪个提交的麻烦。请参阅文档: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` / `` | 开始搜索 | |
## 正常
@@ -305,8 +305,8 @@ If you would instead like to start an interactive rebase from the selected commi
| Key | Action | Info |
|-----|--------|-------------|
| `` o `` | 打开配置文件 | Open file in default application. |
| `` e `` | 编辑配置文件 | Open file in external editor. |
| `` o `` | 打开配置文件 | 使用默认程序打开该文件 |
| `` e `` | 编辑配置文件 | 使用外部编辑器打开文件 |
| `` u `` | 检查更新 | |
| `` <enter> `` | 切换到最近的仓库 | |
| `` a `` | 显示所有分支的日志 | |
@@ -324,46 +324,46 @@ If you would instead like to start an interactive rebase from the selected commi
|-----|--------|-------------|
| `` <enter> `` | 执行 | |
| `` <esc> `` | 关闭 | |
| `` / `` | Filter the current view by text | |
| `` / `` | 通过文本过滤当前视图 | |
## 贮藏
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | 应用 | Apply the stash entry to your working directory. |
| `` g `` | 应用并删除 | Apply the stash entry to your working directory and remove the stash entry. |
| `` d `` | 删除 | Remove the stash entry from the stash list. |
| `` n `` | 新分支 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. |
| `` r `` | Rename stash | |
| `` <space> `` | 应用 | 将贮藏项应用到您的工作目录。 |
| `` g `` | 应用并删除 | 将存储项应用到工作目录并删除存储项。 |
| `` d `` | 删除 | 从贮藏列表中删除该贮藏项 |
| `` n `` | 新分支 | 从选定的贮藏项创建一个新分支。这是通过 git 检查创建贮藏项的提交,从该提交创建一个新分支,然后将贮藏项作为附加提交应用到新分支来实现的。 |
| `` r `` | 重命名贮藏 | |
| `` <enter> `` | 查看提交的文件 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | |
## 远程分支
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将分支名称复制到剪贴板 | |
| `` <space> `` | 检出 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` <space> `` | 检出 | 基于当前选中的远程分支检出一个新的本地分支或者将远程分支作分离的HEAD。 |
| `` n `` | 新分支 | |
| `` M `` | 合并到当前检出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | 将已检出的分支变基到该分支 | Rebase the checked-out branch onto the selected branch. |
| `` d `` | Delete | Delete the remote branch from the remote. |
| `` u `` | Set as upstream | 设置为检出分支的上游 |
| `` s `` | Sort order | |
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
| `` r `` | 将已检出的分支变基到该分支 | 将检出的分支变基到所选的分支上。 |
| `` d `` | 删除 | 从远程删除远程分支。 |
| `` u `` | 设置为上游 | 设置为检出分支的上游 |
| `` s `` | 排序 | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` <enter> `` | 查看提交 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | |
## 远程页面
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | View branches | |
| `` <enter> `` | 查看分支 | |
| `` n `` | 添加新的远程仓库 | |
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. |
| `` e `` | Edit | 编辑远程仓库 |
| `` d `` | 删除 | 删除选中的远程。从远程跟踪远程分支的任何本地分支都不会受到影响。 |
| `` e `` | 编辑 | 编辑远程仓库 |
| `` f `` | 抓取 | 抓取远程仓库 |
| `` / `` | Filter the current view by text | |
| `` / `` | 通过文本过滤当前视图 | |

View File

@@ -12,13 +12,13 @@ _說明`<c-b>` 表示 CtrlB、`<a-b>` 表示 AltB`B`表示 ShiftB
| `` <pgup> (fn+up/shift+k) `` | 向上捲動主面板 | |
| `` <pgdown> (fn+down/shift+j) `` | 向下捲動主面板 | |
| `` @ `` | 開啟命令記錄選單 | View options for the command log e.g. show/hide the command log and focus the command log. |
| `` P `` | 推送 | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` p `` | 拉取 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` P `` | 推送 | 推送到遠端。如果沒有設定遠端,會開啟設定視窗。 |
| `` p `` | 拉取 | 從遠端同步當前分支。如果沒有設定遠端,會開啟設定視窗。 |
| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | 增加差異檢視中顯示變更周圍上下文的大小 | Increase the amount of the context shown around changes in the diff view. |
| `` { `` | 減小差異檢視中顯示變更周圍上下文的大小 | Decrease the amount of the context shown around changes in the diff view. |
| `` : `` | 執行自訂命令 | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` <c-p> `` | 檢視自訂補丁選項 | |
| `` m `` | 查看合併/變基選項 | View options to abort/continue/skip the current merge/rebase. |
| `` 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`. |
@@ -60,8 +60,8 @@ _說明`<c-b>` 表示 CtrlB、`<a-b>` 表示 AltB`B`表示 ShiftB
| `` v `` | 切換拖曳選擇 | |
| `` a `` | 切換選擇程式碼塊 | Toggle hunk selection mode. |
| `` <c-o> `` | 複製所選文本至剪貼簿 | |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` e `` | 編輯檔案 | Open file in external editor. |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
| `` <space> `` | 向 (或從) 補丁中添加/刪除行 | |
| `` <esc> `` | 退出自訂補丁建立器 | |
| `` / `` | 搜尋 | |
@@ -84,9 +84,9 @@ _說明`<c-b>` 表示 CtrlB、`<a-b>` 表示 AltB`B`表示 ShiftB
| `` <left> `` | 選擇上一個衝突 | |
| `` <right> `` | 選擇下一個衝突 | |
| `` z `` | 復原 | Undo last merge conflict resolution. |
| `` e `` | 編輯檔案 | Open file in external editor. |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` M `` | 開啟外部合併工具 (git mergetool) | Run `git mergetool`. |
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` M `` | 開啟外部合併工具 | 執行 `git mergetool` |
| `` <esc> `` | 返回檔案面板 | |
## 主面板(預存)
@@ -100,12 +100,12 @@ _說明`<c-b>` 表示 CtrlB、`<a-b>` 表示 AltB`B`表示 ShiftB
| `` <c-o> `` | 複製所選文本至剪貼簿 | |
| `` <space> `` | 切換預存 | 切換現有行的狀態 (已預存/未預存) |
| `` d `` | 刪除變更 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` e `` | 編輯檔案 | Open file in external editor. |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
| `` <esc> `` | 返回檔案面板 | |
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). |
| `` E `` | 編輯程式碼塊 | Edit selected hunk in external editor. |
| `` c `` | 提交變更 | Commit staged changes. |
| `` c `` | 提交變更 | 提交暫存區變更 |
| `` w `` | 沒有預提交 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> |
@@ -131,7 +131,7 @@ _說明`<c-b>` 表示 CtrlB、`<a-b>` 表示 AltB`B`表示 ShiftB
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 複製提交 (揀選) | 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> `` | 重設選定的揀選 (複製) 提交 | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <enter> `` | 檢視所選項目的檔案 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -156,7 +156,7 @@ _說明`<c-b>` 表示 CtrlB、`<a-b>` 表示 AltB`B`表示 ShiftB
|-----|--------|-------------|
| `` n `` | New worktree | |
| `` <space> `` | Switch | Switch to the selected worktree. |
| `` o `` | Open in editor | |
| `` o `` | 在編輯器中開啟 | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
| `` / `` | 搜尋 | |
@@ -169,22 +169,22 @@ _說明`<c-b>` 表示 CtrlB、`<a-b>` 表示 AltB`B`表示 ShiftB
| `` 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. |
| `` r `` | 改寫提交 | Reword the selected commit's message. |
| `` r `` | 改寫提交 | 改寫選中的提交訊息 |
| `` R `` | 使用編輯器改寫提交 | |
| `` d `` | 刪除提交 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
| `` e `` | Edit (start interactive rebase) | 編輯提交 |
| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.
| `` e `` | 編輯(開始互動變基) | 編輯提交 |
| `` i `` | 開始互動變基 | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.
If you would instead like to start an interactive rebase from the selected commit, press `e`. |
| `` p `` | Pick | 挑選提交 (於變基過程中) |
| `` p `` | 挑選 | 挑選提交 (於變基過程中) |
| `` F `` | 建立修復提交 | 為此提交建立修復提交 |
| `` S `` | 壓縮上方所有「fixup」提交自動壓縮 | 是否壓縮上方 {{.commit}} 所有「fixup」提交 |
| `` <c-j> `` | 向下移動提交 | |
| `` <c-k> `` | 向上移動提交 | |
| `` V `` | 貼上提交 (揀選) | |
| `` B `` | 為了變基已標注提交為基準提交 | 請為了下一次變基選擇一項基準提交;此將執行 `git rebase --onto`。 |
| `` A `` | Amend | 使用已預存的更改修正提交 |
| `` A `` | 修改 | 使用已預存的更改修正提交 |
| `` a `` | 設定/重設提交作者 | Set/Reset commit author or set co-author. |
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. |
| `` t `` | 還原 | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. |
| `` T `` | 打標籤到提交 | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. |
| `` <c-l> `` | 開啟記錄選單 | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. |
| `` <space> `` | 檢出 | Checkout the selected commit as a detached HEAD. |
@@ -193,7 +193,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` n `` | 從提交建立新分支 | |
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 複製提交 (揀選) | 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-t> `` | Open external diff tool (git difftool) | |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <enter> `` | 檢視所選項目的檔案 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -212,9 +212,9 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <c-o> `` | 複製檔案名稱到剪貼簿 | |
| `` c `` | 檢出 | 檢出檔案 |
| `` d `` | Remove | Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file. |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` e `` | Edit | Open file in external editor. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯 | 使用外部編輯器開啟 |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <space> `` | 切換檔案是否包含在補丁中 | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` a `` | 切換所有檔案是否包含在補丁中 | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` <enter> `` | 輸入檔案以將選定的行添加至補丁(或切換目錄折疊) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
@@ -246,7 +246,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 複製提交 (揀選) | 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> `` | 重設選定的揀選 (複製) 提交 | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -257,23 +257,23 @@ If you would instead like to start an interactive rebase from the selected commi
|-----|--------|-------------|
| `` <c-o> `` | 複製分支名稱到剪貼簿 | |
| `` i `` | 顯示 git-flow 選項 | |
| `` <space> `` | 檢出 | Checkout selected item. |
| `` <space> `` | 檢出 | 檢出選定的項目。 |
| `` n `` | 新分支 | |
| `` o `` | 建立拉取請求 | |
| `` O `` | 建立拉取請求選項 | |
| `` <c-y> `` | 複製拉取請求的 URL 到剪貼板 | |
| `` c `` | 根據名稱檢出 | Checkout by name. In the input box you can enter '-' to switch to the last branch. |
| `` F `` | 強制檢出 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
| `` d `` | Delete | View delete options for local/remote branch. |
| `` d `` | 刪除 | View delete options for local/remote branch. |
| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. |
| `` M `` | 合併到當前檢出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` f `` | 從上游快進此分支 | Fast-forward selected branch from its upstream. |
| `` f `` | 從上游快進此分支 | 從遠端快進所選的分支 |
| `` T `` | 建立標籤 | |
| `` s `` | Sort order | |
| `` s `` | 排序規則 | |
| `` g `` | 檢視重設選項 | |
| `` R `` | 重新命名分支 | |
| `` u `` | 檢視上游設定 | 檢視有關上游分支的設定(例如重設至上游 |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` u `` | 檢視遠端設定 | 檢視有關遠端分支的設定(例如重設至遠端 |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -284,10 +284,10 @@ If you would instead like to start an interactive rebase from the selected commi
|-----|--------|-------------|
| `` <space> `` | 檢出 | Checkout the selected tag tag as a detached HEAD. |
| `` n `` | 建立標籤 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
| `` d `` | Delete | View delete options for local/remote tag. |
| `` d `` | 刪除 | View delete options for local/remote tag. |
| `` P `` | 推送標籤 | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` g `` | 重設 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -299,35 +299,35 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <c-o> `` | 複製檔案名稱到剪貼簿 | |
| `` <space> `` | 切換預存 | Toggle staged for selected file. |
| `` <c-b> `` | 篩選檔案 (預存/未預存) | |
| `` y `` | Copy to clipboard | |
| `` c `` | 提交變更 | Commit staged changes. |
| `` y `` | 複製到剪貼簿 | |
| `` c `` | 提交變更 | 提交暫存區變更 |
| `` w `` | 沒有預提交 hook 就提交更改 | |
| `` A `` | 修改上次提交 | |
| `` 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> |
| `` e `` | Edit | Open file in external editor. |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` e `` | 編輯 | 使用外部編輯器開啟 |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` i `` | 忽略或排除檔案 | |
| `` r `` | 重新整理檔案 | |
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
| `` s `` | 收藏 | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
| `` S `` | 檢視收藏選項 | View stash options (e.g. stash all, stash staged, stash unstaged). |
| `` a `` | 全部預存/取消預存 | Toggle staged/unstaged for all files in working tree. |
| `` <enter> `` | 選擇檔案中的單個程式碼塊/行,或展開/折疊目錄 | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. |
| `` d `` | Discard | View options for discarding changes to the selected file. |
| `` g `` | 檢視上游重設選項 | |
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). |
| `` d `` | 捨棄 | 檢視選中變動進行捨棄復原 |
| `` g `` | 檢視遠端重設選項 | |
| `` 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. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | 開啟外部合併工具 (git mergetool) | Run `git mergetool`. |
| `` f `` | 擷取 | Fetch changes from remote. |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` M `` | 開啟外部合併工具 | 執行 `git mergetool` |
| `` f `` | 擷取 | 同步遠端異動 |
| `` / `` | 搜尋 | |
## 狀態
| Key | Action | Info |
|-----|--------|-------------|
| `` o `` | 開啟設定檔案 | Open file in default application. |
| `` e `` | 編輯設定檔案 | Open file in external editor. |
| `` o `` | 開啟設定檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯設定檔案 | 使用外部編輯器開啟 |
| `` u `` | 檢查更新 | |
| `` <enter> `` | 切換到最近使用的版本庫 | |
| `` a `` | 顯示所有分支日誌 | |
@@ -346,7 +346,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <enter> `` | View branches | |
| `` n `` | 新增遠端 | |
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. |
| `` e `` | Edit | 編輯遠端 |
| `` e `` | 編輯 | 編輯遠端 |
| `` f `` | 擷取 | 擷取遠端 |
| `` / `` | 搜尋 | |
@@ -359,11 +359,11 @@ If you would instead like to start an interactive rebase from the selected commi
| `` n `` | 新分支 | |
| `` M `` | 合併到當前檢出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. |
| `` d `` | Delete | Delete the remote branch from the remote. |
| `` u `` | Set as upstream | 將此分支設為當前分支之上游 |
| `` s `` | Sort order | |
| `` d `` | 刪除 | Delete the remote branch from the remote. |
| `` u `` | 設置為遠端 | 將此分支設為當前分支之遠端 |
| `` s `` | 排序規則 | |
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |

12
go.mod
View File

@@ -16,7 +16,7 @@ require (
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
github.com/jesseduffield/gocui v0.3.1-0.20240628061234-aed9e133e65b
github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
@@ -24,7 +24,7 @@ require (
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.15
github.com/mattn/go-runewidth v0.0.16
github.com/mgutz/str v1.2.0
github.com/mitchellh/go-ps v1.0.0
github.com/sahilm/fuzzy v0.1.0
@@ -38,7 +38,7 @@ require (
github.com/stretchr/testify v1.8.1
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8
golang.org/x/sync v0.7.0
golang.org/x/sync v0.8.0
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -75,8 +75,8 @@ require (
github.com/xanzy/ssh-agent v0.2.1 // indirect
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/sys v0.25.0 // indirect
golang.org/x/term v0.24.0 // indirect
golang.org/x/text v0.18.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

23
go.sum
View File

@@ -188,8 +188,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
github.com/jesseduffield/gocui v0.3.1-0.20240628061234-aed9e133e65b h1:oxCq0DvR2GMGf4UaoaASb0nQK/fJMQW3c3PNCLWCjS8=
github.com/jesseduffield/gocui v0.3.1-0.20240628061234-aed9e133e65b/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f h1:ZzsAUDwPFLPITKLcJpMSqt/3rERdI8YRZKr2l0plrls=
github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
@@ -235,8 +235,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.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw=
github.com/mgutz/str v1.2.0/go.mod h1:w1v0ofgLaJdoD0HpQ3fycxKD1WtxpjSo151pK/31q6w=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@@ -423,8 +424,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -474,14 +475,14 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
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=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
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=
@@ -492,8 +493,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
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=

View File

@@ -63,22 +63,20 @@ func Run(
func NewCommon(config config.AppConfigurer) (*common.Common, error) {
userConfig := config.GetUserConfig()
appState := config.GetAppState()
var err error
log := newLogger(config)
tr, err := i18n.NewTranslationSetFromConfig(log, userConfig.Gui.Language)
if err != nil {
return nil, err
}
// Initialize with English for the time being; the real translation set for
// the configured language will be read after reading the user config
tr := i18n.EnglishTranslationSet()
return &common.Common{
Log: log,
Tr: tr,
UserConfig: userConfig,
AppState: appState,
Debug: config.GetDebug(),
Fs: afero.NewOsFs(),
}, nil
cmn := &common.Common{
Log: log,
Tr: tr,
AppState: appState,
Debug: config.GetDebug(),
Fs: afero.NewOsFs(),
}
cmn.SetUserConfig(userConfig)
return cmn, nil
}
func newLogger(cfg config.AppConfigurer) *logrus.Entry {
@@ -118,11 +116,11 @@ func NewApp(config config.AppConfigurer, test integrationTypes.IntegrationTest,
return app, err
}
// If we're not in a repo, repoPaths will be nil. The error is moot for us
// If we're not in a repo, GetRepoPaths will return an error. The error is moot for us
// at this stage, since we'll try to init a new repo in setupRepo(), below
repoPaths, err := git_commands.GetRepoPaths(app.OSCommand.Cmd, gitVersion)
if err != nil {
return app, err
common.Log.Infof("Error getting repo paths: %v", err)
}
showRecentRepos, err := app.setupRepo(repoPaths)
@@ -195,7 +193,7 @@ func (app *App) setupRepo(
var shouldInitRepo bool
initialBranchArg := ""
switch app.UserConfig.NotARepository {
switch app.UserConfig().NotARepository {
case "prompt":
// Offer to initialize a new repository in current directory.
fmt.Print(app.Tr.CreateRepo)

View File

@@ -246,16 +246,18 @@ func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
// Takes the hash of some commit, and the hash of a fixup commit that was created
// at the end of the branch, then moves the fixup commit down to right after the
// original commit, changing its type to "fixup"
// original commit, changing its type to "fixup" (only if ChangeToFixup is true)
type MoveFixupCommitDownInstruction struct {
OriginalHash string
FixupHash string
OriginalHash string
FixupHash string
ChangeToFixup bool
}
func NewMoveFixupCommitDownInstruction(originalHash string, fixupHash string) Instruction {
func NewMoveFixupCommitDownInstruction(originalHash string, fixupHash string, changeToFixup bool) Instruction {
return &MoveFixupCommitDownInstruction{
OriginalHash: originalHash,
FixupHash: fixupHash,
OriginalHash: originalHash,
FixupHash: fixupHash,
ChangeToFixup: changeToFixup,
}
}
@@ -269,7 +271,7 @@ func (self *MoveFixupCommitDownInstruction) SerializedInstructions() string {
func (self *MoveFixupCommitDownInstruction) run(common *common.Common) error {
return handleInteractiveRebase(common, func(path string) error {
return utils.MoveFixupCommitDown(path, self.OriginalHash, self.FixupHash, getCommentChar())
return utils.MoveFixupCommitDown(path, self.OriginalHash, self.FixupHash, self.ChangeToFixup, getCommentChar())
})
}

View File

@@ -136,6 +136,12 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
if integrationTest != nil {
integrationTest.SetupConfig(appConfig)
// Preserve the changes that the test setup just made to the config, so
// they don't get lost when we reload the config while running the test
// (which happens when switching between repos, going in and out of
// submodules, etc).
appConfig.SaveGlobalUserConfig()
}
common, err := NewCommon(appConfig)

View File

@@ -63,6 +63,11 @@ func generateAtDir(cheatsheetDir string) {
if err != nil {
log.Fatal(err)
}
tr, err := i18n.NewTranslationSetFromConfig(common.Log, lang)
if err != nil {
log.Fatal(err)
}
common.Tr = tr
mApp, _ := app.NewApp(mConfig, nil, common)
path := cheatsheetDir + "/Keybindings_" + lang + ".md"
file, err := os.Create(path)

View File

@@ -38,6 +38,10 @@ func (self *gitCmdObjBuilder) NewShell(cmdStr string) oscommands.ICmdObj {
return self.innerBuilder.NewShell(cmdStr).AddEnvVars(defaultEnvVar)
}
func (self *gitCmdObjBuilder) NewInteractiveShell(cmdStr string) oscommands.ICmdObj {
return self.innerBuilder.NewInteractiveShell(cmdStr).AddEnvVars(defaultEnvVar)
}
func (self *gitCmdObjBuilder) Quote(str string) string {
return self.innerBuilder.Quote(str)
}

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mgutz/str"
@@ -145,7 +146,7 @@ func (self *BranchCommands) GetGraph(branchName string) (string, error) {
}
func (self *BranchCommands) GetGraphCmdObj(branchName string) oscommands.ICmdObj {
branchLogCmdTemplate := self.UserConfig.Git.BranchLogCmd
branchLogCmdTemplate := self.UserConfig().Git.BranchLogCmd
templateValues := map[string]string{
"branchName": self.cmd.Quote(branchName),
}
@@ -236,7 +237,7 @@ func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
}
cmdArgs := NewGitCmd("merge").
Arg("--no-edit").
Arg(strings.Fields(self.UserConfig.Git.Merging.Args)...).
Arg(strings.Fields(self.UserConfig().Git.Merging.Args)...).
ArgIf(opts.FastForwardOnly, "--ff-only").
ArgIf(opts.Squash, "--squash", "--ff").
Arg(branchName).
@@ -248,9 +249,9 @@ func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
func (self *BranchCommands) AllBranchesLogCmdObj() oscommands.ICmdObj {
// Only choose between non-empty, non-identical commands
candidates := lo.Uniq(lo.WithoutEmpty(append([]string{
self.UserConfig.Git.AllBranchesLogCmd,
self.UserConfig().Git.AllBranchesLogCmd,
},
self.UserConfig.Git.AllBranchesLogCmds...,
self.UserConfig().Git.AllBranchesLogCmds...,
)))
n := len(candidates)
@@ -260,3 +261,26 @@ func (self *BranchCommands) AllBranchesLogCmdObj() oscommands.ICmdObj {
return self.cmd.New(str.ToArgv(candidates[i])).DontLog()
}
func (self *BranchCommands) IsBranchMerged(branch *models.Branch, mainBranches *MainBranches) (bool, error) {
branchesToCheckAgainst := []string{"HEAD"}
if branch.RemoteBranchStoredLocally() {
branchesToCheckAgainst = append(branchesToCheckAgainst, fmt.Sprintf("%s@{upstream}", branch.Name))
}
branchesToCheckAgainst = append(branchesToCheckAgainst, mainBranches.Get()...)
cmdArgs := NewGitCmd("rev-list").
Arg("--max-count=1").
Arg(branch.Name).
Arg(lo.Map(branchesToCheckAgainst, func(branch string, _ int) string {
return fmt.Sprintf("^%s", branch)
})...).
ToArgv()
stdout, _, err := self.cmd.New(cmdArgs).RunWithOutputs()
if err != nil {
return false, err
}
return stdout == "", nil
}

View File

@@ -140,7 +140,7 @@ func (self *BranchLoader) Load(reflogCommits []*models.Commit,
}
}
if loadBehindCounts && self.UserConfig.Gui.ShowDivergenceFromBaseBranch != "none" {
if loadBehindCounts && self.UserConfig().Gui.ShowDivergenceFromBaseBranch != "none" {
onWorker(func() error {
return self.GetBehindBaseBranchValuesForAllBranches(branches, mainBranches, renderFunc)
})

View File

@@ -88,7 +88,7 @@ func (self *CommitCommands) ResetToCommit(hash string, strength string, envVars
func (self *CommitCommands) CommitCmdObj(summary string, description string) oscommands.ICmdObj {
messageArgs := self.commitMessageArgs(summary, description)
skipHookPrefix := self.UserConfig.Git.SkipHookPrefix
skipHookPrefix := self.UserConfig().Git.SkipHookPrefix
cmdArgs := NewGitCmd("commit").
ArgIf(skipHookPrefix != "" && strings.HasPrefix(summary, skipHookPrefix), "--no-verify").
@@ -117,7 +117,7 @@ func (self *CommitCommands) CommitInEditorWithMessageFileCmdObj(tmpMessageFile s
}
// RewordLastCommit rewords the topmost commit with the given message
func (self *CommitCommands) RewordLastCommit(summary string, description string) error {
func (self *CommitCommands) RewordLastCommit(summary string, description string) oscommands.ICmdObj {
messageArgs := self.commitMessageArgs(summary, description)
cmdArgs := NewGitCmd("commit").
@@ -125,7 +125,7 @@ func (self *CommitCommands) RewordLastCommit(summary string, description string)
Arg(messageArgs...).
ToArgv()
return self.cmd.New(cmdArgs).Run()
return self.cmd.New(cmdArgs)
}
func (self *CommitCommands) commitMessageArgs(summary string, description string) []string {
@@ -148,7 +148,7 @@ func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj {
}
func (self *CommitCommands) signoffFlag() string {
if self.UserConfig.Git.Commit.SignOff {
if self.UserConfig().Git.Commit.SignOff {
return "--signoff"
} else {
return ""
@@ -189,7 +189,7 @@ type Author struct {
func (self *CommitCommands) GetCommitAuthor(commitHash string) (Author, error) {
cmdArgs := NewGitCmd("show").
Arg("--no-patch", "--pretty=format:'%an%x00%ae'", commitHash).
Arg("--no-patch", "--pretty=format:%an%x00%ae", commitHash).
ToArgv()
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
@@ -258,13 +258,13 @@ func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
func (self *CommitCommands) ShowCmdObj(hash string, filterPath string) oscommands.ICmdObj {
contextSize := self.AppState.DiffContextSize
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
cmdArgs := NewGitCmd("show").
Config("diff.noprefix=false").
ConfigIf(extDiffCmd != "", "diff.external="+extDiffCmd).
ArgIfElse(extDiffCmd != "", "--ext-diff", "--no-ext-diff").
Arg("--submodule").
Arg("--color="+self.UserConfig.Git.Paging.ColorArg).
Arg("--color="+self.UserConfig().Git.Paging.ColorArg).
Arg(fmt.Sprintf("--unified=%d", contextSize)).
Arg("--stat").
Arg("--decorate").

View File

@@ -322,9 +322,9 @@ func TestGetCommits(t *testing.T) {
},
}
common.UserConfig.Git.MainBranches = scenario.mainBranches
common.UserConfig().Git.MainBranches = scenario.mainBranches
opts := scenario.opts
opts.MainBranches = NewMainBranches(scenario.mainBranches, cmd)
opts.MainBranches = NewMainBranches(common, cmd)
commits, err := builder.GetCommits(opts)
assert.Equal(t, scenario.expectedCommits, commits)

View File

@@ -33,7 +33,7 @@ func TestCommitRewordCommit(t *testing.T) {
t.Run(s.testName, func(t *testing.T) {
instance := buildCommitCommands(commonDeps{runner: s.runner})
assert.NoError(t, instance.RewordLastCommit(s.summary, s.description))
assert.NoError(t, instance.RewordLastCommit(s.summary, s.description).Run())
s.runner.CheckForMissingCalls()
})
}

View File

@@ -43,7 +43,7 @@ func (self *ConfigCommands) ConfiguredPager() string {
}
func (self *ConfigCommands) GetPager(width int) string {
useConfig := self.UserConfig.Git.Paging.UseConfig
useConfig := self.UserConfig().Git.Paging.UseConfig
if useConfig {
pager := self.ConfiguredPager()
return strings.Split(pager, "| less")[0]
@@ -53,14 +53,14 @@ func (self *ConfigCommands) GetPager(width int) string {
"columnWidth": strconv.Itoa(width/2 - 6),
}
pagerTemplate := string(self.UserConfig.Git.Paging.Pager)
pagerTemplate := string(self.UserConfig().Git.Paging.Pager)
return utils.ResolvePlaceholderString(pagerTemplate, templateValues)
}
// UsingGpg tells us whether the user has gpg enabled so that we can know
// whether we need to run a subprocess to allow them to enter their password
func (self *ConfigCommands) UsingGpg() bool {
overrideGpg := self.UserConfig.Git.OverrideGpg
overrideGpg := self.UserConfig().Git.OverrideGpg
if overrideGpg {
return false
}

View File

@@ -58,9 +58,9 @@ func buildGitCommon(deps commonDeps) *GitCommon {
}
gitCommon.cmd = cmd
gitCommon.Common.UserConfig = deps.userConfig
if gitCommon.Common.UserConfig == nil {
gitCommon.Common.UserConfig = config.GetDefaultConfig()
gitCommon.Common.SetUserConfig(deps.userConfig)
if gitCommon.Common.UserConfig() == nil {
gitCommon.Common.SetUserConfig(config.GetDefaultConfig())
}
gitCommon.version = deps.gitVersion

View File

@@ -17,7 +17,7 @@ func NewDiffCommands(gitCommon *GitCommon) *DiffCommands {
}
func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
useExtDiff := extDiffCmd != ""
return self.cmd.New(
@@ -26,7 +26,7 @@ func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
Arg("--submodule").
Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)).
Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
Arg(diffArgs...).
Dir(self.repoPaths.worktreePath).
ToArgv(),

View File

@@ -31,7 +31,7 @@ func (self *FileCommands) Cat(fileName string) (string, error) {
}
func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (string, error) {
editor := self.UserConfig.OS.EditCommand
editor := self.UserConfig().OS.EditCommand
if editor == "" {
editor = self.config.GetCoreEditor()
@@ -60,7 +60,7 @@ func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (
"line": strconv.Itoa(lineNumber),
}
editCmdTemplate := self.UserConfig.OS.EditCommandTemplate
editCmdTemplate := self.UserConfig().OS.EditCommandTemplate
if len(editCmdTemplate) == 0 {
switch editor {
case "emacs", "nano", "vi", "vim", "nvim":
@@ -78,7 +78,7 @@ func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (
func (self *FileCommands) GetEditCmdStr(filenames []string) (string, bool) {
// Legacy support for old config; to be removed at some point
if self.UserConfig.OS.Edit == "" && self.UserConfig.OS.EditCommandTemplate != "" {
if self.UserConfig().OS.Edit == "" && self.UserConfig().OS.EditCommandTemplate != "" {
// If multiple files are selected, we'll simply edit just the first one.
// It's not worth fixing this for the legacy support.
if cmdStr, err := self.GetEditCmdStrLegacy(filenames[0], 1); err == nil {
@@ -86,7 +86,7 @@ func (self *FileCommands) GetEditCmdStr(filenames []string) (string, bool) {
}
}
template, suspend := config.GetEditTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
template, suspend := config.GetEditTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
quotedFilenames := lo.Map(filenames, func(filename string, _ int) string { return self.cmd.Quote(filename) })
templateValues := map[string]string{
@@ -99,13 +99,13 @@ func (self *FileCommands) GetEditCmdStr(filenames []string) (string, bool) {
func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (string, bool) {
// Legacy support for old config; to be removed at some point
if self.UserConfig.OS.EditAtLine == "" && self.UserConfig.OS.EditCommandTemplate != "" {
if self.UserConfig().OS.EditAtLine == "" && self.UserConfig().OS.EditCommandTemplate != "" {
if cmdStr, err := self.GetEditCmdStrLegacy(filename, lineNumber); err == nil {
return cmdStr, true
}
}
template, suspend := config.GetEditAtLineTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
template, suspend := config.GetEditAtLineTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
templateValues := map[string]string{
"filename": self.cmd.Quote(filename),
@@ -118,13 +118,13 @@ func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (
func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber int) string {
// Legacy support for old config; to be removed at some point
if self.UserConfig.OS.EditAtLineAndWait == "" && self.UserConfig.OS.EditCommandTemplate != "" {
if self.UserConfig().OS.EditAtLineAndWait == "" && self.UserConfig().OS.EditCommandTemplate != "" {
if cmdStr, err := self.GetEditCmdStrLegacy(filename, lineNumber); err == nil {
return cmdStr
}
}
template := config.GetEditAtLineAndWaitTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
template := config.GetEditAtLineAndWaitTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
templateValues := map[string]string{
"filename": self.cmd.Quote(filename),
@@ -136,7 +136,7 @@ func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber
}
func (self *FileCommands) GetOpenDirInEditorCmdStr(path string) (string, bool) {
template, suspend := config.GetOpenDirInEditorTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
template, suspend := config.GetOpenDirInEditorTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
templateValues := map[string]string{
"dir": self.cmd.Quote(path),

View File

@@ -5,32 +5,34 @@ import (
"sync"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
"github.com/sasha-s/go-deadlock"
)
type MainBranches struct {
// List of main branches configured by the user. Just the bare names.
configuredMainBranches []string
// Which of these actually exist in the repository. Full ref names, and it
// could be either "refs/heads/..." or "refs/remotes/origin/..." depending
// on which one exists for a given bare name.
c *common.Common
// Which of the configured main branches actually exist in the repository. Full
// ref names, and it could be either "refs/heads/..." or "refs/remotes/origin/..."
// depending on which one exists for a given bare name.
existingMainBranches []string
previousMainBranches []string
cmd oscommands.ICmdObjBuilder
mutex *deadlock.Mutex
}
func NewMainBranches(
configuredMainBranches []string,
cmn *common.Common,
cmd oscommands.ICmdObjBuilder,
) *MainBranches {
return &MainBranches{
configuredMainBranches: configuredMainBranches,
existingMainBranches: nil,
cmd: cmd,
mutex: &deadlock.Mutex{},
c: cmn,
existingMainBranches: nil,
cmd: cmd,
mutex: &deadlock.Mutex{},
}
}
@@ -40,8 +42,11 @@ func (self *MainBranches) Get() []string {
self.mutex.Lock()
defer self.mutex.Unlock()
if self.existingMainBranches == nil {
self.existingMainBranches = self.determineMainBranches()
configuredMainBranches := self.c.UserConfig().Git.MainBranches
if self.existingMainBranches == nil || !utils.EqualSlices(self.previousMainBranches, configuredMainBranches) {
self.existingMainBranches = self.determineMainBranches(configuredMainBranches)
self.previousMainBranches = configuredMainBranches
}
return self.existingMainBranches
@@ -71,13 +76,13 @@ func (self *MainBranches) GetMergeBase(refName string) string {
return ignoringWarnings(output)
}
func (self *MainBranches) determineMainBranches() []string {
func (self *MainBranches) determineMainBranches(configuredMainBranches []string) []string {
var existingBranches []string
var wg sync.WaitGroup
existingBranches = make([]string, len(self.configuredMainBranches))
existingBranches = make([]string, len(configuredMainBranches))
for i, branchName := range self.configuredMainBranches {
for i, branchName := range configuredMainBranches {
wg.Add(1)
go utils.Safe(func() {
defer wg.Done()

View File

@@ -35,9 +35,8 @@ func NewRebaseCommands(
}
func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, summary string, description string) error {
if models.IsHeadCommit(commits, index) {
// we've selected the top commit so no rebase is required
return self.commit.RewordLastCommit(summary, description)
if self.config.UsingGpg() {
return errors.New(self.Tr.DisabledForGPG)
}
err := self.BeginInteractiveRebaseForCommit(commits, index, false)
@@ -46,7 +45,7 @@ func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, su
}
// now the selected commit should be our head so we'll amend it with the new message
err = self.commit.RewordLastCommit(summary, description)
err = self.commit.RewordLastCommit(summary, description).Run()
if err != nil {
return err
}
@@ -285,6 +284,11 @@ func (self *RebaseCommands) GitRebaseEditTodo(todosFileContent []byte) error {
return cmdObj.Run()
}
func (self *RebaseCommands) getHashOfLastCommitMade() (string, error) {
cmdArgs := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToArgv()
return self.cmd.New(cmdArgs).RunWithOutput()
}
// AmendTo amends the given commit with whatever files are staged
func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error {
commit := commits[commitIndex]
@@ -293,9 +297,7 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
return err
}
// Get the hash of the commit we just created
cmdArgs := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToArgv()
fixupHash, err := self.cmd.New(cmdArgs).RunWithOutput()
fixupHash, err := self.getHashOfLastCommitMade()
if err != nil {
return err
}
@@ -303,7 +305,20 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex+1),
overrideEditor: true,
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Hash, fixupHash),
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Hash, fixupHash, true),
}).Run()
}
func (self *RebaseCommands) MoveFixupCommitDown(commits []*models.Commit, targetCommitIndex int) error {
fixupHash, err := self.getHashOfLastCommitMade()
if err != nil {
return err
}
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: getBaseHashOrRoot(commits, targetCommitIndex+1),
overrideEditor: true,
instruction: daemon.NewMoveFixupCommitDownInstruction(commits[targetCommitIndex].Hash, fixupHash, false),
}).Run()
}

View File

@@ -3,7 +3,6 @@ package git_commands
import (
ioFs "io/fs"
"os"
"path"
"path/filepath"
"strings"
@@ -64,9 +63,9 @@ func (self *RepoPaths) IsBareRepo() bool {
func MockRepoPaths(currentPath string) *RepoPaths {
return &RepoPaths{
worktreePath: currentPath,
worktreeGitDirPath: path.Join(currentPath, ".git"),
worktreeGitDirPath: filepath.Join(currentPath, ".git"),
repoPath: currentPath,
repoGitDirPath: path.Join(currentPath, ".git"),
repoGitDirPath: filepath.Join(currentPath, ".git"),
repoName: "lazygit",
isBareRepo: false,
}
@@ -116,9 +115,9 @@ func GetRepoPathsForDir(
if isSubmodule {
repoPath = worktreePath
} else {
repoPath = path.Dir(repoGitDirPath)
repoPath = filepath.Dir(repoGitDirPath)
}
repoName := path.Base(repoPath)
repoName := filepath.Base(repoPath)
return &RepoPaths{
worktreePath: worktreePath,
@@ -154,7 +153,7 @@ func linkedWortkreePaths(fs afero.Fs, repoGitDirPath string) []string {
result := []string{}
// For each directory in this path we're going to cat the `gitdir` file and append its contents to our result
// That file points us to the `.git` file in the worktree.
worktreeGitDirsPath := path.Join(repoGitDirPath, "worktrees")
worktreeGitDirsPath := filepath.Join(repoGitDirPath, "worktrees")
// ensure the directory exists
_, err := fs.Stat(worktreeGitDirsPath)
@@ -171,7 +170,7 @@ func linkedWortkreePaths(fs afero.Fs, repoGitDirPath string) []string {
return nil
}
gitDirPath := path.Join(currPath, "gitdir")
gitDirPath := filepath.Join(currPath, "gitdir")
gitDirBytes, err := afero.ReadFile(fs, gitDirPath)
if err != nil {
// ignoring error
@@ -179,7 +178,7 @@ func linkedWortkreePaths(fs afero.Fs, repoGitDirPath string) []string {
}
trimmedGitDir := strings.TrimSpace(string(gitDirBytes))
// removing the .git part
worktreeDir := path.Dir(trimmedGitDir)
worktreeDir := filepath.Dir(trimmedGitDir)
result = append(result, worktreeDir)
return nil
})

View File

@@ -2,11 +2,13 @@ package git_commands
import (
"fmt"
"runtime"
"strings"
"testing"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/samber/lo"
"github.com/stretchr/testify/assert"
)
@@ -29,7 +31,17 @@ func TestGetRepoPaths(t *testing.T) {
Name: "typical case",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
// setup for main worktree
expectedOutput := []string{
mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{
// --show-toplevel
`C:\path\to\repo`,
// --git-dir
`C:\path\to\repo\.git`,
// --git-common-dir
`C:\path\to\repo\.git`,
// --is-bare-repository
"false",
// --show-superproject-working-tree
}, []string{
// --show-toplevel
"/path/to/repo",
// --git-dir
@@ -39,28 +51,45 @@ func TestGetRepoPaths(t *testing.T) {
// --is-bare-repository
"false",
// --show-superproject-working-tree
}
})
runner.ExpectGitArgs(
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
strings.Join(expectedOutput, "\n"),
strings.Join(mockOutput, "\n"),
nil)
},
Path: "/path/to/repo",
Expected: &RepoPaths{
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
worktreePath: `C:\path\to\repo`,
worktreeGitDirPath: `C:\path\to\repo\.git`,
repoPath: `C:\path\to\repo`,
repoGitDirPath: `C:\path\to\repo\.git`,
repoName: `repo`,
isBareRepo: false,
}, &RepoPaths{
worktreePath: "/path/to/repo",
worktreeGitDirPath: "/path/to/repo/.git",
repoPath: "/path/to/repo",
repoGitDirPath: "/path/to/repo/.git",
repoName: "repo",
isBareRepo: false,
},
}),
Err: nil,
},
{
Name: "bare repo",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
// setup for main worktree
expectedOutput := []string{
mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{
// --show-toplevel
`C:\path\to\repo`,
// --git-dir
`C:\path\to\bare_repo\bare.git`,
// --git-common-dir
`C:\path\to\bare_repo\bare.git`,
// --is-bare-repository
`true`,
// --show-superproject-working-tree
}, []string{
// --show-toplevel
"/path/to/repo",
// --git-dir
@@ -70,27 +99,45 @@ func TestGetRepoPaths(t *testing.T) {
// --is-bare-repository
"true",
// --show-superproject-working-tree
}
})
runner.ExpectGitArgs(
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
strings.Join(expectedOutput, "\n"),
strings.Join(mockOutput, "\n"),
nil)
},
Path: "/path/to/repo",
Expected: &RepoPaths{
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
worktreePath: `C:\path\to\repo`,
worktreeGitDirPath: `C:\path\to\bare_repo\bare.git`,
repoPath: `C:\path\to\bare_repo`,
repoGitDirPath: `C:\path\to\bare_repo\bare.git`,
repoName: `bare_repo`,
isBareRepo: true,
}, &RepoPaths{
worktreePath: "/path/to/repo",
worktreeGitDirPath: "/path/to/bare_repo/bare.git",
repoPath: "/path/to/bare_repo",
repoGitDirPath: "/path/to/bare_repo/bare.git",
repoName: "bare_repo",
isBareRepo: true,
},
}),
Err: nil,
},
{
Name: "submodule",
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
expectedOutput := []string{
mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{
// --show-toplevel
`C:\path\to\repo\submodule1`,
// --git-dir
`C:\path\to\repo\.git\modules\submodule1`,
// --git-common-dir
`C:\path\to\repo\.git\modules\submodule1`,
// --is-bare-repository
`false`,
// --show-superproject-working-tree
`C:\path\to\repo`,
}, []string{
// --show-toplevel
"/path/to/repo/submodule1",
// --git-dir
@@ -101,21 +148,28 @@ func TestGetRepoPaths(t *testing.T) {
"false",
// --show-superproject-working-tree
"/path/to/repo",
}
})
runner.ExpectGitArgs(
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
strings.Join(expectedOutput, "\n"),
strings.Join(mockOutput, "\n"),
nil)
},
Path: "/path/to/repo/submodule1",
Expected: &RepoPaths{
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
worktreePath: `C:\path\to\repo\submodule1`,
worktreeGitDirPath: `C:\path\to\repo\.git\modules\submodule1`,
repoPath: `C:\path\to\repo\submodule1`,
repoGitDirPath: `C:\path\to\repo\.git\modules\submodule1`,
repoName: `submodule1`,
isBareRepo: false,
}, &RepoPaths{
worktreePath: "/path/to/repo/submodule1",
worktreeGitDirPath: "/path/to/repo/.git/modules/submodule1",
repoPath: "/path/to/repo/submodule1",
repoGitDirPath: "/path/to/repo/.git/modules/submodule1",
repoName: "submodule1",
isBareRepo: false,
},
}),
Err: nil,
},
{

View File

@@ -84,7 +84,7 @@ func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
cmdArgs := NewGitCmd("stash").Arg("show").
Arg("-p").
Arg("--stat").
Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)).
Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
Arg(fmt.Sprintf("--unified=%d", self.AppState.DiffContextSize)).
ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
Arg(fmt.Sprintf("--find-renames=%d%%", self.AppState.RenameSimilarityThreshold)).

View File

@@ -60,7 +60,7 @@ func (self *SyncCommands) fetchCommandBuilder(fetchAll bool) *GitCommandBuilder
}
func (self *SyncCommands) FetchCmdObj(task gocui.Task) oscommands.ICmdObj {
cmdArgs := self.fetchCommandBuilder(self.UserConfig.Git.FetchAll).ToArgv()
cmdArgs := self.fetchCommandBuilder(self.UserConfig().Git.FetchAll).ToArgv()
cmdObj := self.cmd.New(cmdArgs)
cmdObj.PromptOnCredentialRequest(task)
@@ -72,7 +72,7 @@ func (self *SyncCommands) Fetch(task gocui.Task) error {
}
func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj {
cmdArgs := self.fetchCommandBuilder(self.UserConfig.Git.FetchAll).ToArgv()
cmdArgs := self.fetchCommandBuilder(self.UserConfig().Git.FetchAll).ToArgv()
cmdObj := self.cmd.New(cmdArgs)
cmdObj.DontLog().FailOnCredentialRequest()
@@ -95,7 +95,7 @@ func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
Arg("--no-edit").
ArgIf(opts.FastForwardOnly, "--ff-only").
ArgIf(opts.RemoteName != "", opts.RemoteName).
ArgIf(opts.BranchName != "", opts.BranchName).
ArgIf(opts.BranchName != "", "refs/heads/"+opts.BranchName).
GitDirIf(opts.WorktreeGitDir != "", opts.WorktreeGitDir).
ToArgv()
@@ -112,7 +112,7 @@ func (self *SyncCommands) FastForward(
) error {
cmdArgs := self.fetchCommandBuilder(false).
Arg(remoteName).
Arg(remoteBranchName + ":" + branchName).
Arg("refs/heads/" + remoteBranchName + ":" + branchName).
ToArgv()
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()

View File

@@ -133,7 +133,7 @@ func TestSyncFetch(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
instance := buildSyncCommands(commonDeps{})
instance.UserConfig.Git.FetchAll = s.fetchAllConfig
instance.UserConfig().Git.FetchAll = s.fetchAllConfig
task := gocui.NewFakeTask()
s.test(instance.FetchCmdObj(task))
})
@@ -171,7 +171,7 @@ func TestSyncFetchBackground(t *testing.T) {
for _, s := range scenarios {
t.Run(s.testName, func(t *testing.T) {
instance := buildSyncCommands(commonDeps{})
instance.UserConfig.Git.FetchAll = s.fetchAllConfig
instance.UserConfig().Git.FetchAll = s.fetchAllConfig
s.test(instance.FetchBackgroundCmdObj())
})
}

View File

@@ -3,7 +3,7 @@ package git_commands
import (
"fmt"
"os"
"path"
"path/filepath"
"github.com/go-errors/errors"
"github.com/jesseduffield/lazygit/pkg/commands/models"
@@ -233,7 +233,7 @@ func (self *WorkingTreeCommands) Ignore(filename string) error {
// Exclude adds a file to the .git/info/exclude for the repo
func (self *WorkingTreeCommands) Exclude(filename string) error {
excludeFile := path.Join(self.repoPaths.repoGitDirPath, "info", "exclude")
excludeFile := filepath.Join(self.repoPaths.repoGitDirPath, "info", "exclude")
return self.os.AppendLineToFile(excludeFile, filename)
}
@@ -245,7 +245,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool,
}
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool) oscommands.ICmdObj {
colorArg := self.UserConfig.Git.Paging.ColorArg
colorArg := self.UserConfig().Git.Paging.ColorArg
if plain {
colorArg = "never"
}
@@ -253,7 +253,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
contextSize := self.AppState.DiffContextSize
prevPath := node.GetPreviousPath()
noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile()
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
useExtDiff := extDiffCmd != "" && !plain
cmdArgs := NewGitCmd("diff").
@@ -285,12 +285,12 @@ 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.ICmdObj {
contextSize := self.AppState.DiffContextSize
colorArg := self.UserConfig.Git.Paging.ColorArg
colorArg := self.UserConfig().Git.Paging.ColorArg
if plain {
colorArg = "never"
}
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
useExtDiff := extDiffCmd != "" && !plain
cmdArgs := NewGitCmd("diff").

View File

@@ -42,7 +42,7 @@ func (self *CachedGitConfig) Get(key string) string {
defer self.mutex.Unlock()
if value, ok := self.cache[key]; ok {
self.log.Debugf("using cache for key " + key)
self.log.Debug("using cache for key " + key)
return value
}
@@ -56,7 +56,7 @@ func (self *CachedGitConfig) GetGeneral(args string) string {
defer self.mutex.Unlock()
if value, ok := self.cache[args]; ok {
self.log.Debugf("using cache for args " + args)
self.log.Debug("using cache for args " + args)
return value
}
@@ -69,7 +69,7 @@ func (self *CachedGitConfig) getGeneralAux(args string) string {
cmd := getGitConfigGeneralCmd(args)
value, err := self.runGitConfigCmd(cmd)
if err != nil {
self.log.Debugf("Error getting git config value for args: " + args + ". Error: " + err.Error())
self.log.Debugf("Error getting git config value for args: %s. Error: %v", args, err.Error())
return ""
}
return strings.TrimSpace(value)
@@ -79,7 +79,7 @@ func (self *CachedGitConfig) getAux(key string) string {
cmd := getGitConfigCmd(key)
value, err := self.runGitConfigCmd(cmd)
if err != nil {
self.log.Debugf("Error getting git config value for key: " + key + ". Error: " + err.Error())
self.log.Debugf("Error getting git config value for key: %s. Error: %v", key, err.Error())
return ""
}
return strings.TrimSpace(value)

View File

@@ -53,6 +53,10 @@ func (b *Branch) RefName() string {
return b.Name
}
func (b *Branch) ShortRefName() string {
return b.RefName()
}
func (b *Branch) ParentRefName() string {
return b.RefName() + "^"
}

View File

@@ -70,6 +70,10 @@ func (c *Commit) RefName() string {
return c.Hash
}
func (c *Commit) ShortRefName() string {
return c.Hash[:7]
}
func (c *Commit) ParentRefName() string {
if c.IsFirstCommit() {
return EmptyTreeCommitHash

View File

@@ -18,6 +18,10 @@ func (r *RemoteBranch) RefName() string {
return r.FullName()
}
func (r *RemoteBranch) ShortRefName() string {
return r.RefName()
}
func (r *RemoteBranch) ParentRefName() string {
return r.RefName() + "^"
}

View File

@@ -17,6 +17,10 @@ func (s *StashEntry) RefName() string {
return fmt.Sprintf("stash@{%d}", s.Index)
}
func (s *StashEntry) ShortRefName() string {
return s.RefName()
}
func (s *StashEntry) ParentRefName() string {
return s.RefName() + "^"
}

View File

@@ -16,6 +16,10 @@ func (t *Tag) RefName() string {
return t.Name
}
func (t *Tag) ShortRefName() string {
return t.RefName()
}
func (t *Tag) ParentRefName() string {
return t.RefName() + "^"
}

View File

@@ -14,6 +14,8 @@ type ICmdObjBuilder interface {
New(args []string) ICmdObj
// NewShell takes a string like `git commit` and returns an executable shell command for it e.g. `sh -c 'git commit'`
NewShell(commandStr string) ICmdObj
// Like NewShell, but uses the user's shell rather than "bash", and passes -i to it
NewInteractiveShell(commandStr string) ICmdObj
// Quote wraps a string in quotes with any necessary escaping applied. The reason for bundling this up with the other methods in this interface is that we basically always need to make use of this when creating new command objects.
Quote(str string) string
}
@@ -43,10 +45,23 @@ func (self *CmdObjBuilder) NewWithEnviron(args []string, env []string) ICmdObj {
}
func (self *CmdObjBuilder) NewShell(commandStr string) ICmdObj {
var quotedCommand string
quotedCommand := self.quotedCommandString(commandStr)
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s", self.platform.Shell, self.platform.ShellArg, quotedCommand))
return self.New(cmdArgs)
}
func (self *CmdObjBuilder) NewInteractiveShell(commandStr string) ICmdObj {
quotedCommand := self.quotedCommandString(commandStr)
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s %s", self.platform.InteractiveShell, self.platform.InteractiveShellArg, self.platform.ShellArg, quotedCommand))
return self.New(cmdArgs)
}
func (self *CmdObjBuilder) quotedCommandString(commandStr string) string {
// Windows does not seem to like quotes around the command
if self.platform.OS == "windows" {
quotedCommand = strings.NewReplacer(
return strings.NewReplacer(
"^", "^^",
"&", "^&",
"|", "^|",
@@ -54,13 +69,9 @@ func (self *CmdObjBuilder) NewShell(commandStr string) ICmdObj {
">", "^>",
"%", "^%",
).Replace(commandStr)
} else {
quotedCommand = self.Quote(commandStr)
}
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s", self.platform.Shell, self.platform.ShellArg, quotedCommand))
return self.New(cmdArgs)
return self.Quote(commandStr)
}
func (self *CmdObjBuilder) CloneWithNewRunner(decorate func(ICmdObjRunner) ICmdObjRunner) *CmdObjBuilder {

View File

@@ -319,7 +319,7 @@ func (self *cmdObjRunner) getCredentialPromptFn(cmdObj ICmdObj) (func(Credential
}
// runAndDetectCredentialRequest detect a username / password / passphrase question in a command
// promptUserForCredential is a function that gets executed when this function detect you need to fillin a password or passphrase
// promptUserForCredential is a function that gets executed when this function detect you need to fill in a password or passphrase
// The promptUserForCredential argument will be "username", "password" or "passphrase" and expects the user's password/passphrase or username back
func (self *cmdObjRunner) runAndDetectCredentialRequest(
cmdObj ICmdObj,

View File

@@ -51,11 +51,13 @@ func NewDummyCmdObjBuilder(runner ICmdObjRunner) *CmdObjBuilder {
}
var dummyPlatform = &Platform{
OS: "darwin",
Shell: "bash",
ShellArg: "-c",
OpenCommand: "open {{filename}}",
OpenLinkCommand: "open {{link}}",
OS: "darwin",
Shell: "bash",
InteractiveShell: "bash",
ShellArg: "-c",
InteractiveShellArg: "-i",
OpenCommand: "open {{filename}}",
OpenLinkCommand: "open {{link}}",
}
func NewDummyOSCommandWithRunner(runner *FakeCmdObjRunner) *OSCommand {

View File

@@ -35,11 +35,13 @@ type OSCommand struct {
// Platform stores the os state
type Platform struct {
OS string
Shell string
ShellArg string
OpenCommand string
OpenLinkCommand string
OS string
Shell string
InteractiveShell string
ShellArg string
InteractiveShellArg string
OpenCommand string
OpenLinkCommand string
}
// NewOSCommand os command runner
@@ -78,10 +80,10 @@ func FileType(path string) string {
}
func (c *OSCommand) OpenFile(filename string) error {
commandTemplate := c.UserConfig.OS.Open
commandTemplate := c.UserConfig().OS.Open
if commandTemplate == "" {
// Legacy support
commandTemplate = c.UserConfig.OS.OpenCommand
commandTemplate = c.UserConfig().OS.OpenCommand
}
if commandTemplate == "" {
commandTemplate = config.GetPlatformDefaultConfig().Open
@@ -94,10 +96,10 @@ func (c *OSCommand) OpenFile(filename string) error {
}
func (c *OSCommand) OpenLink(link string) error {
commandTemplate := c.UserConfig.OS.OpenLink
commandTemplate := c.UserConfig().OS.OpenLink
if commandTemplate == "" {
// Legacy support
commandTemplate = c.UserConfig.OS.OpenLinkCommand
commandTemplate = c.UserConfig().OS.OpenLinkCommand
}
if commandTemplate == "" {
commandTemplate = config.GetPlatformDefaultConfig().OpenLink
@@ -292,8 +294,8 @@ func (c *OSCommand) CopyToClipboard(str string) error {
},
)
c.LogCommand(msg, false)
if c.UserConfig.OS.CopyToClipboardCmd != "" {
cmdStr := utils.ResolvePlaceholderString(c.UserConfig.OS.CopyToClipboardCmd, map[string]string{
if c.UserConfig().OS.CopyToClipboardCmd != "" {
cmdStr := utils.ResolvePlaceholderString(c.UserConfig().OS.CopyToClipboardCmd, map[string]string{
"text": c.Cmd.Quote(str),
})
return c.Cmd.NewShell(cmdStr).Run()
@@ -305,8 +307,8 @@ func (c *OSCommand) CopyToClipboard(str string) error {
func (c *OSCommand) PasteFromClipboard() (string, error) {
var s string
var err error
if c.UserConfig.OS.CopyToClipboardCmd != "" {
cmdStr := c.UserConfig.OS.ReadFromClipboardCmd
if c.UserConfig().OS.CopyToClipboardCmd != "" {
cmdStr := c.UserConfig().OS.ReadFromClipboardCmd
s, err = c.Cmd.NewShell(cmdStr).RunWithOutput()
} else {
s, err = clipboard.ReadAll()

View File

@@ -4,15 +4,26 @@
package oscommands
import (
"os"
"runtime"
)
func GetPlatform() *Platform {
return &Platform{
OS: runtime.GOOS,
Shell: "bash",
ShellArg: "-c",
OpenCommand: "open {{filename}}",
OpenLinkCommand: "open {{link}}",
OS: runtime.GOOS,
Shell: "bash",
InteractiveShell: getUserShell(),
ShellArg: "-c",
InteractiveShellArg: "-i",
OpenCommand: "open {{filename}}",
OpenLinkCommand: "open {{link}}",
}
}
func getUserShell() string {
if shell := os.Getenv("SHELL"); shell != "" {
return shell
}
return "bash"
}

View File

@@ -75,7 +75,7 @@ func TestOSCommandOpenFileDarwin(t *testing.T) {
for _, s := range scenarios {
oSCmd := NewDummyOSCommandWithRunner(s.runner)
oSCmd.Platform.OS = "darwin"
oSCmd.UserConfig.OS.Open = "open {{filename}}"
oSCmd.UserConfig().OS.Open = "open {{filename}}"
s.test(oSCmd.OpenFile(s.filename))
}
@@ -135,7 +135,7 @@ func TestOSCommandOpenFileLinux(t *testing.T) {
for _, s := range scenarios {
oSCmd := NewDummyOSCommandWithRunner(s.runner)
oSCmd.Platform.OS = "linux"
oSCmd.UserConfig.OS.Open = `xdg-open {{filename}} > /dev/null`
oSCmd.UserConfig().OS.Open = `xdg-open {{filename}} > /dev/null`
s.test(oSCmd.OpenFile(s.filename))
}

View File

@@ -2,8 +2,10 @@ package oscommands
func GetPlatform() *Platform {
return &Platform{
OS: "windows",
Shell: "cmd",
ShellArg: "/c",
OS: "windows",
Shell: "cmd",
InteractiveShell: "cmd",
ShellArg: "/c",
InteractiveShellArg: "",
}
}

View File

@@ -71,7 +71,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
}
oSCmd.Platform = platform
oSCmd.Cmd.platform = platform
oSCmd.UserConfig.OS.OpenCommand = `start "" {{filename}}`
oSCmd.UserConfig().OS.OpenCommand = `start "" {{filename}}`
s.test(oSCmd.OpenFile(s.filename))
}

View File

@@ -1,7 +1,6 @@
package patch
import (
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
@@ -54,7 +53,7 @@ func (self *Patch) Lines() []*PatchLine {
// Returns the patch line index of the first line in the given hunk
func (self *Patch) HunkStartIdx(hunkIndex int) int {
hunkIndex = utils.Clamp(hunkIndex, 0, len(self.hunks)-1)
hunkIndex = lo.Clamp(hunkIndex, 0, len(self.hunks)-1)
result := len(self.header)
for i := 0; i < hunkIndex; i++ {
@@ -65,7 +64,7 @@ func (self *Patch) HunkStartIdx(hunkIndex int) int {
// Returns the patch line index of the last line in the given hunk
func (self *Patch) HunkEndIdx(hunkIndex int) int {
hunkIndex = utils.Clamp(hunkIndex, 0, len(self.hunks)-1)
hunkIndex = lo.Clamp(hunkIndex, 0, len(self.hunks)-1)
return self.HunkStartIdx(hunkIndex) + self.hunks[hunkIndex].lineCount() - 1
}
@@ -118,7 +117,7 @@ func (self *Patch) HunkContainingLine(idx int) int {
// Returns the patch line index of the next change (i.e. addition or deletion).
func (self *Patch) GetNextChangeIdx(idx int) int {
idx = utils.Clamp(idx, 0, self.LineCount()-1)
idx = lo.Clamp(idx, 0, self.LineCount()-1)
lines := self.Lines()

View File

@@ -1,6 +1,8 @@
package common
import (
"sync/atomic"
"github.com/jesseduffield/lazygit/pkg/config"
"github.com/jesseduffield/lazygit/pkg/i18n"
"github.com/sirupsen/logrus"
@@ -11,10 +13,18 @@ import (
type Common struct {
Log *logrus.Entry
Tr *i18n.TranslationSet
UserConfig *config.UserConfig
userConfig atomic.Pointer[config.UserConfig]
AppState *config.AppState
Debug bool
// for interacting with the filesystem. We use afero rather than the default
// `os` package for the sake of mocking the filesystem in tests
Fs afero.Fs
}
func (c *Common) UserConfig() *config.UserConfig {
return c.userConfig.Load()
}
func (c *Common) SetUserConfig(userConfig *config.UserConfig) {
c.userConfig.Store(userConfig)
}

View File

@@ -2,29 +2,31 @@ package config
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/adrg/xdg"
"github.com/jesseduffield/lazygit/pkg/utils/yaml_utils"
"github.com/samber/lo"
"gopkg.in/yaml.v3"
)
// AppConfig contains the base configuration fields required for lazygit.
type AppConfig struct {
Debug bool `long:"debug" env:"DEBUG" default:"false"`
Version string `long:"version" env:"VERSION" default:"unversioned"`
BuildDate string `long:"build-date" env:"BUILD_DATE"`
Name string `long:"name" env:"NAME" default:"lazygit"`
BuildSource string `long:"build-source" env:"BUILD_SOURCE" default:""`
UserConfig *UserConfig
UserConfigPaths []string
DeafultConfFiles bool
UserConfigDir string
TempDir string
AppState *AppState
IsNewRepo bool
debug bool `long:"debug" env:"DEBUG" default:"false"`
version string `long:"version" env:"VERSION" default:"unversioned"`
buildDate string `long:"build-date" env:"BUILD_DATE"`
name string `long:"name" env:"NAME" default:"lazygit"`
buildSource string `long:"build-source" env:"BUILD_SOURCE" default:""`
userConfig *UserConfig
globalUserConfigFiles []*ConfigFile
userConfigFiles []*ConfigFile
userConfigDir string
tempDir string
appState *AppState
}
type AppConfigurer interface {
@@ -38,13 +40,29 @@ type AppConfigurer interface {
GetUserConfig() *UserConfig
GetUserConfigPaths() []string
GetUserConfigDir() string
ReloadUserConfig() error
ReloadUserConfigForRepo(repoConfigFiles []*ConfigFile) error
ReloadChangedUserConfigFiles() (error, bool)
GetTempDir() string
GetAppState() *AppState
SaveAppState() error
}
type ConfigFilePolicy int
const (
ConfigFilePolicyCreateIfMissing ConfigFilePolicy = iota
ConfigFilePolicyErrorIfMissing
ConfigFilePolicySkipIfMissing
)
type ConfigFile struct {
Path string
Policy ConfigFilePolicy
modDate time.Time
exists bool
}
// NewAppConfig makes a new app config
func NewAppConfig(
name string,
@@ -60,17 +78,22 @@ func NewAppConfig(
return nil, err
}
var userConfigPaths []string
var configFiles []*ConfigFile
customConfigFiles := os.Getenv("LG_CONFIG_FILE")
if customConfigFiles != "" {
// Load user defined config files
userConfigPaths = strings.Split(customConfigFiles, ",")
userConfigPaths := strings.Split(customConfigFiles, ",")
configFiles = lo.Map(userConfigPaths, func(path string, _ int) *ConfigFile {
return &ConfigFile{Path: path, Policy: ConfigFilePolicyErrorIfMissing}
})
} else {
// Load default config files
userConfigPaths = []string{filepath.Join(configDir, ConfigFilename)}
path := filepath.Join(configDir, ConfigFilename)
configFile := &ConfigFile{Path: path, Policy: ConfigFilePolicyCreateIfMissing}
configFiles = []*ConfigFile{configFile}
}
userConfig, err := loadUserConfigWithDefaults(userConfigPaths)
userConfig, err := loadUserConfigWithDefaults(configFiles)
if err != nil {
return nil, err
}
@@ -92,26 +115,22 @@ func NewAppConfig(
}
appConfig := &AppConfig{
Name: name,
Version: version,
BuildDate: date,
Debug: debuggingFlag,
BuildSource: buildSource,
UserConfig: userConfig,
UserConfigPaths: userConfigPaths,
UserConfigDir: configDir,
TempDir: tempDir,
AppState: appState,
IsNewRepo: false,
name: name,
version: version,
buildDate: date,
debug: debuggingFlag,
buildSource: buildSource,
userConfig: userConfig,
globalUserConfigFiles: configFiles,
userConfigFiles: configFiles,
userConfigDir: configDir,
tempDir: tempDir,
appState: appState,
}
return appConfig, nil
}
func isCustomConfigFile(path string) bool {
return path != filepath.Join(ConfigDir(), ConfigFilename)
}
func ConfigDir() string {
_, filePath := findConfigFile("config.yml")
@@ -123,32 +142,48 @@ func findOrCreateConfigDir() (string, error) {
return folder, os.MkdirAll(folder, 0o755)
}
func loadUserConfigWithDefaults(configFiles []string) (*UserConfig, error) {
func loadUserConfigWithDefaults(configFiles []*ConfigFile) (*UserConfig, error) {
return loadUserConfig(configFiles, GetDefaultConfig())
}
func loadUserConfig(configFiles []string, base *UserConfig) (*UserConfig, error) {
for _, path := range configFiles {
if _, err := os.Stat(path); err != nil {
func loadUserConfig(configFiles []*ConfigFile, base *UserConfig) (*UserConfig, error) {
for _, configFile := range configFiles {
path := configFile.Path
statInfo, err := os.Stat(path)
if err == nil {
configFile.exists = true
configFile.modDate = statInfo.ModTime()
} else {
if !os.IsNotExist(err) {
return nil, err
}
// if use has supplied their own custom config file path(s), we assume
// the files have already been created, so we won't go and create them here.
if isCustomConfigFile(path) {
switch configFile.Policy {
case ConfigFilePolicyErrorIfMissing:
return nil, err
}
file, err := os.Create(path)
if err != nil {
if os.IsPermission(err) {
// apparently when people have read-only permissions they prefer us to fail silently
continue
case ConfigFilePolicySkipIfMissing:
configFile.exists = false
continue
case ConfigFilePolicyCreateIfMissing:
file, err := os.Create(path)
if err != nil {
if os.IsPermission(err) {
// apparently when people have read-only permissions they prefer us to fail silently
continue
}
return nil, err
}
return nil, err
file.Close()
configFile.exists = true
statInfo, err := os.Stat(configFile.Path)
if err != nil {
return nil, err
}
configFile.modDate = statInfo.ModTime()
}
file.Close()
}
content, err := os.ReadFile(path)
@@ -161,10 +196,14 @@ func loadUserConfig(configFiles []string, base *UserConfig) (*UserConfig, error)
return nil, err
}
existingCustomCommands := base.CustomCommands
if err := yaml.Unmarshal(content, base); err != nil {
return nil, fmt.Errorf("The config at `%s` couldn't be parsed, please inspect it before opening up an issue.\n%w", path, err)
}
base.CustomCommands = append(base.CustomCommands, existingCustomCommands...)
if err := base.Validate(); err != nil {
return nil, fmt.Errorf("The config at `%s` has a validation error.\n%w", path, err)
}
@@ -184,6 +223,12 @@ func migrateUserConfig(path string, content []byte) ([]byte, error) {
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
}
changedContent, err = yaml_utils.RenameYamlKey(changedContent, []string{"keybinding", "universal", "executeCustomCommand"},
"executeShellCommand")
if err != nil {
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
}
changedContent, err = changeNullKeybindingsToDisabled(changedContent)
if err != nil {
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
@@ -214,53 +259,81 @@ func changeNullKeybindingsToDisabled(changedContent []byte) ([]byte, error) {
}
func (c *AppConfig) GetDebug() bool {
return c.Debug
return c.debug
}
func (c *AppConfig) GetVersion() string {
return c.Version
return c.version
}
func (c *AppConfig) GetName() string {
return c.Name
return c.name
}
// GetBuildSource returns the source of the build. For builds from goreleaser
// this will be binaryBuild
func (c *AppConfig) GetBuildSource() string {
return c.BuildSource
return c.buildSource
}
// GetUserConfig returns the user config
func (c *AppConfig) GetUserConfig() *UserConfig {
return c.UserConfig
return c.userConfig
}
// GetAppState returns the app state
func (c *AppConfig) GetAppState() *AppState {
return c.AppState
return c.appState
}
func (c *AppConfig) GetUserConfigPaths() []string {
return c.UserConfigPaths
return lo.FilterMap(c.userConfigFiles, func(f *ConfigFile, _ int) (string, bool) {
return f.Path, f.exists
})
}
func (c *AppConfig) GetUserConfigDir() string {
return c.UserConfigDir
return c.userConfigDir
}
func (c *AppConfig) ReloadUserConfig() error {
userConfig, err := loadUserConfigWithDefaults(c.UserConfigPaths)
func (c *AppConfig) ReloadUserConfigForRepo(repoConfigFiles []*ConfigFile) error {
configFiles := append(c.globalUserConfigFiles, repoConfigFiles...)
userConfig, err := loadUserConfigWithDefaults(configFiles)
if err != nil {
return err
}
c.UserConfig = userConfig
c.userConfig = userConfig
c.userConfigFiles = configFiles
return nil
}
func (c *AppConfig) ReloadChangedUserConfigFiles() (error, bool) {
fileHasChanged := func(f *ConfigFile) bool {
info, err := os.Stat(f.Path)
if err != nil && !os.IsNotExist(err) {
// If we can't stat the file, assume it hasn't changed
return false
}
exists := err == nil
return exists != f.exists || (exists && info.ModTime() != f.modDate)
}
if lo.NoneBy(c.userConfigFiles, fileHasChanged) {
return nil, false
}
userConfig, err := loadUserConfigWithDefaults(c.userConfigFiles)
if err != nil {
return err, false
}
c.userConfig = userConfig
return nil, true
}
func (c *AppConfig) GetTempDir() string {
return c.TempDir
return c.tempDir
}
// findConfigFile looks for a possibly existing config file.
@@ -299,14 +372,9 @@ func stateFilePath(filename string) (string, error) {
return xdg.StateFile(filepath.Join("lazygit", filename))
}
// ConfigFilename returns the filename of the default config file
func (c *AppConfig) ConfigFilename() string {
return filepath.Join(c.UserConfigDir, ConfigFilename)
}
// SaveAppState marshalls the AppState struct and writes it to the disk
func (c *AppConfig) SaveAppState() error {
marshalledAppState, err := yaml.Marshal(c.AppState)
marshalledAppState, err := yaml.Marshal(c.appState)
if err != nil {
return err
}
@@ -357,6 +425,24 @@ func loadAppState() (*AppState, error) {
return appState, nil
}
// SaveGlobalUserConfig saves the UserConfig back to disk. This is only used in
// integration tests, so we are a bit sloppy with error handling.
func (c *AppConfig) SaveGlobalUserConfig() {
if len(c.globalUserConfigFiles) != 1 {
panic("expected exactly one global user config file")
}
yamlContent, err := yaml.Marshal(c.userConfig)
if err != nil {
log.Fatalf("error marshalling user config: %v", err)
}
err = os.WriteFile(c.globalUserConfigFiles[0].Path, yamlContent, 0o644)
if err != nil {
log.Fatalf("error saving user config: %v", err)
}
}
// AppState stores data between runs of the app like when the last update check
// was performed and which other repos have been checked out
type AppState struct {
@@ -365,8 +451,10 @@ type AppState struct {
StartupPopupVersion int
LastVersion string // this is the last version the user was using, for the purpose of showing release notes
// these are for custom commands typed in directly, not for custom commands in the lazygit config
CustomCommandsHistory []string
// these are for shell commands typed in directly, not for custom commands in the lazygit config.
// For backwards compatibility we keep the old name in yaml files.
ShellCommandsHistory []string `yaml:"customcommandshistory"`
HideCommandLog bool
IgnoreWhitespaceInDiffView bool
DiffContextSize int

View File

@@ -7,12 +7,12 @@ import (
// NewDummyAppConfig creates a new dummy AppConfig for testing
func NewDummyAppConfig() *AppConfig {
appConfig := &AppConfig{
Name: "lazygit",
Version: "unversioned",
Debug: false,
UserConfig: GetDefaultConfig(),
AppState: &AppState{},
name: "lazygit",
version: "unversioned",
debug: false,
userConfig: GetDefaultConfig(),
appState: &AppState{},
}
_ = yaml.Unmarshal([]byte{}, appConfig.AppState)
_ = yaml.Unmarshal([]byte{}, appConfig.appState)
return appConfig
}

View File

@@ -113,6 +113,13 @@ func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset
openDirInEditorTemplate: "xed -- {{dir}}",
suspend: returnBool(false),
},
"zed": {
editTemplate: "zed -- {{filename}}",
editAtLineTemplate: "zed -- {{filename}}:{{line}}",
editAtLineAndWaitTemplate: "zed --wait -- {{filename}}:{{line}}",
openDirInEditorTemplate: "zed -- {{dir}}",
suspend: returnBool(false),
},
}
// Some of our presets have a different name than the editor they are using.

View File

@@ -161,6 +161,10 @@ type GuiConfig struct {
// Status panel view.
// One of 'dashboard' (default) | 'allBranchesLog'
StatusPanelView string `yaml:"statusPanelView" jsonschema:"enum=dashboard,enum=allBranchesLog"`
// If true, jump to the Files panel after popping a stash
SwitchToFilesAfterStashPop bool `yaml:"switchToFilesAfterStashPop"`
// If true, jump to the Files panel after applying a stash
SwitchToFilesAfterStashApply bool `yaml:"switchToFilesAfterStashApply"`
}
func (c *GuiConfig) UseFuzzySearch() bool {
@@ -224,6 +228,11 @@ type GitConfig struct {
AutoRefresh bool `yaml:"autoRefresh"`
// 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.
AutoStageResolvedConflicts bool `yaml:"autoStageResolvedConflicts"`
// Command used when displaying the current branch git log in the main window
BranchLogCmd string `yaml:"branchLogCmd"`
// Command used to display git log of all branches in the main window.
@@ -366,6 +375,7 @@ type KeybindingUniversalConfig struct {
NextBlockAlt2 string `yaml:"nextBlock-alt2"`
PrevBlockAlt2 string `yaml:"prevBlock-alt2"`
JumpToBlock []string `yaml:"jumpToBlock"`
FocusMainView string `yaml:"focusMainView"`
NextMatch string `yaml:"nextMatch"`
PrevMatch string `yaml:"prevMatch"`
StartSearch string `yaml:"startSearch"`
@@ -385,7 +395,7 @@ type KeybindingUniversalConfig struct {
ScrollDownMainAlt1 string `yaml:"scrollDownMain-alt1"`
ScrollUpMainAlt2 string `yaml:"scrollUpMain-alt2"`
ScrollDownMainAlt2 string `yaml:"scrollDownMain-alt2"`
ExecuteCustomCommand string `yaml:"executeCustomCommand"`
ExecuteShellCommand string `yaml:"executeShellCommand"`
CreateRebaseOptionsMenu string `yaml:"createRebaseOptionsMenu"`
Push string `yaml:"pushFiles"` // 'Files' appended for legacy reasons
Pull string `yaml:"pullFiles"` // 'Files' appended for legacy reasons
@@ -541,7 +551,7 @@ type OSConfig struct {
// 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"`
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"`
// Command for opening a file, as if the file is double-clicked. Should
// contain "{{filename}}", but doesn't support "{{line}}".
@@ -724,7 +734,9 @@ func GetDefaultConfig() *UserConfig {
Frames: []string{"|", "/", "-", "\\"},
Rate: 50,
},
StatusPanelView: "dashboard",
StatusPanelView: "dashboard",
SwitchToFilesAfterStashPop: true,
SwitchToFilesAfterStashApply: true,
},
Git: GitConfig{
Paging: PagingConfig{
@@ -753,6 +765,7 @@ func GetDefaultConfig() *UserConfig {
AutoFetch: true,
AutoRefresh: true,
FetchAll: true,
AutoStageResolvedConflicts: true,
BranchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --",
AllBranchesLogCmd: "git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium",
DisableForcePushing: false,
@@ -804,6 +817,7 @@ func GetDefaultConfig() *UserConfig {
PrevBlockAlt2: "<backtab>",
NextBlockAlt2: "<tab>",
JumpToBlock: []string{"1", "2", "3", "4", "5"},
FocusMainView: "0",
NextMatch: "n",
PrevMatch: "N",
StartSearch: "/",
@@ -824,7 +838,7 @@ func GetDefaultConfig() *UserConfig {
ScrollDownMainAlt1: "J",
ScrollUpMainAlt2: "<c-u>",
ScrollDownMainAlt2: "<c-d>",
ExecuteCustomCommand: ":",
ExecuteShellCommand: ":",
CreateRebaseOptionsMenu: "m",
Push: "P",
Pull: "p",

View File

@@ -25,7 +25,7 @@ func (self *BackgroundRoutineMgr) PauseBackgroundRefreshes(pause bool) {
}
func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
userConfig := self.gui.UserConfig
userConfig := self.gui.UserConfig()
if userConfig.Git.AutoFetch {
fetchInterval := userConfig.Refresher.FetchInterval
@@ -77,13 +77,13 @@ func (self *BackgroundRoutineMgr) startBackgroundFetch() {
self.gui.waitForIntro.Wait()
isNew := self.gui.IsNewRepo
userConfig := self.gui.UserConfig
userConfig := self.gui.UserConfig()
if !isNew {
time.After(time.Duration(userConfig.Refresher.FetchInterval) * time.Second)
}
err := self.backgroundFetch()
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
_ = self.gui.c.Alert(self.gui.c.Tr.NoAutomaticGitFetchTitle, self.gui.c.Tr.NoAutomaticGitFetchBody)
self.gui.c.Alert(self.gui.c.Tr.NoAutomaticGitFetchTitle, self.gui.c.Tr.NoAutomaticGitFetchBody)
} else {
self.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), self.gui.stopChan, func() error {
err := self.backgroundFetch()

View File

@@ -55,11 +55,11 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
func (gui *Gui) printCommandLogHeader() {
introStr := fmt.Sprintf(
gui.c.Tr.CommandLogHeader,
keybindings.Label(gui.c.UserConfig.Keybinding.Universal.ExtrasMenu),
keybindings.Label(gui.c.UserConfig().Keybinding.Universal.ExtrasMenu),
)
fmt.Fprintln(gui.Views.Extras, style.FgCyan.Sprint(introStr))
if gui.c.UserConfig.Gui.ShowRandomTip {
if gui.c.UserConfig().Gui.ShowRandomTip {
fmt.Fprintf(
gui.Views.Extras,
"%s: %s",
@@ -70,7 +70,7 @@ func (gui *Gui) printCommandLogHeader() {
}
func (gui *Gui) getRandomTip() string {
config := gui.c.UserConfig.Keybinding
config := gui.c.UserConfig().Keybinding
formattedKey := func(key string) string {
return keybindings.Label(key)

View File

@@ -1,7 +1,6 @@
package gui
import (
"errors"
"sync"
"github.com/jesseduffield/lazygit/pkg/gui/context"
@@ -37,9 +36,9 @@ func NewContextMgr(
// use when you don't want to return to the original context upon
// hitting escape: you want to go that context's parent instead.
func (self *ContextMgr) Replace(c types.Context) error {
func (self *ContextMgr) Replace(c types.Context) {
if !c.IsFocusable() {
return nil
return
}
self.Lock()
@@ -51,14 +50,14 @@ func (self *ContextMgr) Replace(c types.Context) error {
self.ContextStack = append(self.ContextStack[0:len(self.ContextStack)-1], c)
}
defer self.Unlock()
self.Unlock()
return self.ActivateContext(c, types.OnFocusOpts{})
self.Activate(c, types.OnFocusOpts{})
}
func (self *ContextMgr) Push(c types.Context, opts ...types.OnFocusOpts) error {
func (self *ContextMgr) Push(c types.Context, opts ...types.OnFocusOpts) {
if len(opts) > 1 {
return errors.New("cannot pass multiple opts to Push")
panic("cannot pass multiple opts to Push")
}
singleOpts := types.OnFocusOpts{}
@@ -68,22 +67,18 @@ func (self *ContextMgr) Push(c types.Context, opts ...types.OnFocusOpts) error {
}
if !c.IsFocusable() {
return nil
return
}
contextsToDeactivate, contextToActivate := self.pushToContextStack(c)
for _, contextToDeactivate := range contextsToDeactivate {
if err := self.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
return err
}
self.deactivate(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()})
}
if contextToActivate == nil {
return nil
if contextToActivate != nil {
self.Activate(contextToActivate, singleOpts)
}
return self.ActivateContext(contextToActivate, singleOpts)
}
// Adjusts the context stack based on the context that's being pushed and
@@ -144,13 +139,13 @@ func (self *ContextMgr) pushToContextStack(c types.Context) ([]types.Context, ty
return contextsToDeactivate, c
}
func (self *ContextMgr) Pop() error {
func (self *ContextMgr) Pop() {
self.Lock()
if len(self.ContextStack) == 1 {
// cannot escape from bottommost context
self.Unlock()
return nil
return
}
var currentContext types.Context
@@ -160,44 +155,12 @@ func (self *ContextMgr) Pop() error {
self.Unlock()
if err := self.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
return err
}
self.deactivate(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()})
return self.ActivateContext(newContext, types.OnFocusOpts{})
self.Activate(newContext, types.OnFocusOpts{})
}
func (self *ContextMgr) RemoveContexts(contextsToRemove []types.Context) error {
self.Lock()
if len(self.ContextStack) == 1 {
self.Unlock()
return nil
}
rest := lo.Filter(self.ContextStack, func(context types.Context, _ int) bool {
for _, contextToRemove := range contextsToRemove {
if context.GetKey() == contextToRemove.GetKey() {
return false
}
}
return true
})
self.ContextStack = rest
contextToActivate := rest[len(rest)-1]
self.Unlock()
for _, context := range contextsToRemove {
if err := self.deactivateContext(context, types.OnFocusLostOpts{NewContextKey: contextToActivate.GetKey()}); err != nil {
return err
}
}
// activate the item at the top of the stack
return self.ActivateContext(contextToActivate, types.OnFocusOpts{})
}
func (self *ContextMgr) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
func (self *ContextMgr) deactivate(c types.Context, opts types.OnFocusLostOpts) {
view, _ := self.gui.c.GocuiGui().View(c.GetViewName())
if opts.NewContextKey != context.SEARCH_CONTEXT_KEY {
@@ -213,18 +176,14 @@ func (self *ContextMgr) deactivateContext(c types.Context, opts types.OnFocusLos
view.Visible = false
}
if err := c.HandleFocusLost(opts); err != nil {
return err
}
return nil
c.HandleFocusLost(opts)
}
func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts) error {
func (self *ContextMgr) Activate(c types.Context, opts types.OnFocusOpts) {
viewName := c.GetViewName()
v, err := self.gui.c.GocuiGui().View(viewName)
if err != nil {
return err
panic(err)
}
self.gui.helpers.Window.SetWindowContext(c)
@@ -235,7 +194,7 @@ func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts)
oldView.HighlightInactive = true
}
if _, err := self.gui.c.GocuiGui().SetCurrentView(viewName); err != nil {
return err
panic(err)
}
self.gui.helpers.Search.RenderSearchStatus(c)
@@ -249,11 +208,7 @@ func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts)
self.gui.c.GocuiGui().Cursor = v.Editable
if err := c.HandleFocus(opts); err != nil {
return err
}
return nil
c.HandleFocus(opts)
}
func (self *ContextMgr) Current() types.Context {
@@ -330,6 +285,18 @@ func (self *ContextMgr) IsCurrent(c types.Context) bool {
return self.Current().GetKey() == c.GetKey()
}
func (self *ContextMgr) IsCurrentOrParent(c types.Context) bool {
current := self.Current()
for current != nil {
if current.GetKey() == c.GetKey() {
return true
}
current = current.GetParentContext()
}
return false
}
func (self *ContextMgr) AllFilterable() []types.IFilterableContext {
var result []types.IFilterableContext
@@ -392,7 +359,7 @@ func (self *ContextMgr) ContextForKey(key types.ContextKey) types.Context {
return nil
}
func (self *ContextMgr) PopupContexts() []types.Context {
func (self *ContextMgr) CurrentPopup() []types.Context {
self.RLock()
defer self.RUnlock()

View File

@@ -16,7 +16,7 @@ type BaseContext struct {
keybindingsFns []types.KeybindingsFn
mouseKeybindingsFns []types.MouseKeybindingsFn
onClickFn func() error
onRenderToMainFn func() error
onRenderToMainFn func()
onFocusFn onFocusFn
onFocusLostFn onFocusLostFn
@@ -31,8 +31,8 @@ type BaseContext struct {
}
type (
onFocusFn = func(types.OnFocusOpts) error
onFocusLostFn = func(types.OnFocusLostOpts) error
onFocusFn = func(types.OnFocusOpts)
onFocusLostFn = func(types.OnFocusLostOpts)
)
var _ types.IBaseContext = &BaseContext{}
@@ -148,13 +148,13 @@ func (self *BaseContext) GetOnClick() func() error {
return self.onClickFn
}
func (self *BaseContext) AddOnRenderToMainFn(fn func() error) {
func (self *BaseContext) AddOnRenderToMainFn(fn func()) {
if fn != nil {
self.onRenderToMainFn = fn
}
}
func (self *BaseContext) GetOnRenderToMain() func() error {
func (self *BaseContext) GetOnRenderToMain() func() {
return self.onRenderToMainFn
}
@@ -212,3 +212,7 @@ func (self *BaseContext) NeedsRerenderOnHeightChange() bool {
func (self *BaseContext) Title() string {
return ""
}
func (self *BaseContext) TotalContentHeight() int {
return self.view.ViewLinesHeight()
}

View File

@@ -32,7 +32,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
c.Modes().Diffing.Ref,
c.Views().Branches.Width(),
c.Tr,
c.UserConfig,
c.UserConfig(),
c.Model().Worktrees,
)
}

View File

@@ -1,6 +1,8 @@
package context
import (
"fmt"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
@@ -28,7 +30,7 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
viewModel := filetree.NewCommitFileTreeViewModel(
func() []*models.CommitFile { return c.Model().CommitFiles },
c.Log,
c.UserConfig.Gui.ShowFileTree,
c.UserConfig().Gui.ShowFileTree,
)
getDisplayStrings := func(_ int, _ int) [][]string {
@@ -36,7 +38,7 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
return [][]string{{style.FgRed.Sprint("(none)")}}
}
showFileIcons := icons.IsIconEnabled() && c.UserConfig.Gui.ShowFileIcons
showFileIcons := icons.IsIconEnabled() && c.UserConfig().Gui.ShowFileIcons
lines := presentation.RenderCommitFileTree(viewModel, c.Git().Patch.PatchBuilder, showFileIcons)
return lo.Map(lines, func(line string, _ int) []string {
return []string{line}
@@ -75,6 +77,25 @@ func (self *CommitFilesContext) GetDiffTerminals() []string {
return []string{self.GetRef().RefName()}
}
func (self *CommitFilesContext) GetFromAndToForDiff() (string, string) {
if refs := self.GetRefRange(); refs != nil {
return refs.From.ParentRefName(), refs.To.RefName()
}
ref := self.GetRef()
return ref.ParentRefName(), ref.RefName()
}
func (self *CommitFilesContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
return nil
}
func (self *CommitFilesContext) ReInit(ref types.Ref, refRange *types.RefRange) {
self.SetRef(ref)
self.SetRefRange(refRange)
if refRange != nil {
self.SetTitleRef(fmt.Sprintf("%s-%s", refRange.From.ShortRefName(), refRange.To.ShortRefName()))
} else {
self.SetTitleRef(ref.Description())
}
self.GetView().Title = self.Title()
}

View File

@@ -113,19 +113,19 @@ func (self *CommitMessageContext) SetPanelState(
self.c.Views().CommitDescription.Subtitle = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionSubTitle,
map[string]string{
"togglePanelKeyBinding": keybindings.Label(self.c.UserConfig.Keybinding.Universal.TogglePanel),
"commitMenuKeybinding": keybindings.Label(self.c.UserConfig.Keybinding.CommitMessage.CommitMenu),
"togglePanelKeyBinding": keybindings.Label(self.c.UserConfig().Keybinding.Universal.TogglePanel),
"commitMenuKeybinding": keybindings.Label(self.c.UserConfig().Keybinding.CommitMessage.CommitMenu),
})
self.c.Views().CommitDescription.Visible = true
}
func (self *CommitMessageContext) RenderCommitLength() {
if !self.c.UserConfig.Gui.CommitLength.Show {
return
if self.c.UserConfig().Gui.CommitLength.Show {
self.c.Views().CommitMessage.Subtitle = getBufferLength(self.c.Views().CommitMessage)
} else {
self.c.Views().CommitMessage.Subtitle = ""
}
self.c.Views().CommitMessage.Subtitle = getBufferLength(self.c.Views().CommitMessage)
}
func getBufferLength(view *gocui.View) string {

View File

@@ -24,6 +24,8 @@ const (
STASH_CONTEXT_KEY types.ContextKey = "stash"
NORMAL_MAIN_CONTEXT_KEY types.ContextKey = "normal"
NORMAL_SECONDARY_CONTEXT_KEY types.ContextKey = "normalSecondary"
DIFF_MAIN_CONTEXT_KEY types.ContextKey = "diff"
DIFF_SECONDARY_CONTEXT_KEY types.ContextKey = "diffSecondary"
STAGING_MAIN_CONTEXT_KEY types.ContextKey = "staging"
STAGING_SECONDARY_CONTEXT_KEY types.ContextKey = "stagingSecondary"
PATCH_BUILDING_MAIN_CONTEXT_KEY types.ContextKey = "patchBuilding"
@@ -100,6 +102,8 @@ type ContextTree struct {
Suggestions *SuggestionsContext
Normal types.Context
NormalSecondary types.Context
Diff types.Context
DiffSecondary types.Context
Staging *PatchExplorerContext
StagingSecondary *PatchExplorerContext
CustomPatchBuilder *PatchExplorerContext
@@ -149,6 +153,8 @@ func (self *ContextTree) Flatten() []types.Context {
self.Staging,
self.CustomPatchBuilderSecondary,
self.CustomPatchBuilder,
self.Diff,
self.DiffSecondary,
self.NormalSecondary,
self.Normal,

View File

@@ -0,0 +1,53 @@
package context
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
type DiffContext struct {
*SimpleContext
*SearchTrait
c *ContextCommon
}
var _ types.ISearchableContext = (*DiffContext)(nil)
func NewDiffContext(
view *gocui.View,
windowName string,
key types.ContextKey,
c *ContextCommon,
) *DiffContext {
ctx := &DiffContext{
SimpleContext: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
View: view,
WindowName: windowName,
Key: key,
Focusable: true,
HighlightOnFocus: true,
})),
SearchTrait: NewSearchTrait(c),
c: c,
}
// TODO: copied from PatchExplorerContext. Do we need something like this?
// ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(
// func(selectedLineIdx int) error {
// ctx.GetMutex().Lock()
// defer ctx.GetMutex().Unlock()
// ctx.NavigateTo(ctx.c.Context().IsCurrent(ctx), selectedLineIdx)
// return nil
// }),
// )
return ctx
}
func (self *DiffContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
return nil
}

View File

@@ -49,7 +49,7 @@ func (self *ListContextTrait) FocusLine() {
} else if self.renderOnlyVisibleLines {
newOrigin, _ := self.GetViewTrait().ViewPortYBounds()
if oldOrigin != newOrigin {
return self.HandleRender()
self.HandleRender()
}
}
return nil
@@ -72,26 +72,26 @@ func formatListFooter(selectedLineIdx int, length int) string {
return fmt.Sprintf("%d of %d", selectedLineIdx+1, length)
}
func (self *ListContextTrait) HandleFocus(opts types.OnFocusOpts) error {
func (self *ListContextTrait) HandleFocus(opts types.OnFocusOpts) {
self.FocusLine()
self.GetViewTrait().SetHighlight(self.list.Len() > 0)
return self.Context.HandleFocus(opts)
self.Context.HandleFocus(opts)
}
func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) error {
func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) {
self.GetViewTrait().SetOriginX(0)
if self.refreshViewportOnChange {
self.refreshViewport()
}
return self.Context.HandleFocusLost(opts)
self.Context.HandleFocusLost(opts)
}
// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view
func (self *ListContextTrait) HandleRender() error {
func (self *ListContextTrait) HandleRender() {
self.list.ClampSelection()
if self.renderOnlyVisibleLines {
// Rendering only the visible area can save a lot of cell memory for
@@ -110,13 +110,12 @@ func (self *ListContextTrait) HandleRender() error {
}
self.c.Render()
self.setFooter()
return nil
}
func (self *ListContextTrait) OnSearchSelect(selectedLineIdx int) error {
self.GetList().SetSelection(self.ViewIndexToModelIndex(selectedLineIdx))
return self.HandleFocus(types.OnFocusOpts{})
self.HandleFocus(types.OnFocusOpts{})
return nil
}
func (self *ListContextTrait) IsItemVisible(item types.HasUrn) bool {
@@ -140,3 +139,11 @@ func (self *ListContextTrait) RangeSelectEnabled() bool {
func (self *ListContextTrait) RenderOnlyVisibleLines() bool {
return self.renderOnlyVisibleLines
}
func (self *ListContextTrait) TotalContentHeight() int {
result := self.list.Len()
if self.getNonModelItems != nil {
result += len(self.getNonModelItems())
}
return result
}

View File

@@ -52,7 +52,7 @@ func (self *ListRenderer) ModelIndexToViewIndex(modelIndex int) int {
}
func (self *ListRenderer) ViewIndexToModelIndex(viewIndex int) int {
viewIndex = utils.Clamp(viewIndex, 0, self.list.Len()+self.numNonModelItems)
viewIndex = lo.Clamp(viewIndex, 0, self.list.Len()+self.numNonModelItems)
if self.modelIndicesByViewIndex != nil {
return self.modelIndicesByViewIndex[viewIndex]
}
@@ -121,7 +121,10 @@ func (self *ListRenderer) insertNonModelItems(
break
}
if item.Index+offset >= startIdx {
padding := strings.Repeat(" ", columnPositions[item.Column])
padding := ""
if columnPositions != nil {
padding = strings.Repeat(" ", columnPositions[item.Column])
}
lines = slices.Insert(lines, item.Index+offset-startIdx, padding+item.Content)
}
offset++

View File

@@ -34,7 +34,7 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
getDisplayStrings := func(startIdx int, endIdx int) [][]string {
selectedCommitHash := ""
if c.CurrentContext().GetKey() == LOCAL_COMMITS_CONTEXT_KEY {
if c.Context().Current().GetKey() == LOCAL_COMMITS_CONTEXT_KEY {
selectedCommit := viewModel.GetSelected()
if selectedCommit != nil {
selectedCommitHash = selectedCommit.Hash
@@ -54,10 +54,10 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
c.Modes().CherryPicking.SelectedHashSet(),
c.Modes().Diffing.Ref,
c.Modes().MarkedBaseCommit.GetHash(),
c.UserConfig.Gui.TimeFormat,
c.UserConfig.Gui.ShortTimeFormat,
c.UserConfig().Gui.TimeFormat,
c.UserConfig().Gui.ShortTimeFormat,
time.Now(),
c.UserConfig.Git.ParseEmoji,
c.UserConfig().Git.ParseEmoji,
selectedCommitHash,
startIdx,
endIdx,
@@ -110,7 +110,7 @@ func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *ContextCommon
self := &LocalCommitsViewModel{
ListViewModel: NewListViewModel(getModel),
limitCommits: true,
showWholeGitGraph: c.UserConfig.Git.Log.ShowWholeGraph,
showWholeGitGraph: c.UserConfig().Git.Log.ShowWholeGraph,
}
return self
@@ -128,6 +128,19 @@ func (self *LocalCommitsContext) GetSelectedRef() types.Ref {
return commit
}
func (self *LocalCommitsContext) GetSelectedRefRangeForDiffFiles() *types.RefRange {
commits, startIdx, endIdx := self.GetSelectedItems()
if commits == nil || startIdx == endIdx {
return nil
}
from := commits[len(commits)-1]
to := commits[0]
if from.IsTODO() || to.IsTODO() {
return nil
}
return &types.RefRange{From: from, To: to}
}
// Returns the commit hash of the selected commit, or an empty string if no
// commit is selected
func (self *LocalCommitsContext) GetSelectedCommitHash() string {
@@ -202,6 +215,15 @@ func shouldShowGraph(c *ContextCommon) bool {
}
func searchModelCommits(caseSensitive bool, commits []*models.Commit, columnPositions []int, searchStr string) []gocui.SearchPosition {
if columnPositions == nil {
// This should never happen. We are being called at a time where our
// entire view content is scrolled out of view, so that we didn't draw
// anything the last time we rendered. If we run into a scenario where
// this happens, we should fix it, but until we found them all, at least
// make sure we don't crash.
return []gocui.SearchPosition{}
}
normalize := lo.Ternary(caseSensitive, func(s string) string { return s }, strings.ToLower)
return lo.FilterMap(commits, func(commit *models.Commit, idx int) (gocui.SearchPosition, bool) {
// The XStart and XEnd values are only used if the search string can't

View File

@@ -139,7 +139,7 @@ func (self *MenuViewModel) GetNonModelItems() []*NonModelItem {
// Don't display section headers when we are filtering, and the filter mode
// is fuzzy. The reason is that filtering changes the order of the items
// (they are sorted by best match), so all the sections would be messed up.
if self.FilteredListViewModel.IsFiltering() && self.c.UserConfig.Gui.UseFuzzySearch() {
if self.FilteredListViewModel.IsFiltering() && self.c.UserConfig().Gui.UseFuzzySearch() {
return result
}
@@ -197,9 +197,7 @@ func (self *MenuContext) OnMenuPress(selectedItem *types.MenuItem) error {
return nil
}
if err := self.c.PopContext(); err != nil {
return err
}
self.c.Context().Pop()
if selectedItem == nil {
return nil

View File

@@ -68,13 +68,11 @@ func (self *MergeConflictsContext) IsUserScrolling() bool {
return self.viewModel.userVerticalScrolling
}
func (self *MergeConflictsContext) RenderAndFocus() error {
func (self *MergeConflictsContext) RenderAndFocus() {
self.setContent()
self.FocusSelection()
self.c.Render()
return nil
}
func (self *MergeConflictsContext) Render() error {
@@ -99,7 +97,7 @@ func (self *MergeConflictsContext) setContent() {
func (self *MergeConflictsContext) FocusSelection() {
if !self.IsUserScrolling() {
_ = self.GetView().SetOriginY(self.GetOriginY())
self.GetView().SetOriginY(self.GetOriginY())
}
self.SetSelectedLineRange()

View File

@@ -4,17 +4,14 @@ import "github.com/jesseduffield/lazygit/pkg/gui/types"
type ParentContextMgr struct {
ParentContext types.Context
// we can't know on the calling end whether a Context is actually a nil value without reflection, so we're storing this flag here to tell us. There has got to be a better way around this
hasParent bool
}
var _ types.ParentContexter = (*ParentContextMgr)(nil)
func (self *ParentContextMgr) SetParentContext(context types.Context) {
self.ParentContext = context
self.hasParent = true
}
func (self *ParentContextMgr) GetParentContext() (types.Context, bool) {
return self.ParentContext, self.hasParent
func (self *ParentContextMgr) GetParentContext() types.Context {
return self.ParentContext
}

View File

@@ -53,7 +53,8 @@ func NewPatchExplorerContext(
func(selectedLineIdx int) error {
ctx.GetMutex().Lock()
defer ctx.GetMutex().Unlock()
return ctx.NavigateTo(ctx.c.IsCurrentContext(ctx), selectedLineIdx)
ctx.NavigateTo(ctx.c.Context().IsCurrent(ctx), selectedLineIdx)
return nil
}),
)
@@ -78,28 +79,22 @@ func (self *PatchExplorerContext) GetIncludedLineIndices() []int {
return self.getIncludedLineIndices()
}
func (self *PatchExplorerContext) RenderAndFocus(isFocused bool) error {
func (self *PatchExplorerContext) RenderAndFocus(isFocused bool) {
self.setContent(isFocused)
self.FocusSelection()
self.c.Render()
return nil
}
func (self *PatchExplorerContext) Render(isFocused bool) error {
func (self *PatchExplorerContext) Render(isFocused bool) {
self.setContent(isFocused)
self.c.Render()
return nil
}
func (self *PatchExplorerContext) Focus() error {
func (self *PatchExplorerContext) Focus() {
self.FocusSelection()
self.c.Render()
return nil
}
func (self *PatchExplorerContext) setContent(isFocused bool) {
@@ -116,7 +111,7 @@ func (self *PatchExplorerContext) FocusSelection() {
newOriginY := state.CalculateOrigin(origin, bufferHeight, numLines)
_ = view.SetOriginY(newOriginY)
view.SetOriginY(newOriginY)
startIdx, endIdx := state.SelectedRange()
// As far as the view is concerned, we are always selecting a range
@@ -132,11 +127,11 @@ func (self *PatchExplorerContext) GetContentToRender(isFocused bool) string {
return self.GetState().RenderForLineIndices(isFocused, self.GetIncludedLineIndices())
}
func (self *PatchExplorerContext) NavigateTo(isFocused bool, selectedLineIdx int) error {
func (self *PatchExplorerContext) NavigateTo(isFocused bool, selectedLineIdx int) {
self.GetState().SetLineSelectMode()
self.GetState().SelectLine(selectedLineIdx)
return self.RenderAndFocus(isFocused)
self.RenderAndFocus(isFocused)
}
func (self *PatchExplorerContext) GetMutex() *deadlock.Mutex {

View File

@@ -33,9 +33,9 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
c.Modes().CherryPicking.SelectedHashSet(),
c.Modes().Diffing.Ref,
time.Now(),
c.UserConfig.Gui.TimeFormat,
c.UserConfig.Gui.ShortTimeFormat,
c.UserConfig.Git.ParseEmoji,
c.UserConfig().Gui.TimeFormat,
c.UserConfig().Gui.ShortTimeFormat,
c.UserConfig().Git.ParseEmoji,
)
}
@@ -71,6 +71,11 @@ func (self *ReflogCommitsContext) GetSelectedRef() types.Ref {
return commit
}
func (self *ReflogCommitsContext) GetSelectedRefRangeForDiffFiles() *types.RefRange {
// It doesn't make much sense to show a range diff between two reflog entries.
return nil
}
func (self *ReflogCommitsContext) GetCommits() []*models.Commit {
return self.getModel()
}

View File

@@ -26,7 +26,7 @@ func NewRemotesContext(c *ContextCommon) *RemotesContext {
getDisplayStrings := func(_ int, _ int) [][]string {
return presentation.GetRemoteListDisplayStrings(
viewModel.GetItems(), c.Modes().Diffing.Ref, c.State().GetItemOperation, c.Tr, c.UserConfig)
viewModel.GetItems(), c.Modes().Diffing.Ref, c.State().GetItemOperation, c.Tr, c.UserConfig())
}
return &RemotesContext{

View File

@@ -51,7 +51,7 @@ func (self *SearchTrait) onSelectItemWrapper(innerFunc func(int) error) func(int
}
func (self *SearchTrait) RenderSearchStatus(index int, total int) {
keybindingConfig := self.c.UserConfig.Keybinding
keybindingConfig := self.c.UserConfig().Keybinding
if total == 0 {
self.c.SetViewContent(

View File

@@ -57,6 +57,8 @@ func NewContextTree(c *ContextCommon) *ContextTree {
Focusable: false,
}),
),
Diff: NewDiffContext(c.Views().Diff, "main", DIFF_MAIN_CONTEXT_KEY, c),
DiffSecondary: NewDiffContext(c.Views().DiffSecondary, "secondary", DIFF_SECONDARY_CONTEXT_KEY, c),
Staging: NewPatchExplorerContext(
c.Views().Staging,
"main",

View File

@@ -31,43 +31,33 @@ func NewDisplayContext(key types.ContextKey, view *gocui.View, windowName string
)
}
func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error {
func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) {
if self.highlightOnFocus {
self.GetViewTrait().SetHighlight(true)
}
if self.onFocusFn != nil {
if err := self.onFocusFn(opts); err != nil {
return err
}
self.onFocusFn(opts)
}
if self.onRenderToMainFn != nil {
if err := self.onRenderToMainFn(); err != nil {
return err
}
self.onRenderToMainFn()
}
return nil
}
func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) error {
func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) {
self.GetViewTrait().SetHighlight(false)
_ = self.view.SetOriginX(0)
self.view.SetOriginX(0)
if self.onFocusLostFn != nil {
return self.onFocusLostFn(opts)
self.onFocusLostFn(opts)
}
return nil
}
func (self *SimpleContext) HandleRender() error {
return nil
func (self *SimpleContext) HandleRender() {
}
func (self *SimpleContext) HandleRenderToMain() error {
func (self *SimpleContext) HandleRenderToMain() {
if self.onRenderToMainFn != nil {
return self.onRenderToMainFn()
self.onRenderToMainFn()
}
return nil
}

View File

@@ -61,6 +61,11 @@ func (self *StashContext) GetSelectedRef() types.Ref {
return stash
}
func (self *StashContext) GetSelectedRefRangeForDiffFiles() *types.RefRange {
// It doesn't make much sense to show a range diff between two stash entries.
return nil
}
func (self *StashContext) GetDiffTerminals() []string {
itemId := self.GetSelectedItemId()

View File

@@ -40,14 +40,14 @@ func NewSubCommitsContext(
getDisplayStrings := func(startIdx int, endIdx int) [][]string {
// This can happen if a sub-commits view is asked to be rerendered while
// it is invisble; for example when switching screen modes, which
// it is invisible; for example when switching screen modes, which
// rerenders all views.
if viewModel.GetRef() == nil {
return [][]string{}
}
selectedCommitHash := ""
if c.CurrentContext().GetKey() == SUB_COMMITS_CONTEXT_KEY {
if c.Context().Current().GetKey() == SUB_COMMITS_CONTEXT_KEY {
selectedCommit := viewModel.GetSelected()
if selectedCommit != nil {
selectedCommitHash = selectedCommit.Hash
@@ -68,10 +68,10 @@ func NewSubCommitsContext(
c.Modes().CherryPicking.SelectedHashSet(),
c.Modes().Diffing.Ref,
"",
c.UserConfig.Gui.TimeFormat,
c.UserConfig.Gui.ShortTimeFormat,
c.UserConfig().Gui.TimeFormat,
c.UserConfig().Gui.ShortTimeFormat,
time.Now(),
c.UserConfig.Git.ParseEmoji,
c.UserConfig().Git.ParseEmoji,
selectedCommitHash,
startIdx,
endIdx,
@@ -186,6 +186,19 @@ func (self *SubCommitsContext) GetSelectedRef() types.Ref {
return commit
}
func (self *SubCommitsContext) GetSelectedRefRangeForDiffFiles() *types.RefRange {
commits, startIdx, endIdx := self.GetSelectedItems()
if commits == nil || startIdx == endIdx {
return nil
}
from := commits[len(commits)-1]
to := commits[0]
if from.Divergence != to.Divergence {
return nil
}
return &types.RefRange{From: from, To: to}
}
func (self *SubCommitsContext) GetCommits() []*models.Commit {
return self.getModel()
}

View File

@@ -70,7 +70,7 @@ func (self *SuggestionsContext) SetSuggestions(suggestions []*types.Suggestion)
self.State.Suggestions = suggestions
self.SetSelection(0)
self.c.ResetViewOrigin(self.GetView())
_ = self.HandleRender()
self.HandleRender()
}
func (self *SuggestionsContext) RefreshSuggestions() {

View File

@@ -30,7 +30,7 @@ func NewTagsContext(
return presentation.GetTagListDisplayStrings(
viewModel.GetItems(),
c.State().GetItemOperation,
c.Modes().Diffing.Ref, c.Tr, c.UserConfig)
c.Modes().Diffing.Ref, c.Tr, c.UserConfig())
}
return &TagsContext{

View File

@@ -3,6 +3,7 @@ package traits
import (
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type RangeSelectMode int
@@ -85,7 +86,7 @@ func (self *ListCursor) clampValue(value int) int {
clampedValue := -1
length := self.getLength()
if length > 0 {
clampedValue = utils.Clamp(value, 0, length-1)
clampedValue = lo.Clamp(value, 0, length-1)
}
return clampedValue

View File

@@ -57,7 +57,7 @@ func (self *ViewTrait) SetFooter(value string) {
}
func (self *ViewTrait) SetOriginX(value int) {
_ = self.view.SetOriginX(value)
self.view.SetOriginX(value)
}
// tells us the start of line indexes shown in the view currently as well as the capacity of lines shown in the viewport.

View File

@@ -25,11 +25,11 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
viewModel := filetree.NewFileTreeViewModel(
func() []*models.File { return c.Model().Files },
c.Log,
c.UserConfig.Gui.ShowFileTree,
c.UserConfig().Gui.ShowFileTree,
)
getDisplayStrings := func(_ int, _ int) [][]string {
showFileIcons := icons.IsIconEnabled() && c.UserConfig.Gui.ShowFileIcons
showFileIcons := icons.IsIconEnabled() && c.UserConfig().Gui.ShowFileIcons
lines := presentation.RenderFileTree(viewModel, c.Model().Submodules, showFileIcons)
return lo.Map(lines, func(line string, _ int) []string {
return []string{line}

View File

@@ -107,7 +107,7 @@ func (gui *Gui) resetHelpersAndControllers() {
Files: helpers.NewFilesHelper(helperCommon),
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper),
Tags: helpers.NewTagsHelper(helperCommon, commitsHelper),
BranchesHelper: helpers.NewBranchesHelper(helperCommon),
BranchesHelper: helpers.NewBranchesHelper(helperCommon, worktreeHelper),
GPG: helpers.NewGpgHelper(helperCommon),
MergeAndRebase: rebaseHelper,
MergeConflicts: mergeConflictsHelper,
@@ -181,6 +181,7 @@ func (gui *Gui) resetHelpersAndControllers() {
contextLinesController := controllers.NewContextLinesController(common)
renameSimilarityThresholdController := controllers.NewRenameSimilarityThresholdController(common)
verticalScrollControllerFactory := controllers.NewVerticalScrollControllerFactory(common, &gui.viewBufferManagerMap)
viewSelectionControllerFactory := controllers.NewViewSelectionControllerFactory(common, &gui.viewBufferManagerMap)
branchesController := controllers.NewBranchesController(common)
gitFlowController := controllers.NewGitFlowController(common)
@@ -189,6 +190,8 @@ func (gui *Gui) resetHelpersAndControllers() {
patchExplorerControllerFactory := controllers.NewPatchExplorerControllerFactory(common)
stagingController := controllers.NewStagingController(common, gui.State.Contexts.Staging, gui.State.Contexts.StagingSecondary, false)
stagingSecondaryController := controllers.NewStagingController(common, gui.State.Contexts.StagingSecondary, gui.State.Contexts.Staging, true)
diffController := controllers.NewDiffController(common, gui.State.Contexts.Diff, gui.State.Contexts.DiffSecondary, &gui.viewBufferManagerMap)
diffSecondaryController := controllers.NewDiffController(common, gui.State.Contexts.DiffSecondary, gui.State.Contexts.Diff, &gui.viewBufferManagerMap)
patchBuildingController := controllers.NewPatchBuildingController(common)
snakeController := controllers.NewSnakeController(common)
reflogCommitsController := controllers.NewReflogCommitsController(common)
@@ -197,7 +200,7 @@ func (gui *Gui) resetHelpersAndControllers() {
commandLogController := controllers.NewCommandLogController(common)
confirmationController := controllers.NewConfirmationController(common)
suggestionsController := controllers.NewSuggestionsController(common)
jumpToSideWindowController := controllers.NewJumpToSideWindowController(common)
jumpToSideWindowController := controllers.NewJumpToSideWindowController(common, gui.handleNextTab)
sideWindowControllerFactory := controllers.NewSideWindowControllerFactory(common)
@@ -259,7 +262,7 @@ func (gui *Gui) resetHelpersAndControllers() {
gui.State.Contexts.Stash,
} {
controllers.AttachControllers(context, controllers.NewSwitchToDiffFilesController(
common, context, gui.State.Contexts.CommitFiles,
common, context,
))
}
@@ -306,6 +309,18 @@ func (gui *Gui) resetHelpersAndControllers() {
mergeConflictsController,
)
controllers.AttachControllers(gui.State.Contexts.Diff,
diffController,
verticalScrollControllerFactory.Create(gui.State.Contexts.Diff),
viewSelectionControllerFactory.Create(gui.State.Contexts.Diff),
)
controllers.AttachControllers(gui.State.Contexts.DiffSecondary,
diffSecondaryController,
verticalScrollControllerFactory.Create(gui.State.Contexts.DiffSecondary),
viewSelectionControllerFactory.Create(gui.State.Contexts.DiffSecondary),
)
controllers.AttachControllers(gui.State.Contexts.Files,
filesController,
)
@@ -354,6 +369,7 @@ func (gui *Gui) resetHelpersAndControllers() {
controllers.AttachControllers(gui.State.Contexts.CommitDescription,
commitDescriptionController,
verticalScrollControllerFactory.Create(gui.State.Contexts.CommitDescription),
)
controllers.AttachControllers(gui.State.Contexts.RemoteBranches,

View File

@@ -19,14 +19,14 @@ func (self *baseController) GetOnClick() func() error {
return nil
}
func (self *baseController) GetOnRenderToMain() func() error {
func (self *baseController) GetOnRenderToMain() func() {
return nil
}
func (self *baseController) GetOnFocus() func(types.OnFocusOpts) error {
func (self *baseController) GetOnFocus() func(types.OnFocusOpts) {
return nil
}
func (self *baseController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
func (self *baseController) GetOnFocusLost() func(types.OnFocusLostOpts) {
return nil
}

View File

@@ -280,7 +280,7 @@ func (self *BasicCommitsController) createResetMenu(commit *models.Commit) error
}
func (self *BasicCommitsController) checkout(commit *models.Commit) error {
return self.c.Confirm(types.ConfirmOpts{
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.CheckoutCommit,
Prompt: self.c.Tr.SureCheckoutThisCommit,
HandleConfirm: func() error {
@@ -288,6 +288,7 @@ func (self *BasicCommitsController) checkout(commit *models.Commit) error {
return self.c.Helpers().Refs.CheckoutRef(commit.Hash, types.CheckoutRefOptions{})
},
})
return nil
}
func (self *BasicCommitsController) copyRange(*models.Commit) error {
@@ -311,8 +312,8 @@ func (self *BasicCommitsController) canCopyCommits(selectedCommits []*models.Com
func (self *BasicCommitsController) handleOldCherryPickKey() error {
msg := utils.ResolvePlaceholderString(self.c.Tr.OldCherryPickKeyWarning,
map[string]string{
"copy": keybindings.Label(self.c.UserConfig.Keybinding.Commits.CherryPickCopy),
"paste": keybindings.Label(self.c.UserConfig.Keybinding.Commits.PasteCommits),
"copy": keybindings.Label(self.c.UserConfig().Keybinding.Commits.CherryPickCopy),
"paste": keybindings.Label(self.c.UserConfig().Keybinding.Commits.PasteCommits),
})
return errors.New(msg)

View File

@@ -201,10 +201,10 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
{
Label: self.c.Tr.Bisect.ChooseTerms,
OnPress: func() error {
return self.c.Prompt(types.PromptOpts{
self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.Bisect.OldTermPrompt,
HandleConfirm: func(oldTerm string) error {
return self.c.Prompt(types.PromptOpts{
self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.Bisect.NewTermPrompt,
HandleConfirm: func(newTerm string) error {
self.c.LogAction(self.c.Tr.Actions.StartBisect)
@@ -215,8 +215,10 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
},
})
return nil
},
})
return nil
},
Key: 't',
},
@@ -235,7 +237,7 @@ func (self *BisectController) showBisectCompleteMessage(candidateHashes []string
return err
}
return self.c.Confirm(types.ConfirmOpts{
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.Bisect.CompleteTitle,
Prompt: fmt.Sprintf(prompt, strings.TrimSpace(formattedCommits)),
HandleConfirm: func() error {
@@ -247,6 +249,8 @@ func (self *BisectController) showBisectCompleteMessage(candidateHashes []string
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
},
})
return nil
}
func (self *BisectController) afterMark(selectCurrent bool, waitToReselect bool) error {
@@ -290,7 +294,7 @@ func (self *BisectController) selectCurrentBisectCommit() {
for i, commit := range self.c.Model().Commits {
if commit.Hash == info.GetCurrentHash() {
self.context().SetSelection(i)
_ = self.context().HandleFocus(types.OnFocusOpts{})
self.context().HandleFocus(types.OnFocusOpts{})
break
}
}

View File

@@ -3,7 +3,6 @@ package controllers
import (
"errors"
"fmt"
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
@@ -170,9 +169,9 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
}
}
func (self *BranchesController) GetOnRenderToMain() func() error {
return func() error {
return self.c.Helpers().Diff.WithDiffModeCheck(func() error {
func (self *BranchesController) GetOnRenderToMain() func() {
return func() {
self.c.Helpers().Diff.WithDiffModeCheck(func() {
var task types.UpdateTask
branch := self.context().GetSelected()
if branch == nil {
@@ -183,7 +182,7 @@ func (self *BranchesController) GetOnRenderToMain() func() error {
task = types.NewRunPtyTask(cmdObj.GetCmd())
}
return self.c.RenderToMainViews(types.RefreshMainOpts{
self.c.RenderToMainViews(types.RefreshMainOpts{
Pair: self.c.MainViewPairs().Normal,
Main: &types.ViewUpdateOpts{
Title: self.c.Tr.LogTitle,
@@ -410,13 +409,15 @@ func (self *BranchesController) promptToCheckoutWorktree(worktree *models.Worktr
"worktreeName": worktree.Name,
})
return self.c.Confirm(types.ConfirmOpts{
self.c.Confirm(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 {
@@ -460,7 +461,7 @@ func (self *BranchesController) forceCheckout() error {
message := self.c.Tr.SureForceCheckout
title := self.c.Tr.ForceCheckoutBranch
return self.c.Confirm(types.ConfirmOpts{
self.c.Confirm(types.ConfirmOpts{
Title: title,
Prompt: message,
HandleConfirm: func() error {
@@ -471,10 +472,12 @@ func (self *BranchesController) forceCheckout() error {
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
},
})
return nil
}
func (self *BranchesController) checkoutByName() error {
return self.c.Prompt(types.PromptOpts{
self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.BranchName + ":",
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
HandleConfirm: func(response string) error {
@@ -485,18 +488,22 @@ func (self *BranchesController) checkoutByName() error {
}
return self.c.Helpers().Refs.CheckoutRef(response, types.CheckoutRefOptions{
OnRefNotFound: func(ref string) error {
return self.c.Confirm(types.ConfirmOpts{
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.BranchNotFoundTitle,
Prompt: fmt.Sprintf("%s %s%s", self.c.Tr.BranchNotFoundPrompt, ref, "?"),
HandleConfirm: func() error {
return self.createNewBranchWithName(ref)
},
})
return nil
},
})
},
},
)
return nil
}
func (self *BranchesController) createNewBranchWithName(newBranchName string) error {
@@ -513,89 +520,16 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, KeepBranchSelectionIndex: true})
}
func (self *BranchesController) checkedOutByOtherWorktree(branch *models.Branch) bool {
return git_commands.CheckedOutByOtherWorktree(branch, self.c.Model().Worktrees)
}
func (self *BranchesController) promptWorktreeBranchDelete(selectedBranch *models.Branch) error {
worktree, ok := self.worktreeForBranch(selectedBranch)
if !ok {
self.c.Log.Error("promptWorktreeBranchDelete out of sync with list of worktrees")
return nil
}
// TODO: i18n
title := utils.ResolvePlaceholderString(self.c.Tr.BranchCheckedOutByWorktree, map[string]string{
"worktreeName": worktree.Name,
"branchName": selectedBranch.Name,
})
return self.c.Menu(types.CreateMenuOptions{
Title: title,
Items: []*types.MenuItem{
{
Label: self.c.Tr.SwitchToWorktree,
OnPress: func() error {
return self.c.Helpers().Worktree.Switch(worktree, context.LOCAL_BRANCHES_CONTEXT_KEY)
},
},
{
Label: self.c.Tr.DetachWorktree,
Tooltip: self.c.Tr.DetachWorktreeTooltip,
OnPress: func() error {
return self.c.Helpers().Worktree.Detach(worktree)
},
},
{
Label: self.c.Tr.RemoveWorktree,
OnPress: func() error {
return self.c.Helpers().Worktree.Remove(worktree, false)
},
},
},
})
}
func (self *BranchesController) localDelete(branch *models.Branch) error {
if self.checkedOutByOtherWorktree(branch) {
return self.promptWorktreeBranchDelete(branch)
}
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(_ gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch)
err := self.c.Git().Branch.LocalDelete(branch.Name, false)
if err != nil && strings.Contains(err.Error(), "git branch -D ") {
return self.forceDelete(branch)
}
if err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
})
return self.c.Helpers().BranchesHelper.ConfirmLocalDelete(branch)
}
func (self *BranchesController) remoteDelete(branch *models.Branch) error {
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(branch.UpstreamRemote, branch.Name)
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(branch.UpstreamRemote, branch.UpstreamBranch)
}
func (self *BranchesController) forceDelete(branch *models.Branch) error {
title := self.c.Tr.ForceDeleteBranchTitle
message := utils.ResolvePlaceholderString(
self.c.Tr.ForceDeleteBranchMessage,
map[string]string{
"selectedBranchName": branch.Name,
},
)
return self.c.Confirm(types.ConfirmOpts{
Title: title,
Prompt: message,
HandleConfirm: func() error {
if err := self.c.Git().Branch.LocalDelete(branch.Name, true); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
},
})
func (self *BranchesController) localAndRemoteDelete(branch *models.Branch) error {
return self.c.Helpers().BranchesHelper.ConfirmLocalAndRemoteDelete(branch)
}
func (self *BranchesController) delete(branch *models.Branch) error {
@@ -623,6 +557,19 @@ func (self *BranchesController) delete(branch *models.Branch) error {
remoteDeleteItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
}
deleteBothItem := &types.MenuItem{
Label: self.c.Tr.DeleteLocalAndRemoteBranch,
Key: 'b',
OnPress: func() error {
return self.localAndRemoteDelete(branch)
},
}
if checkedOutBranch.Name == branch.Name {
deleteBothItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CantDeleteCheckOutBranch}
} else if !branch.IsTrackingRemote() || branch.UpstreamGone {
deleteBothItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
}
menuTitle := utils.ResolvePlaceholderString(
self.c.Tr.DeleteBranchTitle,
map[string]string{
@@ -632,7 +579,7 @@ func (self *BranchesController) delete(branch *models.Branch) error {
return self.c.Menu(types.CreateMenuOptions{
Title: menuTitle,
Items: []*types.MenuItem{localDeleteItem, remoteDeleteItem},
Items: []*types.MenuItem{localDeleteItem, remoteDeleteItem, deleteBothItem},
})
}
@@ -715,7 +662,7 @@ func (self *BranchesController) createResetMenu(selectedBranch *models.Branch) e
func (self *BranchesController) rename(branch *models.Branch) error {
promptForNewName := func() error {
return self.c.Prompt(types.PromptOpts{
self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.NewBranchNamePrompt + " " + branch.Name + ":",
InitialContent: branch.Name,
HandleConfirm: func(newBranchName string) error {
@@ -734,15 +681,15 @@ func (self *BranchesController) rename(branch *models.Branch) error {
for i, newBranch := range self.c.Model().Branches {
if newBranch.Name == newBranchName {
self.context().SetSelection(i)
if err := self.context().HandleRender(); err != nil {
return err
}
self.context().HandleRender()
}
}
return nil
},
})
return nil
}
// I could do an explicit check here for whether the branch is tracking a remote branch
@@ -752,11 +699,13 @@ func (self *BranchesController) rename(branch *models.Branch) error {
return promptForNewName()
}
return self.c.Confirm(types.ConfirmOpts{
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.RenameBranch,
Prompt: self.c.Tr.RenameBranchWarning,
HandleConfirm: promptForNewName,
})
return nil
}
func (self *BranchesController) newBranch(selectedBranch *models.Branch) error {
@@ -781,13 +730,15 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
{
LabelColumns: fromToLabelColumns(branch.Name, self.c.Tr.SelectBranch),
OnPress: func() error {
return self.c.Prompt(types.PromptOpts{
self.c.Prompt(types.PromptOpts{
Title: branch.Name + " →",
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteBranchesSuggestionsFunc("/"),
HandleConfirm: func(targetBranchName string) error {
return self.createPullRequest(branch.Name, targetBranchName)
},
})
return nil
},
},
}
@@ -810,7 +761,7 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
menuItems = append(menuItems, menuItemsForBranch(selectedBranch)...)
return self.c.Menu(types.CreateMenuOptions{Title: fmt.Sprintf(self.c.Tr.CreatePullRequestOptions), Items: menuItems})
return self.c.Menu(types.CreateMenuOptions{Title: fmt.Sprint(self.c.Tr.CreatePullRequestOptions), Items: menuItems})
}
func (self *BranchesController) createPullRequest(from string, to string) error {

View File

@@ -26,10 +26,9 @@ func (self *CommandLogController) GetKeybindings(opts types.KeybindingsOpts) []*
return bindings
}
func (self *CommandLogController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
return func(types.OnFocusLostOpts) error {
func (self *CommandLogController) GetOnFocusLost() func(types.OnFocusLostOpts) {
return func(types.OnFocusLostOpts) {
self.c.Views().Extras.Autoscroll = true
return nil
}
}

View File

@@ -1,6 +1,7 @@
package controllers
import (
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
)
@@ -45,19 +46,27 @@ func (self *CommitDescriptionController) GetKeybindings(opts types.KeybindingsOp
}
func (self *CommitDescriptionController) Context() types.Context {
return self.context()
return self.c.Contexts().CommitDescription
}
func (self *CommitDescriptionController) context() *context.CommitMessageContext {
return self.c.Contexts().CommitMessage
func (self *CommitDescriptionController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{
{
ViewName: self.Context().GetViewName(),
Key: gocui.MouseLeft,
Handler: self.onClick,
},
}
}
func (self *CommitDescriptionController) switchToCommitMessage() error {
return self.c.ReplaceContext(self.c.Contexts().CommitMessage)
self.c.Context().Replace(self.c.Contexts().CommitMessage)
return nil
}
func (self *CommitDescriptionController) close() error {
return self.c.Helpers().Commits.CloseCommitMessagePanel()
self.c.Helpers().Commits.CloseCommitMessagePanel()
return nil
}
func (self *CommitDescriptionController) confirm() error {
@@ -68,3 +77,12 @@ func (self *CommitDescriptionController) openCommitMenu() error {
authorSuggestion := self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc()
return self.c.Helpers().Commits.OpenCommitMenu(authorSuggestion)
}
func (self *CommitDescriptionController) onClick(opts gocui.ViewMouseBindingOpts) error {
// Activate the description panel when the commit message panel is currently active
if self.c.Context().Current().GetKey() == context.COMMIT_MESSAGE_CONTEXT_KEY {
self.c.Context().Replace(self.c.Contexts().CommitDescription)
}
return nil
}

View File

@@ -3,6 +3,7 @@ package controllers
import (
"errors"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
@@ -58,10 +59,19 @@ func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts)
return bindings
}
func (self *CommitMessageController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
return func(types.OnFocusLostOpts) error {
func (self *CommitMessageController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
return []*gocui.ViewMouseBinding{
{
ViewName: self.Context().GetViewName(),
Key: gocui.MouseLeft,
Handler: self.onClick,
},
}
}
func (self *CommitMessageController) GetOnFocusLost() func(types.OnFocusLostOpts) {
return func(types.OnFocusLostOpts) {
self.context().RenderCommitLength()
return nil
}
}
@@ -85,9 +95,7 @@ func (self *CommitMessageController) handleNextCommit() error {
}
func (self *CommitMessageController) switchToCommitDescription() error {
if err := self.c.ReplaceContext(self.c.Contexts().CommitDescription); err != nil {
return err
}
self.c.Context().Replace(self.c.Contexts().CommitDescription)
return nil
}
@@ -118,8 +126,8 @@ func (self *CommitMessageController) setCommitMessageAtIndex(index int) (bool, e
}
return false, errors.New(self.c.Tr.CommitWithoutMessageErr)
}
if self.c.UserConfig.Git.Commit.AutoWrapCommitMessage {
commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig.Git.Commit.AutoWrapWidth)
if self.c.UserConfig().Git.Commit.AutoWrapCommitMessage {
commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig().Git.Commit.AutoWrapWidth)
}
self.c.Helpers().Commits.UpdateCommitPanelView(commitMessage)
return true, nil
@@ -130,10 +138,20 @@ func (self *CommitMessageController) confirm() error {
}
func (self *CommitMessageController) close() error {
return self.c.Helpers().Commits.CloseCommitMessagePanel()
self.c.Helpers().Commits.CloseCommitMessagePanel()
return nil
}
func (self *CommitMessageController) openCommitMenu() error {
authorSuggestion := self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc()
return self.c.Helpers().Commits.OpenCommitMenu(authorSuggestion)
}
func (self *CommitMessageController) onClick(opts gocui.ViewMouseBindingOpts) error {
// Activate the commit message panel when the commit description panel is currently active
if self.c.Context().Current().GetKey() == context.COMMIT_DESCRIPTION_CONTEXT_KEY {
self.c.Context().Replace(self.c.Contexts().CommitMessage)
}
return nil
}

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