Compare commits

..
82 Commits
Author SHA1 Message Date
Jesse Duffield 759ddd3e80 five 2024-12-01 17:09:45 +11:00
Jesse Duffield b431a354aa four 2024-12-01 17:09:45 +11:00
Jesse Duffield 24a3b815c5 fixup! blah 2024-12-01 17:09:45 +11:00
Jesse Duffield 7efb900336 blah 2024-12-01 17:08:57 +11:00
Jesse Duffield ab8e1f0265 three 2024-12-01 17:08:23 +11:00
Jesse Duffield 8cf3d8b269 two 2024-12-01 16:47:03 +11:00
Jesse Duffield e6b5fb93eb one 2024-12-01 16:47:00 +11:00
Stefan Haller 649a8b7276 Improve editing a commit
In 67b8ef449c we changed the "edit" command to insert a "break" after the
selected commit, rather than setting the selected todo to "edit". The reason for
doing this was that it now works for merge commits too.

Back then, I claimed "In most cases the behavior is exactly the same as before."
Unfortunately that's not true, there are two reasons why the previous behavior
was better (both are demonstrated by tests earlier in this branch):
- when editing the last commit of a branch in the middle of a stack of branches,
  we are now missing the update-ref todo after it, which means that amending the
  commit breaks the stack
- it breaks auto-amending (see the added test earlier in this branch for an
  explanation)

For these reasons, we are going back to the previous approach of setting the
selected commit to "edit" whenever possible, i.e. unless it's a merge commit.

The only scenario where this could still be a problem is when you have a stack
of branches, and the last commit of one of the branches in the stack is a merge
commit, and you try to edit that. In my experience with stacked branches this is
very unlikely, in almost all cases my stacked branches are linear.
2024-11-29 18:29:47 +01:00
Stefan Haller 345c533d64 Extract helper methods
We'll reuse them in the next commit.
2024-11-29 18:24:00 +01:00
Stefan Haller 5e542c3992 Filter out merge commits when generating todo changes in InteractiveRebase
We will need this because under some conditions we are going to use this
function to edit a range of commits, and we can't set merge commits to "edit".
This corresponds to the code in startInteractiveRebaseWithEdit which has similar
logic.

It is a bit unfortunate that we will have these two different ways of setting
todos to edit: startInteractiveRebaseWithEdit does it after stopping in the
rebase, in the Then function of its refresh, but InteractiveRebase does it in
the daemon with a ChangeTodoActionsInstruction. It still makes sense though,
given how InteractiveRebase works.

This not only affects "edit", but also "drop", "fixup", and "squash".
Previously, when trying to use these for a range selection that includes a merge
commit, they would fail with the cryptic error message "Some todos not found in
git-rebase-todo"; now they simply exclude the merge commit. I'm not sure if one
is better or worse than the other, and we should probably simply disable the
commands when a merge commit is selected, but that's out of scope in this PR.
2024-11-29 18:24:00 +01:00
Stefan Haller 0b33634c73 Add test for editing several commits right after a merge commit
This is very similar to edit_range_select_outside_rebase.go, except that it
selects commits right after, and including, a merge commit.

This test already works correctly. The reason we add it is that we are going to
have two different implementations of the `e` command depending on whether the
last selected commit is a merge commit, and we want to make sure they both work
with a range selection.
2024-11-29 18:24:00 +01:00
Stefan Haller 8d6da58bfa Add test to auto-amend a commit after pressing e on it
Auto-amending is a little-known feature of git that is very convenient once you
know it: whenever you stop at a commit marked with `edit` in an interactive
rebase, you can make changes and stage them, and when you continue the rebase
they automatically get amended to the commit you had stopped at. This is so
convenient because making changes to a commit is one of the main reasons why you
edit a commit.

Unfortunately this currently doesn't work in lazygit because we don't actually
use `edit` to stop at the first commit (instead, we add a `break` todo after it,
which doesn't have the auto-amend functionality).

We'll improve this later in this branch.
2024-11-29 17:47:31 +01:00
Stefan Haller cd6d92da1d Add test for editing the last commit of a branch in a stack
The test demonstrates that the "update-ref" todo after the selected commit is
missing, which means when we amend the commit it'll break the stack.
2024-11-29 17:47:30 +01:00
Stefan Haller 46a333407d Cleanup: remove a no-op Focus() call 2024-11-29 17:33:46 +01:00
Jesse Duffield c3cf48cc49 Allow user to select remote and branch when creating a PR (#1889)
When creating a PR against a selected branch (via O = "create pull
request
options"), the user will first be asked to select a remote (if there is
more
than one). After that, the suggestion area is populated with all remote
branches
at that origin - instead of all local ones. After all, creating a PR
against a
branch that doesn't exist on the remote won't work.

Please note that for the "PR is not filed against 'origin' remote" use
case
(e.g. when contributing via a fork that is 'origin' to a GitHub project
that is
'upstream'), the opened URL will not be correct. This is not a
regression and
will be fixed in an upcoming PR.

Fixes #1826.
2024-11-23 12:10:51 +11:00
Moritz Haase 949e131ebe pkg/gui: Allow user to select remote and branch when creating a PR
When creating a PR against a selected branch (via O = "create pull request
options"), the user will first be asked to select a remote (if there is more
than one). After that, the suggestion area is populated with all remote branches
at that origin - instead of all local ones. After all, creating a PR against a
branch that doesn't exist on the remote won't work.

Please note that for the "PR is not filed against 'origin' remote" use case
(e.g. when contributing via a fork that is 'origin' to a GitHub project that is
'upstream'), the opened URL will not be correct. This is not a regression and
will be fixed in an upcoming PR.

Fixes #1826.
2024-11-23 11:52:21 +11:00
Jesse Duffield b62546c391 Allow DiffContextSize to be decreased to zero (#4050)
- **PR Description**

Per #4012, the diff context size should be able to be decreased to zero.
I update the type def for DiffContextSize to be an unsigned integer and
add saturated add and subtraction in `context_lines_controller.go` where
the variable is updated (++ or --)

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

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

<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for
examples
-->
2024-11-19 08:27:38 +11:00
LU Jialin de8dc935a3 use unsigned integer in test and fix CI/linter complaint 2024-11-19 08:21:19 +11:00
LU Jialin dd765801db add test case for decreasing Diff Context length to zero 2024-11-19 08:21:19 +11:00
LU Jialin 111407d9a6 use an unsigned_64 for DiffContextSize and add saturated add/subtract 2024-11-19 08:21:19 +11:00
Jesse Duffield 9f03f9e95d ci: update upload-artifact and download-artifact actions to v4 (#4063)
- **PR Description**

v3 of `actions/upload-artifact` and `actions/download-artifact` will be
fully deprecated by **5 December 2024**. Jobs that are scheduled to run
during the brownout periods will also fail. See:

1.
https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/
2.
https://github.blog/changelog/2024-11-05-notice-of-breaking-changes-for-github-actions/

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

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

<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for
examples
-->
2024-11-17 12:06:22 +11:00
Eng Zer Jun 181b00b758 ci: update upload-artifact and download-artifact actions to v4
v3 of `actions/upload-artifact` and `actions/download-artifact` will be
fully deprecated by 5 December 2024. Jobs that are scheduled to run
during the brownout periods will also fail. See [1][2].

[1]: https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/
[2]: https://github.blog/changelog/2024-11-05-notice-of-breaking-changes-for-github-actions/

Signed-off-by: Eng Zer Jun <engzerjun@gmail.com>
2024-11-16 00:42:08 +08:00
Jesse Duffield e1e4e1be1f Add new filter to only show tracked files in Files panel (#4024)
- **PR Description**

Added new filter to only show tracked files in Files panel. This allows
to hide all non-tracked files on large repos.

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

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

<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for
examples
-->

FYI This is my first PR in this repo.
2024-11-12 17:00:58 +11:00
Yaroslav Veremenko fdeaf9cea0 Add new filter to only show tracked files in Files panel
This allows to hide all non-tracked files on large repos
2024-11-12 16:54:24 +11:00
Jesse Duffield b0a766cc95 Fixes to lazygit Ubuntu install instructions in README.md (#3970)
- **PR Description**
Found that the method to pull the latest tarball for the project was
incorrect. This pull request fixes the instructions in the Ubuntu
section of the Readme.

Simple fix to README.md
2024-11-09 18:57:18 +11:00
Stephen Martin f858460ab9 Fixes to lazygit Ubuntu install instructions in README.md 2024-11-09 18:42:19 +11:00
Jesse Duffield b2cbd93619 Add config option to disable switching tabs with panel jump keys (#3927)
# **PR Description**

## Problem this PR attempts to solve

I really appreciate the feature added in #3794 that allows switching
tabs using panel jump keys when the side panel is already active and see
how it is convenient for many users. However, after using lazygit for
quite some time, I've developed muscle memory where I rely on pressing a
number key to instantly switch to a panel and perform an action,
regardless of which panel is currently active. I have found myself
several times in the past few days clicking '2' and going to hit 'a' +
'c' to commit all and find I was already had window 2 active and my tab
was instead switched to 'Worktrees' and the keybindings no longer apply
to stage or commit.

## Solution

- Add a config `SwitchTabsWithPanelJumpKeys` as a gui boolean that is
true by default, keeping the current new behavior added by #3794
- When `SwitchTabsWithPanelJumpKeys` is set to false in the user's
config, the old behavior is returned which does not switch tabs using
the side panel jump keys.
- This is behavior is verified with an integration test with
``SwitchTabsWithPanelJumpKeys` set to false that shows the expected
behavior that jumping to window 2 while window 2 is already active does
not switch tabs within window 2

To disable switching tabs with panel jump keys, add the following to
your config.yml:
```yaml
gui:
  # If true, when using the panel jump keys (default 1 through 5) and target panel is already active, go to next tab instead
  switchTabsWithPanelJumpKeys: false
```

### P.S. 
This is my first contribution to lazygit and while I absolutely strived
to read all documentation and follow all standards, I accept that this
PR may not be perfect and am very open to feedback and suggestions to
improve both this code and any future contributions. I absolutely love
lazygit, I use it everyday and swear by it as the most powerful and
efficient tool for managing git, I love and appreciate all the work all
the maintainers do to constantly improve it. So thank you to all who are
reading this and I look forward to contributing more to make lazygit
even more of the best git tool available!

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

<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for
examples
-->
2024-11-09 17:23:59 +11:00
Harris Greenstein 8da43af924 Add config option to disable tab switching with jump keys 2024-11-09 17:19:14 +11:00
Jesse Duffield 7edf629eeb Fix installation for Ubuntu in README.md (#4031)
- **PR Description**
If '/usr/local/bin' does not exist, 'install' will eventually result in
a regular file named '/usr/local/bin' being created.

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

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

<!--
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for
examples
-->
2024-11-09 17:18:58 +11:00
Shaobo Song 1e8b8471ca Fix installation for Ubuntu in README.md
If '/usr/local/bin' does not exist, 'install' will eventually result
in a regular file named '/usr/local/bin' being created.
2024-11-05 20:52:35 +08:00
Stefan Haller 095eb130e9 Fix scroll position when entering staging view by clicking in the main view (#3992)
This is both a bug fix and a behavior change:

- The bug fix is that when entering the staging view for a selected file
by clicking in the main view, and the main view was scrolled down, then
it would scroll to the top and show an ugly range selection from the
line that was clicked to the line that is now under the mouse after
scrolling up.
- The behavior change is that when leaving the staging view by pressing
escape, it now retains its scroll position rather than scrolling back up
to the top.

In addition, this fixes a minor flickering issue when leaving the
staging view.

Note that maintaining the scroll position when going into and out of the
staging view is not always perfect for two reasons:
- the focused staging view does not wrap long lines, but the unfocused
one does
- a pager like delta might change the number of lines e.g. of the diff
hunks

For the second problem there's little we can do, but the first one will
be improved once we wrap lines in the focused staging view (see #1384
and #3558), which I'm currently working on.

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

* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [ ] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [ ] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [x] You've read through your own file changes for silly mistakes etc
2024-10-18 22:49:15 +02:00
Stefan Haller f08b3e9e1d Cleanup: remove isFocused parameter from GetContentToRender and related methods
It became unused in f3eb180f75.
2024-10-18 22:46:48 +02:00
Stefan Haller 8f3e59b78e Don't render staging view when it loses focus
As far as I understand, this was needed back when the staging context was still
responsible for rendering its highlight (as opposed to the gocui view, as it is
today). It was necessary to call it with isFocused=false so that it removed the
highlight. The isFocused bool is no longer used today (and we'll remove it in
the next commit), so there's no need to render the view here any more.

This fixes flickering when leaving the staging view by pressing escape. The
reason is that in this case the patch state was already set to nil by the time
we get here, so we would render an empty view for a brief moment.

On top of that, it fixes unwanted scrolling to the top when leaving the staging
view. The reason for this is that we have code in layout that scrolls views up
if needed, e.g. because the window got taller or the view content got shorter
(added in #3839). This kicked in because we emptied the view, and scrolled it
all the way to the top, which we don't want.
2024-10-18 22:46:47 +02:00
Stefan Haller eaf3bf0971 Change NewRenderStringWith{out}ScrollTask to reuse the task key of the existing task
This way it won't scroll to the top; we want this when entering the staging
panel or the patch building panel by clicking into the view, and also when
returning from these views by pressing escape. Note that there's a bug in this
latter case: the focused panel still scrolls to the top when hitting escape, we
will fix this in the next commit.

Change it in the same way for NewRenderStringWithScrollTask, just for
consistency, although it's not really necessary there. We use this function only
for focusing the merge conflict view, and in that case we already have an empty
task key before and after, so it doesn't change anything there.
2024-10-18 22:46:47 +02:00
Stefan Haller 4883c867bb Fix non-sticky range select after clicking in staging view (#3998)
- **PR Description**

When clicking in a single-file diff view to enter staging (or custom
patch editing, when coming from the commit files panel), and then
pressing shift-down or shift-up to select a range, it would move the
selected line rather than creating a range. Only on the next press would
it start to select a range from there.

This is very similar to the fix we made for pressing escape in #3828.

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

* [x] Cheatsheets are up-to-date (run `go generate ./...`)
* [x] Code has been formatted (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
* [x] Tests have been added/updated (see
[here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md)
for the integration test guide)
* [ ] Text is internationalised (see
[here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
* [ ] If a new UserConfig entry was added, make sure it can be
hot-reloaded (see
[here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
* [ ] Docs have been updated if necessary
* [x] You've read through your own file changes for silly mistakes etc
2024-10-18 22:46:27 +02:00
Stefan Haller a58770ee1b Fix pressing shift-down after clicking in diff view
When clicking in a single-file diff view to enter staging (or custom patch
editing, when coming from the commit files panel), and then pressing shift-down
or shift-up to select a range, it would move the selected line rather than
creating a range. Only on the next press would it start to select a range from
there.

This is very similar to the fix we made for pressing escape in 0e4d266a52.
2024-10-18 10:08:27 +02:00
Stefan Haller 7655f6864e Add test demonstrating a bug with clicking in the staging view
When clicking in the main view to enter staging, and then pressing shift-down to
select a range, it moves the selection rather than selecting a two-line range.
We'll fix this in the next commit.
2024-10-18 10:08:27 +02:00
Stefan Haller 286e5f4849 Fix clicking multiple times in an integration test
So far we only had tests that called Click() only once. If you have a test that
calls Click twice (we'll add one in the next commit), you'll notice that the
second click is interpreted as a drag because the mouse button wasn't released
in between. Fix this by sending a "mouse-up" event after the click.
2024-10-18 10:06:52 +02:00
Stefan Haller 052974be65 Allow pasting commits multiple times (#3983)
- **PR Description**

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

Discussed in #3198.

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

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

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

Fixes #3747.

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

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

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

Fixes #3444.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Fixes #3933.

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

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

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

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

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

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

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

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

Fixes #3798.

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

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

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

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

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

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

I'm not sure how much testing/proof-reading/peer review we want to do on
these.
2024-09-15 09:43:15 +02:00
Stefan Haller f74551e464 Generate keybindings 2024-09-08 15:20:52 +02:00
Stefan Haller e06b1cef60 Update language files from Crowdin 2024-09-08 15:19:26 +02:00
90 changed files with 2456 additions and 768 deletions
+3
View File
@@ -21,6 +21,9 @@ changelog:
- title: I18n 🌎
labels:
- i18n
- title: Performance Improvements 📊
labels:
- performance
- title: Other Changes
labels:
- "*"
+4 -4
View File
@@ -39,7 +39,7 @@ jobs:
mkdir -p /tmp/code_coverage
go test ./... -short -cover -args "-test.gocoverdir=/tmp/code_coverage"
- name: Upload code coverage artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: coverage-unit-${{ matrix.os }}-${{ github.run_id }}
path: /tmp/code_coverage
@@ -100,7 +100,7 @@ jobs:
mkdir -p /tmp/code_coverage
./scripts/run_integration_tests.sh
- name: Upload code coverage artifacts
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: coverage-integration-${{ matrix.git-version }}-${{ github.run_id }}
path: /tmp/code_coverage
@@ -184,7 +184,7 @@ jobs:
with:
mode: exactly
count: 1
labels: "ignore-for-release, feature, enhancement, bug, maintenance, docs, i18n"
labels: "ignore-for-release, feature, enhancement, bug, maintenance, docs, i18n, performance"
upload-coverage:
# List all jobs that produce coverage files
needs: [unit-tests, integration-tests]
@@ -200,7 +200,7 @@ jobs:
go-version: 1.22.x
- name: Download all coverage artifacts
uses: actions/download-artifact@v3
uses: actions/download-artifact@v4
with:
path: /tmp/code_coverage
+3 -3
View File
@@ -306,10 +306,10 @@ sudo eopkg install lazygit
### Ubuntu
```sh
LAZYGIT_VERSION=$(curl -s "https://api.github.com/repos/jesseduffield/lazygit/releases/latest" | grep -Po '"tag_name": "v\K[^"]*')
curl -Lo lazygit.tar.gz "https://github.com/jesseduffield/lazygit/releases/latest/download/lazygit_${LAZYGIT_VERSION}_Linux_x86_64.tar.gz"
LAZYGIT_VERSION=$(curl -s "https://api.github.com/repos/jesseduffield/lazygit/releases/latest" | \grep -Po '"tag_name": *"v\K[^"]*')
curl -Lo lazygit.tar.gz "https://github.com/jesseduffield/lazygit/releases/download/v${LAZYGIT_VERSION}/lazygit_${LAZYGIT_VERSION}_Linux_x86_64.tar.gz"
tar xf lazygit.tar.gz lazygit
sudo install lazygit /usr/local/bin
sudo install lazygit -D -t /usr/local/bin/
```
Verify the correct installation of lazygit:
+9
View File
@@ -246,6 +246,15 @@ gui:
# One of 'dashboard' (default) | 'allBranchesLog'
statusPanelView: dashboard
# If true, jump to the Files panel after popping a stash
switchToFilesAfterStashPop: true
# If true, jump to the Files panel after applying a stash
switchToFilesAfterStashApply: true
# If true, when using the panel jump keys (default 1 through 5) and target panel is already active, go to next tab instead
switchTabsWithPanelJumpKeys: false
# Config relating to git
git:
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md
+165 -165
View File
@@ -2,7 +2,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
# Lazygit 按键绑定
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
_图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
## 全局键绑定
@@ -11,28 +11,28 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` <c-r> `` | 切换到最近的仓库 | |
| `` <pgup> (fn+up/shift+k) `` | 向上滚动主面板 | |
| `` <pgdown> (fn+down/shift+j) `` | 向下滚动主面板 | |
| `` @ `` | 打开命令日志菜单 | View options for the command log e.g. show/hide the command log and focus the command log. |
| `` P `` | 推送 | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` p `` | 拉取 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` @ `` | 打开命令日志菜单 | 查看命令日志的选项,例如显示/隐藏命令日志以及聚焦命令日志 |
| `` P `` | 推送 | 推送当前分支到它的上游。如果上游为配置,你可以在弹窗中配置上游分支。 |
| `` p `` | 拉取 | 从当前分支的远程分支获取改动。如果上游为配置,你可以在弹窗中配置上游分支。 |
| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | 扩大差异视图中显示的上下文范围 | Increase the amount of the context shown around changes in the diff view. |
| `` { `` | 缩小差异视图中显示的上下文范围 | Decrease the amount of the context shown around changes in the diff view. |
| `` } `` | 扩大差异视图中显示的上下文范围 | 增加diff视图中围绕更改显示的上下文数量 |
| `` { `` | 缩小差异视图中显示的上下文范围 | 减少diff视图中围绕更改显示的上下文数量 |
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
| `` <c-p> `` | 查看自定义补丁选项 | |
| `` m `` | 查看 合并/变基 选项 | View options to abort/continue/skip the current merge/rebase. |
| `` R `` | 刷新 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
| `` + `` | 下一屏模式(正常/半屏/全屏) | |
| `` m `` | 查看 合并/变基 选项 | 查看当前合并或变基的中止、继续、跳过选项 |
| `` R `` | 刷新 | 刷新git状态(即在后台上运行`git status`,`git branch`等命令以更新面板内容) 不会运行`git fetch` |
| `` + `` | 下一屏模式(正常/半屏/全屏) | |
| `` _ `` | 上一屏模式 | |
| `` ? `` | 打开菜单 | |
| `` <c-s> `` | 查看按路径过滤选项 | View options for filtering the commit log, so that only commits matching the filter are shown. |
| `` W `` | 打开 diff 菜单 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` <c-e> `` | 打开 diff 菜单 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
| `` <c-s> `` | 查看按路径过滤选项 | 查看用于过滤提交日志的选项,以便仅显示与过滤器匹配的提交。 |
| `` W `` | 打开 diff 菜单 | 查看与比较两个引用相关的选项,例如与选定的 ref 进行比较,输入要比较的 ref,然后反转比较方向。 |
| `` <c-e> `` | 打开 diff 菜单 | 查看与比较两个引用相关的选项,例如与选定的 ref 进行比较,输入要比较的 ref,然后反转比较方向。 |
| `` q `` | 退出 | |
| `` <esc> `` | 取消 | |
| `` <c-w> `` | 切换是否在差异视图中显示空白字符差异 | Toggle whether or not whitespace changes are shown in the diff view. |
| `` z `` | (通过 reflog)撤销「实验功能」 | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` <c-z> `` | (通过 reflog)重做「实验功能」 | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
| `` <c-w> `` | 切换是否在差异视图中显示空白字符差异 | 切换是否在diff视图中显示空白更改 |
| `` z `` | (通过 reflog)撤销「实验功能」 | Reflog将用于确定运行哪个git命令来撤消最后一个git命令。这并不包括对工作树的更改,只考虑提交。 |
| `` <c-z> `` | (通过 reflog)重做「实验功能」 | Reflog将用于确定运行哪个git命令来重做上一个git命令。这并不包括对工作树的更改,只考虑提交。 |
## 列表面板导航
@@ -43,8 +43,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| `` < `` | 滚动到顶部 | |
| `` > `` | 滚动到底部 | |
| `` v `` | 切换拖动选择 | |
| `` <s-down> `` | Range select down | |
| `` <s-up> `` | Range select up | |
| `` <s-down> `` | 向下扩展选择范围 | |
| `` <s-up> `` | 向上扩展选择范围 | |
| `` / `` | 开始搜索 | |
| `` H `` | 向左滚动 | |
| `` L `` | 向右滚动 | |
@@ -56,27 +56,17 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. |
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD。 |
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hash、URL、diff、消息、作者)。 |
| `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | |
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 复制提交(拣选) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` <enter> `` | 查看提交 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
## Worktrees
| Key | Action | Info |
|-----|--------|-------------|
| `` n `` | New worktree | |
| `` <space> `` | Switch | Switch to the selected worktree. |
| `` o `` | Open in editor | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
| `` / `` | Filter the current view by text | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | |
## 分支页面
@@ -84,42 +74,42 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|-----|--------|-------------|
| `` <c-o> `` | 将分支名称复制到剪贴板 | |
| `` i `` | 显示 git-flow 选项 | |
| `` <space> `` | 检出 | Checkout selected item. |
| `` <space> `` | 检出 | 检出选中的项目 |
| `` n `` | 新分支 | |
| `` o `` | 创建抓取请求 | |
| `` O `` | 创建抓取请求选项 | |
| `` <c-y> `` | 将抓取请求 URL 复制到剪贴板 | |
| `` c `` | 按名称检出 | Checkout by name. In the input box you can enter '-' to switch to the last branch. |
| `` F `` | 强制检出 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
| `` d `` | Delete | View delete options for local/remote branch. |
| `` r `` | 将已检出的分支变基到该分支 | Rebase the checked-out branch onto the selected branch. |
| `` M `` | 合并到当前检出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` f `` | 从上游快进此分支 | Fast-forward selected branch from its upstream. |
| `` c `` | 按名称检出 | 按名称检出。在输入框中,您可以输入'-' 来切换到最后一个分支。 |
| `` F `` | 强制检出 | 强制检出所选分支。这将在检出所选分支之前放弃工作目录中的所有本地更改。 |
| `` d `` | 删除 | 查看本地/远程分支的删除选项 |
| `` r `` | 将已检出的分支变基到该分支 | 将检出的分支变基到所选的分支上。 |
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
| `` f `` | 从上游快进此分支 | 将当前分支直接移动到远程追踪分支的最新提交 |
| `` T `` | 创建标签 | |
| `` s `` | Sort order | |
| `` s `` | 排序 | |
| `` g `` | 查看重置选项 | |
| `` R `` | 重命名分支 | |
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` u `` | 查看上游选项 | 查看与分支上游相关的选项,例如设置/取消设置上游和重置为上游。 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` <enter> `` | 查看提交 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | |
## 子提交
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. |
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD。 |
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hash、URL、diff、消息、作者)。 |
| `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | |
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 复制提交(拣选) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` <enter> `` | 查看提交的文件 | |
| `` w `` | View worktree options | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 开始搜索 | |
## 子模块
@@ -127,104 +117,114 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将子模块名称复制到剪贴板 | |
| `` <enter> `` | Enter | 输入子模块 |
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. |
| `` u `` | Update | 更新子模块 |
| `` <enter> `` | 进入 | 输入子模块 |
| `` d `` | 删除 | 删除选定的子模块及其相应的目录 |
| `` u `` | 更新 | 更新子模块 |
| `` n `` | 添加新的子模块 | |
| `` e `` | 更新子模块 URL | |
| `` i `` | Initialize | 初始化子模块 |
| `` i `` | 初始化 | 初始化子模块 |
| `` b `` | 查看批量子模块选项 | |
| `` / `` | Filter the current view by text | |
| `` / `` | 通过文本过滤当前视图 | |
## 工作区
| Key | Action | Info |
|-----|--------|-------------|
| `` n `` | 新建工作树 | |
| `` <space> `` | 切换 | 切换到选中的工作树 |
| `` o `` | 在编辑器中编写 | |
| `` d `` | 删除 | 删除选定的工作树。这将删除工作树的目录以及 .git 目录中有关工作树的元数据。 |
| `` / `` | 通过文本过滤当前视图 | |
## 提交
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
| `` b `` | 查看二分查找选项 | |
| `` s `` | 压缩 | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
| `` f `` | 修正(fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` r `` | 改写提交 | Reword the selected commit's message. |
| `` s `` | 压缩(Squash) | 将已选提交压缩到该提交之下。这些选定的提交的消息会附加到该提交的消息之下。 |
| `` f `` | 修正(fixup) | 将选定的提交合并到其下面的提交中。与压缩类似,但所选提交的消息将被丢弃。 |
| `` r `` | 改写提交 | 重写所选提交的消息。 |
| `` R `` | 使用编辑器重命名提交 | |
| `` d `` | 删除提交 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
| `` e `` | Edit (start interactive rebase) | 编辑提交 |
| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.
If you would instead like to start an interactive rebase from the selected commit, press `e`. |
| `` p `` | Pick | 选择提交(变基过程中) |
| `` d `` | 删除提交 | 删除选中的提交。这将通过变基从分支中删除该提交,如果该提交修改的内容依赖于后续的提交,则需要解决合并冲突。 |
| `` e `` | 编辑(开始交互式变基) | 编辑提交 |
| `` i `` | 开始交互式变基 | 为分支上的提交启动交互式变基。这将包括从 HEAD 提交到第一个合并提交或主分支提交的所有提交。
如果您想从所选提交启动交互式变基,请按 `e`。 |
| `` p `` | 拣选(Pick) | 选择提交(变基过程中) |
| `` F `` | 为此提交创建修正 | 创建修正提交 |
| `` S `` | Apply fixup commits | 压缩在所选提交之上的所有“fixup!”提交(自动压缩) |
| `` S `` | 应用该修复提交 | 压缩在所选提交之上的所有“fixup!”提交(自动压缩) |
| `` <c-j> `` | 下移提交 | |
| `` <c-k> `` | 上移提交 | |
| `` V `` | 粘贴提交(拣选) | |
| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. |
| `` A `` | Amend | 用已暂存的更改来修补提交 |
| `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. |
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. |
| `` T `` | 标签提交 | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. |
| `` <c-l> `` | 打开日志菜单 | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. |
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. |
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
| `` V `` | 粘贴提交(拣选) | |
| `` B `` | 标记一个主提交用于变基 | 选择下一次变基的主提交。当您变基到一个分支时,只有高于主提交的提交才会被引入。这使用“git rebase --onto”命令。 |
| `` A `` | 修补(Amend) | 用已暂存的变更来修补提交 |
| `` a `` | 修补提交属性 | 设置或重置提交的作者,或添加其他作者。 |
| `` t `` | 撤销(Revert) | 为所选提交创建还原提交,这会反向应用所选提交的更改。 |
| `` T `` | 标签提交 | 创建一个新标签指向所选提交。你可以在弹窗中输入标签名称和描述(可选)。 |
| `` <c-l> `` | 打开日志菜单 | 查看提交日志的选项,例如更改排序顺序、隐藏 git graph、显示整个 git graph。 |
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD。 |
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hash、URL、diff、消息、作者)。 |
| `` o `` | 在浏览器中打开提交 | |
| `` n `` | 从提交创建新分支 | |
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 复制提交(拣选) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` <enter> `` | 查看提交的文件 | |
| `` w `` | View worktree options | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 开始搜索 | |
## 提交信息
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 确认 | |
| `` <esc> `` | 关闭 | |
## 提交文件
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将文件名复制到剪贴板 | |
| `` c `` | 检出 | 检出文件 |
| `` d `` | Remove | 放弃对此文件的提交更改 |
| `` o `` | 打开文件 | Open file in default application. |
| `` e `` | Edit | Open file in external editor. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <space> `` | 补丁中包含的切换文件 | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` <enter> `` | 输入文件以将所选行添加到补丁中(或切换目录折叠) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
| `` ` `` | 切换文件树视图 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
| `` d `` | 删除 | 放弃对此文件的提交变更 |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` e `` | 编辑 | 使用外部编辑器打开文件 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` <space> `` | 补丁中包含的切换文件 | 切换文件是否包含在自定义补丁中。请参阅 https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches。 |
| `` a `` | 操作所有文件 | 添加或删除所有提交中的文件到自定义的补丁中。请参阅 https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches。 |
| `` <enter> `` | 输入文件以将所选行添加到补丁中(或切换目录折叠) | 如果已选择一个文件,则Enter进入该文件,以便您可以向自定义补丁添加/删除单独的行。如果选择了目录,则切换目录。 |
| `` ` `` | 切换文件树视图 | 在平铺部署与树布局之间切换文件视图。平铺布局在一个列表中展示所有文件路径,树布局则根据目录分组展示。 |
| `` / `` | 开始搜索 | |
## 提交讯息
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | 确认 | |
| `` <esc> `` | 关闭 | |
## 文件
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将文件名复制到剪贴板 | |
| `` <space> `` | 切换暂存状态 | Toggle staged for selected file. |
| `` <c-b> `` | Filter files by status | |
| `` y `` | Copy to clipboard | |
| `` c `` | 提交更改 | Commit staged changes. |
| `` w `` | 提交更改而无需预先提交钩子 | |
| `` <space> `` | 切换暂存状态 | 为选定的文件切换暂存状态 |
| `` <c-b> `` | 通过状态过滤文件 | |
| `` y `` | 复制到剪贴板 | |
| `` c `` | 提交变更 | 提交暂存文件 |
| `` w `` | 提交变更而无需预先提交钩子 | |
| `` A `` | 修补最后一次提交 | |
| `` C `` | 提交更改(使用编辑器编辑提交信息) | |
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` e `` | Edit | Open file in external editor. |
| `` o `` | 打开文件 | Open file in default application. |
| `` C `` | 提交变更(使用编辑器编辑提交信息) | |
| `` <c-f> `` | 找到用于修复的基准提交 | 找到您当前变更所基于的提交,以便于修正/改进该提交。这样做可以省去您逐一查看分支提交来确定应该修正/改进哪个提交的麻烦。请参阅文档: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` e `` | 编辑 | 使用外部编辑器打开文件 |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` i `` | 忽略文件 | |
| `` r `` | 刷新文件 | |
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
| `` S `` | 查看贮藏选项 | View stash options (e.g. stash all, stash staged, stash unstaged). |
| `` a `` | 切换所有文件的暂存状态 | Toggle staged/unstaged for all files in working tree. |
| `` <enter> `` | 暂存单个 块/行 用于文件, 或 折叠/展开 目录 | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. |
| `` d `` | 查看'放弃更改'选项 | View options for discarding changes to the selected file. |
| `` s `` | 贮藏 | 贮藏所有变更.若要使用其他贮藏变体,请使用查看贮藏选项快捷键 |
| `` S `` | 查看贮藏选项 | 查看贮藏选项(例如:贮藏所有、贮藏已暂存变更、贮藏未暂存变更) |
| `` a `` | 切换所有文件的暂存状态 | 切换工作区中所有文件的已暂存/未暂存状态 |
| `` <enter> `` | 暂存单个 块/行 用于文件, 或 折叠/展开 目录 | 如果选中的是一个文件,则会进入到暂存视图,以便可以暂存单个代码块/行。如果选中的是一个目录,则会折叠/展开这个目录 |
| `` d `` | 查看'放弃变更'选项 | 查看选中文件的放弃变更选项 |
| `` g `` | 查看上游重置选项 | |
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). |
| `` ` `` | 切换文件树视图 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | 打开外部合并工具 (git mergetool) | Run `git mergetool`. |
| `` f `` | 抓取 | Fetch changes from remote. |
| `` D `` | 重置 | 查看工作树的重置选项(例如:清除工作树)。 |
| `` ` `` | 切换文件树视图 | 在平铺部署与树布局之间切换文件视图。平铺布局在一个列表中展示所有文件路径,树布局则根据目录分组展示。 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` M `` | 打开外部合并工具(git mergetool) | 执行 `git mergetool`. |
| `` f `` | 抓取 | 从远程获取变更 |
| `` / `` | 开始搜索 | |
## 构建补丁中
@@ -234,10 +234,10 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <left> `` | 选择上一个区块 | |
| `` <right> `` | 选择下一个区块 | |
| `` v `` | 切换拖动选择 | |
| `` a `` | 切换选择区块 | Toggle hunk selection mode. |
| `` a `` | 切换选择代码块 | 切换代码块选择模式 |
| `` <c-o> `` | 将选中文本复制到剪贴板 | |
| `` o `` | 打开文件 | Open file in default application. |
| `` e `` | 编辑文件 | Open file in external editor. |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` e `` | 编辑文件 | 使用外部编辑器打开文件 |
| `` <space> `` | 添加/移除 行到补丁 | |
| `` <esc> `` | 退出逐行模式 | |
| `` / `` | 开始搜索 | |
@@ -246,15 +246,15 @@ If you would instead like to start an interactive rebase from the selected commi
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | 检出 | Checkout the selected tag tag as a detached HEAD. |
| `` n `` | 创建标签 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
| `` d `` | Delete | View delete options for local/remote tag. |
| `` P `` | 推送标签 | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <space> `` | 检出 | 检出选择的标签作为分离的HEAD |
| `` n `` | 创建标签 | 基于当前提交创建一个新标签。你将在弹窗中输入标签名称和描述(可选)。 |
| `` d `` | 删除 | 查看本地/远程标签的删除选项 |
| `` P `` | 推送标签 | 推送选择的标签到远端。你将在弹窗中选择一个远端。 |
| `` g `` | 重置 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` <enter> `` | 查看提交 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | |
## 正在合并
@@ -266,10 +266,10 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <down> `` | 选择底部块 | |
| `` <left> `` | 选择上一个冲突 | |
| `` <right> `` | 选择下一个冲突 | |
| `` z `` | 撤销 | Undo last merge conflict resolution. |
| `` e `` | 编辑文件 | Open file in external editor. |
| `` o `` | 打开文件 | Open file in default application. |
| `` M `` | 打开外部合并工具 (git mergetool) | Run `git mergetool`. |
| `` z `` | 撤销 | 撤消上次合并冲突解决 |
| `` e `` | 编辑文件 | 使用外部编辑器打开文件 |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` M `` | 打开外部合并工具(git mergetool) | 执行 `git mergetool`. |
| `` <esc> `` | 返回文件面板 | |
## 正在暂存
@@ -279,19 +279,19 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <left> `` | 选择上一个区块 | |
| `` <right> `` | 选择下一个区块 | |
| `` v `` | 切换拖动选择 | |
| `` a `` | 切换选择区块 | Toggle hunk selection mode. |
| `` a `` | 切换选择代码块 | 切换代码块选择模式 |
| `` <c-o> `` | 将选中文本复制到剪贴板 | |
| `` <space> `` | 切换暂存状态 | 切换行暂存状态 |
| `` d `` | 取消变更 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. |
| `` o `` | 打开文件 | Open file in default application. |
| `` e `` | 编辑文件 | Open file in external editor. |
| `` d `` | 取消变更(git reset) | 当选择未暂存的变更时,使用git reset丢弃该变更。当选择已暂存的变更时,取消暂存该变更 |
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
| `` e `` | 编辑文件 | 使用外部编辑器打开文件 |
| `` <esc> `` | 返回文件面板 | |
| `` <tab> `` | 切换到其他面板 | Switch to other view (staged/unstaged changes). |
| `` E `` | Edit hunk | Edit selected hunk in external editor. |
| `` c `` | 提交更改 | Commit staged changes. |
| `` w `` | 提交更改而无需预先提交钩子 | |
| `` C `` | 提交更改(使用编辑器编辑提交信息) | |
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` <tab> `` | 切换到其他面板 | 切换到其他视图(已暂存/未暂存的变更) |
| `` E `` | 编辑代码块 | 在外部编辑器中编辑选中的代码块 |
| `` c `` | 提交变更 | 提交暂存文件 |
| `` w `` | 提交变更而无需预先提交钩子 | |
| `` C `` | 提交变更(使用编辑器编辑提交信息) | |
| `` <c-f> `` | 找到用于修复的基准提交 | 找到您当前变更所基于的提交,以便于修正/改进该提交。这样做可以省去您逐一查看分支提交来确定应该修正/改进哪个提交的麻烦。请参阅文档: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` / `` | 开始搜索 | |
## 正常
@@ -305,8 +305,8 @@ If you would instead like to start an interactive rebase from the selected commi
| Key | Action | Info |
|-----|--------|-------------|
| `` o `` | 打开配置文件 | Open file in default application. |
| `` e `` | 编辑配置文件 | Open file in external editor. |
| `` o `` | 打开配置文件 | 使用默认程序打开该文件 |
| `` e `` | 编辑配置文件 | 使用外部编辑器打开文件 |
| `` u `` | 检查更新 | |
| `` <enter> `` | 切换到最近的仓库 | |
| `` a `` | 显示所有分支的日志 | |
@@ -324,46 +324,46 @@ If you would instead like to start an interactive rebase from the selected commi
|-----|--------|-------------|
| `` <enter> `` | 执行 | |
| `` <esc> `` | 关闭 | |
| `` / `` | Filter the current view by text | |
| `` / `` | 通过文本过滤当前视图 | |
## 贮藏
| Key | Action | Info |
|-----|--------|-------------|
| `` <space> `` | 应用 | Apply the stash entry to your working directory. |
| `` g `` | 应用并删除 | Apply the stash entry to your working directory and remove the stash entry. |
| `` d `` | 删除 | Remove the stash entry from the stash list. |
| `` n `` | 新分支 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. |
| `` r `` | Rename stash | |
| `` <space> `` | 应用 | 将贮藏项应用到您的工作目录。 |
| `` g `` | 应用并删除 | 将存储项应用到工作目录并删除存储项。 |
| `` d `` | 删除 | 从贮藏列表中删除该贮藏项 |
| `` n `` | 新分支 | 从选定的贮藏项创建一个新分支。这是通过 git 检查创建贮藏项的提交,从该提交创建一个新分支,然后将贮藏项作为附加提交应用到新分支来实现的。 |
| `` r `` | 重命名贮藏 | |
| `` <enter> `` | 查看提交的文件 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | |
## 远程分支
| Key | Action | Info |
|-----|--------|-------------|
| `` <c-o> `` | 将分支名称复制到剪贴板 | |
| `` <space> `` | 检出 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
| `` <space> `` | 检出 | 基于当前选中的远程分支检出一个新的本地分支,或者将远程分支作分离的HEAD。 |
| `` n `` | 新分支 | |
| `` M `` | 合并到当前检出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | 将已检出的分支变基到该分支 | Rebase the checked-out branch onto the selected branch. |
| `` d `` | Delete | Delete the remote branch from the remote. |
| `` u `` | Set as upstream | 设置为检出分支的上游 |
| `` s `` | Sort order | |
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
| `` r `` | 将已检出的分支变基到该分支 | 将检出的分支变基到所选的分支上。 |
| `` d `` | 删除 | 从远程删除远程分支。 |
| `` u `` | 设置为上游 | 设置为检出分支的上游 |
| `` s `` | 排序 | |
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
| `` <enter> `` | 查看提交 | |
| `` w `` | View worktree options | |
| `` / `` | Filter the current view by text | |
| `` w `` | 查看工作区选项 | |
| `` / `` | 通过文本过滤当前视图 | |
## 远程页面
| Key | Action | Info |
|-----|--------|-------------|
| `` <enter> `` | View branches | |
| `` <enter> `` | 查看分支 | |
| `` n `` | 添加新的远程仓库 | |
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. |
| `` e `` | Edit | 编辑远程仓库 |
| `` d `` | 删除 | 删除选中的远程。从远程跟踪远程分支的任何本地分支都不会受到影响。 |
| `` e `` | 编辑 | 编辑远程仓库 |
| `` f `` | 抓取 | 抓取远程仓库 |
| `` / `` | Filter the current view by text | |
| `` / `` | 通过文本过滤当前视图 | |
+50 -50
View File
@@ -12,8 +12,8 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
| `` <pgup> (fn+up/shift+k) `` | 向上捲動主面板 | |
| `` <pgdown> (fn+down/shift+j) `` | 向下捲動主面板 | |
| `` @ `` | 開啟命令記錄選單 | View options for the command log e.g. show/hide the command log and focus the command log. |
| `` P `` | 推送 | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` p `` | 拉取 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
| `` P `` | 推送 | 推送到遠端。如果沒有設定遠端,會開啟設定視窗。 |
| `` p `` | 拉取 | 從遠端同步當前分支。如果沒有設定遠端,會開啟設定視窗。 |
| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
| `` } `` | 增加差異檢視中顯示變更周圍上下文的大小 | Increase the amount of the context shown around changes in the diff view. |
@@ -60,8 +60,8 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
| `` v `` | 切換拖曳選擇 | |
| `` a `` | 切換選擇程式碼塊 | Toggle hunk selection mode. |
| `` <c-o> `` | 複製所選文本至剪貼簿 | |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` e `` | 編輯檔案 | Open file in external editor. |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
| `` <space> `` | 向 (或從) 補丁中添加/刪除行 | |
| `` <esc> `` | 退出自訂補丁建立器 | |
| `` / `` | 搜尋 | |
@@ -84,9 +84,9 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
| `` <left> `` | 選擇上一個衝突 | |
| `` <right> `` | 選擇下一個衝突 | |
| `` z `` | 復原 | Undo last merge conflict resolution. |
| `` e `` | 編輯檔案 | Open file in external editor. |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` M `` | 開啟外部合併工具 (git mergetool) | Run `git mergetool`. |
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` M `` | 開啟外部合併工具 | 執行 `git mergetool`。 |
| `` <esc> `` | 返回檔案面板 | |
## 主面板(預存)
@@ -100,12 +100,12 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
| `` <c-o> `` | 複製所選文本至剪貼簿 | |
| `` <space> `` | 切換預存 | 切換現有行的狀態 (已預存/未預存) |
| `` d `` | 刪除變更 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` e `` | 編輯檔案 | Open file in external editor. |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
| `` <esc> `` | 返回檔案面板 | |
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). |
| `` E `` | 編輯程式碼塊 | Edit selected hunk in external editor. |
| `` c `` | 提交變更 | Commit staged changes. |
| `` c `` | 提交變更 | 提交暫存區變更 |
| `` w `` | 沒有預提交 hook 就提交更改 | |
| `` C `` | 使用 git 編輯器提交變更 | |
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
@@ -131,7 +131,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-r> `` | 重設選定的揀選 (複製) 提交 | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <enter> `` | 檢視所選項目的檔案 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -156,7 +156,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
|-----|--------|-------------|
| `` n `` | New worktree | |
| `` <space> `` | Switch | Switch to the selected worktree. |
| `` o `` | Open in editor | |
| `` o `` | 在編輯器中開啟 | |
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
| `` / `` | 搜尋 | |
@@ -169,22 +169,22 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
| `` b `` | 查看二分選項 | |
| `` s `` | 壓縮 (Squash) | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
| `` f `` | 修復 (Fixup) | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
| `` r `` | 改寫提交 | Reword the selected commit's message. |
| `` r `` | 改寫提交 | 改寫選中的提交訊息 |
| `` R `` | 使用編輯器改寫提交 | |
| `` d `` | 刪除提交 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
| `` e `` | Edit (start interactive rebase) | 編輯提交 |
| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.
| `` e `` | 編輯(開始互動變基) | 編輯提交 |
| `` i `` | 開始互動變基 | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.
If you would instead like to start an interactive rebase from the selected commit, press `e`. |
| `` p `` | Pick | 挑選提交 (於變基過程中) |
| `` p `` | 挑選 | 挑選提交 (於變基過程中) |
| `` F `` | 建立修復提交 | 為此提交建立修復提交 |
| `` S `` | 壓縮上方所有「fixup」提交(自動壓縮) | 是否壓縮上方 {{.commit}} 所有「fixup」提交? |
| `` <c-j> `` | 向下移動提交 | |
| `` <c-k> `` | 向上移動提交 | |
| `` V `` | 貼上提交 (揀選) | |
| `` B `` | 為了變基已標注提交為基準提交 | 請為了下一次變基選擇一項基準提交;此將執行 `git rebase --onto`。 |
| `` A `` | Amend | 使用已預存的更改修正提交 |
| `` A `` | 修改 | 使用已預存的更改修正提交 |
| `` a `` | 設定/重設提交作者 | Set/Reset commit author or set co-author. |
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. |
| `` t `` | 還原 | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. |
| `` T `` | 打標籤到提交 | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. |
| `` <c-l> `` | 開啟記錄選單 | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. |
| `` <space> `` | 檢出 | Checkout the selected commit as a detached HEAD. |
@@ -193,7 +193,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` n `` | 從提交建立新分支 | |
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <enter> `` | 檢視所選項目的檔案 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -212,9 +212,9 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <c-o> `` | 複製檔案名稱到剪貼簿 | |
| `` c `` | 檢出 | 檢出檔案 |
| `` d `` | Remove | Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file. |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` e `` | Edit | Open file in external editor. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯 | 使用外部編輯器開啟 |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <space> `` | 切換檔案是否包含在補丁中 | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` a `` | 切換所有檔案是否包含在補丁中 | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
| `` <enter> `` | 輸入檔案以將選定的行添加至補丁(或切換目錄折疊) | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
@@ -246,7 +246,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` C `` | 複製提交 (揀選) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
| `` <c-r> `` | 重設選定的揀選 (複製) 提交 | |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -257,23 +257,23 @@ If you would instead like to start an interactive rebase from the selected commi
|-----|--------|-------------|
| `` <c-o> `` | 複製分支名稱到剪貼簿 | |
| `` i `` | 顯示 git-flow 選項 | |
| `` <space> `` | 檢出 | Checkout selected item. |
| `` <space> `` | 檢出 | 檢出選定的項目。 |
| `` n `` | 新分支 | |
| `` o `` | 建立拉取請求 | |
| `` O `` | 建立拉取請求選項 | |
| `` <c-y> `` | 複製拉取請求的 URL 到剪貼板 | |
| `` c `` | 根據名稱檢出 | Checkout by name. In the input box you can enter '-' to switch to the last branch. |
| `` F `` | 強制檢出 | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
| `` d `` | Delete | View delete options for local/remote branch. |
| `` d `` | 刪除 | View delete options for local/remote branch. |
| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. |
| `` M `` | 合併到當前檢出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` f `` | 從上游快進此分支 | Fast-forward selected branch from its upstream. |
| `` f `` | 從上游快進此分支 | 從遠端快進所選的分支 |
| `` T `` | 建立標籤 | |
| `` s `` | Sort order | |
| `` s `` | 排序規則 | |
| `` g `` | 檢視重設選項 | |
| `` R `` | 重新命名分支 | |
| `` u `` | 檢視上游設定 | 檢視有關上游分支的設定(例如重設至上游) |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` u `` | 檢視遠端設定 | 檢視有關遠端分支的設定(例如重設至遠端) |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -284,10 +284,10 @@ If you would instead like to start an interactive rebase from the selected commi
|-----|--------|-------------|
| `` <space> `` | 檢出 | Checkout the selected tag tag as a detached HEAD. |
| `` n `` | 建立標籤 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
| `` d `` | Delete | View delete options for local/remote tag. |
| `` d `` | 刪除 | View delete options for local/remote tag. |
| `` P `` | 推送標籤 | Push the selected tag to a remote. You'll be prompted to select a remote. |
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` g `` | 重設 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
@@ -299,35 +299,35 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <c-o> `` | 複製檔案名稱到剪貼簿 | |
| `` <space> `` | 切換預存 | Toggle staged for selected file. |
| `` <c-b> `` | 篩選檔案 (預存/未預存) | |
| `` y `` | Copy to clipboard | |
| `` c `` | 提交變更 | Commit staged changes. |
| `` y `` | 複製到剪貼簿 | |
| `` c `` | 提交變更 | 提交暫存區變更 |
| `` w `` | 沒有預提交 hook 就提交更改 | |
| `` A `` | 修改上次提交 | |
| `` C `` | 使用 git 編輯器提交變更 | |
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
| `` e `` | Edit | Open file in external editor. |
| `` o `` | 開啟檔案 | Open file in default application. |
| `` e `` | 編輯 | 使用外部編輯器開啟 |
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
| `` i `` | 忽略或排除檔案 | |
| `` r `` | 重新整理檔案 | |
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
| `` s `` | 收藏 | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
| `` S `` | 檢視收藏選項 | View stash options (e.g. stash all, stash staged, stash unstaged). |
| `` a `` | 全部預存/取消預存 | Toggle staged/unstaged for all files in working tree. |
| `` <enter> `` | 選擇檔案中的單個程式碼塊/行,或展開/折疊目錄 | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. |
| `` d `` | Discard | View options for discarding changes to the selected file. |
| `` g `` | 檢視上游重設選項 | |
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). |
| `` d `` | 捨棄 | 檢視選中變動進行捨棄復原 |
| `` g `` | 檢視遠端重設選項 | |
| `` D `` | 重設 | View reset options for working tree (e.g. nuking the working tree). |
| `` ` `` | 顯示檔案樹狀視圖 | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` M `` | 開啟外部合併工具 (git mergetool) | Run `git mergetool`. |
| `` f `` | 擷取 | Fetch changes from remote. |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` M `` | 開啟外部合併工具 | 執行 `git mergetool`。 |
| `` f `` | 擷取 | 同步遠端異動 |
| `` / `` | 搜尋 | |
## 狀態
| Key | Action | Info |
|-----|--------|-------------|
| `` o `` | 開啟設定檔案 | Open file in default application. |
| `` e `` | 編輯設定檔案 | Open file in external editor. |
| `` o `` | 開啟設定檔案 | 使用預設軟體開啟 |
| `` e `` | 編輯設定檔案 | 使用外部編輯器開啟 |
| `` u `` | 檢查更新 | |
| `` <enter> `` | 切換到最近使用的版本庫 | |
| `` a `` | 顯示所有分支日誌 | |
@@ -346,7 +346,7 @@ If you would instead like to start an interactive rebase from the selected commi
| `` <enter> `` | View branches | |
| `` n `` | 新增遠端 | |
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. |
| `` e `` | Edit | 編輯遠端 |
| `` e `` | 編輯 | 編輯遠端 |
| `` f `` | 擷取 | 擷取遠端 |
| `` / `` | 搜尋 | |
@@ -359,11 +359,11 @@ If you would instead like to start an interactive rebase from the selected commi
| `` n `` | 新分支 | |
| `` M `` | 合併到當前檢出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. |
| `` d `` | Delete | Delete the remote branch from the remote. |
| `` u `` | Set as upstream | 將此分支設為當前分支之上游 |
| `` s `` | Sort order | |
| `` d `` | 刪除 | Delete the remote branch from the remote. |
| `` u `` | 設置為遠端 | 將此分支設為當前分支之遠端 |
| `` s `` | 排序規則 | |
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
| `` <c-t> `` | Open external diff tool (git difftool) | |
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
| `` <enter> `` | 檢視提交 | |
| `` w `` | 檢視工作目錄選項 | |
| `` / `` | 搜尋 | |
+1 -1
View File
@@ -16,7 +16,7 @@ require (
github.com/integrii/flaggy v1.4.0
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
github.com/jesseduffield/gocui v0.3.1-0.20240906064314-bfab49c720d7
github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
+2 -2
View File
@@ -188,8 +188,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
github.com/jesseduffield/gocui v0.3.1-0.20240906064314-bfab49c720d7 h1:QeLCKRAt4T6sBg5tSrOc4OojCuAcPxUA+4vNMPY4aH4=
github.com/jesseduffield/gocui v0.3.1-0.20240906064314-bfab49c720d7/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f h1:ZzsAUDwPFLPITKLcJpMSqt/3rERdI8YRZKr2l0plrls=
github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
+1
View File
@@ -0,0 +1 @@
lolll
+1
View File
@@ -0,0 +1 @@
lol
+9 -7
View File
@@ -246,16 +246,18 @@ func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
// Takes the hash of some commit, and the hash of a fixup commit that was created
// at the end of the branch, then moves the fixup commit down to right after the
// original commit, changing its type to "fixup"
// original commit, changing its type to "fixup" (only if ChangeToFixup is true)
type MoveFixupCommitDownInstruction struct {
OriginalHash string
FixupHash string
OriginalHash string
FixupHash string
ChangeToFixup bool
}
func NewMoveFixupCommitDownInstruction(originalHash string, fixupHash string) Instruction {
func NewMoveFixupCommitDownInstruction(originalHash string, fixupHash string, changeToFixup bool) Instruction {
return &MoveFixupCommitDownInstruction{
OriginalHash: originalHash,
FixupHash: fixupHash,
OriginalHash: originalHash,
FixupHash: fixupHash,
ChangeToFixup: changeToFixup,
}
}
@@ -269,7 +271,7 @@ func (self *MoveFixupCommitDownInstruction) SerializedInstructions() string {
func (self *MoveFixupCommitDownInstruction) run(common *common.Common) error {
return handleInteractiveRebase(common, func(path string) error {
return utils.MoveFixupCommitDown(path, self.OriginalHash, self.FixupHash, getCommentChar())
return utils.MoveFixupCommitDown(path, self.OriginalHash, self.FixupHash, self.ChangeToFixup, getCommentChar())
})
}
+24
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/utils"
"github.com/mgutz/str"
@@ -260,3 +261,26 @@ func (self *BranchCommands) AllBranchesLogCmdObj() oscommands.ICmdObj {
return self.cmd.New(str.ToArgv(candidates[i])).DontLog()
}
func (self *BranchCommands) IsBranchMerged(branch *models.Branch, mainBranches *MainBranches) (bool, error) {
branchesToCheckAgainst := []string{"HEAD"}
if branch.RemoteBranchStoredLocally() {
branchesToCheckAgainst = append(branchesToCheckAgainst, fmt.Sprintf("%s@{upstream}", branch.Name))
}
branchesToCheckAgainst = append(branchesToCheckAgainst, mainBranches.Get()...)
cmdArgs := NewGitCmd("rev-list").
Arg("--max-count=1").
Arg(branch.Name).
Arg(lo.Map(branchesToCheckAgainst, func(branch string, _ int) string {
return fmt.Sprintf("^%s", branch)
})...).
ToArgv()
stdout, _, err := self.cmd.New(cmdArgs).RunWithOutputs()
if err != nil {
return false, err
}
return stdout == "", nil
}
+1 -1
View File
@@ -189,7 +189,7 @@ type Author struct {
func (self *CommitCommands) GetCommitAuthor(commitHash string) (Author, error) {
cmdArgs := NewGitCmd("show").
Arg("--no-patch", "--pretty=format:'%an%x00%ae'", commitHash).
Arg("--no-patch", "--pretty=format:%an%x00%ae", commitHash).
ToArgv()
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
+1 -1
View File
@@ -230,7 +230,7 @@ func TestCommitShowCmdObj(t *testing.T) {
type scenario struct {
testName string
filterPath string
contextSize int
contextSize uint64
similarityThreshold int
ignoreWhitespace bool
extDiffCmd string
+22 -6
View File
@@ -145,11 +145,11 @@ func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx
baseHashOrRoot := getBaseHashOrRoot(commits, baseIndex)
changes := lo.Map(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) daemon.ChangeTodoAction {
changes := lo.FilterMap(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) (daemon.ChangeTodoAction, bool) {
return daemon.ChangeTodoAction{
Hash: commit.Hash,
NewAction: action,
}
}, !commit.IsMerge()
})
self.os.LogCommand(logTodoChanges(changes), false)
@@ -284,6 +284,11 @@ func (self *RebaseCommands) GitRebaseEditTodo(todosFileContent []byte) error {
return cmdObj.Run()
}
func (self *RebaseCommands) getHashOfLastCommitMade() (string, error) {
cmdArgs := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToArgv()
return self.cmd.New(cmdArgs).RunWithOutput()
}
// AmendTo amends the given commit with whatever files are staged
func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error {
commit := commits[commitIndex]
@@ -292,9 +297,7 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
return err
}
// Get the hash of the commit we just created
cmdArgs := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToArgv()
fixupHash, err := self.cmd.New(cmdArgs).RunWithOutput()
fixupHash, err := self.getHashOfLastCommitMade()
if err != nil {
return err
}
@@ -302,7 +305,20 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex+1),
overrideEditor: true,
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Hash, fixupHash),
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Hash, fixupHash, true),
}).Run()
}
func (self *RebaseCommands) MoveFixupCommitDown(commits []*models.Commit, targetCommitIndex int) error {
fixupHash, err := self.getHashOfLastCommitMade()
if err != nil {
return err
}
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
baseHashOrRoot: getBaseHashOrRoot(commits, targetCommitIndex+1),
overrideEditor: true,
instruction: daemon.NewMoveFixupCommitDownInstruction(commits[targetCommitIndex].Hash, fixupHash, false),
}).Run()
}
+1 -1
View File
@@ -100,7 +100,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
type scenario struct {
testName string
index int
contextSize int
contextSize uint64
similarityThreshold int
ignoreWhitespace bool
expected []string
@@ -210,7 +210,7 @@ func TestWorkingTreeDiff(t *testing.T) {
plain bool
cached bool
ignoreWhitespace bool
contextSize int
contextSize uint64
similarityThreshold int
runner *oscommands.FakeCmdObjRunner
}
@@ -352,7 +352,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
reverse bool
plain bool
ignoreWhitespace bool
contextSize int
contextSize uint64
runner *oscommands.FakeCmdObjRunner
}
+1 -1
View File
@@ -457,7 +457,7 @@ type AppState struct {
HideCommandLog bool
IgnoreWhitespaceInDiffView bool
DiffContextSize int
DiffContextSize uint64
RenameSimilarityThreshold int
LocalBranchSortOrder string
RemoteBranchSortOrder string
+10 -1
View File
@@ -161,6 +161,12 @@ type GuiConfig struct {
// Status panel view.
// One of 'dashboard' (default) | 'allBranchesLog'
StatusPanelView string `yaml:"statusPanelView" jsonschema:"enum=dashboard,enum=allBranchesLog"`
// If true, jump to the Files panel after popping a stash
SwitchToFilesAfterStashPop bool `yaml:"switchToFilesAfterStashPop"`
// If true, jump to the Files panel after applying a stash
SwitchToFilesAfterStashApply bool `yaml:"switchToFilesAfterStashApply"`
// If true, when using the panel jump keys (default 1 through 5) and target panel is already active, go to next tab instead
SwitchTabsWithPanelJumpKeys bool `yaml:"switchTabsWithPanelJumpKeys"`
}
func (c *GuiConfig) UseFuzzySearch() bool {
@@ -729,7 +735,10 @@ func GetDefaultConfig() *UserConfig {
Frames: []string{"|", "/", "-", "\\"},
Rate: 50,
},
StatusPanelView: "dashboard",
StatusPanelView: "dashboard",
SwitchToFilesAfterStashPop: true,
SwitchToFilesAfterStashApply: true,
SwitchTabsWithPanelJumpKeys: false,
},
Git: GitConfig{
Paging: PagingConfig{
+4 -1
View File
@@ -121,7 +121,10 @@ func (self *ListRenderer) insertNonModelItems(
break
}
if item.Index+offset >= startIdx {
padding := strings.Repeat(" ", columnPositions[item.Column])
padding := ""
if columnPositions != nil {
padding = strings.Repeat(" ", columnPositions[item.Column])
}
lines = slices.Insert(lines, item.Index+offset-startIdx, padding+item.Content)
}
offset++
+11 -11
View File
@@ -53,7 +53,7 @@ func NewPatchExplorerContext(
func(selectedLineIdx int) error {
ctx.GetMutex().Lock()
defer ctx.GetMutex().Unlock()
ctx.NavigateTo(ctx.c.Context().IsCurrent(ctx), selectedLineIdx)
ctx.NavigateTo(selectedLineIdx)
return nil
}),
)
@@ -79,15 +79,15 @@ func (self *PatchExplorerContext) GetIncludedLineIndices() []int {
return self.getIncludedLineIndices()
}
func (self *PatchExplorerContext) RenderAndFocus(isFocused bool) {
self.setContent(isFocused)
func (self *PatchExplorerContext) RenderAndFocus() {
self.setContent()
self.FocusSelection()
self.c.Render()
}
func (self *PatchExplorerContext) Render(isFocused bool) {
self.setContent(isFocused)
func (self *PatchExplorerContext) Render() {
self.setContent()
self.c.Render()
}
@@ -97,8 +97,8 @@ func (self *PatchExplorerContext) Focus() {
self.c.Render()
}
func (self *PatchExplorerContext) setContent(isFocused bool) {
self.GetView().SetContent(self.GetContentToRender(isFocused))
func (self *PatchExplorerContext) setContent() {
self.GetView().SetContent(self.GetContentToRender())
}
func (self *PatchExplorerContext) FocusSelection() {
@@ -119,19 +119,19 @@ func (self *PatchExplorerContext) FocusSelection() {
view.SetCursorY(endIdx - newOriginY)
}
func (self *PatchExplorerContext) GetContentToRender(isFocused bool) string {
func (self *PatchExplorerContext) GetContentToRender() string {
if self.GetState() == nil {
return ""
}
return self.GetState().RenderForLineIndices(isFocused, self.GetIncludedLineIndices())
return self.GetState().RenderForLineIndices(self.GetIncludedLineIndices())
}
func (self *PatchExplorerContext) NavigateTo(isFocused bool, selectedLineIdx int) {
func (self *PatchExplorerContext) NavigateTo(selectedLineIdx int) {
self.GetState().SetLineSelectMode()
self.GetState().SelectLine(selectedLineIdx)
self.RenderAndFocus(isFocused)
self.RenderAndFocus()
}
func (self *PatchExplorerContext) GetMutex() *deadlock.Mutex {
+1 -1
View File
@@ -107,7 +107,7 @@ func (gui *Gui) resetHelpersAndControllers() {
Files: helpers.NewFilesHelper(helperCommon),
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper),
Tags: helpers.NewTagsHelper(helperCommon, commitsHelper),
BranchesHelper: helpers.NewBranchesHelper(helperCommon),
BranchesHelper: helpers.NewBranchesHelper(helperCommon, worktreeHelper),
GPG: helpers.NewGpgHelper(helperCommon),
MergeAndRebase: rebaseHelper,
MergeConflicts: mergeConflictsHelper,
+54 -85
View File
@@ -3,7 +3,6 @@ package controllers
import (
"errors"
"fmt"
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
@@ -521,91 +520,16 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, KeepBranchSelectionIndex: true})
}
func (self *BranchesController) checkedOutByOtherWorktree(branch *models.Branch) bool {
return git_commands.CheckedOutByOtherWorktree(branch, self.c.Model().Worktrees)
}
func (self *BranchesController) promptWorktreeBranchDelete(selectedBranch *models.Branch) error {
worktree, ok := self.worktreeForBranch(selectedBranch)
if !ok {
self.c.Log.Error("promptWorktreeBranchDelete out of sync with list of worktrees")
return nil
}
// TODO: i18n
title := utils.ResolvePlaceholderString(self.c.Tr.BranchCheckedOutByWorktree, map[string]string{
"worktreeName": worktree.Name,
"branchName": selectedBranch.Name,
})
return self.c.Menu(types.CreateMenuOptions{
Title: title,
Items: []*types.MenuItem{
{
Label: self.c.Tr.SwitchToWorktree,
OnPress: func() error {
return self.c.Helpers().Worktree.Switch(worktree, context.LOCAL_BRANCHES_CONTEXT_KEY)
},
},
{
Label: self.c.Tr.DetachWorktree,
Tooltip: self.c.Tr.DetachWorktreeTooltip,
OnPress: func() error {
return self.c.Helpers().Worktree.Detach(worktree)
},
},
{
Label: self.c.Tr.RemoveWorktree,
OnPress: func() error {
return self.c.Helpers().Worktree.Remove(worktree, false)
},
},
},
})
}
func (self *BranchesController) localDelete(branch *models.Branch) error {
if self.checkedOutByOtherWorktree(branch) {
return self.promptWorktreeBranchDelete(branch)
}
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(_ gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch)
err := self.c.Git().Branch.LocalDelete(branch.Name, false)
if err != nil && strings.Contains(err.Error(), "git branch -D ") {
return self.forceDelete(branch)
}
if err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
})
return self.c.Helpers().BranchesHelper.ConfirmLocalDelete(branch)
}
func (self *BranchesController) remoteDelete(branch *models.Branch) error {
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(branch.UpstreamRemote, branch.Name)
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(branch.UpstreamRemote, branch.UpstreamBranch)
}
func (self *BranchesController) forceDelete(branch *models.Branch) error {
title := self.c.Tr.ForceDeleteBranchTitle
message := utils.ResolvePlaceholderString(
self.c.Tr.ForceDeleteBranchMessage,
map[string]string{
"selectedBranchName": branch.Name,
},
)
self.c.Confirm(types.ConfirmOpts{
Title: title,
Prompt: message,
HandleConfirm: func() error {
if err := self.c.Git().Branch.LocalDelete(branch.Name, true); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
},
})
return nil
func (self *BranchesController) localAndRemoteDelete(branch *models.Branch) error {
return self.c.Helpers().BranchesHelper.ConfirmLocalAndRemoteDelete(branch)
}
func (self *BranchesController) delete(branch *models.Branch) error {
@@ -633,6 +557,19 @@ func (self *BranchesController) delete(branch *models.Branch) error {
remoteDeleteItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
}
deleteBothItem := &types.MenuItem{
Label: self.c.Tr.DeleteLocalAndRemoteBranch,
Key: 'b',
OnPress: func() error {
return self.localAndRemoteDelete(branch)
},
}
if checkedOutBranch.Name == branch.Name {
deleteBothItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CantDeleteCheckOutBranch}
} else if !branch.IsTrackingRemote() || branch.UpstreamGone {
deleteBothItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
}
menuTitle := utils.ResolvePlaceholderString(
self.c.Tr.DeleteBranchTitle,
map[string]string{
@@ -642,7 +579,7 @@ func (self *BranchesController) delete(branch *models.Branch) error {
return self.c.Menu(types.CreateMenuOptions{
Title: menuTitle,
Items: []*types.MenuItem{localDeleteItem, remoteDeleteItem},
Items: []*types.MenuItem{localDeleteItem, remoteDeleteItem, deleteBothItem},
})
}
@@ -793,11 +730,23 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
{
LabelColumns: fromToLabelColumns(branch.Name, self.c.Tr.SelectBranch),
OnPress: func() error {
if !branch.IsTrackingRemote() {
return errors.New(self.c.Tr.PullRequestNoUpstream)
}
if len(self.c.Model().Remotes) == 1 {
toRemote := self.c.Model().Remotes[0].Name
self.c.Log.Debugf("PR will target the only existing remote '%s'", toRemote)
return self.promptForTargetBranchNameAndCreatePullRequest(branch, toRemote)
}
self.c.Prompt(types.PromptOpts{
Title: branch.Name + " →",
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteBranchesSuggestionsFunc("/"),
HandleConfirm: func(targetBranchName string) error {
return self.createPullRequest(branch.Name, targetBranchName)
Title: self.c.Tr.SelectTargetRemote,
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteSuggestionsFunc(),
HandleConfirm: func(toRemote string) error {
self.c.Log.Debugf("PR will target remote '%s'", toRemote)
return self.promptForTargetBranchNameAndCreatePullRequest(branch, toRemote)
},
})
@@ -827,6 +776,26 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
return self.c.Menu(types.CreateMenuOptions{Title: fmt.Sprint(self.c.Tr.CreatePullRequestOptions), Items: menuItems})
}
func (self *BranchesController) promptForTargetBranchNameAndCreatePullRequest(fromBranch *models.Branch, toRemote string) error {
remoteDoesNotExist := lo.NoneBy(self.c.Model().Remotes, func(remote *models.Remote) bool {
return remote.Name == toRemote
})
if remoteDoesNotExist {
return fmt.Errorf(self.c.Tr.NoValidRemoteName, toRemote)
}
self.c.Prompt(types.PromptOpts{
Title: fmt.Sprintf("%s → %s/", fromBranch.UpstreamBranch, toRemote),
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteBranchesForRemoteSuggestionsFunc(toRemote),
HandleConfirm: func(toBranch string) error {
self.c.Log.Debugf("PR will target branch '%s' on remote '%s'", toBranch, toRemote)
return self.createPullRequest(fromBranch.UpstreamBranch, toBranch)
},
})
return nil
}
func (self *BranchesController) createPullRequest(from string, to string) error {
url, err := self.c.Helpers().Host.GetPullRequestURL(from, to)
if err != nil {
@@ -303,7 +303,8 @@ func (self *CommitFilesController) toggleForPatch(selectedNodes []*filetree.Comm
self.c.Git().Patch.PatchBuilder.Reset()
}
return self.c.PostRefreshUpdate(self.context())
self.c.PostRefreshUpdate(self.context())
return nil
})
}
@@ -387,9 +388,7 @@ func (self *CommitFilesController) enterCommitFile(node *filetree.CommitFileNode
func (self *CommitFilesController) handleToggleCommitFileDirCollapsed(node *filetree.CommitFileNode) error {
self.context().CommitFileTreeViewModel.ToggleCollapsed(node.GetPath())
if err := self.c.PostRefreshUpdate(self.context()); err != nil {
self.c.Log.Error(err)
}
self.c.PostRefreshUpdate(self.context())
return nil
}
@@ -398,7 +397,8 @@ func (self *CommitFilesController) handleToggleCommitFileDirCollapsed(node *file
func (self *CommitFilesController) toggleTreeView() error {
self.context().CommitFileTreeViewModel.ToggleShowTree()
return self.c.PostRefreshUpdate(self.context())
self.c.PostRefreshUpdate(self.context())
return nil
}
// NOTE: these functions are identical to those in files_controller.go (except for types) and
@@ -3,6 +3,7 @@ package controllers
import (
"errors"
"fmt"
"math"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
@@ -68,7 +69,9 @@ func (self *ContextLinesController) Increase() error {
return err
}
self.c.AppState.DiffContextSize++
if self.c.AppState.DiffContextSize < math.MaxUint64 {
self.c.AppState.DiffContextSize++
}
return self.applyChange()
}
@@ -76,14 +79,14 @@ func (self *ContextLinesController) Increase() error {
}
func (self *ContextLinesController) Decrease() error {
old_size := self.c.AppState.DiffContextSize
if self.isShowingDiff() && old_size > 1 {
if self.isShowingDiff() {
if err := self.checkCanChangeContext(); err != nil {
return err
}
self.c.AppState.DiffContextSize = old_size - 1
if self.c.AppState.DiffContextSize > 0 {
self.c.AppState.DiffContextSize--
}
return self.applyChange()
}
+13 -8
View File
@@ -373,9 +373,7 @@ func (self *FilesController) optimisticChange(nodes []*filetree.FileNode, optimi
}
if rerender {
if err := self.c.PostRefreshUpdate(self.c.Contexts().Files); err != nil {
return err
}
self.c.PostRefreshUpdate(self.c.Contexts().Files)
}
return nil
@@ -697,6 +695,13 @@ func (self *FilesController) handleStatusFilterPressed() error {
},
Key: 'u',
},
{
Label: self.c.Tr.FilterTrackedFiles,
OnPress: func() error {
return self.setStatusFiltering(filetree.DisplayTracked)
},
Key: 't',
},
{
Label: self.c.Tr.ResetFilter,
OnPress: func() error {
@@ -710,7 +715,8 @@ func (self *FilesController) handleStatusFilterPressed() error {
func (self *FilesController) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error {
self.context().FileTreeViewModel.SetStatusFilter(filter)
return self.c.PostRefreshUpdate(self.context())
self.c.PostRefreshUpdate(self.context())
return nil
}
func (self *FilesController) edit(nodes []*filetree.FileNode) error {
@@ -949,9 +955,7 @@ func (self *FilesController) handleToggleDirCollapsed() error {
self.context().FileTreeViewModel.ToggleCollapsed(node.GetPath())
if err := self.c.PostRefreshUpdate(self.c.Contexts().Files); err != nil {
self.c.Log.Error(err)
}
self.c.PostRefreshUpdate(self.c.Contexts().Files)
return nil
}
@@ -959,7 +963,8 @@ func (self *FilesController) handleToggleDirCollapsed() error {
func (self *FilesController) toggleTreeView() error {
self.context().FileTreeViewModel.ToggleShowTree()
return self.c.PostRefreshUpdate(self.context())
self.c.PostRefreshUpdate(self.context())
return nil
}
func (self *FilesController) handleStashSave(stashFunc func(message string) error, action string) error {
+149 -3
View File
@@ -4,20 +4,68 @@ import (
"strings"
"github.com/jesseduffield/gocui"
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/types"
"github.com/jesseduffield/lazygit/pkg/utils"
)
type BranchesHelper struct {
c *HelperCommon
c *HelperCommon
worktreeHelper *WorktreeHelper
}
func NewBranchesHelper(c *HelperCommon) *BranchesHelper {
func NewBranchesHelper(c *HelperCommon, worktreeHelper *WorktreeHelper) *BranchesHelper {
return &BranchesHelper{
c: c,
c: c,
worktreeHelper: worktreeHelper,
}
}
func (self *BranchesHelper) ConfirmLocalDelete(branch *models.Branch) error {
if self.checkedOutByOtherWorktree(branch) {
return self.promptWorktreeBranchDelete(branch)
}
isMerged, err := self.c.Git().Branch.IsBranchMerged(branch, self.c.Model().MainBranches)
if err != nil {
return err
}
doDelete := func() error {
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(_ gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch)
if err := self.c.Git().Branch.LocalDelete(branch.Name, true); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
})
}
if isMerged {
return doDelete()
}
title := self.c.Tr.ForceDeleteBranchTitle
message := utils.ResolvePlaceholderString(
self.c.Tr.ForceDeleteBranchMessage,
map[string]string{
"selectedBranchName": branch.Name,
},
)
self.c.Confirm(types.ConfirmOpts{
Title: title,
Prompt: message,
HandleConfirm: func() error {
return doDelete()
},
})
return nil
}
func (self *BranchesHelper) ConfirmDeleteRemote(remoteName string, branchName string) error {
title := utils.ResolvePlaceholderString(
self.c.Tr.DeleteBranchTitle,
@@ -49,6 +97,104 @@ func (self *BranchesHelper) ConfirmDeleteRemote(remoteName string, branchName st
return nil
}
func (self *BranchesHelper) ConfirmLocalAndRemoteDelete(branch *models.Branch) error {
if self.checkedOutByOtherWorktree(branch) {
return self.promptWorktreeBranchDelete(branch)
}
isMerged, err := self.c.Git().Branch.IsBranchMerged(branch, self.c.Model().MainBranches)
if err != nil {
return err
}
prompt := utils.ResolvePlaceholderString(
self.c.Tr.DeleteLocalAndRemoteBranchPrompt,
map[string]string{
"localBranchName": branch.Name,
"remoteBranchName": branch.UpstreamBranch,
"remoteName": branch.UpstreamRemote,
},
)
if !isMerged {
prompt += "\n\n" + utils.ResolvePlaceholderString(
self.c.Tr.ForceDeleteBranchMessage,
map[string]string{
"selectedBranchName": branch.Name,
},
)
}
self.c.Confirm(types.ConfirmOpts{
Title: self.c.Tr.DeleteLocalAndRemoteBranch,
Prompt: prompt,
HandleConfirm: func() error {
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task) error {
// Delete the remote branch first so that we keep the local one
// in case of failure
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch)
if err := self.c.Git().Remote.DeleteRemoteBranch(task, branch.UpstreamRemote, branch.Name); err != nil {
return err
}
self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch)
if err := self.c.Git().Branch.LocalDelete(branch.Name, true); err != nil {
return err
}
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
})
},
})
return nil
}
func ShortBranchName(fullBranchName string) string {
return strings.TrimPrefix(strings.TrimPrefix(fullBranchName, "refs/heads/"), "refs/remotes/")
}
func (self *BranchesHelper) checkedOutByOtherWorktree(branch *models.Branch) bool {
return git_commands.CheckedOutByOtherWorktree(branch, self.c.Model().Worktrees)
}
func (self *BranchesHelper) worktreeForBranch(branch *models.Branch) (*models.Worktree, bool) {
return git_commands.WorktreeForBranch(branch, self.c.Model().Worktrees)
}
func (self *BranchesHelper) promptWorktreeBranchDelete(selectedBranch *models.Branch) error {
worktree, ok := self.worktreeForBranch(selectedBranch)
if !ok {
self.c.Log.Error("promptWorktreeBranchDelete out of sync with list of worktrees")
return nil
}
title := utils.ResolvePlaceholderString(self.c.Tr.BranchCheckedOutByWorktree, map[string]string{
"worktreeName": worktree.Name,
"branchName": selectedBranch.Name,
})
return self.c.Menu(types.CreateMenuOptions{
Title: title,
Items: []*types.MenuItem{
{
Label: self.c.Tr.SwitchToWorktree,
OnPress: func() error {
return self.worktreeHelper.Switch(worktree, context.LOCAL_BRANCHES_CONTEXT_KEY)
},
},
{
Label: self.c.Tr.DetachWorktree,
Tooltip: self.c.Tr.DetachWorktreeTooltip,
OnPress: func() error {
return self.worktreeHelper.Detach(worktree)
},
},
{
Label: self.c.Tr.RemoveWorktree,
OnPress: func() error {
return self.worktreeHelper.Remove(worktree, false)
},
},
},
})
}
@@ -57,7 +57,10 @@ func (self *CherryPickHelper) CopyRange(commitsList []*models.Commit, context ty
}
}
return self.rerender()
self.getData().DidPaste = false
self.rerender()
return nil
}
// HandlePasteCommits begins a cherry-pick rebase with the commits the user has copied.
@@ -102,7 +105,8 @@ func (self *CherryPickHelper) Paste() error {
return err
}
if !isInRebase {
return self.Reset()
self.getData().DidPaste = true
self.rerender()
}
return nil
})
@@ -113,14 +117,15 @@ func (self *CherryPickHelper) Paste() error {
}
func (self *CherryPickHelper) CanPaste() bool {
return self.getData().Active()
return self.getData().CanPaste()
}
func (self *CherryPickHelper) Reset() error {
self.getData().ContextKey = ""
self.getData().CherryPickedCommits = nil
return self.rerender()
self.rerender()
return nil
}
// you can only copy from one context at a time, because the order and position of commits matter
@@ -136,16 +141,12 @@ func (self *CherryPickHelper) resetIfNecessary(context types.Context) error {
return nil
}
func (self *CherryPickHelper) rerender() error {
func (self *CherryPickHelper) rerender() {
for _, context := range []types.Context{
self.c.Contexts().LocalCommits,
self.c.Contexts().ReflogCommits,
self.c.Contexts().SubCommits,
} {
if err := self.c.PostRefreshUpdate(context); err != nil {
return err
}
self.c.PostRefreshUpdate(context)
}
return nil
}
@@ -221,7 +221,7 @@ func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts typ
confirmationView.RenderTextArea()
} else {
self.c.ResetViewOrigin(confirmationView)
self.c.SetViewContent(confirmationView, style.AttrBold.Sprint(underlineLinks(opts.Prompt)))
self.c.SetViewContent(confirmationView, style.AttrBold.Sprint(opts.Prompt))
}
self.setKeyBindings(cancel, opts)
@@ -233,28 +233,6 @@ func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts typ
self.c.Context().Push(self.c.Contexts().Confirmation)
}
func underlineLinks(text string) string {
result := ""
remaining := text
for {
linkStart := strings.Index(remaining, "https://")
if linkStart == -1 {
break
}
linkEnd := strings.IndexAny(remaining[linkStart:], " \n>")
if linkEnd == -1 {
linkEnd = len(remaining)
} else {
linkEnd += linkStart
}
underlinedLink := style.PrintSimpleHyperlink(remaining[linkStart:linkEnd])
result += remaining[:linkStart] + underlinedLink
remaining = remaining[linkEnd:]
}
return result + remaining
}
func (self *ConfirmationHelper) setKeyBindings(cancel goContext.CancelFunc, opts types.CreatePopupPanelOpts) {
var onConfirm func() error
if opts.HandleConfirmPrompt != nil {
@@ -1,63 +0,0 @@
package helpers
import (
"testing"
"github.com/gookit/color"
"github.com/stretchr/testify/assert"
"github.com/xo/terminfo"
)
func Test_underlineLinks(t *testing.T) {
scenarios := []struct {
name string
text string
expectedResult string
}{
{
name: "empty string",
text: "",
expectedResult: "",
},
{
name: "no links",
text: "abc",
expectedResult: "abc",
},
{
name: "entire string is a link",
text: "https://example.com",
expectedResult: "\x1b]8;;https://example.com\x1b\\https://example.com\x1b]8;;\x1b\\",
},
{
name: "link preceded and followed by text",
text: "bla https://example.com xyz",
expectedResult: "bla \x1b]8;;https://example.com\x1b\\https://example.com\x1b]8;;\x1b\\ xyz",
},
{
name: "more than one link",
text: "bla https://link1 blubb https://link2 xyz",
expectedResult: "bla \x1b]8;;https://link1\x1b\\https://link1\x1b]8;;\x1b\\ blubb \x1b]8;;https://link2\x1b\\https://link2\x1b]8;;\x1b\\ xyz",
},
{
name: "link in angle brackets",
text: "See <https://example.com> for details",
expectedResult: "See <\x1b]8;;https://example.com\x1b\\https://example.com\x1b]8;;\x1b\\> for details",
},
{
name: "link followed by newline",
text: "URL: https://example.com\nNext line",
expectedResult: "URL: \x1b]8;;https://example.com\x1b\\https://example.com\x1b]8;;\x1b\\\nNext line",
},
}
oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelMillions)
defer color.ForceSetColorLevel(oldColorLevel)
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
result := underlineLinks(s.text)
assert.Equal(t, s.expectedResult, result)
})
}
}
@@ -487,5 +487,6 @@ func (self *MergeAndRebaseHelper) SquashMergeCommitted(refName, checkedOutBranch
func (self *MergeAndRebaseHelper) ResetMarkedBaseCommit() error {
self.c.Modes().MarkedBaseCommit.Reset()
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
return nil
}
@@ -52,7 +52,8 @@ func (self *PatchBuildingHelper) Reset() error {
}
// refreshing the current context so that the secondary panel is hidden if necessary.
return self.c.PostRefreshUpdate(self.c.Context().Current())
self.c.PostRefreshUpdate(self.c.Context().Current())
return nil
}
func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpts) {
@@ -97,7 +98,7 @@ func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpt
return
}
mainContent := context.GetContentToRender(true)
mainContent := context.GetContentToRender()
self.c.Contexts().CustomPatchBuilder.FocusSelection()
+27 -42
View File
@@ -157,7 +157,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
}
if scopeSet.Includes(types.STASH) {
refresh("stash", func() { _ = self.refreshStashEntries() })
refresh("stash", func() { self.refreshStashEntries() })
}
if scopeSet.Includes(types.TAGS) {
@@ -169,7 +169,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
}
if scopeSet.Includes(types.WORKTREES) && !includeWorktreesWithBranches {
refresh("worktrees", func() { _ = self.refreshWorktrees() })
refresh("worktrees", func() { self.refreshWorktrees() })
}
if scopeSet.Includes(types.STAGING) {
@@ -343,7 +343,8 @@ func (self *RefreshHelper) refreshCommitsWithLimit() error {
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState()
self.c.Model().CheckedOutBranch = checkedOutBranchName
return self.refreshView(self.c.Contexts().LocalCommits)
self.refreshView(self.c.Contexts().LocalCommits)
return nil
}
func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
@@ -368,7 +369,8 @@ func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
self.c.Model().SubCommits = commits
self.RefreshAuthors(commits)
return self.refreshView(self.c.Contexts().SubCommits)
self.refreshView(self.c.Contexts().SubCommits)
return nil
}
func (self *RefreshHelper) RefreshAuthors(commits []*models.Commit) {
@@ -397,7 +399,8 @@ func (self *RefreshHelper) refreshCommitFilesContext() error {
self.c.Model().CommitFiles = files
self.c.Contexts().CommitFiles.CommitFileTreeViewModel.SetTree()
return self.refreshView(self.c.Contexts().CommitFiles)
self.refreshView(self.c.Contexts().CommitFiles)
return nil
}
func (self *RefreshHelper) refreshRebaseCommits() error {
@@ -411,7 +414,8 @@ func (self *RefreshHelper) refreshRebaseCommits() error {
self.c.Model().Commits = updatedCommits
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState()
return self.refreshView(self.c.Contexts().LocalCommits)
self.refreshView(self.c.Contexts().LocalCommits)
return nil
}
func (self *RefreshHelper) refreshTags() error {
@@ -422,7 +426,8 @@ func (self *RefreshHelper) refreshTags() error {
self.c.Model().Tags = tags
return self.refreshView(self.c.Contexts().Tags)
self.refreshView(self.c.Contexts().Tags)
return nil
}
func (self *RefreshHelper) refreshStateSubmoduleConfigs() error {
@@ -482,9 +487,7 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSele
if refreshWorktrees {
self.loadWorktrees()
if err := self.refreshView(self.c.Contexts().Worktrees); err != nil {
self.c.Log.Error(err)
}
self.refreshView(self.c.Contexts().Worktrees)
}
if !keepBranchSelectionIndex && prevSelectedBranch != nil {
@@ -495,9 +498,7 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSele
}
}
if err := self.refreshView(self.c.Contexts().Branches); err != nil {
self.c.Log.Error(err)
}
self.refreshView(self.c.Contexts().Branches)
// Need to re-render the commits view because the visualization of local
// branch heads might have changed
@@ -525,14 +526,8 @@ func (self *RefreshHelper) refreshFilesAndSubmodules() error {
}
self.c.OnUIThread(func() error {
if err := self.refreshView(self.c.Contexts().Submodules); err != nil {
self.c.Log.Error(err)
}
if err := self.refreshView(self.c.Contexts().Files); err != nil {
self.c.Log.Error(err)
}
self.refreshView(self.c.Contexts().Submodules)
self.refreshView(self.c.Contexts().Files)
return nil
})
@@ -653,7 +648,8 @@ func (self *RefreshHelper) refreshReflogCommits() error {
model.FilteredReflogCommits = model.ReflogCommits
}
return self.refreshView(self.c.Contexts().ReflogCommits)
self.refreshView(self.c.Contexts().ReflogCommits)
return nil
}
func (self *RefreshHelper) refreshRemotes() error {
@@ -677,14 +673,8 @@ func (self *RefreshHelper) refreshRemotes() error {
}
}
if err := self.refreshView(self.c.Contexts().Remotes); err != nil {
return err
}
if err := self.refreshView(self.c.Contexts().RemoteBranches); err != nil {
return err
}
self.refreshView(self.c.Contexts().Remotes)
self.refreshView(self.c.Contexts().RemoteBranches)
return nil
}
@@ -698,23 +688,20 @@ func (self *RefreshHelper) loadWorktrees() {
self.c.Model().Worktrees = worktrees
}
func (self *RefreshHelper) refreshWorktrees() error {
func (self *RefreshHelper) refreshWorktrees() {
self.loadWorktrees()
// need to refresh branches because the branches view shows worktrees against
// branches
if err := self.refreshView(self.c.Contexts().Branches); err != nil {
return err
}
return self.refreshView(self.c.Contexts().Worktrees)
self.refreshView(self.c.Contexts().Branches)
self.refreshView(self.c.Contexts().Worktrees)
}
func (self *RefreshHelper) refreshStashEntries() error {
func (self *RefreshHelper) refreshStashEntries() {
self.c.Model().StashEntries = self.c.Git().Loaders.StashLoader.
GetStashEntries(self.c.Modes().Filtering.GetPath())
return self.refreshView(self.c.Contexts().Stash)
self.refreshView(self.c.Contexts().Stash)
}
// never call this on its own, it should only be called from within refreshCommits()
@@ -754,12 +741,12 @@ func (self *RefreshHelper) refForLog() string {
return bisectInfo.GetStartHash()
}
func (self *RefreshHelper) refreshView(context types.Context) error {
func (self *RefreshHelper) refreshView(context types.Context) {
// Re-applying the filter must be done before re-rendering the view, so that
// the filtered list model is up to date for rendering.
self.searchHelper.ReApplyFilter(context)
err := self.c.PostRefreshUpdate(context)
self.c.PostRefreshUpdate(context)
self.c.AfterLayout(func() error {
// Re-applying the search must be done after re-rendering the view though,
@@ -773,6 +760,4 @@ func (self *RefreshHelper) refreshView(context types.Context) error {
self.searchHelper.ReApplySearch(context)
return nil
})
return err
}
+2 -2
View File
@@ -213,7 +213,7 @@ func (self *SearchHelper) Cancel() {
switch context := state.Context.(type) {
case types.IFilterableContext:
context.ClearFilter()
_ = self.c.PostRefreshUpdate(context)
self.c.PostRefreshUpdate(context)
case types.ISearchableContext:
context.ClearSearchString()
context.GetView().ClearSearch()
@@ -231,7 +231,7 @@ func (self *SearchHelper) OnPromptContentChanged(searchString string) {
context.SetSelection(0)
context.GetView().SetOriginY(0)
context.SetFilter(searchString, self.c.UserConfig().Gui.UseFuzzySearch())
_ = self.c.PostRefreshUpdate(context)
self.c.PostRefreshUpdate(context)
case types.ISearchableContext:
// do nothing
default:
@@ -73,8 +73,8 @@ func (self *StagingHelper) RefreshStagingPanel(focusOpts types.OnFocusOpts) {
mainState := mainContext.GetState()
secondaryState := secondaryContext.GetState()
mainContent := mainContext.GetContentToRender(!secondaryFocused)
secondaryContent := secondaryContext.GetContentToRender(secondaryFocused)
mainContent := mainContext.GetContentToRender()
secondaryContent := secondaryContext.GetContentToRender()
mainContext.GetMutex().Unlock()
secondaryContext.GetMutex().Unlock()
@@ -67,10 +67,7 @@ func (self *SubCommitsHelper) ViewSubCommits(opts ViewSubCommitsOpts) error {
subCommitsContext.GetView().ClearSearch()
subCommitsContext.GetView().TitlePrefix = opts.Context.GetView().TitlePrefix
err = self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
if err != nil {
return err
}
self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
self.c.Context().Push(self.c.Contexts().SubCommits)
return nil
@@ -162,10 +162,26 @@ func (self *SuggestionsHelper) getRemoteBranchNames(separator string) []string {
})
}
func (self *SuggestionsHelper) getRemoteBranchNamesForRemote(remoteName string) []string {
remote, ok := lo.Find(self.c.Model().Remotes, func(remote *models.Remote) bool {
return remote.Name == remoteName
})
if ok {
return lo.Map(remote.Branches, func(branch *models.RemoteBranch, _ int) string {
return branch.Name
})
}
return nil
}
func (self *SuggestionsHelper) GetRemoteBranchesSuggestionsFunc(separator string) func(string) []*types.Suggestion {
return FilterFunc(self.getRemoteBranchNames(separator), self.c.UserConfig().Gui.UseFuzzySearch())
}
func (self *SuggestionsHelper) GetRemoteBranchesForRemoteSuggestionsFunc(remoteName string) func(string) []*types.Suggestion {
return FilterFunc(self.getRemoteBranchNamesForRemote(remoteName), self.c.UserConfig().Gui.UseFuzzySearch())
}
func (self *SuggestionsHelper) getTagNames() []string {
return lo.Map(self.c.Model().Tags, func(tag *models.Tag, _ int) string {
return tag.Name
@@ -49,7 +49,8 @@ func (self *JumpToSideWindowController) GetKeybindings(opts types.KeybindingsOpt
func (self *JumpToSideWindowController) goToSideWindow(window string) func() error {
return func() error {
if self.c.Helpers().Window.CurrentWindow() == window {
sideWindowAlreadyActive := self.c.Helpers().Window.CurrentWindow() == window
if sideWindowAlreadyActive && self.c.UserConfig().Gui.SwitchTabsWithPanelJumpKeys {
return self.nextTabFunc()
}
+105 -23
View File
@@ -9,6 +9,7 @@ import (
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
"github.com/jesseduffield/lazygit/pkg/gui/context"
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
"github.com/jesseduffield/lazygit/pkg/gui/style"
@@ -115,7 +116,7 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
},
{
Key: opts.GetKey(editCommitKey),
Handler: self.withItems(self.edit),
Handler: self.withItemsRange(self.edit),
GetDisabledReason: self.require(
self.itemRangeSelected(self.midRebaseCommandEnabled),
),
@@ -510,11 +511,25 @@ func (self *LocalCommitsController) drop(selectedCommits []*models.Commit, start
return nil
}
func (self *LocalCommitsController) edit(selectedCommits []*models.Commit) error {
func (self *LocalCommitsController) edit(selectedCommits []*models.Commit, startIdx int, endIdx int) error {
if self.isRebasing() {
return self.updateTodos(todo.Edit, selectedCommits)
}
commits := self.c.Model().Commits
if !commits[endIdx].IsMerge() {
selectionRangeAndMode := self.getSelectionRangeAndMode()
err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, todo.Edit)
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
err,
types.RefreshOptions{
Mode: types.BLOCK_UI, Then: func() error {
self.restoreSelectionRangeAndMode(selectionRangeAndMode)
return nil
},
})
}
return self.startInteractiveRebaseWithEdit(selectedCommits)
}
@@ -532,10 +547,7 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
) error {
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
self.c.LogAction(self.c.Tr.Actions.EditCommit)
selectedIdx, rangeStartIdx, rangeSelectMode := self.context().GetSelectionRangeAndMode()
commits := self.c.Model().Commits
selectedHash := commits[selectedIdx].Hash
rangeStartHash := commits[rangeStartIdx].Hash
selectionRangeAndMode := self.getSelectionRangeAndMode()
err := self.c.Git().Rebase.EditRebase(commitsToEdit[len(commitsToEdit)-1].Hash)
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
err,
@@ -554,23 +566,41 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
}
}
// We need to select the same commit range again because after starting a rebase,
// new lines can be added for update-ref commands in the TODO file, due to
// stacked branches. So the selected commits may be in different positions in the list.
_, newSelectedIdx, ok1 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
return c.Hash == selectedHash
})
_, newRangeStartIdx, ok2 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
return c.Hash == rangeStartHash
})
if ok1 && ok2 {
self.context().SetSelectionRangeAndMode(newSelectedIdx, newRangeStartIdx, rangeSelectMode)
}
self.restoreSelectionRangeAndMode(selectionRangeAndMode)
return nil
}})
})
}
type SelectionRangeAndMode struct {
selectedHash string
rangeStartHash string
mode traits.RangeSelectMode
}
func (self *LocalCommitsController) getSelectionRangeAndMode() SelectionRangeAndMode {
selectedIdx, rangeStartIdx, rangeSelectMode := self.context().GetSelectionRangeAndMode()
commits := self.c.Model().Commits
selectedHash := commits[selectedIdx].Hash
rangeStartHash := commits[rangeStartIdx].Hash
return SelectionRangeAndMode{selectedHash, rangeStartHash, rangeSelectMode}
}
func (self *LocalCommitsController) restoreSelectionRangeAndMode(selectionRangeAndMode SelectionRangeAndMode) {
// We need to select the same commit range again because after starting a rebase,
// new lines can be added for update-ref commands in the TODO file, due to
// stacked branches. So the selected commits may be in different positions in the list.
_, newSelectedIdx, ok1 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
return c.Hash == selectionRangeAndMode.selectedHash
})
_, newRangeStartIdx, ok2 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
return c.Hash == selectionRangeAndMode.rangeStartHash
})
if ok1 && ok2 {
self.context().SetSelectionRangeAndMode(newSelectedIdx, newRangeStartIdx, selectionRangeAndMode.mode)
}
}
func (self *LocalCommitsController) findCommitForQuickStartInteractiveRebase() (*models.Commit, error) {
commit, index, ok := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
return c.IsMerge() || c.Status == models.StatusMerged
@@ -895,6 +925,10 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
return err
}
if err := self.moveFixupCommitToOwnerStackedBranch(commit); err != nil {
return err
}
self.context().MoveSelectedLine(1)
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC})
})
@@ -924,6 +958,50 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
})
}
func (self *LocalCommitsController) moveFixupCommitToOwnerStackedBranch(targetCommit *models.Commit) error {
if self.c.Git().Version.IsOlderThan(2, 38, 0) {
// Git 2.38.0 introduced the `rebase.updateRefs` config option. Don't
// move the commit down with older versions, as it would break the stack.
return nil
}
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
// Can't move commits while rebasing
return nil
}
if targetCommit.Status == models.StatusMerged {
// Target commit is already on main. It's a bit questionable that we
// allow creating a fixup commit for it in the first place, but we
// always did, so why restrict that now; however, it doesn't make sense
// to move the created fixup commit down in that case.
return nil
}
if !self.c.Git().Config.GetRebaseUpdateRefs() {
// If the user has disabled rebase.updateRefs, we don't move the fixup
// because this would break the stack of branches (presumably they like
// to manage it themselves manually, or something).
return nil
}
headOfOwnerBranchIdx := -1
for i := self.context().GetSelectedLineIdx(); i > 0; i-- {
if lo.SomeBy(self.c.Model().Branches, func(b *models.Branch) bool {
return b.CommitHash == self.c.Model().Commits[i].Hash
}) {
headOfOwnerBranchIdx = i
break
}
}
if headOfOwnerBranchIdx == -1 {
return nil
}
return self.c.Git().Rebase.MoveFixupCommitDown(self.c.Model().Commits, headOfOwnerBranchIdx)
}
func (self *LocalCommitsController) createAmendCommit(commit *models.Commit, includeFileChanges bool) error {
commitMessage, err := self.c.Git().Commit.GetCommitMessage(commit.Hash)
if err != nil {
@@ -947,6 +1025,10 @@ func (self *LocalCommitsController) createAmendCommit(commit *models.Commit, inc
return err
}
if err := self.moveFixupCommitToOwnerStackedBranch(commit); err != nil {
return err
}
self.context().MoveSelectedLine(1)
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC})
})
@@ -1125,10 +1207,9 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
return func() error {
self.c.GetAppState().GitLogShowGraph = value
self.c.SaveAppStateAndLogError()
if err := self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits); err != nil {
return err
}
return self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
return nil
}
}
return self.c.Menu(types.CreateMenuOptions{
@@ -1234,7 +1315,8 @@ func (self *LocalCommitsController) markAsBaseCommit(commit *models.Commit) erro
} else {
self.c.Modes().MarkedBaseCommit.SetHash(commit.Hash)
}
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
return nil
}
func (self *LocalCommitsController) isHeadCommit(idx int) bool {
@@ -160,7 +160,8 @@ func (self *PatchBuildingController) Escape() error {
if state.SelectingRange() || state.SelectingHunk() {
state.SetLineSelectMode()
return self.c.PostRefreshUpdate(context)
self.c.PostRefreshUpdate(context)
return nil
}
self.c.Helpers().PatchBuilding.Escape()
@@ -302,7 +302,7 @@ func (self *PatchExplorerController) withRenderAndFocus(f func() error) func() e
return err
}
self.context.RenderAndFocus(self.isFocused())
self.context.RenderAndFocus()
return nil
})
}
+2 -1
View File
@@ -58,7 +58,8 @@ func (self *QuitActions) Escape() error {
if listContext, ok := currentContext.(types.IListContext); ok {
if listContext.GetList().IsSelectingRange() {
listContext.GetList().CancelRangeSelect()
return self.c.PostRefreshUpdate(listContext)
self.c.PostRefreshUpdate(listContext)
return nil
}
}
+1 -3
View File
@@ -127,9 +127,7 @@ func (self *RemotesController) enter(remote *models.Remote) error {
remoteBranchesContext.SetParentContext(self.Context())
remoteBranchesContext.GetView().TitlePrefix = self.Context().GetView().TitlePrefix
if err := self.c.PostRefreshUpdate(remoteBranchesContext); err != nil {
return err
}
self.c.PostRefreshUpdate(remoteBranchesContext)
self.c.Context().Push(remoteBranchesContext)
return nil
+2 -3
View File
@@ -132,8 +132,6 @@ func (self *StagingController) GetOnFocusLost() func(types.OnFocusLostOpts) {
if opts.NewContextKey != self.otherContext.GetKey() {
self.c.Views().Staging.Wrap = true
self.c.Views().StagingSecondary.Wrap = true
self.c.Contexts().Staging.Render(false)
self.c.Contexts().StagingSecondary.Render(false)
}
}
}
@@ -168,7 +166,8 @@ func (self *StagingController) EditFile() error {
func (self *StagingController) Escape() error {
if self.context.GetState().SelectingRange() || self.context.GetState().SelectingHunk() {
self.context.GetState().SetLineSelectMode()
return self.c.PostRefreshUpdate(self.context)
self.c.PostRefreshUpdate(self.context)
return nil
}
self.c.Context().Pop()
+6 -2
View File
@@ -111,7 +111,9 @@ func (self *StashController) handleStashApply(stashEntry *models.StashEntry) err
if err != nil {
return err
}
self.c.Context().Push(self.c.Contexts().Files)
if self.c.UserConfig().Gui.SwitchToFilesAfterStashApply {
self.c.Context().Push(self.c.Contexts().Files)
}
return nil
}
@@ -138,7 +140,9 @@ func (self *StashController) handleStashPop(stashEntry *models.StashEntry) error
if err != nil {
return err
}
self.c.Context().Push(self.c.Contexts().Files)
if self.c.UserConfig().Gui.SwitchToFilesAfterStashPop {
self.c.Context().Push(self.c.Contexts().Files)
}
return nil
}
+6 -6
View File
@@ -208,12 +208,12 @@ func (self *StatusController) showDashboard() {
[]string{
lazygitTitle(),
fmt.Sprintf("Copyright %d Jesse Duffield", time.Now().Year()),
fmt.Sprintf("Keybindings: %s", style.PrintSimpleHyperlink(fmt.Sprintf(constants.Links.Docs.Keybindings, versionStr))),
fmt.Sprintf("Config Options: %s", style.PrintSimpleHyperlink(fmt.Sprintf(constants.Links.Docs.Config, versionStr))),
fmt.Sprintf("Tutorial: %s", style.PrintSimpleHyperlink(constants.Links.Docs.Tutorial)),
fmt.Sprintf("Raise an Issue: %s", style.PrintSimpleHyperlink(constants.Links.Issues)),
fmt.Sprintf("Release Notes: %s", style.PrintSimpleHyperlink(constants.Links.Releases)),
style.FgMagenta.Sprintf("Become a sponsor: %s", style.PrintSimpleHyperlink(constants.Links.Donate)), // caffeine ain't free
fmt.Sprintf("Keybindings: %s", fmt.Sprintf(constants.Links.Docs.Keybindings, versionStr)),
fmt.Sprintf("Config Options: %s", fmt.Sprintf(constants.Links.Docs.Config, versionStr)),
fmt.Sprintf("Tutorial: %s", constants.Links.Docs.Tutorial),
fmt.Sprintf("Raise an Issue: %s", constants.Links.Issues),
fmt.Sprintf("Release Notes: %s", constants.Links.Releases),
style.FgMagenta.Sprintf("Become a sponsor: %s", constants.Links.Donate), // caffeine ain't free
}, "\n\n") + "\n"
self.c.RenderToMainViews(types.RefreshMainOpts{
@@ -162,10 +162,7 @@ func (self *FilesController) createResetMenu() error {
func (self *FilesController) animateExplosion() {
self.Explode(self.c.Views().Files, func() {
err := self.c.PostRefreshUpdate(self.c.Contexts().Files)
if err != nil {
self.c.Log.Error(err)
}
self.c.PostRefreshUpdate(self.c.Contexts().Files)
})
}
+17 -5
View File
@@ -10,6 +10,8 @@ import (
func BuildTreeFromFiles(files []*models.File) *Node[models.File] {
root := &Node[models.File]{}
childrenMapsByNode := make(map[*Node[models.File]]map[string]*Node[models.File])
var curr *Node[models.File]
for _, file := range files {
splitPath := split(file.Name)
@@ -23,11 +25,19 @@ func BuildTreeFromFiles(files []*models.File) *Node[models.File] {
}
path := join(splitPath[:i+1])
for _, existingChild := range curr.Children {
if existingChild.Path == path {
curr = existingChild
continue outer
}
var currNodeChildrenMap map[string]*Node[models.File]
var isCurrNodeMapped bool
if currNodeChildrenMap, isCurrNodeMapped = childrenMapsByNode[curr]; !isCurrNodeMapped {
currNodeChildrenMap = make(map[string]*Node[models.File])
childrenMapsByNode[curr] = currNodeChildrenMap
}
child, doesCurrNodeHaveChildAlready := currNodeChildrenMap[path]
if doesCurrNodeHaveChildAlready {
curr = child
continue outer
}
newChild := &Node[models.File]{
@@ -36,6 +46,8 @@ func BuildTreeFromFiles(files []*models.File) *Node[models.File] {
}
curr.Children = append(curr.Children, newChild)
currNodeChildrenMap[path] = newChild
curr = newChild
}
}
+3
View File
@@ -15,6 +15,7 @@ const (
DisplayAll FileTreeDisplayFilter = iota
DisplayStaged
DisplayUnstaged
DisplayTracked
// this shows files with merge conflicts
DisplayConflicted
)
@@ -82,6 +83,8 @@ func (self *FileTree) getFilesForDisplay() []*models.File {
return self.FilterFiles(func(file *models.File) bool { return file.HasStagedChanges })
case DisplayUnstaged:
return self.FilterFiles(func(file *models.File) bool { return file.HasUnstagedChanges })
case DisplayTracked:
return self.FilterFiles(func(file *models.File) bool { return file.Tracked })
case DisplayConflicted:
return self.FilterFiles(func(file *models.File) bool { return file.HasMergeConflicts })
default:
+13
View File
@@ -40,6 +40,19 @@ func TestFilterAction(t *testing.T) {
{Name: "file1", ShortStatus: "M ", HasStagedChanges: true},
},
},
{
name: "filter files that are tracked",
filter: DisplayTracked,
files: []*models.File{
{Name: "dir2/dir2/file4", ShortStatus: "M ", Tracked: true},
{Name: "dir2/file5", ShortStatus: "M ", Tracked: false},
{Name: "file1", ShortStatus: "M ", Tracked: true},
},
expected: []*models.File{
{Name: "dir2/dir2/file4", ShortStatus: "M ", Tracked: true},
{Name: "file1", ShortStatus: "M ", Tracked: true},
},
},
{
name: "filter all files",
filter: DisplayAll,
+2 -2
View File
@@ -29,8 +29,8 @@ func (self *guiCommon) Refresh(opts types.RefreshOptions) error {
return self.gui.helpers.Refresh.Refresh(opts)
}
func (self *guiCommon) PostRefreshUpdate(context types.Context) error {
return self.gui.postRefreshUpdate(context)
func (self *guiCommon) PostRefreshUpdate(context types.Context) {
self.gui.postRefreshUpdate(context)
}
func (self *guiCommon) RunSubprocessAndRefresh(cmdObj oscommands.ICmdObj) error {
+5
View File
@@ -57,6 +57,11 @@ func (self *GuiDriver) Click(x, y int) {
0,
)
self.waitTillIdle()
self.gui.g.ReplayedEvents.MouseEvents <- gocui.NewTcellMouseEventWrapper(
tcell.NewEventMouse(x, y, tcell.ButtonNone, 0),
0,
)
self.waitTillIdle()
}
// wait until lazygit is idle (i.e. all processing is done) before continuing
+1 -1
View File
@@ -57,7 +57,7 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error {
return err
}
_ = gui.c.PostRefreshUpdate(gui.State.Contexts.Menu)
gui.c.PostRefreshUpdate(gui.State.Contexts.Menu)
// TODO: ensure that if we're opened a menu from within a menu that it renders correctly
gui.c.Context().Push(gui.State.Contexts.Menu)
+14 -1
View File
@@ -9,8 +9,13 @@ import (
type CherryPicking struct {
CherryPickedCommits []*models.Commit
// we only allow cherry picking from one context at a time, so you can't copy a commit from the local commits context and then also copy a commit in the reflog context
// we only allow cherry picking from one context at a time, so you can't copy a commit from
// the local commits context and then also copy a commit in the reflog context
ContextKey string
// keep track of whether the currently copied commits have been pasted already. If so, we hide
// the mode and the blue display of the commits, but we still allow pasting them again.
DidPaste bool
}
func New() *CherryPicking {
@@ -21,10 +26,18 @@ func New() *CherryPicking {
}
func (self *CherryPicking) Active() bool {
return self.CanPaste() && !self.DidPaste
}
func (self *CherryPicking) CanPaste() bool {
return len(self.CherryPickedCommits) > 0
}
func (self *CherryPicking) SelectedHashSet() *set.Set[string] {
if self.DidPaste {
return set.New[string]()
}
hashes := lo.Map(self.CherryPickedCommits, func(commit *models.Commit, _ int) string {
return commit.Hash
})
+2 -2
View File
@@ -94,7 +94,7 @@ func (s *State) ToggleStickySelectRange() {
}
func (s *State) ToggleSelectRange(sticky bool) {
if s.selectMode == RANGE {
if s.SelectingRange() {
s.selectMode = LINE
} else {
s.selectMode = RANGE
@@ -249,7 +249,7 @@ func (s *State) AdjustSelectedLineIdx(change int) {
s.SelectLine(s.selectedLineIdx + change)
}
func (s *State) RenderForLineIndices(isFocused bool, includedLineIndices []int) string {
func (s *State) RenderForLineIndices(includedLineIndices []int) string {
includedLineIndicesSet := set.NewFromSlice(includedLineIndices)
return s.patch.FormatView(patch.FormatViewOpts{
IncLineIndices: includedLineIndicesSet,
+39 -39
View File
@@ -21,7 +21,7 @@ var nameIconMap = map[string]IconProperties{
".bash_profile": {Icon: "\ue615", Color: 113}, // 
".bashprofile": {Icon: "\ue615", Color: 113}, // 
".bashrc": {Icon: "\ue795", Color: 113}, // 
".dockerignore": {Icon: "\uf0868", Color: 68}, // 󰡨
".dockerignore": {Icon: "\U000f0868", Color: 68}, // 󰡨
".ds_store": {Icon: "\ue615", Color: 239}, // 
".editorconfig": {Icon: "\ue652", Color: 255}, // 
".env": {Icon: "\uf462", Color: 227}, // 
@@ -44,7 +44,7 @@ var nameIconMap = map[string]IconProperties{
".mailmap": {Icon: "\U000f02a2", Color: 202}, // 󰊢
".npmignore": {Icon: "\ue71e", Color: 197}, // 
".npmrc": {Icon: "\ue71e", Color: 197}, // 
".nuxtrc": {Icon: "\uf1106", Color: 42}, // 󱄆
".nuxtrc": {Icon: "\U000f1106", Color: 42}, // 󱄆
".nvmrc": {Icon: "\ue718", Color: 71}, // 
".prettierignore": {Icon: "\ue6b4", Color: 33}, // 
".prettierrc": {Icon: "\ue6b4", Color: 33}, // 
@@ -76,21 +76,21 @@ var nameIconMap = map[string]IconProperties{
"cantorrc": {Icon: "\uf373", Color: 32}, // 
"Cargo.lock": {Icon: "\ue7a8", Color: 216}, // 
"Cargo.toml": {Icon: "\ue7a8", Color: 216}, // 
"checkhealth": {Icon: "\uf04d9", Color: 75}, // 󰓙
"checkhealth": {Icon: "\U000f04d9", Color: 75}, // 󰓙
"cmakelists.txt": {Icon: "\ue615", Color: 66}, // 
"commit_editmsg": {Icon: "\ue702", Color: 196}, // 
"COMMIT_EDITMSG": {Icon: "\ue702", Color: 239}, // 
"commitlint.config.js": {Icon: "\uf0718", Color: 30}, // 󰜘
"commitlint.config.ts": {Icon: "\uf0718", Color: 30}, // 󰜘
"commitlint.config.js": {Icon: "\U000f0718", Color: 30}, // 󰜘
"commitlint.config.ts": {Icon: "\U000f0718", Color: 30}, // 󰜘
"compose.yaml": {Icon: "\uf308", Color: 68}, // 
"compose.yml": {Icon: "\uf308", Color: 68}, // 
"config": {Icon: "\ue5fc", Color: 241}, // 
"containerfile": {Icon: "\uf0868", Color: 68}, // 󰡨
"containerfile": {Icon: "\U000f0868", Color: 68}, // 󰡨
"copying": {Icon: "\ue60a", Color: 185}, // 
"copying.lesser": {Icon: "\ue60a", Color: 185}, // 
"docker-compose.yaml": {Icon: "\uf308", Color: 68}, // 
"docker-compose.yml": {Icon: "\uf308", Color: 68}, // 
"dockerfile": {Icon: "\uf0868", Color: 68}, // 󰡨
"dockerfile": {Icon: "\U000f0868", Color: 68}, // 󰡨
"Dockerfile": {Icon: "\uf308", Color: 68}, // 
"ds_store": {Icon: "\uf179", Color: 15}, // 
"eslint.config.cjs": {Icon: "\ue655", Color: 56}, // 
@@ -148,10 +148,10 @@ var nameIconMap = map[string]IconProperties{
"mpv.conf": {Icon: "\uf36e", Color: 53}, // 
"node_modules": {Icon: "\ue718", Color: 197}, // 
"npmignore": {Icon: "\ue71e", Color: 197}, // 
"nuxt.config.cjs": {Icon: "\uf1106", Color: 42}, // 󱄆
"nuxt.config.js": {Icon: "\uf1106", Color: 42}, // 󱄆
"nuxt.config.mjs": {Icon: "\uf1106", Color: 42}, // 󱄆
"nuxt.config.ts": {Icon: "\uf1106", Color: 42}, // 󱄆
"nuxt.config.cjs": {Icon: "\U000f1106", Color: 42}, // 󱄆
"nuxt.config.js": {Icon: "\U000f1106", Color: 42}, // 󱄆
"nuxt.config.mjs": {Icon: "\U000f1106", Color: 42}, // 󱄆
"nuxt.config.ts": {Icon: "\U000f1106", Color: 42}, // 󱄆
"package.json": {Icon: "\ue71e", Color: 197}, // 
"package-lock.json": {Icon: "\ue71e", Color: 52}, // 
"PKGBUILD": {Icon: "\uf303", Color: 38}, // 
@@ -165,23 +165,23 @@ var nameIconMap = map[string]IconProperties{
"PrusaSlicer.ini": {Icon: "\uf351", Color: 202}, // 
"py.typed": {Icon: "\ue606", Color: 214}, // 
"QtProject.conf": {Icon: "\uf375", Color: 77}, // 
"R": {Icon: "\uf07d4", Color: 25}, // 󰟔
"robots.txt": {Icon: "\uf06a9", Color: 60}, // 󰚩
"R": {Icon: "\U000f07d4", Color: 25}, // 󰟔
"robots.txt": {Icon: "\U000f06a9", Color: 60}, // 󰚩
"rubydoc": {Icon: "\ue73b", Color: 160}, // 
"settings.gradle": {Icon: "\ue660", Color: 24}, // 
"svelte.config.js": {Icon: "\ue697", Color: 196}, // 
"sxhkdrc": {Icon: "\uf355", Color: 236}, // 
"sym-lib-table": {Icon: "\uf34c", Color: 231}, // 
"tailwind.config.js": {Icon: "\uf13ff", Color: 45}, // 󱏿
"tailwind.config.mjs": {Icon: "\uf13ff", Color: 45}, // 󱏿
"tailwind.config.ts": {Icon: "\uf13ff", Color: 45}, // 󱏿
"tailwind.config.js": {Icon: "\U000f13ff", Color: 45}, // 󱏿
"tailwind.config.mjs": {Icon: "\U000f13ff", Color: 45}, // 󱏿
"tailwind.config.ts": {Icon: "\U000f13ff", Color: 45}, // 󱏿
"tmux.conf": {Icon: "\uebc8", Color: 34}, // 
"tmux.conf.local": {Icon: "\uebc8", Color: 34}, // 
"tsconfig.json": {Icon: "\ue69d", Color: 74}, // 
"unlicense": {Icon: "\ue60a", Color: 185}, // 
"vagrantfile$": {Icon: "\uf2b8", Color: 27}, // 
"vlcrc": {Icon: "\uf057c", Color: 208}, // 󰕼
"webpack": {Icon: "\uf072b", Color: 74}, // 󰜫
"vlcrc": {Icon: "\U000f057c", Color: 208}, // 󰕼
"webpack": {Icon: "\U000f072b", Color: 74}, // 󰜫
"weston.ini": {Icon: "\uf367", Color: 214}, // 
"workspace": {Icon: "\ue63a", Color: 113}, // 
"xmobarrc.hs": {Icon: "\uf35e", Color: 203}, // 
@@ -207,7 +207,7 @@ var extIconMap = map[string]IconProperties{
".app": {Icon: "\ueae8", Color: 124}, // 
".apple": {Icon: "\uf179", Color: 15}, // 
".applescript": {Icon: "\uf179", Color: 66}, // 
".asc": {Icon: "\uf099d", Color: 242}, // 󰦝
".asc": {Icon: "\U000f099d", Color: 242}, // 󰦝
".ass": {Icon: "\U000f0a16", Color: 214}, // 󰨖
".astro": {Icon: "\ue6b3", Color: 197}, // 
".avif": {Icon: "\uf1c5", Color: 140}, // 
@@ -320,7 +320,7 @@ var extIconMap = map[string]IconProperties{
".exe": {Icon: "\uf17a", Color: 81}, // 
".ex": {Icon: "\ue62d", Color: 140}, // 
".exs": {Icon: "\ue62d", Color: 140}, // 
".f3d": {Icon: "\uf0eeb", Color: 101}, // 󰻫
".f3d": {Icon: "\U000f0eeb", Color: 101}, // 󰻫
".f90": {Icon: "\U000f121a", Color: 97}, // 󱈚
".fbx": {Icon: "\U000f01a7", Color: 102}, // 󰆧
".fcbak": {Icon: "\uf336", Color: 160}, // 
@@ -380,7 +380,7 @@ var extIconMap = map[string]IconProperties{
".hs": {Icon: "\ue777", Color: 140}, // 
".htm": {Icon: "\uf13b", Color: 196}, // 
".html": {Icon: "\uf13b", Color: 196}, // 
".huff": {Icon: "\uf0858", Color: 56}, // 󰡘
".huff": {Icon: "\U000f0858", Color: 56}, // 󰡘
".hurl": {Icon: "\uf0ec", Color: 198}, // 
".hx": {Icon: "\ue666", Color: 208}, // 
".hxx": {Icon: "\uf0fd", Color: 140}, // 
@@ -425,7 +425,7 @@ var extIconMap = map[string]IconProperties{
".jsx": {Icon: "\ue7ba", Color: 45}, // 
".jwmrc": {Icon: "\uf35b", Color: 32}, // 
".jxl": {Icon: "\uf1c5", Color: 241}, // 
".kbx": {Icon: "\uf0bc4", Color: 243}, // 󰯄
".kbx": {Icon: "\U000f0bc4", Color: 243}, // 󰯄
".kdb": {Icon: "\uf23e", Color: 71}, // 
".kdbx": {Icon: "\uf23e", Color: 71}, // 
".kdenlive": {Icon: "\uf33c", Color: 110}, // 
@@ -472,12 +472,12 @@ var extIconMap = map[string]IconProperties{
".magnet": {Icon: "\uf076", Color: 124}, // 
".makefile": {Icon: "\ue779", Color: 66}, // 
".markdown": {Icon: "\uf48a", Color: 74}, // 
".material": {Icon: "\uf0509", Color: 163}, // 󰔉
".md5": {Icon: "\uf0565", Color: 103}, // 󰕥
".material": {Icon: "\U000f0509", Color: 163}, // 󰔉
".md5": {Icon: "\U000f0565", Color: 103}, // 󰕥
".md": {Icon: "\uf48a", Color: 74}, // 
".mdx": {Icon: "\uf48a", Color: 74}, // 
".m": {Icon: "\ue61e", Color: 111}, // 
".mint": {Icon: "\uf032a", Color: 108}, // 󰌪
".mint": {Icon: "\U000f032a", Color: 108}, // 󰌪
".mjs": {Icon: "\ue74e", Color: 185}, // 
".mkd": {Icon: "\uf48a", Color: 74}, // 
".mk": {Icon: "\ue795", Color: 241}, // 
@@ -535,10 +535,10 @@ var extIconMap = map[string]IconProperties{
".properties": {Icon: "\ue60b", Color: 185}, // 
".ps1": {Icon: "\ue795", Color: 241}, // 
".psb": {Icon: "\ue7b8", Color: 74}, // 
".psd1": {Icon: "\uf0a0a", Color: 68}, // 󰨊
".psd1": {Icon: "\U000f0a0a", Color: 68}, // 󰨊
".psd": {Icon: "\ue7b8", Color: 241}, // 
".psm1": {Icon: "\uf0a0a", Color: 68}, // 󰨊
".pub": {Icon: "\uf0dd6", Color: 222}, // 󰷖
".psm1": {Icon: "\U000f0a0a", Color: 68}, // 󰨊
".pub": {Icon: "\U000f0dd6", Color: 222}, // 󰷖
".pxd": {Icon: "\ue606", Color: 39}, // 
".pxi": {Icon: "\ue606", Color: 39}, // 
".pxm": {Icon: "\uf1c5", Color: 241}, // 
@@ -570,7 +570,7 @@ var extIconMap = map[string]IconProperties{
".rlib": {Icon: "\ue7a8", Color: 216}, // 
".rmd": {Icon: "\uf48a", Color: 74}, // 
".rpm": {Icon: "\ue7bb", Color: 52}, // 
".rproj": {Icon: "\uf05c6", Color: 29}, // 󰗆
".rproj": {Icon: "\U000f05c6", Color: 29}, // 󰗆
".rs": {Icon: "\ue7a8", Color: 216}, // 
".rspec": {Icon: "\ue21e", Color: 160}, // 
".rspec_parallel": {Icon: "\ue21e", Color: 160}, // 
@@ -584,13 +584,13 @@ var extIconMap = map[string]IconProperties{
".scad": {Icon: "\uf34e", Color: 220}, // 
".scala": {Icon: "\ue737", Color: 74}, // 
".sc": {Icon: "\ue737", Color: 167}, // 
".scm": {Icon: "\uf0627", Color: 255}, // 󰘧
".scm": {Icon: "\U000f0627", Color: 255}, // 󰘧
".scss": {Icon: "\ue749", Color: 204}, // 
".sha1": {Icon: "\uf0565", Color: 103}, // 󰕥
".sha224": {Icon: "\uf0565", Color: 103}, // 󰕥
".sha256": {Icon: "\uf0565", Color: 103}, // 󰕥
".sha384": {Icon: "\uf0565", Color: 103}, // 󰕥
".sha512": {Icon: "\uf0565", Color: 103}, // 󰕥
".sha1": {Icon: "\U000f0565", Color: 103}, // 󰕥
".sha224": {Icon: "\U000f0565", Color: 103}, // 󰕥
".sha256": {Icon: "\U000f0565", Color: 103}, // 󰕥
".sha384": {Icon: "\U000f0565", Color: 103}, // 󰕥
".sha512": {Icon: "\U000f0565", Color: 103}, // 󰕥
".shell": {Icon: "\ue795", Color: 239}, // 
".sh": {Icon: "\ue795", Color: 239}, // 
".sig": {Icon: "\u03bb", Color: 166}, // λ
@@ -631,10 +631,10 @@ var extIconMap = map[string]IconProperties{
".swift": {Icon: "\ue755", Color: 208}, // 
".tar": {Icon: "\uf410", Color: 241}, // 
".taz": {Icon: "\uf410", Color: 241}, // 
".tbc": {Icon: "\uf06d3", Color: 25}, // 󰛓
".tbc": {Icon: "\U000f06d3", Color: 25}, // 󰛓
".tbz2": {Icon: "\uf410", Color: 241}, // 
".tbz": {Icon: "\uf410", Color: 241}, // 
".tcl": {Icon: "\uf06d3", Color: 25}, // 󰛓
".tcl": {Icon: "\U000f06d3", Color: 25}, // 󰛓
".templ": {Icon: "\ueac4", Color: 178}, // 
".terminal": {Icon: "\uf489", Color: 34}, // 
".test.js": {Icon: "\uf499", Color: 185}, // 
@@ -680,7 +680,7 @@ var extIconMap = map[string]IconProperties{
".wav": {Icon: "\uf001", Color: 241}, // 
".webmanifest": {Icon: "\ue60b", Color: 185}, // 
".webm": {Icon: "\uf03d", Color: 241}, // 
".webpack": {Icon: "\uf072b", Color: 74}, // 󰜫
".webpack": {Icon: "\U000f072b", Color: 74}, // 󰜫
".webp": {Icon: "\uf1c5", Color: 241}, // 
".windows": {Icon: "\uf17a", Color: 81}, // 
".wma": {Icon: "\uf001", Color: 39}, // 
@@ -690,7 +690,7 @@ var extIconMap = map[string]IconProperties{
".wrz": {Icon: "\U000f01a7", Color: 102}, // 󰆧
".wvc": {Icon: "\uf001", Color: 39}, // 
".wv": {Icon: "\uf001", Color: 39}, // 
".xaml": {Icon: "\uf0673", Color: 56}, // 󰙳
".xaml": {Icon: "\U000f0673", Color: 56}, // 󰙳
".xcf": {Icon: "\uf338", Color: 240}, // 
".xcplayground": {Icon: "\ue755", Color: 166}, // 
".xcstrings": {Icon: "\U000f05ca", Color: 31}, // 󰗊
@@ -0,0 +1,21 @@
package icons
import (
"testing"
)
func TestFileIcons(t *testing.T) {
t.Run("TestFileIcons", func(t *testing.T) {
for name, icon := range nameIconMap {
if len([]rune(icon.Icon)) != 1 {
t.Errorf("nameIconMap[\"%s\"] is not a single rune", name)
}
}
for ext, icon := range extIconMap {
if len([]rune(icon.Icon)) != 1 {
t.Errorf("extIconMap[\"%s\"] is not a single rune", ext)
}
}
})
}
+2 -4
View File
@@ -53,9 +53,7 @@ func (gui *Gui) newStringTaskWithoutScroll(view *gocui.View, str string) error {
return nil
}
// Using empty key so that on subsequent calls we won't reset the view's origin.
// Note this means that we will be scrolling back to the top if we're switching from a different key
if err := manager.NewTask(f, ""); err != nil {
if err := manager.NewTask(f, manager.GetTaskKey()); err != nil {
return err
}
@@ -71,7 +69,7 @@ func (gui *Gui) newStringTaskWithScroll(view *gocui.View, str string, originX in
return nil
}
if err := manager.NewTask(f, ""); err != nil {
if err := manager.NewTask(f, manager.GetTaskKey()); err != nil {
return err
}
+1 -1
View File
@@ -33,7 +33,7 @@ type IGuiCommon interface {
// we call this when we've changed something in the view model but not the actual model,
// e.g. expanding or collapsing a folder in a file view. Calling 'Refresh' in this
// case would be overkill, although refresh will internally call 'PostRefreshUpdate'
PostRefreshUpdate(Context) error
PostRefreshUpdate(Context)
// renders string to a view without resetting its origin
SetViewContent(view *gocui.View, content string)
+4 -4
View File
@@ -177,11 +177,11 @@ type IPatchExplorerContext interface {
GetState() *patch_exploring.State
SetState(*patch_exploring.State)
GetIncludedLineIndices() []int
RenderAndFocus(isFocused bool)
Render(isFocused bool)
RenderAndFocus()
Render()
Focus()
GetContentToRender(isFocused bool) string
NavigateTo(isFocused bool, selectedLineIdx int)
GetContentToRender() string
NavigateTo(selectedLineIdx int)
GetMutex() *deadlock.Mutex
IsPatchExplorerContext() // used for type switch
}
+1 -3
View File
@@ -126,7 +126,7 @@ func (gui *Gui) render() {
// postRefreshUpdate is to be called on a context after the state that it depends on has been refreshed
// if the context's view is set to another context we do nothing.
// if the context's view is the current view we trigger a focus; re-selecting the current item.
func (gui *Gui) postRefreshUpdate(c types.Context) error {
func (gui *Gui) postRefreshUpdate(c types.Context) {
t := time.Now()
defer func() {
gui.Log.Infof("postRefreshUpdate for %s took %s", c.GetKey(), time.Since(t))
@@ -137,6 +137,4 @@ func (gui *Gui) postRefreshUpdate(c types.Context) error {
if gui.currentViewName() == c.GetViewName() {
c.HandleFocus(types.OnFocusOpts{})
}
return nil
}
+3
View File
@@ -118,6 +118,7 @@ func (gui *Gui) createAllViews() error {
view.Wrap = true
view.IgnoreCarriageReturns = true
view.UnderlineHyperLinksOnlyOnHover = true
view.AutoRenderHyperLinks = true
}
gui.Views.Staging.Title = gui.c.Tr.UnstagedChanges
@@ -157,6 +158,7 @@ func (gui *Gui) createAllViews() error {
gui.Views.Confirmation.Visible = false
gui.Views.Confirmation.Editor = gocui.EditorFunc(gui.promptEditor)
gui.Views.Confirmation.AutoRenderHyperLinks = true
gui.Views.Suggestions.Visible = false
@@ -171,6 +173,7 @@ func (gui *Gui) createAllViews() error {
gui.Views.Extras.Title = gui.c.Tr.CommandLog
gui.Views.Extras.Autoscroll = true
gui.Views.Extras.Wrap = true
gui.Views.Extras.AutoRenderHyperLinks = true
gui.Views.Snake.Title = gui.c.Tr.SnakeTitle
gui.Views.Snake.FgColor = gocui.ColorGreen
+12 -6
View File
@@ -87,6 +87,7 @@ type TranslationSet struct {
AllFilesDiffCopiedToast string
FilterStagedFiles string
FilterUnstagedFiles string
FilterTrackedFiles string
ResetFilter string
MergeConflictsTitle string
Checkout string
@@ -107,6 +108,7 @@ type TranslationSet struct {
DeleteLocalBranch string
DeleteRemoteBranchOption string
DeleteRemoteBranchPrompt string
DeleteLocalAndRemoteBranchPrompt string
ForceDeleteBranchTitle string
ForceDeleteBranchMessage string
RebaseBranch string
@@ -472,8 +474,8 @@ type TranslationSet struct {
RemoveRemoteTooltip string
RemoveRemotePrompt string
DeleteRemoteBranch string
DeleteRemoteBranchMessage string
DeleteRemoteBranchTooltip string
DeleteLocalAndRemoteBranch string
SetAsUpstream string
SetAsUpstreamTooltip string
SetUpstream string
@@ -684,6 +686,8 @@ type TranslationSet struct {
CreatePullRequestOptions string
DefaultBranch string
SelectBranch string
SelectTargetRemote string
NoValidRemoteName string
CreatePullRequest string
SelectConfigFile string
NoConfigFileFoundErr string
@@ -849,7 +853,6 @@ type Actions struct {
CheckoutBranch string
ForceCheckoutBranch string
DeleteLocalBranch string
DeleteBranch string
Merge string
SquashMerge string
RebaseBranch string
@@ -1075,6 +1078,7 @@ func EnglishTranslationSet() *TranslationSet {
AllFilesDiffCopiedToast: "All files diff copied to clipboard",
FilterStagedFiles: "Show only staged files",
FilterUnstagedFiles: "Show only unstaged files",
FilterTrackedFiles: "Show only tracked files",
ResetFilter: "Reset filter",
NoChangedFiles: "No changed files",
SoftReset: "Soft reset",
@@ -1088,6 +1092,7 @@ func EnglishTranslationSet() *TranslationSet {
DeleteLocalBranch: "Delete local branch",
DeleteRemoteBranchOption: "Delete remote branch",
DeleteRemoteBranchPrompt: "Are you sure you want to delete the remote branch '{{.selectedBranchName}}' from '{{.upstream}}'?",
DeleteLocalAndRemoteBranchPrompt: "Are you sure you want to delete both '{{.localBranchName}}' from your machine, and '{{.remoteBranchName}}' from '{{.remoteName}}'?",
ForceDeleteBranchTitle: "Force delete branch",
ForceDeleteBranchMessage: "'{{.selectedBranchName}}' is not fully merged. Are you sure you want to delete it?",
RebaseBranch: "Rebase",
@@ -1461,10 +1466,10 @@ func EnglishTranslationSet() *TranslationSet {
EditRemoteUrl: `Enter updated remote url for {{.remoteName}}:`,
RemoveRemote: `Remove remote`,
RemoveRemoteTooltip: `Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected.`,
RemoveRemotePrompt: "Are you sure you want to remove remote",
RemoveRemotePrompt: "Are you sure you want to remove remote?",
DeleteRemoteBranch: "Delete remote branch",
DeleteRemoteBranchMessage: "Are you sure you want to delete remote branch",
DeleteRemoteBranchTooltip: "Delete the remote branch from the remote.",
DeleteLocalAndRemoteBranch: "Delete local and remote branch",
SetAsUpstream: "Set as upstream",
SetAsUpstreamTooltip: "Set the selected remote branch as the upstream of the checked-out branch.",
SetUpstream: "Set upstream of selected branch",
@@ -1480,7 +1485,7 @@ func EnglishTranslationSet() *TranslationSet {
ViewUpstreamRebaseOptionsTooltip: "View options for rebasing the checked-out branch onto {{upstream}}. Note: this will not rebase the selected branch onto the upstream, it will rebase the checked-out branch onto the upstream.",
UpstreamGenericName: "upstream of selected branch",
SetUpstreamTitle: "Set upstream branch",
SetUpstreamMessage: "Are you sure you want to set the upstream branch of '{{.checkedOut}}' to '{{.selected}}'",
SetUpstreamMessage: "Are you sure you want to set the upstream branch of '{{.checkedOut}}' to '{{.selected}}'?",
EditRemoteTooltip: "Edit the selected remote's name or URL.",
TagCommit: "Tag commit",
TagCommitTooltip: "Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description.",
@@ -1673,6 +1678,8 @@ func EnglishTranslationSet() *TranslationSet {
CreatePullRequestOptions: "View create pull request options",
DefaultBranch: "Default branch",
SelectBranch: "Select branch",
SelectTargetRemote: "Select target remote",
NoValidRemoteName: "A remote named '%s' does not exist",
SelectConfigFile: "Select config file",
NoConfigFileFoundErr: "No config file found",
LoadingFileSuggestions: "Loading file suggestions",
@@ -1797,7 +1804,6 @@ func EnglishTranslationSet() *TranslationSet {
CheckoutBranch: "Checkout branch",
ForceCheckoutBranch: "Force checkout branch",
DeleteLocalBranch: "Delete local branch",
DeleteBranch: "Delete branch",
Merge: "Merge",
SquashMerge: "Squash merge",
RebaseBranch: "Rebase branch",
+500 -71
View File
@@ -5,79 +5,176 @@
"BranchesTitle": "分支",
"CommitsTitle": "提交",
"StashTitle": "贮藏",
"UnstagedChanges": "未暂存更改",
"StagedChanges": "已暂存更改",
"SnakeTitle": "贪吃蛇",
"EasterEgg": "彩蛋",
"UnstagedChanges": "未暂存变更",
"StagedChanges": "已暂存变更",
"MainTitle": "主要",
"StagingTitle": "正在暂存",
"MergingTitle": "正在合并",
"SquashMergeUncommittedTitle": "压缩合并并保持未提交状态",
"SquashMergeCommittedTitle": "压缩合并,然后提交",
"SquashMergeUncommitted": "将分支 ‘{{.selectedBranch}}’ 压缩合并到工作树中",
"SquashMergeCommitted": "将分支 '{{.selectedBranch}}' 压缩合并为单个提交,到 '{{.checkedOutBranch}}' 分支中。",
"RegularMergeTooltip": "将分支 '{{.selectedBranch}}' 合并到 '{{.checkedOutBranch}}'",
"NormalTitle": "正常",
"LogTitle": "日志",
"CommitSummary": "提交信息",
"CredentialsUsername": "用户名",
"CredentialsPassword": "密码",
"CredentialsPassphrase": "输入 SSH 密钥的密码",
"CredentialsPIN": "为 SSH key 输入PIN",
"PassUnameWrong": "密码 和/或 用户名错误",
"Commit": "提交更改",
"Commit": "提交变更",
"CommitTooltip": "提交暂存文件",
"AmendLastCommit": "修补最后一次提交",
"AmendLastCommitTitle": "修补最后一次提交",
"SureToAmend": "您确定要修补上一次提交吗?之后您可以从提交面板更改提交消息。",
"NoCommitToAmend": "没有需要提交的修补。",
"CommitChangesWithEditor": "提交更改(使用编辑器编辑提交信息)",
"SureToAmend": "您确定要修补上一次提交吗?之后您可以从提交面板更改提交消息",
"NoCommitToAmend": "没有需要提交的修补",
"CommitChangesWithEditor": "提交变更(使用编辑器编辑提交信息)",
"FindBaseCommitForFixup": "找到用于修复的基准提交",
"FindBaseCommitForFixupTooltip": "找到您当前变更所基于的提交,以便于修正/改进该提交。这样做可以省去您逐一查看分支提交来确定应该修正/改进哪个提交的麻烦。请参阅文档: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md>",
"NoBaseCommitsFound": "没有找到接触提交",
"MultipleBaseCommitsFoundStaged": "找到了多个基础提交(尝试一次暂存更少的变更)",
"MultipleBaseCommitsFoundUnstaged": "找到了多个基础提交(尝试暂存部分变更)",
"BaseCommitIsAlreadyOnMainBranch": "此变更内容所在的提交已经存在于主分支上了",
"BaseCommitIsNotInCurrentView": "基础提交不在当前视图中",
"HunksWithOnlyAddedLinesWarning": "差异中仅包含添加行,小心检查这些是否属于已找到的主提交。\n\n是否继续?",
"StatusTitle": "状态",
"GlobalTitle": "全局键绑定",
"Menu": "菜单",
"Execute": "执行",
"Stage": "切换暂存状态",
"StageTooltip": "为选定的文件切换暂存状态",
"ToggleStagedAll": "切换所有文件的暂存状态",
"ToggleStagedAllTooltip": "切换工作区中所有文件的已暂存/未暂存状态",
"ToggleTreeView": "切换文件树视图",
"OpenMergeTool": "打开外部合并工具 (git mergetool)",
"ToggleTreeViewTooltip": "在平铺部署与树布局之间切换文件视图。平铺布局在一个列表中展示所有文件路径,树布局则根据目录分组展示。",
"OpenDiffTool": "使用外部差异比较工具(git difftool)",
"OpenMergeTool": "打开外部合并工具(git mergetool)",
"OpenMergeToolTooltip": "执行 `git mergetool`.",
"Refresh": "刷新",
"RefreshTooltip": "刷新git状态(即在后台上运行`git status`,`git branch`等命令以更新面板内容) 不会运行`git fetch`",
"Push": "推送",
"Pull": "拉取",
"PushTooltip": "推送当前分支到它的上游。如果上游为配置,你可以在弹窗中配置上游分支。",
"PullTooltip": "从当前分支的远程分支获取改动。如果上游为配置,你可以在弹窗中配置上游分支。",
"Scroll": "滚动",
"FileFilter": "通过状态过滤文件",
"CopyToClipboardMenu": "复制到剪贴板",
"CopyFileName": "文件名",
"CopyFilePath": "路径",
"CopyFileDiffTooltip": "如果存在已暂存更改,该命令将仅作用于它们。否则将作用于所有未暂存更改。",
"CopySelectedDiff": "比较选中的文件",
"CopyAllFilesDiff": "对比全部文件",
"NoContentToCopyError": "无可复制内容",
"FileNameCopiedToast": "文件名已复制至剪贴板",
"FilePathCopiedToast": "文件路径已复制至剪贴板",
"FileDiffCopiedToast": "文件差异已复制至剪贴板",
"AllFilesDiffCopiedToast": "全部文件差异已复制至剪贴板",
"FilterStagedFiles": "仅显示已暂存文件",
"FilterUnstagedFiles": "仅显示未暂存文件",
"ResetFilter": "重置过滤",
"MergeConflictsTitle": "合并冲突",
"Checkout": "检出",
"NoChangedFiles": "没有更改过文件",
"CheckoutTooltip": "检出选中的项目",
"CantCheckoutBranchWhilePulling": "当前分支在拉取远端时,无法检出到其他分支。",
"TagCheckoutTooltip": "检出选择的标签作为分离的HEAD",
"RemoteBranchCheckoutTooltip": "基于当前选中的远程分支检出一个新的本地分支,或者将远程分支作分离的HEAD。",
"CantPullOrPushSameBranchTwice": "在推送或拉取分支的过程中,你不能再次推送或拉取同一个分支",
"NoChangedFiles": "没有变更的文件",
"SoftReset": "软重置",
"AlreadyCheckedOutBranch": "您已经检出至此分支",
"SureForceCheckout": "您确定要强制检出吗?您将丢失所有本地更改",
"SureForceCheckout": "您确定要强制检出吗?您将丢失所有本地变更",
"ForceCheckoutBranch": "强制检出分支",
"BranchName": "分支名称",
"NewBranchNameBranchOff": "新分支名称(基于 {{.branchName}})",
"NewBranchNameBranchOff": "新分支名称(基于 {{.branchName}})",
"CantDeleteCheckOutBranch": "您不能删除已检出的分支!",
"DeleteBranchTitle": "删除分支'{{.selectedBranchName}}'?",
"DeleteLocalBranch": "删除本地分支",
"DeleteRemoteBranchOption": "删除远程分支",
"DeleteRemoteBranchPrompt": "你确定要从'{{.upstream}}'中删除远程分支'{{.selectedBranchName}}'?",
"ForceDeleteBranchTitle": "强制删除分支",
"ForceDeleteBranchMessage": "{{.selectedBranchName}} 还没有被完全合并。您确定要删除它吗?",
"RebaseBranch": "将已检出的分支变基到该分支",
"RebaseBranchTooltip": "将检出的分支变基到所选的分支上。",
"CantRebaseOntoSelf": "您不能将分支变基到其自身",
"CantMergeBranchIntoItself": "您不能将分支合并到其自身",
"ForceCheckout": "强制检出",
"ForceCheckoutTooltip": "强制检出所选分支。这将在检出所选分支之前放弃工作目录中的所有本地更改。",
"CheckoutByName": "按名称检出",
"CheckoutByNameTooltip": "按名称检出。在输入框中,您可以输入'-' 来切换到最后一个分支。",
"RemoteBranchCheckoutTitle": "检出 {{.branchName}}",
"RemoteBranchCheckoutPrompt": "您希望已什么方式检出到该分支?",
"CheckoutTypeNewBranch": "新建本地分支",
"CheckoutTypeNewBranchTooltip": "检出远程分支到本地,并跟踪它。",
"CheckoutTypeDetachedHead": "分离HEAD",
"CheckoutTypeDetachedHeadTooltip": "将远程分支检出作为分离HEAD,如果您只想用来测试而不是正式使用,这会很有用。您之后仍可以根据它创建本地分支。",
"NewBranch": "新分支",
"NewBranchFromStashTooltip": "从选定的贮藏项创建一个新分支。这是通过 git 检查创建贮藏项的提交,从该提交创建一个新分支,然后将贮藏项作为附加提交应用到新分支来实现的。",
"NoBranchesThisRepo": "此仓库中没有分支",
"CommitWithoutMessageErr": "您必须编写提交消息才能进行提交",
"Close": "关闭",
"CloseCancel": "关闭",
"Confirm": "确认",
"Quit": "退出",
"Fixup": "修正(fixup)",
"SquashTooltip": "将已选提交压缩到该提交之下。这些选定的提交的消息会附加到该提交的消息之下。",
"CannotSquashOrFixupFirstCommit": "下面没有可以压缩的提交",
"Fixup": "修正(fixup)",
"FixupTooltip": "将选定的提交合并到其下面的提交中。与压缩类似,但所选提交的消息将被丢弃。",
"SureFixupThisCommit": "您确定要“修正”此提交吗?它将合并到下面的提交中",
"SureSquashThisCommit": "您确定要将这个提交压缩到下面的提交中吗?",
"Squash": "压缩",
"PickCommitTooltip": "选择提交(变基过程中)",
"Squash": "压缩(Squash)",
"SquashMerge": "压缩合并",
"PickCommitTooltip": "选择提交(变基过程中)",
"Pick": "拣选(Pick)",
"CantPickDisabledReason": "在变基期间,无法选择提交",
"Edit": "编辑",
"RevertCommit": "还原提交",
"Revert": "撤销(Revert)",
"RevertCommitTooltip": "为所选提交创建还原提交,这会反向应用所选提交的更改。",
"Reword": "改写提交",
"CommitRewordTooltip": "重写所选提交的消息。",
"DropCommit": "删除提交",
"DropCommitTooltip": "删除选中的提交。这将通过变基从分支中删除该提交,如果该提交修改的内容依赖于后续的提交,则需要解决合并冲突。",
"MoveDownCommit": "下移提交",
"MoveUpCommit": "上移提交",
"CannotMoveAnyFurther": "无法进一步移动",
"EditCommit": "编辑(开始交互式变基)",
"EditCommitTooltip": "编辑提交",
"AmendCommitTooltip": "用已暂存的更改来修补提交",
"AmendCommitTooltip": "用已暂存的变更来修补提交",
"Amend": "修补(Amend)",
"ResetAuthor": "重置作者",
"ResetAuthorTooltip": "将提交作者重置为当前配置的用户。这也将更新作者的时间戳",
"SetAuthor": "设置作者",
"SetAuthorTooltip": "基于提示设置作者",
"AddCoAuthor": "添加共同作者",
"AmendCommitAttribute": "修补提交属性",
"AmendCommitAttributeTooltip": "设置或重置提交的作者,或添加其他作者。",
"SetAuthorPromptTitle": "设置作者(格式为 'Name <Email>')",
"AddCoAuthorPromptTitle": "添加共同作者(格式为 'Name <Email>')",
"AddCoAuthorTooltip": "添加共同作者 使用GitHub/GitLab元数据共同作者(Co-authored-by)",
"SureResetCommitAuthor": "将更新提交的作者名称,同时也将更新提交时间,是否继续?",
"RewordCommitEditor": "使用编辑器重命名提交",
"NoCommitsThisBranch": "该分支没有提交",
"UpdateRefHere": "更新分支到 '{{.ref}}'",
"ExecCommandHere": "在这里执行以下命令:",
"Error": "错误",
"Undo": "撤销",
"UndoReflog": "(通过 reflog)撤销「实验功能」",
"RedoReflog": "(通过 reflog)重做「实验功能」",
"UndoReflog": "(通过 reflog)撤销「实验功能」",
"RedoReflog": "(通过 reflog)重做「实验功能」",
"UndoTooltip": "Reflog将用于确定运行哪个git命令来撤消最后一个git命令。这并不包括对工作树的更改,只考虑提交。",
"RedoTooltip": "Reflog将用于确定运行哪个git命令来重做上一个git命令。这并不包括对工作树的更改,只考虑提交。",
"UndoMergeResolveTooltip": "撤消上次合并冲突解决",
"DiscardAllTooltip": "丢弃'{{.path}}'中已暂存和未暂存的变更",
"DiscardUnstagedTooltip": "丢弃'{{.path}}'中未暂存的变更",
"DiscardUnstagedDisabled": "选中的项目既没有已暂存的变更也没有未暂存的变更",
"Pop": "应用并删除",
"StashPopTooltip": "将存储项应用到工作目录并删除存储项。",
"Drop": "删除",
"StashDropTooltip": "从贮藏列表中删除该贮藏项",
"Apply": "应用",
"StashApplyTooltip": "将贮藏项应用到您的工作目录。",
"NoStashEntries": "没有贮藏条目",
"StashDrop": "删除贮藏",
"SureDropStashEntry": "您确定要删除此贮藏条目吗?",
@@ -86,27 +183,47 @@
"StashApply": "应用贮藏",
"SureApplyStashEntry": "您确定要应用此贮藏条目?",
"NoTrackedStagedFilesStash": "没有可以贮藏的已跟踪/暂存文件",
"StashChanges": "贮藏更改",
"NoFilesToStash": "没有需要贮藏的文件",
"StashChanges": "贮藏变更",
"RenameStash": "重命名贮藏",
"RenameStashPrompt": "重命名贮藏: {{.stashName}}",
"OpenConfig": "打开配置文件",
"EditConfig": "编辑配置文件",
"ForcePush": "强制推送",
"ForcePushPrompt": "您的分支已与远程分支不同。按‘esc’取消,或‘enter’强制推送.",
"ForcePushDisabled": "您的分支已与远程分支不同, 并且您已经禁用了强行推送",
"UpdatesRejected": "更新被拒绝。在下次推送前,请先抓取并检查远程分支。",
"UpdatesRejectedAndForcePushDisabled": "更新被拒绝,您已禁用强制推送",
"CheckForUpdate": "检查更新",
"CheckingForUpdates": "正在检查更新…",
"UpdateAvailableTitle": "有可用更新!",
"UpdateAvailable": "下载并安装版本 {{.newVersion}}?",
"UpdateInProgressWaitingStatus": "更新中",
"UpdateCompletedTitle": "更新完成!",
"UpdateCompleted": "已成功安装更新,重新启动 lazygit 使其生效。",
"FailedToRetrieveLatestVersionErr": "检索版本信息失败",
"OnLatestVersionErr": "已是最新版本",
"MajorVersionErr": "新版本 ({{.newVersion}}) 与当前版本 ({{.currentVersion}}) 相比,具有非向后兼容的更改",
"MajorVersionErr": "新版本({{.newVersion}})与当前版本({{.currentVersion}})相比,具有非向后兼容的更改",
"CouldNotFindBinaryErr": "在 {{.url}} 处找不到任何二进制文件",
"UpdateFailedErr": "更新失败: {{.errMessage}}",
"ConfirmQuitDuringUpdateTitle": "当前正在更新中...",
"ConfirmQuitDuringUpdate": "当前正在更新中,你确定要退出吗?",
"MergeToolTitle": "合并工具",
"MergeToolPrompt": "确定要打开 `git mergetool` 吗?",
"IntroPopupMessage": "\n感谢使用 lazygit!你真的太棒了。下面几点你可能会感兴趣:\n\n 1) 观看此视频,快速了解 lazygit 的功能:\n https://youtu.be/CPLdltN7wgE\n\n 2) 记得看看最新发行说明:\n https://github.com/jesseduffield/lazygit/releases\n\n 3) 使用 git 说明你是一位程序员!你可以和我们一起让 lazygit 变得更好。\n 考虑为本项目做些贡献吧:\n https://github.com/jesseduffield/lazygit\n 你也可以直接赞助,并告诉我哪里需要改进,点右下角的捐赠按钮就好了。\n 哪怕只是给仓库点个星星也很棒!\n",
"IntroPopupMessage": "\n感谢使用 lazygit!你真的太棒了。下面几点你可能会感兴趣:\n\n 1) 观看此视频,快速了解 lazygit 的功能:\n https://youtu.be/CPLdltN7wgE\n\n 2) 记得看看最新发行说明:\n https://github.com/jesseduffield/lazygit/releases\n\n 3) 使用 git 说明你是一位程序员!你可以和我们一起让 lazygit 变得更好。\n 考虑为本项目做些贡献吧:\n https://github.com/jesseduffield/lazygit\n 你也可以直接赞助,并告诉我哪里需要改进,点右下角的捐赠按钮就好了。\n 哪怕只是给仓库点个星星也很棒!\n",
"DeprecatedEditConfigWarning": "englishDeprecatedEditConfigWarning",
"GitconfigParseErr": "由于存在未加引号的'\\'字符,因此 Gogit 无法解析您的 gitconfig 文件。删除它们应该可以解决问题。",
"EditFile": "编辑文件",
"EditFileTooltip": "使用外部编辑器打开文件",
"OpenFile": "打开文件",
"OpenFileTooltip": "使用默认程序打开该文件",
"OpenInEditor": "在编辑器中编写",
"IgnoreFile": "添加到 .gitignore",
"ExcludeFile": "添加到 .git/info/exclude",
"RefreshFiles": "刷新文件",
"Merge": "合并到当前检出的分支",
"RegularMerge": "常规合并",
"MergeBranchTooltip": "Merge selected branch into currently checked out branch.",
"ConfirmQuit": "您确定要退出吗?",
"SwitchRepo": "切换到最近的仓库",
"AllBranchesLogGraph": "显示所有分支的日志",
@@ -114,31 +231,48 @@
"CopyPullRequestURL": "将抓取请求 URL 复制到剪贴板",
"NoBranchOnRemote": "该分支在远程上不存在. 您需要先将其推送到远程.",
"Fetch": "抓取",
"FetchTooltip": "从远程获取变更",
"NoAutomaticGitFetchTitle": "无法自动进行 \"git fetch\"",
"NoAutomaticGitFetchBody": "Lazygit 不能在私人仓库中使用 \"git fetch\"; 请在文件面板中使用 'f' 手动运行 \"git fetch\"",
"FileEnter": "暂存单个 块/行 用于文件, 或 折叠/展开 目录",
"FileEnterTooltip": "如果选中的是一个文件,则会进入到暂存视图,以便可以暂存单个代码块/行。如果选中的是一个目录,则会折叠/展开这个目录",
"FileStagingRequirements": "只能暂存跟踪文件的单独行",
"StageSelectionTooltip": "切换行暂存状态",
"DiscardSelection": "取消变更 (git reset)",
"ToggleSelectHunk": "切换选择区块",
"DiscardSelection": "取消变更(git reset)",
"DiscardSelectionTooltip": "当选择未暂存的变更时,使用git reset丢弃该变更。当选择已暂存的变更时,取消暂存该变更",
"ToggleSelectHunk": "切换选择代码块",
"ToggleSelectHunkTooltip": "切换代码块选择模式",
"ToggleSelectionForPatch": "添加/移除 行到补丁",
"EditHunk": "编辑代码块",
"EditHunkTooltip": "在外部编辑器中编辑选中的代码块",
"ToggleStagingView": "切换到其他面板",
"ToggleStagingViewTooltip": "切换到其他视图(已暂存/未暂存的变更)",
"ReturnToFilesPanel": "返回文件面板",
"FastForward": "从上游快进此分支",
"FastForwardTooltip": "将当前分支直接移动到远程追踪分支的最新提交",
"FastForwarding": "抓取并快进",
"FoundConflictsTitle": "自动合并失败",
"ViewConflictsMenuItem": "查看冲突",
"AbortMenuItem": "关于 %s",
"PickHunk": "选中区块",
"PickAllHunks": "选中所有区块",
"ViewMergeRebaseOptions": "查看 合并/变基 选项",
"ViewMergeRebaseOptionsTooltip": "查看当前合并或变基的中止、继续、跳过选项",
"ViewMergeOptions": "查看 合并(merge) 选项",
"ViewRebaseOptions": "View rebase opGlobalTitletions",
"NotMergingOrRebasing": "您目前既不进行变基也不进行合并",
"AlreadyRebasing": "在变基时无法执行此操作",
"RecentRepos": "最近的仓库",
"MergeOptionsTitle": "合并选项",
"RebaseOptionsTitle": "变基选项",
"CommitSummaryTitle": "提交讯息",
"CommitSummaryTitle": "提交信息",
"CommitDescriptionTitle": "提交信息说明",
"CommitDescriptionSubTitle": "按 {{.togglePanelKeyBinding}} 键切换焦点, {{.commitMenuKeybinding}} 打开菜单",
"LocalBranchesTitle": "分支页面",
"SearchTitle": "搜索",
"TagsTitle": "标签页面",
"MenuTitle": "菜单",
"CommitMenuTitle": "提交 菜单",
"RemotesTitle": "远程页面",
"RemoteBranchesTitle": "远程分支",
"PatchBuildingTitle": "构建补丁中",
@@ -146,17 +280,33 @@
"SecondaryTitle": "次要",
"ReflogCommitsTitle": "Reflog 页面",
"ConflictsResolved": "已解决所有冲突。是否继续?",
"Continue": "继续",
"RebasingTitle": "变基 '{{.checkedOutBranch}}'",
"RebasingFromBaseCommitTitle": "从标记的几点变基'{{.checkedOutBranch}}'",
"SimpleRebase": "简单变基到 '{{.ref}}'",
"InteractiveRebase": "交互式变基到 '{{.ref}}'",
"RebaseOntoBaseBranch": "变基到主分支 ({{.baseBranch}})",
"InteractiveRebaseTooltip": "由于交互式变基被中断,所以你可以在继续变基之前更新TODO提交。",
"RebaseOntoBaseBranchTooltip": "将已检出的分支变基到主分支上 (例如:最近的主分支).",
"MustSelectTodoCommits": "在变基过程中, 该操作仅在选中TODO提交时有效。",
"FwdNoUpstream": "此分支没有上游,无法快进",
"FwdNoLocalUpstream": "此分支的远程未在本地注册,无法快进",
"FwdCommitsToPush": "此分支带有尚未推送的提交,无法快进",
"PullRequestNoUpstream": "没有设置上游的分支无法执行拉取请求",
"ErrorOccurred": "发生错误!请在以下位置创建 issue",
"NoRoom": "空间不足",
"YouAreHere": "您在这里",
"YouDied": "你死了!",
"RewordNotSupported": "当前不支持交互式重新基准化时的重新措词提交",
"CherryPickCopy": "复制提交(拣选)",
"PasteCommits": "粘贴提交(拣选)",
"ChangingThisActionIsNotAllowed": "不允许更改这类变基待办项目",
"CherryPickCopy": "复制提交(拣选)",
"CherryPickCopyTooltip": "标记提交为已复制。然后,在本地提交视图中,你可以按 `{{.paste}}` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `{{.escape}}` 来取消选择。",
"CherryPickCopyRangeTooltip": "将上次已复制的提交到当前选中的提交都标记为已复制",
"PasteCommits": "粘贴提交(拣选)",
"SureCherryPick": "您确定要将选中的提交进行拣选到这个分支吗?",
"CherryPick": "拣选 (Cherry-Pick)",
"CherryPick": "拣选(Cherry-Pick)",
"CannotCherryPickNonCommit": "无法拣选TODO类型的提交",
"CannotCherryPickMergeCommit": "不支持拣选合并类型的提交",
"Donate": "捐助",
"AskQuestion": "提问咨询",
"PrevLine": "选择上一行",
@@ -171,71 +321,128 @@
"ScrollUp": "向上滚动",
"ScrollUpMainWindow": "向上滚动主面板",
"ScrollDownMainWindow": "向下滚动主面板",
"AmendCommitTitle": "修改提交",
"AmendCommitPrompt": "您确定要使用暂存文件来修改此提交吗?",
"AmendCommitTitle": "修补(amend)提交",
"AmendCommitPrompt": "您确定要使用暂存文件来修补此提交吗?",
"DropCommitTitle": "删除提交",
"DropCommitPrompt": "您确定要删除此提交吗?",
"DropUpdateRefPrompt": "您确定要删除选定的 update-ref 待办事项吗?除非中止变基,否则这是不可逆转的。",
"PullingStatus": "正在拉取",
"PushingStatus": "正在推送",
"FetchingStatus": "正在抓取",
"SquashingStatus": "正在压缩",
"FixingStatus": "正在修正",
"DeletingStatus": "正在删除",
"DroppingStatus": "删除中...",
"MovingStatus": "正在移动",
"RebasingStatus": "正在变基",
"AmendingStatus": "正在修改",
"MergingStatus": "合并中...",
"LowercaseRebasingStatus": "变基中...",
"LowercaseMergingStatus": "合并中...",
"AmendingStatus": "正在修补",
"CherryPickingStatus": "正在拣选",
"UndoingStatus": "正在撤销",
"RedoingStatus": "正在重做",
"CheckingOutStatus": "长子检出",
"CheckingOutStatus": "正在检出",
"CommittingStatus": "正在提交",
"RevertingStatus": "还原中...",
"CreatingFixupCommitStatus": "正在创建一个修复提交",
"CommitFiles": "提交文件",
"SubCommitsDynamicTitle": "提交 (%s)",
"CommitFilesDynamicTitle": "比较文件差异 (%s)",
"RemoteBranchesDynamicTitle": "远程分支 (%s)",
"ViewItemFiles": "查看提交的文件",
"ViewItemFilesTooltip": "查看所选项的修改文件",
"CommitFilesTitle": "提交文件",
"CheckoutCommitFileTooltip": "检出文件",
"DiscardOldFileChangeTooltip": "放弃对此文件的提交更改",
"DiscardFileChangesTitle": "放弃文件更改",
"DiscardFileChangesPrompt": "您确定要舍弃此提交对该文件的更改吗?如果此文件是在此提交中创建的,它将被删除",
"CanOnlyDiscardFromLocalCommits": "只能从本地提交中丢弃更改",
"Remove": "删除",
"DiscardOldFileChangeTooltip": "放弃对此文件的提交变更",
"DiscardFileChangesTitle": "放弃文件变更",
"DiscardFileChangesPrompt": "您确定要舍弃此提交对该文件的变更吗?如果此文件是在此提交中创建的,它将被删除",
"DisabledForGPG": "该功能不适用于使用 GPG 的用户",
"CreateRepo": "当前目录不在 git 仓库中。是否在此目录创建一个新的 git 仓库?(y/n): ",
"BareRepo": "您已经尝试在空仓库中打开Lazygit,但是Lazygit还不支持空仓库。打开最近的仓库吗?(y / n) ",
"InitialBranch": "分支名称? (git的默认值为空): ",
"NoRecentRepositories": "必须在git存储库中打开lazygit。没有有效的最近存储库。即将退出...",
"IncorrectNotARepository": "'notARepository'的值不正确。它应该是“prompt”,“create”,“skip”或“quit”中的一个。",
"AutoStashTitle": "自动存储?",
"AutoStashPrompt": "您必须隐藏并弹出更改以使更改生效。自动执行?(enter/esc)",
"StashPrefix": "自动隐藏更改 ",
"Discard": "查看'放弃更改'选项",
"AutoStashPrompt": "您必须隐藏并弹出变更以使变更生效。自动执行?(enter/esc)",
"StashPrefix": "自动隐藏变更 ",
"Discard": "查看'放弃变更'选项",
"DiscardChangesTitle": "放弃变更",
"DiscardFileChangesTooltip": "查看选中文件的放弃变更选项",
"Cancel": "取消",
"DiscardAllChanges": "放弃所有更改",
"DiscardAllChanges": "放弃所有变更",
"DiscardUnstagedChanges": "放弃未暂存的变更",
"DiscardAllChangesToAllFiles": "清空工作区",
"DiscardAnyUnstagedChanges": "丢弃未暂存的变更",
"DiscardUntrackedFiles": "丢弃未跟踪的文件",
"DiscardStagedChanges": "丢弃已暂存的变更",
"HardReset": "硬重置",
"BranchDeleteTooltip": "查看本地/远程分支的删除选项",
"TagDeleteTooltip": "查看本地/远程标签的删除选项",
"Delete": "删除",
"Reset": "重置",
"ResetTooltip": "查看重置选项 (soft/mixed/hard) 用于重置到选择项",
"ViewResetOptions": "查看重置选项",
"FileResetOptionsTooltip": "查看工作树的重置选项(例如:清除工作树)。",
"CreateFixupCommit": "为此提交创建修正",
"CreateFixupCommitTooltip": "创建修正提交",
"SquashAboveCommitsTooltip": "压缩在所选提交之上的所有“fixup!”提交(自动压缩)",
"CreateAmendCommit": "创建 'amend!' 提交",
"FixupMenu_Fixup": "修复提交",
"FixupMenu_FixupTooltip": "允许你修复另一个提交并保持原本的提交信息",
"FixupMenu_AmendWithChanges": "基于当前变动内容修改提交",
"FixupMenu_AmendWithChangesTooltip": "允许你修复另一个提交并修改提交信息",
"FixupMenu_AmendWithoutChanges": "修改提交(不含变动内容,类似reword)",
"FixupMenu_AmendWithoutChangesTooltip": "允许您修改另一个提交的提交消息而不更改其内容",
"SquashAboveCommitsTooltip": "压缩在所选提交之上的所有“fixup!”提交(自动压缩)",
"SquashCommitsAboveSelectedTooltip": "压缩当前选中提交下的所有修复提交(自动压缩)",
"SquashCommitsInCurrentBranchTooltip": "压缩当前分支中的所有修复提交(自动压缩)",
"SquashAboveCommits": "应用该修复提交",
"SquashCommitsInCurrentBranch": "在当前分支",
"SquashCommitsAboveSelectedCommit": "在选定提交之上",
"CannotSquashCommitsInCurrentBranch": "在当前分支中无法压缩提交:因为分离HEAD提交是一个合并提交或者已经存在于主分支中",
"ExecuteCustomCommand": "执行自定义命令",
"ExecuteCustomCommandTooltip": "弹出一个提示符,您可以在其中输入要执行的 shell 命令。不要与预配置的自定义命令混淆。",
"CustomCommand": "自定义命令:",
"CommitChangesWithoutHook": "提交更改而无需预先提交钩子",
"CommitChangesWithoutHook": "提交变更而无需预先提交钩子",
"SkipHookPrefixNotConfigured": "您尚未配置用于跳过钩子的提交消息前缀。请在您的配置中设置 `git.skipHookPrefix ='WIP'`",
"ResetTo": "重置为",
"ResetSoftTooltip": "将 HEAD 重置为所选提交,并将当前提交和所选提交之间的更改保留为已暂存更改。",
"ResetMixedTooltip": "将 HEAD 重置为所选提交,并将当前提交和所选提交之间的更改保留为未暂存的更改。",
"ResetHardTooltip": "将 HEAD 重置为所选提交,并丢弃当前提交和所选提交之间的所有更改,以及工作树中的所有当前修改。",
"PressEnterToReturn": "按下 Enter 键返回 lazygit",
"ViewStashOptions": "查看贮藏选项",
"StashAllChanges": "将所有更改加入贮藏",
"StashAllChangesKeepIndex": "将已暂存的更改加入贮藏",
"ViewStashOptionsTooltip": "查看贮藏选项(例如:贮藏所有、贮藏已暂存变更、贮藏未暂存变更)",
"Stash": "贮藏",
"StashTooltip": "贮藏所有变更.若要使用其他贮藏变体,请使用查看贮藏选项快捷键",
"StashAllChanges": "将所有变更加入贮藏",
"StashStagedChanges": "贮藏已暂存变更",
"StashAllChangesKeepIndex": "将已暂存的变更加入贮藏",
"StashUnstagedChanges": "贮藏未暂存变更",
"StashIncludeUntrackedChanges": "贮藏所有变更,包括未跟踪的文件",
"StashOptions": "贮藏选项",
"NotARepository": "错误:必须在 git 仓库中运行",
"WorkingDirectoryDoesNotExist": "错误:当前工作目录不存在",
"Jump": "跳到面板",
"ScrollLeftRight": "左右滚动",
"ScrollLeft": "向左滚动",
"ScrollRight": "向右滚动",
"DiscardPatch": "丢弃补丁",
"DiscardPatchConfirm": "您一次只能通过一个提交或贮藏条目构建补丁。需要放弃当前补丁吗?",
"DiscardPatchSameCommitConfirm": "您当前已将更改添加到此提交的补丁中。放弃当前补丁?",
"CantPatchWhileRebasingError": "处于合并或变基状态时,您无法构建修补程序或运行修补程序命令",
"ToggleAddToPatch": "补丁中包含的切换文件",
"ToggleAddToPatchTooltip": "切换文件是否包含在自定义补丁中。请参阅 {{.doc}}。",
"ToggleAllInPatch": "操作所有文件",
"ToggleAllInPatchTooltip": "添加或删除所有提交中的文件到自定义的补丁中。请参阅 {{.doc}}。",
"UpdatingPatch": "正在更新补丁",
"ViewPatchOptions": "查看自定义补丁选项",
"PatchOptionsTitle": "补丁选项",
"NoPatchError": "尚未创建补丁。你可以在提交中的文件上按下“空格”或使用“回车”添加其中的特定行以开始构建补丁",
"EnterCommitFile": "输入文件以将所选行添加到补丁中(或切换目录折叠)",
"EmptyPatchError": "补丁还是空的。首先将一些文件或行添加到您的补丁中。",
"EnterCommitFile": "输入文件以将所选行添加到补丁中(或切换目录折叠)",
"EnterCommitFileTooltip": "如果已选择一个文件,则Enter进入该文件,以便您可以向自定义补丁添加/删除单独的行。如果选择了目录,则切换目录。",
"ExitCustomPatchBuilder": "退出逐行模式",
"EnterUpstream": "以这种格式输入上游:'<远程仓库> <分支名称>'",
"InvalidUpstream": "上游格式无效,格式应当为:'<remote> <branchname>'",
@@ -243,51 +450,92 @@
"NewRemote": "添加新的远程仓库",
"NewRemoteName": "新远程仓库名称:",
"NewRemoteUrl": "新远程仓库 URL:",
"ViewBranches": "查看分支",
"EditRemoteName": "输入远程仓库 {{.remoteName}} 的新名称:",
"EditRemoteUrl": "输入远程仓库 {{.remoteName}} 的新 URL:",
"RemoveRemote": "删除远程",
"RemoveRemoteTooltip": "删除选中的远程。从远程跟踪远程分支的任何本地分支都不会受到影响。",
"RemoveRemotePrompt": "您确定要删除远程仓库吗?",
"DeleteRemoteBranch": "删除远程分支",
"DeleteRemoteBranchMessage": "您确定要删除远程分支吗?",
"DeleteRemoteBranchTooltip": "从远程删除远程分支。",
"SetAsUpstream": "设置为上游",
"SetAsUpstreamTooltip": "设置为检出分支的上游",
"SetUpstream": "设置为检出分支的上游",
"UnsetUpstream": "取消已选分支的上游",
"ViewDivergenceFromUpstream": "查看与上游的差异",
"ViewDivergenceFromBaseBranch": "查看主分支({{.baseBranch}})与上游的差异",
"CouldNotDetermineBaseBranch": "无法确定主分支",
"DivergenceSectionHeaderLocal": "本地",
"DivergenceSectionHeaderRemote": "远端",
"ViewUpstreamResetOptions": "重置已检出的分支到上游{{.upstream}}",
"ViewUpstreamResetOptionsTooltip": "查看用于将检出分支重置到 {{upstream}} 的选项。注意:这不会将选定的分支重置到上游,而是将检出的分支重置到上游。",
"ViewUpstreamRebaseOptions": "将检出分支的上游重新设置为 {{.upstream}}",
"ViewUpstreamRebaseOptionsTooltip": "查看将检出分支变基到 {{upstream}} 的选项。注意:这不会将所选分支重新设置为上游,而是将检出分支重新设置为上游。",
"UpstreamGenericName": "所选分支的上游",
"SetUpstreamTitle": "设置上游分支",
"SetUpstreamMessage": "您确定要将 {{.checkedOut}} 的上游分支设置为 {{.selected}} 吗?",
"EditRemoteTooltip": "编辑远程仓库",
"TagCommit": "标签提交",
"TagCommitTooltip": "创建一个新标签指向所选提交。你可以在弹窗中输入标签名称和描述(可选)。",
"TagMenuTitle": "创建标签",
"TagNameTitle": "标签名称",
"TagMessageTitle": "标签消息",
"LightweightTag": "轻量标签",
"AnnotatedTag": "附注标签",
"DeleteTagTitle": "要删除 '{{.tagName}}' 标签?",
"DeleteLocalTag": "删除本地标签",
"DeleteRemoteTag": "删除远程标签",
"SelectRemoteTagUpstream": "被删除标签'{{.tagName}}'的远端",
"DeleteRemoteTagPrompt": "你确定要从'{{.upstream}}'中删除远程标签'{{.tagName}}'吗?",
"RemoteTagDeletedMessage": "远程标签已删除",
"PushTagTitle": "将 {{.tagName}} 推送到远程仓库:",
"PushTag": "推送标签",
"PushTagTooltip": "推送选择的标签到远端。你将在弹窗中选择一个远端。",
"NewTag": "创建标签",
"NewTagTooltip": "基于当前提交创建一个新标签。你将在弹窗中输入标签名称和描述(可选)。",
"CreatingTag": "创建标签",
"ForceTag": "强制标记标签",
"ForceTagPrompt": "该标签‘{{.tagName}}’已存在。请按{{.cancelKey}}取消,或者按{{.confirmKey}}覆盖它。",
"FetchRemoteTooltip": "抓取远程仓库",
"FetchingRemoteStatus": "抓取远程仓库中",
"CheckoutCommit": "检出提交",
"CheckoutCommitTooltip": "检出所选择的提交作为分离HEAD。",
"SureCheckoutThisCommit": "您确定要检出此提交吗?",
"GitFlowOptions": "显示 git-flow 选项",
"NotAGitFlowBranch": "这似乎不是 git flow 分支",
"NewBranchNamePrompt": "输入分支的新名称",
"IgnoreTracked": "忽略跟踪文件",
"ExcludeTracked": "排除跟踪文件",
"IgnoreTrackedPrompt": "您确定要忽略已跟踪的文件吗?",
"ExcludeTrackedPrompt": "您确定要排除已跟踪的文件吗?",
"ViewResetToUpstreamOptions": "查看上游重置选项",
"NextScreenMode": "下一屏模式(正常/半屏/全屏)",
"NextScreenMode": "下一屏模式(正常/半屏/全屏)",
"PrevScreenMode": "上一屏模式",
"StartSearch": "开始搜索",
"StartFilter": "通过文本过滤当前视图",
"Panel": "面板",
"Keybindings": "按键绑定",
"KeybindingsLegend": "图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b",
"KeybindingsMenuSectionLocal": "本地",
"KeybindingsMenuSectionGlobal": "全局",
"KeybindingsMenuSectionNavigation": "导航",
"RenameBranch": "重命名分支",
"Upstream": "上游",
"UpstreamTooltip": "查看所选分支的上游选项,例如设置/取消设置上游和重置为上游。",
"BranchUpstreamOptionsTitle": "上游选项",
"ViewBranchUpstreamOptions": "查看上游选项",
"ViewBranchUpstreamOptionsTooltip": "查看与分支上游相关的选项,例如设置/取消设置上游和重置为上游。",
"UpstreamNotSetError": "所选分支没有上游(或者上游没有存储在本地)",
"NewGitFlowBranchPrompt": "新的 {{.branchType}} 名称:",
"RenameBranchWarning": "该分支正在跟踪远程仓库。此操作将仅会重命名本地分支名称,而不会重命名远程分支的名称。确定继续?",
"OpenKeybindingsMenu": "打开菜单",
"ResetCherryPick": "重置已拣选(复制)的提交",
"ResetCherryPick": "重置已拣选(复制)的提交",
"NextTab": "下一个标签",
"PrevTab": "上一个标签",
"CantUndoWhileRebasing": "进行基础调整时无法撤消",
"CantRedoWhileRebasing": "变基时无法重做",
"MustStashWarning": "将补丁拉出到索引中需要存储和取消存储所做的更改。如果出现问题,您将可以从存储中访问文件。继续?",
"MustStashWarning": "将补丁拉出到索引中需要存储和取消存储所做的变更。如果出现问题,您将可以从存储中访问文件。继续?",
"MustStashTitle": "必须保存进度",
"ConfirmationTitle": "确认面板",
"PrevPage": "上一页",
@@ -295,13 +543,17 @@
"GotoTop": "滚动到顶部",
"GotoBottom": "滚动到底部",
"FilteringBy": "过滤依据",
"ResetInParentheses": "(重置)",
"ResetInParentheses": "(重置)",
"OpenFilteringMenu": "查看按路径过滤选项",
"OpenFilteringMenuTooltip": "查看用于过滤提交日志的选项,以便仅显示与过滤器匹配的提交。",
"FilterBy": "过滤",
"ExitFilterMode": "停止按路径过滤",
"FilterPathOption": "输入要过滤的路径",
"FilterAuthorOption": "输入作者进行过滤",
"EnterFileName": "输入路径:",
"EnterAuthor": "输入作者:",
"FilteringMenuTitle": "正在过滤",
"WillCancelExistingFilterTooltip": "注意:这将取消现有的过滤器",
"MustExitFilterModeTitle": "命令不可用",
"MustExitFilterModePrompt": "命令在过滤模式下不可用。退出过滤模式?",
"Diff": "差异",
@@ -311,10 +563,22 @@
"DiffingMenuTitle": "正在 diff",
"SwapDiff": "反向 diff",
"ViewDiffingOptions": "打开 diff 菜单",
"ViewDiffingOptionsTooltip": "查看与比较两个引用相关的选项,例如与选定的 ref 进行比较,输入要比较的 ref,然后反转比较方向。",
"OpenCommandLogMenu": "打开命令日志菜单",
"OpenCommandLogMenuTooltip": "查看命令日志的选项,例如显示/隐藏命令日志以及聚焦命令日志",
"ShowingGitDiff": "显示输出:",
"CommitDiff": "比较提交差异",
"CopyCommitHashToClipboard": "将提交的 hash 复制到剪贴板",
"CommitHash": "提交的 hash",
"CommitURL": "提交URL",
"CopyCommitMessageToClipboard": "将提交消息复制到剪贴板",
"PasteCommitMessageFromClipboard": "从剪贴板粘贴提交信息",
"SurePasteCommitMessage": "粘贴将覆盖当前提交消息,继续吗?",
"CommitMessage": "提交信息",
"CommitSubject": "提交主题",
"CommitAuthor": "作者",
"CopyCommitAttributeToClipboard": "复制提交属性到剪贴板",
"CopyCommitAttributeToClipboardTooltip": "复制提交属性到剪贴板(例如,hash、URL、diff、消息、作者)。",
"CopyBranchNameToClipboard": "将分支名称复制到剪贴板",
"CopyPathToClipboard": "将文件名复制到剪贴板",
"CommitPrefixPatternError": "提交前缀模式错误",
@@ -323,18 +587,21 @@
"NoFilesStagedPrompt": "您尚未暂存任何文件。提交所有文件?",
"BranchNotFoundTitle": "找不到分支",
"BranchNotFoundPrompt": "找不到分支。创建一个新分支命名为:",
"BranchUnknown": "未知的分支",
"DiscardChangeTitle": "取消暂存选中的行",
"DiscardChangePrompt": "您确定要删除所选的行(git reset)吗?这是不可逆的。\n要禁用此对话框,请将 'gui.skipDiscardChangeWarning' 的配置键设置为 true",
"DiscardChangePrompt": "您确定要删除所选的行(git reset)吗?这是不可逆的。\n要禁用此对话框,请将 'gui.skipDiscardChangeWarning' 的配置键设置为 true",
"CreateNewBranchFromCommit": "从提交创建新分支",
"BuildingPatch": "正在构建补丁",
"ViewCommits": "查看提交",
"MinGitVersionError": "Git 版本必须至少为 2.20(即从 2018 年开始的版本)。请更新 git。或者在 https://github.com/jesseduffield/lazygit/issues 上提出一个问题,以使 lazygit 更加向后兼容。",
"MinGitVersionError": "Git 版本必须至少为 2.20(即从 2018 年开始的版本)。请更新 git。或者在 https://github.com/jesseduffield/lazygit/issues 上提出一个问题,以使 lazygit 更加向后兼容。",
"RunningCustomCommandStatus": "正在运行自定义命令",
"SubmoduleStashAndReset": "存放未提交的子模块更改和更新",
"SubmoduleStashAndReset": "存放未提交的子模块变更和更新",
"AndResetSubmodules": "和重置子模块",
"EnterSubmoduleTooltip": "输入子模块",
"Enter": "进入",
"CopySubmoduleNameToClipboard": "将子模块名称复制到剪贴板",
"RemoveSubmodule": "删除子模块",
"RemoveSubmoduleTooltip": "删除选定的子模块及其相应的目录",
"RemoveSubmodulePrompt": "您确定要删除子模块 '%s' 及其对应的目录吗?这是不可逆的。",
"ResettingSubmoduleStatus": "正在重置子模块",
"NewSubmoduleName": "新的子模块名称:",
@@ -347,6 +614,8 @@
"EditSubmoduleUrl": "更新子模块 URL",
"InitializingSubmoduleStatus": "正在初始化子模块",
"InitSubmoduleTooltip": "初始化子模块",
"Update": "更新",
"Initialize": "初始化",
"SubmoduleUpdateTooltip": "更新子模块",
"UpdatingSubmoduleStatus": "正在更新子模块",
"BulkInitSubmodules": "批量初始化子模块",
@@ -359,15 +628,22 @@
"SubmodulesTitle": "子模块",
"NavigationTitle": "列表面板导航",
"SuggestionsCheatsheetTitle": "意见建议",
"SuggestionsTitle": "意见建议 (点击 %s 以聚焦)",
"SuggestionsTitle": "意见建议(按 %s 键以切换焦点)",
"SuggestionsSubtitle": "(按 %s 键进行删除, %s 键进行编辑)",
"ExtrasTitle": "附加",
"PushingTagStatus": "推送标签",
"PullRequestURLCopiedToClipboard": "抓取请求网址已复制到剪贴板",
"CommitMessageCopiedToClipboard": "提交消息复制到剪贴板",
"CommitDiffCopiedToClipboard": "复制提交差异到剪贴板",
"CommitURLCopiedToClipboard": "复制提交URL到剪贴板",
"CommitMessageCopiedToClipboard": "复制提交消息到剪贴板",
"CommitSubjectCopiedToClipboard": "复制提交主题到剪贴板",
"CommitAuthorCopiedToClipboard": "复制作者到剪贴板",
"PatchCopiedToClipboard": "已复制补丁内容到剪贴板",
"CopiedToClipboard": "复制到剪贴板",
"ErrCannotEditDirectory": "无法编辑目录:您只能编辑单个文件",
"ErrStageDirWithInlineMergeConflicts": "无法 暂存/取消暂存 包含具有内联合并冲突的文件的目录。请先解决合并冲突",
"ErrRepositoryMovedOrDeleted": "找不到仓库。它可能已被移动或删除 ¯\\_(ツ)_/¯",
"ErrWorktreeMovedOrRemoved": "找不到工作树,它可能被删除或者移走了。 ¯\\\\_(ツ)_/¯",
"CommandLog": "命令日志",
"ToggleShowCommandLog": "切换 显示/隐藏 命令日志",
"FocusCommandLog": "焦点命令日志",
@@ -375,8 +651,14 @@
"RandomTip": "随机小提示",
"SelectParentCommitForMerge": "选择父提交进行合并",
"ToggleWhitespaceInDiffView": "切换是否在差异视图中显示空白字符差异",
"ToggleWhitespaceInDiffViewTooltip": "切换是否在diff视图中显示空白更改",
"IgnoreWhitespaceDiffViewSubTitle": "(忽略空白)",
"IgnoreWhitespaceNotSupportedHere": "该视图不支持忽略空白",
"IncreaseContextInDiffView": "扩大差异视图中显示的上下文范围",
"IncreaseContextInDiffViewTooltip": "增加diff视图中围绕更改显示的上下文数量",
"DecreaseContextInDiffView": "缩小差异视图中显示的上下文范围",
"DecreaseContextInDiffViewTooltip": "减少diff视图中围绕更改显示的上下文数量",
"DiffContextSizeChanged": "将diff上下文大小更改为%d",
"CreatePullRequestOptions": "创建抓取请求选项",
"DefaultBranch": "默认分支",
"SelectBranch": "选择分支",
@@ -387,60 +669,178 @@
"LoadingCommits": "正在加载提交",
"MustSpecifyOriginError": "指定分支时,必须同时指定远程",
"GitOutput": "Git 输出:",
"GitCommandFailed": "Git 命令执行失败。查看命令日志了解详情 (使用 %s 打开)",
"GitCommandFailed": "Git 命令执行失败。查看命令日志了解详情(使用 %s 打开)",
"AbortTitle": "放弃 %s",
"AbortPrompt": "您确定要放弃当前 %s 吗?",
"OpenLogMenu": "打开日志菜单",
"OpenLogMenuTooltip": "查看提交日志的选项,例如更改排序顺序、隐藏 git graph、显示整个 git graph。",
"LogMenuTitle": "提交日志选项",
"ToggleShowGitGraphAll": "切换显示完整 git 分支图 (向 `git log` 命令传入 `--all` 选项)",
"ToggleShowGitGraphAll": "切换显示完整 git 分支图(向 `git log` 命令传入 `--all` 选项)",
"ShowGitGraph": "显示 git 分支图",
"SortOrder": "排序",
"SortAlphabetical": "字母顺序",
"SortByDate": "日期大小",
"SortByRecency": "新旧程度",
"SortBasedOnReflog": "(基于reflog)",
"SortCommits": "提交排序",
"CantChangeContextSizeError": "无法在补丁构建模式下更改上下文,因为我们在发布该功能时懒得支持它。 如果你真的想要这么做,请告诉我们!",
"CantChangeContextSizeError": "无法在补丁构建模式下变更上下文,因为我们在发布该功能时懒得支持它。 如果你真的想要这么做,请告诉我们!",
"OpenCommitInBrowser": "在浏览器中打开提交",
"ViewBisectOptions": "查看二分查找选项",
"ConfirmRevertCommit": "你确定要恢复{{.selectedCommit}}?",
"RewordInEditorTitle": "在编辑器中重写",
"RewordInEditorPrompt": "您确定要在编辑器中重写此提交吗?",
"CheckoutPrompt": "您确定要检出 '%s' 吗?",
"HardResetAutostashPrompt": "您确定要硬重置到 '%s' 吗?如有必要,将执行自动贮藏。",
"UpstreamGone": "(上游不存在)",
"NukeDescription": "如果您想让工作树中的所有更改消失,可以这样做。如果存在受污染的子模块更改,这会将这些更改贮藏在子模块中。",
"DiscardStagedChangesDescription": "这将创建一个仅包含已暂存更改的新贮藏条目,然后这些更改删除,以便工作树仅保留未暂存的更改。",
"EmptyOutput": "<Empty output>",
"Patch": "补丁",
"CustomPatch": "自定义补丁",
"CommitsCopied": "已复制这些提交",
"CommitCopied": "已复制提交",
"ResetPatch": "重置补丁",
"ResetPatchTooltip": "清理当前补丁",
"ApplyPatch": "应用补丁",
"ApplyPatchTooltip": "应用当前补丁到工作树中",
"ApplyPatchInReverse": "反向应用补丁",
"ApplyPatchInReverseTooltip": "反向应用当前补丁到工作树中",
"RemovePatchFromOriginalCommit": "从原来的提交 (%s) 中删除补丁",
"RemovePatchFromOriginalCommitTooltip": "从这些提交中删除该补丁。这是通过在提交时启动交互式变基,反向应用补丁,然后继续变基来实现的。如果之后的提交依赖于补丁,您可能需要解决冲突。",
"MovePatchOutIntoIndex": "将补丁移出到索引中",
"MovePatchOutIntoIndexTooltip": "将补丁从提交中移出并移入到索引中。这是通过在提交时启动交互式变基、反向应用补丁、继续变基直至完成,然后将补丁应用到索引来实现的。如果之后的提交依赖于补丁,您可能需要解决冲突。",
"MovePatchIntoNewCommit": "引入补丁到新提交中",
"MovePatchIntoNewCommitTooltip": "将补丁从提交中移出并移至位于原始提交之上的新提交中。这是通过在原始提交处启动交互式变基,反向应用补丁,然后将补丁应用到索引并将其作为新提交提交,然后继续变基直至完成来实现的。如果以后的提交依赖于补丁,您可能需要解决冲突。",
"MovePatchToSelectedCommit": "移动补丁到所选提交 (%s)",
"MovePatchToSelectedCommitTooltip": "将补丁从其原始提交修改到选定的提交中。 实现这一点的方法是在原始提交时启动交互式重置,反向应用补丁, 然后在应用补丁和修改选定的提交之前,继续将其重新建立到选定的提交上。 重置将继续完成。如果源代码和目标代码提交之间的提交取决于补丁,您可能需要解决冲突。",
"CopyPatchToClipboard": "复制补丁到剪贴板",
"NoMatchesFor": "%s %s 没有匹配项",
"MatchesFor": "正在匹配'%s' (%d of %d) %s",
"SearchKeybindings": "%s: 下一个匹配项, %s: 上一个匹配项, %s: 退出搜索模式",
"SearchPrefix": "搜索: ",
"FilterPrefix": "过滤: ",
"ExitSearchMode": "%s:退出搜索模式",
"ExitTextFilterMode": "%s:退出过滤模式",
"Switch": "切换",
"SwitchToWorktree": "切换至工作树",
"SwitchToWorktreeTooltip": "切换到选中的工作树",
"AlreadyCheckedOutByWorktree": "该分支由工作树 {{.worktreeName}} 检出,您想切换到该工作树吗?",
"BranchCheckedOutByWorktree": "该分支 {{.branchName}} 由工作树 {{.worktreeName}} 签出",
"DetachWorktreeTooltip": "这将在工作树上运行“git checkout --detach”,以便它停止占用分支,但工作树的工作树将保持不变。",
"Switching": "正在切换",
"RemoveWorktree": "移除工作区",
"RemoveWorktreeTitle": "移除工作区",
"DetachWorktree": "分离工作树",
"DetachingWorktree": "正在分离工作树",
"WorktreesTitle": "工作区",
"WorktreeTitle": "工作区",
"RemoveWorktreePrompt": "你确定要删除工作树 {{.worktreeName}}' ?",
"ForceRemoveWorktreePrompt": "'{{.worktreeName}}' 包含已修改或未跟踪的文件(说实话,它可能包含两者)。您确定要删除它吗?",
"RemovingWorktree": "正在删除工作树",
"AddingWorktree": "添加工作区",
"CantDeleteCurrentWorktree": "您不能删除当前的工作树!",
"AlreadyInWorktree": "您已经在选中的工作树",
"CantDeleteMainWorktree": "您不能移除主工作树!",
"NoWorktreesThisRepo": "没有工作区",
"MissingWorktree": "(缺失)",
"MainWorktree": "(主要)",
"NewWorktree": "新建工作树",
"NewWorktreePath": "新建工作树路径",
"NewWorktreeBase": "新建工作树基于ref",
"RemoveWorktreeTooltip": "删除选定的工作树。这将删除工作树的目录以及 .git 目录中有关工作树的元数据。",
"BranchNameCannotBeBlank": "分支名称不能为空",
"NewBranchName": "新分支名称",
"NewBranchNameLeaveBlank": "新分支名称(为空则默认检出为 {{.default}})",
"ViewWorktreeOptions": "查看工作区选项",
"CreateWorktreeFrom": "从 {{.ref}} 创建工作树",
"CreateWorktreeFromDetached": "从 {{.ref}} 创建工作树(分离)",
"LcWorktree": "工作区",
"ChangingDirectoryTo": "将目录更改为 {{.path}}",
"Name": "名称",
"Branch": "分支",
"Path": "路径",
"MarkedBaseCommitStatus": "已标记一个主提交用于变基",
"MarkAsBaseCommit": "标记一个主提交用于变基",
"MarkAsBaseCommitTooltip": "选择下一次变基的主提交。当您变基到一个分支时,只有高于主提交的提交才会被引入。这使用“git rebase --onto”命令。",
"MarkedCommitMarker": "↑↑↑ 将从这里开始变基 ↑↑↑",
"PleaseGoToURL": "清查看 {{.url}}",
"NoCopiedCommits": "未复制提交",
"DisabledMenuItemPrefix": "不可用: ",
"QuickStartInteractiveRebase": "开始交互式变基",
"QuickStartInteractiveRebaseTooltip": "为分支上的提交启动交互式变基。这将包括从 HEAD 提交到第一个合并提交或主分支提交的所有提交。\n如果您想从所选提交启动交互式变基,请按 `{{.editKey}}`。",
"CannotQuickStartInteractiveRebase": "无法启动交互式变基:HEAD 提交是合并提交或存在于主分支上,因此没有适当的主提交来启动变基。您可以通过选择提交并按 `{{.editKey}}` 从特定提交启动交互式变基。",
"ToggleRangeSelect": "切换拖动选择",
"RangeSelectUp": "向上扩展选择范围",
"RangeSelectDown": "向下扩展选择范围",
"RangeSelectNotSupported": "该操作不支持范围选择,请选择单个项目",
"NoItemSelected": "没有条目被选中",
"SelectedItemIsNotABranch": "选中的条目不是一个分支",
"SelectedItemDoesNotHaveFiles": "选中的条目中没有",
"RangeSelectNotSupportedForSubmodules": "子模块不支持范围选择",
"OldCherryPickKeyWarning": "The 'c' key is no longer the default key for copying commits to cherry pick. Please use `{{.copy}}` instead (and `{{.paste}}` to paste). The reason for this change is that the 'v' key for selecting a range of lines when staging is now also used for selecting a range of lines in any list view, meaning that we needed to find a new key for pasting commits, and if we're going to now use `{{.paste}}` for pasting commits, we may as well use `{{.copy}}` for copying them. If you want to configure the keybindings to get the old behaviour, set the following in your config:\n\nkeybinding:\n universal:\n toggleRangeSelect: <something other than v>\n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'",
"CommandDoesNotSupportOpeningInEditor": "该命令不支持切换到编辑器",
"Actions": {
"CheckoutCommit": "检出提交",
"CheckoutTag": "检出标签",
"CheckoutBranch": "检出分支",
"ForceCheckoutBranch": "强制检出分支",
"DeleteLocalBranch": "删除本地分支",
"DeleteBranch": "删除分支",
"Merge": "合并",
"SquashMerge": "压缩合并",
"RebaseBranch": "变基分支",
"RenameBranch": "重命名分支",
"CreateBranch": "建立分支",
"FastForwardBranch": "快进分支",
"CherryPick": "(拣选) 粘贴提交",
"CheckoutFile": "检出文件",
"DiscardOldFileChange": "放弃旧文件更改",
"DiscardOldFileChange": "放弃旧文件变更",
"SquashCommitDown": "向下压缩提交",
"FixupCommit": "修正提交",
"RewordCommit": "改写提交",
"DropCommit": "删除提交",
"EditCommit": "编辑提交",
"AmendCommit": "修改提交",
"AmendCommit": "提交修补",
"ResetCommitAuthor": "重设作者",
"SetCommitAuthor": "设置作者",
"AddCommitCoAuthor": "添加其他的提交作者",
"RevertCommit": "还原提交",
"CreateFixupCommit": "创建修正提交",
"SquashAllAboveFixupCommits": "压缩以上所有的修正提交",
"MoveCommitUp": "上移提交",
"MoveCommitDown": "下移提交",
"CopyCommitMessageToClipboard": "将提交消息复制到剪贴板",
"CopyCommitSubjectToClipboard": "复制提交主题到剪贴板",
"CopyCommitDiffToClipboard": "复制提交差异到剪贴板",
"CopyCommitHashToClipboard": "复制完整的提交哈希到剪贴板",
"CopyCommitURLToClipboard": "复制提交URL到剪贴板",
"CopyCommitAuthorToClipboard": "复制提交作者到剪贴板",
"CopyCommitAttributeToClipboard": "复制到剪贴板",
"CopyPatchToClipboard": "复制路径到剪贴板",
"CustomCommand": "自定义命令",
"DiscardAllChangesInDirectory": "丢弃目录中的所有更改",
"DiscardUnstagedChangesInDirectory": "丢弃目录中未暂存的更改",
"DiscardAllChangesInFile": "丢弃文件中的所有更改",
"DiscardAllUnstagedChangesInFile": "丢弃文件中所有未暂存的更改",
"DiscardAllChangesInDirectory": "丢弃目录中的所有变更",
"DiscardUnstagedChangesInDirectory": "丢弃目录中未暂存的变更",
"DiscardAllChangesInFile": "丢弃文件中的所有变更",
"DiscardAllUnstagedChangesInFile": "丢弃文件中所有未暂存的变更",
"StageFile": "暂存文件",
"StageResolvedFiles": "合并冲突已解决的已暂存文件",
"UnstageFile": "取消暂存文件",
"UnstageAllFiles": "取消暂存所有文件",
"StageAllFiles": "暂存所有文件",
"IgnoreExcludeFile": "忽略文件",
"Commit": "提交 (Commit)",
"IgnoreFileErr": "无法忽略 .gitignore",
"ExcludeFile": "排除文件",
"ExcludeGitIgnoreErr": "无法排除.gitignore",
"Commit": "提交(Commit)",
"EditFile": "编辑文件",
"Push": "推送 (Push)",
"Pull": "拉取 (Pull)",
"Push": "推送(Push)",
"Pull": "拉取(Pull)",
"OpenFile": "打开文件",
"StashAllChanges": "贮藏所有更改",
"StashStagedChanges": "贮藏暂存的更改",
"StashAllChanges": "贮藏所有变更",
"StashAllChangesKeepIndex": "贮藏所有更改并保持索引",
"StashStagedChanges": "贮藏暂存的变更",
"StashUnstagedChanges": "贮藏所有未暂存更改",
"StashIncludeUntrackedChanges": "贮藏所有更改包括未跟踪的文件",
"GitFlowFinish": "git flow 结果",
"GitFlowStart": "git flow 开始",
"CopyToClipboard": "复制到剪贴板",
@@ -455,7 +855,8 @@
"RemoveRemote": "移除远程",
"UpdateRemote": "更新远程",
"ApplyPatch": "应用补丁",
"Stash": "贮藏 (Stash)",
"Stash": "贮藏(Stash)",
"RenameStash": "重命名贮藏",
"RemoveSubmodule": "删除子模块",
"ResetSubmodule": "重置子模块",
"AddSubmodule": "添加子模块",
@@ -467,36 +868,64 @@
"UpdateSubmodule": "更新子模块",
"CreateLightweightTag": "创建轻量标签",
"CreateAnnotatedTag": "创建附注标签",
"DeleteLocalTag": "删除本地标签",
"DeleteRemoteTag": "删除远程标签",
"PushTag": "推送标签",
"NukeWorkingTree": "Nuke 工作树",
"DiscardUnstagedFileChanges": "放弃未暂存的文件更改",
"DiscardUnstagedFileChanges": "放弃未暂存的文件变更",
"RemoveUntrackedFiles": "删除未跟踪的文件",
"RemoveStagedFiles": "移除贮藏文件",
"SoftReset": "软重置",
"MixedReset": "混合重置",
"HardReset": "硬重置",
"Undo": "撤销",
"Redo": "重做",
"CopyPullRequestURL": "复制拉取请求 URL",
"OpenDiffTool": "打开差异比较工具",
"OpenMergeTool": "打开合并工具",
"OpenCommitInBrowser": "在浏览器中打开提交",
"OpenPullRequest": "在浏览器中打开拉取请求",
"StartBisect": "开始二分查找 (Bisect)",
"StartBisect": "开始二分查找(Bisect)",
"ResetBisect": "重置二分查找",
"BisectSkip": "二分查找跳过",
"BisectMark": "二分查找标记"
"BisectMark": "二分查找标记",
"RemoveWorktree": "移除工作区",
"AddWorktree": "添加工作区"
},
"Bisect": {
"MarkStart": "将 %s 标记为 %s (start bisect)",
"ResetTitle": "重置 'git bisect'",
"ResetPrompt": "您确定要重置 'git bisect' 吗?",
"ResetOption": "重置二分查找",
"ChooseTerms": "二选一",
"OldTermPrompt": "旧/好的提交:",
"NewTermPrompt": "新/坏的提交:",
"BisectMenuTitle": "二分查找",
"Mark": "将 %s 标记为 %s",
"SkipCurrent": "跳过 %s",
"SkipSelected": "跳过所选提交(%s)",
"CompleteTitle": "二分查找完成",
"CompletePrompt": "二分查找完成!以下提交引入了此变更:\n\n%s\n\n您现在要重置 'git bisect' 吗?",
"CompletePromptIndeterminate": "二分查找完成!一些提交被跳过了,所以下列提交中的任何一个都可能引入了此变更:\n\n%s\n\n您现在要重置 'git bisect' 吗?"
"CompletePromptIndeterminate": "二分查找完成!一些提交被跳过了,所以下列提交中的任何一个都可能引入了此变更:\n\n%s\n\n您现在要重置 'git bisect' 吗?",
"Bisecting": "二分查找中"
},
"Log": {},
"BreakingChangesByVersion": {}
"Log": {
"EditRebase": "开始基于 '{{.ref}}' 进行交互式变基",
"MoveCommitUp": "向下移动 TODO: '{{.shortHash}}'",
"MoveCommitDown": "向上移动 TODO: '{{.shortHash}}'",
"CherryPickCommits": "拣选提交:\n'{{.commitLines}}'",
"HandleUndo": "撤销最后一次的冲突解决方案",
"HandleMidRebaseCommand": "将提交 {{.shortHash}} 的变基操作更新为 '{{.action}}'",
"RemoveFile": "正在删除路径 '{{.path}}'",
"CopyToClipboard": "正在复制 '{{.str}}' 到剪贴板",
"Remove": "删除 '{{.filename}}'",
"CreateFileWithContent": "正在创建文件 '{{.path}}'",
"AppendingLineToFile": "将 '{{.line}}' 附加到文件 '{{.filename}}'",
"EditRebaseFromBaseCommit": "开始从'{{.baseCommit}}'进行交互式变基到'{{.targetBranchName}}‘"
},
"BreakingChangesTitle": "重大变化",
"BreakingChangesMessage": "您正在更新到 lazygit 的新版本,其中含有中断的更改。请阅读下面的说明,并在必要时更新您的配置。\n欲了解更多信息,请参阅 <https://github.com/jesseduffield/lazygit/releases> 的完整版本说明。",
"BreakingChangesByVersion": {
"0.41.0": "- 当您按“g”调出 git 重置菜单时,“mixed”选项现在是第一个也是默认选项,而不是“soft”。这是因为“mixed”是最常用的选项。\n- 提交消息面板现在默认自动硬换行(即,当您到达页边距时,它会添加换行符)。您可以像这样调整配置:\n\ngit:\n commit:\n autoWrapCommitMessage: true\n autoWrapWidth: 72\n\n- “v”键已在暂存视图中用于启动范围选择,但现在您可以使用它在任何视图中启动范围选择。不幸的是,这与粘贴提交(cherry-pick)的“v”键绑定冲突,因此现在粘贴提交是通过“shift+V”完成的,为了一致性,复制提交现在是通过“shift+C”而不是“c”。请注意,“v”键绑定只是启动范围选择的一种方法:您可以使用 shift+向上/向下箭头。因此,如果您想配置cherry-pick键绑定以获得旧行为,请在配置中设置以下内容:\n\nkeybinding:\n universal:\n toggleRangeSelect: <something other than v>\n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'\n\n- 使用“shift-S”压缩修复现在会弹出一个菜单,默认选项是压缩分支中的所有修复提交。仅压缩所选提交之上的修复提交的原始行为仍然可以作为该菜单中的第二个选项使用。\n- Push/pull/fetch 加载状态现在显示在分支上,而不是在弹出窗口中。这允许您例如并行获取多个分支并查看每个分支的状态。\n- 提交视图中的 git 日志图现在始终默认显示(以前仅在视图最大化时显示)。如果你发现这太冗余了,你可以通过 ctrl+L -> 'Show git graph' -> 'when maximized' 将其改回来\n- 在远程分支上按空格用于显示输入新本地分支名称的提示,以从远程分支检出。现在它只是直接检查远程分支,让您在同名的新本地分支或分离的头之间进行选择。旧的行为仍然可以通过“n”键绑定使用。\n- 默认情况下,过滤(例如按“/”时)不太模糊;它现在只匹配子字符串。多个子字符串可以通过用空格分隔来匹配。如果您想恢复到旧的行为,请在配置中设置以下内容:\n\ngui:\n filterMode: 'fuzzy'\n\t "
}
}
+93 -22
View File
@@ -12,6 +12,8 @@
"MainTitle": "主要",
"StagingTitle": "主面板(預存)",
"MergingTitle": "主面板(合併)",
"SquashMergeUncommittedTitle": "壓縮合併但不提交",
"SquashMergeCommittedTitle": "壓縮合併並提交",
"NormalTitle": "主面板(一般)",
"LogTitle": "版本記錄",
"CommitSummary": "提交摘要",
@@ -21,6 +23,7 @@
"CredentialsPIN": "SSH 金鑰 PIN 碼",
"PassUnameWrong": "密碼、密語或使用者名稱錯誤",
"Commit": "提交變更",
"CommitTooltip": "提交暫存區變更",
"AmendLastCommit": "修改上次提交",
"AmendLastCommitTitle": "修改上次提交",
"SureToAmend": "是否確定要修改上次提交?之後你可以從提交面板中再次更改此次提交的訊息。",
@@ -33,17 +36,32 @@
"Stage": "切換預存",
"ToggleStagedAll": "全部預存/取消預存",
"ToggleTreeView": "顯示檔案樹狀視圖",
"OpenMergeTool": "開啟外部合併工具 (git mergetool)",
"OpenDiffTool": "開啟外部差異工具 (git difftool)",
"OpenMergeTool": "開啟外部合併工具",
"OpenMergeToolTooltip": "執行 `git mergetool`。",
"Refresh": "重新整理",
"Push": "推送",
"Pull": "拉取",
"PushTooltip": "推送到遠端。如果沒有設定遠端,會開啟設定視窗。",
"PullTooltip": "從遠端同步當前分支。如果沒有設定遠端,會開啟設定視窗。",
"Scroll": "捲動",
"FileFilter": "篩選檔案 (預存/未預存)",
"CopyToClipboardMenu": "複製到剪貼簿",
"CopyFileName": "檔案名稱",
"CopyFilePath": "路徑",
"CopyFileDiffTooltip": "如果有已預存的項目,此指令只考慮它們。否則,它將考慮所有未暫存的項目。",
"CopySelectedDiff": "所選檔案的差異",
"CopyAllFilesDiff": "所有檔案的差異",
"FileNameCopiedToast": "檔案名稱已複製",
"FilePathCopiedToast": "檔案路徑已複製",
"FileDiffCopiedToast": "已複製檔案差異",
"AllFilesDiffCopiedToast": "已複製所有檔案差異",
"FilterStagedFiles": "僅顯示預存的檔案",
"FilterUnstagedFiles": "僅顯示未預存的檔案",
"ResetFilter": "重設篩選",
"MergeConflictsTitle": "合併衝突",
"Checkout": "檢出",
"CheckoutTooltip": "檢出選定的項目。",
"NoChangedFiles": "沒有變更的檔案",
"SoftReset": "軟重設",
"AlreadyCheckedOutBranch": "你已經檢出這個分支了",
@@ -52,12 +70,18 @@
"BranchName": "分支名稱",
"NewBranchNameBranchOff": "新的分支名稱 (根據 '{{.branchName}}' 分支創建)",
"CantDeleteCheckOutBranch": "無法刪除已檢出的分支!",
"DeleteRemoteBranchPrompt": "確定要刪除遠端 {{.upstream}} 的標籤 '{{.selectedBranchName}}'?",
"ForceDeleteBranchMessage": "'{{.selectedBranchName}}' 分支尚未完全合併。是否刪除?",
"RebaseBranch": "將已檢出的分支變基至此分支",
"CantRebaseOntoSelf": "無法將分支變基至自己",
"CantMergeBranchIntoItself": "無法將一個分支合併至自己",
"ForceCheckout": "強制檢出",
"CheckoutByName": "根據名稱檢出",
"RemoteBranchCheckoutTitle": "檢出 {{.branchName}}",
"CheckoutTypeNewBranch": "新本地分支",
"CheckoutTypeNewBranchTooltip": "將遠端分支檢出為追蹤它的本地分支。",
"CheckoutTypeDetachedHead": "分離 HEAD",
"CheckoutTypeDetachedHeadTooltip": "將遠端分支檢出為分離的 HEAD,在只想測試但不動工時很實用。您稍後仍能根據它建立一個本地分支。",
"NewBranch": "新分支",
"NoBranchesThisRepo": "這個版本庫中沒有分支",
"CommitWithoutMessageErr": "沒有提交訊息,無法提交",
@@ -71,15 +95,22 @@
"SureSquashThisCommit": "是否要把這個提交壓縮到下面的提交中?",
"Squash": "壓縮 (Squash)",
"PickCommitTooltip": "挑選提交 (於變基過程中)",
"Pick": "挑選",
"Edit": "編輯",
"RevertCommit": "還原提交",
"Revert": "還原",
"Reword": "改寫提交",
"CommitRewordTooltip": "改寫選中的提交訊息",
"DropCommit": "刪除提交",
"MoveDownCommit": "向下移動提交",
"MoveUpCommit": "向上移動提交",
"EditCommit": "編輯(開始互動變基)",
"EditCommitTooltip": "編輯提交",
"AmendCommitTooltip": "使用已預存的更改修正提交",
"Amend": "修改",
"ResetAuthor": "重設作者",
"SetAuthor": "設定作者",
"AddCoAuthor": "添加合作者",
"AmendCommitAttribute": "設定/重設提交作者",
"SetAuthorPromptTitle": "設定作者(格式:「姓名 <電子郵件>」)",
"SureResetCommitAuthor": "為了符合已配置的使用者,此作者的提交欄位以及時間戳將被更新。是否繼續?",
@@ -131,15 +162,19 @@
"ConfirmQuitDuringUpdate": "正在進行更新,是否結束?",
"MergeToolTitle": "合併工具",
"MergeToolPrompt": "是否開啟 'git mergetool'?",
"IntroPopupMessage": "\n感謝使用 lazygit!這裡有一些資源可供參考:\n\n 1) 📺lazygit 教學📺:\n https://youtu.be/CPLdltN7wgE\n\n 2) 📣釋出說明📣:\n https://github.com/jesseduffield/lazygit/releases\n\n 3) 💖如果你想要貢獻一份心力你可以💖:\n 改進 lazygit 原始碼:https://github.com/jesseduffield/lazygit\n 按右下角的捐款斗內我們\n 或單存添加 lazygit 到你的 star 清單內以增加曝光度都能大力的幫助我們!\n",
"IntroPopupMessage": "\n感謝使用 lazygit!這裡有一些資源可供參考:\n\n 1) 📺lazygit 教學📺:\n https://youtu.be/CPLdltN7wgE\n\n 2) 📣發佈說明📣:\n https://github.com/jesseduffield/lazygit/releases\n\n 3) 💖如果你想要貢獻一份心力你可以💖:\n 改進 lazygit 原始碼:https://github.com/jesseduffield/lazygit\n 按右下角的捐款斗內我們\n 或單純添加 lazygit 到你的 star 清單內以增加曝光度都能大力的幫助我們!\n",
"DeprecatedEditConfigWarning": "\n### Deprecated config warning ###\n\n以下設定已被取代並將於未來版本中刪除:\n{{configs}}\n\n編輯器設定教學:\n\n https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#configuring-file-editing\n\n",
"GitconfigParseErr": "Gogit 無法解析你的 gitconfig 檔案,因為存在未引用的 '\\' 字符,刪除它們應該可以解決這個問題。",
"EditFile": "編輯檔案",
"EditFileTooltip": "使用外部編輯器開啟",
"OpenFile": "開啟檔案",
"OpenFileTooltip": "使用預設軟體開啟",
"OpenInEditor": "在編輯器中開啟",
"IgnoreFile": "添加到 .gitignore",
"ExcludeFile": "添加到 .git/info/exclude",
"RefreshFiles": "重新整理檔案",
"Merge": "合併到當前檢出的分支",
"RegularMerge": "一般合併",
"ConfirmQuit": "是否結束?",
"SwitchRepo": "切換到最近使用的版本庫",
"AllBranchesLogGraph": "顯示所有分支日誌",
@@ -147,6 +182,7 @@
"CopyPullRequestURL": "複製拉取請求的 URL 到剪貼板",
"NoBranchOnRemote": "這個分支在遠端不存在。需要先將其推送至遠端。",
"Fetch": "擷取",
"FetchTooltip": "同步遠端異動",
"NoAutomaticGitFetchTitle": "手動 git 擷取",
"NoAutomaticGitFetchBody": "lazygit 無法在私有庫使用 \"git 擷取\";在檔案面板中使用 'f' 手動執行 \"git 擷取\"",
"FileEnter": "選擇檔案中的單個程式碼塊/行,或展開/折疊目錄",
@@ -159,11 +195,15 @@
"ToggleStagingView": "切換至另一個面板 (已預存/未預存更改)",
"ReturnToFilesPanel": "返回檔案面板",
"FastForward": "從上游快進此分支",
"FastForwardTooltip": "從遠端快進所選的分支",
"FastForwarding": "的擷取和快進中",
"FoundConflictsTitle": "自動合併失敗",
"ViewConflictsMenuItem": "檢視衝突",
"AbortMenuItem": "關於%s",
"PickHunk": "挑選程式碼片段",
"PickAllHunks": "挑選所有程式碼片段",
"ViewMergeRebaseOptions": "查看合併/變基選項",
"ViewRebaseOptions": "查看合併/變基選項",
"NotMergingOrRebasing": "你當前既不在變基也不在合併中",
"AlreadyRebasing": "無法在變基期間執行此操作",
"RecentRepos": "最近的版本庫",
@@ -188,9 +228,10 @@
"SimpleRebase": "簡單變基 變基至 '{{.ref}}'",
"InteractiveRebase": "互動變基 變基至 '{{.ref}}'",
"InteractiveRebaseTooltip": "開始一個互動變基,以中斷開始,這樣你可以在繼續之前更新TODO提交",
"FwdNoUpstream": "無法快進無上游分支",
"FwdNoUpstream": "無法快進無遠端的分支 ",
"FwdNoLocalUpstream": "無法快進尚未在本地註冊的遠端分支",
"FwdCommitsToPush": "無法快進帶有尚未推送的提交的分支",
"PullRequestNoUpstream": "無法對沒有遠端的分支拉取",
"ErrorOccurred": "發生錯誤!請在此詢問錯誤:",
"NoRoom": "無足夠的空間",
"YouAreHere": "你在這",
@@ -238,9 +279,9 @@
"CommittingStatus": "提交中",
"RevertingStatus": "還原中",
"CommitFiles": "提交檔案",
"SubCommitsDynamicTitle": "提交 (共 %s項)",
"CommitFilesDynamicTitle": "差異檔案 (共 %s項)",
"RemoteBranchesDynamicTitle": "遠端分支 (共 %s項)",
"SubCommitsDynamicTitle": "提交(%s)",
"CommitFilesDynamicTitle": "差異檔案(%s)",
"RemoteBranchesDynamicTitle": "遠端分支(%s)",
"ViewItemFiles": "檢視所選項目的檔案",
"CommitFilesTitle": "提交檔案",
"CheckoutCommitFileTooltip": "檢出檔案",
@@ -255,6 +296,9 @@
"AutoStashTitle": "是否自動收藏?",
"AutoStashPrompt": "必須收藏並拾起變更才得以繼續操作。是否自動執行?(Enter/Esc)",
"StashPrefix": "自動收藏 ",
"Discard": "捨棄",
"DiscardChangesTitle": "捨棄變更",
"DiscardFileChangesTooltip": "檢視選中變動進行捨棄復原",
"Cancel": "取消",
"DiscardAllChanges": "刪除所有變更",
"DiscardUnstagedChanges": "刪除未預存變更",
@@ -263,6 +307,8 @@
"DiscardUntrackedFiles": "刪除未追蹤檔案",
"DiscardStagedChanges": "刪除已預存變更",
"HardReset": "強制重設",
"Delete": "刪除",
"Reset": "重設",
"ViewResetOptions": "檢視重設選項",
"CreateFixupCommit": "建立修復提交",
"CreateFixupCommitTooltip": "為此提交建立修復提交",
@@ -275,6 +321,7 @@
"ResetTo": "重設至",
"PressEnterToReturn": "按 Enter 返回到 lazygit",
"ViewStashOptions": "檢視收藏選項",
"Stash": "收藏",
"StashAllChanges": "收藏所有變更",
"StashStagedChanges": "收藏已預存變更",
"StashAllChangesKeepIndex": "收藏所有變更並保留預存區",
@@ -297,8 +344,8 @@
"NoPatchError": "尚未建立補丁。要開始建立補丁,請在提交檔案上使用空格或輸入以添加特定行",
"EnterCommitFile": "輸入檔案以將選定的行添加至補丁(或切換目錄折疊)",
"ExitCustomPatchBuilder": "退出自訂補丁建立器",
"EnterUpstream": "輸入上游為 '<remote> <branchname>'",
"InvalidUpstream": "無效的上游。必須符合 '<remote> <branchname>' 的格式",
"EnterUpstream": "輸入遠端為 '<remote> <branchname>'",
"InvalidUpstream": "無效的遠端分支名稱。必須符合 '<remote> <branchname>' 的格式",
"ReturnToRemotesList": "返回遠端列表",
"NewRemote": "新增遠端",
"NewRemoteName": "新遠端名稱:",
@@ -309,10 +356,18 @@
"RemoveRemotePrompt": "你確定要移除遠端?",
"DeleteRemoteBranch": "刪除遠端分支",
"DeleteRemoteBranchMessage": "你確定要刪除遠端分支?",
"SetAsUpstreamTooltip": "將此分支設為當前分支之上游",
"SetUpstream": "設定所選分支之上游",
"UnsetUpstream": "取消設定選定分支之上游",
"SetUpstreamTitle": "設定上游分支",
"SetAsUpstream": "設置為遠端",
"SetAsUpstreamTooltip": "將此分支設為當前分支之遠端",
"SetUpstream": "設定選定分支的遠端分支",
"UnsetUpstream": "重置選定分支的遠端",
"ViewDivergenceFromUpstream": "檢視與遠端的差異",
"DivergenceSectionHeaderLocal": "本地",
"ViewUpstreamResetOptions": "重設當前分支進 {{.upstream}}",
"ViewUpstreamResetOptionsTooltip": "查看重設當前分支進 {{upstream}} 的選項。注意:此動作不會重置所選的遠端,而是將當前分支重置到遠端",
"ViewUpstreamRebaseOptions": "將當前分支變基到 {{.upstream}}",
"ViewUpstreamRebaseOptionsTooltip": "查看變基當前分支到 {{upstream}} 的選項。注意:此動作不會變基所選的遠端,而是將當前分支變基到遠端",
"UpstreamGenericName": "選定分支的選端",
"SetUpstreamTitle": "設定遠端分支",
"SetUpstreamMessage": "你確定要將 '{{. selected}}' 設為 '{{.checkedOut}}' 的上游分支?",
"EditRemoteTooltip": "編輯遠端",
"TagCommit": "打標籤到提交",
@@ -321,6 +376,8 @@
"TagMessageTitle": "標籤訊息",
"LightweightTag": "輕量標籤",
"AnnotatedTag": "附註標籤",
"DeleteLocalTag": "刪除本地標籤",
"DeleteRemoteTagPrompt": "確定要刪除遠端 {{.upstream}} 的標籤 '{{.tagName}}'?",
"PushTagTitle": "推送標籤 '{{.tagName}}' 至遠端:",
"PushTag": "推送標籤",
"NewTag": "建立標籤",
@@ -334,7 +391,7 @@
"IgnoreTracked": "忽略已追蹤檔案",
"ExcludeTracked": "排除已追蹤檔案",
"IgnoreTrackedPrompt": "你確定要忽略一個已追蹤的檔案?",
"ViewResetToUpstreamOptions": "檢視上游重設選項",
"ViewResetToUpstreamOptions": "檢視遠端重設選項",
"NextScreenMode": "下一個螢幕模式(常規/半螢幕/全螢幕)",
"PrevScreenMode": "上一個螢幕模式",
"StartSearch": "搜尋",
@@ -342,11 +399,15 @@
"Panel": "面板",
"Keybindings": "鍵盤快捷鍵",
"KeybindingsLegend": "說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B",
"KeybindingsMenuSectionLocal": "本地",
"KeybindingsMenuSectionGlobal": "全域",
"RenameBranch": "重新命名分支",
"BranchUpstreamOptionsTitle": "上游分支設定",
"ViewBranchUpstreamOptions": "檢視上游設定",
"ViewBranchUpstreamOptionsTooltip": "檢視有關上游分支的設定(例如重設至上游)",
"UpstreamNotSetError": "目標分支沒有上游分支(或其上游分支未儲存於本地)",
"Upstream": "遠端",
"UpstreamTooltip": "查看所選分支的遠端設定(例如設置/重置遠端)",
"BranchUpstreamOptionsTitle": "上游遠端設定",
"ViewBranchUpstreamOptions": "檢視遠端設定",
"ViewBranchUpstreamOptionsTooltip": "檢視有關遠端分支的設定(例如重設至遠端)",
"UpstreamNotSetError": "目標分支沒有遠端對應分支(或其遠端分支未儲存於本地)",
"NewGitFlowBranchPrompt": "{{.branchType}} 名稱:",
"RenameBranchWarning": "此分支正在追蹤遠端分支。此操作僅會重新命名本地分支名稱,而不是遠端分支的名稱。是否繼續?",
"OpenKeybindingsMenu": "開啟選單",
@@ -475,6 +536,11 @@
"LogMenuTitle": "提交記錄選項",
"ToggleShowGitGraphAll": "切換顯示整個 git 圖表(將 `--all` 標誌傳遞給 `git log`)",
"ShowGitGraph": "顯示 git 圖表",
"SortOrder": "排序規則",
"SortAlphabetical": "依字母",
"SortByDate": "依時間",
"SortByRecency": "依最近使用",
"SortBasedOnReflog": "(依據歷史記錄)",
"SortCommits": "提交排序順序",
"CantChangeContextSizeError": "在製作補丁期間無法更改上下文大小,因為當發布功能時我們太懒了以至於沒有支援它。如果你真的需要它,請告訴我們!",
"OpenCommitInBrowser": "在瀏覽器中開啟提交",
@@ -484,8 +550,8 @@
"RewordInEditorPrompt": "是否在編輯器中改寫此提交?",
"CheckoutPrompt": "是否檢出 '%s' ?",
"HardResetAutostashPrompt": "是否強制重設為 '%s' ?如果需要會進行自動存儲。",
"UpstreamGone": "(上游已經不存在)",
"NukeDescription": "如果你想讓所有工作樹上的變更消失,這就是要做的方式。如果有未提交的子模組變更,它將把這些變更藏在子模組中。",
"UpstreamGone": "(遠端已經不存在)",
"NukeDescription": "如果你想讓所有工作樹上的變更消失,這就是正確的選項。如果有未提交的子模組變更,它們將被收藏在子模組中。",
"DiscardStagedChangesDescription": "這將創建一個新的存儲條目,其中只包含預存檔案,然後如果存儲條目不需要,將其刪除,因此工作樹僅保留未預存的變更。",
"EmptyOutput": "<空輸出>",
"Patch": "補丁",
@@ -547,12 +613,14 @@
"PleaseGoToURL": "請開啟 URL:{{.url}}",
"NoCopiedCommits": "未複製提交",
"DisabledMenuItemPrefix": "已停用:",
"QuickStartInteractiveRebase": "開始互動變基",
"ToggleRangeSelect": "切換拖曳選擇",
"Actions": {
"CheckoutCommit": "檢出提交",
"CheckoutTag": "檢出標籤",
"CheckoutBranch": "檢出分支",
"ForceCheckoutBranch": "強制檢出分支",
"DeleteLocalBranch": "刪除本地分支",
"DeleteBranch": "刪除分支",
"Merge": "合併",
"RebaseBranch": "變基分支",
@@ -615,7 +683,7 @@
"MovePatchIntoIndex": "將補丁移動到索引中",
"MovePatchIntoNewCommit": "將補丁移動到新提交中",
"DeleteRemoteBranch": "刪除遠端分支",
"SetBranchUpstream": "設置分支上游",
"SetBranchUpstream": "設置遠端分支",
"AddRemote": "添加遠端",
"RemoveRemote": "移除遠端",
"UpdateRemote": "更新遠端",
@@ -633,12 +701,13 @@
"UpdateSubmodule": "更新子模塊",
"CreateLightweightTag": "建立輕量標籤",
"CreateAnnotatedTag": "建立附註標籤",
"DeleteLocalTag": "刪除本地標籤",
"PushTag": "推送標籤",
"NukeWorkingTree": "清空工作樹",
"DiscardUnstagedFileChanges": "放棄未預存的檔案更改",
"RemoveUntrackedFiles": "移除未追蹤的檔案",
"RemoveStagedFiles": "移除已預存的檔案",
"SoftReset": "軟重設",
"SoftReset": "軟重設(保留變動)",
"MixedReset": "混合重設",
"HardReset": "強制重設",
"Undo": "復原",
@@ -665,6 +734,8 @@
"CompletePromptIndeterminate": "二分查找完成!有一些提交被跳過,因此以下任何提交皆可能引進更改:\n\n%s\n\n是否重設 `git bisect`?",
"Bisecting": "二分查找中"
},
"Log": {},
"Log": {
"CopyToClipboard": "{{.str}} 已複製"
},
"BreakingChangesByVersion": {}
}
+150 -25
View File
@@ -15,27 +15,52 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
CloneIntoRemote("origin").
EmptyCommit("blah").
NewBranch("branch-one").
EmptyCommit("on branch-one 01").
PushBranchAndSetUpstream("origin", "branch-one").
EmptyCommit("on branch-one 02").
Checkout("master").
Merge("branch-one"). // branch-one is contained in master, so no delete confirmation
NewBranch("branch-two").
PushBranchAndSetUpstream("origin", "branch-two").
EmptyCommit("deletion blocker").
NewBranch("branch-three")
EmptyCommit("on branch-two 01").
PushBranchAndSetUpstream("origin", "branch-two"). // branch-two is contained in its own upstream, so no delete confirmation either
NewBranchFrom("branch-three", "master").
EmptyCommit("on branch-three 01").
NewBranch("current-head"). // branch-three is contained in the current head, so no delete confirmation
EmptyCommit("on current-head").
NewBranchFrom("branch-four", "master").
EmptyCommit("on branch-four 01").
PushBranchAndSetUpstream("origin", "branch-four").
EmptyCommit("on branch-four 02"). // branch-four is not contained in any of these, so we get a delete confirmation
NewBranchFrom("branch-five", "master").
EmptyCommit("on branch-five 01").
PushBranchAndSetUpstream("origin", "branch-five"). // branch-five is contained in its own upstream
NewBranchFrom("branch-six", "master").
EmptyCommit("on branch-six 01").
PushBranchAndSetUpstream("origin", "branch-six").
EmptyCommit("on branch-six 02"). // branch-six is not contained in any of these, so we get a delete confirmation
Checkout("current-head")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Branches().
Focus().
Lines(
MatchesRegexp(`\*.*branch-three`).IsSelected(),
MatchesRegexp(`branch-two`),
MatchesRegexp(`branch-one`),
MatchesRegexp(`master`),
Contains("current-head").IsSelected(),
Contains("branch-six ↑1"),
Contains("branch-five ✓"),
Contains("branch-four ↑1"),
Contains("branch-three"),
Contains("branch-two ✓"),
Contains("master"),
Contains("branch-one ↑1"),
).
// Deleting the current branch is not possible
Press(keys.Universal.Remove).
Tap(func() {
t.ExpectPopup().
Menu().
Tooltip(Contains("You cannot delete the checked out branch!")).
Title(Equals("Delete branch 'branch-three'?")).
Title(Equals("Delete branch 'current-head'?")).
Select(Contains("Delete local branch")).
Confirm().
Tap(func() {
@@ -43,7 +68,54 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
}).
Cancel()
}).
SelectNextItem().
// Delete branch-four. This is the only branch that is not fully merged, so we get
// a confirmation popup.
NavigateToLine(Contains("branch-four")).
Press(keys.Universal.Remove).
Tap(func() {
t.ExpectPopup().
Menu().
Title(Equals("Delete branch 'branch-four'?")).
Select(Contains("Delete local branch")).
Confirm()
t.ExpectPopup().
Confirmation().
Title(Equals("Force delete branch")).
Content(Equals("'branch-four' is not fully merged. Are you sure you want to delete it?")).
Confirm()
}).
Lines(
Contains("current-head"),
Contains("branch-six ↑1"),
Contains("branch-five ✓"),
Contains("branch-three").IsSelected(),
Contains("branch-two ✓"),
Contains("master"),
Contains("branch-one ↑1"),
).
// Delete branch-three. This branch is contained in the current head, so this just works
// without any confirmation.
Press(keys.Universal.Remove).
Tap(func() {
t.ExpectPopup().
Menu().
Title(Equals("Delete branch 'branch-three'?")).
Select(Contains("Delete local branch")).
Confirm()
}).
Lines(
Contains("current-head"),
Contains("branch-six ↑1"),
Contains("branch-five ✓"),
Contains("branch-two ✓").IsSelected(),
Contains("master"),
Contains("branch-one ↑1"),
).
// Delete branch-two. This branch is contained in its own upstream, so this just works
// without any confirmation.
Press(keys.Universal.Remove).
Tap(func() {
t.ExpectPopup().
@@ -52,18 +124,16 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
Select(Contains("Delete local branch")).
Confirm()
}).
Tap(func() {
t.ExpectPopup().
Confirmation().
Title(Equals("Force delete branch")).
Content(Equals("'branch-two' is not fully merged. Are you sure you want to delete it?")).
Confirm()
}).
Lines(
MatchesRegexp(`\*.*branch-three`),
MatchesRegexp(`branch-one`).IsSelected(),
MatchesRegexp(`master`),
Contains("current-head"),
Contains("branch-six ↑1"),
Contains("branch-five ✓"),
Contains("master").IsSelected(),
Contains("branch-one ↑1"),
).
// Delete remote branch of branch-one. We only get the normal remote branch confirmation for this one.
SelectNextItem().
Press(keys.Universal.Remove).
Tap(func() {
t.ExpectPopup().
@@ -87,7 +157,12 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
t.Views().
RemoteBranches().
Lines(Equals("branch-two")).
Lines(
Equals("branch-five"),
Equals("branch-four"),
Equals("branch-six"),
Equals("branch-two"),
).
Press(keys.Universal.Return)
t.Views().
@@ -95,10 +170,15 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
Focus()
}).
Lines(
MatchesRegexp(`\*.*branch-three`),
MatchesRegexp(`branch-one \(upstream gone\)`).IsSelected(),
MatchesRegexp(`master`),
Contains("current-head"),
Contains("branch-six ↑1"),
Contains("branch-five ✓"),
Contains("master"),
Contains("branch-one (upstream gone)").IsSelected(),
).
// Delete local branch of branch-one. Even though its upstream is gone, we don't get a confirmation
// because it is contained in master.
Press(keys.Universal.Remove).
Tap(func() {
t.ExpectPopup().
@@ -108,8 +188,53 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
Confirm()
}).
Lines(
MatchesRegexp(`\*.*branch-three`),
MatchesRegexp(`master`).IsSelected(),
Contains("current-head"),
Contains("branch-six ↑1"),
Contains("branch-five ✓"),
Contains("master").IsSelected(),
).
// Delete both local and remote branch of branch-six. We get the force-delete warning because it is not fully merged.
NavigateToLine(Contains("branch-six")).
Press(keys.Universal.Remove).
Tap(func() {
t.ExpectPopup().
Menu().
Title(Equals("Delete branch 'branch-six'?")).
Select(Contains("Delete local and remote branch")).
Confirm()
t.ExpectPopup().
Confirmation().
Title(Equals("Delete local and remote branch")).
Content(Contains("Are you sure you want to delete both 'branch-six' from your machine, and 'branch-six' from 'origin'?").
Contains("'branch-six' is not fully merged. Are you sure you want to delete it?")).
Confirm()
}).
Lines(
Contains("current-head"),
Contains("branch-five ✓").IsSelected(),
Contains("master"),
).
// Delete both local and remote branch of branch-five. We get the same popups, but the confirmation
// doesn't contain the force-delete warning.
Press(keys.Universal.Remove).
Tap(func() {
t.ExpectPopup().
Menu().
Title(Equals("Delete branch 'branch-five'?")).
Select(Contains("Delete local and remote branch")).
Confirm()
t.ExpectPopup().
Confirmation().
Title(Equals("Delete local and remote branch")).
Content(Equals("Are you sure you want to delete both 'branch-five' from your machine, and 'branch-five' from 'origin'?").
DoesNotContain("not fully merged")).
Confirm()
}).
Lines(
Contains("current-head"),
Contains("master").IsSelected(),
)
},
})
@@ -0,0 +1,49 @@
package branch
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var DeleteRemoteBranchWithDifferentName = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Delete a remote branch that has a different name than the local branch",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
},
SetupRepo: func(shell *Shell) {
shell.EmptyCommit("one")
shell.CloneIntoRemote("origin")
shell.NewBranch("mybranch-local")
shell.PushBranchAndSetUpstream("origin", "mybranch-local:mybranch-remote")
shell.Checkout("master")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Branches().
Focus().
Lines(
Contains("master").IsSelected(),
Contains("mybranch-local ✓"),
).
SelectNextItem().
Press(keys.Universal.Remove).
Tap(func() {
t.ExpectPopup().
Menu().
Title(Equals("Delete branch 'mybranch-local'?")).
Select(Contains("Delete remote branch")).
Confirm()
}).
Tap(func() {
t.ExpectPopup().
Confirmation().
Title(Equals("Delete branch 'mybranch-remote'?")).
Content(Equals("Are you sure you want to delete the remote branch 'mybranch-remote' from 'origin'?")).
Confirm()
}).
Lines(
Contains("master"),
Contains("mybranch-local (upstream gone)").IsSelected(),
)
},
})
@@ -0,0 +1,54 @@
package branch
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var OpenPullRequestInvalidTargetRemoteName = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Open up a pull request, specifying a non-existing target remote",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
// Create an initial commit ('git branch set-upstream-to' bails out otherwise)
shell.CreateFileAndAdd("file", "content1")
shell.Commit("one")
// Create a new branch
shell.NewBranch("branch-1")
// Create a couple of remotes
shell.CloneIntoRemote("upstream")
shell.CloneIntoRemote("origin")
// To allow a pull request to be created from a branch, it must have an upstream set.
shell.SetBranchUpstream("branch-1", "origin/branch-1")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
// Open a PR for the current branch (i.e. 'branch-1')
t.Views().
Branches().
Focus().
Press(keys.Branches.ViewPullRequestOptions)
t.ExpectPopup().
Menu().
Title(Equals("View create pull request options")).
Select(Contains("Select branch")).
Confirm()
// Verify that we're prompted to enter the remote and enter the name of a non-existing one.
t.ExpectPopup().
Prompt().
Title(Equals("Select target remote")).
Type("non-existing-remote").
Confirm()
// Verify that this leads to an error being shown (instead of progressing to branch selection).
t.ExpectPopup().Alert().
Title(Equals("Error")).
Content(Contains("A remote named 'non-existing-remote' does not exist")).
Confirm()
},
})
@@ -0,0 +1,74 @@
package branch
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var OpenPullRequestSelectRemoteAndTargetBranch = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Open up a pull request, specifying a remote and target branch",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
config.GetUserConfig().OS.OpenLink = "echo {{link}} > /tmp/openlink"
},
SetupRepo: func(shell *Shell) {
// Create an initial commit ('git branch set-upstream-to' bails out otherwise)
shell.CreateFileAndAdd("file", "content1")
shell.Commit("one")
// Create a new branch and a remote that has that branch
shell.NewBranch("branch-1")
shell.CloneIntoRemote("upstream")
// Create another branch and a second remote. The first remote doesn't have this branch.
shell.NewBranch("branch-2")
shell.CloneIntoRemote("origin")
// To allow a pull request to be created from a branch, it must have an upstream set.
shell.SetBranchUpstream("branch-2", "origin/branch-2")
shell.RunCommand([]string{"git", "remote", "set-url", "origin", "https://github.com/my-personal-fork/lazygit"})
shell.RunCommand([]string{"git", "remote", "set-url", "upstream", "https://github.com/jesseduffield/lazygit"})
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
// Open a PR for the current branch (i.e. 'branch-2')
t.Views().
Branches().
Focus().
Press(keys.Branches.ViewPullRequestOptions)
t.ExpectPopup().
Menu().
Title(Equals("View create pull request options")).
Select(Contains("Select branch")).
Confirm()
// Verify that we're prompted to enter the remote
t.ExpectPopup().
Prompt().
Title(Equals("Select target remote")).
SuggestionLines(
Equals("origin"),
Equals("upstream")).
ConfirmSuggestion(Equals("upstream"))
// Verify that we're prompted to enter the target branch and that only those branches
// present in the selected remote are listed as suggestions (i.e. 'branch-2' is not there).
t.ExpectPopup().
Prompt().
Title(Equals("branch-2 → upstream/")).
SuggestionLines(
Equals("branch-1"),
Equals("master")).
ConfirmSuggestion(Equals("master"))
// Verify that the expected URL is used (by checking the openlink file)
//
// Please note that when targeting a different remote - like it's done here in this test -
// the link is not yet correct. Thus, this test is expected to fail once this is fixed.
t.FileSystem().FileContent(
"/tmp/openlink",
Equals("https://github.com/my-personal-fork/lazygit/compare/master...branch-2?expand=1\n"))
},
})
@@ -0,0 +1,34 @@
package branch
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var ShowDivergenceFromUpstreamNoDivergence = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Show divergence from upstream when the divergence view is empty",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.EmptyCommit("commit1")
shell.CloneIntoRemote("origin")
shell.SetBranchUpstream("master", "origin/master")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Branches().
Focus().
Lines(Contains("master")).
Press(keys.Branches.SetUpstream)
t.ExpectPopup().Menu().Title(Contains("Upstream")).Select(Contains("View divergence from upstream")).Confirm()
t.Views().SubCommits().
IsFocused().
Title(Contains("Commits (master <-> origin/master)")).
Lines(
Contains("--- Remote ---"),
Contains("--- Local ---"),
)
},
})
@@ -79,5 +79,32 @@ var CherryPick = NewIntegrationTest(NewIntegrationTestArgs{
Contains("one"),
Contains("base"),
)
// Even though the cherry-picking mode has been reset, it's still possible to paste the copied commits again:
t.Views().Branches().
Focus().
NavigateToLine(Contains("master")).
PressPrimaryAction()
t.Views().Commits().
Focus().
Lines(
Contains("base").IsSelected(),
).
Press(keys.Commits.PasteCommits).
Tap(func() {
t.ExpectPopup().Alert().
Title(Equals("Cherry-pick")).
Content(Contains("Are you sure you want to cherry-pick the copied commits onto this branch?")).
Confirm()
}).
Tap(func() {
t.Views().Information().Content(DoesNotContain("commits copied"))
}).
Lines(
Contains("four"),
Contains("three"),
Contains("base"),
)
},
})
@@ -0,0 +1,48 @@
package commit
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
// We're emulating the clipboard by writing to a file called clipboard
var CopyAuthorToClipboard = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Copy a commit author name to the clipboard",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
// Include delimiters around the text so that we can assert on the entire content
config.GetUserConfig().OS.CopyToClipboardCmd = "echo /{{text}}/ > clipboard"
},
SetupRepo: func(shell *Shell) {
shell.SetAuthor("John Doe", "john@doe.com")
shell.EmptyCommit("commit")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Lines(
Contains("commit").IsSelected(),
).
Press(keys.Commits.CopyCommitAttributeToClipboard)
t.ExpectPopup().Menu().
Title(Equals("Copy to clipboard")).
Select(Contains("Commit author")).
Confirm()
t.ExpectToast(Equals("Commit author copied to clipboard"))
t.Views().Files().
Focus().
Press(keys.Files.RefreshFiles).
Lines(
Contains("clipboard").IsSelected(),
)
t.Views().Main().Content(Contains("/John Doe <john@doe.com>/"))
},
})
@@ -0,0 +1,53 @@
package commit
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var CreateFixupCommitInBranchStack = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Create a fixup commit in a stack of branches, verify that it is created at the end of the branch it belongs to",
ExtraCmdArgs: []string{},
Skip: false,
GitVersion: AtLeast("2.38.0"),
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.NewBranch("branch1")
shell.EmptyCommit("branch1 commit 1")
shell.EmptyCommit("branch1 commit 2")
shell.EmptyCommit("branch1 commit 3")
shell.NewBranch("branch2")
shell.EmptyCommit("branch2 commit 1")
shell.EmptyCommit("branch2 commit 2")
shell.CreateFileAndAdd("fixup-file", "fixup content")
shell.SetConfig("rebase.updateRefs", "true")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Lines(
Contains("CI ◯ branch2 commit 2"),
Contains("CI ◯ branch2 commit 1"),
Contains("CI ◯ * branch1 commit 3"),
Contains("CI ◯ branch1 commit 2"),
Contains("CI ◯ branch1 commit 1"),
).
NavigateToLine(Contains("branch1 commit 2")).
Press(keys.Commits.CreateFixupCommit).
Tap(func() {
t.ExpectPopup().Menu().
Title(Equals("Create fixup commit")).
Select(Contains("fixup! commit")).
Confirm()
}).
Lines(
Contains("CI ◯ branch2 commit 2"),
Contains("CI ◯ branch2 commit 1"),
Contains("CI ◯ * fixup! branch1 commit 2"),
Contains("CI ◯ branch1 commit 3"),
Contains("CI ◯ branch1 commit 2"),
Contains("CI ◯ branch1 commit 1"),
)
},
})
@@ -0,0 +1,57 @@
package conflicts
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var ResolveWithoutTrailingLf = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Regression test for resolving a merge conflict when the file doesn't have a trailing newline",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.
NewBranch("branch1").
CreateFileAndAdd("file", "a\n\nno eol").
Commit("initial commit").
UpdateFileAndAdd("file", "a1\n\nno eol").
Commit("commit on branch1").
NewBranchFrom("branch2", "HEAD^").
UpdateFileAndAdd("file", "a2\n\nno eol").
Commit("commit on branch2").
Checkout("branch1").
RunCommandExpectError([]string{"git", "merge", "--no-edit", "branch2"})
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Files().
IsFocused().
Lines(
Contains("UU file").IsSelected(),
).
PressEnter()
t.Views().MergeConflicts().
IsFocused().
SelectedLines(
Contains("<<<<<<< HEAD"),
Contains("a1"),
Contains("======="),
).
SelectNextItem().
PressPrimaryAction()
t.ExpectPopup().Alert().
Title(Equals("Continue")).
Content(Contains("All merge conflicts resolved. Continue?")).
Cancel()
t.Views().Files().
Focus().
Lines(
Contains("M file").IsSelected(),
)
t.Views().Main().Content(Contains("-a1\n+a2\n").DoesNotContain("-no eol"))
},
})
@@ -38,7 +38,6 @@ var DropTodoCommitWithUpdateRef = NewIntegrationTest(NewIntegrationTestArgs{
).
NavigateToLine(Contains("commit 02")).
Press(keys.Universal.Edit).
Focus().
Lines(
Contains("pick").Contains("CI commit 07"),
Contains("pick").Contains("CI commit 06"),
@@ -0,0 +1,55 @@
package interactive_rebase
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var EditAndAutoAmend = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Edit a commit, make a change and stage it, then continue the rebase to auto-amend the commit",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shell.
CreateNCommits(3)
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Lines(
Contains("commit 03"),
Contains("commit 02"),
Contains("commit 01"),
).
NavigateToLine(Contains("commit 02")).
Press(keys.Universal.Edit).
Lines(
Contains("commit 03"),
MatchesRegexp("YOU ARE HERE.*commit 02").IsSelected(),
Contains("commit 01"),
)
t.Shell().CreateFile("fixup-file", "fixup content")
t.Views().Files().
Focus().
Press(keys.Files.RefreshFiles).
Lines(
Contains("??").Contains("fixup-file").IsSelected(),
).
PressPrimaryAction()
t.Common().ContinueRebase()
t.Views().Commits().
Focus().
Lines(
Contains("commit 03"),
Contains("commit 02").IsSelected(),
Contains("commit 01"),
)
t.Views().Main().
Content(Contains("fixup content"))
},
})
@@ -0,0 +1,74 @@
package interactive_rebase
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var EditLastCommitOfStackedBranch = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Edit and amend the last commit of a branch in a stack of branches, and ensure that it doesn't break the stack",
ExtraCmdArgs: []string{},
Skip: false,
GitVersion: AtLeast("2.38.0"),
SetupConfig: func(config *config.AppConfig) {
config.GetUserConfig().Git.MainBranches = []string{"master"}
config.GetAppState().GitLogShowGraph = "never"
},
SetupRepo: func(shell *Shell) {
shell.
CreateNCommits(1).
NewBranch("branch1").
CreateNCommitsStartingAt(2, 2).
NewBranch("branch2").
CreateNCommitsStartingAt(2, 4)
shell.SetConfig("rebase.updateRefs", "true")
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
Lines(
Contains("CI commit 05").IsSelected(),
Contains("CI commit 04"),
Contains("CI * commit 03"),
Contains("CI commit 02"),
Contains("CI commit 01"),
).
NavigateToLine(Contains("commit 03")).
Press(keys.Universal.Edit).
Lines(
Contains("pick").Contains("CI commit 05"),
Contains("pick").Contains("CI commit 04"),
Contains("update-ref").Contains("branch1"),
Contains("<-- YOU ARE HERE --- * commit 03").IsSelected(),
Contains("CI commit 02"),
Contains("CI commit 01"),
)
t.Shell().CreateFile("fixup-file", "fixup content")
t.Views().Files().
Focus().
Press(keys.Files.RefreshFiles).
Lines(
Contains("??").Contains("fixup-file").IsSelected(),
).
PressPrimaryAction().
Press(keys.Files.AmendLastCommit)
t.ExpectPopup().Confirmation().
Title(Equals("Amend last commit")).
Content(Contains("Are you sure you want to amend last commit?")).
Confirm()
t.Common().ContinueRebase()
t.Views().Commits().
Focus().
Lines(
Contains("CI commit 05"),
Contains("CI commit 04"),
Contains("CI * commit 03"),
Contains("CI commit 02"),
Contains("CI commit 01"),
)
},
})
@@ -0,0 +1,43 @@
package interactive_rebase
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
"github.com/jesseduffield/lazygit/pkg/integration/tests/shared"
)
var EditRangeSelectDownToMergeOutsideRebase = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Select a range of commits (the last one being a merge commit) to edit outside of a rebase",
ExtraCmdArgs: []string{},
Skip: false,
GitVersion: AtLeast("2.22.0"), // first version that supports the --rebase-merges option
SetupConfig: func(config *config.AppConfig) {},
SetupRepo: func(shell *Shell) {
shared.CreateMergeCommit(shell)
shell.CreateNCommits(2)
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Commits().
Focus().
TopLines(
Contains("CI ◯ commit 02").IsSelected(),
Contains("CI ◯ commit 01"),
Contains("Merge branch 'second-change-branch' into first-change-branch"),
).
Press(keys.Universal.RangeSelectDown).
Press(keys.Universal.RangeSelectDown).
Press(keys.Universal.Edit).
Lines(
Contains("edit CI commit 02").IsSelected(),
Contains("edit CI commit 01").IsSelected(),
Contains(" CI ⏣─╮ <-- YOU ARE HERE --- Merge branch 'second-change-branch' into first-change-branch").IsSelected(),
Contains(" CI │ ◯ * second-change-branch unrelated change"),
Contains(" CI │ ◯ second change"),
Contains(" CI ◯ │ first change"),
Contains(" CI ◯─╯ * original"),
Contains(" CI ◯ three"),
Contains(" CI ◯ two"),
Contains(" CI ◯ one"),
)
},
})
@@ -127,6 +127,26 @@ var DiffContextChange = NewIntegrationTest(NewIntegrationTestArgs{
Contains(`+3b`),
Contains(` 4a`),
).
Press(keys.Universal.DecreaseContextInDiffView).
Tap(func() {
t.ExpectToast(Equals("Changed diff context size to 0"))
}).
SelectedLines(
Contains(`@@ -3,1 +3 @@`),
Contains(`-3a`),
Contains(`+3b`),
).
Press(keys.Universal.IncreaseContextInDiffView).
Tap(func() {
t.ExpectToast(Equals("Changed diff context size to 1"))
}).
SelectedLines(
Contains(`@@ -2,3 +2,3 @@`),
Contains(` 2a`),
Contains(`-3a`),
Contains(`+3b`),
Contains(` 4a`),
).
Press(keys.Universal.IncreaseContextInDiffView).
Tap(func() {
t.ExpectToast(Equals("Changed diff context size to 2"))
+11
View File
@@ -43,11 +43,14 @@ var tests = []*components.IntegrationTest{
branch.CreateTag,
branch.Delete,
branch.DeleteRemoteBranchWithCredentialPrompt,
branch.DeleteRemoteBranchWithDifferentName,
branch.DetachedHead,
branch.NewBranchAutostash,
branch.NewBranchFromRemoteTrackingDifferentName,
branch.NewBranchFromRemoteTrackingSameName,
branch.OpenPullRequestInvalidTargetRemoteName,
branch.OpenPullRequestNoUpstream,
branch.OpenPullRequestSelectRemoteAndTargetBranch,
branch.OpenWithCliArg,
branch.Rebase,
branch.RebaseAbortOnConflict,
@@ -65,6 +68,7 @@ var tests = []*components.IntegrationTest{
branch.SetUpstream,
branch.ShowDivergenceFromBaseBranch,
branch.ShowDivergenceFromUpstream,
branch.ShowDivergenceFromUpstreamNoDivergence,
branch.SortLocalBranches,
branch.SortRemoteBranches,
branch.SquashMerge,
@@ -86,7 +90,9 @@ var tests = []*components.IntegrationTest{
commit.CommitWithGlobalPrefix,
commit.CommitWithNonMatchingBranchName,
commit.CommitWithPrefix,
commit.CopyAuthorToClipboard,
commit.CreateAmendCommit,
commit.CreateFixupCommitInBranchStack,
commit.CreateTag,
commit.DiscardOldFileChanges,
commit.FindBaseCommitForFixup,
@@ -119,6 +125,7 @@ var tests = []*components.IntegrationTest{
conflicts.ResolveExternally,
conflicts.ResolveMultipleFiles,
conflicts.ResolveNoAutoStage,
conflicts.ResolveWithoutTrailingLf,
conflicts.UndoChooseHunk,
custom_commands.AccessCommitProperties,
custom_commands.BasicCommand,
@@ -202,8 +209,11 @@ var tests = []*components.IntegrationTest{
interactive_rebase.DropCommitInCopiedBranchWithUpdateRef,
interactive_rebase.DropTodoCommitWithUpdateRef,
interactive_rebase.DropWithCustomCommentChar,
interactive_rebase.EditAndAutoAmend,
interactive_rebase.EditFirstCommit,
interactive_rebase.EditLastCommitOfStackedBranch,
interactive_rebase.EditNonTodoCommitDuringRebase,
interactive_rebase.EditRangeSelectDownToMergeOutsideRebase,
interactive_rebase.EditRangeSelectOutsideRebase,
interactive_rebase.EditTheConflCommit,
interactive_rebase.FixupFirstCommit,
@@ -341,6 +351,7 @@ var tests = []*components.IntegrationTest{
tag.ForceTagLightweight,
tag.Reset,
ui.Accordion,
ui.DisableSwitchTabWithPanelJumpKeys,
ui.DoublePopup,
ui.EmptyMenu,
ui.KeybindingSuggestionsWhenSwitchingRepos,
@@ -0,0 +1,26 @@
package ui
import (
"github.com/jesseduffield/lazygit/pkg/config"
. "github.com/jesseduffield/lazygit/pkg/integration/components"
)
var DisableSwitchTabWithPanelJumpKeys = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Verify that the tab does not change by default when jumping to an already focused panel",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {
},
SetupRepo: func(shell *Shell) {
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
t.Views().Status().Focus().
Press(keys.Universal.JumpToBlock[1])
t.Views().Files().IsFocused().
Press(keys.Universal.JumpToBlock[1])
// Despite jumping to an already focused panel,
// the tab should not change from the base files view
t.Views().Files().IsFocused()
},
})
+15 -3
View File
@@ -50,7 +50,7 @@ var RangeSelect = NewIntegrationTest(NewIntegrationTestArgs{
shell.CreateFile("file1", fileContent)
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
assertRangeSelectBehaviour := func(v *ViewDriver) {
assertRangeSelectBehaviour := func(v *ViewDriver, otherView *ViewDriver, lineIdxOfFirstItem int) {
v.
SelectedLines(
Contains("line 1"),
@@ -152,9 +152,21 @@ var RangeSelect = NewIntegrationTest(NewIntegrationTestArgs{
SelectedLines(
Contains("line 10"),
)
// Click in view, press shift+arrow -> nonsticky range
otherView.Focus()
v.Click(1, lineIdxOfFirstItem).
SelectedLines(
Contains("line 1"),
).
Press(keys.Universal.RangeSelectDown).
SelectedLines(
Contains("line 1"),
Contains("line 2"),
)
}
assertRangeSelectBehaviour(t.Views().Commits().Focus())
assertRangeSelectBehaviour(t.Views().Commits().Focus(), t.Views().Branches(), 0)
t.Views().Files().
Focus().
@@ -163,6 +175,6 @@ var RangeSelect = NewIntegrationTest(NewIntegrationTestArgs{
).
PressEnter()
assertRangeSelectBehaviour(t.Views().Staging().IsFocused())
assertRangeSelectBehaviour(t.Views().Staging().IsFocused(), t.Views().Files(), 6)
},
})
@@ -6,10 +6,12 @@ import (
)
var SwitchTabWithPanelJumpKeys = NewIntegrationTest(NewIntegrationTestArgs{
Description: "Switch tab with the panel jump keys",
Description: "Switch tab with the panel jump keys after enabling the feature",
ExtraCmdArgs: []string{},
Skip: false,
SetupConfig: func(config *config.AppConfig) {},
SetupConfig: func(config *config.AppConfig) {
config.GetUserConfig().Gui.SwitchTabsWithPanelJumpKeys = true
},
SetupRepo: func(shell *Shell) {
},
Run: func(t *TestDriver, keys config.KeybindingConfig) {
+10 -5
View File
@@ -2,6 +2,7 @@ package utils
import (
"bufio"
"io"
"os"
)
@@ -12,14 +13,18 @@ func ForEachLineInFile(path string, f func(string, int)) error {
}
defer file.Close()
reader := bufio.NewReader(file)
forEachLineInStream(file, f)
return nil
}
func forEachLineInStream(reader io.Reader, f func(string, int)) {
bufferedReader := bufio.NewReader(reader)
for i := 0; true; i++ {
line, err := reader.ReadString('\n')
if err != nil {
line, _ := bufferedReader.ReadString('\n')
if len(line) == 0 {
break
}
f(line, i)
}
return nil
}
+57
View File
@@ -0,0 +1,57 @@
package utils
import (
"strings"
"testing"
"github.com/stretchr/testify/assert"
)
func Test_forEachLineInStream(t *testing.T) {
scenarios := []struct {
name string
input string
expectedLines []string
}{
{
name: "empty input",
input: "",
expectedLines: []string{},
},
{
name: "single line",
input: "abc\n",
expectedLines: []string{"abc\n"},
},
{
name: "single line without line feed",
input: "abc",
expectedLines: []string{"abc"},
},
{
name: "multiple lines",
input: "abc\ndef\n",
expectedLines: []string{"abc\n", "def\n"},
},
{
name: "multiple lines including empty lines",
input: "abc\n\ndef\n",
expectedLines: []string{"abc\n", "\n", "def\n"},
},
{
name: "multiple lines without linefeed at end of file",
input: "abc\ndef\nghi",
expectedLines: []string{"abc\n", "def\n", "ghi"},
},
}
for _, s := range scenarios {
t.Run(s.name, func(t *testing.T) {
lines := []string{}
forEachLineInStream(strings.NewReader(s.input), func(line string, i int) {
lines = append(lines, line)
})
assert.EqualValues(t, s.expectedLines, lines)
})
}
}
+6 -4
View File
@@ -221,13 +221,13 @@ func moveTodosUp(todos []todo.Todo, todosToMove []Todo) ([]todo.Todo, error) {
return todos, nil
}
func MoveFixupCommitDown(fileName string, originalHash string, fixupHash string, commentChar byte) error {
func MoveFixupCommitDown(fileName string, originalHash string, fixupHash string, changeToFixup bool, commentChar byte) error {
todos, err := ReadRebaseTodoFile(fileName, commentChar)
if err != nil {
return err
}
newTodos, err := moveFixupCommitDown(todos, originalHash, fixupHash)
newTodos, err := moveFixupCommitDown(todos, originalHash, fixupHash, changeToFixup)
if err != nil {
return err
}
@@ -235,7 +235,7 @@ func MoveFixupCommitDown(fileName string, originalHash string, fixupHash string,
return WriteRebaseTodoFile(fileName, newTodos, commentChar)
}
func moveFixupCommitDown(todos []todo.Todo, originalHash string, fixupHash string) ([]todo.Todo, error) {
func moveFixupCommitDown(todos []todo.Todo, originalHash string, fixupHash string, changeToFixup bool) ([]todo.Todo, error) {
isOriginal := func(t todo.Todo) bool {
return (t.Command == todo.Pick || t.Command == todo.Merge) && equalHash(t.Commit, originalHash)
}
@@ -259,7 +259,9 @@ func moveFixupCommitDown(todos []todo.Todo, originalHash string, fixupHash strin
newTodos := MoveElement(todos, fixupIndex, originalIndex+1)
newTodos[originalIndex+1].Command = todo.Fixup
if changeToFixup {
newTodos[originalIndex+1].Command = todo.Fixup
}
return newTodos, nil
}
+31 -8
View File
@@ -266,23 +266,40 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
todos []todo.Todo
originalHash string
fixupHash string
changeToFixup bool
expectedTodos []todo.Todo
expectedErr error
}{
{
name: "fixup commit is the last commit",
name: "fixup commit is the last commit (change to fixup)",
todos: []todo.Todo{
{Command: todo.Pick, Commit: "original"},
{Command: todo.Pick, Commit: "fixup"},
},
originalHash: "original",
fixupHash: "fixup",
originalHash: "original",
fixupHash: "fixup",
changeToFixup: true,
expectedTodos: []todo.Todo{
{Command: todo.Pick, Commit: "original"},
{Command: todo.Fixup, Commit: "fixup"},
},
expectedErr: nil,
},
{
name: "fixup commit is the last commit (don't change to fixup)",
todos: []todo.Todo{
{Command: todo.Pick, Commit: "original"},
{Command: todo.Pick, Commit: "fixup"},
},
originalHash: "original",
fixupHash: "fixup",
changeToFixup: false,
expectedTodos: []todo.Todo{
{Command: todo.Pick, Commit: "original"},
{Command: todo.Pick, Commit: "fixup"},
},
expectedErr: nil,
},
{
name: "fixup commit is separated from original commit",
todos: []todo.Todo{
@@ -290,8 +307,9 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
{Command: todo.Pick, Commit: "other"},
{Command: todo.Pick, Commit: "fixup"},
},
originalHash: "original",
fixupHash: "fixup",
originalHash: "original",
fixupHash: "fixup",
changeToFixup: true,
expectedTodos: []todo.Todo{
{Command: todo.Pick, Commit: "original"},
{Command: todo.Fixup, Commit: "fixup"},
@@ -306,8 +324,9 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
{Command: todo.Pick, Commit: "other"},
{Command: todo.Pick, Commit: "fixup"},
},
originalHash: "original",
fixupHash: "fixup",
originalHash: "original",
fixupHash: "fixup",
changeToFixup: true,
expectedTodos: []todo.Todo{
{Command: todo.Merge, Commit: "original"},
{Command: todo.Fixup, Commit: "fixup"},
@@ -324,6 +343,7 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
},
originalHash: "original",
fixupHash: "fixup",
changeToFixup: true,
expectedTodos: nil,
expectedErr: errors.New("Expected exactly one original hash, found 2"),
},
@@ -336,6 +356,7 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
},
originalHash: "original",
fixupHash: "fixup",
changeToFixup: true,
expectedTodos: nil,
expectedErr: errors.New("Expected exactly one fixup hash, found 2"),
},
@@ -346,6 +367,7 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
},
originalHash: "original",
fixupHash: "fixup",
changeToFixup: true,
expectedTodos: nil,
expectedErr: errors.New("Expected exactly one fixup hash, found 0"),
},
@@ -356,6 +378,7 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
},
originalHash: "original",
fixupHash: "fixup",
changeToFixup: true,
expectedTodos: nil,
expectedErr: errors.New("Expected exactly one original hash, found 0"),
},
@@ -363,7 +386,7 @@ func TestRebaseCommands_moveFixupCommitDown(t *testing.T) {
for _, scenario := range scenarios {
t.Run(scenario.name, func(t *testing.T) {
actualTodos, actualErr := moveFixupCommitDown(scenario.todos, scenario.originalHash, scenario.fixupHash)
actualTodos, actualErr := moveFixupCommitDown(scenario.todos, scenario.originalHash, scenario.fixupHash, scenario.changeToFixup)
if scenario.expectedErr == nil {
assert.NoError(t, actualErr)
+15
View File
@@ -452,6 +452,21 @@
],
"description": "Status panel view.\nOne of 'dashboard' (default) | 'allBranchesLog'",
"default": "dashboard"
},
"switchToFilesAfterStashPop": {
"type": "boolean",
"description": "If true, jump to the Files panel after popping a stash",
"default": true
},
"switchToFilesAfterStashApply": {
"type": "boolean",
"description": "If true, jump to the Files panel after applying a stash",
"default": true
},
"switchTabsWithPanelJumpKeys": {
"type": "boolean",
"description": "If true, when using the panel jump keys (default 1 through 5) and target panel is already active, go to next tab instead",
"default": false
}
},
"additionalProperties": false,
+61
View File
@@ -183,6 +183,10 @@ type View struct {
// if true, the user can scroll all the way past the last item until it appears at the top of the view
CanScrollPastBottom bool
// if true, the view will automatically recognize https: URLs in the content written to it and render
// them as hyperlinks
AutoRenderHyperLinks bool
// if true, the view will underline hyperlinks only when the cursor is on
// them; otherwise, they will always be underlined
UnderlineHyperLinksOnlyOnHover bool
@@ -780,6 +784,7 @@ func (v *View) writeRunes(p []rune) {
for _, r := range p {
switch r {
case '\n':
v.autoRenderHyperlinksInCurrentLine()
if c, ok := v.readCell(v.wx+1, v.wy); !ok || c.chr == 0 {
v.writeCells(v.wx, v.wy, []cell{{
chr: 0,
@@ -793,6 +798,7 @@ func (v *View) writeRunes(p []rune) {
v.lines = append(v.lines, nil)
}
case '\r':
v.autoRenderHyperlinksInCurrentLine()
if c, ok := v.readCell(v.wx, v.wy); !ok || c.chr == 0 {
v.writeCells(v.wx, v.wy, []cell{{
chr: 0,
@@ -829,6 +835,61 @@ func (v *View) writeString(s string) {
v.writeRunes([]rune(s))
}
func findSubstring(line []cell, substringToFind []rune) int {
for i := 0; i < len(line)-len(substringToFind); i++ {
for j := 0; j < len(substringToFind); j++ {
if line[i+j].chr != substringToFind[j] {
break
}
if j == len(substringToFind)-1 {
return i
}
}
}
return -1
}
func (v *View) autoRenderHyperlinksInCurrentLine() {
if !v.AutoRenderHyperLinks {
return
}
// We need a heuristic to find the end of a hyperlink. Searching for the
// first character that is not a valid URI character is not quite good
// enough, because in markdown it's common to have a hyperlink followed by a
// ')', so we want to stop there. Hopefully URLs containing ')' are uncommon
// enough that this is not a problem.
lineEndCharacters := map[rune]bool{
'\000': true,
' ': true,
'\n': true,
'>': true,
'"': true,
')': true,
}
line := v.lines[v.wy]
start := 0
for {
linkStart := findSubstring(line[start:], []rune("https://"))
if linkStart == -1 {
break
}
linkStart += start
link := ""
linkEnd := linkStart
for ; linkEnd < len(line); linkEnd++ {
if _, ok := lineEndCharacters[line[linkEnd].chr]; ok {
break
}
link += string(line[linkEnd].chr)
}
for i := linkStart; i < linkEnd; i++ {
v.lines[v.wy][i].hyperlink = link
}
start = linkEnd
}
}
// parseInput parses char by char the input written to the View. It returns nil
// while processing ESC sequences. Otherwise, it returns a cell slice that
// contains the processed data.
+1 -1
View File
@@ -172,7 +172,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem
github.com/jesseduffield/go-git/v5/utils/merkletrie/index
github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
# github.com/jesseduffield/gocui v0.3.1-0.20240906064314-bfab49c720d7
# github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f
## explicit; go 1.12
github.com/jesseduffield/gocui
# github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10