Compare commits
242 Commits
v0.43.0
...
focus-main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cbd23107ef | ||
|
|
50426cda3a | ||
|
|
e031f437e9 | ||
|
|
9bd212aab1 | ||
|
|
9df81067e9 | ||
|
|
f31037864e | ||
|
|
03e060d82c | ||
|
|
f88b1942ae | ||
|
|
2e05ea57dc | ||
|
|
052974be65 | ||
|
|
85523402d6 | ||
|
|
f473d23d65 | ||
|
|
59a937ee7a | ||
|
|
53f8249ee1 | ||
|
|
f71274b601 | ||
|
|
f2fd435c05 | ||
|
|
4e361e1a87 | ||
|
|
696e78fcc8 | ||
|
|
b71aa5e23b | ||
|
|
72cf3efb1d | ||
|
|
ae610dcbb7 | ||
|
|
d11e11d179 | ||
|
|
825f5c0a91 | ||
|
|
26e3a93fc3 | ||
|
|
1ceb5a6b37 | ||
|
|
65b731f484 | ||
|
|
c6a7722066 | ||
|
|
1ab70ec645 | ||
|
|
e181de1180 | ||
|
|
c712b1d0fe | ||
|
|
c4e5995cb9 | ||
|
|
be3683ccc8 | ||
|
|
7e7309f97e | ||
|
|
d2b6f93858 | ||
|
|
a91fe517d3 | ||
|
|
8a328a553a | ||
|
|
3d56357294 | ||
|
|
9b2a0c4538 | ||
|
|
a04ad24a60 | ||
|
|
0d633896ae | ||
|
|
611fabde11 | ||
|
|
f8073c7188 | ||
|
|
4dadcd2ace | ||
|
|
b18f12ca0f | ||
|
|
c67979abbb | ||
|
|
0e489bb5cc | ||
|
|
647f533e71 | ||
|
|
b22149d832 | ||
|
|
396215a5c9 | ||
|
|
42c157a5e6 | ||
|
|
a793f709b6 | ||
|
|
f74551e464 | ||
|
|
e06b1cef60 | ||
|
|
2d0c7cb0fc | ||
|
|
f5b8619ded | ||
|
|
4c6c915a77 | ||
|
|
064fae41e7 | ||
|
|
d4ef8e53d5 | ||
|
|
b15a1c7ae7 | ||
|
|
6f0182f11c | ||
|
|
371998e635 | ||
|
|
072b465fa6 | ||
|
|
8302575078 | ||
|
|
8edcd71234 | ||
|
|
5446683881 | ||
|
|
b91beb68e1 | ||
|
|
5659f1f3e9 | ||
|
|
753b16b697 | ||
|
|
da7a28c117 | ||
|
|
c35743d7ad | ||
|
|
f0eafabd6d | ||
|
|
fc4cf5d196 | ||
|
|
4e880e56c4 | ||
|
|
e8e39f5ce2 | ||
|
|
370ab2d19c | ||
|
|
fa8cd47227 | ||
|
|
9ec77bba91 | ||
|
|
4ec9262ff6 | ||
|
|
ba21d4e651 | ||
|
|
3cffed9412 | ||
|
|
90b8fd242d | ||
|
|
1191aca60f | ||
|
|
2f01af49e8 | ||
|
|
32fef9aadb | ||
|
|
717cb40f05 | ||
|
|
ef7d1a8602 | ||
|
|
a6656e307c | ||
|
|
442592a149 | ||
|
|
ac335907ae | ||
|
|
b07ce19b9a | ||
|
|
079c5a9905 | ||
|
|
a2c4fad410 | ||
|
|
567e898e22 | ||
|
|
14a29d6d6f | ||
|
|
8e2ed8c538 | ||
|
|
6ad4ffea3b | ||
|
|
770d51634c | ||
|
|
c51e13941c | ||
|
|
05ae0800c3 | ||
|
|
4bbfe7b3cc | ||
|
|
00530477c9 | ||
|
|
90c1334535 | ||
|
|
83ef031922 | ||
|
|
820c2bc0fd | ||
|
|
a484954d74 | ||
|
|
56be1a4950 | ||
|
|
1e8eb72b2f | ||
|
|
18935bf7f0 | ||
|
|
457c4c248d | ||
|
|
8c553dcde9 | ||
|
|
a709caf138 | ||
|
|
8dea2dab88 | ||
|
|
63aa32c521 | ||
|
|
24841f22f1 | ||
|
|
d712c2f199 | ||
|
|
4525216d26 | ||
|
|
ae61da7485 | ||
|
|
fb9f6153fc | ||
|
|
a0c808842b | ||
|
|
8a8490d97d | ||
|
|
68c7f9840a | ||
|
|
8d37f48744 | ||
|
|
37f32755b5 | ||
|
|
5380fe2483 | ||
|
|
8e71df3e53 | ||
|
|
30f43a245b | ||
|
|
283ed29f10 | ||
|
|
ccd39bb8ae | ||
|
|
db40653202 | ||
|
|
d3940729eb | ||
|
|
56a6ee6afb | ||
|
|
a37a3fc4a1 | ||
|
|
af87cd1dd6 | ||
|
|
e675025411 | ||
|
|
c3d5798c6c | ||
|
|
15d17e16dd | ||
|
|
926061557b | ||
|
|
8522337f32 | ||
|
|
6114f69ee5 | ||
|
|
0aa351443f | ||
|
|
c28ecabfd8 | ||
|
|
bbd779b437 | ||
|
|
b411897a5a | ||
|
|
fb97c30080 | ||
|
|
61b59837bb | ||
|
|
e65c0a9d5e | ||
|
|
3f7674a2e9 | ||
|
|
fb81fc6057 | ||
|
|
e1acb6a547 | ||
|
|
250eb14de1 | ||
|
|
61ae5e16ae | ||
|
|
e1efbfc842 | ||
|
|
7d486cabeb | ||
|
|
c2e953a09f | ||
|
|
59450c7d12 | ||
|
|
ca9e006cca | ||
|
|
387bdb1b84 | ||
|
|
5fb98655b3 | ||
|
|
addfa2f961 | ||
|
|
7679b109cb | ||
|
|
6a418c65ca | ||
|
|
aa55995924 | ||
|
|
5431c269d3 | ||
|
|
73696629d9 | ||
|
|
38b1255119 | ||
|
|
57de11b709 | ||
|
|
ce50533689 | ||
|
|
18ad975573 | ||
|
|
fd8e480363 | ||
|
|
3d6d677453 | ||
|
|
aef8e71b82 | ||
|
|
5872779faa | ||
|
|
04d7907864 | ||
|
|
65a2eccfdb | ||
|
|
4a272afc67 | ||
|
|
2499a6c8a3 | ||
|
|
74ed1ac584 | ||
|
|
d6d48f2866 | ||
|
|
f98b57aa5e | ||
|
|
f114321322 | ||
|
|
940700dc56 | ||
|
|
be0fa98d11 | ||
|
|
e71fc43952 | ||
|
|
cd0f72d66a | ||
|
|
48bb3a9dce | ||
|
|
55d8e801f1 | ||
|
|
54765d2236 | ||
|
|
dbf6716b9b | ||
|
|
a19bb0bbfe | ||
|
|
75a95865ff | ||
|
|
07dd8a2b07 | ||
|
|
8bcfa3660a | ||
|
|
0e4d266a52 | ||
|
|
7676572358 | ||
|
|
7fb758cc1d | ||
|
|
22f0d9cdd3 | ||
|
|
1cb29cea15 | ||
|
|
1eb5d89f1d | ||
|
|
d570552206 | ||
|
|
41f41ee4ee | ||
|
|
94d6f4dae7 | ||
|
|
f30387e7f5 | ||
|
|
3a8b97841f | ||
|
|
df3afb1b89 | ||
|
|
7f935c1ea8 | ||
|
|
7ed94c0410 | ||
|
|
8e15451117 | ||
|
|
111d10fe5c | ||
|
|
98335361fd | ||
|
|
bd36b8a95e | ||
|
|
d89dc967b8 | ||
|
|
dbca9306de | ||
|
|
0cbe08b105 | ||
|
|
af5b19a9da | ||
|
|
da8e4e44b7 | ||
|
|
69666d1089 | ||
|
|
b37d6dcd1c | ||
|
|
c72be6cbf3 | ||
|
|
5a3049485c | ||
|
|
39e77d1823 | ||
|
|
da94ee7a9a | ||
|
|
62ca873ddd | ||
|
|
9404c2309c | ||
|
|
4a2508e960 | ||
|
|
ce6388bdfa | ||
|
|
58d7467180 | ||
|
|
cb53e377a8 | ||
|
|
a3560eb451 | ||
|
|
ef4fd70f9c | ||
|
|
74fe069da9 | ||
|
|
542030f190 | ||
|
|
206b2c6f0b | ||
|
|
dc87592876 | ||
|
|
37f35da436 | ||
|
|
f598da0df2 | ||
|
|
20ccb03a45 | ||
|
|
891362dfb2 | ||
|
|
463cf35e64 | ||
|
|
1919a2d2d6 | ||
|
|
71ad3fac63 | ||
|
|
ae4a579153 | ||
|
|
da86096e19 |
7
.codespellrc
Normal file
7
.codespellrc
Normal file
@@ -0,0 +1,7 @@
|
||||
[codespell]
|
||||
# Ref: https://github.com/codespell-project/codespell#using-a-config-file
|
||||
skip = .git*,go.sum,*.lock,.codespellrc,vendor,translations,Keybindings_*.md
|
||||
check-hidden = true
|
||||
# camel-cased
|
||||
ignore-regex = (\b[A-Za-z][a-z]*[A-Z]\S+\b|\.edn\b|\S+…|\\nd\b)
|
||||
ignore-words-list = fomrat,inbetween
|
||||
1
.github/pull_request_template.md
vendored
1
.github/pull_request_template.md
vendored
@@ -6,6 +6,7 @@
|
||||
* [ ] Code has been formatted (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#code-formatting))
|
||||
* [ ] Tests have been added/updated (see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md) for the integration test guide)
|
||||
* [ ] Text is internationalised (see [here](https://github.com/jesseduffield/lazygit/blob/master/CONTRIBUTING.md#internationalisation))
|
||||
* [ ] If a new UserConfig entry was added, make sure it can be hot-reloaded (see [here](https://github.com/jesseduffield/lazygit/blob/master/docs/dev/Codebase_Guide.md#using-userconfig))
|
||||
* [ ] Docs have been updated if necessary
|
||||
* [ ] You've read through your own file changes for silly mistakes etc
|
||||
|
||||
|
||||
3
.github/release.yml
vendored
3
.github/release.yml
vendored
@@ -21,6 +21,9 @@ changelog:
|
||||
- title: I18n 🌎
|
||||
labels:
|
||||
- i18n
|
||||
- title: Performance Improvements 📊
|
||||
labels:
|
||||
- performance
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- "*"
|
||||
|
||||
25
.github/workflows/ci.yml
vendored
25
.github/workflows/ci.yml
vendored
@@ -170,9 +170,9 @@ jobs:
|
||||
with:
|
||||
go-version: 1.22.x
|
||||
- name: Lint
|
||||
uses: golangci/golangci-lint-action@v3.7.0
|
||||
uses: golangci/golangci-lint-action@v6.1.0
|
||||
with:
|
||||
version: v1.58
|
||||
version: v1.60
|
||||
- name: errors
|
||||
run: golangci-lint run
|
||||
if: ${{ failure() }}
|
||||
@@ -184,7 +184,7 @@ jobs:
|
||||
with:
|
||||
mode: exactly
|
||||
count: 1
|
||||
labels: "ignore-for-release, feature, enhancement, bug, maintenance, docs, i18n"
|
||||
labels: "ignore-for-release, feature, enhancement, bug, maintenance, docs, i18n, performance"
|
||||
upload-coverage:
|
||||
# List all jobs that produce coverage files
|
||||
needs: [unit-tests, integration-tests]
|
||||
@@ -219,3 +219,22 @@ jobs:
|
||||
CODACY_PROJECT_TOKEN=${{ secrets.CODACY_PROJECT_TOKEN }} \
|
||||
bash <(curl -Ls https://coverage.codacy.com/get.sh) report \
|
||||
--force-coverage-parser go -r coverage.out
|
||||
|
||||
check-for-fixups:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.ref != 'refs/heads/master'
|
||||
steps:
|
||||
# See https://github.com/actions/checkout/issues/552#issuecomment-1167086216
|
||||
- name: "PR commits"
|
||||
run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} ))" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: "Checkout PR branch and all PR commits"
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
fetch-depth: ${{ env.PR_FETCH_DEPTH }}
|
||||
|
||||
- name: Check for fixups
|
||||
run: |
|
||||
./scripts/check_for_fixups.sh ${{ github.event.pull_request.base.ref }}
|
||||
|
||||
25
.github/workflows/codespell.yml
vendored
Normal file
25
.github/workflows/codespell.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Codespell configuration is within .codespellrc
|
||||
---
|
||||
name: Codespell
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
codespell:
|
||||
name: Check for spelling errors
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Annotate locations with typos
|
||||
uses: codespell-project/codespell-problem-matcher@v1
|
||||
- name: Codespell
|
||||
uses: codespell-project/actions-codespell@v2
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@
|
||||
|
||||
# Hidden
|
||||
.*
|
||||
!.codespellrc
|
||||
|
||||
# Notes
|
||||
*.notes
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
linters:
|
||||
disable:
|
||||
- structcheck # gives false positives
|
||||
enable:
|
||||
- gofumpt
|
||||
- thelper
|
||||
- goimports
|
||||
- tparallel
|
||||
- wastedassign
|
||||
- exportloopref
|
||||
- unparam
|
||||
- prealloc
|
||||
- unconvert
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# User Config
|
||||
|
||||
Default path for the config file:
|
||||
Default path for the global config file:
|
||||
|
||||
- Linux: `~/.config/lazygit/config.yml`
|
||||
- MacOS: `~/Library/Application\ Support/lazygit/config.yml`
|
||||
@@ -16,6 +16,8 @@ If you want to change the config directory:
|
||||
|
||||
- MacOS: `export XDG_CONFIG_HOME="$HOME/.config"`
|
||||
|
||||
In addition to the global config file you can create repo-specific config files in `<repo>/.git/lazygit.yml`. Settings in these files override settings in the global config file. In addition, files called `.lazygit.yml` in any of the parent directories of a repo will also be loaded; this can be useful if you have settings that you want to apply to a group of repositories.
|
||||
|
||||
JSON schema is available for `config.yml` so that IntelliSense in Visual Studio Code (completion and error checking) is automatically enabled when the [YAML Red Hat][yaml] extension is installed. However, note that automatic schema detection only works if your config file is in one of the standard paths mentioned above. If you override the path to the file, you can still make IntelliSense work by adding
|
||||
|
||||
```yaml
|
||||
@@ -244,6 +246,12 @@ gui:
|
||||
# One of 'dashboard' (default) | 'allBranchesLog'
|
||||
statusPanelView: dashboard
|
||||
|
||||
# If true, jump to the Files panel after popping a stash
|
||||
switchToFilesAfterStashPop: true
|
||||
|
||||
# If true, jump to the Files panel after applying a stash
|
||||
switchToFilesAfterStashApply: true
|
||||
|
||||
# Config relating to git
|
||||
git:
|
||||
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md
|
||||
@@ -303,6 +311,12 @@ git:
|
||||
# If true, pass the --all arg to git fetch
|
||||
fetchAll: true
|
||||
|
||||
# If true, lazygit will automatically stage files that used to have merge
|
||||
# conflicts but no longer do; and it will also ask you if you want to
|
||||
# continue a merge or rebase if you've resolved all conflicts. If false, it
|
||||
# won't do either of these things.
|
||||
autoStageResolvedConflicts: true
|
||||
|
||||
# Command used when displaying the current branch git log in the main window
|
||||
branchLogCmd: git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --
|
||||
|
||||
@@ -495,7 +509,7 @@ keybinding:
|
||||
scrollDownMain-alt1: J
|
||||
scrollUpMain-alt2: <c-u>
|
||||
scrollDownMain-alt2: <c-d>
|
||||
executeCustomCommand: ':'
|
||||
executeShellCommand: ':'
|
||||
createRebaseOptionsMenu: m
|
||||
|
||||
# 'Files' appended for legacy reasons
|
||||
@@ -665,7 +679,7 @@ os:
|
||||
editPreset: 'vscode'
|
||||
```
|
||||
|
||||
Supported presets are `vim`, `nvim`, `nvim-remote`, `lvim`, `emacs`, `nano`, `micro`, `vscode`, `sublime`, `bbedit`, `kakoune`, `helix`, and `xcode`. In many cases lazygit will be able to guess the right preset from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
|
||||
Supported presets are `vim`, `nvim`, `nvim-remote`, `lvim`, `emacs`, `nano`, `micro`, `vscode`, `sublime`, `bbedit`, `kakoune`, `helix`, `xcode`, and `zed`. In many cases lazygit will be able to guess the right preset from your $(git config core.editor), or an environment variable such as $VISUAL or $EDITOR.
|
||||
|
||||
`nvim-remote` is an experimental preset for when you have invoked lazygit from within a neovim process, allowing lazygit to open the file from within the parent process rather than spawning a new one.
|
||||
|
||||
|
||||
@@ -87,6 +87,11 @@ The permitted contexts are:
|
||||
| stash | The 'Stash' tab |
|
||||
| global | This keybinding will take affect everywhere |
|
||||
|
||||
> **Bonus**
|
||||
>
|
||||
> You can use a comma-separated string, such as `context: 'commits, subCommits'`, to make it effective in multiple contexts.
|
||||
|
||||
|
||||
## Prompts
|
||||
|
||||
### Common fields
|
||||
@@ -291,9 +296,7 @@ Here's an example using a command but not specifying anything else: so each line
|
||||
Your commands can contain placeholder strings using Go's [template syntax](https://jan.newmarch.name/golang/template/chapter-template.html). The template syntax is pretty powerful, letting you do things like conditionals if you want, but for the most part you'll simply want to be accessing the fields on the following objects:
|
||||
|
||||
```
|
||||
SelectedLocalCommit
|
||||
SelectedReflogCommit
|
||||
SelectedSubCommit
|
||||
SelectedCommit
|
||||
SelectedFile
|
||||
SelectedPath
|
||||
SelectedLocalBranch
|
||||
@@ -306,6 +309,9 @@ SelectedWorktree
|
||||
CheckedOutBranch
|
||||
```
|
||||
|
||||
(For legacy reasons, `SelectedLocalCommit`, `SelectedReflogCommit`, and `SelectedSubCommit` are also available, but they are deprecated.)
|
||||
|
||||
|
||||
To see what fields are available on e.g. the `SelectedFile`, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/services/custom_commands/models.go) (all the modelling lives in the same file).
|
||||
|
||||
## Keybinding collisions
|
||||
|
||||
@@ -26,6 +26,8 @@ git:
|
||||
|
||||

|
||||
|
||||
A cool feature of delta is --hyperlinks, which renders clickable links for the line numbers in the left margin, and lazygit supports these. To use them, set the `pager:` config to `delta --dark --paging=never --line-numbers --hyperlinks --hyperlinks-file-link-format="lazygit-edit://{path}:{line}"`; this allows you to click on an underlined line number in the diff to jump right to that same line in your editor.
|
||||
|
||||
## Diff-so-fancy
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
## Packages
|
||||
|
||||
* `pkg/app`: Contains startup code, inititalises a bunch of stuff like logging, the user config, etc, before starting the gui. Catches and handles some errors that the gui raises.
|
||||
* `pkg/app`: Contains startup code, initialises a bunch of stuff like logging, the user config, etc, before starting the gui. Catches and handles some errors that the gui raises.
|
||||
* `pkg/app/daemon`: Contains code relating to the lazygit daemon. This could be better named: it's is not a daemon in the sense that it's a long-running background process; rather it's a short-lived background process that we pass to git for certain tasks, like GIT_EDITOR for when we want to set the TODO file for an interactive rebase.
|
||||
* `pkg/cheatsheet`: Generates the keybinding cheatsheets in `docs/keybindings`.
|
||||
* `pkg/commands/git_commands`: All communication to the git binary happens here. So for example there's a `Checkout` method which calls `git checkout`.
|
||||
@@ -12,7 +12,7 @@
|
||||
* `pkg/commands/models`: Contains model structs that represent commits, branches, files, etc.
|
||||
* `pkg/commands/patch`: Contains code for parsing and working with git patches
|
||||
* `pkg/common`: Contains the `Common` struct which holds common dependencies like the logger, i18n, and the user config. Most structs in the code will have a field named `c` which holds a common struct (or a derivative of the common struct).
|
||||
* `pkg/config`: Contains code relating to the Lazygit user config. Specifically `pkg/config/user_config/go` defines the user config struct and its default values.
|
||||
* `pkg/config`: Contains code relating to the Lazygit user config. Specifically `pkg/config/user_config/go` defines the user config struct and its default values. See [below](#using-userconfig) for some important information about using it.
|
||||
* `pkg/constants`: Contains some constant strings (e.g. links to docs)
|
||||
* `pkg/env`: Contains code relating to setting/getting environment variables
|
||||
* `pkg/i18n`: Contains internationalised strings
|
||||
@@ -86,6 +86,12 @@ The event loop is managed in the `MainLoop` function of `vendor/github.com/jesse
|
||||
|
||||
Often, as part of handling a keypress, we'll want to run some code asynchronously so that it doesn't block the UI thread. For this we'll typically run `self.c.OnWorker(myFunc)`. If the worker wants to then do something on the UI thread again it can call `self.c.OnUIThread(myOtherFunc)`.
|
||||
|
||||
## Using UserConfig
|
||||
|
||||
The UserConfig struct is loaded from lazygit's global config file (and possibly repo-specific config files). It can be re-loaded while lazygit is running, e.g. when the user edits one of the config files. In this case we should make sure that any new or changed config values take effect immediately. The easiest way to achieve this is what we do in most controllers or helpers: these have a pointer to the `common.Common` struct, which contains the UserConfig, and access it from there. Since the UserConfig instance in `common.Common` is updated whenever we reload the config, the code can be sure that it always uses an up-to-date value, and there's nothing else to do.
|
||||
|
||||
If that's not possible for some reason, see if you can add code to `Gui.onUserConfigLoaded` to update things from the new config; there are some examples in that function to use as a guide. If that's too hard to do too, add the config to the list in `Gui.checkForChangedConfigsThatDontAutoReload` so that the user is asked to quit and restart lazygit.
|
||||
|
||||
## Legacy code structure
|
||||
|
||||
Before we had controllers and contexts, all the code lived directly in the gui package under a gui God Struct. This was fairly bloated and so we split things out to have a better separation of concerns. Nonetheless, it's a big effort to migrate all the code so we still have some logic in the gui struct that ought to live somewhere else. Likewise, we have some keybindings defined in `pkg/gui/keybindings.go` that ought to live on a controller (all keybindings used to be defined in that one file).
|
||||
|
||||
@@ -122,7 +122,7 @@ is this:
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
+ "github.com/samber/lo"
|
||||
"golang.org/x/sync/errgroup"
|
||||
@@ -308,9 +309,5 @@ func (self *FixupHelper) blameAddedLines(addedLineHunks []*hunk) ([]string, erro
|
||||
@@ -308,9 +309,5 @@ func (self *FixupHelper) blameAddedLines(addedLineHunks []*hunk) ([]string, error
|
||||
func findCommit(hash string) (*models.Commit, int, bool) {
|
||||
- for i, commit := range self.c.Model().Commits {
|
||||
- if commit.Hash == hash {
|
||||
|
||||
@@ -18,7 +18,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
|
||||
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view. |
|
||||
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view. |
|
||||
| `` : `` | Execute custom command | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
|
||||
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
|
||||
| `` <c-p> `` | View custom patch options | |
|
||||
| `` m `` | View merge/rebase options | View options to abort/continue/skip the current merge/rebase. |
|
||||
| `` R `` | Refresh | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
|
||||
|
||||
@@ -18,7 +18,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
|
||||
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view. |
|
||||
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view. |
|
||||
| `` : `` | カスタムコマンドを実行 | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
|
||||
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
|
||||
| `` <c-p> `` | View custom patch options | |
|
||||
| `` m `` | View merge/rebase options | View options to abort/continue/skip the current merge/rebase. |
|
||||
| `` R `` | リフレッシュ | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
|
||||
|
||||
@@ -18,7 +18,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
|
||||
| `` } `` | Diff 보기의 변경 사항 주위에 표시되는 컨텍스트의 크기를 늘리기 | Increase the amount of the context shown around changes in the diff view. |
|
||||
| `` { `` | Diff 보기의 변경 사항 주위에 표시되는 컨텍스트 크기 줄이기 | Decrease the amount of the context shown around changes in the diff view. |
|
||||
| `` : `` | Execute custom command | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
|
||||
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
|
||||
| `` <c-p> `` | 커스텀 Patch 옵션 보기 | |
|
||||
| `` m `` | View merge/rebase options | View options to abort/continue/skip the current merge/rebase. |
|
||||
| `` R `` | 새로고침 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
|
||||
|
||||
@@ -18,7 +18,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
|
||||
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view. |
|
||||
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view. |
|
||||
| `` : `` | Voer aangepaste commando uit | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
|
||||
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
|
||||
| `` <c-p> `` | Bekijk aangepaste patch opties | |
|
||||
| `` m `` | Bekijk merge/rebase opties | View options to abort/continue/skip the current merge/rebase. |
|
||||
| `` R `` | Verversen | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
|
||||
|
||||
@@ -18,7 +18,7 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
|
||||
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
|
||||
| `` } `` | Zwiększ rozmiar kontekstu w widoku różnic | Zwiększ ilość kontekstu pokazywanego wokół zmian w widoku różnic. |
|
||||
| `` { `` | Zmniejsz rozmiar kontekstu w widoku różnic | Zmniejsz ilość kontekstu pokazywanego wokół zmian w widoku różnic. |
|
||||
| `` : `` | Wykonaj polecenie niestandardowe | Wyświetl monit, w którym możesz wprowadzić polecenie powłoki do wykonania. Nie należy mylić z wcześniej skonfigurowanymi poleceniami niestandardowymi. |
|
||||
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
|
||||
| `` <c-p> `` | Wyświetl opcje niestandardowej łatki | |
|
||||
| `` m `` | Pokaż opcje scalania/rebase | Pokaż opcje do przerwania/kontynuowania/pominięcia bieżącego scalania/rebase. |
|
||||
| `` R `` | Odśwież | Odśwież stan git (tj. uruchom `git status`, `git branch`, itp. w tle, aby zaktualizować zawartość paneli). To nie uruchamia `git fetch`. |
|
||||
|
||||
@@ -18,7 +18,7 @@ _Связки клавиш_
|
||||
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
|
||||
| `` } `` | Увеличить размер контекста, отображаемого вокруг изменений в просмотрщике сравнении | Increase the amount of the context shown around changes in the diff view. |
|
||||
| `` { `` | Уменьшите размер контекста, отображаемого вокруг изменений в просмотрщике сравнении | Decrease the amount of the context shown around changes in the diff view. |
|
||||
| `` : `` | Выполнить пользовательскую команду | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
|
||||
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
|
||||
| `` <c-p> `` | Просмотреть пользовательские параметры патча | |
|
||||
| `` m `` | Просмотреть параметры слияния/перебазирования | View options to abort/continue/skip the current merge/rebase. |
|
||||
| `` R `` | Обновить | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
|
||||
|
||||
@@ -2,7 +2,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|
||||
# Lazygit 按键绑定
|
||||
|
||||
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
_图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|
||||
|
||||
## 全局键绑定
|
||||
|
||||
@@ -11,28 +11,28 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` <c-r> `` | 切换到最近的仓库 | |
|
||||
| `` <pgup> (fn+up/shift+k) `` | 向上滚动主面板 | |
|
||||
| `` <pgdown> (fn+down/shift+j) `` | 向下滚动主面板 | |
|
||||
| `` @ `` | 打开命令日志菜单 | View options for the command log e.g. show/hide the command log and focus the command log. |
|
||||
| `` P `` | 推送 | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
|
||||
| `` p `` | 拉取 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
|
||||
| `` @ `` | 打开命令日志菜单 | 查看命令日志的选项,例如显示/隐藏命令日志以及聚焦命令日志 |
|
||||
| `` P `` | 推送 | 推送当前分支到它的上游。如果上游为配置,你可以在弹窗中配置上游分支。 |
|
||||
| `` p `` | 拉取 | 从当前分支的远程分支获取改动。如果上游为配置,你可以在弹窗中配置上游分支。 |
|
||||
| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
|
||||
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
|
||||
| `` } `` | 扩大差异视图中显示的上下文范围 | Increase the amount of the context shown around changes in the diff view. |
|
||||
| `` { `` | 缩小差异视图中显示的上下文范围 | Decrease the amount of the context shown around changes in the diff view. |
|
||||
| `` : `` | 执行自定义命令 | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
|
||||
| `` } `` | 扩大差异视图中显示的上下文范围 | 增加diff视图中围绕更改显示的上下文数量 |
|
||||
| `` { `` | 缩小差异视图中显示的上下文范围 | 减少diff视图中围绕更改显示的上下文数量 |
|
||||
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
|
||||
| `` <c-p> `` | 查看自定义补丁选项 | |
|
||||
| `` m `` | 查看 合并/变基 选项 | View options to abort/continue/skip the current merge/rebase. |
|
||||
| `` R `` | 刷新 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
|
||||
| `` + `` | 下一屏模式(正常/半屏/全屏) | |
|
||||
| `` m `` | 查看 合并/变基 选项 | 查看当前合并或变基的中止、继续、跳过选项 |
|
||||
| `` R `` | 刷新 | 刷新git状态(即在后台上运行`git status`,`git branch`等命令以更新面板内容) 不会运行`git fetch` |
|
||||
| `` + `` | 下一屏模式(正常/半屏/全屏) | |
|
||||
| `` _ `` | 上一屏模式 | |
|
||||
| `` ? `` | 打开菜单 | |
|
||||
| `` <c-s> `` | 查看按路径过滤选项 | View options for filtering the commit log, so that only commits matching the filter are shown. |
|
||||
| `` W `` | 打开 diff 菜单 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
|
||||
| `` <c-e> `` | 打开 diff 菜单 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
|
||||
| `` <c-s> `` | 查看按路径过滤选项 | 查看用于过滤提交日志的选项,以便仅显示与过滤器匹配的提交。 |
|
||||
| `` W `` | 打开 diff 菜单 | 查看与比较两个引用相关的选项,例如与选定的 ref 进行比较,输入要比较的 ref,然后反转比较方向。 |
|
||||
| `` <c-e> `` | 打开 diff 菜单 | 查看与比较两个引用相关的选项,例如与选定的 ref 进行比较,输入要比较的 ref,然后反转比较方向。 |
|
||||
| `` q `` | 退出 | |
|
||||
| `` <esc> `` | 取消 | |
|
||||
| `` <c-w> `` | 切换是否在差异视图中显示空白字符差异 | Toggle whether or not whitespace changes are shown in the diff view. |
|
||||
| `` z `` | (通过 reflog)撤销「实验功能」 | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
|
||||
| `` <c-z> `` | (通过 reflog)重做「实验功能」 | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
|
||||
| `` <c-w> `` | 切换是否在差异视图中显示空白字符差异 | 切换是否在diff视图中显示空白更改 |
|
||||
| `` z `` | (通过 reflog)撤销「实验功能」 | Reflog将用于确定运行哪个git命令来撤消最后一个git命令。这并不包括对工作树的更改,只考虑提交。 |
|
||||
| `` <c-z> `` | (通过 reflog)重做「实验功能」 | Reflog将用于确定运行哪个git命令来重做上一个git命令。这并不包括对工作树的更改,只考虑提交。 |
|
||||
|
||||
## 列表面板导航
|
||||
|
||||
@@ -43,8 +43,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` < `` | 滚动到顶部 | |
|
||||
| `` > `` | 滚动到底部 | |
|
||||
| `` v `` | 切换拖动选择 | |
|
||||
| `` <s-down> `` | Range select down | |
|
||||
| `` <s-up> `` | Range select up | |
|
||||
| `` <s-down> `` | 向下扩展选择范围 | |
|
||||
| `` <s-up> `` | 向上扩展选择范围 | |
|
||||
| `` / `` | 开始搜索 | |
|
||||
| `` H `` | 向左滚动 | |
|
||||
| `` L `` | 向右滚动 | |
|
||||
@@ -56,27 +56,17 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
|
||||
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. |
|
||||
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
|
||||
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD。 |
|
||||
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,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 | |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
@@ -12,13 +12,13 @@ _說明:`<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. |
|
||||
| `` { `` | 減小差異檢視中顯示變更周圍上下文的大小 | Decrease the amount of the context shown around changes in the diff view. |
|
||||
| `` : `` | 執行自訂命令 | Bring up a prompt where you can enter a shell command to execute. Not to be confused with pre-configured custom commands. |
|
||||
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
|
||||
| `` <c-p> `` | 檢視自訂補丁選項 | |
|
||||
| `` m `` | 查看合併/變基選項 | View options to abort/continue/skip the current merge/rebase. |
|
||||
| `` R `` | 重新整理 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
|
||||
@@ -60,8 +60,8 @@ _說明:`<c-b>` 表示 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 `` | 檢視工作目錄選項 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
|
||||
12
go.mod
12
go.mod
@@ -16,7 +16,7 @@ require (
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240628061234-aed9e133e65b
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
|
||||
@@ -24,7 +24,7 @@ require (
|
||||
github.com/karimkhaleel/jsonschema v0.0.0-20231001195015-d933f0d94ea3
|
||||
github.com/kyokomi/emoji/v2 v2.2.8
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||
github.com/mattn/go-runewidth v0.0.15
|
||||
github.com/mattn/go-runewidth v0.0.16
|
||||
github.com/mgutz/str v1.2.0
|
||||
github.com/mitchellh/go-ps v1.0.0
|
||||
github.com/sahilm/fuzzy v0.1.0
|
||||
@@ -38,7 +38,7 @@ require (
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
|
||||
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8
|
||||
golang.org/x/sync v0.7.0
|
||||
golang.org/x/sync v0.8.0
|
||||
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -75,8 +75,8 @@ require (
|
||||
github.com/xanzy/ssh-agent v0.2.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.21.0 // indirect
|
||||
golang.org/x/term v0.21.0 // indirect
|
||||
golang.org/x/text v0.16.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/term v0.24.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
23
go.sum
23
go.sum
@@ -188,8 +188,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240628061234-aed9e133e65b h1:oxCq0DvR2GMGf4UaoaASb0nQK/fJMQW3c3PNCLWCjS8=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240628061234-aed9e133e65b/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f h1:ZzsAUDwPFLPITKLcJpMSqt/3rERdI8YRZKr2l0plrls=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240928100326-393cf89a5d3f/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
|
||||
@@ -235,8 +235,9 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw=
|
||||
github.com/mgutz/str v1.2.0/go.mod h1:w1v0ofgLaJdoD0HpQ3fycxKD1WtxpjSo151pK/31q6w=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
@@ -423,8 +424,8 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -474,14 +475,14 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
|
||||
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -492,8 +493,8 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
@@ -63,22 +63,20 @@ func Run(
|
||||
func NewCommon(config config.AppConfigurer) (*common.Common, error) {
|
||||
userConfig := config.GetUserConfig()
|
||||
appState := config.GetAppState()
|
||||
|
||||
var err error
|
||||
log := newLogger(config)
|
||||
tr, err := i18n.NewTranslationSetFromConfig(log, userConfig.Gui.Language)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Initialize with English for the time being; the real translation set for
|
||||
// the configured language will be read after reading the user config
|
||||
tr := i18n.EnglishTranslationSet()
|
||||
|
||||
return &common.Common{
|
||||
Log: log,
|
||||
Tr: tr,
|
||||
UserConfig: userConfig,
|
||||
AppState: appState,
|
||||
Debug: config.GetDebug(),
|
||||
Fs: afero.NewOsFs(),
|
||||
}, nil
|
||||
cmn := &common.Common{
|
||||
Log: log,
|
||||
Tr: tr,
|
||||
AppState: appState,
|
||||
Debug: config.GetDebug(),
|
||||
Fs: afero.NewOsFs(),
|
||||
}
|
||||
cmn.SetUserConfig(userConfig)
|
||||
return cmn, nil
|
||||
}
|
||||
|
||||
func newLogger(cfg config.AppConfigurer) *logrus.Entry {
|
||||
@@ -118,11 +116,11 @@ func NewApp(config config.AppConfigurer, test integrationTypes.IntegrationTest,
|
||||
return app, err
|
||||
}
|
||||
|
||||
// If we're not in a repo, repoPaths will be nil. The error is moot for us
|
||||
// If we're not in a repo, GetRepoPaths will return an error. The error is moot for us
|
||||
// at this stage, since we'll try to init a new repo in setupRepo(), below
|
||||
repoPaths, err := git_commands.GetRepoPaths(app.OSCommand.Cmd, gitVersion)
|
||||
if err != nil {
|
||||
return app, err
|
||||
common.Log.Infof("Error getting repo paths: %v", err)
|
||||
}
|
||||
|
||||
showRecentRepos, err := app.setupRepo(repoPaths)
|
||||
@@ -195,7 +193,7 @@ func (app *App) setupRepo(
|
||||
|
||||
var shouldInitRepo bool
|
||||
initialBranchArg := ""
|
||||
switch app.UserConfig.NotARepository {
|
||||
switch app.UserConfig().NotARepository {
|
||||
case "prompt":
|
||||
// Offer to initialize a new repository in current directory.
|
||||
fmt.Print(app.Tr.CreateRepo)
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -136,6 +136,12 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
|
||||
|
||||
if integrationTest != nil {
|
||||
integrationTest.SetupConfig(appConfig)
|
||||
|
||||
// Preserve the changes that the test setup just made to the config, so
|
||||
// they don't get lost when we reload the config while running the test
|
||||
// (which happens when switching between repos, going in and out of
|
||||
// submodules, etc).
|
||||
appConfig.SaveGlobalUserConfig()
|
||||
}
|
||||
|
||||
common, err := NewCommon(appConfig)
|
||||
|
||||
@@ -63,6 +63,11 @@ func generateAtDir(cheatsheetDir string) {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
tr, err := i18n.NewTranslationSetFromConfig(common.Log, lang)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
common.Tr = tr
|
||||
mApp, _ := app.NewApp(mConfig, nil, common)
|
||||
path := cheatsheetDir + "/Keybindings_" + lang + ".md"
|
||||
file, err := os.Create(path)
|
||||
|
||||
@@ -38,6 +38,10 @@ func (self *gitCmdObjBuilder) NewShell(cmdStr string) oscommands.ICmdObj {
|
||||
return self.innerBuilder.NewShell(cmdStr).AddEnvVars(defaultEnvVar)
|
||||
}
|
||||
|
||||
func (self *gitCmdObjBuilder) NewInteractiveShell(cmdStr string) oscommands.ICmdObj {
|
||||
return self.innerBuilder.NewInteractiveShell(cmdStr).AddEnvVars(defaultEnvVar)
|
||||
}
|
||||
|
||||
func (self *gitCmdObjBuilder) Quote(str string) string {
|
||||
return self.innerBuilder.Quote(str)
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/mgutz/str"
|
||||
@@ -145,7 +146,7 @@ func (self *BranchCommands) GetGraph(branchName string) (string, error) {
|
||||
}
|
||||
|
||||
func (self *BranchCommands) GetGraphCmdObj(branchName string) oscommands.ICmdObj {
|
||||
branchLogCmdTemplate := self.UserConfig.Git.BranchLogCmd
|
||||
branchLogCmdTemplate := self.UserConfig().Git.BranchLogCmd
|
||||
templateValues := map[string]string{
|
||||
"branchName": self.cmd.Quote(branchName),
|
||||
}
|
||||
@@ -236,7 +237,7 @@ func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
|
||||
}
|
||||
cmdArgs := NewGitCmd("merge").
|
||||
Arg("--no-edit").
|
||||
Arg(strings.Fields(self.UserConfig.Git.Merging.Args)...).
|
||||
Arg(strings.Fields(self.UserConfig().Git.Merging.Args)...).
|
||||
ArgIf(opts.FastForwardOnly, "--ff-only").
|
||||
ArgIf(opts.Squash, "--squash", "--ff").
|
||||
Arg(branchName).
|
||||
@@ -248,9 +249,9 @@ func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
|
||||
func (self *BranchCommands) AllBranchesLogCmdObj() oscommands.ICmdObj {
|
||||
// Only choose between non-empty, non-identical commands
|
||||
candidates := lo.Uniq(lo.WithoutEmpty(append([]string{
|
||||
self.UserConfig.Git.AllBranchesLogCmd,
|
||||
self.UserConfig().Git.AllBranchesLogCmd,
|
||||
},
|
||||
self.UserConfig.Git.AllBranchesLogCmds...,
|
||||
self.UserConfig().Git.AllBranchesLogCmds...,
|
||||
)))
|
||||
|
||||
n := len(candidates)
|
||||
@@ -260,3 +261,26 @@ func (self *BranchCommands) AllBranchesLogCmdObj() oscommands.ICmdObj {
|
||||
|
||||
return self.cmd.New(str.ToArgv(candidates[i])).DontLog()
|
||||
}
|
||||
|
||||
func (self *BranchCommands) IsBranchMerged(branch *models.Branch, mainBranches *MainBranches) (bool, error) {
|
||||
branchesToCheckAgainst := []string{"HEAD"}
|
||||
if branch.RemoteBranchStoredLocally() {
|
||||
branchesToCheckAgainst = append(branchesToCheckAgainst, fmt.Sprintf("%s@{upstream}", branch.Name))
|
||||
}
|
||||
branchesToCheckAgainst = append(branchesToCheckAgainst, mainBranches.Get()...)
|
||||
|
||||
cmdArgs := NewGitCmd("rev-list").
|
||||
Arg("--max-count=1").
|
||||
Arg(branch.Name).
|
||||
Arg(lo.Map(branchesToCheckAgainst, func(branch string, _ int) string {
|
||||
return fmt.Sprintf("^%s", branch)
|
||||
})...).
|
||||
ToArgv()
|
||||
|
||||
stdout, _, err := self.cmd.New(cmdArgs).RunWithOutputs()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return stdout == "", nil
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ func (self *BranchLoader) Load(reflogCommits []*models.Commit,
|
||||
}
|
||||
}
|
||||
|
||||
if loadBehindCounts && self.UserConfig.Gui.ShowDivergenceFromBaseBranch != "none" {
|
||||
if loadBehindCounts && self.UserConfig().Gui.ShowDivergenceFromBaseBranch != "none" {
|
||||
onWorker(func() error {
|
||||
return self.GetBehindBaseBranchValuesForAllBranches(branches, mainBranches, renderFunc)
|
||||
})
|
||||
|
||||
@@ -88,7 +88,7 @@ func (self *CommitCommands) ResetToCommit(hash string, strength string, envVars
|
||||
func (self *CommitCommands) CommitCmdObj(summary string, description string) oscommands.ICmdObj {
|
||||
messageArgs := self.commitMessageArgs(summary, description)
|
||||
|
||||
skipHookPrefix := self.UserConfig.Git.SkipHookPrefix
|
||||
skipHookPrefix := self.UserConfig().Git.SkipHookPrefix
|
||||
|
||||
cmdArgs := NewGitCmd("commit").
|
||||
ArgIf(skipHookPrefix != "" && strings.HasPrefix(summary, skipHookPrefix), "--no-verify").
|
||||
@@ -117,7 +117,7 @@ func (self *CommitCommands) CommitInEditorWithMessageFileCmdObj(tmpMessageFile s
|
||||
}
|
||||
|
||||
// RewordLastCommit rewords the topmost commit with the given message
|
||||
func (self *CommitCommands) RewordLastCommit(summary string, description string) error {
|
||||
func (self *CommitCommands) RewordLastCommit(summary string, description string) oscommands.ICmdObj {
|
||||
messageArgs := self.commitMessageArgs(summary, description)
|
||||
|
||||
cmdArgs := NewGitCmd("commit").
|
||||
@@ -125,7 +125,7 @@ func (self *CommitCommands) RewordLastCommit(summary string, description string)
|
||||
Arg(messageArgs...).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
return self.cmd.New(cmdArgs)
|
||||
}
|
||||
|
||||
func (self *CommitCommands) commitMessageArgs(summary string, description string) []string {
|
||||
@@ -148,7 +148,7 @@ func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj {
|
||||
}
|
||||
|
||||
func (self *CommitCommands) signoffFlag() string {
|
||||
if self.UserConfig.Git.Commit.SignOff {
|
||||
if self.UserConfig().Git.Commit.SignOff {
|
||||
return "--signoff"
|
||||
} else {
|
||||
return ""
|
||||
@@ -189,7 +189,7 @@ type Author struct {
|
||||
|
||||
func (self *CommitCommands) GetCommitAuthor(commitHash string) (Author, error) {
|
||||
cmdArgs := NewGitCmd("show").
|
||||
Arg("--no-patch", "--pretty=format:'%an%x00%ae'", commitHash).
|
||||
Arg("--no-patch", "--pretty=format:%an%x00%ae", commitHash).
|
||||
ToArgv()
|
||||
|
||||
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
|
||||
@@ -258,13 +258,13 @@ func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
|
||||
func (self *CommitCommands) ShowCmdObj(hash string, filterPath string) oscommands.ICmdObj {
|
||||
contextSize := self.AppState.DiffContextSize
|
||||
|
||||
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
|
||||
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
|
||||
cmdArgs := NewGitCmd("show").
|
||||
Config("diff.noprefix=false").
|
||||
ConfigIf(extDiffCmd != "", "diff.external="+extDiffCmd).
|
||||
ArgIfElse(extDiffCmd != "", "--ext-diff", "--no-ext-diff").
|
||||
Arg("--submodule").
|
||||
Arg("--color="+self.UserConfig.Git.Paging.ColorArg).
|
||||
Arg("--color="+self.UserConfig().Git.Paging.ColorArg).
|
||||
Arg(fmt.Sprintf("--unified=%d", contextSize)).
|
||||
Arg("--stat").
|
||||
Arg("--decorate").
|
||||
|
||||
@@ -322,9 +322,9 @@ func TestGetCommits(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
common.UserConfig.Git.MainBranches = scenario.mainBranches
|
||||
common.UserConfig().Git.MainBranches = scenario.mainBranches
|
||||
opts := scenario.opts
|
||||
opts.MainBranches = NewMainBranches(scenario.mainBranches, cmd)
|
||||
opts.MainBranches = NewMainBranches(common, cmd)
|
||||
commits, err := builder.GetCommits(opts)
|
||||
|
||||
assert.Equal(t, scenario.expectedCommits, commits)
|
||||
|
||||
@@ -33,7 +33,7 @@ func TestCommitRewordCommit(t *testing.T) {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildCommitCommands(commonDeps{runner: s.runner})
|
||||
|
||||
assert.NoError(t, instance.RewordLastCommit(s.summary, s.description))
|
||||
assert.NoError(t, instance.RewordLastCommit(s.summary, s.description).Run())
|
||||
s.runner.CheckForMissingCalls()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ func (self *ConfigCommands) ConfiguredPager() string {
|
||||
}
|
||||
|
||||
func (self *ConfigCommands) GetPager(width int) string {
|
||||
useConfig := self.UserConfig.Git.Paging.UseConfig
|
||||
useConfig := self.UserConfig().Git.Paging.UseConfig
|
||||
if useConfig {
|
||||
pager := self.ConfiguredPager()
|
||||
return strings.Split(pager, "| less")[0]
|
||||
@@ -53,14 +53,14 @@ func (self *ConfigCommands) GetPager(width int) string {
|
||||
"columnWidth": strconv.Itoa(width/2 - 6),
|
||||
}
|
||||
|
||||
pagerTemplate := string(self.UserConfig.Git.Paging.Pager)
|
||||
pagerTemplate := string(self.UserConfig().Git.Paging.Pager)
|
||||
return utils.ResolvePlaceholderString(pagerTemplate, templateValues)
|
||||
}
|
||||
|
||||
// UsingGpg tells us whether the user has gpg enabled so that we can know
|
||||
// whether we need to run a subprocess to allow them to enter their password
|
||||
func (self *ConfigCommands) UsingGpg() bool {
|
||||
overrideGpg := self.UserConfig.Git.OverrideGpg
|
||||
overrideGpg := self.UserConfig().Git.OverrideGpg
|
||||
if overrideGpg {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -58,9 +58,9 @@ func buildGitCommon(deps commonDeps) *GitCommon {
|
||||
}
|
||||
gitCommon.cmd = cmd
|
||||
|
||||
gitCommon.Common.UserConfig = deps.userConfig
|
||||
if gitCommon.Common.UserConfig == nil {
|
||||
gitCommon.Common.UserConfig = config.GetDefaultConfig()
|
||||
gitCommon.Common.SetUserConfig(deps.userConfig)
|
||||
if gitCommon.Common.UserConfig() == nil {
|
||||
gitCommon.Common.SetUserConfig(config.GetDefaultConfig())
|
||||
}
|
||||
|
||||
gitCommon.version = deps.gitVersion
|
||||
|
||||
@@ -17,7 +17,7 @@ func NewDiffCommands(gitCommon *GitCommon) *DiffCommands {
|
||||
}
|
||||
|
||||
func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
|
||||
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
|
||||
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
|
||||
useExtDiff := extDiffCmd != ""
|
||||
|
||||
return self.cmd.New(
|
||||
@@ -26,7 +26,7 @@ func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
|
||||
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
|
||||
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
|
||||
Arg("--submodule").
|
||||
Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)).
|
||||
Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
|
||||
Arg(diffArgs...).
|
||||
Dir(self.repoPaths.worktreePath).
|
||||
ToArgv(),
|
||||
|
||||
@@ -31,7 +31,7 @@ func (self *FileCommands) Cat(fileName string) (string, error) {
|
||||
}
|
||||
|
||||
func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (string, error) {
|
||||
editor := self.UserConfig.OS.EditCommand
|
||||
editor := self.UserConfig().OS.EditCommand
|
||||
|
||||
if editor == "" {
|
||||
editor = self.config.GetCoreEditor()
|
||||
@@ -60,7 +60,7 @@ func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (
|
||||
"line": strconv.Itoa(lineNumber),
|
||||
}
|
||||
|
||||
editCmdTemplate := self.UserConfig.OS.EditCommandTemplate
|
||||
editCmdTemplate := self.UserConfig().OS.EditCommandTemplate
|
||||
if len(editCmdTemplate) == 0 {
|
||||
switch editor {
|
||||
case "emacs", "nano", "vi", "vim", "nvim":
|
||||
@@ -78,7 +78,7 @@ func (self *FileCommands) GetEditCmdStrLegacy(filename string, lineNumber int) (
|
||||
|
||||
func (self *FileCommands) GetEditCmdStr(filenames []string) (string, bool) {
|
||||
// Legacy support for old config; to be removed at some point
|
||||
if self.UserConfig.OS.Edit == "" && self.UserConfig.OS.EditCommandTemplate != "" {
|
||||
if self.UserConfig().OS.Edit == "" && self.UserConfig().OS.EditCommandTemplate != "" {
|
||||
// If multiple files are selected, we'll simply edit just the first one.
|
||||
// It's not worth fixing this for the legacy support.
|
||||
if cmdStr, err := self.GetEditCmdStrLegacy(filenames[0], 1); err == nil {
|
||||
@@ -86,7 +86,7 @@ func (self *FileCommands) GetEditCmdStr(filenames []string) (string, bool) {
|
||||
}
|
||||
}
|
||||
|
||||
template, suspend := config.GetEditTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
|
||||
template, suspend := config.GetEditTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
|
||||
quotedFilenames := lo.Map(filenames, func(filename string, _ int) string { return self.cmd.Quote(filename) })
|
||||
|
||||
templateValues := map[string]string{
|
||||
@@ -99,13 +99,13 @@ func (self *FileCommands) GetEditCmdStr(filenames []string) (string, bool) {
|
||||
|
||||
func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (string, bool) {
|
||||
// Legacy support for old config; to be removed at some point
|
||||
if self.UserConfig.OS.EditAtLine == "" && self.UserConfig.OS.EditCommandTemplate != "" {
|
||||
if self.UserConfig().OS.EditAtLine == "" && self.UserConfig().OS.EditCommandTemplate != "" {
|
||||
if cmdStr, err := self.GetEditCmdStrLegacy(filename, lineNumber); err == nil {
|
||||
return cmdStr, true
|
||||
}
|
||||
}
|
||||
|
||||
template, suspend := config.GetEditAtLineTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
|
||||
template, suspend := config.GetEditAtLineTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
|
||||
|
||||
templateValues := map[string]string{
|
||||
"filename": self.cmd.Quote(filename),
|
||||
@@ -118,13 +118,13 @@ func (self *FileCommands) GetEditAtLineCmdStr(filename string, lineNumber int) (
|
||||
|
||||
func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber int) string {
|
||||
// Legacy support for old config; to be removed at some point
|
||||
if self.UserConfig.OS.EditAtLineAndWait == "" && self.UserConfig.OS.EditCommandTemplate != "" {
|
||||
if self.UserConfig().OS.EditAtLineAndWait == "" && self.UserConfig().OS.EditCommandTemplate != "" {
|
||||
if cmdStr, err := self.GetEditCmdStrLegacy(filename, lineNumber); err == nil {
|
||||
return cmdStr
|
||||
}
|
||||
}
|
||||
|
||||
template := config.GetEditAtLineAndWaitTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
|
||||
template := config.GetEditAtLineAndWaitTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
|
||||
|
||||
templateValues := map[string]string{
|
||||
"filename": self.cmd.Quote(filename),
|
||||
@@ -136,7 +136,7 @@ func (self *FileCommands) GetEditAtLineAndWaitCmdStr(filename string, lineNumber
|
||||
}
|
||||
|
||||
func (self *FileCommands) GetOpenDirInEditorCmdStr(path string) (string, bool) {
|
||||
template, suspend := config.GetOpenDirInEditorTemplate(&self.UserConfig.OS, self.guessDefaultEditor)
|
||||
template, suspend := config.GetOpenDirInEditorTemplate(&self.UserConfig().OS, self.guessDefaultEditor)
|
||||
|
||||
templateValues := map[string]string{
|
||||
"dir": self.cmd.Quote(path),
|
||||
|
||||
@@ -5,32 +5,34 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type MainBranches struct {
|
||||
// List of main branches configured by the user. Just the bare names.
|
||||
configuredMainBranches []string
|
||||
// Which of these actually exist in the repository. Full ref names, and it
|
||||
// could be either "refs/heads/..." or "refs/remotes/origin/..." depending
|
||||
// on which one exists for a given bare name.
|
||||
c *common.Common
|
||||
// Which of the configured main branches actually exist in the repository. Full
|
||||
// ref names, and it could be either "refs/heads/..." or "refs/remotes/origin/..."
|
||||
// depending on which one exists for a given bare name.
|
||||
existingMainBranches []string
|
||||
|
||||
previousMainBranches []string
|
||||
|
||||
cmd oscommands.ICmdObjBuilder
|
||||
mutex *deadlock.Mutex
|
||||
}
|
||||
|
||||
func NewMainBranches(
|
||||
configuredMainBranches []string,
|
||||
cmn *common.Common,
|
||||
cmd oscommands.ICmdObjBuilder,
|
||||
) *MainBranches {
|
||||
return &MainBranches{
|
||||
configuredMainBranches: configuredMainBranches,
|
||||
existingMainBranches: nil,
|
||||
cmd: cmd,
|
||||
mutex: &deadlock.Mutex{},
|
||||
c: cmn,
|
||||
existingMainBranches: nil,
|
||||
cmd: cmd,
|
||||
mutex: &deadlock.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,8 +42,11 @@ func (self *MainBranches) Get() []string {
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
if self.existingMainBranches == nil {
|
||||
self.existingMainBranches = self.determineMainBranches()
|
||||
configuredMainBranches := self.c.UserConfig().Git.MainBranches
|
||||
|
||||
if self.existingMainBranches == nil || !utils.EqualSlices(self.previousMainBranches, configuredMainBranches) {
|
||||
self.existingMainBranches = self.determineMainBranches(configuredMainBranches)
|
||||
self.previousMainBranches = configuredMainBranches
|
||||
}
|
||||
|
||||
return self.existingMainBranches
|
||||
@@ -71,13 +76,13 @@ func (self *MainBranches) GetMergeBase(refName string) string {
|
||||
return ignoringWarnings(output)
|
||||
}
|
||||
|
||||
func (self *MainBranches) determineMainBranches() []string {
|
||||
func (self *MainBranches) determineMainBranches(configuredMainBranches []string) []string {
|
||||
var existingBranches []string
|
||||
var wg sync.WaitGroup
|
||||
|
||||
existingBranches = make([]string, len(self.configuredMainBranches))
|
||||
existingBranches = make([]string, len(configuredMainBranches))
|
||||
|
||||
for i, branchName := range self.configuredMainBranches {
|
||||
for i, branchName := range configuredMainBranches {
|
||||
wg.Add(1)
|
||||
go utils.Safe(func() {
|
||||
defer wg.Done()
|
||||
|
||||
@@ -35,9 +35,8 @@ func NewRebaseCommands(
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, summary string, description string) error {
|
||||
if models.IsHeadCommit(commits, index) {
|
||||
// we've selected the top commit so no rebase is required
|
||||
return self.commit.RewordLastCommit(summary, description)
|
||||
if self.config.UsingGpg() {
|
||||
return errors.New(self.Tr.DisabledForGPG)
|
||||
}
|
||||
|
||||
err := self.BeginInteractiveRebaseForCommit(commits, index, false)
|
||||
@@ -46,7 +45,7 @@ func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, su
|
||||
}
|
||||
|
||||
// now the selected commit should be our head so we'll amend it with the new message
|
||||
err = self.commit.RewordLastCommit(summary, description)
|
||||
err = self.commit.RewordLastCommit(summary, description).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -285,6 +284,11 @@ func (self *RebaseCommands) GitRebaseEditTodo(todosFileContent []byte) error {
|
||||
return cmdObj.Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) getHashOfLastCommitMade() (string, error) {
|
||||
cmdArgs := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToArgv()
|
||||
return self.cmd.New(cmdArgs).RunWithOutput()
|
||||
}
|
||||
|
||||
// AmendTo amends the given commit with whatever files are staged
|
||||
func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error {
|
||||
commit := commits[commitIndex]
|
||||
@@ -293,9 +297,7 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the hash of the commit we just created
|
||||
cmdArgs := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToArgv()
|
||||
fixupHash, err := self.cmd.New(cmdArgs).RunWithOutput()
|
||||
fixupHash, err := self.getHashOfLastCommitMade()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -303,7 +305,20 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex+1),
|
||||
overrideEditor: true,
|
||||
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Hash, fixupHash),
|
||||
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Hash, fixupHash, true),
|
||||
}).Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) MoveFixupCommitDown(commits []*models.Commit, targetCommitIndex int) error {
|
||||
fixupHash, err := self.getHashOfLastCommitMade()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseHashOrRoot: getBaseHashOrRoot(commits, targetCommitIndex+1),
|
||||
overrideEditor: true,
|
||||
instruction: daemon.NewMoveFixupCommitDownInstruction(commits[targetCommitIndex].Hash, fixupHash, false),
|
||||
}).Run()
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package git_commands
|
||||
import (
|
||||
ioFs "io/fs"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
@@ -64,9 +63,9 @@ func (self *RepoPaths) IsBareRepo() bool {
|
||||
func MockRepoPaths(currentPath string) *RepoPaths {
|
||||
return &RepoPaths{
|
||||
worktreePath: currentPath,
|
||||
worktreeGitDirPath: path.Join(currentPath, ".git"),
|
||||
worktreeGitDirPath: filepath.Join(currentPath, ".git"),
|
||||
repoPath: currentPath,
|
||||
repoGitDirPath: path.Join(currentPath, ".git"),
|
||||
repoGitDirPath: filepath.Join(currentPath, ".git"),
|
||||
repoName: "lazygit",
|
||||
isBareRepo: false,
|
||||
}
|
||||
@@ -116,9 +115,9 @@ func GetRepoPathsForDir(
|
||||
if isSubmodule {
|
||||
repoPath = worktreePath
|
||||
} else {
|
||||
repoPath = path.Dir(repoGitDirPath)
|
||||
repoPath = filepath.Dir(repoGitDirPath)
|
||||
}
|
||||
repoName := path.Base(repoPath)
|
||||
repoName := filepath.Base(repoPath)
|
||||
|
||||
return &RepoPaths{
|
||||
worktreePath: worktreePath,
|
||||
@@ -154,7 +153,7 @@ func linkedWortkreePaths(fs afero.Fs, repoGitDirPath string) []string {
|
||||
result := []string{}
|
||||
// For each directory in this path we're going to cat the `gitdir` file and append its contents to our result
|
||||
// That file points us to the `.git` file in the worktree.
|
||||
worktreeGitDirsPath := path.Join(repoGitDirPath, "worktrees")
|
||||
worktreeGitDirsPath := filepath.Join(repoGitDirPath, "worktrees")
|
||||
|
||||
// ensure the directory exists
|
||||
_, err := fs.Stat(worktreeGitDirsPath)
|
||||
@@ -171,7 +170,7 @@ func linkedWortkreePaths(fs afero.Fs, repoGitDirPath string) []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
gitDirPath := path.Join(currPath, "gitdir")
|
||||
gitDirPath := filepath.Join(currPath, "gitdir")
|
||||
gitDirBytes, err := afero.ReadFile(fs, gitDirPath)
|
||||
if err != nil {
|
||||
// ignoring error
|
||||
@@ -179,7 +178,7 @@ func linkedWortkreePaths(fs afero.Fs, repoGitDirPath string) []string {
|
||||
}
|
||||
trimmedGitDir := strings.TrimSpace(string(gitDirBytes))
|
||||
// removing the .git part
|
||||
worktreeDir := path.Dir(trimmedGitDir)
|
||||
worktreeDir := filepath.Dir(trimmedGitDir)
|
||||
result = append(result, worktreeDir)
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -2,11 +2,13 @@ package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -29,7 +31,17 @@ func TestGetRepoPaths(t *testing.T) {
|
||||
Name: "typical case",
|
||||
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
|
||||
// setup for main worktree
|
||||
expectedOutput := []string{
|
||||
mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{
|
||||
// --show-toplevel
|
||||
`C:\path\to\repo`,
|
||||
// --git-dir
|
||||
`C:\path\to\repo\.git`,
|
||||
// --git-common-dir
|
||||
`C:\path\to\repo\.git`,
|
||||
// --is-bare-repository
|
||||
"false",
|
||||
// --show-superproject-working-tree
|
||||
}, []string{
|
||||
// --show-toplevel
|
||||
"/path/to/repo",
|
||||
// --git-dir
|
||||
@@ -39,28 +51,45 @@ func TestGetRepoPaths(t *testing.T) {
|
||||
// --is-bare-repository
|
||||
"false",
|
||||
// --show-superproject-working-tree
|
||||
}
|
||||
})
|
||||
runner.ExpectGitArgs(
|
||||
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
|
||||
strings.Join(expectedOutput, "\n"),
|
||||
strings.Join(mockOutput, "\n"),
|
||||
nil)
|
||||
},
|
||||
Path: "/path/to/repo",
|
||||
Expected: &RepoPaths{
|
||||
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
|
||||
worktreePath: `C:\path\to\repo`,
|
||||
worktreeGitDirPath: `C:\path\to\repo\.git`,
|
||||
repoPath: `C:\path\to\repo`,
|
||||
repoGitDirPath: `C:\path\to\repo\.git`,
|
||||
repoName: `repo`,
|
||||
isBareRepo: false,
|
||||
}, &RepoPaths{
|
||||
worktreePath: "/path/to/repo",
|
||||
worktreeGitDirPath: "/path/to/repo/.git",
|
||||
repoPath: "/path/to/repo",
|
||||
repoGitDirPath: "/path/to/repo/.git",
|
||||
repoName: "repo",
|
||||
isBareRepo: false,
|
||||
},
|
||||
}),
|
||||
Err: nil,
|
||||
},
|
||||
{
|
||||
Name: "bare repo",
|
||||
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
|
||||
// setup for main worktree
|
||||
expectedOutput := []string{
|
||||
mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{
|
||||
// --show-toplevel
|
||||
`C:\path\to\repo`,
|
||||
// --git-dir
|
||||
`C:\path\to\bare_repo\bare.git`,
|
||||
// --git-common-dir
|
||||
`C:\path\to\bare_repo\bare.git`,
|
||||
// --is-bare-repository
|
||||
`true`,
|
||||
// --show-superproject-working-tree
|
||||
}, []string{
|
||||
// --show-toplevel
|
||||
"/path/to/repo",
|
||||
// --git-dir
|
||||
@@ -70,27 +99,45 @@ func TestGetRepoPaths(t *testing.T) {
|
||||
// --is-bare-repository
|
||||
"true",
|
||||
// --show-superproject-working-tree
|
||||
}
|
||||
})
|
||||
runner.ExpectGitArgs(
|
||||
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
|
||||
strings.Join(expectedOutput, "\n"),
|
||||
strings.Join(mockOutput, "\n"),
|
||||
nil)
|
||||
},
|
||||
Path: "/path/to/repo",
|
||||
Expected: &RepoPaths{
|
||||
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
|
||||
worktreePath: `C:\path\to\repo`,
|
||||
worktreeGitDirPath: `C:\path\to\bare_repo\bare.git`,
|
||||
repoPath: `C:\path\to\bare_repo`,
|
||||
repoGitDirPath: `C:\path\to\bare_repo\bare.git`,
|
||||
repoName: `bare_repo`,
|
||||
isBareRepo: true,
|
||||
}, &RepoPaths{
|
||||
worktreePath: "/path/to/repo",
|
||||
worktreeGitDirPath: "/path/to/bare_repo/bare.git",
|
||||
repoPath: "/path/to/bare_repo",
|
||||
repoGitDirPath: "/path/to/bare_repo/bare.git",
|
||||
repoName: "bare_repo",
|
||||
isBareRepo: true,
|
||||
},
|
||||
}),
|
||||
Err: nil,
|
||||
},
|
||||
{
|
||||
Name: "submodule",
|
||||
BeforeFunc: func(runner *oscommands.FakeCmdObjRunner, getRevParseArgs argFn) {
|
||||
expectedOutput := []string{
|
||||
mockOutput := lo.Ternary(runtime.GOOS == "windows", []string{
|
||||
// --show-toplevel
|
||||
`C:\path\to\repo\submodule1`,
|
||||
// --git-dir
|
||||
`C:\path\to\repo\.git\modules\submodule1`,
|
||||
// --git-common-dir
|
||||
`C:\path\to\repo\.git\modules\submodule1`,
|
||||
// --is-bare-repository
|
||||
`false`,
|
||||
// --show-superproject-working-tree
|
||||
`C:\path\to\repo`,
|
||||
}, []string{
|
||||
// --show-toplevel
|
||||
"/path/to/repo/submodule1",
|
||||
// --git-dir
|
||||
@@ -101,21 +148,28 @@ func TestGetRepoPaths(t *testing.T) {
|
||||
"false",
|
||||
// --show-superproject-working-tree
|
||||
"/path/to/repo",
|
||||
}
|
||||
})
|
||||
runner.ExpectGitArgs(
|
||||
append(getRevParseArgs(), "--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--is-bare-repository", "--show-superproject-working-tree"),
|
||||
strings.Join(expectedOutput, "\n"),
|
||||
strings.Join(mockOutput, "\n"),
|
||||
nil)
|
||||
},
|
||||
Path: "/path/to/repo/submodule1",
|
||||
Expected: &RepoPaths{
|
||||
Expected: lo.Ternary(runtime.GOOS == "windows", &RepoPaths{
|
||||
worktreePath: `C:\path\to\repo\submodule1`,
|
||||
worktreeGitDirPath: `C:\path\to\repo\.git\modules\submodule1`,
|
||||
repoPath: `C:\path\to\repo\submodule1`,
|
||||
repoGitDirPath: `C:\path\to\repo\.git\modules\submodule1`,
|
||||
repoName: `submodule1`,
|
||||
isBareRepo: false,
|
||||
}, &RepoPaths{
|
||||
worktreePath: "/path/to/repo/submodule1",
|
||||
worktreeGitDirPath: "/path/to/repo/.git/modules/submodule1",
|
||||
repoPath: "/path/to/repo/submodule1",
|
||||
repoGitDirPath: "/path/to/repo/.git/modules/submodule1",
|
||||
repoName: "submodule1",
|
||||
isBareRepo: false,
|
||||
},
|
||||
}),
|
||||
Err: nil,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -84,7 +84,7 @@ func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
|
||||
cmdArgs := NewGitCmd("stash").Arg("show").
|
||||
Arg("-p").
|
||||
Arg("--stat").
|
||||
Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)).
|
||||
Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
|
||||
Arg(fmt.Sprintf("--unified=%d", self.AppState.DiffContextSize)).
|
||||
ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
|
||||
Arg(fmt.Sprintf("--find-renames=%d%%", self.AppState.RenameSimilarityThreshold)).
|
||||
|
||||
@@ -60,7 +60,7 @@ func (self *SyncCommands) fetchCommandBuilder(fetchAll bool) *GitCommandBuilder
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FetchCmdObj(task gocui.Task) oscommands.ICmdObj {
|
||||
cmdArgs := self.fetchCommandBuilder(self.UserConfig.Git.FetchAll).ToArgv()
|
||||
cmdArgs := self.fetchCommandBuilder(self.UserConfig().Git.FetchAll).ToArgv()
|
||||
|
||||
cmdObj := self.cmd.New(cmdArgs)
|
||||
cmdObj.PromptOnCredentialRequest(task)
|
||||
@@ -72,7 +72,7 @@ func (self *SyncCommands) Fetch(task gocui.Task) error {
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj {
|
||||
cmdArgs := self.fetchCommandBuilder(self.UserConfig.Git.FetchAll).ToArgv()
|
||||
cmdArgs := self.fetchCommandBuilder(self.UserConfig().Git.FetchAll).ToArgv()
|
||||
|
||||
cmdObj := self.cmd.New(cmdArgs)
|
||||
cmdObj.DontLog().FailOnCredentialRequest()
|
||||
@@ -95,7 +95,7 @@ func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
|
||||
Arg("--no-edit").
|
||||
ArgIf(opts.FastForwardOnly, "--ff-only").
|
||||
ArgIf(opts.RemoteName != "", opts.RemoteName).
|
||||
ArgIf(opts.BranchName != "", opts.BranchName).
|
||||
ArgIf(opts.BranchName != "", "refs/heads/"+opts.BranchName).
|
||||
GitDirIf(opts.WorktreeGitDir != "", opts.WorktreeGitDir).
|
||||
ToArgv()
|
||||
|
||||
@@ -112,7 +112,7 @@ func (self *SyncCommands) FastForward(
|
||||
) error {
|
||||
cmdArgs := self.fetchCommandBuilder(false).
|
||||
Arg(remoteName).
|
||||
Arg(remoteBranchName + ":" + branchName).
|
||||
Arg("refs/heads/" + remoteBranchName + ":" + branchName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
|
||||
|
||||
@@ -133,7 +133,7 @@ func TestSyncFetch(t *testing.T) {
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildSyncCommands(commonDeps{})
|
||||
instance.UserConfig.Git.FetchAll = s.fetchAllConfig
|
||||
instance.UserConfig().Git.FetchAll = s.fetchAllConfig
|
||||
task := gocui.NewFakeTask()
|
||||
s.test(instance.FetchCmdObj(task))
|
||||
})
|
||||
@@ -171,7 +171,7 @@ func TestSyncFetchBackground(t *testing.T) {
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildSyncCommands(commonDeps{})
|
||||
instance.UserConfig.Git.FetchAll = s.fetchAllConfig
|
||||
instance.UserConfig().Git.FetchAll = s.fetchAllConfig
|
||||
s.test(instance.FetchBackgroundCmdObj())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package git_commands
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
@@ -233,7 +233,7 @@ func (self *WorkingTreeCommands) Ignore(filename string) error {
|
||||
|
||||
// Exclude adds a file to the .git/info/exclude for the repo
|
||||
func (self *WorkingTreeCommands) Exclude(filename string) error {
|
||||
excludeFile := path.Join(self.repoPaths.repoGitDirPath, "info", "exclude")
|
||||
excludeFile := filepath.Join(self.repoPaths.repoGitDirPath, "info", "exclude")
|
||||
return self.os.AppendLineToFile(excludeFile, filename)
|
||||
}
|
||||
|
||||
@@ -245,7 +245,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool,
|
||||
}
|
||||
|
||||
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool) oscommands.ICmdObj {
|
||||
colorArg := self.UserConfig.Git.Paging.ColorArg
|
||||
colorArg := self.UserConfig().Git.Paging.ColorArg
|
||||
if plain {
|
||||
colorArg = "never"
|
||||
}
|
||||
@@ -253,7 +253,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
|
||||
contextSize := self.AppState.DiffContextSize
|
||||
prevPath := node.GetPreviousPath()
|
||||
noIndex := !node.GetIsTracked() && !node.GetHasStagedChanges() && !cached && node.GetIsFile()
|
||||
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
|
||||
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
|
||||
useExtDiff := extDiffCmd != "" && !plain
|
||||
|
||||
cmdArgs := NewGitCmd("diff").
|
||||
@@ -285,12 +285,12 @@ func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bo
|
||||
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) oscommands.ICmdObj {
|
||||
contextSize := self.AppState.DiffContextSize
|
||||
|
||||
colorArg := self.UserConfig.Git.Paging.ColorArg
|
||||
colorArg := self.UserConfig().Git.Paging.ColorArg
|
||||
if plain {
|
||||
colorArg = "never"
|
||||
}
|
||||
|
||||
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
|
||||
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
|
||||
useExtDiff := extDiffCmd != "" && !plain
|
||||
|
||||
cmdArgs := NewGitCmd("diff").
|
||||
|
||||
@@ -42,7 +42,7 @@ func (self *CachedGitConfig) Get(key string) string {
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
if value, ok := self.cache[key]; ok {
|
||||
self.log.Debugf("using cache for key " + key)
|
||||
self.log.Debug("using cache for key " + key)
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func (self *CachedGitConfig) GetGeneral(args string) string {
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
if value, ok := self.cache[args]; ok {
|
||||
self.log.Debugf("using cache for args " + args)
|
||||
self.log.Debug("using cache for args " + args)
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func (self *CachedGitConfig) getGeneralAux(args string) string {
|
||||
cmd := getGitConfigGeneralCmd(args)
|
||||
value, err := self.runGitConfigCmd(cmd)
|
||||
if err != nil {
|
||||
self.log.Debugf("Error getting git config value for args: " + args + ". Error: " + err.Error())
|
||||
self.log.Debugf("Error getting git config value for args: %s. Error: %v", args, err.Error())
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(value)
|
||||
@@ -79,7 +79,7 @@ func (self *CachedGitConfig) getAux(key string) string {
|
||||
cmd := getGitConfigCmd(key)
|
||||
value, err := self.runGitConfigCmd(cmd)
|
||||
if err != nil {
|
||||
self.log.Debugf("Error getting git config value for key: " + key + ". Error: " + err.Error())
|
||||
self.log.Debugf("Error getting git config value for key: %s. Error: %v", key, err.Error())
|
||||
return ""
|
||||
}
|
||||
return strings.TrimSpace(value)
|
||||
|
||||
@@ -53,6 +53,10 @@ func (b *Branch) RefName() string {
|
||||
return b.Name
|
||||
}
|
||||
|
||||
func (b *Branch) ShortRefName() string {
|
||||
return b.RefName()
|
||||
}
|
||||
|
||||
func (b *Branch) ParentRefName() string {
|
||||
return b.RefName() + "^"
|
||||
}
|
||||
|
||||
@@ -70,6 +70,10 @@ func (c *Commit) RefName() string {
|
||||
return c.Hash
|
||||
}
|
||||
|
||||
func (c *Commit) ShortRefName() string {
|
||||
return c.Hash[:7]
|
||||
}
|
||||
|
||||
func (c *Commit) ParentRefName() string {
|
||||
if c.IsFirstCommit() {
|
||||
return EmptyTreeCommitHash
|
||||
|
||||
@@ -18,6 +18,10 @@ func (r *RemoteBranch) RefName() string {
|
||||
return r.FullName()
|
||||
}
|
||||
|
||||
func (r *RemoteBranch) ShortRefName() string {
|
||||
return r.RefName()
|
||||
}
|
||||
|
||||
func (r *RemoteBranch) ParentRefName() string {
|
||||
return r.RefName() + "^"
|
||||
}
|
||||
|
||||
@@ -17,6 +17,10 @@ func (s *StashEntry) RefName() string {
|
||||
return fmt.Sprintf("stash@{%d}", s.Index)
|
||||
}
|
||||
|
||||
func (s *StashEntry) ShortRefName() string {
|
||||
return s.RefName()
|
||||
}
|
||||
|
||||
func (s *StashEntry) ParentRefName() string {
|
||||
return s.RefName() + "^"
|
||||
}
|
||||
|
||||
@@ -16,6 +16,10 @@ func (t *Tag) RefName() string {
|
||||
return t.Name
|
||||
}
|
||||
|
||||
func (t *Tag) ShortRefName() string {
|
||||
return t.RefName()
|
||||
}
|
||||
|
||||
func (t *Tag) ParentRefName() string {
|
||||
return t.RefName() + "^"
|
||||
}
|
||||
|
||||
@@ -14,6 +14,8 @@ type ICmdObjBuilder interface {
|
||||
New(args []string) ICmdObj
|
||||
// NewShell takes a string like `git commit` and returns an executable shell command for it e.g. `sh -c 'git commit'`
|
||||
NewShell(commandStr string) ICmdObj
|
||||
// Like NewShell, but uses the user's shell rather than "bash", and passes -i to it
|
||||
NewInteractiveShell(commandStr string) ICmdObj
|
||||
// Quote wraps a string in quotes with any necessary escaping applied. The reason for bundling this up with the other methods in this interface is that we basically always need to make use of this when creating new command objects.
|
||||
Quote(str string) string
|
||||
}
|
||||
@@ -43,10 +45,23 @@ func (self *CmdObjBuilder) NewWithEnviron(args []string, env []string) ICmdObj {
|
||||
}
|
||||
|
||||
func (self *CmdObjBuilder) NewShell(commandStr string) ICmdObj {
|
||||
var quotedCommand string
|
||||
quotedCommand := self.quotedCommandString(commandStr)
|
||||
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s", self.platform.Shell, self.platform.ShellArg, quotedCommand))
|
||||
|
||||
return self.New(cmdArgs)
|
||||
}
|
||||
|
||||
func (self *CmdObjBuilder) NewInteractiveShell(commandStr string) ICmdObj {
|
||||
quotedCommand := self.quotedCommandString(commandStr)
|
||||
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s %s", self.platform.InteractiveShell, self.platform.InteractiveShellArg, self.platform.ShellArg, quotedCommand))
|
||||
|
||||
return self.New(cmdArgs)
|
||||
}
|
||||
|
||||
func (self *CmdObjBuilder) quotedCommandString(commandStr string) string {
|
||||
// Windows does not seem to like quotes around the command
|
||||
if self.platform.OS == "windows" {
|
||||
quotedCommand = strings.NewReplacer(
|
||||
return strings.NewReplacer(
|
||||
"^", "^^",
|
||||
"&", "^&",
|
||||
"|", "^|",
|
||||
@@ -54,13 +69,9 @@ func (self *CmdObjBuilder) NewShell(commandStr string) ICmdObj {
|
||||
">", "^>",
|
||||
"%", "^%",
|
||||
).Replace(commandStr)
|
||||
} else {
|
||||
quotedCommand = self.Quote(commandStr)
|
||||
}
|
||||
|
||||
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s", self.platform.Shell, self.platform.ShellArg, quotedCommand))
|
||||
|
||||
return self.New(cmdArgs)
|
||||
return self.Quote(commandStr)
|
||||
}
|
||||
|
||||
func (self *CmdObjBuilder) CloneWithNewRunner(decorate func(ICmdObjRunner) ICmdObjRunner) *CmdObjBuilder {
|
||||
|
||||
@@ -319,7 +319,7 @@ func (self *cmdObjRunner) getCredentialPromptFn(cmdObj ICmdObj) (func(Credential
|
||||
}
|
||||
|
||||
// runAndDetectCredentialRequest detect a username / password / passphrase question in a command
|
||||
// promptUserForCredential is a function that gets executed when this function detect you need to fillin a password or passphrase
|
||||
// promptUserForCredential is a function that gets executed when this function detect you need to fill in a password or passphrase
|
||||
// The promptUserForCredential argument will be "username", "password" or "passphrase" and expects the user's password/passphrase or username back
|
||||
func (self *cmdObjRunner) runAndDetectCredentialRequest(
|
||||
cmdObj ICmdObj,
|
||||
|
||||
@@ -51,11 +51,13 @@ func NewDummyCmdObjBuilder(runner ICmdObjRunner) *CmdObjBuilder {
|
||||
}
|
||||
|
||||
var dummyPlatform = &Platform{
|
||||
OS: "darwin",
|
||||
Shell: "bash",
|
||||
ShellArg: "-c",
|
||||
OpenCommand: "open {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
OS: "darwin",
|
||||
Shell: "bash",
|
||||
InteractiveShell: "bash",
|
||||
ShellArg: "-c",
|
||||
InteractiveShellArg: "-i",
|
||||
OpenCommand: "open {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
}
|
||||
|
||||
func NewDummyOSCommandWithRunner(runner *FakeCmdObjRunner) *OSCommand {
|
||||
|
||||
@@ -35,11 +35,13 @@ type OSCommand struct {
|
||||
|
||||
// Platform stores the os state
|
||||
type Platform struct {
|
||||
OS string
|
||||
Shell string
|
||||
ShellArg string
|
||||
OpenCommand string
|
||||
OpenLinkCommand string
|
||||
OS string
|
||||
Shell string
|
||||
InteractiveShell string
|
||||
ShellArg string
|
||||
InteractiveShellArg string
|
||||
OpenCommand string
|
||||
OpenLinkCommand string
|
||||
}
|
||||
|
||||
// NewOSCommand os command runner
|
||||
@@ -78,10 +80,10 @@ func FileType(path string) string {
|
||||
}
|
||||
|
||||
func (c *OSCommand) OpenFile(filename string) error {
|
||||
commandTemplate := c.UserConfig.OS.Open
|
||||
commandTemplate := c.UserConfig().OS.Open
|
||||
if commandTemplate == "" {
|
||||
// Legacy support
|
||||
commandTemplate = c.UserConfig.OS.OpenCommand
|
||||
commandTemplate = c.UserConfig().OS.OpenCommand
|
||||
}
|
||||
if commandTemplate == "" {
|
||||
commandTemplate = config.GetPlatformDefaultConfig().Open
|
||||
@@ -94,10 +96,10 @@ func (c *OSCommand) OpenFile(filename string) error {
|
||||
}
|
||||
|
||||
func (c *OSCommand) OpenLink(link string) error {
|
||||
commandTemplate := c.UserConfig.OS.OpenLink
|
||||
commandTemplate := c.UserConfig().OS.OpenLink
|
||||
if commandTemplate == "" {
|
||||
// Legacy support
|
||||
commandTemplate = c.UserConfig.OS.OpenLinkCommand
|
||||
commandTemplate = c.UserConfig().OS.OpenLinkCommand
|
||||
}
|
||||
if commandTemplate == "" {
|
||||
commandTemplate = config.GetPlatformDefaultConfig().OpenLink
|
||||
@@ -292,8 +294,8 @@ func (c *OSCommand) CopyToClipboard(str string) error {
|
||||
},
|
||||
)
|
||||
c.LogCommand(msg, false)
|
||||
if c.UserConfig.OS.CopyToClipboardCmd != "" {
|
||||
cmdStr := utils.ResolvePlaceholderString(c.UserConfig.OS.CopyToClipboardCmd, map[string]string{
|
||||
if c.UserConfig().OS.CopyToClipboardCmd != "" {
|
||||
cmdStr := utils.ResolvePlaceholderString(c.UserConfig().OS.CopyToClipboardCmd, map[string]string{
|
||||
"text": c.Cmd.Quote(str),
|
||||
})
|
||||
return c.Cmd.NewShell(cmdStr).Run()
|
||||
@@ -305,8 +307,8 @@ func (c *OSCommand) CopyToClipboard(str string) error {
|
||||
func (c *OSCommand) PasteFromClipboard() (string, error) {
|
||||
var s string
|
||||
var err error
|
||||
if c.UserConfig.OS.CopyToClipboardCmd != "" {
|
||||
cmdStr := c.UserConfig.OS.ReadFromClipboardCmd
|
||||
if c.UserConfig().OS.CopyToClipboardCmd != "" {
|
||||
cmdStr := c.UserConfig().OS.ReadFromClipboardCmd
|
||||
s, err = c.Cmd.NewShell(cmdStr).RunWithOutput()
|
||||
} else {
|
||||
s, err = clipboard.ReadAll()
|
||||
|
||||
@@ -4,15 +4,26 @@
|
||||
package oscommands
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func GetPlatform() *Platform {
|
||||
return &Platform{
|
||||
OS: runtime.GOOS,
|
||||
Shell: "bash",
|
||||
ShellArg: "-c",
|
||||
OpenCommand: "open {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
OS: runtime.GOOS,
|
||||
Shell: "bash",
|
||||
InteractiveShell: getUserShell(),
|
||||
ShellArg: "-c",
|
||||
InteractiveShellArg: "-i",
|
||||
OpenCommand: "open {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
}
|
||||
}
|
||||
|
||||
func getUserShell() string {
|
||||
if shell := os.Getenv("SHELL"); shell != "" {
|
||||
return shell
|
||||
}
|
||||
|
||||
return "bash"
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ func TestOSCommandOpenFileDarwin(t *testing.T) {
|
||||
for _, s := range scenarios {
|
||||
oSCmd := NewDummyOSCommandWithRunner(s.runner)
|
||||
oSCmd.Platform.OS = "darwin"
|
||||
oSCmd.UserConfig.OS.Open = "open {{filename}}"
|
||||
oSCmd.UserConfig().OS.Open = "open {{filename}}"
|
||||
|
||||
s.test(oSCmd.OpenFile(s.filename))
|
||||
}
|
||||
@@ -135,7 +135,7 @@ func TestOSCommandOpenFileLinux(t *testing.T) {
|
||||
for _, s := range scenarios {
|
||||
oSCmd := NewDummyOSCommandWithRunner(s.runner)
|
||||
oSCmd.Platform.OS = "linux"
|
||||
oSCmd.UserConfig.OS.Open = `xdg-open {{filename}} > /dev/null`
|
||||
oSCmd.UserConfig().OS.Open = `xdg-open {{filename}} > /dev/null`
|
||||
|
||||
s.test(oSCmd.OpenFile(s.filename))
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package oscommands
|
||||
|
||||
func GetPlatform() *Platform {
|
||||
return &Platform{
|
||||
OS: "windows",
|
||||
Shell: "cmd",
|
||||
ShellArg: "/c",
|
||||
OS: "windows",
|
||||
Shell: "cmd",
|
||||
InteractiveShell: "cmd",
|
||||
ShellArg: "/c",
|
||||
InteractiveShellArg: "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,7 +71,7 @@ func TestOSCommandOpenFileWindows(t *testing.T) {
|
||||
}
|
||||
oSCmd.Platform = platform
|
||||
oSCmd.Cmd.platform = platform
|
||||
oSCmd.UserConfig.OS.OpenCommand = `start "" {{filename}}`
|
||||
oSCmd.UserConfig().OS.OpenCommand = `start "" {{filename}}`
|
||||
|
||||
s.test(oSCmd.OpenFile(s.filename))
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package patch
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@@ -54,7 +53,7 @@ func (self *Patch) Lines() []*PatchLine {
|
||||
|
||||
// Returns the patch line index of the first line in the given hunk
|
||||
func (self *Patch) HunkStartIdx(hunkIndex int) int {
|
||||
hunkIndex = utils.Clamp(hunkIndex, 0, len(self.hunks)-1)
|
||||
hunkIndex = lo.Clamp(hunkIndex, 0, len(self.hunks)-1)
|
||||
|
||||
result := len(self.header)
|
||||
for i := 0; i < hunkIndex; i++ {
|
||||
@@ -65,7 +64,7 @@ func (self *Patch) HunkStartIdx(hunkIndex int) int {
|
||||
|
||||
// Returns the patch line index of the last line in the given hunk
|
||||
func (self *Patch) HunkEndIdx(hunkIndex int) int {
|
||||
hunkIndex = utils.Clamp(hunkIndex, 0, len(self.hunks)-1)
|
||||
hunkIndex = lo.Clamp(hunkIndex, 0, len(self.hunks)-1)
|
||||
|
||||
return self.HunkStartIdx(hunkIndex) + self.hunks[hunkIndex].lineCount() - 1
|
||||
}
|
||||
@@ -118,7 +117,7 @@ func (self *Patch) HunkContainingLine(idx int) int {
|
||||
|
||||
// Returns the patch line index of the next change (i.e. addition or deletion).
|
||||
func (self *Patch) GetNextChangeIdx(idx int) int {
|
||||
idx = utils.Clamp(idx, 0, self.LineCount()-1)
|
||||
idx = lo.Clamp(idx, 0, self.LineCount()-1)
|
||||
|
||||
lines := self.Lines()
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/sirupsen/logrus"
|
||||
@@ -11,10 +13,18 @@ import (
|
||||
type Common struct {
|
||||
Log *logrus.Entry
|
||||
Tr *i18n.TranslationSet
|
||||
UserConfig *config.UserConfig
|
||||
userConfig atomic.Pointer[config.UserConfig]
|
||||
AppState *config.AppState
|
||||
Debug bool
|
||||
// for interacting with the filesystem. We use afero rather than the default
|
||||
// `os` package for the sake of mocking the filesystem in tests
|
||||
Fs afero.Fs
|
||||
}
|
||||
|
||||
func (c *Common) UserConfig() *config.UserConfig {
|
||||
return c.userConfig.Load()
|
||||
}
|
||||
|
||||
func (c *Common) SetUserConfig(userConfig *config.UserConfig) {
|
||||
c.userConfig.Store(userConfig)
|
||||
}
|
||||
|
||||
@@ -2,29 +2,31 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils/yaml_utils"
|
||||
"github.com/samber/lo"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// AppConfig contains the base configuration fields required for lazygit.
|
||||
type AppConfig struct {
|
||||
Debug bool `long:"debug" env:"DEBUG" default:"false"`
|
||||
Version string `long:"version" env:"VERSION" default:"unversioned"`
|
||||
BuildDate string `long:"build-date" env:"BUILD_DATE"`
|
||||
Name string `long:"name" env:"NAME" default:"lazygit"`
|
||||
BuildSource string `long:"build-source" env:"BUILD_SOURCE" default:""`
|
||||
UserConfig *UserConfig
|
||||
UserConfigPaths []string
|
||||
DeafultConfFiles bool
|
||||
UserConfigDir string
|
||||
TempDir string
|
||||
AppState *AppState
|
||||
IsNewRepo bool
|
||||
debug bool `long:"debug" env:"DEBUG" default:"false"`
|
||||
version string `long:"version" env:"VERSION" default:"unversioned"`
|
||||
buildDate string `long:"build-date" env:"BUILD_DATE"`
|
||||
name string `long:"name" env:"NAME" default:"lazygit"`
|
||||
buildSource string `long:"build-source" env:"BUILD_SOURCE" default:""`
|
||||
userConfig *UserConfig
|
||||
globalUserConfigFiles []*ConfigFile
|
||||
userConfigFiles []*ConfigFile
|
||||
userConfigDir string
|
||||
tempDir string
|
||||
appState *AppState
|
||||
}
|
||||
|
||||
type AppConfigurer interface {
|
||||
@@ -38,13 +40,29 @@ type AppConfigurer interface {
|
||||
GetUserConfig() *UserConfig
|
||||
GetUserConfigPaths() []string
|
||||
GetUserConfigDir() string
|
||||
ReloadUserConfig() error
|
||||
ReloadUserConfigForRepo(repoConfigFiles []*ConfigFile) error
|
||||
ReloadChangedUserConfigFiles() (error, bool)
|
||||
GetTempDir() string
|
||||
|
||||
GetAppState() *AppState
|
||||
SaveAppState() error
|
||||
}
|
||||
|
||||
type ConfigFilePolicy int
|
||||
|
||||
const (
|
||||
ConfigFilePolicyCreateIfMissing ConfigFilePolicy = iota
|
||||
ConfigFilePolicyErrorIfMissing
|
||||
ConfigFilePolicySkipIfMissing
|
||||
)
|
||||
|
||||
type ConfigFile struct {
|
||||
Path string
|
||||
Policy ConfigFilePolicy
|
||||
modDate time.Time
|
||||
exists bool
|
||||
}
|
||||
|
||||
// NewAppConfig makes a new app config
|
||||
func NewAppConfig(
|
||||
name string,
|
||||
@@ -60,17 +78,22 @@ func NewAppConfig(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var userConfigPaths []string
|
||||
var configFiles []*ConfigFile
|
||||
customConfigFiles := os.Getenv("LG_CONFIG_FILE")
|
||||
if customConfigFiles != "" {
|
||||
// Load user defined config files
|
||||
userConfigPaths = strings.Split(customConfigFiles, ",")
|
||||
userConfigPaths := strings.Split(customConfigFiles, ",")
|
||||
configFiles = lo.Map(userConfigPaths, func(path string, _ int) *ConfigFile {
|
||||
return &ConfigFile{Path: path, Policy: ConfigFilePolicyErrorIfMissing}
|
||||
})
|
||||
} else {
|
||||
// Load default config files
|
||||
userConfigPaths = []string{filepath.Join(configDir, ConfigFilename)}
|
||||
path := filepath.Join(configDir, ConfigFilename)
|
||||
configFile := &ConfigFile{Path: path, Policy: ConfigFilePolicyCreateIfMissing}
|
||||
configFiles = []*ConfigFile{configFile}
|
||||
}
|
||||
|
||||
userConfig, err := loadUserConfigWithDefaults(userConfigPaths)
|
||||
userConfig, err := loadUserConfigWithDefaults(configFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -92,26 +115,22 @@ func NewAppConfig(
|
||||
}
|
||||
|
||||
appConfig := &AppConfig{
|
||||
Name: name,
|
||||
Version: version,
|
||||
BuildDate: date,
|
||||
Debug: debuggingFlag,
|
||||
BuildSource: buildSource,
|
||||
UserConfig: userConfig,
|
||||
UserConfigPaths: userConfigPaths,
|
||||
UserConfigDir: configDir,
|
||||
TempDir: tempDir,
|
||||
AppState: appState,
|
||||
IsNewRepo: false,
|
||||
name: name,
|
||||
version: version,
|
||||
buildDate: date,
|
||||
debug: debuggingFlag,
|
||||
buildSource: buildSource,
|
||||
userConfig: userConfig,
|
||||
globalUserConfigFiles: configFiles,
|
||||
userConfigFiles: configFiles,
|
||||
userConfigDir: configDir,
|
||||
tempDir: tempDir,
|
||||
appState: appState,
|
||||
}
|
||||
|
||||
return appConfig, nil
|
||||
}
|
||||
|
||||
func isCustomConfigFile(path string) bool {
|
||||
return path != filepath.Join(ConfigDir(), ConfigFilename)
|
||||
}
|
||||
|
||||
func ConfigDir() string {
|
||||
_, filePath := findConfigFile("config.yml")
|
||||
|
||||
@@ -123,32 +142,48 @@ func findOrCreateConfigDir() (string, error) {
|
||||
return folder, os.MkdirAll(folder, 0o755)
|
||||
}
|
||||
|
||||
func loadUserConfigWithDefaults(configFiles []string) (*UserConfig, error) {
|
||||
func loadUserConfigWithDefaults(configFiles []*ConfigFile) (*UserConfig, error) {
|
||||
return loadUserConfig(configFiles, GetDefaultConfig())
|
||||
}
|
||||
|
||||
func loadUserConfig(configFiles []string, base *UserConfig) (*UserConfig, error) {
|
||||
for _, path := range configFiles {
|
||||
if _, err := os.Stat(path); err != nil {
|
||||
func loadUserConfig(configFiles []*ConfigFile, base *UserConfig) (*UserConfig, error) {
|
||||
for _, configFile := range configFiles {
|
||||
path := configFile.Path
|
||||
statInfo, err := os.Stat(path)
|
||||
if err == nil {
|
||||
configFile.exists = true
|
||||
configFile.modDate = statInfo.ModTime()
|
||||
} else {
|
||||
if !os.IsNotExist(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// if use has supplied their own custom config file path(s), we assume
|
||||
// the files have already been created, so we won't go and create them here.
|
||||
if isCustomConfigFile(path) {
|
||||
switch configFile.Policy {
|
||||
case ConfigFilePolicyErrorIfMissing:
|
||||
return nil, err
|
||||
}
|
||||
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
if os.IsPermission(err) {
|
||||
// apparently when people have read-only permissions they prefer us to fail silently
|
||||
continue
|
||||
case ConfigFilePolicySkipIfMissing:
|
||||
configFile.exists = false
|
||||
continue
|
||||
|
||||
case ConfigFilePolicyCreateIfMissing:
|
||||
file, err := os.Create(path)
|
||||
if err != nil {
|
||||
if os.IsPermission(err) {
|
||||
// apparently when people have read-only permissions they prefer us to fail silently
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return nil, err
|
||||
file.Close()
|
||||
|
||||
configFile.exists = true
|
||||
statInfo, err := os.Stat(configFile.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
configFile.modDate = statInfo.ModTime()
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
|
||||
content, err := os.ReadFile(path)
|
||||
@@ -161,10 +196,14 @@ func loadUserConfig(configFiles []string, base *UserConfig) (*UserConfig, error)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingCustomCommands := base.CustomCommands
|
||||
|
||||
if err := yaml.Unmarshal(content, base); err != nil {
|
||||
return nil, fmt.Errorf("The config at `%s` couldn't be parsed, please inspect it before opening up an issue.\n%w", path, err)
|
||||
}
|
||||
|
||||
base.CustomCommands = append(base.CustomCommands, existingCustomCommands...)
|
||||
|
||||
if err := base.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("The config at `%s` has a validation error.\n%w", path, err)
|
||||
}
|
||||
@@ -184,6 +223,12 @@ func migrateUserConfig(path string, content []byte) ([]byte, error) {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
|
||||
}
|
||||
|
||||
changedContent, err = yaml_utils.RenameYamlKey(changedContent, []string{"keybinding", "universal", "executeCustomCommand"},
|
||||
"executeShellCommand")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
|
||||
}
|
||||
|
||||
changedContent, err = changeNullKeybindingsToDisabled(changedContent)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
|
||||
@@ -214,53 +259,81 @@ func changeNullKeybindingsToDisabled(changedContent []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
func (c *AppConfig) GetDebug() bool {
|
||||
return c.Debug
|
||||
return c.debug
|
||||
}
|
||||
|
||||
func (c *AppConfig) GetVersion() string {
|
||||
return c.Version
|
||||
return c.version
|
||||
}
|
||||
|
||||
func (c *AppConfig) GetName() string {
|
||||
return c.Name
|
||||
return c.name
|
||||
}
|
||||
|
||||
// GetBuildSource returns the source of the build. For builds from goreleaser
|
||||
// this will be binaryBuild
|
||||
func (c *AppConfig) GetBuildSource() string {
|
||||
return c.BuildSource
|
||||
return c.buildSource
|
||||
}
|
||||
|
||||
// GetUserConfig returns the user config
|
||||
func (c *AppConfig) GetUserConfig() *UserConfig {
|
||||
return c.UserConfig
|
||||
return c.userConfig
|
||||
}
|
||||
|
||||
// GetAppState returns the app state
|
||||
func (c *AppConfig) GetAppState() *AppState {
|
||||
return c.AppState
|
||||
return c.appState
|
||||
}
|
||||
|
||||
func (c *AppConfig) GetUserConfigPaths() []string {
|
||||
return c.UserConfigPaths
|
||||
return lo.FilterMap(c.userConfigFiles, func(f *ConfigFile, _ int) (string, bool) {
|
||||
return f.Path, f.exists
|
||||
})
|
||||
}
|
||||
|
||||
func (c *AppConfig) GetUserConfigDir() string {
|
||||
return c.UserConfigDir
|
||||
return c.userConfigDir
|
||||
}
|
||||
|
||||
func (c *AppConfig) ReloadUserConfig() error {
|
||||
userConfig, err := loadUserConfigWithDefaults(c.UserConfigPaths)
|
||||
func (c *AppConfig) ReloadUserConfigForRepo(repoConfigFiles []*ConfigFile) error {
|
||||
configFiles := append(c.globalUserConfigFiles, repoConfigFiles...)
|
||||
userConfig, err := loadUserConfigWithDefaults(configFiles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.UserConfig = userConfig
|
||||
c.userConfig = userConfig
|
||||
c.userConfigFiles = configFiles
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *AppConfig) ReloadChangedUserConfigFiles() (error, bool) {
|
||||
fileHasChanged := func(f *ConfigFile) bool {
|
||||
info, err := os.Stat(f.Path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
// If we can't stat the file, assume it hasn't changed
|
||||
return false
|
||||
}
|
||||
exists := err == nil
|
||||
return exists != f.exists || (exists && info.ModTime() != f.modDate)
|
||||
}
|
||||
|
||||
if lo.NoneBy(c.userConfigFiles, fileHasChanged) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
userConfig, err := loadUserConfigWithDefaults(c.userConfigFiles)
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
|
||||
c.userConfig = userConfig
|
||||
return nil, true
|
||||
}
|
||||
|
||||
func (c *AppConfig) GetTempDir() string {
|
||||
return c.TempDir
|
||||
return c.tempDir
|
||||
}
|
||||
|
||||
// findConfigFile looks for a possibly existing config file.
|
||||
@@ -299,14 +372,9 @@ func stateFilePath(filename string) (string, error) {
|
||||
return xdg.StateFile(filepath.Join("lazygit", filename))
|
||||
}
|
||||
|
||||
// ConfigFilename returns the filename of the default config file
|
||||
func (c *AppConfig) ConfigFilename() string {
|
||||
return filepath.Join(c.UserConfigDir, ConfigFilename)
|
||||
}
|
||||
|
||||
// SaveAppState marshalls the AppState struct and writes it to the disk
|
||||
func (c *AppConfig) SaveAppState() error {
|
||||
marshalledAppState, err := yaml.Marshal(c.AppState)
|
||||
marshalledAppState, err := yaml.Marshal(c.appState)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -357,6 +425,24 @@ func loadAppState() (*AppState, error) {
|
||||
return appState, nil
|
||||
}
|
||||
|
||||
// SaveGlobalUserConfig saves the UserConfig back to disk. This is only used in
|
||||
// integration tests, so we are a bit sloppy with error handling.
|
||||
func (c *AppConfig) SaveGlobalUserConfig() {
|
||||
if len(c.globalUserConfigFiles) != 1 {
|
||||
panic("expected exactly one global user config file")
|
||||
}
|
||||
|
||||
yamlContent, err := yaml.Marshal(c.userConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("error marshalling user config: %v", err)
|
||||
}
|
||||
|
||||
err = os.WriteFile(c.globalUserConfigFiles[0].Path, yamlContent, 0o644)
|
||||
if err != nil {
|
||||
log.Fatalf("error saving user config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// AppState stores data between runs of the app like when the last update check
|
||||
// was performed and which other repos have been checked out
|
||||
type AppState struct {
|
||||
@@ -365,8 +451,10 @@ type AppState struct {
|
||||
StartupPopupVersion int
|
||||
LastVersion string // this is the last version the user was using, for the purpose of showing release notes
|
||||
|
||||
// these are for custom commands typed in directly, not for custom commands in the lazygit config
|
||||
CustomCommandsHistory []string
|
||||
// these are for shell commands typed in directly, not for custom commands in the lazygit config.
|
||||
// For backwards compatibility we keep the old name in yaml files.
|
||||
ShellCommandsHistory []string `yaml:"customcommandshistory"`
|
||||
|
||||
HideCommandLog bool
|
||||
IgnoreWhitespaceInDiffView bool
|
||||
DiffContextSize int
|
||||
|
||||
@@ -7,12 +7,12 @@ import (
|
||||
// NewDummyAppConfig creates a new dummy AppConfig for testing
|
||||
func NewDummyAppConfig() *AppConfig {
|
||||
appConfig := &AppConfig{
|
||||
Name: "lazygit",
|
||||
Version: "unversioned",
|
||||
Debug: false,
|
||||
UserConfig: GetDefaultConfig(),
|
||||
AppState: &AppState{},
|
||||
name: "lazygit",
|
||||
version: "unversioned",
|
||||
debug: false,
|
||||
userConfig: GetDefaultConfig(),
|
||||
appState: &AppState{},
|
||||
}
|
||||
_ = yaml.Unmarshal([]byte{}, appConfig.AppState)
|
||||
_ = yaml.Unmarshal([]byte{}, appConfig.appState)
|
||||
return appConfig
|
||||
}
|
||||
|
||||
@@ -113,6 +113,13 @@ func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset
|
||||
openDirInEditorTemplate: "xed -- {{dir}}",
|
||||
suspend: returnBool(false),
|
||||
},
|
||||
"zed": {
|
||||
editTemplate: "zed -- {{filename}}",
|
||||
editAtLineTemplate: "zed -- {{filename}}:{{line}}",
|
||||
editAtLineAndWaitTemplate: "zed --wait -- {{filename}}:{{line}}",
|
||||
openDirInEditorTemplate: "zed -- {{dir}}",
|
||||
suspend: returnBool(false),
|
||||
},
|
||||
}
|
||||
|
||||
// Some of our presets have a different name than the editor they are using.
|
||||
|
||||
@@ -161,6 +161,10 @@ type GuiConfig struct {
|
||||
// Status panel view.
|
||||
// One of 'dashboard' (default) | 'allBranchesLog'
|
||||
StatusPanelView string `yaml:"statusPanelView" jsonschema:"enum=dashboard,enum=allBranchesLog"`
|
||||
// If true, jump to the Files panel after popping a stash
|
||||
SwitchToFilesAfterStashPop bool `yaml:"switchToFilesAfterStashPop"`
|
||||
// If true, jump to the Files panel after applying a stash
|
||||
SwitchToFilesAfterStashApply bool `yaml:"switchToFilesAfterStashApply"`
|
||||
}
|
||||
|
||||
func (c *GuiConfig) UseFuzzySearch() bool {
|
||||
@@ -224,6 +228,11 @@ type GitConfig struct {
|
||||
AutoRefresh bool `yaml:"autoRefresh"`
|
||||
// If true, pass the --all arg to git fetch
|
||||
FetchAll bool `yaml:"fetchAll"`
|
||||
// If true, lazygit will automatically stage files that used to have merge
|
||||
// conflicts but no longer do; and it will also ask you if you want to
|
||||
// continue a merge or rebase if you've resolved all conflicts. If false, it
|
||||
// won't do either of these things.
|
||||
AutoStageResolvedConflicts bool `yaml:"autoStageResolvedConflicts"`
|
||||
// Command used when displaying the current branch git log in the main window
|
||||
BranchLogCmd string `yaml:"branchLogCmd"`
|
||||
// Command used to display git log of all branches in the main window.
|
||||
@@ -366,6 +375,7 @@ type KeybindingUniversalConfig struct {
|
||||
NextBlockAlt2 string `yaml:"nextBlock-alt2"`
|
||||
PrevBlockAlt2 string `yaml:"prevBlock-alt2"`
|
||||
JumpToBlock []string `yaml:"jumpToBlock"`
|
||||
FocusMainView string `yaml:"focusMainView"`
|
||||
NextMatch string `yaml:"nextMatch"`
|
||||
PrevMatch string `yaml:"prevMatch"`
|
||||
StartSearch string `yaml:"startSearch"`
|
||||
@@ -385,7 +395,7 @@ type KeybindingUniversalConfig struct {
|
||||
ScrollDownMainAlt1 string `yaml:"scrollDownMain-alt1"`
|
||||
ScrollUpMainAlt2 string `yaml:"scrollUpMain-alt2"`
|
||||
ScrollDownMainAlt2 string `yaml:"scrollDownMain-alt2"`
|
||||
ExecuteCustomCommand string `yaml:"executeCustomCommand"`
|
||||
ExecuteShellCommand string `yaml:"executeShellCommand"`
|
||||
CreateRebaseOptionsMenu string `yaml:"createRebaseOptionsMenu"`
|
||||
Push string `yaml:"pushFiles"` // 'Files' appended for legacy reasons
|
||||
Pull string `yaml:"pullFiles"` // 'Files' appended for legacy reasons
|
||||
@@ -541,7 +551,7 @@ type OSConfig struct {
|
||||
|
||||
// A built-in preset that sets all of the above settings. Supported presets
|
||||
// are defined in the getPreset function in editor_presets.go.
|
||||
EditPreset string `yaml:"editPreset,omitempty" jsonschema:"example=vim,example=nvim,example=emacs,example=nano,example=vscode,example=sublime,example=kakoune,example=helix,example=xcode"`
|
||||
EditPreset string `yaml:"editPreset,omitempty" jsonschema:"example=vim,example=nvim,example=emacs,example=nano,example=vscode,example=sublime,example=kakoune,example=helix,example=xcode,example=zed"`
|
||||
|
||||
// Command for opening a file, as if the file is double-clicked. Should
|
||||
// contain "{{filename}}", but doesn't support "{{line}}".
|
||||
@@ -724,7 +734,9 @@ func GetDefaultConfig() *UserConfig {
|
||||
Frames: []string{"|", "/", "-", "\\"},
|
||||
Rate: 50,
|
||||
},
|
||||
StatusPanelView: "dashboard",
|
||||
StatusPanelView: "dashboard",
|
||||
SwitchToFilesAfterStashPop: true,
|
||||
SwitchToFilesAfterStashApply: true,
|
||||
},
|
||||
Git: GitConfig{
|
||||
Paging: PagingConfig{
|
||||
@@ -753,6 +765,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
AutoFetch: true,
|
||||
AutoRefresh: true,
|
||||
FetchAll: true,
|
||||
AutoStageResolvedConflicts: true,
|
||||
BranchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --",
|
||||
AllBranchesLogCmd: "git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium",
|
||||
DisableForcePushing: false,
|
||||
@@ -804,6 +817,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
PrevBlockAlt2: "<backtab>",
|
||||
NextBlockAlt2: "<tab>",
|
||||
JumpToBlock: []string{"1", "2", "3", "4", "5"},
|
||||
FocusMainView: "0",
|
||||
NextMatch: "n",
|
||||
PrevMatch: "N",
|
||||
StartSearch: "/",
|
||||
@@ -824,7 +838,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
ScrollDownMainAlt1: "J",
|
||||
ScrollUpMainAlt2: "<c-u>",
|
||||
ScrollDownMainAlt2: "<c-d>",
|
||||
ExecuteCustomCommand: ":",
|
||||
ExecuteShellCommand: ":",
|
||||
CreateRebaseOptionsMenu: "m",
|
||||
Push: "P",
|
||||
Pull: "p",
|
||||
|
||||
@@ -25,7 +25,7 @@ func (self *BackgroundRoutineMgr) PauseBackgroundRefreshes(pause bool) {
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
|
||||
userConfig := self.gui.UserConfig
|
||||
userConfig := self.gui.UserConfig()
|
||||
|
||||
if userConfig.Git.AutoFetch {
|
||||
fetchInterval := userConfig.Refresher.FetchInterval
|
||||
@@ -77,13 +77,13 @@ func (self *BackgroundRoutineMgr) startBackgroundFetch() {
|
||||
self.gui.waitForIntro.Wait()
|
||||
|
||||
isNew := self.gui.IsNewRepo
|
||||
userConfig := self.gui.UserConfig
|
||||
userConfig := self.gui.UserConfig()
|
||||
if !isNew {
|
||||
time.After(time.Duration(userConfig.Refresher.FetchInterval) * time.Second)
|
||||
}
|
||||
err := self.backgroundFetch()
|
||||
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
|
||||
_ = self.gui.c.Alert(self.gui.c.Tr.NoAutomaticGitFetchTitle, self.gui.c.Tr.NoAutomaticGitFetchBody)
|
||||
self.gui.c.Alert(self.gui.c.Tr.NoAutomaticGitFetchTitle, self.gui.c.Tr.NoAutomaticGitFetchBody)
|
||||
} else {
|
||||
self.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), self.gui.stopChan, func() error {
|
||||
err := self.backgroundFetch()
|
||||
|
||||
@@ -55,11 +55,11 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
|
||||
func (gui *Gui) printCommandLogHeader() {
|
||||
introStr := fmt.Sprintf(
|
||||
gui.c.Tr.CommandLogHeader,
|
||||
keybindings.Label(gui.c.UserConfig.Keybinding.Universal.ExtrasMenu),
|
||||
keybindings.Label(gui.c.UserConfig().Keybinding.Universal.ExtrasMenu),
|
||||
)
|
||||
fmt.Fprintln(gui.Views.Extras, style.FgCyan.Sprint(introStr))
|
||||
|
||||
if gui.c.UserConfig.Gui.ShowRandomTip {
|
||||
if gui.c.UserConfig().Gui.ShowRandomTip {
|
||||
fmt.Fprintf(
|
||||
gui.Views.Extras,
|
||||
"%s: %s",
|
||||
@@ -70,7 +70,7 @@ func (gui *Gui) printCommandLogHeader() {
|
||||
}
|
||||
|
||||
func (gui *Gui) getRandomTip() string {
|
||||
config := gui.c.UserConfig.Keybinding
|
||||
config := gui.c.UserConfig().Keybinding
|
||||
|
||||
formattedKey := func(key string) string {
|
||||
return keybindings.Label(key)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package gui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
@@ -37,9 +36,9 @@ func NewContextMgr(
|
||||
|
||||
// use when you don't want to return to the original context upon
|
||||
// hitting escape: you want to go that context's parent instead.
|
||||
func (self *ContextMgr) Replace(c types.Context) error {
|
||||
func (self *ContextMgr) Replace(c types.Context) {
|
||||
if !c.IsFocusable() {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
self.Lock()
|
||||
@@ -51,14 +50,14 @@ func (self *ContextMgr) Replace(c types.Context) error {
|
||||
self.ContextStack = append(self.ContextStack[0:len(self.ContextStack)-1], c)
|
||||
}
|
||||
|
||||
defer self.Unlock()
|
||||
self.Unlock()
|
||||
|
||||
return self.ActivateContext(c, types.OnFocusOpts{})
|
||||
self.Activate(c, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (self *ContextMgr) Push(c types.Context, opts ...types.OnFocusOpts) error {
|
||||
func (self *ContextMgr) Push(c types.Context, opts ...types.OnFocusOpts) {
|
||||
if len(opts) > 1 {
|
||||
return errors.New("cannot pass multiple opts to Push")
|
||||
panic("cannot pass multiple opts to Push")
|
||||
}
|
||||
|
||||
singleOpts := types.OnFocusOpts{}
|
||||
@@ -68,22 +67,18 @@ func (self *ContextMgr) Push(c types.Context, opts ...types.OnFocusOpts) error {
|
||||
}
|
||||
|
||||
if !c.IsFocusable() {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
contextsToDeactivate, contextToActivate := self.pushToContextStack(c)
|
||||
|
||||
for _, contextToDeactivate := range contextsToDeactivate {
|
||||
if err := self.deactivateContext(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()}); err != nil {
|
||||
return err
|
||||
}
|
||||
self.deactivate(contextToDeactivate, types.OnFocusLostOpts{NewContextKey: c.GetKey()})
|
||||
}
|
||||
|
||||
if contextToActivate == nil {
|
||||
return nil
|
||||
if contextToActivate != nil {
|
||||
self.Activate(contextToActivate, singleOpts)
|
||||
}
|
||||
|
||||
return self.ActivateContext(contextToActivate, singleOpts)
|
||||
}
|
||||
|
||||
// Adjusts the context stack based on the context that's being pushed and
|
||||
@@ -144,13 +139,13 @@ func (self *ContextMgr) pushToContextStack(c types.Context) ([]types.Context, ty
|
||||
return contextsToDeactivate, c
|
||||
}
|
||||
|
||||
func (self *ContextMgr) Pop() error {
|
||||
func (self *ContextMgr) Pop() {
|
||||
self.Lock()
|
||||
|
||||
if len(self.ContextStack) == 1 {
|
||||
// cannot escape from bottommost context
|
||||
self.Unlock()
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
var currentContext types.Context
|
||||
@@ -160,44 +155,12 @@ func (self *ContextMgr) Pop() error {
|
||||
|
||||
self.Unlock()
|
||||
|
||||
if err := self.deactivateContext(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()}); err != nil {
|
||||
return err
|
||||
}
|
||||
self.deactivate(currentContext, types.OnFocusLostOpts{NewContextKey: newContext.GetKey()})
|
||||
|
||||
return self.ActivateContext(newContext, types.OnFocusOpts{})
|
||||
self.Activate(newContext, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (self *ContextMgr) RemoveContexts(contextsToRemove []types.Context) error {
|
||||
self.Lock()
|
||||
|
||||
if len(self.ContextStack) == 1 {
|
||||
self.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
rest := lo.Filter(self.ContextStack, func(context types.Context, _ int) bool {
|
||||
for _, contextToRemove := range contextsToRemove {
|
||||
if context.GetKey() == contextToRemove.GetKey() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
self.ContextStack = rest
|
||||
contextToActivate := rest[len(rest)-1]
|
||||
self.Unlock()
|
||||
|
||||
for _, context := range contextsToRemove {
|
||||
if err := self.deactivateContext(context, types.OnFocusLostOpts{NewContextKey: contextToActivate.GetKey()}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// activate the item at the top of the stack
|
||||
return self.ActivateContext(contextToActivate, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
func (self *ContextMgr) deactivateContext(c types.Context, opts types.OnFocusLostOpts) error {
|
||||
func (self *ContextMgr) deactivate(c types.Context, opts types.OnFocusLostOpts) {
|
||||
view, _ := self.gui.c.GocuiGui().View(c.GetViewName())
|
||||
|
||||
if opts.NewContextKey != context.SEARCH_CONTEXT_KEY {
|
||||
@@ -213,18 +176,14 @@ func (self *ContextMgr) deactivateContext(c types.Context, opts types.OnFocusLos
|
||||
view.Visible = false
|
||||
}
|
||||
|
||||
if err := c.HandleFocusLost(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
c.HandleFocusLost(opts)
|
||||
}
|
||||
|
||||
func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts) error {
|
||||
func (self *ContextMgr) Activate(c types.Context, opts types.OnFocusOpts) {
|
||||
viewName := c.GetViewName()
|
||||
v, err := self.gui.c.GocuiGui().View(viewName)
|
||||
if err != nil {
|
||||
return err
|
||||
panic(err)
|
||||
}
|
||||
|
||||
self.gui.helpers.Window.SetWindowContext(c)
|
||||
@@ -235,7 +194,7 @@ func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts)
|
||||
oldView.HighlightInactive = true
|
||||
}
|
||||
if _, err := self.gui.c.GocuiGui().SetCurrentView(viewName); err != nil {
|
||||
return err
|
||||
panic(err)
|
||||
}
|
||||
|
||||
self.gui.helpers.Search.RenderSearchStatus(c)
|
||||
@@ -249,11 +208,7 @@ func (self *ContextMgr) ActivateContext(c types.Context, opts types.OnFocusOpts)
|
||||
|
||||
self.gui.c.GocuiGui().Cursor = v.Editable
|
||||
|
||||
if err := c.HandleFocus(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
c.HandleFocus(opts)
|
||||
}
|
||||
|
||||
func (self *ContextMgr) Current() types.Context {
|
||||
@@ -330,6 +285,18 @@ func (self *ContextMgr) IsCurrent(c types.Context) bool {
|
||||
return self.Current().GetKey() == c.GetKey()
|
||||
}
|
||||
|
||||
func (self *ContextMgr) IsCurrentOrParent(c types.Context) bool {
|
||||
current := self.Current()
|
||||
for current != nil {
|
||||
if current.GetKey() == c.GetKey() {
|
||||
return true
|
||||
}
|
||||
current = current.GetParentContext()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *ContextMgr) AllFilterable() []types.IFilterableContext {
|
||||
var result []types.IFilterableContext
|
||||
|
||||
@@ -392,7 +359,7 @@ func (self *ContextMgr) ContextForKey(key types.ContextKey) types.Context {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ContextMgr) PopupContexts() []types.Context {
|
||||
func (self *ContextMgr) CurrentPopup() []types.Context {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ type BaseContext struct {
|
||||
keybindingsFns []types.KeybindingsFn
|
||||
mouseKeybindingsFns []types.MouseKeybindingsFn
|
||||
onClickFn func() error
|
||||
onRenderToMainFn func() error
|
||||
onRenderToMainFn func()
|
||||
onFocusFn onFocusFn
|
||||
onFocusLostFn onFocusLostFn
|
||||
|
||||
@@ -31,8 +31,8 @@ type BaseContext struct {
|
||||
}
|
||||
|
||||
type (
|
||||
onFocusFn = func(types.OnFocusOpts) error
|
||||
onFocusLostFn = func(types.OnFocusLostOpts) error
|
||||
onFocusFn = func(types.OnFocusOpts)
|
||||
onFocusLostFn = func(types.OnFocusLostOpts)
|
||||
)
|
||||
|
||||
var _ types.IBaseContext = &BaseContext{}
|
||||
@@ -148,13 +148,13 @@ func (self *BaseContext) GetOnClick() func() error {
|
||||
return self.onClickFn
|
||||
}
|
||||
|
||||
func (self *BaseContext) AddOnRenderToMainFn(fn func() error) {
|
||||
func (self *BaseContext) AddOnRenderToMainFn(fn func()) {
|
||||
if fn != nil {
|
||||
self.onRenderToMainFn = fn
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BaseContext) GetOnRenderToMain() func() error {
|
||||
func (self *BaseContext) GetOnRenderToMain() func() {
|
||||
return self.onRenderToMainFn
|
||||
}
|
||||
|
||||
@@ -212,3 +212,7 @@ func (self *BaseContext) NeedsRerenderOnHeightChange() bool {
|
||||
func (self *BaseContext) Title() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *BaseContext) TotalContentHeight() int {
|
||||
return self.view.ViewLinesHeight()
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
c.Modes().Diffing.Ref,
|
||||
c.Views().Branches.Width(),
|
||||
c.Tr,
|
||||
c.UserConfig,
|
||||
c.UserConfig(),
|
||||
c.Model().Worktrees,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
@@ -28,7 +30,7 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
viewModel := filetree.NewCommitFileTreeViewModel(
|
||||
func() []*models.CommitFile { return c.Model().CommitFiles },
|
||||
c.Log,
|
||||
c.UserConfig.Gui.ShowFileTree,
|
||||
c.UserConfig().Gui.ShowFileTree,
|
||||
)
|
||||
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
@@ -36,7 +38,7 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
return [][]string{{style.FgRed.Sprint("(none)")}}
|
||||
}
|
||||
|
||||
showFileIcons := icons.IsIconEnabled() && c.UserConfig.Gui.ShowFileIcons
|
||||
showFileIcons := icons.IsIconEnabled() && c.UserConfig().Gui.ShowFileIcons
|
||||
lines := presentation.RenderCommitFileTree(viewModel, c.Git().Patch.PatchBuilder, showFileIcons)
|
||||
return lo.Map(lines, func(line string, _ int) []string {
|
||||
return []string{line}
|
||||
@@ -75,6 +77,25 @@ func (self *CommitFilesContext) GetDiffTerminals() []string {
|
||||
return []string{self.GetRef().RefName()}
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) GetFromAndToForDiff() (string, string) {
|
||||
if refs := self.GetRefRange(); refs != nil {
|
||||
return refs.From.ParentRefName(), refs.To.RefName()
|
||||
}
|
||||
ref := self.GetRef()
|
||||
return ref.ParentRefName(), ref.RefName()
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) ReInit(ref types.Ref, refRange *types.RefRange) {
|
||||
self.SetRef(ref)
|
||||
self.SetRefRange(refRange)
|
||||
if refRange != nil {
|
||||
self.SetTitleRef(fmt.Sprintf("%s-%s", refRange.From.ShortRefName(), refRange.To.ShortRefName()))
|
||||
} else {
|
||||
self.SetTitleRef(ref.Description())
|
||||
}
|
||||
self.GetView().Title = self.Title()
|
||||
}
|
||||
|
||||
@@ -113,19 +113,19 @@ func (self *CommitMessageContext) SetPanelState(
|
||||
|
||||
self.c.Views().CommitDescription.Subtitle = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionSubTitle,
|
||||
map[string]string{
|
||||
"togglePanelKeyBinding": keybindings.Label(self.c.UserConfig.Keybinding.Universal.TogglePanel),
|
||||
"commitMenuKeybinding": keybindings.Label(self.c.UserConfig.Keybinding.CommitMessage.CommitMenu),
|
||||
"togglePanelKeyBinding": keybindings.Label(self.c.UserConfig().Keybinding.Universal.TogglePanel),
|
||||
"commitMenuKeybinding": keybindings.Label(self.c.UserConfig().Keybinding.CommitMessage.CommitMenu),
|
||||
})
|
||||
|
||||
self.c.Views().CommitDescription.Visible = true
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) RenderCommitLength() {
|
||||
if !self.c.UserConfig.Gui.CommitLength.Show {
|
||||
return
|
||||
if self.c.UserConfig().Gui.CommitLength.Show {
|
||||
self.c.Views().CommitMessage.Subtitle = getBufferLength(self.c.Views().CommitMessage)
|
||||
} else {
|
||||
self.c.Views().CommitMessage.Subtitle = ""
|
||||
}
|
||||
|
||||
self.c.Views().CommitMessage.Subtitle = getBufferLength(self.c.Views().CommitMessage)
|
||||
}
|
||||
|
||||
func getBufferLength(view *gocui.View) string {
|
||||
|
||||
@@ -24,6 +24,8 @@ const (
|
||||
STASH_CONTEXT_KEY types.ContextKey = "stash"
|
||||
NORMAL_MAIN_CONTEXT_KEY types.ContextKey = "normal"
|
||||
NORMAL_SECONDARY_CONTEXT_KEY types.ContextKey = "normalSecondary"
|
||||
DIFF_MAIN_CONTEXT_KEY types.ContextKey = "diff"
|
||||
DIFF_SECONDARY_CONTEXT_KEY types.ContextKey = "diffSecondary"
|
||||
STAGING_MAIN_CONTEXT_KEY types.ContextKey = "staging"
|
||||
STAGING_SECONDARY_CONTEXT_KEY types.ContextKey = "stagingSecondary"
|
||||
PATCH_BUILDING_MAIN_CONTEXT_KEY types.ContextKey = "patchBuilding"
|
||||
@@ -100,6 +102,8 @@ type ContextTree struct {
|
||||
Suggestions *SuggestionsContext
|
||||
Normal types.Context
|
||||
NormalSecondary types.Context
|
||||
Diff types.Context
|
||||
DiffSecondary types.Context
|
||||
Staging *PatchExplorerContext
|
||||
StagingSecondary *PatchExplorerContext
|
||||
CustomPatchBuilder *PatchExplorerContext
|
||||
@@ -149,6 +153,8 @@ func (self *ContextTree) Flatten() []types.Context {
|
||||
self.Staging,
|
||||
self.CustomPatchBuilderSecondary,
|
||||
self.CustomPatchBuilder,
|
||||
self.Diff,
|
||||
self.DiffSecondary,
|
||||
self.NormalSecondary,
|
||||
self.Normal,
|
||||
|
||||
|
||||
53
pkg/gui/context/diff_context.go
Normal file
53
pkg/gui/context/diff_context.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
type DiffContext struct {
|
||||
*SimpleContext
|
||||
*SearchTrait
|
||||
|
||||
c *ContextCommon
|
||||
}
|
||||
|
||||
var _ types.ISearchableContext = (*DiffContext)(nil)
|
||||
|
||||
func NewDiffContext(
|
||||
view *gocui.View,
|
||||
windowName string,
|
||||
key types.ContextKey,
|
||||
|
||||
c *ContextCommon,
|
||||
) *DiffContext {
|
||||
ctx := &DiffContext{
|
||||
SimpleContext: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
View: view,
|
||||
WindowName: windowName,
|
||||
Key: key,
|
||||
Focusable: true,
|
||||
HighlightOnFocus: true,
|
||||
})),
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
c: c,
|
||||
}
|
||||
|
||||
// TODO: copied from PatchExplorerContext. Do we need something like this?
|
||||
// ctx.GetView().SetOnSelectItem(ctx.SearchTrait.onSelectItemWrapper(
|
||||
// func(selectedLineIdx int) error {
|
||||
// ctx.GetMutex().Lock()
|
||||
// defer ctx.GetMutex().Unlock()
|
||||
// ctx.NavigateTo(ctx.c.Context().IsCurrent(ctx), selectedLineIdx)
|
||||
// return nil
|
||||
// }),
|
||||
// )
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (self *DiffContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
|
||||
return nil
|
||||
}
|
||||
@@ -49,7 +49,7 @@ func (self *ListContextTrait) FocusLine() {
|
||||
} else if self.renderOnlyVisibleLines {
|
||||
newOrigin, _ := self.GetViewTrait().ViewPortYBounds()
|
||||
if oldOrigin != newOrigin {
|
||||
return self.HandleRender()
|
||||
self.HandleRender()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -72,26 +72,26 @@ func formatListFooter(selectedLineIdx int, length int) string {
|
||||
return fmt.Sprintf("%d of %d", selectedLineIdx+1, length)
|
||||
}
|
||||
|
||||
func (self *ListContextTrait) HandleFocus(opts types.OnFocusOpts) error {
|
||||
func (self *ListContextTrait) HandleFocus(opts types.OnFocusOpts) {
|
||||
self.FocusLine()
|
||||
|
||||
self.GetViewTrait().SetHighlight(self.list.Len() > 0)
|
||||
|
||||
return self.Context.HandleFocus(opts)
|
||||
self.Context.HandleFocus(opts)
|
||||
}
|
||||
|
||||
func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) error {
|
||||
func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) {
|
||||
self.GetViewTrait().SetOriginX(0)
|
||||
|
||||
if self.refreshViewportOnChange {
|
||||
self.refreshViewport()
|
||||
}
|
||||
|
||||
return self.Context.HandleFocusLost(opts)
|
||||
self.Context.HandleFocusLost(opts)
|
||||
}
|
||||
|
||||
// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view
|
||||
func (self *ListContextTrait) HandleRender() error {
|
||||
func (self *ListContextTrait) HandleRender() {
|
||||
self.list.ClampSelection()
|
||||
if self.renderOnlyVisibleLines {
|
||||
// Rendering only the visible area can save a lot of cell memory for
|
||||
@@ -110,13 +110,12 @@ func (self *ListContextTrait) HandleRender() error {
|
||||
}
|
||||
self.c.Render()
|
||||
self.setFooter()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ListContextTrait) OnSearchSelect(selectedLineIdx int) error {
|
||||
self.GetList().SetSelection(self.ViewIndexToModelIndex(selectedLineIdx))
|
||||
return self.HandleFocus(types.OnFocusOpts{})
|
||||
self.HandleFocus(types.OnFocusOpts{})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ListContextTrait) IsItemVisible(item types.HasUrn) bool {
|
||||
@@ -140,3 +139,11 @@ func (self *ListContextTrait) RangeSelectEnabled() bool {
|
||||
func (self *ListContextTrait) RenderOnlyVisibleLines() bool {
|
||||
return self.renderOnlyVisibleLines
|
||||
}
|
||||
|
||||
func (self *ListContextTrait) TotalContentHeight() int {
|
||||
result := self.list.Len()
|
||||
if self.getNonModelItems != nil {
|
||||
result += len(self.getNonModelItems())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ func (self *ListRenderer) ModelIndexToViewIndex(modelIndex int) int {
|
||||
}
|
||||
|
||||
func (self *ListRenderer) ViewIndexToModelIndex(viewIndex int) int {
|
||||
viewIndex = utils.Clamp(viewIndex, 0, self.list.Len()+self.numNonModelItems)
|
||||
viewIndex = lo.Clamp(viewIndex, 0, self.list.Len()+self.numNonModelItems)
|
||||
if self.modelIndicesByViewIndex != nil {
|
||||
return self.modelIndicesByViewIndex[viewIndex]
|
||||
}
|
||||
@@ -121,7 +121,10 @@ func (self *ListRenderer) insertNonModelItems(
|
||||
break
|
||||
}
|
||||
if item.Index+offset >= startIdx {
|
||||
padding := strings.Repeat(" ", columnPositions[item.Column])
|
||||
padding := ""
|
||||
if columnPositions != nil {
|
||||
padding = strings.Repeat(" ", columnPositions[item.Column])
|
||||
}
|
||||
lines = slices.Insert(lines, item.Index+offset-startIdx, padding+item.Content)
|
||||
}
|
||||
offset++
|
||||
|
||||
@@ -34,7 +34,7 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
||||
getDisplayStrings := func(startIdx int, endIdx int) [][]string {
|
||||
selectedCommitHash := ""
|
||||
|
||||
if c.CurrentContext().GetKey() == LOCAL_COMMITS_CONTEXT_KEY {
|
||||
if c.Context().Current().GetKey() == LOCAL_COMMITS_CONTEXT_KEY {
|
||||
selectedCommit := viewModel.GetSelected()
|
||||
if selectedCommit != nil {
|
||||
selectedCommitHash = selectedCommit.Hash
|
||||
@@ -54,10 +54,10 @@ func NewLocalCommitsContext(c *ContextCommon) *LocalCommitsContext {
|
||||
c.Modes().CherryPicking.SelectedHashSet(),
|
||||
c.Modes().Diffing.Ref,
|
||||
c.Modes().MarkedBaseCommit.GetHash(),
|
||||
c.UserConfig.Gui.TimeFormat,
|
||||
c.UserConfig.Gui.ShortTimeFormat,
|
||||
c.UserConfig().Gui.TimeFormat,
|
||||
c.UserConfig().Gui.ShortTimeFormat,
|
||||
time.Now(),
|
||||
c.UserConfig.Git.ParseEmoji,
|
||||
c.UserConfig().Git.ParseEmoji,
|
||||
selectedCommitHash,
|
||||
startIdx,
|
||||
endIdx,
|
||||
@@ -110,7 +110,7 @@ func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *ContextCommon
|
||||
self := &LocalCommitsViewModel{
|
||||
ListViewModel: NewListViewModel(getModel),
|
||||
limitCommits: true,
|
||||
showWholeGitGraph: c.UserConfig.Git.Log.ShowWholeGraph,
|
||||
showWholeGitGraph: c.UserConfig().Git.Log.ShowWholeGraph,
|
||||
}
|
||||
|
||||
return self
|
||||
@@ -128,6 +128,19 @@ func (self *LocalCommitsContext) GetSelectedRef() types.Ref {
|
||||
return commit
|
||||
}
|
||||
|
||||
func (self *LocalCommitsContext) GetSelectedRefRangeForDiffFiles() *types.RefRange {
|
||||
commits, startIdx, endIdx := self.GetSelectedItems()
|
||||
if commits == nil || startIdx == endIdx {
|
||||
return nil
|
||||
}
|
||||
from := commits[len(commits)-1]
|
||||
to := commits[0]
|
||||
if from.IsTODO() || to.IsTODO() {
|
||||
return nil
|
||||
}
|
||||
return &types.RefRange{From: from, To: to}
|
||||
}
|
||||
|
||||
// Returns the commit hash of the selected commit, or an empty string if no
|
||||
// commit is selected
|
||||
func (self *LocalCommitsContext) GetSelectedCommitHash() string {
|
||||
@@ -202,6 +215,15 @@ func shouldShowGraph(c *ContextCommon) bool {
|
||||
}
|
||||
|
||||
func searchModelCommits(caseSensitive bool, commits []*models.Commit, columnPositions []int, searchStr string) []gocui.SearchPosition {
|
||||
if columnPositions == nil {
|
||||
// This should never happen. We are being called at a time where our
|
||||
// entire view content is scrolled out of view, so that we didn't draw
|
||||
// anything the last time we rendered. If we run into a scenario where
|
||||
// this happens, we should fix it, but until we found them all, at least
|
||||
// make sure we don't crash.
|
||||
return []gocui.SearchPosition{}
|
||||
}
|
||||
|
||||
normalize := lo.Ternary(caseSensitive, func(s string) string { return s }, strings.ToLower)
|
||||
return lo.FilterMap(commits, func(commit *models.Commit, idx int) (gocui.SearchPosition, bool) {
|
||||
// The XStart and XEnd values are only used if the search string can't
|
||||
|
||||
@@ -139,7 +139,7 @@ func (self *MenuViewModel) GetNonModelItems() []*NonModelItem {
|
||||
// Don't display section headers when we are filtering, and the filter mode
|
||||
// is fuzzy. The reason is that filtering changes the order of the items
|
||||
// (they are sorted by best match), so all the sections would be messed up.
|
||||
if self.FilteredListViewModel.IsFiltering() && self.c.UserConfig.Gui.UseFuzzySearch() {
|
||||
if self.FilteredListViewModel.IsFiltering() && self.c.UserConfig().Gui.UseFuzzySearch() {
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -197,9 +197,7 @@ func (self *MenuContext) OnMenuPress(selectedItem *types.MenuItem) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := self.c.PopContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Context().Pop()
|
||||
|
||||
if selectedItem == nil {
|
||||
return nil
|
||||
|
||||
@@ -68,13 +68,11 @@ func (self *MergeConflictsContext) IsUserScrolling() bool {
|
||||
return self.viewModel.userVerticalScrolling
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) RenderAndFocus() error {
|
||||
func (self *MergeConflictsContext) RenderAndFocus() {
|
||||
self.setContent()
|
||||
self.FocusSelection()
|
||||
|
||||
self.c.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) Render() error {
|
||||
@@ -99,7 +97,7 @@ func (self *MergeConflictsContext) setContent() {
|
||||
|
||||
func (self *MergeConflictsContext) FocusSelection() {
|
||||
if !self.IsUserScrolling() {
|
||||
_ = self.GetView().SetOriginY(self.GetOriginY())
|
||||
self.GetView().SetOriginY(self.GetOriginY())
|
||||
}
|
||||
|
||||
self.SetSelectedLineRange()
|
||||
|
||||
@@ -4,17 +4,14 @@ import "github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
|
||||
type ParentContextMgr struct {
|
||||
ParentContext types.Context
|
||||
// we can't know on the calling end whether a Context is actually a nil value without reflection, so we're storing this flag here to tell us. There has got to be a better way around this
|
||||
hasParent bool
|
||||
}
|
||||
|
||||
var _ types.ParentContexter = (*ParentContextMgr)(nil)
|
||||
|
||||
func (self *ParentContextMgr) SetParentContext(context types.Context) {
|
||||
self.ParentContext = context
|
||||
self.hasParent = true
|
||||
}
|
||||
|
||||
func (self *ParentContextMgr) GetParentContext() (types.Context, bool) {
|
||||
return self.ParentContext, self.hasParent
|
||||
func (self *ParentContextMgr) GetParentContext() types.Context {
|
||||
return self.ParentContext
|
||||
}
|
||||
|
||||
@@ -53,7 +53,8 @@ func NewPatchExplorerContext(
|
||||
func(selectedLineIdx int) error {
|
||||
ctx.GetMutex().Lock()
|
||||
defer ctx.GetMutex().Unlock()
|
||||
return ctx.NavigateTo(ctx.c.IsCurrentContext(ctx), selectedLineIdx)
|
||||
ctx.NavigateTo(ctx.c.Context().IsCurrent(ctx), selectedLineIdx)
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -78,28 +79,22 @@ func (self *PatchExplorerContext) GetIncludedLineIndices() []int {
|
||||
return self.getIncludedLineIndices()
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) RenderAndFocus(isFocused bool) error {
|
||||
func (self *PatchExplorerContext) RenderAndFocus(isFocused bool) {
|
||||
self.setContent(isFocused)
|
||||
|
||||
self.FocusSelection()
|
||||
self.c.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) Render(isFocused bool) error {
|
||||
func (self *PatchExplorerContext) Render(isFocused bool) {
|
||||
self.setContent(isFocused)
|
||||
|
||||
self.c.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) Focus() error {
|
||||
func (self *PatchExplorerContext) Focus() {
|
||||
self.FocusSelection()
|
||||
self.c.Render()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) setContent(isFocused bool) {
|
||||
@@ -116,7 +111,7 @@ func (self *PatchExplorerContext) FocusSelection() {
|
||||
|
||||
newOriginY := state.CalculateOrigin(origin, bufferHeight, numLines)
|
||||
|
||||
_ = view.SetOriginY(newOriginY)
|
||||
view.SetOriginY(newOriginY)
|
||||
|
||||
startIdx, endIdx := state.SelectedRange()
|
||||
// As far as the view is concerned, we are always selecting a range
|
||||
@@ -132,11 +127,11 @@ func (self *PatchExplorerContext) GetContentToRender(isFocused bool) string {
|
||||
return self.GetState().RenderForLineIndices(isFocused, self.GetIncludedLineIndices())
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) NavigateTo(isFocused bool, selectedLineIdx int) error {
|
||||
func (self *PatchExplorerContext) NavigateTo(isFocused bool, selectedLineIdx int) {
|
||||
self.GetState().SetLineSelectMode()
|
||||
self.GetState().SelectLine(selectedLineIdx)
|
||||
|
||||
return self.RenderAndFocus(isFocused)
|
||||
self.RenderAndFocus(isFocused)
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) GetMutex() *deadlock.Mutex {
|
||||
|
||||
@@ -33,9 +33,9 @@ func NewReflogCommitsContext(c *ContextCommon) *ReflogCommitsContext {
|
||||
c.Modes().CherryPicking.SelectedHashSet(),
|
||||
c.Modes().Diffing.Ref,
|
||||
time.Now(),
|
||||
c.UserConfig.Gui.TimeFormat,
|
||||
c.UserConfig.Gui.ShortTimeFormat,
|
||||
c.UserConfig.Git.ParseEmoji,
|
||||
c.UserConfig().Gui.TimeFormat,
|
||||
c.UserConfig().Gui.ShortTimeFormat,
|
||||
c.UserConfig().Git.ParseEmoji,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -71,6 +71,11 @@ func (self *ReflogCommitsContext) GetSelectedRef() types.Ref {
|
||||
return commit
|
||||
}
|
||||
|
||||
func (self *ReflogCommitsContext) GetSelectedRefRangeForDiffFiles() *types.RefRange {
|
||||
// It doesn't make much sense to show a range diff between two reflog entries.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ReflogCommitsContext) GetCommits() []*models.Commit {
|
||||
return self.getModel()
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ func NewRemotesContext(c *ContextCommon) *RemotesContext {
|
||||
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
return presentation.GetRemoteListDisplayStrings(
|
||||
viewModel.GetItems(), c.Modes().Diffing.Ref, c.State().GetItemOperation, c.Tr, c.UserConfig)
|
||||
viewModel.GetItems(), c.Modes().Diffing.Ref, c.State().GetItemOperation, c.Tr, c.UserConfig())
|
||||
}
|
||||
|
||||
return &RemotesContext{
|
||||
|
||||
@@ -51,7 +51,7 @@ func (self *SearchTrait) onSelectItemWrapper(innerFunc func(int) error) func(int
|
||||
}
|
||||
|
||||
func (self *SearchTrait) RenderSearchStatus(index int, total int) {
|
||||
keybindingConfig := self.c.UserConfig.Keybinding
|
||||
keybindingConfig := self.c.UserConfig().Keybinding
|
||||
|
||||
if total == 0 {
|
||||
self.c.SetViewContent(
|
||||
|
||||
@@ -57,6 +57,8 @@ func NewContextTree(c *ContextCommon) *ContextTree {
|
||||
Focusable: false,
|
||||
}),
|
||||
),
|
||||
Diff: NewDiffContext(c.Views().Diff, "main", DIFF_MAIN_CONTEXT_KEY, c),
|
||||
DiffSecondary: NewDiffContext(c.Views().DiffSecondary, "secondary", DIFF_SECONDARY_CONTEXT_KEY, c),
|
||||
Staging: NewPatchExplorerContext(
|
||||
c.Views().Staging,
|
||||
"main",
|
||||
|
||||
@@ -31,43 +31,33 @@ func NewDisplayContext(key types.ContextKey, view *gocui.View, windowName string
|
||||
)
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) error {
|
||||
func (self *SimpleContext) HandleFocus(opts types.OnFocusOpts) {
|
||||
if self.highlightOnFocus {
|
||||
self.GetViewTrait().SetHighlight(true)
|
||||
}
|
||||
|
||||
if self.onFocusFn != nil {
|
||||
if err := self.onFocusFn(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
self.onFocusFn(opts)
|
||||
}
|
||||
|
||||
if self.onRenderToMainFn != nil {
|
||||
if err := self.onRenderToMainFn(); err != nil {
|
||||
return err
|
||||
}
|
||||
self.onRenderToMainFn()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) error {
|
||||
func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) {
|
||||
self.GetViewTrait().SetHighlight(false)
|
||||
_ = self.view.SetOriginX(0)
|
||||
self.view.SetOriginX(0)
|
||||
if self.onFocusLostFn != nil {
|
||||
return self.onFocusLostFn(opts)
|
||||
self.onFocusLostFn(opts)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleRender() error {
|
||||
return nil
|
||||
func (self *SimpleContext) HandleRender() {
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleRenderToMain() error {
|
||||
func (self *SimpleContext) HandleRenderToMain() {
|
||||
if self.onRenderToMainFn != nil {
|
||||
return self.onRenderToMainFn()
|
||||
self.onRenderToMainFn()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -61,6 +61,11 @@ func (self *StashContext) GetSelectedRef() types.Ref {
|
||||
return stash
|
||||
}
|
||||
|
||||
func (self *StashContext) GetSelectedRefRangeForDiffFiles() *types.RefRange {
|
||||
// It doesn't make much sense to show a range diff between two stash entries.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *StashContext) GetDiffTerminals() []string {
|
||||
itemId := self.GetSelectedItemId()
|
||||
|
||||
|
||||
@@ -40,14 +40,14 @@ func NewSubCommitsContext(
|
||||
|
||||
getDisplayStrings := func(startIdx int, endIdx int) [][]string {
|
||||
// This can happen if a sub-commits view is asked to be rerendered while
|
||||
// it is invisble; for example when switching screen modes, which
|
||||
// it is invisible; for example when switching screen modes, which
|
||||
// rerenders all views.
|
||||
if viewModel.GetRef() == nil {
|
||||
return [][]string{}
|
||||
}
|
||||
|
||||
selectedCommitHash := ""
|
||||
if c.CurrentContext().GetKey() == SUB_COMMITS_CONTEXT_KEY {
|
||||
if c.Context().Current().GetKey() == SUB_COMMITS_CONTEXT_KEY {
|
||||
selectedCommit := viewModel.GetSelected()
|
||||
if selectedCommit != nil {
|
||||
selectedCommitHash = selectedCommit.Hash
|
||||
@@ -68,10 +68,10 @@ func NewSubCommitsContext(
|
||||
c.Modes().CherryPicking.SelectedHashSet(),
|
||||
c.Modes().Diffing.Ref,
|
||||
"",
|
||||
c.UserConfig.Gui.TimeFormat,
|
||||
c.UserConfig.Gui.ShortTimeFormat,
|
||||
c.UserConfig().Gui.TimeFormat,
|
||||
c.UserConfig().Gui.ShortTimeFormat,
|
||||
time.Now(),
|
||||
c.UserConfig.Git.ParseEmoji,
|
||||
c.UserConfig().Git.ParseEmoji,
|
||||
selectedCommitHash,
|
||||
startIdx,
|
||||
endIdx,
|
||||
@@ -186,6 +186,19 @@ func (self *SubCommitsContext) GetSelectedRef() types.Ref {
|
||||
return commit
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) GetSelectedRefRangeForDiffFiles() *types.RefRange {
|
||||
commits, startIdx, endIdx := self.GetSelectedItems()
|
||||
if commits == nil || startIdx == endIdx {
|
||||
return nil
|
||||
}
|
||||
from := commits[len(commits)-1]
|
||||
to := commits[0]
|
||||
if from.Divergence != to.Divergence {
|
||||
return nil
|
||||
}
|
||||
return &types.RefRange{From: from, To: to}
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) GetCommits() []*models.Commit {
|
||||
return self.getModel()
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func (self *SuggestionsContext) SetSuggestions(suggestions []*types.Suggestion)
|
||||
self.State.Suggestions = suggestions
|
||||
self.SetSelection(0)
|
||||
self.c.ResetViewOrigin(self.GetView())
|
||||
_ = self.HandleRender()
|
||||
self.HandleRender()
|
||||
}
|
||||
|
||||
func (self *SuggestionsContext) RefreshSuggestions() {
|
||||
|
||||
@@ -30,7 +30,7 @@ func NewTagsContext(
|
||||
return presentation.GetTagListDisplayStrings(
|
||||
viewModel.GetItems(),
|
||||
c.State().GetItemOperation,
|
||||
c.Modes().Diffing.Ref, c.Tr, c.UserConfig)
|
||||
c.Modes().Diffing.Ref, c.Tr, c.UserConfig())
|
||||
}
|
||||
|
||||
return &TagsContext{
|
||||
|
||||
@@ -3,6 +3,7 @@ package traits
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type RangeSelectMode int
|
||||
@@ -85,7 +86,7 @@ func (self *ListCursor) clampValue(value int) int {
|
||||
clampedValue := -1
|
||||
length := self.getLength()
|
||||
if length > 0 {
|
||||
clampedValue = utils.Clamp(value, 0, length-1)
|
||||
clampedValue = lo.Clamp(value, 0, length-1)
|
||||
}
|
||||
|
||||
return clampedValue
|
||||
|
||||
@@ -57,7 +57,7 @@ func (self *ViewTrait) SetFooter(value string) {
|
||||
}
|
||||
|
||||
func (self *ViewTrait) SetOriginX(value int) {
|
||||
_ = self.view.SetOriginX(value)
|
||||
self.view.SetOriginX(value)
|
||||
}
|
||||
|
||||
// tells us the start of line indexes shown in the view currently as well as the capacity of lines shown in the viewport.
|
||||
|
||||
@@ -25,11 +25,11 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
viewModel := filetree.NewFileTreeViewModel(
|
||||
func() []*models.File { return c.Model().Files },
|
||||
c.Log,
|
||||
c.UserConfig.Gui.ShowFileTree,
|
||||
c.UserConfig().Gui.ShowFileTree,
|
||||
)
|
||||
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
showFileIcons := icons.IsIconEnabled() && c.UserConfig.Gui.ShowFileIcons
|
||||
showFileIcons := icons.IsIconEnabled() && c.UserConfig().Gui.ShowFileIcons
|
||||
lines := presentation.RenderFileTree(viewModel, c.Model().Submodules, showFileIcons)
|
||||
return lo.Map(lines, func(line string, _ int) []string {
|
||||
return []string{line}
|
||||
|
||||
@@ -107,7 +107,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
Files: helpers.NewFilesHelper(helperCommon),
|
||||
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper),
|
||||
Tags: helpers.NewTagsHelper(helperCommon, commitsHelper),
|
||||
BranchesHelper: helpers.NewBranchesHelper(helperCommon),
|
||||
BranchesHelper: helpers.NewBranchesHelper(helperCommon, worktreeHelper),
|
||||
GPG: helpers.NewGpgHelper(helperCommon),
|
||||
MergeAndRebase: rebaseHelper,
|
||||
MergeConflicts: mergeConflictsHelper,
|
||||
@@ -181,6 +181,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
contextLinesController := controllers.NewContextLinesController(common)
|
||||
renameSimilarityThresholdController := controllers.NewRenameSimilarityThresholdController(common)
|
||||
verticalScrollControllerFactory := controllers.NewVerticalScrollControllerFactory(common, &gui.viewBufferManagerMap)
|
||||
viewSelectionControllerFactory := controllers.NewViewSelectionControllerFactory(common, &gui.viewBufferManagerMap)
|
||||
|
||||
branchesController := controllers.NewBranchesController(common)
|
||||
gitFlowController := controllers.NewGitFlowController(common)
|
||||
@@ -189,6 +190,8 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
patchExplorerControllerFactory := controllers.NewPatchExplorerControllerFactory(common)
|
||||
stagingController := controllers.NewStagingController(common, gui.State.Contexts.Staging, gui.State.Contexts.StagingSecondary, false)
|
||||
stagingSecondaryController := controllers.NewStagingController(common, gui.State.Contexts.StagingSecondary, gui.State.Contexts.Staging, true)
|
||||
diffController := controllers.NewDiffController(common, gui.State.Contexts.Diff, gui.State.Contexts.DiffSecondary, &gui.viewBufferManagerMap)
|
||||
diffSecondaryController := controllers.NewDiffController(common, gui.State.Contexts.DiffSecondary, gui.State.Contexts.Diff, &gui.viewBufferManagerMap)
|
||||
patchBuildingController := controllers.NewPatchBuildingController(common)
|
||||
snakeController := controllers.NewSnakeController(common)
|
||||
reflogCommitsController := controllers.NewReflogCommitsController(common)
|
||||
@@ -197,7 +200,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
commandLogController := controllers.NewCommandLogController(common)
|
||||
confirmationController := controllers.NewConfirmationController(common)
|
||||
suggestionsController := controllers.NewSuggestionsController(common)
|
||||
jumpToSideWindowController := controllers.NewJumpToSideWindowController(common)
|
||||
jumpToSideWindowController := controllers.NewJumpToSideWindowController(common, gui.handleNextTab)
|
||||
|
||||
sideWindowControllerFactory := controllers.NewSideWindowControllerFactory(common)
|
||||
|
||||
@@ -259,7 +262,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
gui.State.Contexts.Stash,
|
||||
} {
|
||||
controllers.AttachControllers(context, controllers.NewSwitchToDiffFilesController(
|
||||
common, context, gui.State.Contexts.CommitFiles,
|
||||
common, context,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -306,6 +309,18 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
mergeConflictsController,
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.Diff,
|
||||
diffController,
|
||||
verticalScrollControllerFactory.Create(gui.State.Contexts.Diff),
|
||||
viewSelectionControllerFactory.Create(gui.State.Contexts.Diff),
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.DiffSecondary,
|
||||
diffSecondaryController,
|
||||
verticalScrollControllerFactory.Create(gui.State.Contexts.DiffSecondary),
|
||||
viewSelectionControllerFactory.Create(gui.State.Contexts.DiffSecondary),
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.Files,
|
||||
filesController,
|
||||
)
|
||||
@@ -354,6 +369,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.CommitDescription,
|
||||
commitDescriptionController,
|
||||
verticalScrollControllerFactory.Create(gui.State.Contexts.CommitDescription),
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.RemoteBranches,
|
||||
|
||||
@@ -19,14 +19,14 @@ func (self *baseController) GetOnClick() func() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnRenderToMain() func() error {
|
||||
func (self *baseController) GetOnRenderToMain() func() {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||
func (self *baseController) GetOnFocus() func(types.OnFocusOpts) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *baseController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
func (self *baseController) GetOnFocusLost() func(types.OnFocusLostOpts) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -280,7 +280,7 @@ func (self *BasicCommitsController) createResetMenu(commit *models.Commit) error
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) checkout(commit *models.Commit) error {
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.CheckoutCommit,
|
||||
Prompt: self.c.Tr.SureCheckoutThisCommit,
|
||||
HandleConfirm: func() error {
|
||||
@@ -288,6 +288,7 @@ func (self *BasicCommitsController) checkout(commit *models.Commit) error {
|
||||
return self.c.Helpers().Refs.CheckoutRef(commit.Hash, types.CheckoutRefOptions{})
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyRange(*models.Commit) error {
|
||||
@@ -311,8 +312,8 @@ func (self *BasicCommitsController) canCopyCommits(selectedCommits []*models.Com
|
||||
func (self *BasicCommitsController) handleOldCherryPickKey() error {
|
||||
msg := utils.ResolvePlaceholderString(self.c.Tr.OldCherryPickKeyWarning,
|
||||
map[string]string{
|
||||
"copy": keybindings.Label(self.c.UserConfig.Keybinding.Commits.CherryPickCopy),
|
||||
"paste": keybindings.Label(self.c.UserConfig.Keybinding.Commits.PasteCommits),
|
||||
"copy": keybindings.Label(self.c.UserConfig().Keybinding.Commits.CherryPickCopy),
|
||||
"paste": keybindings.Label(self.c.UserConfig().Keybinding.Commits.PasteCommits),
|
||||
})
|
||||
|
||||
return errors.New(msg)
|
||||
|
||||
@@ -201,10 +201,10 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
|
||||
{
|
||||
Label: self.c.Tr.Bisect.ChooseTerms,
|
||||
OnPress: func() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.Bisect.OldTermPrompt,
|
||||
HandleConfirm: func(oldTerm string) error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.Bisect.NewTermPrompt,
|
||||
HandleConfirm: func(newTerm string) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.StartBisect)
|
||||
@@ -215,8 +215,10 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
},
|
||||
})
|
||||
return nil
|
||||
},
|
||||
})
|
||||
return nil
|
||||
},
|
||||
Key: 't',
|
||||
},
|
||||
@@ -235,7 +237,7 @@ func (self *BisectController) showBisectCompleteMessage(candidateHashes []string
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.Bisect.CompleteTitle,
|
||||
Prompt: fmt.Sprintf(prompt, strings.TrimSpace(formattedCommits)),
|
||||
HandleConfirm: func() error {
|
||||
@@ -247,6 +249,8 @@ func (self *BisectController) showBisectCompleteMessage(candidateHashes []string
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BisectController) afterMark(selectCurrent bool, waitToReselect bool) error {
|
||||
@@ -290,7 +294,7 @@ func (self *BisectController) selectCurrentBisectCommit() {
|
||||
for i, commit := range self.c.Model().Commits {
|
||||
if commit.Hash == info.GetCurrentHash() {
|
||||
self.context().SetSelection(i)
|
||||
_ = self.context().HandleFocus(types.OnFocusOpts{})
|
||||
self.context().HandleFocus(types.OnFocusOpts{})
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package controllers
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
@@ -170,9 +169,9 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BranchesController) GetOnRenderToMain() func() error {
|
||||
return func() error {
|
||||
return self.c.Helpers().Diff.WithDiffModeCheck(func() error {
|
||||
func (self *BranchesController) GetOnRenderToMain() func() {
|
||||
return func() {
|
||||
self.c.Helpers().Diff.WithDiffModeCheck(func() {
|
||||
var task types.UpdateTask
|
||||
branch := self.context().GetSelected()
|
||||
if branch == nil {
|
||||
@@ -183,7 +182,7 @@ func (self *BranchesController) GetOnRenderToMain() func() error {
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
}
|
||||
|
||||
return self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
Pair: self.c.MainViewPairs().Normal,
|
||||
Main: &types.ViewUpdateOpts{
|
||||
Title: self.c.Tr.LogTitle,
|
||||
@@ -410,13 +409,15 @@ func (self *BranchesController) promptToCheckoutWorktree(worktree *models.Worktr
|
||||
"worktreeName": worktree.Name,
|
||||
})
|
||||
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.SwitchToWorktree,
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.Helpers().Worktree.Switch(worktree, context.LOCAL_BRANCHES_CONTEXT_KEY)
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesController) handleCreatePullRequest(selectedBranch *models.Branch) error {
|
||||
@@ -460,7 +461,7 @@ func (self *BranchesController) forceCheckout() error {
|
||||
message := self.c.Tr.SureForceCheckout
|
||||
title := self.c.Tr.ForceCheckoutBranch
|
||||
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: title,
|
||||
Prompt: message,
|
||||
HandleConfirm: func() error {
|
||||
@@ -471,10 +472,12 @@ func (self *BranchesController) forceCheckout() error {
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesController) checkoutByName() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.BranchName + ":",
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
|
||||
HandleConfirm: func(response string) error {
|
||||
@@ -485,18 +488,22 @@ func (self *BranchesController) checkoutByName() error {
|
||||
}
|
||||
return self.c.Helpers().Refs.CheckoutRef(response, types.CheckoutRefOptions{
|
||||
OnRefNotFound: func(ref string) error {
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.BranchNotFoundTitle,
|
||||
Prompt: fmt.Sprintf("%s %s%s", self.c.Tr.BranchNotFoundPrompt, ref, "?"),
|
||||
HandleConfirm: func() error {
|
||||
return self.createNewBranchWithName(ref)
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesController) createNewBranchWithName(newBranchName string) error {
|
||||
@@ -513,89 +520,16 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, KeepBranchSelectionIndex: true})
|
||||
}
|
||||
|
||||
func (self *BranchesController) checkedOutByOtherWorktree(branch *models.Branch) bool {
|
||||
return git_commands.CheckedOutByOtherWorktree(branch, self.c.Model().Worktrees)
|
||||
}
|
||||
|
||||
func (self *BranchesController) promptWorktreeBranchDelete(selectedBranch *models.Branch) error {
|
||||
worktree, ok := self.worktreeForBranch(selectedBranch)
|
||||
if !ok {
|
||||
self.c.Log.Error("promptWorktreeBranchDelete out of sync with list of worktrees")
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: i18n
|
||||
title := utils.ResolvePlaceholderString(self.c.Tr.BranchCheckedOutByWorktree, map[string]string{
|
||||
"worktreeName": worktree.Name,
|
||||
"branchName": selectedBranch.Name,
|
||||
})
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: title,
|
||||
Items: []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.SwitchToWorktree,
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().Worktree.Switch(worktree, context.LOCAL_BRANCHES_CONTEXT_KEY)
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.DetachWorktree,
|
||||
Tooltip: self.c.Tr.DetachWorktreeTooltip,
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().Worktree.Detach(worktree)
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.RemoveWorktree,
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().Worktree.Remove(worktree, false)
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BranchesController) localDelete(branch *models.Branch) error {
|
||||
if self.checkedOutByOtherWorktree(branch) {
|
||||
return self.promptWorktreeBranchDelete(branch)
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(_ gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch)
|
||||
err := self.c.Git().Branch.LocalDelete(branch.Name, false)
|
||||
if err != nil && strings.Contains(err.Error(), "git branch -D ") {
|
||||
return self.forceDelete(branch)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
})
|
||||
return self.c.Helpers().BranchesHelper.ConfirmLocalDelete(branch)
|
||||
}
|
||||
|
||||
func (self *BranchesController) remoteDelete(branch *models.Branch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(branch.UpstreamRemote, branch.Name)
|
||||
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(branch.UpstreamRemote, branch.UpstreamBranch)
|
||||
}
|
||||
|
||||
func (self *BranchesController) forceDelete(branch *models.Branch) error {
|
||||
title := self.c.Tr.ForceDeleteBranchTitle
|
||||
message := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ForceDeleteBranchMessage,
|
||||
map[string]string{
|
||||
"selectedBranchName": branch.Name,
|
||||
},
|
||||
)
|
||||
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
Title: title,
|
||||
Prompt: message,
|
||||
HandleConfirm: func() error {
|
||||
if err := self.c.Git().Branch.LocalDelete(branch.Name, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
},
|
||||
})
|
||||
func (self *BranchesController) localAndRemoteDelete(branch *models.Branch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmLocalAndRemoteDelete(branch)
|
||||
}
|
||||
|
||||
func (self *BranchesController) delete(branch *models.Branch) error {
|
||||
@@ -623,6 +557,19 @@ func (self *BranchesController) delete(branch *models.Branch) error {
|
||||
remoteDeleteItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
|
||||
}
|
||||
|
||||
deleteBothItem := &types.MenuItem{
|
||||
Label: self.c.Tr.DeleteLocalAndRemoteBranch,
|
||||
Key: 'b',
|
||||
OnPress: func() error {
|
||||
return self.localAndRemoteDelete(branch)
|
||||
},
|
||||
}
|
||||
if checkedOutBranch.Name == branch.Name {
|
||||
deleteBothItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CantDeleteCheckOutBranch}
|
||||
} else if !branch.IsTrackingRemote() || branch.UpstreamGone {
|
||||
deleteBothItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
|
||||
}
|
||||
|
||||
menuTitle := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteBranchTitle,
|
||||
map[string]string{
|
||||
@@ -632,7 +579,7 @@ func (self *BranchesController) delete(branch *models.Branch) error {
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: menuTitle,
|
||||
Items: []*types.MenuItem{localDeleteItem, remoteDeleteItem},
|
||||
Items: []*types.MenuItem{localDeleteItem, remoteDeleteItem, deleteBothItem},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -715,7 +662,7 @@ func (self *BranchesController) createResetMenu(selectedBranch *models.Branch) e
|
||||
|
||||
func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
promptForNewName := func() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.NewBranchNamePrompt + " " + branch.Name + ":",
|
||||
InitialContent: branch.Name,
|
||||
HandleConfirm: func(newBranchName string) error {
|
||||
@@ -734,15 +681,15 @@ func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
for i, newBranch := range self.c.Model().Branches {
|
||||
if newBranch.Name == newBranchName {
|
||||
self.context().SetSelection(i)
|
||||
if err := self.context().HandleRender(); err != nil {
|
||||
return err
|
||||
}
|
||||
self.context().HandleRender()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// I could do an explicit check here for whether the branch is tracking a remote branch
|
||||
@@ -752,11 +699,13 @@ func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
return promptForNewName()
|
||||
}
|
||||
|
||||
return self.c.Confirm(types.ConfirmOpts{
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.RenameBranch,
|
||||
Prompt: self.c.Tr.RenameBranchWarning,
|
||||
HandleConfirm: promptForNewName,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesController) newBranch(selectedBranch *models.Branch) error {
|
||||
@@ -781,13 +730,15 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
|
||||
{
|
||||
LabelColumns: fromToLabelColumns(branch.Name, self.c.Tr.SelectBranch),
|
||||
OnPress: func() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
self.c.Prompt(types.PromptOpts{
|
||||
Title: branch.Name + " →",
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteBranchesSuggestionsFunc("/"),
|
||||
HandleConfirm: func(targetBranchName string) error {
|
||||
return self.createPullRequest(branch.Name, targetBranchName)
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -810,7 +761,7 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
|
||||
|
||||
menuItems = append(menuItems, menuItemsForBranch(selectedBranch)...)
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{Title: fmt.Sprintf(self.c.Tr.CreatePullRequestOptions), Items: menuItems})
|
||||
return self.c.Menu(types.CreateMenuOptions{Title: fmt.Sprint(self.c.Tr.CreatePullRequestOptions), Items: menuItems})
|
||||
}
|
||||
|
||||
func (self *BranchesController) createPullRequest(from string, to string) error {
|
||||
|
||||
@@ -26,10 +26,9 @@ func (self *CommandLogController) GetKeybindings(opts types.KeybindingsOpts) []*
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *CommandLogController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return func(types.OnFocusLostOpts) error {
|
||||
func (self *CommandLogController) GetOnFocusLost() func(types.OnFocusLostOpts) {
|
||||
return func(types.OnFocusLostOpts) {
|
||||
self.c.Views().Extras.Autoscroll = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
@@ -45,19 +46,27 @@ func (self *CommitDescriptionController) GetKeybindings(opts types.KeybindingsOp
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) Context() types.Context {
|
||||
return self.context()
|
||||
return self.c.Contexts().CommitDescription
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) context() *context.CommitMessageContext {
|
||||
return self.c.Contexts().CommitMessage
|
||||
func (self *CommitDescriptionController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
|
||||
return []*gocui.ViewMouseBinding{
|
||||
{
|
||||
ViewName: self.Context().GetViewName(),
|
||||
Key: gocui.MouseLeft,
|
||||
Handler: self.onClick,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) switchToCommitMessage() error {
|
||||
return self.c.ReplaceContext(self.c.Contexts().CommitMessage)
|
||||
self.c.Context().Replace(self.c.Contexts().CommitMessage)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) close() error {
|
||||
return self.c.Helpers().Commits.CloseCommitMessagePanel()
|
||||
self.c.Helpers().Commits.CloseCommitMessagePanel()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) confirm() error {
|
||||
@@ -68,3 +77,12 @@ func (self *CommitDescriptionController) openCommitMenu() error {
|
||||
authorSuggestion := self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc()
|
||||
return self.c.Helpers().Commits.OpenCommitMenu(authorSuggestion)
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) onClick(opts gocui.ViewMouseBindingOpts) error {
|
||||
// Activate the description panel when the commit message panel is currently active
|
||||
if self.c.Context().Current().GetKey() == context.COMMIT_MESSAGE_CONTEXT_KEY {
|
||||
self.c.Context().Replace(self.c.Contexts().CommitDescription)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package controllers
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
@@ -58,10 +59,19 @@ func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) GetOnFocusLost() func(types.OnFocusLostOpts) error {
|
||||
return func(types.OnFocusLostOpts) error {
|
||||
func (self *CommitMessageController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
|
||||
return []*gocui.ViewMouseBinding{
|
||||
{
|
||||
ViewName: self.Context().GetViewName(),
|
||||
Key: gocui.MouseLeft,
|
||||
Handler: self.onClick,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) GetOnFocusLost() func(types.OnFocusLostOpts) {
|
||||
return func(types.OnFocusLostOpts) {
|
||||
self.context().RenderCommitLength()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,9 +95,7 @@ func (self *CommitMessageController) handleNextCommit() error {
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) switchToCommitDescription() error {
|
||||
if err := self.c.ReplaceContext(self.c.Contexts().CommitDescription); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Context().Replace(self.c.Contexts().CommitDescription)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -118,8 +126,8 @@ func (self *CommitMessageController) setCommitMessageAtIndex(index int) (bool, e
|
||||
}
|
||||
return false, errors.New(self.c.Tr.CommitWithoutMessageErr)
|
||||
}
|
||||
if self.c.UserConfig.Git.Commit.AutoWrapCommitMessage {
|
||||
commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig.Git.Commit.AutoWrapWidth)
|
||||
if self.c.UserConfig().Git.Commit.AutoWrapCommitMessage {
|
||||
commitMessage = helpers.TryRemoveHardLineBreaks(commitMessage, self.c.UserConfig().Git.Commit.AutoWrapWidth)
|
||||
}
|
||||
self.c.Helpers().Commits.UpdateCommitPanelView(commitMessage)
|
||||
return true, nil
|
||||
@@ -130,10 +138,20 @@ func (self *CommitMessageController) confirm() error {
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) close() error {
|
||||
return self.c.Helpers().Commits.CloseCommitMessagePanel()
|
||||
self.c.Helpers().Commits.CloseCommitMessagePanel()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) openCommitMenu() error {
|
||||
authorSuggestion := self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc()
|
||||
return self.c.Helpers().Commits.OpenCommitMenu(authorSuggestion)
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) onClick(opts gocui.ViewMouseBindingOpts) error {
|
||||
// Activate the commit message panel when the commit description panel is currently active
|
||||
if self.c.Context().Current().GetKey() == context.COMMIT_DESCRIPTION_CONTEXT_KEY {
|
||||
self.c.Context().Replace(self.c.Contexts().CommitMessage)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user