Compare commits

...
90 Commits
Author SHA1 Message Date
Stefan Haller bc4b50f817 Add user config to use hunk mode by default when entering staging view (#4685)
- **PR Description**

As of #4684, hunk mode has become so useful that I prefer it over line
mode now. This PR adds a config that lets you use hunk mode by default
in the staging view.

I'm not enabling this by default yet, although I do think it's the more
useful mode for most people. The biggest issue that I still have with
this is that _if_ you need to switch to line mode for some reason, then
it's very non-obvious how to do that. New users might not find out at
all, and think that lazygit doesn't allow staging individual lines.
2025-07-04 19:46:36 +02:00
Stefan Haller 0a73123a66 Escape out of hunk mode only if it was turned on by the user
If hunk mode is on by default because of the config, then it's annoying for
escape to go to line mode.
2025-07-04 19:41:40 +02:00
Stefan Haller 2961c991a4 Add user config to use hunk mode by default when entering staging view 2025-07-04 19:41:40 +02:00
Stefan Haller a6001dc76e Cleanup: rename a badly named function
We used the term "change line" to mean "a line that was added or deleted", but
it sounded like a verb in the function name.
2025-07-04 19:41:39 +02:00
Stefan Haller 9f23b89e90 Improve hunk selection mode in staging view (#4684)
- **PR Description**

Hunk selection mode is one of the features that many people don't know
about, because it is not very discoverable. You can switch to it from
line selection mode by pressing `a` in the staging view.

The problem with this mode is that it selects entire hunks, where hunks
are defined to be sections of the diff starting with `@@`. Very often,
hunks consist of multiple distinct blocks of changes, separated by
context lines. For example, with the default diff context size of 3 it
takes at least 6 unchanged lines between blocks of changes for them to
be separated into distinct hunks; if there are 5 or less unchanged lines
between them, they are grouped into one hunk. And of course, if you
increase the diff context size by pressing `}`, you will get even fewer
hunks.

Now, most of the time I want to navigate between the individual blocks
of changes in a diff, regardless of how git groups them into hunks.
That's what this PR does: when pressing `a`, the selection is extended
to just the current group of changes, separated by context lines; you
can easily stage it by pressing space, and the selection will move on to
the next block of changes. Actual hunks no longer play a role here.
Also, in line selection mode the right/left arrow keys now move between
blocks of changes rather than actual hunks.

I find this new behavior so useful that I almost always switch to hunk
mode right away after entering the staging view. It saves a lot of
keystrokes, since it is very rare that I want to select only some lines
of a block of adjacent changes. This makes me wonder whether we should
enable hunk mode by default when entering staging, but that's going to
be another PR.
2025-07-04 19:37:48 +02:00
Stefan Haller a6096f4702 In hunk staging mode, select blocks of changes rather than actual hunks
Also, pressing right or left arrow moves between blocks of changes rather than
actual hunks. I find this to be much more useful.
2025-07-04 19:31:35 +02:00
Stefan Haller 08ad8a0b2d Don't generate test list before running integration test
It's annoyingly slow, and there is a separate task for generating the list if
needed.

Also, clear the terminal before running the test; this makes it easier to see
the results of the last test run.
2025-07-04 18:26:45 +02:00
Stefan Haller 3ea4cf0b63 Update translations from Crowdin (#4686)
- **PR Description**

Update translations from Crowdin
2025-07-04 18:25:19 +02:00
Stefan Haller 9290c074f7 Update translations from Crowdin 2025-07-04 18:22:13 +02:00
Stefan Haller ca06ce6100 Collapse selection after deleting a range of branches or stashes (#4661)
- **PR Description**

Fixes selection state persistence after deletion by resetting selection
mode.
Resolves [#4612](https://github.com/jesseduffield/lazygit/issues/4612)
2025-07-04 17:34:33 +02:00
nileric bc0cddf03d Collapse selection after deleting a range of stashes 2025-07-04 17:28:29 +02:00
Stefan Haller 4b33efffbe Collapse selection after deleting a range of remote branches
We only want to do this when the function is called from the remote branches
panel. It can also be called with a selection of local branches in order to
delete their remote branches, but in this case the selection shouldn't be
collapsed because the local branches stay around.
2025-07-04 17:20:22 +02:00
Stefan Haller 80a614d330 Fix collapsing the range selection after deleting branches
We had code already that was supposed to do this, but it didn't work. It should
have used SetSelection() instead of SetSelectedLineIdx(); the latter doesn't
actually cancel a range selection.

Introduce a new function specifically for collapsing the range after deleting
multiple items, so that clients don't need two calls (we'll add a bunch more in
this branch).
2025-07-04 17:18:10 +02:00
Stefan Haller 61ff8a4da8 Move to next stageable line when adding a line to a custom patch (#4675)
- **PR Description**

While it's true that the behavior is a little different from the staging
panel, where the staged lines are actually removed from the view and in
many cases the selection stays more or less in the same place, it is
still very useful to move to the next stageable thing in the custom
patch building view too.

Also, we change the visualization of what's included in the patch to
mark only the + and - lines of the patch; for all other lines it doesn't
make a difference whether they are included.

And finally, we make it so that only + and - lines are considered when
pressing space; previously it would also look at selected context lines,
which doesn't make much sense. This improves the experience for mouse
users who like to generously select hunks by dragging across them,
including some context lines above and below.
2025-07-04 10:16:45 +02:00
Stefan Haller 039831a27a Move to next stageable line after adding a line to a custom patch
While it's true that the behavior is a little different from the staging panel,
where the staged lines are actually removed from the view and in many cases the
selection stays more or less in the same place, it is still very useful to move
to the next stageable thing in the custom patch building view too.
2025-07-04 10:14:35 +02:00
Stefan Haller ce9fbe58b2 Toggle only added/deleted lines in patch building view
This improves the experience when selecting a hunk generously with the mouse, by
dragging over it including some context lines above and below. Previously we
would consider the "moving end" of the selection range for whether things need
to be added or removed, but this doesn't make sense if it's a context line. Now
we consider the first actual change line that is included in the range.
2025-07-04 10:14:35 +02:00
Stefan Haller ef1a141347 Export PatchLine.isChange 2025-07-04 10:14:35 +02:00
Stefan Haller 42bbe965fa Render only + and - lines as included in a patch
It is confusing to get header lines, hunk headers, or context lines rendered as
being included in a custom patch, when including these makes no difference to
the patch.

This is only a visual change; internally, we still record these non-patch lines
as being included in the patch. It doesn't matter though; you can press space on
a header line and nothing happens.

It would probably be cleaner to only record + and - lines in the includedLines
array, but that would be a bit more work, and doesn't seem worth it.
2025-07-04 10:14:35 +02:00
Stefan Haller a8e98593ea Auto-stash modified files when cherry-picking or reverting commits (#4683)
- **PR Description**

For cherry-picking, this used to work in earlier versions, but it broke
in #4443. For reverting, it was never supported.

Also, we add some minor improvements while we're at it, such as slightly
better names for the auto-stashes that are created for the various
operations (so that, if an auto-stash pop fails and the stash is kept
around, you can tell more easily what it was for). Also, we now adjust
the selection of the commits list after cherry-picking, so that the same
commit stays selected.
2025-07-04 10:13:52 +02:00
Stefan Haller 6b1cab7011 Auto-stash if necessary when reverting commits 2025-07-03 17:04:10 +02:00
Stefan Haller dc795b5db7 Remove unnecessary refresh
CheckMergeOrRebase calls Refresh already. However, it does an async refresh by
default, so we must turn this into a sync refresh so that moving the selection
down by one works even for the very first commit in history. Also, we must add
an explicit call to FocusLine so that the view selection is in sync with the
model selection; previously this was taken care of by the PostRefreshUpdate call
that happens as part of a refresh.
2025-07-03 17:04:10 +02:00
Stefan Haller 0fc107c8c1 Adjust selection after cherry-picking commits
Keep the same commit selected, by moving the selection down by the number of
cherry-picked commits. We also do this when reverting commits, and it is
possible now that we use a sync waiting status.

We also need to turn the refresh that happens as part of CheckMergeOrRebase into
a sync one, so that the commits list is up to date and the new selection isn't
clamped.
2025-07-03 17:04:10 +02:00
Stefan Haller 09a4e0b209 Add selection assertions to cherry-pick tests
We are about to change the selection behavior when cherry-picking, and it's good
to have tests that document in what way it changes in the next commit.
2025-07-03 16:58:56 +02:00
Stefan Haller 6479c52fb2 Use sync waiting status for cherry-picking
This reduces flicker of the Files panel in the case that an auto-stash needs to
be made.
2025-07-03 16:58:56 +02:00
Stefan Haller 3f3e942f60 Auto-stash if necessary when cherry-picking commits 2025-07-03 16:58:56 +02:00
Stefan Haller e7cb469fd0 Remove unused text StashPrefix 2025-07-03 16:56:30 +02:00
Stefan Haller 9511ed9d2c Use a better name for the auto-stash for moving a custom patch to index 2025-07-03 16:56:30 +02:00
Stefan Haller 2a7ce19b73 Use a better name for the auto-stash for creating a new branch
For the case of creating a new branch by moving commits to it, we were using the
current (old) branch name in the stash name; change this to use the new name
instead.
2025-07-03 16:56:30 +02:00
Stefan Haller 908975c758 Use a better name for the auto-stash for checking out a branch 2025-07-03 16:53:49 +02:00
Stefan Haller a83fc5e343 Use a better name for the auto-stash for undo 2025-07-03 16:53:49 +02:00
Stefan Haller 2c206c374d Use abbreviated hash in undo confirmations 2025-07-03 16:53:49 +02:00
Stefan Haller 50785a2217 Exclude vendor directory from linting
It seems to be excluded already when you run the lint.sh script, but in VS Code
when setting Lint on Save to package it would still lint a file in gocui when
you save it, which is annoying. (Remove the other paths that were there before;
they seem to be unused, and they were added by the auto-migration.)

Unfortunately, gopls will still lint gocui files with its builtin staticcheck
linter, and I couldn't find a way to turn this off. This might be a reason to
turn off staticcheck in gopls (not sure yet).
2025-07-03 16:39:52 +02:00
Stefan Haller 91d8c25183 Fix applying custom patches to a dirty working tree (#4674)
- **PR Description**

Applying or reverting a custom patch when one of the files contained in
the patch had unstaged modifications in the working tree would fail with
the confusing error message "does not match index", regardless of
whether those modifications conflicted with the patch or not. You would
expect this to just work if there are no conflicts, or to get the usual
conflict markers if there are. It was possible to work around this by
manually staging those files, but few people knew about this. Fix this
by staging the files (after asking for confirmation).

Also, fix a minor problem where an auto-stash for the "move patch to
index" command was forgotten to be dropped.
2025-07-02 16:32:32 +02:00
Stefan Haller 3df894ec92 Stage affected unstaged files when applying or reverting a patch
Unlike moving a patch to the index, applying or reverting a patch didn't
auto-stash, which means that applying a patch when there's a modified (but
unstaged) file in the working tree would error out with the message "error:
file1: does not match index", regardless of whether those modifications conflict
with the patch or not.

To fix this, we *could* add auto-stashing like we do for the "move patch to
index" command. However, in this case we rather simply stage the affected files
(after asking for confirmation). This has a few advantages:

- it only changes the staging state of those files that are contained in the
patch (whereas auto-stashing always changes all files to unstaged)
- it doesn't unnecessarily show a confirmation if none of the modified files are
affected by the patch
- if the patch conflicts with the modified files, the conflicts were "backwards"
("ours" was the patch, "theirs" the modified file); it is more logical if "ours"
is the current state of the file, and "theirs" is the patch.

It's a little unfortunate that the behavior isn't exactly the same as for "move
patch to index", but for that one we do need the auto-stash because of the
rebase that runs behind the scenes.
2025-07-02 16:19:06 +02:00
Stefan Haller c440a208a6 Add tests for applying a patch when there's a modified file
The tests show that this currently fails with the confusing error message "does
not match index", regardless of whether the patch conflicts with the
modifications or not. We'll improve this in the next commit.

I don't bother adding tests for reverting a patch, as the code is basically the
same as for apply.
2025-07-02 16:19:06 +02:00
Stefan Haller 32bd9e6029 Drop stash when successfully unstashing files after moving patch to index 2025-07-02 16:19:06 +02:00
Stefan Haller 823aa640b9 Add test for moving patch to index when there's a modified file
This is functionality that works already, we only add the test for more complete
test coverage. However, there's a detail problem, and the test demonstrates
this: we keep the stash even if there was no conflict. We'll fix this next.
2025-07-02 16:19:06 +02:00
Stefan Haller 00d043d743 Cleanup: implement AllFilesInPatch using lo.Keys
Curiously, the function was never called so far, but we're going to use it later
in this branch.
2025-07-02 16:19:06 +02:00
Stefan Haller eebc39d3e9 Show stash name for selected stash (#4673)
- **PR Description**

In the Stash panel, show information about the selected stash above the
diff of the stash in the main view. This is useful for stash entries
with very long names that are not fully visible in the Stash panel.

Fixes #4662.
2025-07-02 16:18:11 +02:00
Stefan Haller 92c9eb614f Show stash title in main view for selected stash entry 2025-07-02 16:15:19 +02:00
Stefan Haller ef16867ff1 Cleanup: add NewRunPtyTaskWithPrefix funxtion instead of setting Prefix manually
This is consistent with NewRunCommandTaskWithPrefix.
2025-07-02 16:15:19 +02:00
Stefan Haller edcfce22f0 Change Refresh to not return an error (#4680)
- **PR Description**

Refresh is one of those functions that shouldn't require error handling
(similar to triggering a redraw of the UI, see
https://github.com/jesseduffield/lazygit/issues/3887).
2025-07-02 16:14:11 +02:00
Stefan Haller c87661dbee Use async refresh rather than manual OnWorker call
This is exactly what async refresh is for.
2025-07-02 16:10:04 +02:00
Stefan Haller d82852a909 Change Refresh to not return an error
Refresh is one of those functions that shouldn't require error handling (similar
to triggering a redraw of the UI, see
https://github.com/jesseduffield/lazygit/issues/3887).

As far as I see, the only reason why Refresh can currently return an error is
that the Then function returns one. The actual refresh errors, e.g. from the git
calls that are made to fetch data, are already logged and swallowed. Most of the
Then functions do only UI stuff such as selecting a list item, and always return
nil; there's only one that can return an error (updating the rebase todo file in
LocalCommitsController.startInteractiveRebaseWithEdit); it's not a critical
error if this fails, it is only used for setting rebase todo items to "edit"
when you start an interactive rebase by pressing 'e' on a range selection of
commits. We simply log this error instead of returning it.
2025-07-02 16:09:42 +02:00
Stefan Haller 2b1f150129 Some code cleanups to the "discard file changes from commit" feature (#4679)
- **PR Description**

I ran into these issues while reading the code.
2025-07-02 16:02:58 +02:00
Stefan Haller 5be97c6145 Remove another unnecessary refresh
This was added after this PR comment:
https://github.com/jesseduffield/lazygit/pull/3276#discussion_r1469077611

> Can we do a refresh after this reset so that the screen shows that the patch
> has been cancelled? That way, if we cancel on the next popup, the screen will
> be in a valid state.

I don't understand what "cancel on the next popup" means; there is no further
popup after this code.
2025-07-02 15:58:03 +02:00
Stefan Haller 80e20417a8 Remove unnecessary refresh
CheckMergeOrRebase already calls Refresh.
2025-07-02 15:58:03 +02:00
Stefan Haller ee62878e6b Cleanup: remove pointless error handling
ForEachFile can't possibly return an error if the passed function doesn't return
one.
2025-07-02 15:47:48 +02:00
Stefan Haller 2a5970265d Fix formatting of a keyboard shortcut in the README.md (#4678)
- **PR Description**

This will format the `z` keyboard shortcut reference in this text in the
same way as all other keyboard shortcuts in the README.md file.
2025-07-02 15:28:48 +02:00
Daniel Jonsson d60a843c23 Fix formatting of a keyboard shortcut in the README.md
This will format the `z` keyboard shortcut reference in this text in the
same way as all other keyboard shortcuts in the README.md file.
2025-07-02 15:00:23 +02:00
Stefan Haller 36388e58b9 Update linter (#4671)
- Update the golangci-lint configuration to use the latest version
(2.2.1)
- Enable a few more linter checks
- Improve the VS Code configuration so that we don't get spurious linter
warnings that wouldn't also show up on CI
- Provide a way to run the exact same golangci-lint version locally that
we use on CI. This is useful in case the globally installed version is a
newer one which emits more warnings.
2025-07-01 11:08:24 +02:00
Stefan Haller ca05a2ccea Enable revive linter, and fix a bunch of warnings
I took the set of enabled checks from revive's recommended configuration [1],
and removed some that I didn't like. There might be other useful checks in
revive that we might want to enable, but this is a nice improvement already.

The bulk of the changes here are removing unnecessary else statements after
returns, but there are a few others too.

[1] https://github.com/mgechev/revive?tab=readme-ov-file#recommended-configuration
2025-06-30 19:13:20 +02:00
Stefan Haller 7aa426fa71 Enable errorlint linter, and fix warnings 2025-06-30 18:30:11 +02:00
Stefan Haller 0471dbaa84 Enable intrange linter, and fix warnings 2025-06-30 18:30:11 +02:00
Stefan Haller 1e92d8b7f3 Enable nolintlint linter, and fix warnings 2025-06-30 18:30:11 +02:00
Stefan Haller d9e299c1df Update VS Code settings to enable staticcheck linter
... and configure it to run the same checks as golangci-lint.

It may seem redundant to have two linter checks enabled; however, there are
reasons to have both. golangci-lint is what we are running on CI, and it has a
lot more linters than gopls, so we want to run it locally too to get the same
coverage. The linter that is built into gopls is only staticcheck, so that's
redundant; but it has the advantage that the warnings appear a bit faster
(golangci-lint runs only on save, and with a bit of delay), and in particular it
provides quick fixes for some of the checks, which is quite convenient.
2025-06-30 18:30:11 +02:00
Stefan Haller 9b54dc0b28 Update VS Code settings to use golangci-lint v2
We configure it to call the same binary that the new lint script also uses (see
beginning of the branch).
2025-06-30 18:30:11 +02:00
Stefan Haller c4de94cff0 Update .golangci.yml to use version 2
The config file was migrated using `golangci-lint migrate`, and then edited by
hand to disable a few checks that we don't want.
2025-06-30 18:30:11 +02:00
Stefan Haller 92093ce56a Fix warning QF1003: could use tagged switch 2025-06-30 18:30:11 +02:00
Stefan Haller c7d0aaa786 Fix warning QF1004: could use strings.ReplaceAll instead 2025-06-30 18:30:11 +02:00
Stefan Haller 4604227238 Make stashEntryFromLine a free-standing function
It doesn't use the receiver at all, so doesn't have to be a method.

This fixes ST1016: methods on the same type should have the same receiver name.
2025-06-30 18:30:11 +02:00
Stefan Haller b7aecf5f17 Fix warning ST1016: methods on the same type should have the same receiver name 2025-06-30 18:30:11 +02:00
Stefan Haller 8c574f888c Fix warning ST1016: methods on the same type should have the same receiver name
Most methods use gui as a receiver name (120 of them), so switch the few that
use self over to that too.
2025-06-30 18:30:11 +02:00
Stefan Haller 7ebf5fff0f Provide a script to lint the whole working copy, and update make lint to use it
The script expects a golangci-lint binary in .bin; this makes it possible to
install the exact same version that also runs on CI.
2025-06-30 18:30:11 +02:00
Stefan Haller 4439531906 Show annotation information for selected tag (#4663)
- **PR Description**

In the Tags panel, show information about the selected tag above the
commit log. This is mostly useful for annotated tags.

Fixes #4659.
2025-06-30 16:14:20 +02:00
Stefan Haller e5b09f34e0 Show tag information for selected tag 2025-06-30 16:12:33 +02:00
Stefan Haller b12b1040c3 Add IsAnnotated field to models.Tag struct 2025-06-30 16:12:33 +02:00
Stefan Haller 718cbdb48c Fix branch head icon appearing at head commit when a remote or tag exists with the same name as the current branch (#4669)
- **PR Description**

Fix the problem that if the `rebase.updateRefs` git config is on, a
branch icon would appear in the commits list for the head commit as soon
as a remote or tag exists whose name is the same as the name of the
current branch.
2025-06-30 16:12:09 +02:00
Stefan Haller 03abdfcfe2 Fix the CurrentBranchName function
The function would return "head/branchname" when there was either a tag or a
remote with the same name.

While fixing this we slightly change the semantic of the function (and of
determineCheckedOutBranchName, which calls it): for a detached head it now
returns an empty string rather than the commit hash. I actually think this is
better.
2025-06-30 16:08:03 +02:00
Stefan Haller f95ac6780f Cleanup: turn around error condition
It is a common go convention for the "happy path" to be as little indented as
possible.
2025-06-30 16:08:03 +02:00
Stefan Haller 61815c10e7 Add a test showing undesired branch icon at head commit
The icon will appear when there's a tag with the same name as the current branch
(that's what we're testing here), or even when there's a remote with the same
name. I'm not adding a test for this latter case, but this was actually how I
discovered the issue.
2025-06-30 16:08:03 +02:00
Stefan Haller 7a8937279a Bump github.com/cloudflare/circl from 1.6.0 to 1.6.1 (#4633)
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl)
from 1.6.0 to 1.6.1.
2025-06-30 16:07:34 +02:00
dependabot[bot] f07c08a071 Bump github.com/cloudflare/circl from 1.6.0 to 1.6.1
Bumps [github.com/cloudflare/circl](https://github.com/cloudflare/circl) from 1.6.0 to 1.6.1.
- [Release notes](https://github.com/cloudflare/circl/releases)
- [Commits](https://github.com/cloudflare/circl/compare/v1.6.0...v1.6.1)

---
updated-dependencies:
- dependency-name: github.com/cloudflare/circl
  dependency-version: 1.6.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-30 14:00:23 +00:00
Stefan Haller 75ee875194 Show GPG error before entering commit editor when rewording non-latest commits (#4660)
- **PR Description**

When GPG signing is enabled but overrideGpg is false, attempting to
reword a non-latest commit would show the "Feature not vailable for
users using GPG" error only after the user had already entered the
commit message.

Now the error is displayed immediately when attempting to start the
reword operation, before opening the editor.

Closes #4611
2025-06-30 08:40:42 +02:00
Mostafa Khaled 9e05e4d810 fix: show GPG error before entering commit editor
Closes #4611
2025-06-30 08:35:52 +02:00
Stefan Haller ef6f96bda3 Add credential prompts for PKCS11-based SSH keys (#4646)
- **PR Description**
Similar to #2239, add credential prompts for PKCS11-based SSH keys.
OpenSSH code reference is
[here](https://github.com/openssh/openssh-portable/blob/2827b6ac304ded8f99e8fbc12e7299133fadb2c2/ssh-pkcs11.c#L263).
2025-06-20 08:26:01 +02:00
Jadeiin b5c2524d75 Add credential prompts for PKCS11-based SSH keys 2025-06-20 08:23:59 +02:00
Stefan Haller 80887f5791 Fix DEFAULT_REMOTE_ICON character code (#4653)
Unicode characters with code longer than 4 digits should be written as
`\Uffffffff` (8 digits)

Fixes #4652
2025-06-20 08:22:15 +02:00
bedlamzd b5c284db39 Fix DEFAULT_REMOTE_ICON character code
Unicode characters with code longer than 4 digits should be written as
`\Uffffffff` (8 digits)

Fixes #4652
2025-06-20 00:03:53 +02:00
Stefan Haller 159efdef9f Fix moving a custom patch from the very first commit of the history to a later commit (#4631)
- **PR Description**

Moving a custom patch from the very first commit of the history to a
later commit would crash with an index-out-of-range error.

I double-checked that all other callers of
PrepareInteractiveRebaseCommand already call getBaseHashOrRoot, so this
was the only one that was broken.

I decided not to add a test for this as the scenario is not a very
common one.

Fixes #4624.
2025-06-19 18:46:28 +02:00
Stefan Haller 206283d1f7 Fix moving a custom patch from the very first commit of the history to a later commit
This would crash with an index-out-of-range error.

I double-checked that all other callers of PrepareInteractiveRebaseCommand
already call getBaseHashOrRoot, so this was the only one that was broken.

I decided not to add a test for this as the scenario is not a very common one.
2025-06-19 18:43:17 +02:00
Stefan Haller ec0f4d242e Instantiate mutexes by value (#4632)
- **PR Description**

Here's a minor cleanup: instantiate mutexes by value so that they don't
have to be initialized explicitly. It is always preferable for structs
to have a valid zero value, and this is one small step in that
direction.

I read this recommendation in the [Uber style
guide](https://github.com/uber-go/guide/blob/master/style.md#zero-value-mutexes-are-valid),
and it makes a lot of sense to me. I find most of the rest of this style
guide to be a very good read, too.
2025-06-19 18:42:56 +02:00
Stefan Haller fd270768e8 Instantiate other mutexes by value
Like in the previous commit, this is preferred because the fields don't need to
be initialized this way.
2025-06-19 18:39:46 +02:00
Stefan Haller 5ee5d42511 Instantiate Mutexes's fields by value
Instead, pass the entire Mutexes struct by pointer to controllers.

This is better because Mutexes now has a zero value and doesn't need to be
initialized.
2025-06-19 18:39:46 +02:00
Stefan Haller 4c92ffda60 Add bold style for border (#4644)
- **PR Description**

bold border looks great and makes it easy to notice the color change
2025-06-15 18:23:47 +02:00
aidancz 80bbdf41fb add bold style for border 2025-06-15 16:28:20 +02:00
Stefan Haller fb2c5ea8b6 Fix stash operations when branch named 'stash' exists (#4641)
- **PR Description**

Pretty basic fix, didn't seem to have any complications. I basically
just grepped for `stash{` and all of the references seemed like they
could benefit from the addition. I only added the refs/ prefix to the
FullRefName() method to align with other similar methods, and to make
this change not impact any user facing modals. I added one integration
test for the super obvious failure behavior. I didn't feel that it was
worth it to create duplicate integration tests for the other behaviors
of drop, apply, pop, etc. I did manually test them though. If
interested, I could add the creation of the `stash` branch to all the
existing tests of stash behavior, just to prove they continue to work
under those conditions. I just didn't do that on the first pass cause I
could see how that could take away from the core behavior the tests are
trying to demonstrate.

Fixes: https://github.com/jesseduffield/lazygit/issues/4634
2025-06-15 16:17:26 +02:00
Chris McDonnell 265557afa2 Add unambigious refs/ prefix to all stash references
Pretty basic fix, didn't seem to have any complications.
I only added the refs/ prefix to the FullRefName() method
to align with other similar methods, and to make this change
not impact any user facing modals.

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

Also adds a test demonstrating that the stash show behavior is now fixed
2025-06-15 16:14:59 +02:00
Stefan Haller aa23a6e2b5 Add option to disable warning when amending last commit (#4640)
- **PR Description**
Add an option in the User Config to disable the warning message when
amending the last commit.

Fixes #4636
2025-06-13 16:47:58 +02:00
John Hamlin 708b30ab8a Add option to disable warning when amending last commit 2025-06-13 16:34:05 +02:00
183 changed files with 2976 additions and 1668 deletions
+2 -2
View File
@@ -169,9 +169,9 @@ jobs:
with:
go-version: 1.24.x
- name: Lint
uses: golangci/golangci-lint-action@v6.5.0
uses: golangci/golangci-lint-action@v8
with:
version: v1.64.6
version: v2.2.1
- name: errors
run: golangci-lint run
if: ${{ failure() }}
+108 -34
View File
@@ -1,36 +1,110 @@
linters:
enable:
- gofumpt
- thelper
- goimports
- tparallel
- wastedassign
- unparam
- prealloc
- unconvert
- exhaustive
- makezero
- nakedret
- copyloopvar
fast: false
linters-settings:
copyloopvar:
# Check all assigning the loop variable to another variable.
# Default: false
# If true, an assignment like `a := x` will be detected as an error.
check-alias: true
exhaustive:
default-signifies-exhaustive: true
staticcheck:
# SA1019 is for checking that we're not using fields marked as deprecated
# in a comment. It decides this in a loose way so I'm silencing it. Also because
# it's tripping on our own structs.
checks: ["all", "-SA1019"]
nakedret:
# the gods will judge me but I just don't like naked returns at all
max-func-lines: 0
version: "2"
run:
go: "1.24"
timeout: 10m
linters:
enable:
- copyloopvar
- errorlint
- exhaustive
- intrange
- makezero
- nakedret
- nolintlint
- prealloc
- revive
- thelper
- tparallel
- unconvert
- unparam
- wastedassign
settings:
copyloopvar:
check-alias: true
exhaustive:
default-signifies-exhaustive: true
nakedret:
# the gods will judge me but I just don't like naked returns at all
max-func-lines: 0
staticcheck:
checks:
- all
# SA1019 is for checking that we're not using fields marked as
# deprecated in a comment. It decides this in a loose way so I'm
# silencing it. Also because it's tripping on our own structs.
- -SA1019
# ST1003 complains about names like remoteUrl or itemId (should be
# remoteURL and itemID). While I like these suggestions, it also
# complains about enum constants that are all caps, and we use these and
# I like them, and also about camelCase identifiers that contain an
# underscore, which we also use in a few places. Since it can't be
# configured to ignore specific cases, and I don't want to use nolint
# comments in the code, we have to disable it altogether.
- -ST1003 # Poorly chosen identifier
# Probably a good idea, but we first have to review our error reporting
# strategy to be able to use it everywhere.
- -ST1005 # Error strings should not be capitalized
# Many of our classes use self as a receiver name, and we think that's fine.
- -ST1006 # Use of self or this as receiver name
# De Morgan's law suggests to replace `!(a && b)` with `!a || !b`; but
# sometimes I find one more readable than the other, so I want to decide
# that myself.
- -QF1001 # De Morgan's law
# QF1003 is about using a tagged switch instead of an if-else chain. In
# many cases this is a useful suggestion; however, sometimes the change
# is only possible by adding a default case to the switch (when there
# was no `else` block in the original code), in which case I don't find
# it to be an improvement.
- -QF1003 # Could replace with tagged switch
# We need to review our use of embedded fields. I suspect that in some
# cases the fix is not to remove the selector for the embedded field,
# but to turn the embedded field into a named field.
- -QF1008 # Could remove embedded field from selector
# The following checks are all disabled by default in golangci-lint, but
# we disable them again explicitly here to make it easier to keep this
# list in sync with the gopls config in .vscode/settings.json.
- -ST1000, # At least one file in a package should have a package comment
- -ST1020, # The documentation of an exported function should start with the function's name
- -ST1021, # The documentation of an exported type should start with type's name
- -ST1022, # The documentation of an exported variable or constant should start with variable's name
dot-import-whitelist:
- github.com/jesseduffield/lazygit/pkg/integration/components
revive:
severity: warning
rules:
- name: atomic
- name: context-as-argument
- name: context-keys-type
- name: error-naming
- name: var-declaration
- name: package-comments
- name: range
- name: time-naming
- name: indent-error-flow
- name: errorf
- name: superfluous-else
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- vendor/
formatters:
enable:
- gofumpt
- goimports
exclusions:
generated: lax
paths:
- vendor/
+24
View File
@@ -1,5 +1,29 @@
{
"gopls": {
"formatting.gofumpt": true,
"ui.diagnostic.staticcheck": true,
"ui.diagnostic.analyses": {
// This list must match the one in .golangci.yml
"SA1019": false,
"ST1003": false,
"ST1005": false,
"ST1006": false,
"QF1001": false,
"QF1003": false,
"QF1008": false,
"ST1000": false,
"ST1020": false,
"ST1021": false,
"ST1022": false,
// Dot imports; this warning is enabled in .golangci.yml, but with an
// extra dot-import-whitelist config. Because I couldn't figure out how to
// specify that extra config for gopls, I'm disabling the check altogether
// here.
"ST1001": false,
},
},
"go.alternateTools": {
"golangci-lint-v2": "${workspaceFolder}/.bin/golangci-lint",
},
"go.lintTool": "golangci-lint-v2",
}
+6 -3
View File
@@ -24,22 +24,24 @@
{
"label": "Run current file integration test",
"type": "shell",
"command": "go generate pkg/integration/tests/tests.go && go run cmd/integration_test/main.go cli ${relativeFile}",
"command": "go run cmd/integration_test/main.go cli ${relativeFile}",
"problemMatcher": [],
"group": {
"kind": "test",
"isDefault": true
},
"presentation": {
"clear": true,
"focus": true
}
},
{
"label": "Run current file integration test (slow)",
"type": "shell",
"command": "go generate pkg/integration/tests/tests.go && go run cmd/integration_test/main.go cli --slow ${relativeFile}",
"command": "go run cmd/integration_test/main.go cli --slow ${relativeFile}",
"problemMatcher": [],
"group": {
"clear": true,
"kind": "test",
},
"presentation": {
@@ -49,12 +51,13 @@
{
"label": "Run current file integration test (sandbox)",
"type": "shell",
"command": "go generate pkg/integration/tests/tests.go && go run cmd/integration_test/main.go cli --sandbox ${relativeFile}",
"command": "go run cmd/integration_test/main.go cli --sandbox ${relativeFile}",
"problemMatcher": [],
"group": {
"kind": "test",
},
"presentation": {
"clear": true,
"focus": true
}
},
+1 -1
View File
@@ -40,7 +40,7 @@ format:
.PHONY: lint
lint:
golangci-lint run
./scripts/lint.sh
# For more details about integration test, see https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md.
.PHONY: integration-test-tui
+1 -1
View File
@@ -194,7 +194,7 @@ Say you're on a feature branch that was itself branched off of the develop branc
### Undo
You can undo the last action by pressing 'z' and redo with `ctrl+z`. Here we drop a couple of commits and then undo the actions.
You can undo the last action by pressing `z` and redo with `ctrl+z`. Here we drop a couple of commits and then undo the actions.
Undo uses the reflog which is specific to commits and branches so we can't undo changes to the working tree or stash.
[More info](/docs/Undoing.md)
+7 -1
View File
@@ -72,6 +72,9 @@ gui:
# When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.
mouseEvents: true
# If true, do not show a warning when amending a commit.
skipAmendWarning: false
# If true, do not show a warning when discarding changes in the staging view.
skipDiscardChangeWarning: false
@@ -113,6 +116,9 @@ gui:
# paragraphs of markdown text.
wrapLinesInStagingView: true
# If true, hunk selection mode will be enabled by default when entering the staging view.
useHunkModeInStagingView: false
# One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
language: auto
@@ -245,7 +251,7 @@ gui:
screenMode: normal
# Window border style.
# One of 'rounded' (default) | 'single' | 'double' | 'hidden'
# One of 'rounded' (default) | 'single' | 'double' | 'hidden' | 'bold'
border: rounded
# If true, show a seriously epic explosion animation when nuking the working tree.
+309 -294
View File
@@ -1,346 +1,314 @@
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
# Lazygit キーバインド
# 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を意味します_
## グローバルキーバインド
| Key | Action | Info |
|-----|--------|-------------|
| `` <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 | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` p `` | Pull | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` ) `` | 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 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 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`. |
| `` + `` | 次のスクリーンモード (normal/half/fullscreen) | |
| `` _ `` | 前のスクリーンモード | |
| `` ? `` | メニューを開く | |
| `` <c-s> `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. |
| `` W `` | 差分メニューを開く | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` <c-e> `` | 差分メニューを開く | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` <c-r> `` | 最近のリポジトリをチェックアウト | |
| `` <pgup> (fn+up/shift+k) `` | メインウィンドウを上にスクロール | |
| `` <pgdown> (fn+down/shift+j) `` | メインウィンドウを下にスクロール | |
| `` @ `` | コマンドログオプションを表示 | コマンドログのオプションを表示します(例:コマンドログの表示/非表示、コマンドログへのフォーカスなど)。 |
| `` P `` | プッシュ | 現在のブランチを対応するアップストリームブランチにプッシュします。アップストリームが設定されていない場合、アップストリームブランチの設定を求められます。 |
| `` p `` | プル | 現在のブランチのリモートから変更をプルします。アップストリームが設定されていない場合、アップストリームブランチの設定を求められます。 |
| `` ) `` | リネーム検出の類似度しきい値を上げる | ファイルの削除と追加をリネーム(ファイル名変更)と判断するための類似度しきい値を上げます。値を上げると、より似ているファイルのみがリネームと認識されます。 |
| `` ( `` | リネーム検出の類似度しきい値を下げる | ファイルの削除と追加をリネーム(ファイル名変更)と判断するための類似度しきい値を下げます。値を下げると、より多くのファイルペアがリネームと認識されます。 |
| `` } `` | 差分コンテキストサイズを増やす | 差分ビューで変更の周りに表示されるコンテキストの量を増やします。 |
| `` { `` | 差分コンテキストサイズを減らす | 差分ビューで変更の周りに表示されるコンテキストの量を減らします。 |
| `` : `` | シェルコマンドを実行 | 実行するシェルコマンドを入力するプロンプトを表示します。 |
| `` <c-p> `` | カスタムパッチオプションを表示 | |
| `` m `` | マージ/リベースオプションを表示 | 現在のマージ/リベースを中止/継続/スキップするオプションを表示します。 |
| `` R `` | 更新 | Gitの状態を更新します(`git status`、`git branch`などをバックグラウンドで実行してパネルの内容を更新します)。これは`git fetch`を実行しません。 |
| `` + `` | 次の画面モード(通常/半分/全画面) | |
| `` _ `` | 前の画面モード | |
| `` ? `` | キーバインディングメニューを開く | |
| `` <c-s> `` | フィルターオプションを表示 | コミットログのフィルタリングオプションを表示し、フィルタに一致するコミットのみを表示します。 |
| `` W `` | 差分オプションを表示 | 2つのrefの差分に関連するオプションを表示します(例:選択したrefとの差分表示、差分を取るrefの入力、差分方向の反転など)。 |
| `` <c-e> `` | 差分オプションを表示 | 2つのrefの差分に関連するオプションを表示します(例:選択したrefとの差分表示、差分を取るrefの入力、差分方向の反転など)。 |
| `` q `` | 終了 | |
| `` <esc> `` | キャンセル | |
| `` <c-w> `` | 空白文字の差分の表示有無を切り替え | Toggle whether or not whitespace changes are shown in the diff view. |
| `` z `` | アンドゥ (via reflog) (experimental) | 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> `` | リドゥ (via reflog) (experimental) | 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> `` | 空白表示の切り替え | 差分ビューで空白の変更を表示するかどうかを切り替えます。 |
| `` z `` | 元に戻す | 最後のgitコマンドを元に戻すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 |
| `` <c-z> `` | やり直す | 最後のgitコマンドをやり直すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 |
## 一覧パネルの操作
## リストパネルのナビゲーション
| Key | Action | Info |
|-----|--------|-------------|
| `` , `` | 前のページ | |
| `` . `` | 次のページ | |
| `` < (<home>) `` | 最上部までスクロール | |
| `` > (<end>) `` | 最下部までスクロール | |
| `` < (<home>) `` | 先頭にスクロール | |
| `` > (<end>) `` | 末尾にスクロール | |
| `` v `` | 範囲選択を切り替え | |
| `` <s-down> `` | Range select down | |
| `` <s-up> `` | Range select up | |
| `` / `` | 検索を開始 | |
| `` H `` | 左スクロール | |
| `` L `` | 右スクロール | |
| `` <s-down> `` | 範囲選択を下に | |
| `` <s-up> `` | 範囲選択を上に | |
| `` / `` | 現在のビューをテキストで検索 | |
| `` H `` | 左にスクロール | |
| `` L `` | 右にスクロール | |
| `` ] `` | 次のタブ | |
| `` [ `` | 前のタブ | |
## Secondary
| Key | Action | Info |
|-----|--------|-------------|
| `` <tab> `` | パネルを切り替え | Switch to other view (staged/unstaged changes). |
| `` <esc> `` | Exit back to side panel | |
| `` / `` | 検索を開始 | |
## Stash
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | 適用 | Apply the stash entry to your working directory. |
| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. |
| `` d `` | Drop | 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 `` | Stashを変更 | |
| `` 0 `` | Focus main view | |
| `` <enter> `` | View files | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## Sub-commits
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | コミットのhashをクリップボードにコピー | |
| `` <space> `` | チェックアウト | Checkout the selected commit as a detached HEAD. |
| `` y `` | コミットの情報をコピー | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` o `` | ブラウザでコミットを開く | |
| `` n `` | コミットにブランチを作成 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | コミットをコピー (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` * `` | Select commits of current branch | |
| `` 0 `` | Focus main view | |
| `` <enter> `` | View files | |
| `` w `` | View worktree options | |
| `` / `` | 検索を開始 | |
## 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 | |
## コミット
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | コミットのhashをクリップボードにコピー | |
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | |
| `` b `` | View bisect options | |
| `` 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 `` | エディタでコミットメッセージを編集 | |
| `` 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 `` | 選択 | Mark the selected commit to be picked (when mid-rebase). This means that the commit will be retained upon continuing the rebase. |
| `` F `` | Fixupコミットを作成 | このコミットに対するfixupコミットを作成 |
| `` S `` | Apply fixup commits | Squash all 'fixup!' commits, either above the selected commit, or all in current branch (autosquash). |
| `` <c-o> `` | コミットハッシュをクリップボードにコピー | |
| `` <c-r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | |
| `` b `` | bisectオプションを表示 | |
| `` s `` | スカッシュ | 選択したコミットをその下のコミットにスカッシュします。スカッシュとは複数のコミットを1つにまとめる操作です。選択したコミットのメッセージが下のコミットに追加されます。 |
| `` f `` | フィックスアップ | 選択したコミットをその下のコミットにマージします。フィックスアップはスカッシュと似ていますが、選択したコミットのメッセージは破棄され、下のコミットのメッセージのみが保持されます。 |
| `` r `` | メッセージ変更 | 選択したコミットのメッセージを変更します。 |
| `` R `` | エディタでメッセージ変更 | |
| `` d `` | 削除 | 選択したコミットを削除します。これはリベースを通じてブランチからコミットを削除します。コミットが後続のコミットが依存する変更を行っている場合、マージコンフリクトを解決する必要があるかもしれません。 |
| `` e `` | 編集(対話型リベースを開始) | 選択したコミットを編集します。これを使用して、選択したコミットから対話型リベースを開始します。すでにリベース中の場合、これは選択したコミットを編集用にマークし、リベースを続行すると、リベースは選択したコミットで一時停止して変更を行えるようにします。 |
| `` i `` | 対話的リベースを開始 | ブランチ上のコミットの対話的リベースを開始します。これには、HEADコミットから最初のマージコミットまたはメインブランチのコミットまでのすべてのコミットが含まれます。
選択したコミットから対話的リベースを開始したい場合は、代わりに `e` を押してください。 |
| `` p `` | ピック | 選択したコミットをピックするようにマークします(リベース中)。これは、リベースを続行すると、コミットが保持されることを意味します。 |
| `` F `` | fixupコミットを作成 | 選択したコミットに対する「fixup!」コミットを作成します。fixupコミットは、選択したコミットの修正用コミットです。後で、同じコミットで `S` を押すと、上記のすべてのfixupコミットが適用されます。 |
| `` S `` | fixupコミットを適用 | すべての「fixup!」コミットを、選択したコミットの上部または現在のブランチ内のすべてをスカッシュします(autosquash)。 |
| `` <c-j> `` | コミットを1つ下に移動 | |
| `` <c-k> `` | コミットを1つ上に移動 | |
| `` V `` | コミットを貼り付け (cherry-pick) | |
| `` 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 | ステージされた変更で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 (e.g. hash, URL, diff, message, author). |
| `` V `` | ペースト(チェリーピック) | |
| `` B `` | リベース用のベースコミットとしてマーク | 次のリベース用のベースコミットを選択します。ブランチにリベースするとき、ベースコミットより上のコミットのみが持ち込まれます。これは `git rebase --onto` コマンドを使用します。 |
| `` A `` | 修正 | ステージされた変更でコミットを修正します。選択したコミットがHEADコミットの場合、これは `git commit --amend` を実行します。それ以外の場合、コミットはリベースを通じて修正されます。 |
| `` a `` | コミット属性を修正 | コミット作者の設定/リセットまたは共同作者の設定を行います。 |
| `` t `` | 元に戻す | 選択したコミットの変更を逆に適用する、リバートコミットを作成します。 |
| `` T `` | コミットにタグを付ける | 選択したコミットを指すタグを新規作成します。タグ名とオプションの説明を入力するよう促されます。 |
| `` <c-l> `` | ログオプションを表示 | コミットログのオプションを表示します(例:並び順の変更、Gitグラフの非表示、Gitグラフ全体の表示)。 |
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したコミットをデタッチドヘッド(特定のブランチに属さない状態)としてチェックアウトします。 |
| `` y `` | コミット属性をクリップボードにコピー | コミット属性をクリップボードにコピーします(例:ハッシュ、URL、差分、メッセージ、作者)。 |
| `` o `` | ブラウザでコミットを開く | |
| `` n `` | コミットにブランチを作成 | |
| `` n `` | コミットから新しいブランチを作成 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | コミットをコピー (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` * `` | Select commits of current branch | |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:
- ソフトリセット:変更を保持し、ステージされた状態にします
- ミックスリセット:変更を保持し、ステージされていない状態にします
- ハードリセット:すべての変更を破棄します |
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
| `` * `` | 現在のブランチのコミットを選択 | |
| `` 0 `` | Focus main view | |
| `` <enter> `` | View files | |
| `` w `` | View worktree options | |
| `` / `` | 検索を開始 | |
| `` <enter> `` | ファイルを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストで検索 | |
## コミットファイル
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | ファイル名をクリップボードにコピー | |
| `` y `` | Copy to clipboard | |
| `` c `` | チェックアウト | Checkout file. This replaces the file in your working tree with the version from the selected commit. |
| `` 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 `` | 編集 | Open file in external editor. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <space> `` | Toggle file included in patch | 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> `` | Enter file / Toggle directory collapsed | 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. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` <c-o> `` | パスをクリップボードにコピー | |
| `` y `` | クリップボードにコピー | |
| `` c `` | チェックアウト(ブランチの切り替え) | ファイルをチェックアウトします。これにより、作業ツリー内のファイルが選択したコミットのバージョンに置き換えられます。 |
| `` 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> `` | ファイルに入る / ディレクトリの折りたたみを切り替える | ファイルが選択されている場合、そのファイルに入ってカスタムパッチに個々の行を追加/削除できます。ディレクトリが選択されている場合、ディレクトリを切り替えます。 |
| `` ` `` | ファイルツリービューを切り替え | ファイルビューをフラットレイアウトとツリーレイアウトの間で切り替えます。フラットレイアウトは全てのファイルパスを単一のリストで表示し、ツリーレイアウトはディレクトリごとにファイルをグループ化します。 |
| `` - `` | すべてのファイルを折りたたむ | ファイルツリー内のすべてのディレクトリを折りたたみます |
| `` = `` | すべてのファイルを展開 | ファイルツリー内のすべてのディレクトリを展開します |
| `` 0 `` | Focus main view | |
| `` / `` | 検索を開始 | |
| `` / `` | 現在のビューをテキストで検索 | |
## コミットメッセージ
## コミット概要
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 確認 | |
| `` <esc> `` | 閉じる | |
## サブコミット
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | コミットハッシュをクリップボードにコピー | |
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したコミットをデタッチドヘッド(特定のブランチに属さない状態)としてチェックアウトします。 |
| `` y `` | コミット属性をクリップボードにコピー | コミット属性をクリップボードにコピーします(例:ハッシュ、URL、差分、メッセージ、作者)。 |
| `` o `` | ブラウザでコミットを開く | |
| `` n `` | コミットから新しいブランチを作成 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:
- ソフトリセット:変更を保持し、ステージされた状態にします
- ミックスリセット:変更を保持し、ステージされていない状態にします
- ハードリセット:すべての変更を破棄します |
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
| `` <c-r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | |
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
| `` * `` | 現在のブランチのコミットを選択 | |
| `` 0 `` | Focus main view | |
| `` <enter> `` | ファイルを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストで検索 | |
## サブモジュール
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | サブモジュール名をクリップボードにコピー | |
| `` <enter> `` | Enter | サブモジュールを開く |
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. |
| `` u `` | Update | サブモジュールを更新 |
| `` n `` | サブモジュールを新規追加 | |
| `` e `` | サブモジュールのURLを更新 | |
| `` i `` | Initialize | サブモジュールを初期化 |
| `` b `` | View bulk submodule options | |
| `` / `` | Filter the current view by text | |
| `` <enter> `` | 入る | サブモジュールに入ります。サブモジュールに入った後、`<esc>`を押して親リポジトリに戻ることができます。 |
| `` d `` | 削除 | 選択したサブモジュールとそれに対応するディレクトリを削除します。 |
| `` u `` | 更新 | 選択したサブモジュールを更新します。 |
| `` n `` | 新しいサブモジュール | |
| `` e `` | サブモジュールURLを更新 | |
| `` i `` | 初期化 | 選択したサブモジュールを初期化してフェッチの準備をします。おそらく、続いて「更新」アクションを呼び出してサブモジュールをフェッチしたいでしょう。 |
| `` b `` | 一括サブモジュールオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | |
## スタッシュ
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | 適用 | スタッシュエントリをワーキングディレクトリに適用します。 |
| `` g `` | ポップ | スタッシュエントリをワーキングディレクトリに適用し、スタッシュエントリを削除します。 |
| `` d `` | 削除 | スタッシュリストからスタッシュエントリを削除します。 |
| `` n `` | 新しいブランチ | 選択したスタッシュエントリから新しいブランチを作成します。これは、スタッシュエントリが作成されたコミットをgitがチェックアウトし、そのコミットから新しいブランチを作成した後、スタッシュエントリを追加のコミットとして新しいブランチに適用することで機能します。 |
| `` r `` | スタッシュの名前を変更 | |
| `` 0 `` | Focus main view | |
| `` <enter> `` | ファイルを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | |
## ステータス
| Key | Action | Info |
|-----|--------|-------------|
| `` o `` | 設定ファイルを開く | Open file in default application. |
| `` e `` | 設定ファイルを編集 | Open file in external editor. |
| `` o `` | 設定ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
| `` e `` | 設定ファイルを編集 | 外部エディタでファイルを開きます。 |
| `` u `` | 更新を確認 | |
| `` <enter> `` | 最近使用したリポジトリに切り替え | |
| `` a `` | Show/cycle all branch logs | |
| `` <enter> `` | 最近のリポジトリをチェックアウト | |
| `` a `` | ブランチログの表示モードを順に切り替え | |
## セカンダリ
| Key | Action | Info |
|-----|--------|-------------|
| `` <tab> `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 |
| `` <esc> `` | Exit back to side panel | |
| `` / `` | 現在のビューをテキストで検索 | |
## タグ
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | Copy tag to clipboard | |
| `` <space> `` | チェックアウト | Checkout the selected 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 | 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) | |
| `` <c-o> `` | タグをクリップボードにコピー | |
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したタグをデタッチドHEADとしてチェックアウトします。 |
| `` n `` | 新しいタグを作成 | 現在のコミットから新しいタグを作成します。タグ名とオプションの説明を入力するよう促されます。 |
| `` d `` | 削除 | ローカル/リモートタグの削除オプションを表示します。 |
| `` P `` | タグをプッシュ | 選択したタグをリモートにプッシュします。リモートを選択するよう促されます。 |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:
- ソフトリセット:変更を保持し、ステージされた状態にします
- ミックスリセット:変更を保持し、ステージされていない状態にします
- ハードリセット:すべての変更を破棄します |
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
| `` 0 `` | Focus main view | |
| `` <enter> `` | コミットを閲覧 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
| `` <enter> `` | コミットを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | |
## ファイル
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | ファイル名をクリップボードにコピー | |
| `` <space> `` | ステージ/アンステージ | Toggle staged for selected file. |
| `` <c-b> `` | ファイルをフィルタ (ステージ/アンステージ) | |
| `` y `` | Copy to clipboard | |
| `` c `` | 変更をコミット | Commit staged changes. |
| `` w `` | pre-commitフックを実行せずに変更をコミット | |
| `` A `` | 最新のコミットにamend | |
| `` 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 `` | 編集 | Open file in external editor. |
| `` o `` | ファイルを開く | Open file in default application. |
| `` i `` | ファイルをignore | |
| `` r `` | ファイルをリフレッシュ | |
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
| `` S `` | View stash options | View stash options (e.g. stash all, stash staged, stash unstaged). |
| `` a `` | すべての変更をステージ/アンステージ | Toggle staged/unstaged for all files in working tree. |
| `` <enter> `` | Stage lines / Collapse directory | 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 `` | View upstream reset options | |
| `` 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 | Fetch changes from remote. |
| `` - `` | Collapse all files | Collapse all directories in the files tree |
| `` = `` | Expand all files | Expand all directories in the file tree |
| `` <c-o> `` | パスをクリップボードにコピー | |
| `` <space> `` | ステージ | 選択したファイルのステージ状態を切り替えます。 |
| `` <c-b> `` | ステータスでファイルをフィルタリング | |
| `` y `` | クリップボードにコピー | |
| `` c `` | コミット | ステージされた変更をコミットします。 |
| `` w `` | pre-commitフックなしで変更をコミット | |
| `` A `` | 直前のコミットを修正 | |
| `` C `` | Gitエディタを使用して変更をコミット | |
| `` <c-f> `` | フィックスアップのベースコミットを検索 | 現在の変更が基づいているコミットを見つけて、コミットの修正/フィックスアップを行います。これにより、ブランチのコミットを一つずつ確認して、どのコミットを修正/フィックスアップすべきかを調べる手間が省けます。詳細はドキュメントを参照: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` e `` | 編集 | 外部エディタでファイルを開きます。 |
| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
| `` i `` | ファイルを無視または除外 | |
| `` r `` | ファイルを更新 | |
| `` s `` | スタッシュ | すべての変更をスタッシュします。スタッシュの他のバリエーションについては、スタッシュオプションを表示するキーバインディングを使用してください。 |
| `` S `` | スタッシュオプションを表示 | スタッシュオプション(すべてをスタッシュ、ステージされた変更をスタッシュ、ステージされていない変更をスタッシュなど)を表示します。 |
| `` a `` | すべてステージ | ワーキングツリー内のすべてのファイルのステージ/アンステージを切り替えます。 |
| `` <enter> `` | 行をステージ / ディレクトリを折りたたむ | 選択された項目がファイルの場合、個々のハンク/行をステージできるようにステージングビューにフォーカスします。選択された項目がディレクトリの場合、ディレクトリを折りたたむ/展開します。 |
| `` d `` | 破棄 | 選択したファイルの変更を破棄するオプションを表示します。 |
| `` g `` | アップストリームへのリセットオプションを表示 | |
| `` D `` | リセット | 作業ツリーのリセットオプション(例:作業ツリーの完全破棄)を表示します。 |
| `` ` `` | ファイルツリービューを切り替え | ファイルビューをフラットレイアウトとツリーレイアウトの間で切り替えます。フラットレイアウトは全てのファイルパスを単一のリストで表示し、ツリーレイアウトはディレクトリごとにファイルをグループ化します。 |
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
| `` M `` | 外部マージツールを開く | `git mergetool`を実行します。 |
| `` f `` | フェッチ | リモートから変更をフェッチします。 |
| `` - `` | すべてのファイルを折りたたむ | ファイルツリー内のすべてのディレクトリを折りたたみます |
| `` = `` | すべてのファイルを展開 | ファイルツリー内のすべてのディレクトリを展開します |
| `` 0 `` | Focus main view | |
| `` / `` | 検索を開始 | |
| `` / `` | 現在のビューをテキストで検索 | |
## ブランチ
## メインパネル(ステージング)
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | ブランチ名をクリップボードにコピー | |
| `` i `` | Show git-flow options | |
| `` <space> `` | チェックアウト | Checkout selected item. |
| `` n `` | 新しいブランチを作成 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
| `` <left> `` | 前のハンクに移動 | |
| `` <right> `` | 次のハンクに移動 | |
| `` v `` | 範囲選択を切り替え | |
| `` a `` | ハンクを選択 | ハンク選択モードを切り替えます。 |
| `` <c-o> `` | 選択したテキストをクリップボードにコピー | |
| `` <space> `` | ステージ | 選択された部分のステージ / アンステージを切り替えます。 |
| `` d `` | 破棄 | ステージされていない変更が選択されている場合、`git reset`を使用して変更を破棄します。ステージされた変更が選択されている場合、変更をアンステージします。 |
| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
| `` e `` | ファイルを編集 | 外部エディタでファイルを開きます。 |
| `` <esc> `` | ファイルパネルに戻る | |
| `` <tab> `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 |
| `` E `` | ハンクを編集 | 選択したハンクを外部エディタで編集します。 |
| `` c `` | コミット | ステージされた変更をコミットします。 |
| `` w `` | pre-commitフックなしで変更をコミット | |
| `` C `` | Gitエディタを使用して変更をコミット | |
| `` <c-f> `` | フィックスアップのベースコミットを検索 | 現在の変更が基づいているコミットを見つけて、コミットの修正/フィックスアップを行います。これにより、ブランチのコミットを一つずつ確認して、どのコミットを修正/フィックスアップすべきかを調べる手間が省けます。詳細はドキュメントを参照: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` / `` | 現在のビューをテキストで検索 | |
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` o `` | Pull Requestを作成 | |
| `` O `` | View create pull request options | |
| `` <c-y> `` | Pull RequestのURLをクリップボードにコピー | |
| `` c `` | Checkout by name | 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 | 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 | Fast-forward selected branch from its upstream. |
| `` T `` | タグを作成 | |
| `` s `` | 並び替え | |
| `` g `` | Reset | |
| `` 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) | |
| `` 0 `` | Focus main view | |
| `` <enter> `` | コミットを閲覧 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## メインパネル (Merging)
## メインパネル(パッチ作成)
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | Pick hunk | |
| `` b `` | Pick all hunks | |
| `` <up> `` | 前のhunkを選択 | |
| `` <down> `` | 次のhunkを選択 | |
| `` <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`. |
| `` <esc> `` | ファイル一覧に戻る | |
| `` <left> `` | 前のハンクに移動 | |
| `` <right> `` | 次のハンクに移動 | |
| `` v `` | 範囲選択を切り替え | |
| `` a `` | ハンクを選択 | ハンク選択モードを切り替えます。 |
| `` <c-o> `` | 選択したテキストをクリップボードにコピー | |
| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
| `` e `` | ファイルを編集 | 外部エディタでファイルを開きます。 |
| `` <space> `` | パッチ内の行を切り替え | |
| `` <esc> `` | カスタムパッチビルダーを終了 | |
| `` / `` | 現在のビューをテキストで検索 | |
## メインパネル (Normal)
## メインパネル(マージ中)
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | ハンクを選択 | |
| `` b `` | すべてのハンクを選択 | |
| `` <up> `` | 前のハンク | |
| `` <down> `` | 次のハンク | |
| `` <left> `` | 前のコンフリクト | |
| `` <right> `` | 次のコンフリクト | |
| `` z `` | 元に戻す | 最後のマージコンフリクト解決を元に戻します。 |
| `` e `` | ファイルを編集 | 外部エディタでファイルを開きます。 |
| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
| `` M `` | 外部マージツールを開く | `git mergetool`を実行します。 |
| `` <esc> `` | ファイルパネルに戻る | |
## メインパネル(通常)
| Key | Action | Info |
|-----|--------|-------------|
| `` mouse wheel down (fn+up) `` | 下にスクロール | |
| `` mouse wheel up (fn+down) `` | 上にスクロール | |
| `` <tab> `` | パネルを切り替え | Switch to other view (staged/unstaged changes). |
| `` <tab> `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 |
| `` <esc> `` | Exit back to side panel | |
| `` / `` | 検索を開始 | |
## メインパネル (Patch Building)
| Key | Action | Info |
|-----|--------|-------------|
| `` <left> `` | 前のhunkを選択 | |
| `` <right> `` | 次のhunkを選択 | |
| `` v `` | 範囲選択を切り替え | |
| `` a `` | Hunk選択を切り替え | Toggle hunk selection mode. |
| `` <c-o> `` | 選択されたテキストをクリップボードにコピー | |
| `` o `` | ファイルを開く | Open file in default application. |
| `` e `` | ファイルを編集 | Open file in external editor. |
| `` <space> `` | 行をパッチに追加/削除 | |
| `` <esc> `` | Exit custom patch builder | |
| `` / `` | 検索を開始 | |
## メインパネル (Staging)
| Key | Action | Info |
|-----|--------|-------------|
| `` <left> `` | 前のhunkを選択 | |
| `` <right> `` | 次のhunkを選択 | |
| `` v `` | 範囲選択を切り替え | |
| `` a `` | Hunk選択を切り替え | Toggle hunk selection mode. |
| `` <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. |
| `` <esc> `` | ファイル一覧に戻る | |
| `` <tab> `` | パネルを切り替え | Switch to other view (staged/unstaged changes). |
| `` E `` | Edit hunk | Edit selected hunk in external editor. |
| `` c `` | 変更をコミット | Commit staged changes. |
| `` w `` | pre-commitフックを実行せずに変更をコミット | |
| `` 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> |
| `` / `` | 検索を開始 | |
| `` / `` | 現在のビューをテキストで検索 | |
## メニュー
@@ -348,59 +316,106 @@ Note that this disregards the selection, the new branch is always created either
|-----|--------|-------------|
| `` <enter> `` | 実行 | |
| `` <esc> `` | 閉じる | |
| `` / `` | Filter the current view by text | |
| `` / `` | 現在のビューをテキストでフィルタリング | |
## リフログ
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | コミットハッシュをクリップボードにコピー | |
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したコミットをデタッチドヘッド(特定のブランチに属さない状態)としてチェックアウトします。 |
| `` y `` | コミット属性をクリップボードにコピー | コミット属性をクリップボードにコピーします(例:ハッシュ、URL、差分、メッセージ、作者)。 |
| `` o `` | ブラウザでコミットを開く | |
| `` n `` | コミットから新しいブランチを作成 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:
- ソフトリセット:変更を保持し、ステージされた状態にします
- ミックスリセット:変更を保持し、ステージされていない状態にします
- ハードリセット:すべての変更を破棄します |
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
| `` <c-r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | |
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
| `` * `` | 現在のブランチのコミットを選択 | |
| `` 0 `` | Focus main view | |
| `` <enter> `` | コミットを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | |
## リモート
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | View branches | |
| `` n `` | リモートを新規追加 | |
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. |
| `` e `` | 編集 | リモートを編集 |
| `` f `` | Fetch | リモートをfetch |
| `` / `` | Filter the current view by text | |
| `` <enter> `` | ブランチを表示 | |
| `` n `` | 新しいリモート | |
| `` d `` | 削除 | 選択したリモートを削除します。そのリモートからのリモートブランチを追跡しているローカルブランチは影響を受けません。 |
| `` e `` | 編集 | 選択したリモートの名前またはURLを編集します。 |
| `` f `` | フェッチ | リモートリポジトリから更新をフェッチします。これにより、ローカルブランチにマージせずに新しいコミットとブランチを取得します。 |
| `` / `` | 現在のビューをテキストでフィルタリング | |
## リモートブランチ
| 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. |
| `` n `` | 新しいブランチを作成 | |
| `` M `` | 現在のブランチにマージ | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. |
| `` d `` | Delete | Delete the remote branch from the remote. |
| `` u `` | Set as upstream | Set the selected remote branch as the upstream of the checked-out branch. |
| `` s `` | 並び替え | |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したリモートブランチに基づいて新しいローカルブランチをチェックアウトするか、リモートブランチをデタッチドヘッドとしてチェックアウトします。 |
| `` n `` | 新しいブランチ | |
| `` M `` | マージ | 選択した項目を現在のブランチにマージするためのオプションを表示します(通常のマージ、スカッシュマージ) |
| `` r `` | リベース | チェックアウトしたブランチを選択したブランチ上にリベースします。 |
| `` d `` | 削除 | リモートからリモートブランチを削除します。 |
| `` u `` | アップストリームとして設定 | 選択したリモートブランチをチェックアウトされたブランチのアップストリームとして設定します。 |
| `` s `` | 並び順 | |
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:
- ソフトリセット:変更を保持し、ステージされた状態にします
- ミックスリセット:変更を保持し、ステージされていない状態にします
- ハードリセット:すべての変更を破棄します |
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
| `` 0 `` | Focus main view | |
| `` <enter> `` | コミットを閲覧 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
| `` <enter> `` | コミットを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | |
## 参照ログ
## ローカルブランチ
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | コミットのhashをクリップボードにコピー | |
| `` <space> `` | チェックアウト | Checkout the selected commit as a detached HEAD. |
| `` y `` | コミットの情報をコピー | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` o `` | ブラウザでコミットを開く | |
| `` n `` | コミットにブランチを作成 | |
| `` <c-o> `` | ブランチ名をクリップボードにコピー | |
| `` i `` | git-flowオプションを表示 | |
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択した項目をチェックアウトします。 |
| `` n `` | 新しいブランチ | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | コミットをコピー (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` * `` | Select commits of current branch | |
| `` o `` | プルリクエストを作成 | |
| `` O `` | プルリクエスト作成オプションを表示 | |
| `` <c-y> `` | プルリクエストURLをクリップボードにコピー | |
| `` c `` | 名前でチェックアウト | 名前でチェックアウトします。入力ボックスに「-」を入力すると、最後のブランチをチェックアウトすることができます。 |
| `` F `` | 強制チェックアウト | 選択したブランチを強制的にチェックアウトします。これにより、選択したブランチをチェックアウトする前にワーキングディレクトリ内のすべてのローカル変更が破棄されます。 |
| `` d `` | 削除 | ローカル/リモートブランチの削除オプションを表示します。 |
| `` r `` | リベース | チェックアウトしたブランチを選択したブランチ上にリベースします。 |
| `` M `` | マージ | 選択した項目を現在のブランチにマージするためのオプションを表示します(通常のマージ、スカッシュマージ) |
| `` f `` | ブランチを最新化(fast-forward) | 選択したブランチを対応するアップストリームの最新状態に追いつかせます(fast-forward)。 |
| `` T `` | 新しいタグを作成 | |
| `` s `` | 並び順 | |
| `` g `` | リセット | |
| `` R `` | ブランチ名を変更 | |
| `` u `` | アップストリームオプションを表示 | ブランチのアップストリームに関連するオプションを表示します(例:アップストリームの設定/解除やアップストリームへのリセット)。 |
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
| `` 0 `` | Focus main view | |
| `` <enter> `` | コミットを閲覧 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
| `` <enter> `` | コミットを表示 | |
| `` w `` | ワークツリーオプションを表示 | |
| `` / `` | 現在のビューをテキストでフィルタリング | |
## ワークツリー
| Key | Action | Info |
|-----|--------|-------------|
| `` n `` | 新しいワークツリー | |
| `` <space> `` | チェックアウト(切り替え) | 選択したワークツリーをチェックアウト(切り替え)します。 |
| `` o `` | エディタで開く | |
| `` d `` | 削除 | 選択したワークツリーを削除します。これはワークツリーのディレクトリとワークツリーに関するメタデータの両方を.gitディレクトリから削除します。 |
| `` / `` | 現在のビューをテキストでフィルタリング | |
## 確認パネル
+1 -1
View File
@@ -91,7 +91,7 @@ Veja a documentação:
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | Copy branch name to clipboard | |
| `` i `` | Show git-flow options | |
| `` i `` | Exibir opções do git-flow | |
| `` <space> `` | Verificar | Checar item selecionado |
| `` n `` | Nova branch | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
+33 -33
View File
@@ -12,15 +12,15 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| `` <pgup> (fn+up/shift+k) `` | 向上滚动主面板 | |
| `` <pgdown> (fn+down/shift+j) `` | 向下滚动主面板 | |
| `` @ `` | 打开命令日志菜单 | 查看命令日志的选项,例如显示/隐藏命令日志以及聚焦命令日志 |
| `` 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. |
| `` P `` | 推送 | 推送当前分支到它的上游。如果上游未配置,您可以在弹窗中配置上游分支。 |
| `` p `` | 拉取 | 从当前分支的远程分支获取改动。如果上游未配置,您可以在弹窗中配置上游分支。 |
| `` ) `` | 提高重命名相似度阈值 | 增加删除和添加对被视作重命名的相似度阈值。 |
| `` ( `` | 降低重命名相似度阈值 | 降低删除和添加对被视作重命名的相似度阈值。 |
| `` } `` | 扩大差异视图中显示的上下文范围 | 增加diff视图中围绕更改显示的上下文数量 |
| `` { `` | 缩小差异视图中显示的上下文范围 | 减少diff视图中围绕更改显示的上下文数量 |
| `` : `` | 执行 Shell 命令 | Bring up a prompt where you can enter a shell command to execute. |
| `` : `` | 执行 Shell 命令 | 调出可输入shell命令执行的提示符。 |
| `` <c-p> `` | 查看自定义补丁选项 | |
| `` m `` | 查看 合并/变基 选项 | 查看当前合并或变基的中止、继续、跳过选项 |
| `` m `` | 查看合并/变基选项 | 查看当前合并或变基的中止、继续、跳过选项 |
| `` R `` | 刷新 | 刷新git状态(即在后台上运行`git status`,`git branch`等命令以更新面板内容) 不会运行`git fetch` |
| `` + `` | 下一屏模式(正常/半屏/全屏) | |
| `` _ `` | 上一屏模式 | |
@@ -55,19 +55,19 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
| `` <c-o> `` | 复制提交哈希到剪贴板 | |
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD。 |
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hash、URL、diff、消息、作者)。 |
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(如hash、URL、diff、消息、作者)。 |
| `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` * `` | Select commits of current branch | |
| `` * `` | 选择当前分支的提交 | |
| `` 0 `` | Focus main view | |
| `` <enter> `` | 查看提交 | |
| `` w `` | 查看工作区选项 | |
@@ -77,19 +77,19 @@ Note that this disregards the selection, the new branch is always created either
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
| `` <c-o> `` | 复制提交哈希到剪贴板 | |
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD。 |
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hash、URL、diff、消息、作者)。 |
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(如hash、URL、diff、消息、作者)。 |
| `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` * `` | Select commits of current branch | |
| `` * `` | 选择当前分支的提交 | |
| `` 0 `` | Focus main view | |
| `` <enter> `` | 查看提交的文件 | |
| `` w `` | 查看工作区选项 | |
@@ -99,7 +99,7 @@ Note that this disregards the selection, the new branch is always created either
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将子模块名称复制到剪贴板 | |
| `` <c-o> `` | 复制子模块名称到剪贴板 | |
| `` <enter> `` | 进入 | 输入子模块 |
| `` d `` | 删除 | 删除选定的子模块及其相应的目录 |
| `` u `` | 更新 | 更新子模块 |
@@ -123,7 +123,7 @@ Note that this disregards the selection, the new branch is always created either
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
| `` <c-o> `` | 复制提交哈希到剪贴板 | |
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
| `` b `` | 查看二分查找选项 | |
| `` s `` | 压缩(Squash) | 将已选提交压缩到该提交之下。这些选定的提交的消息会附加到该提交的消息之下。 |
@@ -144,19 +144,19 @@ Note that this disregards the selection, the new branch is always created either
| `` A `` | 修补(Amend) | 用已暂存的变更来修补提交 |
| `` a `` | 修补提交属性 | 设置或重置提交的作者,或添加其他作者。 |
| `` t `` | 撤销(Revert) | 为所选提交创建还原提交,这会反向应用所选提交的更改。 |
| `` T `` | 标签提交 | 创建一个新标签指向所选提交。你可以在弹窗中输入标签名称和描述(可选)。 |
| `` T `` | 标签提交 | 创建一个新标签指向所选提交。您可以在弹窗中输入标签名称和描述(可选)。 |
| `` <c-l> `` | 打开日志菜单 | 查看提交日志的选项,例如更改排序顺序、隐藏 git graph、显示整个 git graph。 |
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD。 |
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hash、URL、diff、消息、作者)。 |
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(如hash、URL、diff、消息、作者)。 |
| `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | |
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` * `` | Select commits of current branch | |
| `` * `` | 选择当前分支的提交 | |
| `` 0 `` | Focus main view | |
| `` <enter> `` | 查看提交的文件 | |
| `` w `` | 查看工作区选项 | |
@@ -173,7 +173,7 @@ Note that this disregards the selection, the new branch is always created either
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将文件名复制到剪贴板 | |
| `` <c-o> `` | 复制路径到剪贴板 | |
| `` y `` | 复制到剪贴板 | |
| `` c `` | 检出 | 检出文件 |
| `` d `` | 删除 | 放弃对此文件的提交变更 |
@@ -193,7 +193,7 @@ Note that this disregards the selection, the new branch is always created either
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将文件名复制到剪贴板 | |
| `` <c-o> `` | 复制路径到剪贴板 | |
| `` <space> `` | 切换暂存状态 | 为选定的文件切换暂存状态 |
| `` <c-b> `` | 通过状态过滤文件 | |
| `` y `` | 复制到剪贴板 | |
@@ -226,7 +226,7 @@ Note that this disregards the selection, the new branch is always created either
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将分支名称复制到剪贴板 | |
| `` <c-o> `` | 复制分支名称到剪贴板 | |
| `` i `` | 显示 git-flow 选项 | |
| `` <space> `` | 检出 | 检出选中的项目 |
| `` n `` | 新分支 | |
@@ -235,11 +235,11 @@ Note that this disregards the selection, the new branch is always created either
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
| `` o `` | 创建拉取请求 | |
| `` O `` | 创建拉取请求选项 | |
| `` <c-y> `` | 将拉取请求 URL 复制到剪贴板 | |
| `` <c-y> `` | 复制拉取请求 URL 到剪贴板 | |
| `` c `` | 按名称检出 | 按名称检出。在输入框中,您可以输入'-' 来切换到最后一个分支。 |
| `` F `` | 强制检出 | 强制检出所选分支。这将在检出所选分支之前放弃工作目录中的所有本地更改。 |
| `` d `` | 删除 | 查看本地/远程分支的删除选项 |
| `` r `` | 将已检出的分支变基到该分支 | 将检出的分支变基到所选的分支上。 |
| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 |
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
| `` f `` | 从上游快进此分支 | 将当前分支直接移动到远程追踪分支的最新提交 |
| `` T `` | 创建标签 | |
@@ -261,7 +261,7 @@ Note that this disregards the selection, the new branch is always created either
| `` <right> `` | 选择下一个区块 | |
| `` v `` | 切换拖动选择 | |
| `` a `` | 切换选择代码块 | 切换代码块选择模式 |
| `` <c-o> `` | 将选中文本复制到剪贴板 | |
| `` <c-o> `` | 复制选中文本到剪贴板 | |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` e `` | 编辑文件 | 使用外部编辑器打开文件 |
| `` <space> `` | 添加/移除 行到补丁 | |
@@ -272,11 +272,11 @@ Note that this disregards the selection, the new branch is always created either
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将标签复制到剪贴板 | |
| `` <c-o> `` | 复制标签到剪贴板 | |
| `` <space> `` | 检出 | 检出选择的标签作为分离的HEAD |
| `` n `` | 创建标签 | 基于当前提交创建一个新标签。你将在弹窗中输入标签名称和描述(可选)。 |
| `` n `` | 创建标签 | 基于当前提交创建一个新标签。您将在弹窗中输入标签名称和描述(可选)。 |
| `` d `` | 删除 | 查看本地/远程标签的删除选项 |
| `` P `` | 推送标签 | 推送选择的标签到远端。你将在弹窗中选择一个远端。 |
| `` P `` | 推送标签 | 推送选择的标签到远端。您将在弹窗中选择一个远端。 |
| `` g `` | 重置 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` 0 `` | Focus main view | |
@@ -316,7 +316,7 @@ Note that this disregards the selection, the new branch is always created either
| `` <right> `` | 选择下一个区块 | |
| `` v `` | 切换拖动选择 | |
| `` a `` | 切换选择代码块 | 切换代码块选择模式 |
| `` <c-o> `` | 将选中文本复制到剪贴板 | |
| `` <c-o> `` | 复制选中文本到剪贴板 | |
| `` <space> `` | 切换暂存状态 | 切换行暂存状态 |
| `` d `` | 取消变更(git reset) | 当选择未暂存的变更时,使用git reset丢弃该变更。当选择已暂存的变更时,取消暂存该变更 |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
@@ -348,7 +348,7 @@ Note that this disregards the selection, the new branch is always created either
| `` e `` | 编辑配置文件 | 使用外部编辑器打开文件 |
| `` u `` | 检查更新 | |
| `` <enter> `` | 切换到最近的仓库 | |
| `` a `` | Show/cycle all branch logs | |
| `` a `` | 显示/循环所有分支日志 | |
## 确认面板
@@ -394,11 +394,11 @@ Note that this disregards the selection, the new branch is always created either
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将分支名称复制到剪贴板 | |
| `` <c-o> `` | 复制分支名称到剪贴板 | |
| `` <space> `` | 检出 | 基于当前选中的远程分支检出一个新的本地分支,或者将远程分支作分离的HEAD。 |
| `` n `` | 新分支 | |
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
| `` r `` | 将已检出的分支变基到该分支 | 将检出的分支变基到所选的分支上。 |
| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 |
| `` d `` | 删除 | 从远程删除远程分支。 |
| `` u `` | 设置为上游 | 设置为检出分支的上游 |
| `` s `` | 排序 | |
+1 -1
View File
@@ -47,7 +47,7 @@ require (
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/cloudflare/circl v1.6.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
+2 -2
View File
@@ -65,8 +65,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do=
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+2 -2
View File
@@ -86,9 +86,9 @@ func newLogger(cfg config.AppConfigurer) *logrus.Entry {
log.Fatal(err)
}
return logs.NewDevelopmentLogger(logPath)
} else {
return logs.NewProductionLogger()
}
return logs.NewProductionLogger()
}
// NewApp bootstrap a new application
+1 -2
View File
@@ -21,9 +21,8 @@ type TodoLine struct {
func (self *TodoLine) ToString() string {
if self.Action == "break" {
return self.Action + "\n"
} else {
return self.Action + " " + self.Commit.Hash() + " " + self.Commit.Name + "\n"
}
return self.Action + " " + self.Commit.Hash() + " " + self.Commit.Name + "\n"
}
func TodoLinesToString(todoLines []TodoLine) string {
+2 -2
View File
@@ -28,7 +28,7 @@ func (self *gitCmdObjRunner) Run(cmdObj *oscommands.CmdObj) error {
func (self *gitCmdObjRunner) RunWithOutput(cmdObj *oscommands.CmdObj) (string, error) {
var output string
var err error
for i := 0; i < RetryCount; i++ {
for range RetryCount {
newCmdObj := cmdObj.Clone()
output, err = self.innerRunner.RunWithOutput(newCmdObj)
@@ -47,7 +47,7 @@ func (self *gitCmdObjRunner) RunWithOutput(cmdObj *oscommands.CmdObj) (string, e
func (self *gitCmdObjRunner) RunWithOutputs(cmdObj *oscommands.CmdObj) (string, string, error) {
var stdout, stderr string
var err error
for i := 0; i < RetryCount; i++ {
for range RetryCount {
newCmdObj := cmdObj.Clone()
stdout, stderr, err = self.innerRunner.RunWithOutputs(newCmdObj)
+7 -8
View File
@@ -102,19 +102,18 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
}, nil
}
// CurrentBranchName get name of current branch
// CurrentBranchName get name of current branch. Returns empty string if HEAD is detached.
func (self *BranchCommands) CurrentBranchName() (string, error) {
cmdArgs := NewGitCmd("rev-parse").
Arg("--abbrev-ref").
Arg("--verify").
Arg("HEAD").
cmdArgs := NewGitCmd("branch").
Arg("--show-current").
ToArgv()
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err == nil {
return strings.TrimSpace(output), nil
if err != nil {
return "", err
}
return "", err
return strings.TrimSpace(output), nil
}
// LocalDelete delete branch locally
+1 -2
View File
@@ -356,9 +356,8 @@ func parseDifference(track string, regexStr string) string {
match := re.FindStringSubmatch(track)
if len(match) > 1 {
return match[1]
} else {
return "0"
}
return "0"
}
// TODO: only look at the new reflog commits, and otherwise store the recencies in
+1 -2
View File
@@ -149,9 +149,8 @@ func (self *CommitCommands) CommitEditorCmdObj() *oscommands.CmdObj {
func (self *CommitCommands) signoffFlag() string {
if self.UserConfig().Git.Commit.SignOff {
return "--signoff"
} else {
return ""
}
return ""
}
func (self *CommitCommands) GetCommitMessage(commitHash string) (string, error) {
@@ -16,16 +16,16 @@ import (
"github.com/stretchr/testify/assert"
)
var commitsOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|>|better typing for rebase mode
var commitsOutput = strings.ReplaceAll(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|>|better typing for rebase mode
b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164|1640824515|Jesse Duffield|jessedduffield@gmail.com|origin/better-tests|e94e8fc5b6fab4cb755f|>|fix logging
e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c|1640823749|Jesse Duffield|jessedduffield@gmail.com|tag: 123, tag: 456|d8084cd558925eb7c9c3|>|refactor
d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield|jessedduffield@gmail.com||65f910ebd85283b5cce9|>|WIP
65f910ebd85283b5cce9bf67d03d3f1a9ea3813a|1640821275|Jesse Duffield|jessedduffield@gmail.com||26c07b1ab33860a1a759|>|WIP
26c07b1ab33860a1a7591a0638f9925ccf497ffa|1640750752|Jesse Duffield|jessedduffield@gmail.com||3d4470a6c072208722e5|>|WIP
3d4470a6c072208722e5ae9a54bcb9634959a1c5|1640748818|Jesse Duffield|jessedduffield@gmail.com||053a66a7be3da43aacdc|>|WIP
053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|jessedduffield@gmail.com||985fe482e806b172aea4|>|refactoring the config struct`, "|", "\x00", -1)
053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|jessedduffield@gmail.com||985fe482e806b172aea4|>|refactoring the config struct`, "|", "\x00")
var singleCommitOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|>|better typing for rebase mode`, "|", "\x00", -1)
var singleCommitOutput = strings.ReplaceAll(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|>|better typing for rebase mode`, "|", "\x00")
func TestGetCommits(t *testing.T) {
type scenario struct {
+1 -1
View File
@@ -97,7 +97,7 @@ func buildGitCommon(deps commonDeps) *GitCommon {
func buildRepo() *gogit.Repository {
// TODO: think of a way to actually mock this out
var repo *gogit.Repository = nil
var repo *gogit.Repository
return repo
}
+4 -4
View File
@@ -110,8 +110,8 @@ type FileDiff struct {
LinesDeleted int
}
func (fileLoader *FileLoader) getFileDiffs() (map[string]FileDiff, error) {
diffs, err := fileLoader.gitDiffNumStat()
func (self *FileLoader) getFileDiffs() (map[string]FileDiff, error) {
diffs, err := self.gitDiffNumStat()
if err != nil {
return nil, err
}
@@ -157,8 +157,8 @@ type FileStatus struct {
PreviousPath string
}
func (fileLoader *FileLoader) gitDiffNumStat() (string, error) {
return fileLoader.cmd.New(
func (self *FileLoader) gitDiffNumStat() (string, error) {
return self.cmd.New(
NewGitCmd("diff").
Arg("--numstat").
Arg("-z").
@@ -32,9 +32,8 @@ func (self *GitCommandBuilder) ArgIf(condition bool, ifTrue ...string) *GitComma
func (self *GitCommandBuilder) ArgIfElse(condition bool, ifTrue string, ifFalse string) *GitCommandBuilder {
if condition {
return self.Arg(ifTrue)
} else {
return self.Arg(ifFalse)
}
return self.Arg(ifFalse)
}
func (self *GitCommandBuilder) Config(value string) *GitCommandBuilder {
+1 -2
View File
@@ -21,7 +21,7 @@ type MainBranches struct {
previousMainBranches []string
cmd oscommands.ICmdObjBuilder
mutex *deadlock.Mutex
mutex deadlock.Mutex
}
func NewMainBranches(
@@ -32,7 +32,6 @@ func NewMainBranches(
c: cmn,
existingMainBranches: nil,
cmd: cmd,
mutex: &deadlock.Mutex{},
}
}
+4 -3
View File
@@ -1,6 +1,7 @@
package git_commands
import (
"fmt"
"path/filepath"
"time"
@@ -162,7 +163,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
self.os.LogCommand(logTodoChanges(changes), false)
err := self.rebase.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: commits[baseIndex].Hash(),
baseHashOrRoot: getBaseHashOrRoot(commits, baseIndex),
overrideEditor: true,
instruction: daemon.NewChangeTodoActionsInstruction(changes),
}).Run()
@@ -218,7 +219,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitIdx int, stash bool) error {
if stash {
if err := self.stash.Push(self.Tr.StashPrefix + commits[commitIdx].Hash()); err != nil {
if err := self.stash.Push(fmt.Sprintf(self.Tr.AutoStashForMovingPatchToIndex, commits[commitIdx].ShortHash())); err != nil {
return err
}
}
@@ -259,7 +260,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId
}
if stash {
if err := self.stash.Apply(0); err != nil {
if err := self.stash.Pop(0); err != nil {
return err
}
}
+7 -6
View File
@@ -35,6 +35,8 @@ func NewRebaseCommands(
}
func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, summary string, description string) error {
// This check is currently unreachable (handled in LocalCommitsController.reword),
// but kept as a safeguard in case this method is used elsewhere.
if self.config.NeedsGpgSubprocessForCommit() {
return errors.New(self.Tr.DisabledForGPG)
}
@@ -325,9 +327,8 @@ func (self *RebaseCommands) MoveFixupCommitDown(commits []*models.Commit, target
func todoFromCommit(commit *models.Commit) utils.Todo {
if commit.Action == todo.UpdateRef {
return utils.Todo{Ref: commit.Name}
} else {
return utils.Todo{Hash: commit.Hash()}
}
return utils.Todo{Hash: commit.Hash()}
}
// Sets the action for the given commits in the git-rebase-todo file
@@ -410,9 +411,9 @@ func (self *RebaseCommands) BeginInteractiveRebaseForCommit(
instruction: daemon.NewInsertBreakInstruction(),
keepCommitsThatBecomeEmpty: keepCommitsThatBecomeEmpty,
}).Run()
} else {
return self.BeginInteractiveRebaseForCommitRange(commits, commitIndex, commitIndex, keepCommitsThatBecomeEmpty)
}
return self.BeginInteractiveRebaseForCommitRange(commits, commitIndex, commitIndex, keepCommitsThatBecomeEmpty)
}
func (self *RebaseCommands) BeginInteractiveRebaseForCommitRange(
@@ -572,7 +573,7 @@ func getBaseHashOrRoot(commits []*models.Commit, index int) string {
// at time of writing)
if index < len(commits) {
return commits[index].Hash()
} else {
return "--root"
}
return "--root"
}
@@ -14,12 +14,12 @@ import (
"github.com/stretchr/testify/assert"
)
var reflogOutput = strings.Replace(`c3c4b66b64c97ffeecde|1643150483|checkout: moving from A to B|51baa8c1
var reflogOutput = strings.ReplaceAll(`c3c4b66b64c97ffeecde|1643150483|checkout: moving from A to B|51baa8c1
c3c4b66b64c97ffeecde|1643150483|checkout: moving from B to A|51baa8c1
c3c4b66b64c97ffeecde|1643150483|checkout: moving from A to B|51baa8c1
c3c4b66b64c97ffeecde|1643150483|checkout: moving from master to A|51baa8c1
f4ddf2f0d4be4ccc7efa|1643149435|checkout: moving from A to master|51baa8c1
`, "|", "\x00", -1)
`, "|", "\x00")
func TestGetReflogCommits(t *testing.T) {
type scenario struct {
+1 -1
View File
@@ -21,7 +21,7 @@ type RepoPaths struct {
isBareRepo bool
}
var gitPathFormatVersion GitVersion = GitVersion{2, 31, 0, ""}
var gitPathFormatVersion = GitVersion{2, 31, 0, ""}
// Path to the current worktree. If we're in the main worktree, this will
// be the same as RepoPath()
+1 -3
View File
@@ -184,9 +184,7 @@ func TestGetRepoPaths(t *testing.T) {
Expected: nil,
Err: func(getRevParseArgs argFn) error {
args := strings.Join(getRevParseArgs(), " ")
return errors.New(
fmt.Sprintf("'git %v --show-toplevel --absolute-git-dir --git-common-dir --is-bare-repository --show-superproject-working-tree' failed: fatal: invalid gitfile format: /path/to/repo/worktree2/.git", args),
)
return fmt.Errorf("'git %v --show-toplevel --absolute-git-dir --git-common-dir --is-bare-repository --show-superproject-working-tree' failed: fatal: invalid gitfile format: /path/to/repo/worktree2/.git", args)
},
},
}
+6 -6
View File
@@ -32,21 +32,21 @@ func (self *StashCommands) DropNewest() error {
}
func (self *StashCommands) Drop(index int) error {
cmdArgs := NewGitCmd("stash").Arg("drop", fmt.Sprintf("stash@{%d}", index)).
cmdArgs := NewGitCmd("stash").Arg("drop", fmt.Sprintf("refs/stash@{%d}", index)).
ToArgv()
return self.cmd.New(cmdArgs).Run()
}
func (self *StashCommands) Pop(index int) error {
cmdArgs := NewGitCmd("stash").Arg("pop", fmt.Sprintf("stash@{%d}", index)).
cmdArgs := NewGitCmd("stash").Arg("pop", fmt.Sprintf("refs/stash@{%d}", index)).
ToArgv()
return self.cmd.New(cmdArgs).Run()
}
func (self *StashCommands) Apply(index int) error {
cmdArgs := NewGitCmd("stash").Arg("apply", fmt.Sprintf("stash@{%d}", index)).
cmdArgs := NewGitCmd("stash").Arg("apply", fmt.Sprintf("refs/stash@{%d}", index)).
ToArgv()
return self.cmd.New(cmdArgs).Run()
@@ -90,7 +90,7 @@ func (self *StashCommands) ShowStashEntryCmdObj(index int) *oscommands.CmdObj {
Arg(fmt.Sprintf("--unified=%d", self.AppState.DiffContextSize)).
ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
Arg(fmt.Sprintf("--find-renames=%d%%", self.AppState.RenameSimilarityThreshold)).
Arg(fmt.Sprintf("stash@{%d}", index)).
Arg(fmt.Sprintf("refs/stash@{%d}", index)).
Dir(self.repoPaths.worktreePath).
ToArgv()
@@ -152,7 +152,7 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
}
if err := self.cmd.New(
NewGitCmd("stash").Arg("apply", "stash@{1}").ToArgv(),
NewGitCmd("stash").Arg("apply", "refs/stash@{1}").ToArgv(),
).Run(); err != nil {
return err
}
@@ -165,7 +165,7 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
}
if err := self.cmd.New(
NewGitCmd("stash").Arg("drop", "stash@{1}").ToArgv(),
NewGitCmd("stash").Arg("drop", "refs/stash@{1}").ToArgv(),
).Run(); err != nil {
return err
}
+3 -3
View File
@@ -53,7 +53,7 @@ outer:
if err != nil {
return self.getUnfilteredStashEntries()
}
currentStashEntry = self.stashEntryFromLine(lines[i], idx)
currentStashEntry = stashEntryFromLine(lines[i], idx)
for i+1 < len(lines) && !isAStash(lines[i+1]) {
i++
if lines[i] == filterPath {
@@ -70,11 +70,11 @@ func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
rawString, _ := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
return lo.Map(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
return self.stashEntryFromLine(line, index)
return stashEntryFromLine(line, index)
})
}
func (c *StashLoader) stashEntryFromLine(line string, index int) *models.StashEntry {
func stashEntryFromLine(line string, index int) *models.StashEntry {
model := &models.StashEntry{
Name: line,
Index: index,
+9 -9
View File
@@ -10,7 +10,7 @@ import (
func TestStashDrop(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"stash", "drop", "stash@{1}"}, "Dropped refs/stash@{1} (98e9cca532c37c766107093010c72e26f2c24c04)\n", nil)
ExpectGitArgs([]string{"stash", "drop", "refs/stash@{1}"}, "Dropped refs/stash@{1} (98e9cca532c37c766107093010c72e26f2c24c04)\n", nil)
instance := buildStashCommands(commonDeps{runner: runner})
assert.NoError(t, instance.Drop(1))
@@ -19,7 +19,7 @@ func TestStashDrop(t *testing.T) {
func TestStashApply(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"stash", "apply", "stash@{1}"}, "", nil)
ExpectGitArgs([]string{"stash", "apply", "refs/stash@{1}"}, "", nil)
instance := buildStashCommands(commonDeps{runner: runner})
assert.NoError(t, instance.Apply(1))
@@ -28,7 +28,7 @@ func TestStashApply(t *testing.T) {
func TestStashPop(t *testing.T) {
runner := oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"stash", "pop", "stash@{1}"}, "", nil)
ExpectGitArgs([]string{"stash", "pop", "refs/stash@{1}"}, "", nil)
instance := buildStashCommands(commonDeps{runner: runner})
assert.NoError(t, instance.Pop(1))
@@ -113,7 +113,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 3,
similarityThreshold: 50,
ignoreWhitespace: false,
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--find-renames=50%", "stash@{5}"},
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--find-renames=50%", "refs/stash@{5}"},
},
{
testName: "Show diff with custom context size",
@@ -121,7 +121,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 77,
similarityThreshold: 50,
ignoreWhitespace: false,
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=77", "--find-renames=50%", "stash@{5}"},
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=77", "--find-renames=50%", "refs/stash@{5}"},
},
{
testName: "Show diff with custom similarity threshold",
@@ -129,7 +129,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 3,
similarityThreshold: 33,
ignoreWhitespace: false,
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--find-renames=33%", "stash@{5}"},
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--find-renames=33%", "refs/stash@{5}"},
},
{
testName: "Default case",
@@ -137,7 +137,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
contextSize: 3,
similarityThreshold: 50,
ignoreWhitespace: true,
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--ignore-all-space", "--find-renames=50%", "stash@{5}"},
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--ignore-all-space", "--find-renames=50%", "refs/stash@{5}"},
},
}
@@ -177,7 +177,7 @@ func TestStashRename(t *testing.T) {
message: "New message",
expectedHashCmd: []string{"rev-parse", "refs/stash@{3}"},
hashResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n",
expectedDropCmd: []string{"stash", "drop", "stash@{3}"},
expectedDropCmd: []string{"stash", "drop", "refs/stash@{3}"},
expectedStoreCmd: []string{"stash", "store", "-m", "New message", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd"},
},
{
@@ -186,7 +186,7 @@ func TestStashRename(t *testing.T) {
message: "",
expectedHashCmd: []string{"rev-parse", "refs/stash@{4}"},
hashResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n",
expectedDropCmd: []string{"stash", "drop", "stash@{4}"},
expectedDropCmd: []string{"stash", "drop", "refs/stash@{4}"},
expectedStoreCmd: []string{"stash", "store", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd"},
},
}
+1 -2
View File
@@ -49,9 +49,8 @@ func (self *SubmoduleCommands) GetConfigs(parentModule *models.SubmoduleConfig)
if len(matches) > 0 {
return matches[1], true
} else {
return "", false
}
return "", false
}
configs := []*models.SubmoduleConfig{}
+17
View File
@@ -57,3 +57,20 @@ func (self *TagCommands) Push(task gocui.Task, remoteName string, tagName string
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
}
// Return info about an annotated tag in the format:
//
// Tagger: tagger name <tagger email>
// TaggerDate: tagger date
//
// Tag message
//
// Should only be called for annotated tags.
func (self *TagCommands) ShowAnnotationInfo(tagName string) (string, error) {
cmdArgs := NewGitCmd("for-each-ref").
Arg("--format=Tagger: %(taggername) %(taggeremail)%0aTaggerDate: %(taggerdate)%0a%0a%(contents)").
Arg("refs/tags/" + tagName).
ToArgv()
return self.cmd.New(cmdArgs).RunWithOutput()
}
+18 -14
View File
@@ -1,7 +1,7 @@
package git_commands
import (
"regexp"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
@@ -26,9 +26,13 @@ func NewTagLoader(
}
func (self *TagLoader) GetTags() ([]*models.Tag, error) {
// get remote branches, sorted by creation date (descending)
// get tags, sorted by creation date (descending)
// see: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---sortltkeygt
cmdArgs := NewGitCmd("tag").Arg("--list", "-n", "--sort=-creatordate").ToArgv()
cmdArgs := NewGitCmd("for-each-ref").
Arg("--sort=-creatordate").
Arg("--format=%(refname)%00%(objecttype)%00%(contents:subject)").
Arg("refs/tags").
ToArgv()
tagsOutput, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil {
return nil, err
@@ -36,20 +40,20 @@ func (self *TagLoader) GetTags() ([]*models.Tag, error) {
split := utils.SplitLines(tagsOutput)
lineRegex := regexp.MustCompile(`^([^\s]+)(\s+)?(.*)$`)
tags := lo.Map(split, func(line string, _ int) *models.Tag {
matches := lineRegex.FindStringSubmatch(line)
tagName := matches[1]
message := ""
if len(matches) > 3 {
message = matches[3]
tags := lo.FilterMap(split, func(line string, _ int) (*models.Tag, bool) {
fields := strings.SplitN(line, "\x00", 3)
if len(fields) != 3 {
return nil, false
}
tagName := fields[0]
objectType := fields[1]
message := fields[2]
return &models.Tag{
Name: tagName,
Message: message,
}
Name: strings.TrimPrefix(tagName, "refs/tags/"),
Message: message,
IsAnnotated: objectType == "tag",
}, true
})
return tags, nil
+8 -9
View File
@@ -9,10 +9,9 @@ import (
"github.com/stretchr/testify/assert"
)
const tagsOutput = `tag1 this is my message
tag2
tag3 this is my other message
`
const tagsOutput = "refs/tags/tag1\x00tag\x00this is my message\n" +
"refs/tags/tag2\x00commit\x00\n" +
"refs/tags/tag3\x00tag\x00this is my other message\n"
func TestGetTags(t *testing.T) {
type scenario struct {
@@ -26,18 +25,18 @@ func TestGetTags(t *testing.T) {
{
testName: "should return no tags if there are none",
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"tag", "--list", "-n", "--sort=-creatordate"}, "", nil),
ExpectGitArgs([]string{"for-each-ref", "--sort=-creatordate", "--format=%(refname)%00%(objecttype)%00%(contents:subject)", "refs/tags"}, "", nil),
expectedTags: []*models.Tag{},
expectedError: nil,
},
{
testName: "should return tags if present",
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs([]string{"tag", "--list", "-n", "--sort=-creatordate"}, tagsOutput, nil),
ExpectGitArgs([]string{"for-each-ref", "--sort=-creatordate", "--format=%(refname)%00%(objecttype)%00%(contents:subject)", "refs/tags"}, tagsOutput, nil),
expectedTags: []*models.Tag{
{Name: "tag1", Message: "this is my message"},
{Name: "tag2", Message: ""},
{Name: "tag3", Message: "this is my other message"},
{Name: "tag1", Message: "this is my message", IsAnnotated: true},
{Name: "tag2", Message: "", IsAnnotated: false},
{Name: "tag3", Message: "this is my other message", IsAnnotated: true},
},
expectedError: nil,
},
+1 -2
View File
@@ -66,9 +66,8 @@ func (self *WorkingTreeCommands) UnstageAll() error {
func (self *WorkingTreeCommands) UnStageFile(paths []string, tracked bool) error {
if tracked {
return self.UnstageTrackedFiles(paths)
} else {
return self.UnstageUntrackedFiles(paths)
}
return self.UnstageUntrackedFiles(paths)
}
func (self *WorkingTreeCommands) UnstageTrackedFiles(paths []string) error {
+3 -1
View File
@@ -2,6 +2,7 @@ package git_config
import (
"bytes"
"errors"
"fmt"
"io"
"os/exec"
@@ -39,7 +40,8 @@ func runGitConfigCmd(cmd *exec.Cmd) (string, error) {
cmd.Stderr = io.Discard
err := cmd.Run()
if exitError, ok := err.(*exec.ExitError); ok {
var exitError *exec.ExitError
if errors.As(err, &exitError) {
if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok {
if waitStatus.ExitStatus() == 1 {
return "", fmt.Errorf("the key is not found for %s", cmd.Args)
@@ -46,9 +46,8 @@ func (self *HostingServiceMgr) GetPullRequestURL(from string, to string) (string
if to == "" {
return gitService.getPullRequestURLIntoDefaultBranch(url.QueryEscape(from)), nil
} else {
return gitService.getPullRequestURLIntoTargetBranch(url.QueryEscape(from), url.QueryEscape(to)), nil
}
return gitService.getPullRequestURLIntoTargetBranch(url.QueryEscape(from), url.QueryEscape(to)), nil
}
func (self *HostingServiceMgr) GetCommitURL(commitHash string) (string, error) {
+1 -1
View File
@@ -10,7 +10,7 @@ type StashEntry struct {
}
func (s *StashEntry) FullRefName() string {
return s.RefName()
return "refs/" + s.RefName()
}
func (s *StashEntry) RefName() string {
+4
View File
@@ -3,9 +3,13 @@ package models
// Tag : A git tag
type Tag struct {
Name string
// this is either the first line of the message of an annotated tag, or the
// first line of a commit message for a lightweight tag
Message string
// true if this is an annotated tag, false if it's a lightweight tag
IsAnnotated bool
}
func (t *Tag) FullRefName() string {
@@ -391,6 +391,7 @@ func (self *cmdObjRunner) getCheckForCredentialRequestFunc() func([]byte) (Crede
`Username\s*for\s*'.+':`: Username,
`Enter\s*passphrase\s*for\s*key\s*'.+':`: Passphrase,
`Enter\s*PIN\s*for\s*.+\s*key\s*.+:`: PIN,
`Enter\s*PIN\s*for\s*'.+':`: PIN,
`.*2FA Token.*`: Token,
}
@@ -89,11 +89,17 @@ func TestProcessOutput(t *testing.T) {
expectedToWrite: "passphrase",
},
{
name: "pin prompt",
name: "security key pin prompt",
promptUserForCredential: defaultPromptUserForCredential,
output: "Enter PIN for key '123':",
expectedToWrite: "pin",
},
{
name: "pkcs11 key pin prompt",
promptUserForCredential: defaultPromptUserForCredential,
output: "Enter PIN for '123':",
expectedToWrite: "pin",
},
{
name: "2FA token prompt",
promptUserForCredential: defaultPromptUserForCredential,
+2 -2
View File
@@ -222,7 +222,7 @@ func (c *OSCommand) PipeCommands(cmdObjs ...*CmdObj) error {
c.LogCommand(logCmdStr, true)
for i := 0; i < len(cmds)-1; i++ {
for i := range len(cmds) - 1 {
stdout, err := cmds[i].StdoutPipe()
if err != nil {
return err
@@ -283,7 +283,7 @@ func PrepareForChildren(cmd *exec.Cmd) {
}
func (c *OSCommand) CopyToClipboard(str string) error {
escaped := strings.Replace(str, "\n", "\\n", -1)
escaped := strings.ReplaceAll(str, "\n", "\\n")
truncated := utils.TruncateWithEllipsis(escaped, 40)
msg := utils.ResolvePlaceholderString(
+12 -15
View File
@@ -72,30 +72,22 @@ func (self *patchPresenter) format() string {
lineIdx++
}
appendFormattedLine := func(line string, style style.TextStyle) {
formattedLine := self.formatLine(
line,
style,
lineIdx,
)
appendLine(formattedLine)
}
for _, line := range self.patch.header {
appendFormattedLine(line, theme.DefaultTextColor.SetBold())
// always passing false for 'included' here because header lines are not part of the patch
appendLine(self.formatLineAux(line, theme.DefaultTextColor.SetBold(), false))
}
for _, hunk := range self.patch.hunks {
appendLine(
self.formatLine(
self.formatLineAux(
hunk.formatHeaderStart(),
style.FgCyan,
lineIdx,
false,
) +
// we're splitting the line into two parts: the diff header and the context
// We explicitly pass 'included' as false here so that we're only tagging the
// first half of the line as included if the line is indeed included.
// We explicitly pass 'included' as false for both because these are not part
// of the actual patch
self.formatLineAux(
hunk.headerContext,
theme.DefaultTextColor,
@@ -104,7 +96,12 @@ func (self *patchPresenter) format() string {
)
for _, line := range hunk.bodyLines {
appendFormattedLine(line.Content, self.patchLineStyle(line))
style := self.patchLineStyle(line)
if line.IsChange() {
appendLine(self.formatLine(line.Content, style, lineIdx))
} else {
appendLine(self.formatLineAux(line.Content, style, false))
}
}
}
+21 -9
View File
@@ -56,7 +56,7 @@ func (self *Patch) HunkStartIdx(hunkIndex int) int {
hunkIndex = lo.Clamp(hunkIndex, 0, len(self.hunks)-1)
result := len(self.header)
for i := 0; i < hunkIndex; i++ {
for i := range hunkIndex {
result += self.hunks[i].lineCount()
}
return result
@@ -115,15 +115,22 @@ func (self *Patch) HunkContainingLine(idx int) int {
return -1
}
// Returns the patch line index of the next change (i.e. addition or deletion).
func (self *Patch) GetNextChangeIdx(idx int) int {
// Returns the patch line index of the next change (i.e. addition or deletion)
// that matches the same "included" state, given the includedLines. If you don't
// care about included states, pass nil for includedLines and false for included.
func (self *Patch) GetNextChangeIdxOfSameIncludedState(idx int, includedLines []int, included bool) (int, bool) {
idx = lo.Clamp(idx, 0, self.LineCount()-1)
lines := self.Lines()
isMatch := func(i int, line *PatchLine) bool {
sameIncludedState := lo.Contains(includedLines, i) == included
return line.IsChange() && sameIncludedState
}
for i, line := range lines[idx:] {
if line.isChange() {
return i + idx
if isMatch(i+idx, line) {
return i + idx, true
}
}
@@ -131,13 +138,18 @@ func (self *Patch) GetNextChangeIdx(idx int) int {
// return the index of the last change
for i := len(lines) - 1; i >= 0; i-- {
line := lines[i]
if line.isChange() {
return i
if isMatch(i, line) {
return i, true
}
}
// should not be possible
return 0
return 0, false
}
// Returns the patch line index of the next change (i.e. addition or deletion).
func (self *Patch) GetNextChangeIdx(idx int) int {
result, _ := self.GetNextChangeIdxOfSameIncludedState(idx, nil, false)
return result
}
// Returns the length of the patch in lines
+7 -22
View File
@@ -91,7 +91,7 @@ func (p *PatchBuilder) addFileWhole(info *fileInfo) {
// add every line index
// TODO: add tests and then use lo.Range to simplify
info.includedLineIndices = make([]int, lineCount)
for i := 0; i < lineCount; i++ {
for i := range lineCount {
info.includedLineIndices[i] = i
}
}
@@ -124,14 +124,6 @@ func (p *PatchBuilder) RemoveFile(filename string) error {
return nil
}
func getIndicesForRange(first, last int) []int {
indices := []int{}
for i := first; i <= last; i++ {
indices = append(indices, i)
}
return indices
}
func (p *PatchBuilder) getFileInfo(filename string) (*fileInfo, error) {
info, ok := p.fileInfoMap[filename]
if ok {
@@ -152,24 +144,24 @@ func (p *PatchBuilder) getFileInfo(filename string) (*fileInfo, error) {
return info, nil
}
func (p *PatchBuilder) AddFileLineRange(filename string, firstLineIdx, lastLineIdx int) error {
func (p *PatchBuilder) AddFileLineRange(filename string, lineIndices []int) error {
info, err := p.getFileInfo(filename)
if err != nil {
return err
}
info.mode = PART
info.includedLineIndices = lo.Union(info.includedLineIndices, getIndicesForRange(firstLineIdx, lastLineIdx))
info.includedLineIndices = lo.Union(info.includedLineIndices, lineIndices)
return nil
}
func (p *PatchBuilder) RemoveFileLineRange(filename string, firstLineIdx, lastLineIdx int) error {
func (p *PatchBuilder) RemoveFileLineRange(filename string, lineIndices []int) error {
info, err := p.getFileInfo(filename)
if err != nil {
return err
}
info.mode = PART
info.includedLineIndices, _ = lo.Difference(info.includedLineIndices, getIndicesForRange(firstLineIdx, lastLineIdx))
info.includedLineIndices, _ = lo.Difference(info.includedLineIndices, lineIndices)
if len(info.includedLineIndices) == 0 {
p.removeFile(info)
}
@@ -211,9 +203,8 @@ func (p *PatchBuilder) RenderPatchForFile(opts RenderPatchForFileOpts) string {
if opts.Plain {
return patch.FormatPlain()
} else {
return patch.FormatView(FormatViewOpts{})
}
return patch.FormatView(FormatViewOpts{})
}
func (p *PatchBuilder) renderEachFilePatch(plain bool) []string {
@@ -287,11 +278,5 @@ func (p *PatchBuilder) NewPatchRequired(from string, to string, reverse bool) bo
}
func (p *PatchBuilder) AllFilesInPatch() []string {
files := make([]string, 0, len(p.fileInfoMap))
for filename := range p.fileInfoMap {
files = append(files, filename)
}
return files
return lo.Keys(p.fileInfoMap)
}
+1 -1
View File
@@ -18,7 +18,7 @@ type PatchLine struct {
Content string // something like '+ hello' (note the first character is not removed)
}
func (self *PatchLine) isChange() bool {
func (self *PatchLine) IsChange() bool {
return self.Kind == ADDITION || self.Kind == DELETION
}
+2 -2
View File
@@ -84,9 +84,9 @@ func (self *patchTransformer) transformHeader() []string {
result = append(result, line)
}
return result
} else {
return self.patch.header
}
return self.patch.header
}
func (self *patchTransformer) transformHunks() []*Hunk {
+6 -6
View File
@@ -288,7 +288,7 @@ func computeMigratedConfig(path string, content []byte, changes *ChangesSet) ([]
for _, pathToReplace := range pathsToReplace {
err, didReplace := yaml_utils.RenameYamlKey(&rootNode, pathToReplace.oldPath, pathToReplace.newName)
if err != nil {
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s` for key %s: %s", path, strings.Join(pathToReplace.oldPath, "."), err)
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s` for key %s: %w", path, strings.Join(pathToReplace.oldPath, "."), err)
}
if didReplace {
changes.Add(fmt.Sprintf("Renamed '%s' to '%s'", strings.Join(pathToReplace.oldPath, "."), pathToReplace.newName))
@@ -297,27 +297,27 @@ func computeMigratedConfig(path string, content []byte, changes *ChangesSet) ([]
err = changeNullKeybindingsToDisabled(&rootNode, changes)
if err != nil {
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err)
}
err = changeElementToSequence(&rootNode, []string{"git", "commitPrefix"}, changes)
if err != nil {
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err)
}
err = changeCommitPrefixesMap(&rootNode, changes)
if err != nil {
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err)
}
err = changeCustomCommandStreamAndOutputToOutputEnum(&rootNode, changes)
if err != nil {
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err)
}
err = migrateAllBranchesLogCmd(&rootNode, changes)
if err != nil {
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err)
}
// Add more migrations here...
+4 -1
View File
@@ -384,6 +384,9 @@ gui:
# When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.
mouseEvents: true
# If true, do not show a warning when amending a commit.
skipAmendWarning: false
# If true, do not show a warning when discarding changes in the staging view.
skipDiscardChangeWarning: false
@@ -557,7 +560,7 @@ gui:
screenMode: normal
# Window border style.
# One of 'rounded' (default) | 'single' | 'double' | 'hidden'
# One of 'rounded' (default) | 'single' | 'double' | 'hidden' | 'bold'
border: rounded
# If true, show a seriously epic explosion animation when nuking the working tree.
+8 -2
View File
@@ -74,6 +74,8 @@ type GuiConfig struct {
// If true, capture mouse events.
// When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.
MouseEvents bool `yaml:"mouseEvents"`
// If true, do not show a warning when amending a commit.
SkipAmendWarning bool `yaml:"skipAmendWarning"`
// If true, do not show a warning when discarding changes in the staging view.
SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"`
// If true, do not show warning when applying/popping the stash
@@ -105,6 +107,8 @@ type GuiConfig struct {
// makes it much easier to work with diffs that have long lines, e.g.
// paragraphs of markdown text.
WrapLinesInStagingView bool `yaml:"wrapLinesInStagingView"`
// If true, hunk selection mode will be enabled by default when entering the staging view.
UseHunkModeInStagingView bool `yaml:"useHunkModeInStagingView"`
// One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
Language string `yaml:"language" jsonschema:"enum=auto,enum=en,enum=zh-TW,enum=zh-CN,enum=pl,enum=nl,enum=ja,enum=ko,enum=ru"`
// Format used when displaying time e.g. commit time.
@@ -164,8 +168,8 @@ type GuiConfig struct {
// One of: 'normal' (default) | 'half' | 'full'
ScreenMode string `yaml:"screenMode" jsonschema:"enum=normal,enum=half,enum=full"`
// Window border style.
// One of 'rounded' (default) | 'single' | 'double' | 'hidden'
Border string `yaml:"border" jsonschema:"enum=single,enum=double,enum=rounded,enum=hidden"`
// One of 'rounded' (default) | 'single' | 'double' | 'hidden' | 'bold'
Border string `yaml:"border" jsonschema:"enum=single,enum=double,enum=rounded,enum=hidden,enum=bold"`
// If true, show a seriously epic explosion animation when nuking the working tree.
AnimateExplosion bool `yaml:"animateExplosion"`
// Whether to stack UI components on top of each other.
@@ -734,6 +738,7 @@ func GetDefaultConfig() *UserConfig {
ScrollOffBehavior: "margin",
TabWidth: 4,
MouseEvents: true,
SkipAmendWarning: false,
SkipDiscardChangeWarning: false,
SkipStashWarning: false,
SidePanelWidth: 0.3333,
@@ -742,6 +747,7 @@ func GetDefaultConfig() *UserConfig {
MainPanelSplitMode: "flexible",
EnlargedSideViewLocation: "left",
WrapLinesInStagingView: true,
UseHunkModeInStagingView: false,
Language: "auto",
TimeFormat: "02 Jan 06",
ShortTimeFormat: time.Kitchen,
+1 -1
View File
@@ -56,7 +56,7 @@ func validateKeybindingsRecurse(path string, node any) error {
}
}
} else if value.Kind() == reflect.Slice {
for i := 0; i < value.Len(); i++ {
for i := range value.Len() {
if err := validateKeybindingsRecurse(
fmt.Sprintf("%s[%d]", path, i), value.Index(i).Interface()); err != nil {
return err
+3 -2
View File
@@ -93,7 +93,8 @@ func (self *BackgroundRoutineMgr) startBackgroundFilesRefresh(refreshInterval in
self.gui.waitForIntro.Wait()
self.goEvery(time.Second*time.Duration(refreshInterval), self.gui.stopChan, func() error {
return self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
return nil
})
}
@@ -125,7 +126,7 @@ func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan stru
func (self *BackgroundRoutineMgr) backgroundFetch() (err error) {
err = self.gui.git.Sync.FetchBackground()
_ = self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.SYNC})
self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.SYNC})
if err == nil {
err = self.gui.helpers.BranchesHelper.AutoForwardBranches()
+1 -1
View File
@@ -48,7 +48,7 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
textStyle = style.FgMagenta
}
gui.GuiLog = append(gui.GuiLog, cmdStr)
indentedCmdStr := " " + strings.Replace(cmdStr, "\n", "\n ", -1)
indentedCmdStr := " " + strings.ReplaceAll(cmdStr, "\n", "\n ")
fmt.Fprint(gui.Views.Extras, "\n"+textStyle.Sprint(indentedCmdStr))
}
+1 -2
View File
@@ -16,14 +16,13 @@ type FilteredList[T any] struct {
getFilterFields func(T) []string
filter string
mutex *deadlock.Mutex
mutex deadlock.Mutex
}
func NewFilteredList[T any](getList func() []T, getFilterFields func(T) []string) *FilteredList[T] {
return &FilteredList[T]{
getList: getList,
getFilterFields: getFilterFields,
mutex: &deadlock.Mutex{},
}
}
+2 -2
View File
@@ -257,11 +257,11 @@ func TestListRenderer_ModelIndexToViewIndex_and_back(t *testing.T) {
// Need to render first so that it knows the non-model items
self.renderLines(-1, -1)
for i := 0; i < len(s.modelIndices); i++ {
for i := range len(s.modelIndices) {
assert.Equal(t, s.expectedViewIndices[i], self.ModelIndexToViewIndex(s.modelIndices[i]))
}
for i := 0; i < len(s.viewIndices); i++ {
for i := range len(s.viewIndices) {
assert.Equal(t, s.expectedModelIndices[i], self.ViewIndexToModelIndex(s.viewIndices[i]))
}
})
+1 -1
View File
@@ -144,7 +144,7 @@ func (self *MenuViewModel) GetNonModelItems() []*NonModelItem {
}
menuItems := self.FilteredListViewModel.GetItems()
var prevSection *types.MenuSection = nil
var prevSection *types.MenuSection
for i, menuItem := range menuItems {
if menuItem.Section != nil && menuItem.Section != prevSection {
if prevSection != nil {
+2 -3
View File
@@ -12,7 +12,7 @@ type MergeConflictsContext struct {
types.Context
viewModel *ConflictsViewModel
c *ContextCommon
mutex *deadlock.Mutex
mutex deadlock.Mutex
}
type ConflictsViewModel struct {
@@ -33,7 +33,6 @@ func NewMergeConflictsContext(
return &MergeConflictsContext{
viewModel: viewModel,
mutex: &deadlock.Mutex{},
Context: NewSimpleContext(
NewBaseContext(NewBaseContextOpts{
Kind: types.MAIN_CONTEXT,
@@ -57,7 +56,7 @@ func (self *MergeConflictsContext) SetState(state *mergeconflicts.State) {
}
func (self *MergeConflictsContext) GetMutex() *deadlock.Mutex {
return self.mutex
return &self.mutex
}
func (self *MergeConflictsContext) SetUserScrolling(isScrolling bool) {
+2 -3
View File
@@ -15,7 +15,7 @@ type PatchExplorerContext struct {
viewTrait *ViewTrait
getIncludedLineIndices func() []int
c *ContextCommon
mutex *deadlock.Mutex
mutex deadlock.Mutex
}
var (
@@ -36,7 +36,6 @@ func NewPatchExplorerContext(
state: nil,
viewTrait: NewViewTrait(view),
c: c,
mutex: &deadlock.Mutex{},
getIncludedLineIndices: getIncludedLineIndices,
SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
View: view,
@@ -137,7 +136,7 @@ func (self *PatchExplorerContext) NavigateTo(selectedLineIdx int) {
}
func (self *PatchExplorerContext) GetMutex() *deadlock.Mutex {
return self.mutex
return &self.mutex
}
func (self *PatchExplorerContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
+1 -2
View File
@@ -79,9 +79,8 @@ func (self *SuggestionsContext) RefreshSuggestions() {
if findSuggestionsFn != nil {
suggestions := findSuggestionsFn(self.c.GetPromptInput())
return func() { self.SetSuggestions(suggestions) }
} else {
return func() {}
}
return func() {}
})
}
+10 -2
View File
@@ -77,9 +77,8 @@ func (self *ListCursor) SetSelectionRangeAndMode(selectedIdx, rangeStartIdx int,
func (self *ListCursor) GetSelectionRangeAndMode() (int, int, RangeSelectMode) {
if self.IsSelectingRange() {
return self.selectedIdx, self.rangeStartIdx, self.rangeSelectMode
} else {
return self.selectedIdx, self.selectedIdx, self.rangeSelectMode
}
return self.selectedIdx, self.selectedIdx, self.rangeSelectMode
}
func (self *ListCursor) clampValue(value int) int {
@@ -134,10 +133,19 @@ func (self *ListCursor) GetRangeStartIdx() (int, bool) {
return 0, false
}
// Cancel range select mode, but keep the "moving end" of the range selected.
// Used when pressing 'v' or escape to toggle range select mode, for example.
func (self *ListCursor) CancelRangeSelect() {
self.rangeSelectMode = RangeSelectModeNone
}
// Cancel range select mode, but keep the top of the range selected. Note that
// this is different from CancelRangeSelect. Useful after deleting a range of items.
func (self *ListCursor) CollapseRangeSelectionToTop() {
start, _ := self.GetSelectionRange()
self.SetSelection(start)
}
// Returns true if we are in range select mode. Note that we may be in range select
// mode and still only selecting a single item. See AreMultipleItemsSelected below.
func (self *ListCursor) IsSelectingRange() bool {
+2 -2
View File
@@ -24,7 +24,7 @@ func OnFocusWrapper(f func() error) func(opts types.OnFocusOpts) error {
func (gui *Gui) defaultSideContext() types.Context {
if gui.State.Modes.Filtering.Active() {
return gui.State.Contexts.LocalCommits
} else {
return gui.State.Contexts.Files
}
return gui.State.Contexts.Files
}
+17 -13
View File
@@ -54,9 +54,8 @@ func (self *BisectController) openMenu(commit *models.Commit) error {
info := self.c.Git().Bisect.GetInfo()
if info.Started() {
return self.openMidBisectMenu(info, commit)
} else {
return self.openStartBisectMenu(info, commit)
}
return self.openStartBisectMenu(info, commit)
}
func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error {
@@ -176,7 +175,8 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
return err
}
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
self.c.Helpers().Bisect.PostBisectCommandRefresh()
return nil
},
DisabledReason: self.require(self.singleItemSelected())(),
Key: 'b',
@@ -193,7 +193,8 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
return err
}
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
self.c.Helpers().Bisect.PostBisectCommandRefresh()
return nil
},
DisabledReason: self.require(self.singleItemSelected())(),
Key: 'g',
@@ -212,7 +213,8 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
return err
}
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
self.c.Helpers().Bisect.PostBisectCommandRefresh()
return nil
},
})
return nil
@@ -246,7 +248,8 @@ func (self *BisectController) showBisectCompleteMessage(candidateHashes []string
return err
}
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
self.c.Helpers().Bisect.PostBisectCommandRefresh()
return nil
},
})
@@ -271,20 +274,21 @@ func (self *BisectController) afterMark(selectCurrent bool, waitToReselect bool)
}
func (self *BisectController) afterBisectMarkRefresh(selectCurrent bool, waitToReselect bool) error {
selectFn := func() error {
selectFn := func() {
if selectCurrent {
self.selectCurrentBisectCommit()
}
return nil
}
if waitToReselect {
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{}, Then: selectFn})
} else {
_ = selectFn()
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{}, Then: selectFn})
return nil
}
selectFn()
self.c.Helpers().Bisect.PostBisectCommandRefresh()
return nil
}
func (self *BisectController) selectCurrentBisectCommit() {
+21 -22
View File
@@ -263,15 +263,13 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
if err := self.c.Git().Branch.UnsetUpstream(selectedBranch.Name); err != nil {
return err
}
if err := self.c.Refresh(types.RefreshOptions{
self.c.Refresh(types.RefreshOptions{
Mode: types.SYNC,
Scope: []types.RefreshableView{
types.BRANCHES,
types.COMMITS,
},
}); err != nil {
return err
}
})
return nil
},
Key: 'u',
@@ -289,15 +287,13 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
if err := self.c.Git().Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil {
return err
}
if err := self.c.Refresh(types.RefreshOptions{
self.c.Refresh(types.RefreshOptions{
Mode: types.SYNC,
Scope: []types.RefreshableView{
types.BRANCHES,
types.COMMITS,
},
}); err != nil {
return err
}
})
return nil
})
},
@@ -478,7 +474,8 @@ func (self *BranchesController) forceCheckout() error {
if err := self.c.Git().Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
},
})
@@ -526,7 +523,8 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
}
self.context().SetSelection(0)
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, KeepBranchSelectionIndex: true})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, KeepBranchSelectionIndex: true})
return nil
}
func (self *BranchesController) localDelete(branches []*models.Branch) error {
@@ -537,7 +535,7 @@ func (self *BranchesController) remoteDelete(branches []*models.Branch) error {
remoteBranches := lo.Map(branches, func(branch *models.Branch, _ int) *models.RemoteBranch {
return &models.RemoteBranch{Name: branch.UpstreamBranch, RemoteName: branch.UpstreamRemote}
})
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(remoteBranches)
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(remoteBranches, false)
}
func (self *BranchesController) localAndRemoteDelete(branches []*models.Branch) error {
@@ -655,17 +653,17 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
WorktreePath: worktreePath,
},
)
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return err
} else {
self.c.LogAction(action)
err := self.c.Git().Sync.FastForward(
task, branch.Name, branch.UpstreamRemote, branch.UpstreamBranch,
)
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return err
}
self.c.LogAction(action)
err := self.c.Git().Sync.FastForward(
task, branch.Name, branch.UpstreamRemote, branch.UpstreamBranch,
)
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
return err
})
}
@@ -679,7 +677,8 @@ func (self *BranchesController) createSortMenu() error {
self.c.GetAppState().LocalBranchSortOrder = sortOrder
self.c.SaveAppStateAndLogError()
self.c.Contexts().Branches.SetSelection(0)
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
return nil
}
return nil
},
@@ -702,7 +701,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
}
// need to find where the branch is now so that we can re-select it. That means we need to refetch the branches synchronously and then find our branch
_ = self.c.Refresh(types.RefreshOptions{
self.c.Refresh(types.RefreshOptions{
Mode: types.SYNC,
Scope: []types.RefreshableView{types.BRANCHES, types.WORKTREES},
})
@@ -153,7 +153,7 @@ func (self *CommitMessageController) handleCommitIndexChange(value int) error {
func (self *CommitMessageController) setCommitMessageAtIndex(index int) (bool, error) {
commitMessage, err := self.c.Git().Commit.GetCommitMessageFromHistory(index)
if err != nil {
if err == git_commands.ErrInvalidCommitIndex {
if errors.Is(err, git_commands.ErrInvalidCommitIndex) {
return false, nil
}
return false, errors.New(self.c.Tr.CommitWithoutMessageErr)
@@ -289,7 +289,8 @@ func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
}
func (self *CommitFilesController) discard(selectedNodes []*filetree.CommitFileNode) error {
@@ -313,19 +314,13 @@ func (self *CommitFilesController) discard(selectedNodes []*filetree.CommitFileN
// Reset the current patch if there is one.
if self.c.Git().Patch.PatchBuilder.Active() {
self.c.Git().Patch.PatchBuilder.Reset()
if err := self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI}); err != nil {
return err
}
}
for _, node := range selectedNodes {
err := node.ForEachFile(func(file *models.CommitFile) error {
_ = node.ForEachFile(func(file *models.CommitFile) error {
filePaths = append(filePaths, file.GetPath())
return nil
})
if err != nil {
return err
}
}
err := self.c.Git().Rebase.DiscardOldFileChanges(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), filePaths)
@@ -336,7 +331,8 @@ func (self *CommitFilesController) discard(selectedNodes []*filetree.CommitFileN
if self.context().RangeSelectEnabled() {
self.context().GetList().CancelRangeSelect()
}
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC})
return nil
})
},
})
@@ -103,13 +103,13 @@ func (self *ContextLinesController) applyChange() error {
switch currentContext.GetKey() {
// we make an exception for our staging and patch building contexts because they actually need to refresh their state afterwards.
case context.PATCH_BUILDING_MAIN_CONTEXT_KEY:
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.PATCH_BUILDING}})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.PATCH_BUILDING}})
case context.STAGING_MAIN_CONTEXT_KEY, context.STAGING_SECONDARY_CONTEXT_KEY:
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STAGING}})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STAGING}})
default:
currentContext.HandleRenderToMain()
return nil
}
return nil
}
func (self *ContextLinesController) checkCanChangeContext() error {
@@ -4,10 +4,13 @@ import (
"errors"
"fmt"
"github.com/jesseduffield/generics/set"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/samber/lo"
)
type CustomPatchOptionsMenuAction struct {
@@ -189,9 +192,9 @@ func (self *CustomPatchOptionsMenuAction) handleMovePatchIntoWorkingTree() error
})
return nil
} else {
return pull(false)
}
return pull(false)
}
func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error {
@@ -267,15 +270,42 @@ func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommitBefore() e
func (self *CustomPatchOptionsMenuAction) handleApplyPatch(reverse bool) error {
self.returnFocusFromPatchExplorerIfNecessary()
action := self.c.Tr.Actions.ApplyPatch
if reverse {
action = "Apply patch in reverse"
affectedUnstagedFiles := self.getAffectedUnstagedFiles()
apply := func() error {
action := self.c.Tr.Actions.ApplyPatch
if reverse {
action = "Apply patch in reverse"
}
self.c.LogAction(action)
if len(affectedUnstagedFiles) > 0 {
if err := self.c.Git().WorkingTree.StageFiles(affectedUnstagedFiles, nil); err != nil {
return err
}
}
if err := self.c.Git().Patch.ApplyCustomPatch(reverse, true); err != nil {
return err
}
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
}
self.c.LogAction(action)
if err := self.c.Git().Patch.ApplyCustomPatch(reverse, true); err != nil {
return err
if len(affectedUnstagedFiles) > 0 {
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.MustStageFilesAffectedByPatchTitle,
Prompt: self.c.Tr.MustStageFilesAffectedByPatchWarning,
HandleConfirm: func() error {
return apply()
},
})
return nil
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return apply()
}
func (self *CustomPatchOptionsMenuAction) copyPatchToClipboard() error {
@@ -290,3 +320,17 @@ func (self *CustomPatchOptionsMenuAction) copyPatchToClipboard() error {
return nil
}
// Returns a list of files that have unstaged changes and are contained in the patch.
func (self *CustomPatchOptionsMenuAction) getAffectedUnstagedFiles() []string {
unstagedFiles := set.NewFromSlice(lo.FilterMap(self.c.Model().Files, func(f *models.File, _ int) (string, bool) {
if f.GetHasUnstagedChanges() {
return f.GetPath(), true
}
return "", false
}))
return lo.Filter(self.c.Git().Patch.PatchBuilder.AllFilesInPatch(), func(patchFile string, _ int) bool {
return unstagedFiles.Includes(patchFile)
})
}
+8 -4
View File
@@ -23,7 +23,8 @@ func (self *DiffingMenuAction) Call() error {
OnPress: func() error {
self.c.Modes().Diffing.Ref = name
// can scope this down based on current view but too lazy right now
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
},
},
}...)
@@ -38,7 +39,8 @@ func (self *DiffingMenuAction) Call() error {
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
HandleConfirm: func(response string) error {
self.c.Modes().Diffing.Ref = strings.TrimSpace(response)
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
},
})
@@ -53,14 +55,16 @@ func (self *DiffingMenuAction) Call() error {
Label: self.c.Tr.SwapDiff,
OnPress: func() error {
self.c.Modes().Diffing.Reverse = !self.c.Modes().Diffing.Reverse
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
},
},
{
Label: self.c.Tr.ExitDiffMode,
OnPress: func() error {
self.c.Modes().Diffing = diffing.New()
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
},
},
}...)
+31 -27
View File
@@ -18,7 +18,7 @@ import (
)
type FilesController struct {
baseController // nolint: unused
baseController
*ListControllerTrait[*filetree.FileNode]
c *ControllerCommon
}
@@ -262,15 +262,14 @@ func (self *FilesController) GetOnRenderToMain() func() {
}
if node.File.ShortStatus == "DU" || node.File.ShortStatus == "UD" {
cmdObj := self.c.Git().Diff.DiffCmdObj([]string{"--base", "--", node.GetPath()})
task := types.NewRunPtyTask(cmdObj.GetCmd())
task.Prefix = message + "\n\n"
prefix := message + "\n\n"
if node.File.ShortStatus == "DU" {
task.Prefix += self.c.Tr.MergeConflictIncomingDiff
prefix += self.c.Tr.MergeConflictIncomingDiff
} else {
task.Prefix += self.c.Tr.MergeConflictCurrentDiff
prefix += self.c.Tr.MergeConflictCurrentDiff
}
task.Prefix += "\n\n"
opts.Main.Task = task
prefix += "\n\n"
opts.Main.Task = types.NewRunPtyTaskWithPrefix(cmdObj.GetCmd(), prefix)
} else {
opts.Main.Task = types.NewRenderStringTask(message)
}
@@ -486,9 +485,7 @@ func (self *FilesController) press(nodes []*filetree.FileNode) error {
return err
}
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC}); err != nil {
return err
}
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC})
self.context().HandleFocus(types.OnFocusOpts{})
return nil
@@ -566,7 +563,8 @@ func (self *FilesController) handleNonInlineConflict(file *models.File) error {
if err := command(file.GetPath()); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
return nil
}
keepItem := &types.MenuItem{
Label: self.c.Tr.MergeConflictKeepFile,
@@ -611,9 +609,7 @@ func (self *FilesController) toggleStagedAll() error {
return err
}
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC}); err != nil {
return err
}
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC})
self.context().HandleFocus(types.OnFocusOpts{})
return nil
@@ -683,7 +679,8 @@ func (self *FilesController) ignoreOrExcludeTracked(node *filetree.FileNode, trA
return err
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
return nil
}
func (self *FilesController) ignoreOrExcludeUntracked(node *filetree.FileNode, trAction string, f func(string) error) error {
@@ -693,7 +690,8 @@ func (self *FilesController) ignoreOrExcludeUntracked(node *filetree.FileNode, t
return err
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
return nil
}
func (self *FilesController) ignoreOrExcludeFile(node *filetree.FileNode, trText string, trPrompt string, trAction string, f func(string) error) error {
@@ -755,7 +753,8 @@ func (self *FilesController) ignoreOrExcludeMenu(node *filetree.FileNode) error
}
func (self *FilesController) refresh() error {
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
return nil
}
func (self *FilesController) handleAmendCommitPress() error {
@@ -795,7 +794,7 @@ func (self *FilesController) handleAmendCommitPress() error {
},
},
})
} else {
} else if !self.c.UserConfig().Gui.SkipAmendWarning {
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.AmendLastCommitTitle,
Prompt: self.c.Tr.SureToAmend,
@@ -803,9 +802,11 @@ func (self *FilesController) handleAmendCommitPress() error {
return doAmend()
},
})
return nil
}
return nil
return doAmend()
}
func (self *FilesController) isResolvingConflicts() bool {
@@ -898,12 +899,11 @@ func (self *FilesController) setStatusFiltering(filter filetree.FileTreeDisplayF
// Whenever we switch between untracked and other filters, we need to refresh the files view
// because the untracked files filter applies when running `git status`.
if previousFilter != filter && (previousFilter == filetree.DisplayUntracked || filter == filetree.DisplayUntracked) {
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC})
} else {
self.c.PostRefreshUpdate(self.context())
return nil
}
return nil
}
func (self *FilesController) edit(nodes []*filetree.FileNode) error {
@@ -1176,7 +1176,8 @@ func (self *FilesController) handleStashSave(stashFunc func(message string) erro
if err := stashFunc(stashComment); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH, types.FILES}})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH, types.FILES}})
return nil
},
})
@@ -1196,7 +1197,7 @@ func (self *FilesController) fetch() error {
return errors.New(self.c.Tr.PassUnameWrong)
}
_ = self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.SYNC})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.SYNC})
if err == nil {
err = self.c.Helpers().BranchesHelper.AutoForwardBranches()
@@ -1321,7 +1322,8 @@ func (self *FilesController) remove(selectedNodes []*filetree.FileNode) error {
}
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.WORKTREES}})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.WORKTREES}})
return nil
},
Key: self.c.KeybindingsOpts().GetKey(self.c.UserConfig().Keybinding.Files.ConfirmDiscard),
Tooltip: utils.ResolvePlaceholderString(
@@ -1347,7 +1349,8 @@ func (self *FilesController) remove(selectedNodes []*filetree.FileNode) error {
}
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.WORKTREES}})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.WORKTREES}})
return nil
},
Key: 'u',
Tooltip: utils.ResolvePlaceholderString(
@@ -1388,7 +1391,8 @@ func (self *FilesController) ResetSubmodule(submodule *models.SubmoduleConfig) e
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.SUBMODULES}})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.SUBMODULES}})
return nil
})
}
+3 -2
View File
@@ -122,9 +122,10 @@ func (self *FilteringMenuAction) setFiltering() error {
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}, Then: func() error {
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}, Then: func() {
self.c.Contexts().LocalCommits.SetSelection(0)
self.c.Contexts().LocalCommits.FocusLine()
return nil
}})
return nil
}
+2 -1
View File
@@ -143,7 +143,8 @@ func (self *GlobalController) createCustomPatchOptionsMenu() error {
}
func (self *GlobalController) refresh() error {
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
}
func (self *GlobalController) nextScreenMode() error {
+4 -3
View File
@@ -22,13 +22,14 @@ func (self *BisectHelper) Reset() error {
return err
}
return self.PostBisectCommandRefresh()
self.PostBisectCommandRefresh()
return nil
},
})
return nil
}
func (self *BisectHelper) PostBisectCommandRefresh() error {
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{}})
func (self *BisectHelper) PostBisectCommandRefresh() {
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{}})
}
+14 -10
View File
@@ -47,9 +47,10 @@ func (self *BranchesHelper) ConfirmLocalDelete(branches []*models.Branch) error
if err := self.c.Git().Branch.LocalDelete(branchNames, true); err != nil {
return err
}
selectionStart, _ := self.c.Contexts().Branches.GetSelectionRange()
self.c.Contexts().Branches.SetSelectedLineIdx(selectionStart)
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
self.c.Contexts().Branches.CollapseRangeSelectionToTop()
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
return nil
})
}
@@ -81,7 +82,7 @@ func (self *BranchesHelper) ConfirmLocalDelete(branches []*models.Branch) error
return nil
}
func (self *BranchesHelper) ConfirmDeleteRemote(remoteBranches []*models.RemoteBranch) error {
func (self *BranchesHelper) ConfirmDeleteRemote(remoteBranches []*models.RemoteBranch, resetRemoteBranchesSelection bool) error {
var title string
if len(remoteBranches) == 1 {
title = utils.ResolvePlaceholderString(
@@ -113,7 +114,11 @@ func (self *BranchesHelper) ConfirmDeleteRemote(remoteBranches []*models.RemoteB
if err := self.deleteRemoteBranches(remoteBranches, task); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
if resetRemoteBranchesSelection {
self.c.Contexts().RemoteBranches.CollapseRangeSelectionToTop()
}
return nil
})
},
})
@@ -178,10 +183,9 @@ func (self *BranchesHelper) ConfirmLocalAndRemoteDelete(branches []*models.Branc
return err
}
selectionStart, _ := self.c.Contexts().Branches.GetSelectionRange()
self.c.Contexts().Branches.SetSelectedLineIdx(selectionStart)
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
self.c.Contexts().Branches.CollapseRangeSelectionToTop()
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
return nil
})
},
})
@@ -295,7 +299,7 @@ func (self *BranchesHelper) AutoForwardBranches() error {
self.c.LogCommand(strings.TrimRight(updateCommands, "\n"), false)
err := self.c.Git().Branch.UpdateBranchRefs(updateCommands)
_ = self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES}, Mode: types.SYNC})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES}, Mode: types.SYNC})
return err
}
@@ -3,7 +3,6 @@ package helpers
import (
"strconv"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
"github.com/jesseduffield/lazygit/pkg/gui/types"
@@ -77,14 +76,34 @@ func (self *CherryPickHelper) Paste() error {
"numCommits": strconv.Itoa(len(self.getData().CherryPickedCommits)),
}),
HandleConfirm: func() error {
return self.c.WithWaitingStatus(self.c.Tr.CherryPickingStatus, func(gocui.Task) error {
return self.c.WithWaitingStatusSync(self.c.Tr.CherryPickingStatus, func() error {
mustStash := IsWorkingTreeDirty(self.c.Model().Files)
self.c.LogAction(self.c.Tr.Actions.CherryPick)
result := self.c.Git().Rebase.CherryPickCommits(self.getData().CherryPickedCommits)
err := self.rebaseHelper.CheckMergeOrRebase(result)
if mustStash {
if err := self.c.Git().Stash.Push(self.c.Tr.AutoStashForCherryPicking); err != nil {
return err
}
}
cherryPickedCommits := self.getData().CherryPickedCommits
result := self.c.Git().Rebase.CherryPickCommits(cherryPickedCommits)
err := self.rebaseHelper.CheckMergeOrRebaseWithRefreshOptions(result, types.RefreshOptions{Mode: types.SYNC})
if err != nil {
return result
}
// Move the selection down by the number of commits we just
// cherry-picked, to keep the same commit selected as before.
// Don't do this if a rebase todo is selected, because in this
// case we are in a rebase and the cherry-picked commits end up
// below the selection.
if commit := self.c.Contexts().LocalCommits.GetSelected(); commit != nil && !commit.IsTODO() {
self.c.Contexts().LocalCommits.MoveSelection(len(cherryPickedCommits))
self.c.Contexts().LocalCommits.FocusLine()
}
// If we're in the cherry-picking state at this point, it must
// be because there were conflicts. Don't clear the copied
// commits in this case, since we might want to abort and try
@@ -96,7 +115,17 @@ func (self *CherryPickHelper) Paste() error {
if !isInCherryPick {
self.getData().DidPaste = true
self.rerender()
if mustStash {
if err := self.c.Git().Stash.Pop(0); err != nil {
return err
}
self.c.Refresh(types.RefreshOptions{
Scope: []types.RefreshableView{types.STASH, types.FILES},
})
}
}
return nil
})
},
@@ -33,7 +33,8 @@ func (self *CredentialsHelper) PromptUserForCredential(passOrUname oscommands.Cr
HandleConfirm: func(input string) error {
ch <- input + "\n"
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
},
HandleClose: func() error {
ch <- "\n"
+6 -6
View File
@@ -61,9 +61,8 @@ func (self *DiffHelper) GetUpdateTaskForRenderingCommitsDiff(commit *models.Comm
args = append(args, path)
}
cmdObj := self.c.Git().Diff.DiffCmdObj(args)
task := types.NewRunPtyTask(cmdObj.GetCmd())
task.Prefix = style.FgYellow.Sprintf("%s %s-%s\n\n", self.c.Tr.ShowingDiffForRange, from.ShortRefName(), to.ShortRefName())
return task
prefix := style.FgYellow.Sprintf("%s %s-%s\n\n", self.c.Tr.ShowingDiffForRange, from.ShortRefName(), to.ShortRefName())
return types.NewRunPtyTaskWithPrefix(cmdObj.GetCmd(), prefix)
}
cmdObj := self.c.Git().Commit.ShowCmdObj(commit.Hash(), self.c.Modes().Filtering.GetPath())
@@ -72,18 +71,19 @@ func (self *DiffHelper) GetUpdateTaskForRenderingCommitsDiff(commit *models.Comm
func (self *DiffHelper) ExitDiffMode() error {
self.c.Modes().Diffing = diffing.New()
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
}
func (self *DiffHelper) RenderDiff() {
args := self.DiffArgs()
cmdObj := self.c.Git().Diff.DiffCmdObj(args)
task := types.NewRunPtyTask(cmdObj.GetCmd())
task.Prefix = style.FgMagenta.Sprintf(
prefix := style.FgMagenta.Sprintf(
"%s %s\n\n",
self.c.Tr.ShowingGitDiff,
"git diff "+strings.Join(args, " "),
)
task := types.NewRunPtyTaskWithPrefix(cmdObj.GetCmd(), prefix)
self.c.RenderToMainViews(types.RefreshMainOpts{
Pair: self.c.MainViewPairs().Normal,
+1 -1
View File
@@ -133,7 +133,7 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
if err := self.c.Git().WorkingTree.StageAll(); err != nil {
return err
}
_ = self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}})
self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}})
}
self.c.Contexts().LocalCommits.SetSelection(index)
+6 -7
View File
@@ -32,20 +32,18 @@ func (self *GpgHelper) WithGpgHandling(cmdObj *oscommands.CmdObj, configKey git_
return err
}
}
if err := self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: refreshScope}); err != nil {
return err
}
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: refreshScope})
return err
} else {
return self.runAndStream(cmdObj, waitingStatus, onSuccess, refreshScope)
}
return self.runAndStream(cmdObj, waitingStatus, onSuccess, refreshScope)
}
func (self *GpgHelper) runAndStream(cmdObj *oscommands.CmdObj, waitingStatus string, onSuccess func() error, refreshScope []types.RefreshableView) error {
return self.c.WithWaitingStatus(waitingStatus, func(gocui.Task) error {
if err := cmdObj.StreamOutput().Run(); err != nil {
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: refreshScope})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: refreshScope})
return fmt.Errorf(
self.c.Tr.GitCommandFailed, self.c.UserConfig().Keybinding.Universal.ExtrasMenu,
)
@@ -57,6 +55,7 @@ func (self *GpgHelper) runAndStream(cmdObj *oscommands.CmdObj, waitingStatus str
}
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: refreshScope})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: refreshScope})
return nil
})
}
@@ -15,7 +15,7 @@ type InlineStatusHelper struct {
windowHelper *WindowHelper
contextsWithInlineStatus map[types.ContextKey]*inlineStatusInfo
mutex *deadlock.Mutex
mutex deadlock.Mutex
}
func NewInlineStatusHelper(c *HelperCommon, windowHelper *WindowHelper) *InlineStatusHelper {
@@ -23,7 +23,6 @@ func NewInlineStatusHelper(c *HelperCommon, windowHelper *WindowHelper) *InlineS
c: c,
windowHelper: windowHelper,
contextsWithInlineStatus: make(map[types.ContextKey]*inlineStatusInfo),
mutex: &deadlock.Mutex{},
}
}
@@ -150,9 +150,8 @@ func isMergeConflictErr(errStr string) bool {
}
func (self *MergeAndRebaseHelper) CheckMergeOrRebaseWithRefreshOptions(result error, refreshOptions types.RefreshOptions) error {
if err := self.c.Refresh(refreshOptions); err != nil {
return err
}
self.c.Refresh(refreshOptions)
if result == nil {
return nil
} else if strings.Contains(result.Error(), "No changes - did you forget to use") {
@@ -162,9 +161,8 @@ func (self *MergeAndRebaseHelper) CheckMergeOrRebaseWithRefreshOptions(result er
} else if strings.Contains(result.Error(), "No rebase in progress?") {
// assume in this case that we're already done
return nil
} else {
return self.CheckForConflicts(result)
}
return self.CheckForConflicts(result)
}
func (self *MergeAndRebaseHelper) CheckMergeOrRebase(result error) error {
@@ -235,11 +233,9 @@ func (self *MergeAndRebaseHelper) PromptToContinueRebase() error {
// Need to refresh the files to be really sure if this is the case.
// We would otherwise be relying on lazygit's auto-refresh on focus,
// but this is not supported by all terminals or on all platforms.
if err := self.c.Refresh(types.RefreshOptions{
self.c.Refresh(types.RefreshOptions{
Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES},
}); err != nil {
return err
}
})
root := self.c.Contexts().Files.FileTreeViewModel.GetRoot()
if root.GetHasUnstagedChanges() {
@@ -458,7 +454,8 @@ func (self *MergeAndRebaseHelper) SquashMergeCommitted(refName, checkedOutBranch
if err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
}
}
+3 -3
View File
@@ -167,9 +167,9 @@ func (self *ModeHelper) ClearFiltering() error {
self.c.State().GetRepoState().SetScreenMode(types.SCREEN_NORMAL)
}
return self.c.Refresh(types.RefreshOptions{
self.c.Refresh(types.RefreshOptions{
Scope: []types.RefreshableView{types.COMMITS},
Then: func() error {
Then: func() {
// Find the commit that was last selected in filtering mode, and select it again after refreshing
if !self.c.Contexts().LocalCommits.SelectCommitByHash(selectedCommitHash) {
// If we couldn't find it (either because no commit was selected
@@ -178,9 +178,9 @@ func (self *ModeHelper) ClearFiltering() error {
// before we entered filtering
self.c.Contexts().LocalCommits.SelectCommitByHash(self.c.Modes().Filtering.GetSelectedCommitHash())
}
return nil
},
})
return nil
}
func (self *ModeHelper) SetSuppressRebasingMode(value bool) {
@@ -40,11 +40,9 @@ func (self *PatchBuildingHelper) Reset() error {
self.Escape()
}
if err := self.c.Refresh(types.RefreshOptions{
self.c.Refresh(types.RefreshOptions{
Scope: []types.RefreshableView{types.COMMIT_FILES},
}); err != nil {
return err
}
})
// refreshing the current context so that the secondary panel is hidden if necessary.
self.c.PostRefreshUpdate(self.c.Context().Current())
@@ -86,7 +84,7 @@ func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpt
oldState := context.GetState()
state := patch_exploring.NewState(diff, selectedLineIdx, context.GetView(), oldState)
state := patch_exploring.NewState(diff, selectedLineIdx, context.GetView(), oldState, self.c.UserConfig().Gui.UseHunkModeInStagingView)
context.SetState(state)
if state == nil {
self.Escape()
+8 -11
View File
@@ -51,7 +51,7 @@ func NewRefreshHelper(
}
}
func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
func (self *RefreshHelper) Refresh(options types.RefreshOptions) {
if options.Mode == types.ASYNC && options.Then != nil {
panic("RefreshOptions.Then doesn't work with mode ASYNC")
}
@@ -74,7 +74,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
)
}
f := func() error {
f := func() {
var scopeSet *set.Set[types.RefreshableView]
if len(options.Scope) == 0 {
// not refreshing staging/patch-building unless explicitly requested because we only need
@@ -191,22 +191,19 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
wg.Wait()
if options.Then != nil {
if err := options.Then(); err != nil {
return err
}
options.Then()
}
return nil
}
if options.Mode == types.BLOCK_UI {
self.c.OnUIThread(func() error {
return f()
f()
return nil
})
return nil
return
}
return f()
f()
}
func getScopeNames(scopes []types.RefreshableView) []string {
@@ -308,7 +305,7 @@ func (self *RefreshHelper) determineCheckedOutBranchName() string {
// In all other cases, get the branch name by asking git what branch is
// checked out. Note that if we're on a detached head (for reasons other
// than rebasing or bisecting, i.e. it was explicitly checked out), then
// this will return its hash.
// this will return an empty string.
if branchName, err := self.c.Git().Branch.CurrentBranchName(); err == nil {
return branchName
}
+28 -33
View File
@@ -46,7 +46,7 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
// loading a heap of commits is slow so we limit them whenever doing a reset
self.c.Contexts().LocalCommits.SetLimitCommits(true)
_ = self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true})
self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true})
}
localBranch, found := lo.Find(self.c.Model().Branches, func(branch *models.Branch) bool {
@@ -56,9 +56,9 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
withCheckoutStatus := func(f func(gocui.Task) error) error {
if found {
return self.c.WithInlineStatus(localBranch, types.ItemOperationCheckingOut, context.LOCAL_BRANCHES_CONTEXT_KEY, f)
} else {
return self.c.WithWaitingStatus(waitingStatus, f)
}
return self.c.WithWaitingStatus(waitingStatus, f)
}
return withCheckoutStatus(func(gocui.Task) error {
@@ -79,7 +79,7 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
Prompt: self.c.Tr.AutoStashPrompt,
HandleConfirm: func() error {
return withCheckoutStatus(func(gocui.Task) error {
if err := self.c.Git().Stash.Push(self.c.Tr.StashPrefix + ref); err != nil {
if err := self.c.Git().Stash.Push(fmt.Sprintf(self.c.Tr.AutoStashForCheckout, ref)); err != nil {
return err
}
if err := self.c.Git().Branch.Checkout(ref, cmdOptions); err != nil {
@@ -145,12 +145,10 @@ func (self *RefsHelper) CheckoutRemoteBranch(fullBranchName string, localBranchN
}
// Do a sync refresh to make sure the new branch is visible,
// so that we see an inline status when checking it out
if err := self.c.Refresh(types.RefreshOptions{
self.c.Refresh(types.RefreshOptions{
Mode: types.SYNC,
Scope: []types.RefreshableView{types.BRANCHES},
}); err != nil {
return err
}
})
return checkout(localBranchName)
},
},
@@ -183,9 +181,7 @@ func (self *RefsHelper) ResetToRef(ref string, strength string, envVars []string
// loading a heap of commits is slow so we limit them whenever doing a reset
self.c.Contexts().LocalCommits.SetLimitCommits(true)
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.BRANCHES, types.REFLOG, types.COMMITS}}); err != nil {
return err
}
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.BRANCHES, types.REFLOG, types.COMMITS}})
return nil
}
@@ -331,7 +327,7 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
}
}
refresh := func() error {
refresh := func() {
if self.c.Context().Current() != self.c.Contexts().Branches {
self.c.Context().Push(self.c.Contexts().Branches, types.OnFocusOpts{})
}
@@ -339,7 +335,7 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
self.c.Contexts().LocalCommits.SetSelection(0)
self.c.Contexts().Branches.SetSelection(0)
return self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true})
self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true})
}
self.c.Prompt(types.PromptOpts{
@@ -359,20 +355,16 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
Title: self.c.Tr.AutoStashTitle,
Prompt: self.c.Tr.AutoStashPrompt,
HandleConfirm: func() error {
if err := self.c.Git().Stash.Push(self.c.Tr.StashPrefix + newBranchName); err != nil {
if err := self.c.Git().Stash.Push(fmt.Sprintf(self.c.Tr.AutoStashForNewBranch, newBranchName)); err != nil {
return err
}
if err := newBranchFunc(newBranchName, from); err != nil {
return err
}
popErr := self.c.Git().Stash.Pop(0)
err := self.c.Git().Stash.Pop(0)
// Branch switch successful so re-render the UI even if the pop operation failed (e.g. conflict).
refreshError := refresh()
if popErr != nil {
// An error from pop is the more important one to report to the user
return popErr
}
return refreshError
refresh()
return err
},
})
@@ -382,7 +374,8 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
return err
}
return refresh()
refresh()
return nil
},
})
@@ -396,7 +389,7 @@ func (self *RefsHelper) MoveCommitsToNewBranch() error {
return err
}
withNewBranchNamePrompt := func(baseBranchName string, f func(string, string) error) error {
withNewBranchNamePrompt := func(baseBranchName string, f func(string) error) error {
prompt := utils.ResolvePlaceholderString(
self.c.Tr.NewBranchNameBranchOff,
map[string]string{
@@ -415,7 +408,7 @@ func (self *RefsHelper) MoveCommitsToNewBranch() error {
self.c.LogAction(self.c.Tr.MoveCommitsToNewBranch)
newBranchName := SanitizedBranchName(response)
return self.c.WithWaitingStatus(self.c.Tr.MovingCommitsToNewBranchStatus, func(gocui.Task) error {
return f(currentBranch.Name, newBranchName)
return f(newBranchName)
})
},
})
@@ -454,8 +447,8 @@ func (self *RefsHelper) MoveCommitsToNewBranch() error {
{
Label: fmt.Sprintf(self.c.Tr.MoveCommitsToNewBranchFromBaseItem, shortBaseBranchName),
OnPress: func() error {
return withNewBranchNamePrompt(shortBaseBranchName, func(currentBranch string, newBranchName string) error {
return self.moveCommitsToNewBranchOffOfMainBranch(currentBranch, newBranchName, baseBranchRef)
return withNewBranchNamePrompt(shortBaseBranchName, func(newBranchName string) error {
return self.moveCommitsToNewBranchOffOfMainBranch(newBranchName, baseBranchRef)
})
},
},
@@ -469,14 +462,14 @@ func (self *RefsHelper) MoveCommitsToNewBranch() error {
})
}
func (self *RefsHelper) moveCommitsToNewBranchStackedOnCurrentBranch(currentBranch string, newBranchName string) error {
func (self *RefsHelper) moveCommitsToNewBranchStackedOnCurrentBranch(newBranchName string) error {
if err := self.c.Git().Branch.NewWithoutCheckout(newBranchName, "HEAD"); err != nil {
return err
}
mustStash := IsWorkingTreeDirty(self.c.Model().Files)
if mustStash {
if err := self.c.Git().Stash.Push(self.c.Tr.StashPrefix + currentBranch); err != nil {
if err := self.c.Git().Stash.Push(fmt.Sprintf(self.c.Tr.AutoStashForNewBranch, newBranchName)); err != nil {
return err
}
}
@@ -498,17 +491,18 @@ func (self *RefsHelper) moveCommitsToNewBranchStackedOnCurrentBranch(currentBran
self.c.Contexts().LocalCommits.SetSelection(0)
self.c.Contexts().Branches.SetSelection(0)
return self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true})
self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true})
return nil
}
func (self *RefsHelper) moveCommitsToNewBranchOffOfMainBranch(currentBranch string, newBranchName string, baseBranchRef string) error {
func (self *RefsHelper) moveCommitsToNewBranchOffOfMainBranch(newBranchName string, baseBranchRef string) error {
commitsToCherryPick := lo.Filter(self.c.Model().Commits, func(commit *models.Commit, _ int) bool {
return commit.Status == models.StatusUnpushed
})
mustStash := IsWorkingTreeDirty(self.c.Model().Files)
if mustStash {
if err := self.c.Git().Stash.Push(self.c.Tr.StashPrefix + currentBranch); err != nil {
if err := self.c.Git().Stash.Push(fmt.Sprintf(self.c.Tr.AutoStashForNewBranch, newBranchName)); err != nil {
return err
}
}
@@ -536,7 +530,8 @@ func (self *RefsHelper) moveCommitsToNewBranchOffOfMainBranch(currentBranch stri
self.c.Contexts().LocalCommits.SetSelection(0)
self.c.Contexts().Branches.SetSelection(0)
return self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true})
self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true})
return nil
}
func (self *RefsHelper) CanMoveCommitsToNewBranch() *types.DisabledReason {
@@ -563,7 +558,7 @@ func (self *RefsHelper) CanMoveCommitsToNewBranch() *types.DisabledReason {
// SanitizedBranchName will remove all spaces in favor of a dash "-" to meet
// git's branch naming requirement.
func SanitizedBranchName(input string) string {
return strings.Replace(input, " ", "-", -1)
return strings.ReplaceAll(input, " ", "-")
}
// Checks if the given branch name is a remote branch, and returns the name of
@@ -62,12 +62,13 @@ func (self *StagingHelper) RefreshStagingPanel(focusOpts types.OnFocusOpts) {
mainContext.GetMutex().Lock()
secondaryContext.GetMutex().Lock()
hunkMode := self.c.UserConfig().Gui.UseHunkModeInStagingView
mainContext.SetState(
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainContext.GetView(), mainContext.GetState()),
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainContext.GetView(), mainContext.GetState(), hunkMode),
)
secondaryContext.SetState(
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetView(), secondaryContext.GetState()),
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetView(), secondaryContext.GetState(), hunkMode),
)
mainState := mainContext.GetState()
@@ -385,9 +385,8 @@ func splitMainPanelSideBySide(args WindowArrangementArgs) bool {
default:
if args.Width < 200 && args.Height > 30 { // 2 80 character width panels + 40 width for side panel
return false
} else {
return true
}
return true
}
}
@@ -431,11 +430,11 @@ func sidePanelChildren(args WindowArrangementArgs) func(width int, height int) [
Window: window,
Weight: 1,
}
} else {
return &boxlayout.Box{
Window: window,
Size: 0,
}
}
return &boxlayout.Box{
Window: window,
Size: 0,
}
}
@@ -469,33 +468,33 @@ func sidePanelChildren(args WindowArrangementArgs) func(width int, height int) [
accordionBox(&boxlayout.Box{Window: "commits", Weight: 1}),
accordionBox(getDefaultStashWindowBox(args)),
}
} else {
squashedHeight := 1
if height >= 21 {
squashedHeight = 3
}
}
squashedSidePanelBox := func(window string) *boxlayout.Box {
if window == args.CurrentSideWindow {
return &boxlayout.Box{
Window: window,
Weight: 1,
}
} else {
return &boxlayout.Box{
Window: window,
Size: squashedHeight,
}
squashedHeight := 1
if height >= 21 {
squashedHeight = 3
}
squashedSidePanelBox := func(window string) *boxlayout.Box {
if window == args.CurrentSideWindow {
return &boxlayout.Box{
Window: window,
Weight: 1,
}
}
return []*boxlayout.Box{
squashedSidePanelBox("status"),
squashedSidePanelBox("files"),
squashedSidePanelBox("branches"),
squashedSidePanelBox("commits"),
squashedSidePanelBox("stash"),
return &boxlayout.Box{
Window: window,
Size: squashedHeight,
}
}
return []*boxlayout.Box{
squashedSidePanelBox("status"),
squashedSidePanelBox("files"),
squashedSidePanelBox("branches"),
squashedSidePanelBox("commits"),
squashedSidePanelBox("stash"),
}
}
}
@@ -210,9 +210,7 @@ func (self *WorkingTreeHelper) promptToStageAllAndRetry(retry func() error) erro
if err := self.c.Git().WorkingTree.StageAll(); err != nil {
return err
}
if err := self.syncRefresh(); err != nil {
return err
}
self.syncRefresh()
return retry()
},
@@ -222,8 +220,8 @@ func (self *WorkingTreeHelper) promptToStageAllAndRetry(retry func() error) erro
}
// for when you need to refetch files before continuing an action. Runs synchronously.
func (self *WorkingTreeHelper) syncRefresh() error {
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}})
func (self *WorkingTreeHelper) syncRefresh() {
self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}})
}
func (self *WorkingTreeHelper) prepareFilesForCommit() error {
@@ -235,7 +233,7 @@ func (self *WorkingTreeHelper) prepareFilesForCommit() error {
return err
}
return self.syncRefresh()
self.syncRefresh()
}
return nil
@@ -245,7 +243,7 @@ func (self *WorkingTreeHelper) commitPrefixConfigsForRepo() []config.CommitPrefi
cfg, ok := self.c.UserConfig().Git.CommitPrefixes[self.c.Git().RepoPaths.RepoName()]
if ok {
return append(cfg, self.c.UserConfig().Git.CommitPrefix...)
} else {
return self.c.UserConfig().Git.CommitPrefix
}
return self.c.UserConfig().Git.CommitPrefix
}
+20 -18
View File
@@ -132,24 +132,24 @@ func (self *WorktreeHelper) NewWorktreeCheckout(base string, canCheckoutBase boo
},
})
return nil
} else {
// prompt for the new branch name where a blank means we just check out the branch
self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.NewBranchName,
HandleConfirm: func(branchName string) error {
if branchName == "" {
return errors.New(self.c.Tr.BranchNameCannotBeBlank)
}
opts.Branch = branchName
return f()
},
})
return nil
}
// prompt for the new branch name where a blank means we just check out the branch
self.c.Prompt(types.PromptOpts{
Title: self.c.Tr.NewBranchName,
HandleConfirm: func(branchName string) error {
if branchName == "" {
return errors.New(self.c.Tr.BranchNameCannotBeBlank)
}
opts.Branch = branchName
return f()
},
})
return nil
},
})
@@ -198,7 +198,8 @@ func (self *WorktreeHelper) Remove(worktree *models.Worktree, force bool) error
}
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}})
return nil
})
},
})
@@ -214,7 +215,8 @@ func (self *WorktreeHelper) Detach(worktree *models.Worktree) error {
if err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}})
return nil
})
}
+3 -2
View File
@@ -109,10 +109,11 @@ func (self *ListController) handleLineChangeAux(f func(int), change int) error {
// we're not constantly re-rendering the main view.
cursorMoved := before != after
if cursorMoved {
if change == -1 {
switch change {
case -1:
checkScrollUp(self.context.GetViewTrait(), self.c.UserConfig(),
self.context.ModelIndexToViewIndex(before), self.context.ModelIndexToViewIndex(after))
} else if change == 1 {
case 1:
checkScrollDown(self.context.GetViewTrait(), self.c.UserConfig(),
self.context.ModelIndexToViewIndex(before), self.context.ModelIndexToViewIndex(after))
}
+1 -1
View File
@@ -87,7 +87,7 @@ func (self *ListControllerTrait[T]) itemRangeSelected(callbacks ...func([]T, int
}
}
func (self *ListControllerTrait[T]) itemsSelected(callbacks ...func([]T) *types.DisabledReason) func() *types.DisabledReason { //nolint:unused
func (self *ListControllerTrait[T]) itemsSelected(callbacks ...func([]T) *types.DisabledReason) func() *types.DisabledReason {
return func() *types.DisabledReason {
items, _, _ := self.getSelectedItems()
if len(items) == 0 {
+80 -49
View File
@@ -343,6 +343,10 @@ func (self *LocalCommitsController) fixup(selectedCommits []*models.Commit, star
}
func (self *LocalCommitsController) reword(commit *models.Commit) error {
commitIdx := self.context().GetSelectedLineIdx()
if self.c.Git().Config.NeedsGpgSubprocessForCommit() && !self.isHeadCommit(commitIdx) {
return errors.New(self.c.Tr.DisabledForGPG)
}
commitMessage, err := self.c.Git().Commit.GetCommitMessage(commit.Hash())
if err != nil {
return err
@@ -352,7 +356,7 @@ func (self *LocalCommitsController) reword(commit *models.Commit) error {
}
self.c.Helpers().Commits.OpenCommitMessagePanel(
&helpers.OpenCommitMessagePanelOpts{
CommitIndex: self.context().GetSelectedLineIdx(),
CommitIndex: commitIdx,
InitialMessage: commitMessage,
SummaryTitle: self.c.Tr.Actions.RewordCommit,
DescriptionTitle: self.c.Tr.CommitDescriptionTitle,
@@ -388,7 +392,8 @@ func (self *LocalCommitsController) switchFromCommitMessagePanelToEditor(filepat
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
}
func (self *LocalCommitsController) handleReword(summary string, description string) error {
@@ -404,7 +409,8 @@ func (self *LocalCommitsController) handleReword(summary string, description str
if err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
})
}
@@ -515,9 +521,8 @@ func (self *LocalCommitsController) edit(selectedCommits []*models.Commit, start
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
err,
types.RefreshOptions{
Mode: types.BLOCK_UI, Then: func() error {
Mode: types.BLOCK_UI, Then: func() {
self.restoreSelectionRangeAndMode(selectionRangeAndMode)
return nil
},
})
}
@@ -543,7 +548,7 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
err := self.c.Git().Rebase.EditRebase(commitsToEdit[len(commitsToEdit)-1].Hash())
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
err,
types.RefreshOptions{Mode: types.BLOCK_UI, Then: func() error {
types.RefreshOptions{Mode: types.BLOCK_UI, Then: func() {
todos := make([]*models.Commit, 0, len(commitsToEdit)-1)
for _, c := range commitsToEdit[:len(commitsToEdit)-1] {
// Merge commits can't be set to "edit", so just skip them
@@ -554,12 +559,11 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
if len(todos) > 0 {
err := self.updateTodos(todo.Edit, todos)
if err != nil {
return err
self.c.Log.Errorf("error when updating todos: %v", err)
}
}
self.restoreSelectionRangeAndMode(selectionRangeAndMode)
return nil
}})
})
}
@@ -638,9 +642,11 @@ func (self *LocalCommitsController) updateTodos(action todo.TodoCommand, selecte
return err
}
return self.c.Refresh(types.RefreshOptions{
self.c.Refresh(types.RefreshOptions{
Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS},
})
return nil
}
func (self *LocalCommitsController) rewordEnabled(commit *models.Commit) *types.DisabledReason {
@@ -677,9 +683,10 @@ func (self *LocalCommitsController) moveDown(selectedCommits []*models.Commit, s
}
self.context().MoveSelection(1)
return self.c.Refresh(types.RefreshOptions{
self.c.Refresh(types.RefreshOptions{
Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS},
})
return nil
}
return self.c.WithWaitingStatusSync(self.c.Tr.MovingStatus, func() error {
@@ -700,9 +707,10 @@ func (self *LocalCommitsController) moveUp(selectedCommits []*models.Commit, sta
}
self.context().MoveSelection(-1)
return self.c.Refresh(types.RefreshOptions{
self.c.Refresh(types.RefreshOptions{
Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS},
})
return nil
}
return self.c.WithWaitingStatusSync(self.c.Tr.MovingStatus, func() error {
@@ -717,27 +725,20 @@ func (self *LocalCommitsController) moveUp(selectedCommits []*models.Commit, sta
}
func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
var handleCommit func() error
if self.isSelectedHeadCommit() {
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.AmendCommitTitle,
Prompt: self.c.Tr.AmendCommitPrompt,
HandleConfirm: func() error {
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
if err := self.c.Helpers().AmendHelper.AmendHead(); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
})
},
})
return nil
}
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.AmendCommitTitle,
Prompt: self.c.Tr.AmendCommitPrompt,
HandleConfirm: func() error {
handleCommit = func() error {
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
if err := self.c.Helpers().AmendHelper.AmendHead(); err != nil {
return err
}
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
})
}
} else {
handleCommit = func() error {
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.AmendCommit)
@@ -745,7 +746,17 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
})
})
},
}
}
if self.c.UserConfig().Gui.SkipAmendWarning {
return handleCommit()
}
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.AmendCommitTitle,
Prompt: self.c.Tr.AmendCommitPrompt,
HandleConfirm: handleCommit,
})
return nil
@@ -798,7 +809,8 @@ func (self *LocalCommitsController) resetAuthor(start, end int) error {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
})
}
@@ -813,7 +825,8 @@ func (self *LocalCommitsController) setAuthor(start, end int) error {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
})
},
})
@@ -831,7 +844,8 @@ func (self *LocalCommitsController) addCoAuthor(start, end int) error {
if err := self.c.Git().Rebase.AddCommitCoAuthor(self.c.Model().Commits, start, end, value); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
return nil
})
},
})
@@ -859,14 +873,31 @@ func (self *LocalCommitsController) revert(commits []*models.Commit, start, end
HandleConfirm: func() error {
self.c.LogAction(self.c.Tr.Actions.RevertCommit)
return self.c.WithWaitingStatusSync(self.c.Tr.RevertingStatus, func() error {
mustStash := helpers.IsWorkingTreeDirty(self.c.Model().Files)
if mustStash {
if err := self.c.Git().Stash.Push(self.c.Tr.AutoStashForReverting); err != nil {
return err
}
}
result := self.c.Git().Commit.Revert(hashes, isMerge)
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(result); err != nil {
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(result, types.RefreshOptions{Mode: types.SYNC}); err != nil {
return err
}
self.context().MoveSelection(len(commits))
return self.c.Refresh(types.RefreshOptions{
Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS, types.BRANCHES},
})
self.context().FocusLine()
if mustStash {
if err := self.c.Git().Stash.Pop(0); err != nil {
return err
}
self.c.Refresh(types.RefreshOptions{
Scope: []types.RefreshableView{types.STASH, types.FILES},
})
}
return nil
})
},
})
@@ -902,7 +933,8 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
}
self.context().MoveSelectedLine(1)
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.SYNC})
return nil
})
})
},
@@ -1002,7 +1034,8 @@ func (self *LocalCommitsController) createAmendCommit(commit *models.Commit, inc
}
self.context().MoveSelectedLine(1)
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC})
self.c.Refresh(types.RefreshOptions{Mode: types.SYNC})
return nil
})
},
OnSwitchToEditor: nil,
@@ -1128,9 +1161,7 @@ func (self *LocalCommitsController) openSearch() error {
// we usually lazyload these commits but now that we're searching we need to load them now
if self.context().GetLimitCommits() {
self.context().SetLimitCommits(false)
if err := self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS}}); err != nil {
return err
}
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS}})
}
return self.c.Helpers().Search.OpenSearchPrompt(self.context())
@@ -1150,9 +1181,10 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
}
return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func(gocui.Task) error {
return self.c.Refresh(
self.c.Refresh(
types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS}},
)
return nil
})
},
},
@@ -1202,12 +1234,13 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
self.c.GetAppState().GitLogOrder = value
self.c.SaveAppStateAndLogError()
return self.c.WithWaitingStatus(self.c.Tr.LoadingCommits, func(gocui.Task) error {
return self.c.Refresh(
self.c.Refresh(
types.RefreshOptions{
Mode: types.SYNC,
Scope: []types.RefreshableView{types.COMMITS},
},
)
return nil
})
}
}
@@ -1248,9 +1281,7 @@ func (self *LocalCommitsController) GetOnFocus() func(types.OnFocusOpts) {
context := self.context()
if context.GetSelectedLineIdx() > COMMIT_THRESHOLD && context.GetLimitCommits() {
context.SetLimitCommits(false)
self.c.OnWorker(func(_ gocui.Task) error {
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}})
})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS}})
}
}
}
@@ -277,7 +277,7 @@ func (self *MergeConflictsController) pickSelection(selection mergeconflicts.Sel
}
if self.context().GetState().AllConflictsResolved() {
return self.onLastConflictResolved()
self.onLastConflictResolved()
}
return nil
@@ -314,10 +314,10 @@ func (self *MergeConflictsController) resolveConflict(selection mergeconflicts.S
return true, os.WriteFile(state.GetPath(), []byte(content), 0o644)
}
func (self *MergeConflictsController) onLastConflictResolved() error {
func (self *MergeConflictsController) onLastConflictResolved() {
// as part of refreshing files, we handle the situation where a file has had
// its merge conflicts resolved.
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES}})
}
func (self *MergeConflictsController) withRenderAndFocus(f func() error) func() error {
@@ -116,16 +116,16 @@ func (self *PatchBuildingController) ToggleSelectionAndRefresh() error {
return err
}
return self.c.Refresh(types.RefreshOptions{
self.c.Refresh(types.RefreshOptions{
Scope: []types.RefreshableView{types.PATCH_BUILDING, types.COMMIT_FILES},
})
return nil
}
func (self *PatchBuildingController) toggleSelection() error {
self.context().GetMutex().Lock()
defer self.context().GetMutex().Unlock()
toggleFunc := self.c.Git().Patch.PatchBuilder.AddFileLineRange
filename := self.c.Contexts().CommitFiles.GetSelectedPath()
if filename == "" {
return nil
@@ -133,19 +133,26 @@ func (self *PatchBuildingController) toggleSelection() error {
state := self.context().GetState()
// Get added/deleted lines in the selected patch range
lineIndicesToToggle := state.LineIndicesOfAddedOrDeletedLinesInSelectedPatchRange()
if len(lineIndicesToToggle) == 0 {
// Only context lines or header lines selected, so nothing to do
return nil
}
includedLineIndices, err := self.c.Git().Patch.PatchBuilder.GetFileIncLineIndices(filename)
if err != nil {
return err
}
currentLineIsStaged := lo.Contains(includedLineIndices, state.GetSelectedPatchLineIdx())
if currentLineIsStaged {
toggleFunc := self.c.Git().Patch.PatchBuilder.AddFileLineRange
firstSelectedChangeLineIsStaged := lo.Contains(includedLineIndices, lineIndicesToToggle[0])
if firstSelectedChangeLineIsStaged {
toggleFunc = self.c.Git().Patch.PatchBuilder.RemoveFileLineRange
}
// add range of lines to those set for the file
firstLineIdx, lastLineIdx := state.SelectedPatchRange()
if err := toggleFunc(filename, firstLineIdx, lastLineIdx); err != nil {
if err := toggleFunc(filename, lineIndicesToToggle); err != nil {
// might actually want to return an error here
self.c.Log.Error(err)
}
@@ -154,6 +161,8 @@ func (self *PatchBuildingController) toggleSelection() error {
state.SetLineSelectMode()
}
state.SelectNextStageableLineOfSameIncludedState(self.context().GetIncludedLineIndices(), firstSelectedChangeLineIsStaged)
return nil
}
@@ -161,7 +170,7 @@ func (self *PatchBuildingController) Escape() error {
context := self.c.Contexts().CustomPatchBuilder
state := context.GetState()
if state.SelectingRange() || state.SelectingHunk() {
if state.SelectingRange() || state.SelectingHunkEnabledByUser() {
state.SetLineSelectMode()
self.c.PostRefreshUpdate(context)
return nil
@@ -223,13 +223,13 @@ func (self *PatchExplorerController) HandleNextLineRange() error {
}
func (self *PatchExplorerController) HandlePrevHunk() error {
self.context.GetState().CycleHunk(false)
self.context.GetState().SelectPreviousHunk()
return nil
}
func (self *PatchExplorerController) HandleNextHunk() error {
self.context.GetState().CycleHunk(true)
self.context.GetState().SelectNextHunk()
return nil
}
@@ -133,7 +133,7 @@ func (self *RemoteBranchesController) context() *context.RemoteBranchesContext {
}
func (self *RemoteBranchesController) delete(selectedBranches []*models.RemoteBranch) error {
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(selectedBranches)
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(selectedBranches, true)
}
func (self *RemoteBranchesController) merge(selectedBranch *models.RemoteBranch) error {
@@ -150,7 +150,7 @@ func (self *RemoteBranchesController) createSortMenu() error {
self.c.GetAppState().RemoteBranchSortOrder = sortOrder
self.c.SaveAppStateAndLogError()
self.c.Contexts().RemoteBranches.SetSelection(0)
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.REMOTES}})
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.REMOTES}})
}
return nil
},
@@ -181,7 +181,8 @@ func (self *RemoteBranchesController) setAsUpstream(selectedBranch *models.Remot
return err
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
return nil
},
})
+8 -7
View File
@@ -148,12 +148,10 @@ func (self *RemotesController) add() error {
// Do a sync refresh of the remotes so that we can select
// the new one. Loading remotes is not expensive, so we can
// afford it.
if err := self.c.Refresh(types.RefreshOptions{
self.c.Refresh(types.RefreshOptions{
Scope: []types.RefreshableView{types.REMOTES},
Mode: types.SYNC,
}); err != nil {
return err
}
})
// Select the new remote
for idx, remote := range self.c.Model().Remotes {
@@ -185,7 +183,8 @@ func (self *RemotesController) remove(remote *models.Remote) error {
return err
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
return nil
},
})
@@ -232,7 +231,8 @@ func (self *RemotesController) edit(remote *models.Remote) error {
if err := self.c.Git().Remote.UpdateRemoteUrl(updatedRemoteName, updatedRemoteUrl); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
return nil
},
})
@@ -250,9 +250,10 @@ func (self *RemotesController) fetch(remote *models.Remote) error {
return err
}
return self.c.Refresh(types.RefreshOptions{
self.c.Refresh(types.RefreshOptions{
Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES},
Mode: types.ASYNC,
})
return nil
})
}
@@ -88,11 +88,11 @@ func (self *RenameSimilarityThresholdController) applyChange() error {
switch currentContext.GetKey() {
// we make an exception for our files context, because it actually need to refresh its state afterwards.
case context.FILES_CONTEXT_KEY:
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
default:
currentContext.HandleRenderToMain()
return nil
}
return nil
}
func (self *RenameSimilarityThresholdController) isShowingRenames() bool {
+5 -3
View File
@@ -168,7 +168,7 @@ func (self *StagingController) EditFile() error {
}
func (self *StagingController) Escape() error {
if self.context.GetState().SelectingRange() || self.context.GetState().SelectingHunk() {
if self.context.GetState().SelectingRange() || self.context.GetState().SelectingHunkEnabledByUser() {
self.context.GetState().SetLineSelectMode()
self.c.PostRefreshUpdate(self.context)
return nil
@@ -221,7 +221,8 @@ func (self *StagingController) applySelectionAndRefresh(reverse bool) error {
return err
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.STAGING}})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.STAGING}})
return nil
}
func (self *StagingController) applySelection(reverse bool) error {
@@ -275,7 +276,8 @@ func (self *StagingController) EditHunkAndRefresh() error {
return err
}
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.STAGING}})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.STAGING}})
return nil
}
func (self *StagingController) editHunk() error {
+14 -9
View File
@@ -3,6 +3,7 @@ package controllers
import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/style"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
@@ -82,8 +83,10 @@ func (self *StashController) GetOnRenderToMain() func() {
if stashEntry == nil {
task = types.NewRenderStringTask(self.c.Tr.NoStashEntries)
} else {
task = types.NewRunPtyTask(
prefix := style.FgYellow.Sprintf("%s\n\n", stashEntry.Description())
task = types.NewRunPtyTaskWithPrefix(
self.c.Git().Stash.ShowStashEntryCmdObj(stashEntry.Index).GetCmd(),
prefix,
)
}
@@ -107,7 +110,7 @@ func (self *StashController) handleStashApply(stashEntry *models.StashEntry) err
apply := func() error {
self.c.LogAction(self.c.Tr.Actions.Stash)
err := self.c.Git().Stash.Apply(stashEntry.Index)
_ = self.postStashRefresh()
self.postStashRefresh()
if err != nil {
return err
}
@@ -136,7 +139,7 @@ func (self *StashController) handleStashPop(stashEntry *models.StashEntry) error
pop := func() error {
self.c.LogAction(self.c.Tr.Actions.Stash)
err := self.c.Git().Stash.Pop(stashEntry.Index)
_ = self.postStashRefresh()
self.postStashRefresh()
if err != nil {
return err
}
@@ -170,11 +173,12 @@ func (self *StashController) handleStashDrop(stashEntries []*models.StashEntry)
startIndex := stashEntries[0].Index
for range stashEntries {
err := self.c.Git().Stash.Drop(startIndex)
_ = self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}})
if err != nil {
return err
}
}
self.context().CollapseRangeSelectionToTop()
return nil
},
})
@@ -182,12 +186,12 @@ func (self *StashController) handleStashDrop(stashEntries []*models.StashEntry)
return nil
}
func (self *StashController) postStashRefresh() error {
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH, types.FILES}})
func (self *StashController) postStashRefresh() {
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH, types.FILES}})
}
func (self *StashController) handleNewBranchOffStashEntry(stashEntry *models.StashEntry) error {
return self.c.Helpers().Refs.NewBranch(stashEntry.RefName(), stashEntry.Description(), "")
return self.c.Helpers().Refs.NewBranch(stashEntry.FullRefName(), stashEntry.Description(), "")
}
func (self *StashController) handleRenameStashEntry(stashEntry *models.StashEntry) error {
@@ -205,12 +209,13 @@ func (self *StashController) handleRenameStashEntry(stashEntry *models.StashEntr
self.c.LogAction(self.c.Tr.Actions.RenameStash)
err := self.c.Git().Stash.Rename(stashEntry.Index, response)
if err != nil {
_ = self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}})
return err
}
self.context().SetSelection(0) // Select the renamed stash
self.context().FocusLine()
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}})
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH}})
return nil
},
})

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