mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-29 10:36:45 -04:00
Compare commits
94
Commits
@@ -48,6 +48,9 @@ while still being meaningful and self-contained.
|
||||
commits that leave the tree broken and rely on a follow-up to fix it.
|
||||
- **Every commit must be `gofumpt`-formatted.** Run `just format` before
|
||||
committing.
|
||||
- **Every commit must be lint-clean.** Run `just lint` before committing —
|
||||
don't introduce a lint warning in one commit and rely on a later commit
|
||||
(or the user) to clean it up.
|
||||
- **Commit messages explain _why_, not _what_.** The diff already shows what
|
||||
changed; the message should capture the motivation, the constraint, or the
|
||||
bug being fixed. If the reason is obvious from a one-line subject, no body
|
||||
@@ -157,6 +160,16 @@ genuine forks — the ones where a reasonable person might pick differently, or
|
||||
where you'd be trading away something the plan assumed (scope, UX, performance,
|
||||
reload behavior, …). When in doubt, surface it.
|
||||
|
||||
This applies with equal force to unforeseen _discoveries_, not just to
|
||||
decisions you set out to make. If you find something the plan didn't account
|
||||
for — a latent bug, a race, a wrong assumption, a case that turns out
|
||||
unhandled — stop and raise it before designing or writing a fix, even when the
|
||||
fix seems obvious and even when it's "just correctness." Finding the problem is
|
||||
itself the fork: whether to fix it here or in a separate change, how generally
|
||||
to solve it, and whether it reshapes the current work are all calls for me to
|
||||
make with you. Don't quietly fold a self-directed fix for a newly-found problem
|
||||
into the branch and let me discover it in the diff.
|
||||
|
||||
## Prefer the cleaner design over the smaller diff
|
||||
|
||||
When a task could be implemented either by tacking onto existing code or by
|
||||
@@ -243,6 +256,34 @@ Follow this even when the need for the refactor is only discovered in the middle
|
||||
of working on the branch; suggest to the user to rewrite the history to move the
|
||||
refactor to an earlier commit (but don't do it without asking first).
|
||||
|
||||
## Don't read model state right after a `Refresh`
|
||||
|
||||
A `Refresh` (or `RefreshFromWorker`) does its git work on a worker and then
|
||||
*enqueues* the model update onto the UI thread. So when `Refresh` returns, the
|
||||
model is **not** updated yet — the write is still queued. Reading a field
|
||||
synchronously right after refreshing its scope reads the stale, pre-refresh
|
||||
value (and this is true even for SYNC refreshes):
|
||||
|
||||
```go
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
files := self.c.Model().Files // BUG: still the pre-refresh value
|
||||
```
|
||||
|
||||
Put the read in `RefreshOptions.Then` instead — it's queued after the scope's
|
||||
model writes, so it sees the fresh value:
|
||||
|
||||
```go
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Scope: []types.RefreshableView{types.FILES},
|
||||
Then: func() error {
|
||||
files := self.c.Model().Files // fresh
|
||||
return nil
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
`Then` is a `func() error` and works with any non-`ASYNC` mode.
|
||||
|
||||
## Integration test conventions
|
||||
|
||||
Don't bind views to local variables. Always chain method calls directly from
|
||||
|
||||
@@ -73,6 +73,8 @@ gui:
|
||||
# If true, capture mouse events.
|
||||
# When mouse events are captured, it's a little harder to select text: e.g.
|
||||
# requiring you to hold the option key when on macOS.
|
||||
# Mouse capture can also be toggled at runtime with the 'toggleMouseCapture'
|
||||
# keybinding ('%' by default).
|
||||
mouseEvents: true
|
||||
|
||||
# If true, do not show a warning when amending a commit.
|
||||
@@ -419,9 +421,7 @@ git:
|
||||
squashMergeMessage: Squash merge {{selectedRef}} into {{currentBranch}}
|
||||
|
||||
# list of branches that are considered 'main' branches, used when displaying
|
||||
# commits and for determining each branch's base. Lazygit prompts when a branch
|
||||
# could be based on more than one of these (typical when one is regularly merged
|
||||
# into another).
|
||||
# commits
|
||||
mainBranches:
|
||||
- master
|
||||
- main
|
||||
@@ -727,6 +727,7 @@ keybinding:
|
||||
submitEditorText: <enter>
|
||||
extrasMenu: '@'
|
||||
toggleWhitespaceInDiffView: <ctrl+w>
|
||||
toggleMouseCapture: '%'
|
||||
increaseContextInDiffView: '}'
|
||||
decreaseContextInDiffView: '{'
|
||||
increaseRenameSimilarityThreshold: )
|
||||
|
||||
@@ -79,6 +79,22 @@ git:
|
||||
- externalDiffCommand: difft --color=always --display=inline --syntax-highlight=off
|
||||
```
|
||||
|
||||
This can also be used for normal git diffs with custom parameters, such as `--color-words` or `--word-diff` which some people find useful. To do that, save a script like this to, say, `~/bin/color-words.sh`:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
|
||||
git diff --color-words --no-index --color=always --no-ext-diff "$2" "$5"
|
||||
```
|
||||
|
||||
And then use it in your git config like so:
|
||||
|
||||
```yaml
|
||||
git:
|
||||
pagers:
|
||||
- externalDiffCommand: ~/bin/color-words.sh
|
||||
```
|
||||
|
||||
Instead of setting this command in lazygit's `externalDiffCommand` config, you can also tell lazygit to use the external diff command that is configured in git itself (`diff.external`), by using
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -31,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` q, <ctrl+c> `` | Quit | |
|
||||
| `` <ctrl+z> `` | Suspend the application | |
|
||||
| `` <ctrl+w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
|
||||
| `` % `` | Toggle mouse capture | Toggle whether lazygit captures mouse events. When mouse capture is off, the terminal handles the mouse, so you can select text with it; this is useful in terminals that don't have a modifier key to bypass the capture.<br><br>The default can be changed in the config file with the key 'gui.mouseEvents'. |
|
||||
| `` <alt+shift+c> `` | Edit config file | Open file in external editor. |
|
||||
| `` z `` | Undo | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
|
||||
| `` Z `` | Redo | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
|
||||
|
||||
@@ -31,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` q, <ctrl+c> `` | 終了 | |
|
||||
| `` <ctrl+z> `` | Suspend the application | |
|
||||
| `` <ctrl+w> `` | 空白表示の切り替え | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
|
||||
| `` % `` | Toggle mouse capture | Toggle whether lazygit captures mouse events. When mouse capture is off, the terminal handles the mouse, so you can select text with it; this is useful in terminals that don't have a modifier key to bypass the capture.<br><br>The default can be changed in the config file with the key 'gui.mouseEvents'. |
|
||||
| `` <alt+shift+c> `` | 設定ファイルを編集 | 外部エディタでファイルを開きます。 |
|
||||
| `` z `` | 元に戻す | 最後のgitコマンドを元に戻すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 |
|
||||
| `` Z `` | やり直す | 最後のgitコマンドをやり直すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 |
|
||||
|
||||
@@ -31,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` q, <ctrl+c> `` | 종료 | |
|
||||
| `` <ctrl+z> `` | Suspend the application | |
|
||||
| `` <ctrl+w> `` | 공백문자를 Diff 뷰에서 표시 여부 전환 | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
|
||||
| `` % `` | Toggle mouse capture | Toggle whether lazygit captures mouse events. When mouse capture is off, the terminal handles the mouse, so you can select text with it; this is useful in terminals that don't have a modifier key to bypass the capture.<br><br>The default can be changed in the config file with the key 'gui.mouseEvents'. |
|
||||
| `` <alt+shift+c> `` | 설정 파일 수정 | Open file in external editor. |
|
||||
| `` 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. |
|
||||
| `` 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. |
|
||||
|
||||
@@ -10,15 +10,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <pgup>, K, <ctrl+u> (fn+up/shift+k) `` | Scroll naar beneden vanaf hoofdpaneel | |
|
||||
| `` <pgdown>, J, <ctrl+d> (fn+down/shift+j) `` | Scroll naar beneden vanaf hoofdpaneel | |
|
||||
| `` @ `` | View command log options | View options for the command log e.g. show/hide the command log and focus the command log. |
|
||||
| `` P `` | Push | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
|
||||
| `` p `` | Pull | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
|
||||
| `` P `` | Push | Push de huidige branch naar de bijbehorende upstream-branch. Als er geen upstream is geconfigureerd wordt er gevraagd om een upstream-branch te configureren. |
|
||||
| `` p `` | Pull | Pull wijzigingen van de remote voor de huidige branch. Als er geen upstream is geconfigureerd wordt er gevraagd om een upstream-branch te configureren. |
|
||||
| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.<br><br>The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. |
|
||||
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.<br><br>The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. |
|
||||
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view.<br><br>The default can be changed in the config file with the key 'git.diffContextSize'. |
|
||||
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view.<br><br>The default can be changed in the config file with the key 'git.diffContextSize'. |
|
||||
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
|
||||
| `` <ctrl+p> `` | Bekijk aangepaste patch opties | |
|
||||
| `` m `` | Bekijk merge/rebase opties | View options to abort/continue/skip the current merge/rebase. |
|
||||
| `` m `` | Bekijk merge/rebase opties | Toon abort/continue/skip opties voor huidige 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`. |
|
||||
| `` + `` | Volgende scherm modus (normaal/half/groot) | |
|
||||
| `` _ `` | Vorige scherm modus | |
|
||||
@@ -28,10 +28,11 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` ? `` | Open menu | |
|
||||
| `` <ctrl+s> `` | Bekijk scoping opties | View options for filtering the commit log, so that only commits matching the filter are shown. |
|
||||
| `` W, <ctrl+e> `` | Open diff menu | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
|
||||
| `` q, <ctrl+c> `` | Quit | |
|
||||
| `` <ctrl+z> `` | Suspend the application | |
|
||||
| `` q, <ctrl+c> `` | Afsluiten | |
|
||||
| `` <ctrl+z> `` | Pauzeer de applicatie | |
|
||||
| `` <ctrl+w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
|
||||
| `` <alt+shift+c> `` | Verander config bestand | Open file in external editor. |
|
||||
| `` % `` | Toggle mouse capture | Toggle whether lazygit captures mouse events. When mouse capture is off, the terminal handles the mouse, so you can select text with it; this is useful in terminals that don't have a modifier key to bypass the capture.<br><br>The default can be changed in the config file with the key 'gui.mouseEvents'. |
|
||||
| `` <alt+shift+c> `` | Verander config bestand | Open bestand in externe editor. |
|
||||
| `` z `` | Ongedaan maken (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
|
||||
| `` Z `` | Redo (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
|
||||
|
||||
@@ -47,8 +48,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <shift+down> `` | Range select down | |
|
||||
| `` <shift+up> `` | Range select up | |
|
||||
| `` / `` | Start met zoeken | |
|
||||
| `` H `` | Scroll left | |
|
||||
| `` L `` | Scroll right | |
|
||||
| `` H `` | Scroll naar links | |
|
||||
| `` L `` | Scroll naar rechts | |
|
||||
| `` ] `` | Volgende tabblad | |
|
||||
| `` [ `` | Vorige tabblad | |
|
||||
|
||||
@@ -58,15 +59,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|-----|--------|-------------|
|
||||
| `` <ctrl+o> `` | Kopieer de bestandsnaam naar het klembord | |
|
||||
| `` <space> `` | Toggle staged | Toggle staged for selected file. |
|
||||
| `` <ctrl+b> `` | Filter files by status | |
|
||||
| `` y `` | Copy to clipboard | |
|
||||
| `` c `` | Commit veranderingen | Commit staged changes. |
|
||||
| `` <ctrl+b> `` | Filter bestanden op status | |
|
||||
| `` y `` | Kopieer naar klembord | |
|
||||
| `` c `` | Commit veranderingen | Commit gestagede wijzigingen. |
|
||||
| `` w `` | Commit veranderingen zonder pre-commit hook | |
|
||||
| `` A `` | Wijzig laatste commit | |
|
||||
| `` C `` | Commit veranderingen met de git editor | |
|
||||
| `` <ctrl+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 bestand | Open file in default application. |
|
||||
| `` <ctrl+f> `` | Find base commit for fixup | Vind de commit waar je huidige wijzigingen bovenop zijn gebouwd met als doel die commit te amenden/fixen. Hierdoor hoef je dit niet met de hand te doen. Zie: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
|
||||
| `` e `` | Edit | Open bestand in externe editor. |
|
||||
| `` o `` | Open bestand | Open bestand in standaardapplicatie. |
|
||||
| `` i `` | Ignore or exclude file | |
|
||||
| `` r `` | Refresh bestanden | |
|
||||
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
|
||||
@@ -75,13 +76,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <enter> `` | Stage individuele hunks/lijnen | 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 `` | Bekijk 'veranderingen ongedaan maken' opties | View options for discarding changes to the selected file. |
|
||||
| `` g `` | Bekijk upstream reset opties | |
|
||||
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). |
|
||||
| `` D `` | Resetten | View reset options for working tree (e.g. nuking the working tree). |
|
||||
| `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
|
||||
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
|
||||
| `` M `` | Bekijk merge conflict opties | Bekijk opties voor het oplossen van mergeconflicten. |
|
||||
| `` f `` | Fetch | Fetch changes from remote. |
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` = `` | Vouw alle bestanden uit | Vouw alle mappen in de bestandsstructuur uit |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
@@ -91,7 +92,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | Bevestig | |
|
||||
| `` <esc> `` | Sluiten | |
|
||||
| `` <ctrl+o> `` | Copy to clipboard | |
|
||||
| `` <ctrl+o> `` | Kopieer naar klembord | |
|
||||
|
||||
## Branches
|
||||
|
||||
@@ -99,19 +100,19 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|-----|--------|-------------|
|
||||
| `` <ctrl+o> `` | Kopieer branch name naar klembord | |
|
||||
| `` i `` | Laat git-flow opties zien | |
|
||||
| `` <space> `` | Uitchecken | Checkout selected item. |
|
||||
| `` <space> `` | Uitchecken | Geselecteerd item uitchecken. |
|
||||
| `` n `` | Nieuwe branch | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` N `` | Verplaats commits naar nieuwe branch | Maak een nieuwe branch en verplaats niet-gepushte commits van de huidige branch hier naar toe. Gebruik dit in het geval dat je deze commits eigenlijk op een nieuwe branch had willen maken.<br><br>Let op dat de selectie genegeerd wordt. De nieuwe branch komt ofwel bovenop de main branch, of bovenop de huidige branch (je kan kiezen). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` o `` | Maak een pull-request | |
|
||||
| `` O `` | Bekijk opties voor pull-aanvraag | |
|
||||
| `` G `` | Open pull request in browser | |
|
||||
| `` <ctrl+y> `` | Kopieer de URL van het pull-verzoek naar het klembord | |
|
||||
| `` c `` | Uitchecken bij naam | Checkout by name. In the input box you can enter '-' to switch to the previous branch. |
|
||||
| `` - `` | Checkout previous branch | |
|
||||
| `` - `` | Vorige branch uitchecken | |
|
||||
| `` F `` | Forceer checkout | 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 branch | Rebase the checked-out branch onto the selected branch. |
|
||||
| `` d `` | Verwijderen | View delete options for local/remote branch. |
|
||||
| `` r `` | Rebase branch | Rebase de uitgecheckte branch bovenop de geselecteerde branch. |
|
||||
| `` M `` | Merge in met huidige checked out branch | View options for merging the selected item into the current branch (regular merge, squash merge) |
|
||||
| `` f `` | Fast-forward deze branch vanaf zijn upstream | Fast-forward selected branch from its upstream. |
|
||||
| `` T `` | Creëer tag | |
|
||||
@@ -119,7 +120,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` g `` | Bekijk reset opties | |
|
||||
| `` R `` | Hernoem branch | |
|
||||
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Bekijk commits | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
@@ -136,18 +137,18 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <ctrl+o> `` | Kopieer de bestandsnaam naar het klembord | |
|
||||
| `` y `` | Copy to clipboard | |
|
||||
| `` y `` | Kopieer naar klembord | |
|
||||
| `` c `` | Uitchecken | Bestand uitchecken |
|
||||
| `` d `` | Bekijk 'veranderingen ongedaan maken' opties | Uitsluit deze commit zijn veranderingen aan dit bestand |
|
||||
| `` o `` | Open bestand | Open file in default application. |
|
||||
| `` e `` | Edit | Open file in external editor. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` o `` | Open bestand | Open bestand in standaardapplicatie. |
|
||||
| `` e `` | Edit | Open bestand in externe editor. |
|
||||
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
|
||||
| `` <space> `` | Toggle bestand inbegrepen in patch | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
|
||||
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
|
||||
| `` <enter> `` | Enter bestand om geselecteerde regels toe te voegen aan de patch | 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 bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` = `` | Vouw alle bestanden uit | Vouw alle mappen in de bestandsstructuur uit |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
@@ -183,11 +184,11 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
|
||||
| `` o `` | Open commit in browser | |
|
||||
| `` n `` | Creëer nieuwe branch van commit | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` N `` | Verplaats commits naar nieuwe branch | Maak een nieuwe branch en verplaats niet-gepushte commits van de huidige branch hier naar toe. Gebruik dit in het geval dat je deze commits eigenlijk op een nieuwe branch had willen maken.<br><br>Let op dat de selectie genegeerd wordt. De nieuwe branch komt ofwel bovenop de main branch, of bovenop de huidige branch (je kan kiezen). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Bekijk gecommite bestanden | |
|
||||
@@ -219,9 +220,9 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <left>, h `` | Selecteer voorgaand conflict | |
|
||||
| `` <right>, l `` | Selecteer volgende conflict | |
|
||||
| `` z `` | Ongedaan maken | Undo last merge conflict resolution. |
|
||||
| `` e `` | Verander bestand | Open file in external editor. |
|
||||
| `` o `` | Open bestand | Open file in default application. |
|
||||
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
|
||||
| `` e `` | Verander bestand | Open bestand in externe editor. |
|
||||
| `` o `` | Open bestand | Open bestand in standaardapplicatie. |
|
||||
| `` M `` | Bekijk merge conflict opties | Bekijk opties voor het oplossen van mergeconflicten. |
|
||||
| `` <esc> `` | Ga terug naar het bestanden paneel | |
|
||||
|
||||
## Normaal
|
||||
@@ -243,8 +244,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` v `` | Toggle drag selecteer | |
|
||||
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. |
|
||||
| `` <ctrl+o> `` | Copy selected text to clipboard | |
|
||||
| `` o `` | Open bestand | Open file in default application. |
|
||||
| `` e `` | Verander bestand | Open file in external editor. |
|
||||
| `` o `` | Open bestand | Open bestand in standaardapplicatie. |
|
||||
| `` e `` | Verander bestand | Open bestand in externe editor. |
|
||||
| `` <space> `` | Voeg toe/verwijder lijn(en) in patch | |
|
||||
| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. |
|
||||
| `` <esc> `` | Sluit lijn-bij-lijn modus | |
|
||||
@@ -259,12 +260,12 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
|
||||
| `` o `` | Open commit in browser | |
|
||||
| `` n `` | Creëer nieuwe branch van commit | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` N `` | Verplaats commits naar nieuwe branch | Maak een nieuwe branch en verplaats niet-gepushte commits van de huidige branch hier naar toe. Gebruik dit in het geval dat je deze commits eigenlijk op een nieuwe branch had willen maken.<br><br>Let op dat de selectie genegeerd wordt. De nieuwe branch komt ofwel bovenop de main branch, of bovenop de huidige branch (je kan kiezen). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <ctrl+r> `` | Reset cherry-picked (gekopieerde) commits selectie | |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Bekijk commits | |
|
||||
@@ -275,16 +276,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <ctrl+o> `` | Kopieer branch name naar klembord | |
|
||||
| `` <space> `` | Uitchecken | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
|
||||
| `` <space> `` | Uitchecken | Geselecteerde remote branch uitchecken als nieuwe locale branch of als detached head. |
|
||||
| `` n `` | Nieuwe branch | |
|
||||
| `` w `` | New worktree | |
|
||||
| `` M `` | Merge in met huidige checked out branch | View options for merging the selected item into the current branch (regular merge, squash merge) |
|
||||
| `` r `` | Rebase branch | Rebase the checked-out branch onto the selected branch. |
|
||||
| `` d `` | Delete | Delete the remote branch from the remote. |
|
||||
| `` u `` | Set as upstream | Stel in als upstream van uitgecheckte branch |
|
||||
| `` r `` | Rebase branch | Rebase de uitgecheckte branch bovenop de geselecteerde branch. |
|
||||
| `` d `` | Verwijderen | Delete the remote branch from the remote. |
|
||||
| `` u `` | Instellen als upstream | Stel in als upstream van uitgecheckte branch |
|
||||
| `` s `` | Sort order | |
|
||||
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Bekijk commits | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
@@ -293,9 +294,9 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | View branches | |
|
||||
| `` <enter> `` | Bekijk branches | |
|
||||
| `` n `` | Voeg een nieuwe remote toe | |
|
||||
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. |
|
||||
| `` d `` | Verwijderen | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. |
|
||||
| `` e `` | Edit | Wijzig remote |
|
||||
| `` f `` | Fetch | Fetch remote |
|
||||
| `` F `` | Add fork remote | Quickly add a fork remote by replacing the owner in the origin URL and optionally check out a branch from new remote. |
|
||||
@@ -320,15 +321,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | Copy selected text to clipboard | |
|
||||
| `` <space> `` | Toggle staged | Toggle lijnen staged / unstaged |
|
||||
| `` d `` | Verwijdert change (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. |
|
||||
| `` o `` | Open bestand | Open file in default application. |
|
||||
| `` e `` | Verander bestand | Open file in external editor. |
|
||||
| `` o `` | Open bestand | Open bestand in standaardapplicatie. |
|
||||
| `` e `` | Verander bestand | Open bestand in externe editor. |
|
||||
| `` <esc> `` | Ga terug naar het bestanden paneel | |
|
||||
| `` <tab> `` | Ga naar een ander paneel | Switch to other view (staged/unstaged changes). |
|
||||
| `` E `` | Edit hunk | Edit selected hunk in external editor. |
|
||||
| `` c `` | Commit veranderingen | Commit staged changes. |
|
||||
| `` c `` | Commit veranderingen | Commit gestagede wijzigingen. |
|
||||
| `` w `` | Commit veranderingen zonder pre-commit hook | |
|
||||
| `` C `` | Commit veranderingen met de git editor | |
|
||||
| `` <ctrl+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> |
|
||||
| `` <ctrl+f> `` | Find base commit for fixup | Vind de commit waar je huidige wijzigingen bovenop zijn gebouwd met als doel die commit te amenden/fixen. Hierdoor hoef je dit niet met de hand te doen. Zie: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
|
||||
| `` / `` | Start met zoeken | |
|
||||
|
||||
## Stash
|
||||
@@ -340,7 +341,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` d `` | Laten vallen | Remove the stash entry from the stash list. |
|
||||
| `` n `` | Nieuwe branch | 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. |
|
||||
| `` w `` | New worktree | |
|
||||
| `` r `` | Rename stash | |
|
||||
| `` r `` | Hernoem stash | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Bekijk gecommite bestanden | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
@@ -349,7 +350,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` e `` | Verander config bestand | Open file in external editor. |
|
||||
| `` e `` | Verander config bestand | Open bestand in externe editor. |
|
||||
| `` u `` | Check voor updates | |
|
||||
| `` <enter> `` | Wissel naar een recente repo | |
|
||||
| `` a `` | Show/cycle all branch logs | |
|
||||
@@ -365,12 +366,12 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
|
||||
| `` o `` | Open commit in browser | |
|
||||
| `` n `` | Creëer nieuwe branch van commit | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` N `` | Verplaats commits naar nieuwe branch | Maak een nieuwe branch en verplaats niet-gepushte commits van de huidige branch hier naar toe. Gebruik dit in het geval dat je deze commits eigenlijk op een nieuwe branch had willen maken.<br><br>Let op dat de selectie genegeerd wordt. De nieuwe branch komt ofwel bovenop de main branch, of bovenop de huidige branch (je kan kiezen). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <ctrl+r> `` | Reset cherry-picked (gekopieerde) commits selectie | |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Bekijk gecommite bestanden | |
|
||||
@@ -382,7 +383,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|-----|--------|-------------|
|
||||
| `` <ctrl+o> `` | Kopieer submodule naam naar klembord | |
|
||||
| `` <enter> `` | Enter | Enter submodule |
|
||||
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. |
|
||||
| `` d `` | Verwijderen | Remove the selected submodule and its corresponding directory. |
|
||||
| `` u `` | Update | Update selected submodule. |
|
||||
| `` n `` | Voeg nieuwe submodule toe | |
|
||||
| `` e `` | Update submodule URL | |
|
||||
@@ -395,13 +396,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <ctrl+o> `` | Copy tag to clipboard | |
|
||||
| `` <space> `` | Uitchecken | Checkout the selected tag as a detached HEAD. |
|
||||
| `` <space> `` | Uitchecken | Geselecteerde tag uitchecken als detached HEAD. |
|
||||
| `` n `` | Creëer tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` w `` | New worktree | |
|
||||
| `` d `` | Delete | View delete options for local/remote tag. |
|
||||
| `` d `` | Verwijderen | View delete options for local/remote tag. |
|
||||
| `` P `` | Push tag | 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. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` g `` | Resetten | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Bekijk commits | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
@@ -412,6 +413,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|-----|--------|-------------|
|
||||
| `` 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. |
|
||||
| `` o `` | Openen in editor | |
|
||||
| `` d `` | Verwijderen | 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 | |
|
||||
|
||||
@@ -31,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` q, <ctrl+c> `` | Wyjdź | |
|
||||
| `` <ctrl+z> `` | Suspend the application | |
|
||||
| `` <ctrl+w> `` | Przełącz białe znaki | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
|
||||
| `` % `` | Toggle mouse capture | Toggle whether lazygit captures mouse events. When mouse capture is off, the terminal handles the mouse, so you can select text with it; this is useful in terminals that don't have a modifier key to bypass the capture.<br><br>The default can be changed in the config file with the key 'gui.mouseEvents'. |
|
||||
| `` <alt+shift+c> `` | Edytuj plik konfiguracyjny | Otwórz plik w zewnętrznym edytorze. |
|
||||
| `` z `` | Cofnij | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby cofnąć ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. |
|
||||
| `` Z `` | Ponów | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby ponowić ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. |
|
||||
|
||||
@@ -31,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` q, <ctrl+c> `` | Sair | |
|
||||
| `` <ctrl+z> `` | Suspender a aplicação | |
|
||||
| `` <ctrl+w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
|
||||
| `` % `` | Toggle mouse capture | Toggle whether lazygit captures mouse events. When mouse capture is off, the terminal handles the mouse, so you can select text with it; this is useful in terminals that don't have a modifier key to bypass the capture.<br><br>The default can be changed in the config file with the key 'gui.mouseEvents'. |
|
||||
| `` <alt+shift+c> `` | Editar arquivo de configuração | Abrir arquivo no editor externo. |
|
||||
| `` z `` | Desfazer | O reflog será usado para determinar qual comando git para executar para desfazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. |
|
||||
| `` Z `` | Refazer | O reflog será usado para determinar qual comando git para executar para refazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. |
|
||||
|
||||
@@ -31,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` q, <ctrl+c> `` | Выйти | |
|
||||
| `` <ctrl+z> `` | Suspend the application | |
|
||||
| `` <ctrl+w> `` | Переключить отображение изменении пробелов в просмотрщике сравнении | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
|
||||
| `` % `` | Toggle mouse capture | Toggle whether lazygit captures mouse events. When mouse capture is off, the terminal handles the mouse, so you can select text with it; this is useful in terminals that don't have a modifier key to bypass the capture.<br><br>The default can be changed in the config file with the key 'gui.mouseEvents'. |
|
||||
| `` <alt+shift+c> `` | Редактировать файл конфигурации | Open file in external editor. |
|
||||
| `` z `` | Отменить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git запустить, чтобы отменить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. |
|
||||
| `` Z `` | Повторить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git нужно запустить, чтобы повторить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. |
|
||||
|
||||
@@ -31,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` q, <ctrl+c> `` | 退出 | |
|
||||
| `` <ctrl+z> `` | 挂起应用程序 | |
|
||||
| `` <ctrl+w> `` | 切换是否在差异视图中显示空白字符差异 | 切换是否在差异视图中显示空白字符更改。<br><br>默认值可在配置文件中通过键 'git.ignoreWhitespaceInDiffView' 更改。 |
|
||||
| `` % `` | Toggle mouse capture | Toggle whether lazygit captures mouse events. When mouse capture is off, the terminal handles the mouse, so you can select text with it; this is useful in terminals that don't have a modifier key to bypass the capture.<br><br>The default can be changed in the config file with the key 'gui.mouseEvents'. |
|
||||
| `` <alt+shift+c> `` | 编辑配置文件 | 使用外部编辑器打开文件 |
|
||||
| `` z `` | 撤销 | Reflog将用于确定运行哪个git命令来撤消最后一个git命令。这并不包括对工作树的更改,只考虑提交。 |
|
||||
| `` Z `` | 重做 | Reflog将用于确定运行哪个git命令来重做上一个git命令。这并不包括对工作树的更改,只考虑提交。 |
|
||||
|
||||
@@ -31,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` q, <ctrl+c> `` | 結束 | |
|
||||
| `` <ctrl+z> `` | Suspend the application | |
|
||||
| `` <ctrl+w> `` | 切換是否在差異檢視中顯示空格變更 | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
|
||||
| `` % `` | Toggle mouse capture | Toggle whether lazygit captures mouse events. When mouse capture is off, the terminal handles the mouse, so you can select text with it; this is useful in terminals that don't have a modifier key to bypass the capture.<br><br>The default can be changed in the config file with the key 'gui.mouseEvents'. |
|
||||
| `` <alt+shift+c> `` | 編輯設定檔案 | 使用外部編輯器開啟 |
|
||||
| `` z `` | 復原 | 將使用 reflog 確任 git 指令以復原。這不包括工作區更改;只考慮提交。 |
|
||||
| `` Z `` | 取消復原 | 將使用 reflog 確任 git 指令以重作。這不包括工作區更改;只考慮提交。 |
|
||||
|
||||
+56
-2
@@ -110,6 +110,26 @@ gui:
|
||||
# is true.
|
||||
expandedSidePanelWeight: 2
|
||||
|
||||
# If true, don't give a side panel more height than it needs to show its
|
||||
# content; when all panels fit, the leftover height is shared among them so that
|
||||
# they still fill the screen.
|
||||
shrinkSidePanelsToContent: false
|
||||
|
||||
# The side panels, in the order they appear from top to bottom.
|
||||
# Each entry is a list of one or more names that share a single panel as tabs
|
||||
# (cycle through them with the next-tab/previous-tab keys).
|
||||
# Omit a name to hide it; give a name its own one-element list to promote a tab
|
||||
# to a top-level panel.
|
||||
# Valid names are: 'status', 'files', 'worktrees', 'submodules', 'branches',
|
||||
# 'remotes', 'tags', 'commits', 'reflog', 'stash'. 'files', 'branches', and
|
||||
# 'commits' must always be included; they can't be hidden.
|
||||
sidePanels:
|
||||
- [status]
|
||||
- [files, worktrees, submodules]
|
||||
- [branches, remotes, tags]
|
||||
- [commits, reflog]
|
||||
- [stash]
|
||||
|
||||
# Sometimes the main window is split in two (e.g. when the selected file has
|
||||
# both staged and unstaged changes). This setting controls how the two sections
|
||||
# are split.
|
||||
@@ -342,6 +362,11 @@ gui:
|
||||
git:
|
||||
# Array of pagers. Each entry has the following format:
|
||||
#
|
||||
# # A name for the pager, shown in the notification when cycling pagers.
|
||||
# # If not set, the name is derived from the first word of the pager
|
||||
# # command (or of the external diff command).
|
||||
# name: ""
|
||||
#
|
||||
# # Value of the --color arg in the git diff command. Some pagers want
|
||||
# # this to be set to 'always' and some want it set to 'never'
|
||||
# colorArg: "always"
|
||||
@@ -361,6 +386,9 @@ git:
|
||||
# # https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver.
|
||||
# useExternalDiffGitConfig: false
|
||||
#
|
||||
# 'pager', 'externalDiffCommand', and 'useExternalDiffGitConfig' are mutually
|
||||
# exclusive; set at most one per entry.
|
||||
#
|
||||
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md
|
||||
# for more information.
|
||||
pagers: []
|
||||
@@ -406,6 +434,11 @@ git:
|
||||
# If true, periodically refresh files and submodules
|
||||
autoRefresh: true
|
||||
|
||||
# If true, poll the repo periodically for external ref changes (commits, branch
|
||||
# updates, checkouts made outside lazygit) and refresh when one is detected.
|
||||
# Independent of autoRefresh, which only governs the files panel.
|
||||
autoDetectExternalChanges: true
|
||||
|
||||
# If not "none", lazygit will automatically fast-forward local branches to match
|
||||
# their upstream after fetching. Applies to branches that are not the currently
|
||||
# checked out branch, and only to those that are strictly behind their upstream
|
||||
@@ -499,6 +532,15 @@ git:
|
||||
# to 40 to disable truncation.
|
||||
truncateCopiedCommitHashesTo: 12
|
||||
|
||||
# Config relating to git worktrees
|
||||
worktree:
|
||||
# Default parent directory for new worktrees. It is offered as a candidate
|
||||
# location alongside the parent directories of any worktrees you already have.
|
||||
# A relative path is resolved against the repository's root directory, so
|
||||
# "../worktrees" sits beside the repo and ".worktrees" sits inside it.
|
||||
# A leading "~" is expanded to your home directory, so "~/worktrees" works.
|
||||
defaultPath: ""
|
||||
|
||||
# Periodic update checks
|
||||
update:
|
||||
# One of: 'prompt' (default) | 'background' | 'never'
|
||||
@@ -517,6 +559,11 @@ refresher:
|
||||
# Auto-fetch can be disabled via option 'git.autoFetch'.
|
||||
fetchInterval: 60
|
||||
|
||||
# Interval in seconds at which lazygit polls for external ref changes (commits,
|
||||
# branch updates, checkouts made outside lazygit).
|
||||
# Detection can be disabled via option 'git.autoDetectExternalChanges'.
|
||||
externalChangeCheckInterval: 2
|
||||
|
||||
# If true, show a confirmation popup before quitting Lazygit
|
||||
confirmOnQuit: false
|
||||
|
||||
@@ -648,6 +695,7 @@ keybinding:
|
||||
confirmInEditor: [<ctrl+enter>, <ctrl+s>]
|
||||
remove: d
|
||||
new: "n"
|
||||
newWorktree: w
|
||||
edit: e
|
||||
openFile: o
|
||||
scrollUpMain: [<pgup>, K, <ctrl+u>]
|
||||
@@ -667,6 +715,7 @@ keybinding:
|
||||
nextScreenMode: +
|
||||
prevScreenMode: _
|
||||
cyclePagers: '|'
|
||||
cyclePagersReverse: \
|
||||
undo: z
|
||||
redo: Z
|
||||
filteringMenu: <ctrl+s>
|
||||
@@ -681,6 +730,7 @@ keybinding:
|
||||
increaseRenameSimilarityThreshold: )
|
||||
decreaseRenameSimilarityThreshold: (
|
||||
openDiffTool: <ctrl+t>
|
||||
editConfig: <alt+shift+c>
|
||||
status:
|
||||
checkForUpdate: u
|
||||
recentRepos: <enter>
|
||||
@@ -726,8 +776,6 @@ keybinding:
|
||||
fetchRemote: f
|
||||
addForkRemote: F
|
||||
sortOrder: s
|
||||
worktrees:
|
||||
viewWorktreeOptions: w
|
||||
commits:
|
||||
squashDown: s
|
||||
renameCommit: r
|
||||
@@ -1059,6 +1107,12 @@ keybinding:
|
||||
edit: <disabled> # disable 'edit file'
|
||||
```
|
||||
|
||||
### Overriding the platform for default keybindings
|
||||
|
||||
A few keybindings have different defaults on macOS than on Linux and Windows (e.g. word-wise cursor movement in text inputs uses `alt` on macOS but `ctrl` elsewhere). Lazygit picks these based on the OS it's running on, but you can override that with the `LAZYGIT_KEYBINDING_PLATFORM` environment variable. Set it to `darwin`, `linux`, or `windows`; any other value is ignored and the actual OS is used.
|
||||
|
||||
This is useful when running lazygit in a Linux container that you access over ssh from a Mac, where you'd rather use the macOS keybindings.
|
||||
|
||||
### Example Keybindings For Colemak Users
|
||||
|
||||
```yaml
|
||||
|
||||
+18
-29
@@ -2,8 +2,6 @@
|
||||
|
||||
Lazygit supports custom pagers, [configured](/docs/Config.md) in the config.yml file (which can be opened by pressing `e` in the Status panel).
|
||||
|
||||
Support does not extend to Windows users, because we're making use of a package which doesn't have Windows support. However, see [below](#emulating-custom-pagers-on-windows) for a workaround.
|
||||
|
||||
Multiple pagers are supported; you can cycle through them with the `|` key. This can be useful if you usually prefer a particular pager, but want to use a different one for certain kinds of diffs.
|
||||
|
||||
Pagers are configured with the `pagers` array in the git section; here's an example for a multi-pager setup (use an empty object `{}` for the default builtin diff display that doesn't use a pager):
|
||||
@@ -71,7 +69,7 @@ git:
|
||||
- externalDiffCommand: difft --color=always
|
||||
```
|
||||
|
||||
The `colorArg` and `pager` options are not used in this case.
|
||||
The `colorArg` option is not used in this case.
|
||||
|
||||
You can add whatever extra arguments you prefer for your difftool; for instance
|
||||
|
||||
@@ -81,6 +79,22 @@ git:
|
||||
- externalDiffCommand: difft --color=always --display=inline --syntax-highlight=off
|
||||
```
|
||||
|
||||
This can also be used for normal git diffs with custom parameters, such as `--color-words` or `--word-diff` which some people find useful. To do that, save a script like this to, say, `~/bin/color-words.sh`:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
|
||||
git diff --color-words --no-index --color=always --no-ext-diff "$2" "$5"
|
||||
```
|
||||
|
||||
And then use it in your git config like so:
|
||||
|
||||
```yaml
|
||||
git:
|
||||
pagers:
|
||||
- externalDiffCommand: ~/bin/color-words.sh
|
||||
```
|
||||
|
||||
Instead of setting this command in lazygit's `externalDiffCommand` config, you can also tell lazygit to use the external diff command that is configured in git itself (`diff.external`), by using
|
||||
|
||||
```yaml
|
||||
@@ -91,29 +105,4 @@ git:
|
||||
|
||||
This can be useful if you also want to use it for diffs on the command line, and it also has the advantage that you can configure it per file type in `.gitattributes`; see https://git-scm.com/docs/gitattributes#_defining_an_external_diff_driver.
|
||||
|
||||
## Emulating custom pagers on Windows
|
||||
|
||||
There is a trick to emulate custom pagers on Windows using a Powershell script configured as an external diff command. It's not perfect, but certainly better than nothing. To do this, save the following script as `lazygit-pager.ps1` at a convenient place on your disk:
|
||||
|
||||
```pwsh
|
||||
#!/usr/bin/env pwsh
|
||||
|
||||
$old = $args[1].Replace('\', '/')
|
||||
$new = $args[4].Replace('\', '/')
|
||||
$path = $args[0]
|
||||
git diff --no-index --no-ext-diff $old $new
|
||||
| %{ $_.Replace($old, $path).Replace($new, $path) }
|
||||
| delta --width=$env:LAZYGIT_COLUMNS
|
||||
```
|
||||
|
||||
Use the pager of your choice with the arguments you like in the last line of the script. Personally I wouldn't want to use lazygit anymore without delta's `--hyperlinks --hyperlinks-file-link-format="lazygit-edit://{path}:{line}"` args, see [above](#delta).
|
||||
|
||||
In your lazygit config, use
|
||||
|
||||
```yml
|
||||
git:
|
||||
pagers:
|
||||
- externalDiffCommand: "C:/wherever/lazygit-pager.ps1"
|
||||
```
|
||||
|
||||
The main limitation of this approach compared to a "real" pager is that renames are not displayed correctly; they are shown as if they were modifications of the old file. (This affects only the hunk headers; the diff itself is always correct.)
|
||||
`pager`, `externalDiffCommand`, and `useExternalDiffGitConfig` are alternative ways of producing the diff, so a pager entry may use at most one of them.
|
||||
|
||||
@@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` R `` | Refresh | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
|
||||
| `` + `` | Next screen mode (normal/half/fullscreen) | |
|
||||
| `` _ `` | Prev screen mode | |
|
||||
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
|
||||
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers. |
|
||||
| `` \ `` | Cycle pagers (reverse) | Choose the previous pager in the list of configured pagers. |
|
||||
| `` <esc> `` | Cancel | |
|
||||
| `` ? `` | Open keybindings menu | |
|
||||
| `` <ctrl+s> `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. |
|
||||
@@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` q, <ctrl+c> `` | Quit | |
|
||||
| `` <ctrl+z> `` | Suspend the application | |
|
||||
| `` <ctrl+w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
|
||||
| `` <alt+shift+c> `` | Edit config file | Open file in external editor. |
|
||||
| `` z `` | Undo | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
|
||||
| `` Z `` | Redo | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
|
||||
|
||||
@@ -110,13 +112,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | Open commit in browser | |
|
||||
| `` n `` | Create new branch off of commit | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | View files | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Search the current view by text | |
|
||||
|
||||
## Confirmation panel
|
||||
@@ -176,6 +178,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <space> `` | Checkout | Checkout selected item. |
|
||||
| `` n `` | New branch | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` o `` | Create pull request | |
|
||||
| `` O `` | View create pull request options | |
|
||||
| `` G `` | Open pull request in browser | |
|
||||
@@ -195,7 +198,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | View commits | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Main panel (merging)
|
||||
@@ -203,7 +205,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | Pick hunk | |
|
||||
| `` b `` | Pick all hunks | |
|
||||
| `` b `` | Pick both hunks | |
|
||||
| `` <up>, k `` | Previous hunk | |
|
||||
| `` <down>, j `` | Next hunk | |
|
||||
| `` <left>, h `` | Previous conflict | |
|
||||
@@ -280,6 +282,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | Open commit in browser | |
|
||||
| `` n `` | Create new branch off of commit | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | |
|
||||
@@ -287,7 +290,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | View commits | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Remote branches
|
||||
@@ -297,6 +299,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | Copy branch name to clipboard | |
|
||||
| `` <space> `` | Checkout | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
|
||||
| `` n `` | New branch | |
|
||||
| `` w `` | New worktree | |
|
||||
| `` M `` | Merge | View options for merging the selected item into the current branch (regular merge, squash merge) |
|
||||
| `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. |
|
||||
| `` d `` | Delete | Delete the remote branch from the remote. |
|
||||
@@ -306,7 +309,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | View commits | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Remotes
|
||||
@@ -337,17 +339,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. |
|
||||
| `` d `` | Drop | Remove the stash entry from the stash list. |
|
||||
| `` n `` | New branch | 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. |
|
||||
| `` w `` | New worktree | |
|
||||
| `` r `` | Rename stash | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | View files | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Status
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` o `` | Open config file | Open file in default application. |
|
||||
| `` e `` | Edit config file | Open file in external editor. |
|
||||
| `` u `` | Check for update | |
|
||||
| `` <enter> `` | Switch to a recent repo | |
|
||||
@@ -365,6 +366,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | Open commit in browser | |
|
||||
| `` n `` | Create new branch off of commit | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | Copy (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | |
|
||||
@@ -372,7 +374,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | View files | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Search the current view by text | |
|
||||
|
||||
## Submodules
|
||||
@@ -396,13 +397,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | Copy tag to clipboard | |
|
||||
| `` <space> `` | Checkout | Checkout the selected tag as a detached HEAD. |
|
||||
| `` n `` | New tag | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` w `` | New worktree | |
|
||||
| `` d `` | Delete | View delete options for local/remote tag. |
|
||||
| `` P `` | Push tag | 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. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | View commits | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Worktrees
|
||||
|
||||
@@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` R `` | 更新 | Gitの状態を更新します(`git status`、`git branch`などをバックグラウンドで実行してパネルの内容を更新します)。これは`git fetch`を実行しません。 |
|
||||
| `` + `` | 次の画面モード(通常/半分/全画面) | |
|
||||
| `` _ `` | 前の画面モード | |
|
||||
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
|
||||
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers. |
|
||||
| `` \ `` | Cycle pagers (reverse) | Choose the previous pager in the list of configured pagers. |
|
||||
| `` <esc> `` | キャンセル | |
|
||||
| `` ? `` | キーバインディングメニューを開く | |
|
||||
| `` <ctrl+s> `` | フィルターオプションを表示 | コミットログのフィルタリングオプションを表示し、フィルタに一致するコミットのみを表示します。 |
|
||||
@@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` q, <ctrl+c> `` | 終了 | |
|
||||
| `` <ctrl+z> `` | Suspend the application | |
|
||||
| `` <ctrl+w> `` | 空白表示の切り替え | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
|
||||
| `` <alt+shift+c> `` | 設定ファイルを編集 | 外部エディタでファイルを開きます。 |
|
||||
| `` z `` | 元に戻す | 最後のgitコマンドを元に戻すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 |
|
||||
| `` Z `` | やり直す | 最後のgitコマンドをやり直すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 |
|
||||
|
||||
@@ -90,13 +92,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | ブラウザでコミットを開く | |
|
||||
| `` n `` | コミットから新しいブランチを作成 | |
|
||||
| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | 新しいワークツリー | |
|
||||
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
|
||||
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
|
||||
| `` <ctrl+t> `` | 外部差分ツールを開く(git difftool) | |
|
||||
| `` * `` | 現在のブランチのコミットを選択 | |
|
||||
| `` 0 `` | メインビューにフォーカス | |
|
||||
| `` <enter> `` | ファイルを表示 | |
|
||||
| `` w `` | ワークツリーオプションを表示 | |
|
||||
| `` / `` | 現在のビューをテキストで検索 | |
|
||||
|
||||
## コミットファイル
|
||||
@@ -136,6 +138,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | ブラウザでコミットを開く | |
|
||||
| `` n `` | コミットから新しいブランチを作成 | |
|
||||
| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | 新しいワークツリー | |
|
||||
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
|
||||
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
|
||||
| `` <ctrl+r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | |
|
||||
@@ -143,7 +146,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` * `` | 現在のブランチのコミットを選択 | |
|
||||
| `` 0 `` | メインビューにフォーカス | |
|
||||
| `` <enter> `` | ファイルを表示 | |
|
||||
| `` w `` | ワークツリーオプションを表示 | |
|
||||
| `` / `` | 現在のビューをテキストで検索 | |
|
||||
|
||||
## サブモジュール
|
||||
@@ -168,17 +170,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` g `` | ポップ | スタッシュエントリをワーキングディレクトリに適用し、スタッシュエントリを削除します。 |
|
||||
| `` d `` | 削除 | スタッシュリストからスタッシュエントリを削除します。 |
|
||||
| `` n `` | 新しいブランチ | 選択したスタッシュエントリから新しいブランチを作成します。これは、スタッシュエントリが作成されたコミットをgitがチェックアウトし、そのコミットから新しいブランチを作成した後、スタッシュエントリを追加のコミットとして新しいブランチに適用することで機能します。 |
|
||||
| `` w `` | 新しいワークツリー | |
|
||||
| `` r `` | スタッシュの名前を変更 | |
|
||||
| `` 0 `` | メインビューにフォーカス | |
|
||||
| `` <enter> `` | ファイルを表示 | |
|
||||
| `` w `` | ワークツリーオプションを表示 | |
|
||||
| `` / `` | 現在のビューをテキストでフィルタリング | |
|
||||
|
||||
## ステータス
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` o `` | 設定ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
|
||||
| `` e `` | 設定ファイルを編集 | 外部エディタでファイルを開きます。 |
|
||||
| `` u `` | 更新を確認 | |
|
||||
| `` <enter> `` | 最近のリポジトリをチェックアウト | |
|
||||
@@ -201,13 +202,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | タグをクリップボードにコピー | |
|
||||
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したタグをデタッチドHEADとしてチェックアウトします。 |
|
||||
| `` n `` | 新しいタグを作成 | 現在のコミットから新しいタグを作成します。タグ名とオプションの説明を入力するよう促されます。 |
|
||||
| `` w `` | 新しいワークツリー | |
|
||||
| `` d `` | 削除 | ローカル/リモートタグの削除オプションを表示します。 |
|
||||
| `` P `` | タグをプッシュ | 選択したタグをリモートにプッシュします。リモートを選択するよう促されます。 |
|
||||
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
|
||||
| `` <ctrl+t> `` | 外部差分ツールを開く(git difftool) | |
|
||||
| `` 0 `` | メインビューにフォーカス | |
|
||||
| `` <enter> `` | コミットを表示 | |
|
||||
| `` w `` | ワークツリーオプションを表示 | |
|
||||
| `` / `` | 現在のビューをテキストでフィルタリング | |
|
||||
|
||||
## ファイル
|
||||
@@ -286,7 +287,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | ハンクを選択 | |
|
||||
| `` b `` | すべてのハンクを選択 | |
|
||||
| `` b `` | Pick both hunks | |
|
||||
| `` <up>, k `` | 前のハンク | |
|
||||
| `` <down>, j `` | 次のハンク | |
|
||||
| `` <left>, h `` | 前のコンフリクト | |
|
||||
@@ -325,6 +326,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | ブラウザでコミットを開く | |
|
||||
| `` n `` | コミットから新しいブランチを作成 | |
|
||||
| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | 新しいワークツリー | |
|
||||
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:<br>- ソフトリセット:変更を保持し、ステージされた状態にします<br>- ミックスリセット:変更を保持し、ステージされていない状態にします<br>- ハードリセット:すべての変更を破棄します |
|
||||
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
|
||||
| `` <ctrl+r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | |
|
||||
@@ -332,7 +334,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` * `` | 現在のブランチのコミットを選択 | |
|
||||
| `` 0 `` | メインビューにフォーカス | |
|
||||
| `` <enter> `` | コミットを表示 | |
|
||||
| `` w `` | ワークツリーオプションを表示 | |
|
||||
| `` / `` | 現在のビューをテキストでフィルタリング | |
|
||||
|
||||
## リモート
|
||||
@@ -354,6 +355,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | ブランチ名をクリップボードにコピー | |
|
||||
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したリモートブランチに基づいて新しいローカルブランチをチェックアウトするか、リモートブランチをデタッチドヘッドとしてチェックアウトします。 |
|
||||
| `` n `` | 新しいブランチ | |
|
||||
| `` w `` | 新しいワークツリー | |
|
||||
| `` M `` | マージ | 選択した項目を現在のブランチにマージするためのオプションを表示します(通常のマージ、スカッシュマージ) |
|
||||
| `` r `` | リベース | チェックアウトしたブランチを選択したブランチ上にリベースします。 |
|
||||
| `` d `` | 削除 | リモートからリモートブランチを削除します。 |
|
||||
@@ -363,7 +365,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+t> `` | 外部差分ツールを開く(git difftool) | |
|
||||
| `` 0 `` | メインビューにフォーカス | |
|
||||
| `` <enter> `` | コミットを表示 | |
|
||||
| `` w `` | ワークツリーオプションを表示 | |
|
||||
| `` / `` | 現在のビューをテキストでフィルタリング | |
|
||||
|
||||
## ローカルブランチ
|
||||
@@ -375,6 +376,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択した項目をチェックアウトします。 |
|
||||
| `` n `` | 新しいブランチ | |
|
||||
| `` N `` | コミットを新しいブランチに移動 | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | 新しいワークツリー | |
|
||||
| `` o `` | プルリクエストを作成 | |
|
||||
| `` O `` | プルリクエスト作成オプションを表示 | |
|
||||
| `` G `` | Open pull request in browser | |
|
||||
@@ -394,7 +396,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+t> `` | 外部差分ツールを開く(git difftool) | |
|
||||
| `` 0 `` | メインビューにフォーカス | |
|
||||
| `` <enter> `` | コミットを表示 | |
|
||||
| `` w `` | ワークツリーオプションを表示 | |
|
||||
| `` / `` | 現在のビューをテキストでフィルタリング | |
|
||||
|
||||
## ワークツリー
|
||||
|
||||
@@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` R `` | 새로고침 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
|
||||
| `` + `` | 다음 스크린 모드 (normal/half/fullscreen) | |
|
||||
| `` _ `` | 이전 스크린 모드 | |
|
||||
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
|
||||
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers. |
|
||||
| `` \ `` | Cycle pagers (reverse) | Choose the previous pager in the list of configured pagers. |
|
||||
| `` <esc> `` | 취소 | |
|
||||
| `` ? `` | 매뉴 열기 | |
|
||||
| `` <ctrl+s> `` | View filter-by-path options | View options for filtering the commit log, so that only commits matching the filter are shown. |
|
||||
@@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` q, <ctrl+c> `` | 종료 | |
|
||||
| `` <ctrl+z> `` | Suspend the application | |
|
||||
| `` <ctrl+w> `` | 공백문자를 Diff 뷰에서 표시 여부 전환 | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
|
||||
| `` <alt+shift+c> `` | 설정 파일 수정 | Open file in external editor. |
|
||||
| `` 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. |
|
||||
| `` 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. |
|
||||
|
||||
@@ -67,6 +69,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | 브라우저에서 커밋 열기 | |
|
||||
| `` n `` | 커밋에서 새 브랜치를 만듭니다. | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <ctrl+r> `` | Reset cherry-picked (copied) commits selection | |
|
||||
@@ -74,7 +77,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | 커밋 보기 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Secondary
|
||||
@@ -93,10 +95,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. |
|
||||
| `` d `` | Drop | Remove the stash entry from the stash list. |
|
||||
| `` n `` | 새 브랜치 생성 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. |
|
||||
| `` w `` | New worktree | |
|
||||
| `` r `` | Rename stash | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | View selected item's files | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Sub-commits
|
||||
@@ -109,6 +111,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | 브라우저에서 커밋 열기 | |
|
||||
| `` n `` | 커밋에서 새 브랜치를 만듭니다. | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <ctrl+r> `` | Reset cherry-picked (copied) commits selection | |
|
||||
@@ -116,7 +119,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | View selected item's files | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | 검색 시작 | |
|
||||
|
||||
## Worktrees
|
||||
@@ -142,7 +144,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | Pick hunk | |
|
||||
| `` b `` | Pick all hunks | |
|
||||
| `` b `` | Pick both hunks | |
|
||||
| `` <up>, k `` | 이전 hunk를 선택 | |
|
||||
| `` <down>, j `` | 다음 hunk를 선택 | |
|
||||
| `` <left>, h `` | 이전 충돌을 선택 | |
|
||||
@@ -210,6 +212,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <space> `` | 체크아웃 | Checkout selected item. |
|
||||
| `` n `` | 새 브랜치 생성 | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` o `` | 풀 리퀘스트 생성 | |
|
||||
| `` O `` | 풀 리퀘스트 생성 옵션 | |
|
||||
| `` G `` | Open pull request in browser | |
|
||||
@@ -229,14 +232,12 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | 커밋 보기 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## 상태
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` o `` | 설정 파일 열기 | Open file in default application. |
|
||||
| `` e `` | 설정 파일 수정 | Open file in external editor. |
|
||||
| `` u `` | 업데이트 확인 | |
|
||||
| `` <enter> `` | 최근에 사용한 저장소로 전환 | |
|
||||
@@ -277,6 +278,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | 브랜치명을 클립보드에 복사 | |
|
||||
| `` <space> `` | 체크아웃 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
|
||||
| `` n `` | 새 브랜치 생성 | |
|
||||
| `` w `` | New worktree | |
|
||||
| `` 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 the remote branch from the remote. |
|
||||
@@ -286,7 +288,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | 커밋 보기 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## 커밋
|
||||
@@ -322,13 +323,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | 브라우저에서 커밋 열기 | |
|
||||
| `` n `` | 커밋에서 새 브랜치를 만듭니다. | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | 커밋을 복사 (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | View selected item's files | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | 검색 시작 | |
|
||||
|
||||
## 커밋 파일
|
||||
@@ -365,13 +366,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | Copy tag to clipboard | |
|
||||
| `` <space> `` | 체크아웃 | Checkout the selected tag as a detached HEAD. |
|
||||
| `` n `` | 태그를 생성 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` w `` | New worktree | |
|
||||
| `` d `` | 삭제 | View delete options for local/remote tag. |
|
||||
| `` P `` | 태그를 push | Push the selected tag to a remote. You'll be prompted to select a remote. |
|
||||
| `` g `` | 초기화 | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | 커밋 보기 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## 파일
|
||||
|
||||
@@ -10,26 +10,28 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <pgup>, K, <ctrl+u> (fn+up/shift+k) `` | Scroll naar beneden vanaf hoofdpaneel | |
|
||||
| `` <pgdown>, J, <ctrl+d> (fn+down/shift+j) `` | Scroll naar beneden vanaf hoofdpaneel | |
|
||||
| `` @ `` | View command log options | View options for the command log e.g. show/hide the command log and focus the command log. |
|
||||
| `` P `` | Push | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
|
||||
| `` p `` | Pull | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
|
||||
| `` P `` | Push | Push de huidige branch naar de bijbehorende upstream-branch. Als er geen upstream is geconfigureerd wordt er gevraagd om een upstream-branch te configureren. |
|
||||
| `` p `` | Pull | Pull wijzigingen van de remote voor de huidige branch. Als er geen upstream is geconfigureerd wordt er gevraagd om een upstream-branch te configureren. |
|
||||
| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.<br><br>The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. |
|
||||
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.<br><br>The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. |
|
||||
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view.<br><br>The default can be changed in the config file with the key 'git.diffContextSize'. |
|
||||
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view.<br><br>The default can be changed in the config file with the key 'git.diffContextSize'. |
|
||||
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
|
||||
| `` <ctrl+p> `` | Bekijk aangepaste patch opties | |
|
||||
| `` m `` | Bekijk merge/rebase opties | View options to abort/continue/skip the current merge/rebase. |
|
||||
| `` m `` | Bekijk merge/rebase opties | Toon abort/continue/skip opties voor huidige 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`. |
|
||||
| `` + `` | Volgende scherm modus (normaal/half/groot) | |
|
||||
| `` _ `` | Vorige scherm modus | |
|
||||
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
|
||||
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers. |
|
||||
| `` \ `` | Cycle pagers (reverse) | Choose the previous pager in the list of configured pagers. |
|
||||
| `` <esc> `` | Annuleren | |
|
||||
| `` ? `` | Open menu | |
|
||||
| `` <ctrl+s> `` | Bekijk scoping opties | View options for filtering the commit log, so that only commits matching the filter are shown. |
|
||||
| `` W, <ctrl+e> `` | Open diff menu | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
|
||||
| `` q, <ctrl+c> `` | Quit | |
|
||||
| `` <ctrl+z> `` | Suspend the application | |
|
||||
| `` q, <ctrl+c> `` | Afsluiten | |
|
||||
| `` <ctrl+z> `` | Pauzeer de applicatie | |
|
||||
| `` <ctrl+w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
|
||||
| `` <alt+shift+c> `` | Verander config bestand | Open bestand in externe editor. |
|
||||
| `` z `` | Ongedaan maken (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
|
||||
| `` Z `` | Redo (via reflog) (experimenteel) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
|
||||
|
||||
@@ -45,8 +47,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <shift+down> `` | Range select down | |
|
||||
| `` <shift+up> `` | Range select up | |
|
||||
| `` / `` | Start met zoeken | |
|
||||
| `` H `` | Scroll left | |
|
||||
| `` L `` | Scroll right | |
|
||||
| `` H `` | Scroll naar links | |
|
||||
| `` L `` | Scroll naar rechts | |
|
||||
| `` ] `` | Volgende tabblad | |
|
||||
| `` [ `` | Vorige tabblad | |
|
||||
|
||||
@@ -56,15 +58,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|-----|--------|-------------|
|
||||
| `` <ctrl+o> `` | Kopieer de bestandsnaam naar het klembord | |
|
||||
| `` <space> `` | Toggle staged | Toggle staged for selected file. |
|
||||
| `` <ctrl+b> `` | Filter files by status | |
|
||||
| `` y `` | Copy to clipboard | |
|
||||
| `` c `` | Commit veranderingen | Commit staged changes. |
|
||||
| `` <ctrl+b> `` | Filter bestanden op status | |
|
||||
| `` y `` | Kopieer naar klembord | |
|
||||
| `` c `` | Commit veranderingen | Commit gestagede wijzigingen. |
|
||||
| `` w `` | Commit veranderingen zonder pre-commit hook | |
|
||||
| `` A `` | Wijzig laatste commit | |
|
||||
| `` C `` | Commit veranderingen met de git editor | |
|
||||
| `` <ctrl+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 bestand | Open file in default application. |
|
||||
| `` <ctrl+f> `` | Find base commit for fixup | Vind de commit waar je huidige wijzigingen bovenop zijn gebouwd met als doel die commit te amenden/fixen. Hierdoor hoef je dit niet met de hand te doen. Zie: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
|
||||
| `` e `` | Edit | Open bestand in externe editor. |
|
||||
| `` o `` | Open bestand | Open bestand in standaardapplicatie. |
|
||||
| `` i `` | Ignore or exclude file | |
|
||||
| `` r `` | Refresh bestanden | |
|
||||
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
|
||||
@@ -73,13 +75,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <enter> `` | Stage individuele hunks/lijnen | 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 `` | Bekijk 'veranderingen ongedaan maken' opties | View options for discarding changes to the selected file. |
|
||||
| `` g `` | Bekijk upstream reset opties | |
|
||||
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). |
|
||||
| `` D `` | Resetten | View reset options for working tree (e.g. nuking the working tree). |
|
||||
| `` ` `` | Toggle bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
|
||||
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
|
||||
| `` M `` | Bekijk merge conflict opties | Bekijk opties voor het oplossen van mergeconflicten. |
|
||||
| `` f `` | Fetch | Fetch changes from remote. |
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` = `` | Vouw alle bestanden uit | Vouw alle mappen in de bestandsstructuur uit |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
@@ -89,7 +91,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | Bevestig | |
|
||||
| `` <esc> `` | Sluiten | |
|
||||
| `` <ctrl+o> `` | Copy to clipboard | |
|
||||
| `` <ctrl+o> `` | Kopieer naar klembord | |
|
||||
|
||||
## Branches
|
||||
|
||||
@@ -97,18 +99,19 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|-----|--------|-------------|
|
||||
| `` <ctrl+o> `` | Kopieer branch name naar klembord | |
|
||||
| `` i `` | Laat git-flow opties zien | |
|
||||
| `` <space> `` | Uitchecken | Checkout selected item. |
|
||||
| `` <space> `` | Uitchecken | Geselecteerd item uitchecken. |
|
||||
| `` n `` | Nieuwe branch | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` N `` | Verplaats commits naar nieuwe branch | Maak een nieuwe branch en verplaats niet-gepushte commits van de huidige branch hier naar toe. Gebruik dit in het geval dat je deze commits eigenlijk op een nieuwe branch had willen maken.<br><br>Let op dat de selectie genegeerd wordt. De nieuwe branch komt ofwel bovenop de main branch, of bovenop de huidige branch (je kan kiezen). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` o `` | Maak een pull-request | |
|
||||
| `` O `` | Bekijk opties voor pull-aanvraag | |
|
||||
| `` G `` | Open pull request in browser | |
|
||||
| `` <ctrl+y> `` | Kopieer de URL van het pull-verzoek naar het klembord | |
|
||||
| `` c `` | Uitchecken bij naam | Checkout by name. In the input box you can enter '-' to switch to the previous branch. |
|
||||
| `` - `` | Checkout previous branch | |
|
||||
| `` - `` | Vorige branch uitchecken | |
|
||||
| `` F `` | Forceer checkout | 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 branch | Rebase the checked-out branch onto the selected branch. |
|
||||
| `` d `` | Verwijderen | View delete options for local/remote branch. |
|
||||
| `` r `` | Rebase branch | Rebase de uitgecheckte branch bovenop de geselecteerde branch. |
|
||||
| `` M `` | Merge in met huidige checked out branch | View options for merging the selected item into the current branch (regular merge, squash merge) |
|
||||
| `` f `` | Fast-forward deze branch vanaf zijn upstream | Fast-forward selected branch from its upstream. |
|
||||
| `` T `` | Creëer tag | |
|
||||
@@ -116,10 +119,9 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` g `` | Bekijk reset opties | |
|
||||
| `` R `` | Hernoem branch | |
|
||||
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Bekijk commits | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Commit bericht
|
||||
@@ -134,18 +136,18 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <ctrl+o> `` | Kopieer de bestandsnaam naar het klembord | |
|
||||
| `` y `` | Copy to clipboard | |
|
||||
| `` y `` | Kopieer naar klembord | |
|
||||
| `` c `` | Uitchecken | Bestand uitchecken |
|
||||
| `` d `` | Bekijk 'veranderingen ongedaan maken' opties | Uitsluit deze commit zijn veranderingen aan dit bestand |
|
||||
| `` o `` | Open bestand | Open file in default application. |
|
||||
| `` e `` | Edit | Open file in external editor. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` o `` | Open bestand | Open bestand in standaardapplicatie. |
|
||||
| `` e `` | Edit | Open bestand in externe editor. |
|
||||
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
|
||||
| `` <space> `` | Toggle bestand inbegrepen in patch | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
|
||||
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
|
||||
| `` <enter> `` | Enter bestand om geselecteerde regels toe te voegen aan de patch | 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 bestandsboom weergave | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` = `` | Vouw alle bestanden uit | Vouw alle mappen in de bestandsstructuur uit |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
@@ -181,14 +183,14 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
|
||||
| `` o `` | Open commit in browser | |
|
||||
| `` n `` | Creëer nieuwe branch van commit | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` N `` | Verplaats commits naar nieuwe branch | Maak een nieuwe branch en verplaats niet-gepushte commits van de huidige branch hier naar toe. Gebruik dit in het geval dat je deze commits eigenlijk op een nieuwe branch had willen maken.<br><br>Let op dat de selectie genegeerd wordt. De nieuwe branch komt ofwel bovenop de main branch, of bovenop de huidige branch (je kan kiezen). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Bekijk gecommite bestanden | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Start met zoeken | |
|
||||
|
||||
## Input prompt
|
||||
@@ -211,15 +213,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | Kies stuk | |
|
||||
| `` b `` | Kies beide stukken | |
|
||||
| `` b `` | Pick both hunks | |
|
||||
| `` <up>, k `` | Selecteer bovenste hunk | |
|
||||
| `` <down>, j `` | Selecteer onderste hunk | |
|
||||
| `` <left>, h `` | Selecteer voorgaand conflict | |
|
||||
| `` <right>, l `` | Selecteer volgende conflict | |
|
||||
| `` z `` | Ongedaan maken | Undo last merge conflict resolution. |
|
||||
| `` e `` | Verander bestand | Open file in external editor. |
|
||||
| `` o `` | Open bestand | Open file in default application. |
|
||||
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
|
||||
| `` e `` | Verander bestand | Open bestand in externe editor. |
|
||||
| `` o `` | Open bestand | Open bestand in standaardapplicatie. |
|
||||
| `` M `` | Bekijk merge conflict opties | Bekijk opties voor het oplossen van mergeconflicten. |
|
||||
| `` <esc> `` | Ga terug naar het bestanden paneel | |
|
||||
|
||||
## Normaal
|
||||
@@ -241,8 +243,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` v `` | Toggle drag selecteer | |
|
||||
| `` a `` | Toggle hunk selection | Toggle line-by-line vs. hunk selection mode. |
|
||||
| `` <ctrl+o> `` | Copy selected text to clipboard | |
|
||||
| `` o `` | Open bestand | Open file in default application. |
|
||||
| `` e `` | Verander bestand | Open file in external editor. |
|
||||
| `` o `` | Open bestand | Open bestand in standaardapplicatie. |
|
||||
| `` e `` | Verander bestand | Open bestand in externe editor. |
|
||||
| `` <space> `` | Voeg toe/verwijder lijn(en) in patch | |
|
||||
| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. |
|
||||
| `` <esc> `` | Sluit lijn-bij-lijn modus | |
|
||||
@@ -257,15 +259,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
|
||||
| `` o `` | Open commit in browser | |
|
||||
| `` n `` | Creëer nieuwe branch van commit | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` N `` | Verplaats commits naar nieuwe branch | Maak een nieuwe branch en verplaats niet-gepushte commits van de huidige branch hier naar toe. Gebruik dit in het geval dat je deze commits eigenlijk op een nieuwe branch had willen maken.<br><br>Let op dat de selectie genegeerd wordt. De nieuwe branch komt ofwel bovenop de main branch, of bovenop de huidige branch (je kan kiezen). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <ctrl+r> `` | Reset cherry-picked (gekopieerde) commits selectie | |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Bekijk commits | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Remote branches
|
||||
@@ -273,27 +275,27 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <ctrl+o> `` | Kopieer branch name naar klembord | |
|
||||
| `` <space> `` | Uitchecken | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
|
||||
| `` <space> `` | Uitchecken | Geselecteerde remote branch uitchecken als nieuwe locale branch of als detached head. |
|
||||
| `` n `` | Nieuwe branch | |
|
||||
| `` w `` | New worktree | |
|
||||
| `` M `` | Merge in met huidige checked out branch | View options for merging the selected item into the current branch (regular merge, squash merge) |
|
||||
| `` r `` | Rebase branch | Rebase the checked-out branch onto the selected branch. |
|
||||
| `` d `` | Delete | Delete the remote branch from the remote. |
|
||||
| `` u `` | Set as upstream | Stel in als upstream van uitgecheckte branch |
|
||||
| `` r `` | Rebase branch | Rebase de uitgecheckte branch bovenop de geselecteerde branch. |
|
||||
| `` d `` | Verwijderen | Delete the remote branch from the remote. |
|
||||
| `` u `` | Instellen als upstream | Stel in als upstream van uitgecheckte branch |
|
||||
| `` s `` | Sort order | |
|
||||
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Bekijk commits | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Remotes
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | View branches | |
|
||||
| `` <enter> `` | Bekijk branches | |
|
||||
| `` n `` | Voeg een nieuwe remote toe | |
|
||||
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. |
|
||||
| `` d `` | Verwijderen | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. |
|
||||
| `` e `` | Edit | Wijzig remote |
|
||||
| `` f `` | Fetch | Fetch remote |
|
||||
| `` F `` | Add fork remote | Quickly add a fork remote by replacing the owner in the origin URL and optionally check out a branch from new remote. |
|
||||
@@ -318,15 +320,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | Copy selected text to clipboard | |
|
||||
| `` <space> `` | Toggle staged | Toggle lijnen staged / unstaged |
|
||||
| `` d `` | Verwijdert change (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. |
|
||||
| `` o `` | Open bestand | Open file in default application. |
|
||||
| `` e `` | Verander bestand | Open file in external editor. |
|
||||
| `` o `` | Open bestand | Open bestand in standaardapplicatie. |
|
||||
| `` e `` | Verander bestand | Open bestand in externe editor. |
|
||||
| `` <esc> `` | Ga terug naar het bestanden paneel | |
|
||||
| `` <tab> `` | Ga naar een ander paneel | Switch to other view (staged/unstaged changes). |
|
||||
| `` E `` | Edit hunk | Edit selected hunk in external editor. |
|
||||
| `` c `` | Commit veranderingen | Commit staged changes. |
|
||||
| `` c `` | Commit veranderingen | Commit gestagede wijzigingen. |
|
||||
| `` w `` | Commit veranderingen zonder pre-commit hook | |
|
||||
| `` C `` | Commit veranderingen met de git editor | |
|
||||
| `` <ctrl+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> |
|
||||
| `` <ctrl+f> `` | Find base commit for fixup | Vind de commit waar je huidige wijzigingen bovenop zijn gebouwd met als doel die commit te amenden/fixen. Hierdoor hoef je dit niet met de hand te doen. Zie: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
|
||||
| `` / `` | Start met zoeken | |
|
||||
|
||||
## Stash
|
||||
@@ -337,18 +339,17 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. |
|
||||
| `` d `` | Laten vallen | Remove the stash entry from the stash list. |
|
||||
| `` n `` | Nieuwe branch | 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 | |
|
||||
| `` w `` | New worktree | |
|
||||
| `` r `` | Hernoem stash | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Bekijk gecommite bestanden | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Status
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` o `` | Open config bestand | Open file in default application. |
|
||||
| `` e `` | Verander config bestand | Open file in external editor. |
|
||||
| `` e `` | Verander config bestand | Open bestand in externe editor. |
|
||||
| `` u `` | Check voor updates | |
|
||||
| `` <enter> `` | Wissel naar een recente repo | |
|
||||
| `` a `` | Show/cycle all branch logs | |
|
||||
@@ -364,15 +365,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
|
||||
| `` o `` | Open commit in browser | |
|
||||
| `` n `` | Creëer nieuwe branch van commit | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` N `` | Verplaats commits naar nieuwe branch | Maak een nieuwe branch en verplaats niet-gepushte commits van de huidige branch hier naar toe. Gebruik dit in het geval dat je deze commits eigenlijk op een nieuwe branch had willen maken.<br><br>Let op dat de selectie genegeerd wordt. De nieuwe branch komt ofwel bovenop de main branch, of bovenop de huidige branch (je kan kiezen). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` g `` | Bekijk reset opties | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | Kopieer commit (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <ctrl+r> `` | Reset cherry-picked (gekopieerde) commits selectie | |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Bekijk gecommite bestanden | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Start met zoeken | |
|
||||
|
||||
## Submodules
|
||||
@@ -381,7 +382,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|-----|--------|-------------|
|
||||
| `` <ctrl+o> `` | Kopieer submodule naam naar klembord | |
|
||||
| `` <enter> `` | Enter | Enter submodule |
|
||||
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. |
|
||||
| `` d `` | Verwijderen | Remove the selected submodule and its corresponding directory. |
|
||||
| `` u `` | Update | Update selected submodule. |
|
||||
| `` n `` | Voeg nieuwe submodule toe | |
|
||||
| `` e `` | Update submodule URL | |
|
||||
@@ -394,15 +395,15 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <ctrl+o> `` | Copy tag to clipboard | |
|
||||
| `` <space> `` | Uitchecken | Checkout the selected tag as a detached HEAD. |
|
||||
| `` <space> `` | Uitchecken | Geselecteerde tag uitchecken als detached HEAD. |
|
||||
| `` n `` | Creëer tag | 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. |
|
||||
| `` w `` | New worktree | |
|
||||
| `` d `` | Verwijderen | View delete options for local/remote tag. |
|
||||
| `` P `` | Push tag | 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. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` g `` | Resetten | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` <ctrl+t> `` | Open externe diff applicatie (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Bekijk commits | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Worktrees
|
||||
@@ -411,6 +412,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|-----|--------|-------------|
|
||||
| `` 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. |
|
||||
| `` o `` | Openen in editor | |
|
||||
| `` d `` | Verwijderen | 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 | |
|
||||
|
||||
@@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` R `` | Odśwież | Odśwież stan git (tj. uruchom `git status`, `git branch`, itp. w tle, aby zaktualizować zawartość paneli). To nie uruchamia `git fetch`. |
|
||||
| `` + `` | Następny tryb ekranu (normalny/półpełny/pełnoekranowy) | |
|
||||
| `` _ `` | Poprzedni tryb ekranu | |
|
||||
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
|
||||
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers. |
|
||||
| `` \ `` | Cycle pagers (reverse) | Choose the previous pager in the list of configured pagers. |
|
||||
| `` <esc> `` | Anuluj | |
|
||||
| `` ? `` | Otwórz menu przypisań klawiszy | |
|
||||
| `` <ctrl+s> `` | Pokaż opcje filtrowania | Pokaż opcje filtrowania dziennika commitów, tak aby pokazywane były tylko commity pasujące do filtra. |
|
||||
@@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` q, <ctrl+c> `` | Wyjdź | |
|
||||
| `` <ctrl+z> `` | Suspend the application | |
|
||||
| `` <ctrl+w> `` | Przełącz białe znaki | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
|
||||
| `` <alt+shift+c> `` | Edytuj plik konfiguracyjny | Otwórz plik w zewnętrznym edytorze. |
|
||||
| `` z `` | Cofnij | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby cofnąć ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. |
|
||||
| `` Z `` | Ponów | Dziennik reflog zostanie użyty do określenia, jakie polecenie git należy uruchomić, aby ponowić ostatnie polecenie git. Nie obejmuje to zmian w drzewie roboczym; brane są pod uwagę tylko commity. |
|
||||
|
||||
@@ -83,13 +85,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | Otwórz commit w przeglądarce | |
|
||||
| `` n `` | Utwórz nową gałąź z commita | |
|
||||
| `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | Nowe drzewo pracy | |
|
||||
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. |
|
||||
| `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. |
|
||||
| `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Wyświetl pliki | |
|
||||
| `` w `` | Zobacz opcje drzewa pracy | |
|
||||
| `` / `` | Szukaj w bieżącym widoku po tekście | |
|
||||
|
||||
## Dodatkowy
|
||||
@@ -120,6 +122,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | Otwórz commit w przeglądarce | |
|
||||
| `` n `` | Utwórz nową gałąź z commita | |
|
||||
| `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | Nowe drzewo pracy | |
|
||||
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. |
|
||||
| `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. |
|
||||
| `` <ctrl+r> `` | Resetuj wybrane (cherry-picked) commity | |
|
||||
@@ -127,7 +130,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Pokaż commity | |
|
||||
| `` w `` | Zobacz opcje drzewa pracy | |
|
||||
| `` / `` | Filtruj bieżący widok po tekście | |
|
||||
|
||||
## Główny panel (budowanie łatki)
|
||||
@@ -162,6 +164,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <space> `` | Przełącz | Przełącz wybrany element. |
|
||||
| `` n `` | Nowa gałąź | |
|
||||
| `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | Nowe drzewo pracy | |
|
||||
| `` o `` | Utwórz żądanie ściągnięcia | |
|
||||
| `` O `` | Zobacz opcje tworzenia pull requesta | |
|
||||
| `` G `` | Otwórz żądanie ściągnięcia w przeglądarce | |
|
||||
@@ -181,7 +184,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Pokaż commity | |
|
||||
| `` w `` | Zobacz opcje drzewa pracy | |
|
||||
| `` / `` | Filtruj bieżący widok po tekście | |
|
||||
|
||||
## Menu
|
||||
@@ -207,7 +209,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | Wybierz fragment | |
|
||||
| `` b `` | Wybierz wszystkie fragmenty | |
|
||||
| `` b `` | Pick both hunks | |
|
||||
| `` <up>, k `` | Poprzedni fragment | |
|
||||
| `` <down>, j `` | Następny fragment | |
|
||||
| `` <left>, h `` | Poprzedni konflikt | |
|
||||
@@ -316,17 +318,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` g `` | Wyciągnij | Zastosuj wpis schowka do katalogu roboczego i usuń wpis schowka. |
|
||||
| `` d `` | Usuń | Usuń wpis schowka z listy schowka. |
|
||||
| `` n `` | Nowa gałąź | Utwórz nową gałąź z wybranego wpisu schowka. Działa poprzez przełączenie git na commit, na którym wpis schowka został utworzony, tworzenie nowej gałęzi z tego commita, a następnie zastosowanie wpisu schowka do nowej gałęzi jako dodatkowego commita. |
|
||||
| `` w `` | Nowe drzewo pracy | |
|
||||
| `` r `` | Zmień nazwę schowka | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Wyświetl pliki | |
|
||||
| `` w `` | Zobacz opcje drzewa pracy | |
|
||||
| `` / `` | Filtruj bieżący widok po tekście | |
|
||||
|
||||
## Status
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` o `` | Otwórz plik konfiguracyjny | Otwórz plik w domyślnej aplikacji. |
|
||||
| `` e `` | Edytuj plik konfiguracyjny | Otwórz plik w zewnętrznym edytorze. |
|
||||
| `` u `` | Sprawdź aktualizacje | |
|
||||
| `` <enter> `` | Przełącz na ostatnie repozytorium | |
|
||||
@@ -344,6 +345,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | Otwórz commit w przeglądarce | |
|
||||
| `` n `` | Utwórz nową gałąź z commita | |
|
||||
| `` N `` | Przenieś commity do nowej gałęzi | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | Nowe drzewo pracy | |
|
||||
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. |
|
||||
| `` C `` | Kopiuj (cherry-pick) | Oznacz commit jako skopiowany. Następnie, w widoku lokalnych commitów, możesz nacisnąć `V`, aby wkleić (cherry-pick) skopiowane commity do sprawdzonej gałęzi. W dowolnym momencie możesz nacisnąć `<esc>`, aby anulować zaznaczenie. |
|
||||
| `` <ctrl+r> `` | Resetuj wybrane (cherry-picked) commity | |
|
||||
@@ -351,7 +353,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Wyświetl pliki | |
|
||||
| `` w `` | Zobacz opcje drzewa pracy | |
|
||||
| `` / `` | Szukaj w bieżącym widoku po tekście | |
|
||||
|
||||
## Submoduły
|
||||
@@ -375,13 +376,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | Skopiuj tag do schowka | |
|
||||
| `` <space> `` | Przełącz | Przełącz wybrany tag jako odłączoną głowę (detached HEAD). |
|
||||
| `` n `` | Nowy tag | Utwórz nowy tag z bieżącego commita. Zostaniesz poproszony o wprowadzenie nazwy tagu i opcjonalnego opisu. |
|
||||
| `` w `` | Nowe drzewo pracy | |
|
||||
| `` d `` | Usuń | Wyświetl opcje usuwania lokalnego/odległego tagu. |
|
||||
| `` P `` | Wyślij tag | Wyślij wybrany tag do zdalnego. Zostaniesz poproszony o wybranie zdalnego. |
|
||||
| `` g `` | Reset | Wyświetl opcje resetu (miękki/mieszany/twardy) do wybranego elementu. |
|
||||
| `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Pokaż commity | |
|
||||
| `` w `` | Zobacz opcje drzewa pracy | |
|
||||
| `` / `` | Filtruj bieżący widok po tekście | |
|
||||
|
||||
## Zdalne
|
||||
@@ -403,6 +404,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | Kopiuj nazwę gałęzi do schowka | |
|
||||
| `` <space> `` | Przełącz | Przełącz na nową lokalną gałąź na podstawie wybranej gałęzi zdalnej. Nowa gałąź będzie śledzić gałąź zdalną. |
|
||||
| `` n `` | Nowa gałąź | |
|
||||
| `` w `` | Nowe drzewo pracy | |
|
||||
| `` M `` | Scal | Scal wybraną gałąź z aktualnie sprawdzoną gałęzią. |
|
||||
| `` r `` | Przebazuj | Przebazuj przełączoną gałąź na wybraną gałąź. |
|
||||
| `` d `` | Usuń | Usuń gałąź zdalną ze zdalnego. |
|
||||
@@ -412,5 +414,4 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Pokaż commity | |
|
||||
| `` w `` | Zobacz opcje drzewa pracy | |
|
||||
| `` / `` | Filtruj bieżący widok po tekście | |
|
||||
|
||||
@@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` R `` | Atualizar | Atualize o estado do git (ou seja, execute `git status`, `git branch`, etc em segundo plano para atualizar o conteúdo de painéis). Isso não executa `git fetch`. |
|
||||
| `` + `` | Modo de tela seguinte (normal/metade/tela cheia) | |
|
||||
| `` _ `` | Modo de tela anterior | |
|
||||
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
|
||||
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers. |
|
||||
| `` \ `` | Cycle pagers (reverse) | Choose the previous pager in the list of configured pagers. |
|
||||
| `` <esc> `` | Cancelar | |
|
||||
| `` ? `` | Abrir o menu de atalhos do teclado | |
|
||||
| `` <ctrl+s> `` | Ver opções de filtro | View options for filtering the commit log, so that only commits matching the filter are shown. |
|
||||
@@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` q, <ctrl+c> `` | Sair | |
|
||||
| `` <ctrl+z> `` | Suspender a aplicação | |
|
||||
| `` <ctrl+w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
|
||||
| `` <alt+shift+c> `` | Editar arquivo de configuração | Abrir arquivo no editor externo. |
|
||||
| `` z `` | Desfazer | O reflog será usado para determinar qual comando git para executar para desfazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. |
|
||||
| `` Z `` | Refazer | O reflog será usado para determinar qual comando git para executar para refazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. |
|
||||
|
||||
@@ -92,6 +94,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <space> `` | Verificar | Checar item selecionado |
|
||||
| `` n `` | Nova branch | |
|
||||
| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | Nova árvore de trabalho | |
|
||||
| `` o `` | Criar solicitação de pull | |
|
||||
| `` O `` | View create pull request options | |
|
||||
| `` G `` | Open pull request in browser | |
|
||||
@@ -111,7 +114,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | |
|
||||
| `` 0 `` | Focar visualização principal | |
|
||||
| `` <enter> `` | Ver commits | |
|
||||
| `` w `` | Ver opções da árvore de trabalho | |
|
||||
| `` / `` | Filtrar a visualização atual por texto | |
|
||||
|
||||
## Branches remotos
|
||||
@@ -121,6 +123,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | Copiar nome da branch para área de transferência | |
|
||||
| `` <space> `` | Verificar | Checar a nova branch baseada na brach remota selecionada, ou a branch remota como HEAD, desanexado |
|
||||
| `` n `` | Nova branch | |
|
||||
| `` w `` | Nova árvore de trabalho | |
|
||||
| `` M `` | Mesclar | Ver opções para mesclar o item selecionado no branch atual (mesclar regularmente, mesclar squash) |
|
||||
| `` r `` | Refazer | Refazer a branch checada na branch selecionada |
|
||||
| `` d `` | Apagar | Excluir o branch remoto do controle remoto. |
|
||||
@@ -130,7 +133,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | |
|
||||
| `` 0 `` | Focar visualização principal | |
|
||||
| `` <enter> `` | Ver commits | |
|
||||
| `` w `` | Ver opções da árvore de trabalho | |
|
||||
| `` / `` | Filtrar a visualização atual por texto | |
|
||||
|
||||
## Commit arquivos
|
||||
@@ -186,13 +188,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | Abrir commit no navegador | |
|
||||
| `` n `` | Create new branch off of commit | |
|
||||
| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | Nova árvore de trabalho | |
|
||||
| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. |
|
||||
| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. |
|
||||
| `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focar visualização principal | |
|
||||
| `` <enter> `` | Ver arquivos | |
|
||||
| `` w `` | Ver opções da árvore de trabalho | |
|
||||
| `` / `` | Pesquisar na visualização atual por texto | |
|
||||
|
||||
## Etiquetas
|
||||
@@ -202,13 +204,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | Copiar etiqueta para área de transferência | |
|
||||
| `` <space> `` | Verificar | Checar a tag selecionada como um HEAD, desanexado |
|
||||
| `` n `` | Nova etiqueta | Crie uma nova etiqueta a partir do commit atual. Você será solicitado a digitar um nome e uma descrição opcional. |
|
||||
| `` w `` | Nova árvore de trabalho | |
|
||||
| `` d `` | Apagar | Ver opções de exclusão para tag local/remoto. |
|
||||
| `` P `` | Empurrar etiqueta | Push the selected tag to a remote. You'll be prompted to select a remote. |
|
||||
| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. |
|
||||
| `` <ctrl+t> `` | Abrir ferramenta de diff externa (git difftool) | |
|
||||
| `` 0 `` | Focar visualização principal | |
|
||||
| `` <enter> `` | Ver commits | |
|
||||
| `` w `` | Ver opções da árvore de trabalho | |
|
||||
| `` / `` | Filtrar a visualização atual por texto | |
|
||||
|
||||
## Input prompt
|
||||
@@ -271,7 +273,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | Escolha o local | |
|
||||
| `` b `` | Pegar todos os pedaços | |
|
||||
| `` b `` | Pick both hunks | |
|
||||
| `` <up>, k `` | Trecho anterior | |
|
||||
| `` <down>, j `` | Próximo trecho | |
|
||||
| `` <left>, h `` | Conflito anterior | |
|
||||
@@ -308,6 +310,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | Abrir commit no navegador | |
|
||||
| `` n `` | Create new branch off of commit | |
|
||||
| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | Nova árvore de trabalho | |
|
||||
| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. |
|
||||
| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. |
|
||||
| `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | |
|
||||
@@ -315,7 +318,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focar visualização principal | |
|
||||
| `` <enter> `` | Ver commits | |
|
||||
| `` w `` | Ver opções da árvore de trabalho | |
|
||||
| `` / `` | Filtrar a visualização atual por texto | |
|
||||
|
||||
## Remotes
|
||||
@@ -346,17 +348,16 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` g `` | Pop | Aplique a entrada de stash no seu diretório de trabalho e remova a entrada de stash. |
|
||||
| `` d `` | Descartar | Remova a entrada do stash da lista de armazenamento. |
|
||||
| `` n `` | Nova branch | Criar um novo ramo a partir da entrada de lixo selecionada. Isso funciona verificando o commit do qual a entrada de lixo foi criada, criar um novo branch a partir desse commit e, em seguida, aplicar a entrada de lixo ao novo branch como um commit adicional. |
|
||||
| `` w `` | Nova árvore de trabalho | |
|
||||
| `` r `` | Renomear o stash | |
|
||||
| `` 0 `` | Focar visualização principal | |
|
||||
| `` <enter> `` | Ver arquivos | |
|
||||
| `` w `` | Ver opções da árvore de trabalho | |
|
||||
| `` / `` | Filtrar a visualização atual por texto | |
|
||||
|
||||
## Status
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` o `` | Abrir o ficheiro de config | Abrir arquivo no aplicativo padrão. |
|
||||
| `` e `` | Editar arquivo de configuração | Abrir arquivo no editor externo. |
|
||||
| `` u `` | Verificar atualização | |
|
||||
| `` <enter> `` | Mudar para um repositório recente | |
|
||||
@@ -374,6 +375,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | Abrir commit no navegador | |
|
||||
| `` n `` | Create new branch off of commit | |
|
||||
| `` N `` | Mover commits para uma nova branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | Nova árvore de trabalho | |
|
||||
| `` g `` | Restaurar | Ver opções de redefinição (soft/mixed/hard) para redefinir para o item selecionado. |
|
||||
| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. |
|
||||
| `` <ctrl+r> `` | Reset copied (cherry-picked) commits selection | |
|
||||
@@ -381,7 +383,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focar visualização principal | |
|
||||
| `` <enter> `` | Ver arquivos | |
|
||||
| `` w `` | Ver opções da árvore de trabalho | |
|
||||
| `` / `` | Pesquisar na visualização atual por texto | |
|
||||
|
||||
## Submódulos
|
||||
|
||||
@@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` R `` | Обновить | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
|
||||
| `` + `` | Следующий режим экрана (нормальный/полуэкранный/полноэкранный) | |
|
||||
| `` _ `` | Предыдущий режим экрана | |
|
||||
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
|
||||
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers. |
|
||||
| `` \ `` | Cycle pagers (reverse) | Choose the previous pager in the list of configured pagers. |
|
||||
| `` <esc> `` | Отменить | |
|
||||
| `` ? `` | Открыть меню | |
|
||||
| `` <ctrl+s> `` | Просмотреть параметры фильтрации по пути | View options for filtering the commit log, so that only commits matching the filter are shown. |
|
||||
@@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` q, <ctrl+c> `` | Выйти | |
|
||||
| `` <ctrl+z> `` | Suspend the application | |
|
||||
| `` <ctrl+w> `` | Переключить отображение изменении пробелов в просмотрщике сравнении | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
|
||||
| `` <alt+shift+c> `` | Редактировать файл конфигурации | Open file in external editor. |
|
||||
| `` z `` | Отменить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git запустить, чтобы отменить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. |
|
||||
| `` Z `` | Повторить (через reflog) (экспериментальный) | Журнал ссылок (reflog) будет использоваться для определения того, какую команду git нужно запустить, чтобы повторить последнюю команду git. Сюда не входят изменения в рабочем дереве; учитываются только коммиты. |
|
||||
|
||||
@@ -112,7 +114,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | Выбрать эту часть | |
|
||||
| `` b `` | Выбрать все части | |
|
||||
| `` b `` | Pick both hunks | |
|
||||
| `` <up>, k `` | Выбрать предыдущую часть | |
|
||||
| `` <down>, j `` | Выбрать следующую часть | |
|
||||
| `` <left>, h `` | Выбрать предыдущий конфликт | |
|
||||
@@ -149,6 +151,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | Открыть коммит в браузере | |
|
||||
| `` n `` | Создать новую ветку с этого коммита | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <ctrl+r> `` | Сбросить отобранную (скопированную \| cherry-picked) выборку коммитов | |
|
||||
@@ -156,7 +159,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Просмотреть коммиты | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Коммиты
|
||||
@@ -192,13 +194,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | Открыть коммит в браузере | |
|
||||
| `` n `` | Создать новую ветку с этого коммита | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Просмотреть файлы выбранного элемента | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Найти | |
|
||||
|
||||
## Локальные Ветки
|
||||
@@ -210,6 +212,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <space> `` | Переключить | Checkout selected item. |
|
||||
| `` n `` | Новая ветка | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` o `` | Создать запрос на принятие изменений | |
|
||||
| `` O `` | Создать параметры запроса принятие изменений | |
|
||||
| `` G `` | Open pull request in browser | |
|
||||
@@ -229,7 +232,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Просмотреть коммиты | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Меню
|
||||
@@ -258,6 +260,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | Открыть коммит в браузере | |
|
||||
| `` n `` | Создать новую ветку с этого коммита | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` g `` | Просмотреть параметры сброса | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | Скопировать отобранные коммит (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <ctrl+r> `` | Сбросить отобранную (скопированную \| cherry-picked) выборку коммитов | |
|
||||
@@ -265,7 +268,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Просмотреть файлы выбранного элемента | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Найти | |
|
||||
|
||||
## Подмодули
|
||||
@@ -313,7 +315,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` o `` | Открыть файл конфигурации | Open file in default application. |
|
||||
| `` e `` | Редактировать файл конфигурации | Open file in external editor. |
|
||||
| `` u `` | Проверить обновления | |
|
||||
| `` <enter> `` | Переключиться на последний репозиторий | |
|
||||
@@ -328,13 +329,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | Copy tag to clipboard | |
|
||||
| `` <space> `` | Переключить | Checkout the selected tag as a detached HEAD. |
|
||||
| `` n `` | Создать тег | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` w `` | New worktree | |
|
||||
| `` 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. |
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Просмотреть коммиты | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Удалённые ветки
|
||||
@@ -344,6 +345,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | Скопировать название ветки в буфер обмена | |
|
||||
| `` <space> `` | Переключить | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
|
||||
| `` n `` | Новая ветка | |
|
||||
| `` w `` | New worktree | |
|
||||
| `` 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. |
|
||||
@@ -353,7 +355,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+t> `` | Open external diff tool (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Просмотреть коммиты | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Удалённые репозитории
|
||||
@@ -409,8 +410,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` 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. |
|
||||
| `` w `` | New worktree | |
|
||||
| `` r `` | Переименовать хранилище | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | Просмотреть файлы выбранного элемента | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
@@ -23,6 +23,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` + `` | 下一屏模式(正常/半屏/全屏) | |
|
||||
| `` _ `` | 上一屏模式 | |
|
||||
| `` \| `` | 切换分页器 | 从已配置的分页器列表中选择下一个分页器 |
|
||||
| `` \ `` | Cycle pagers (reverse) | Choose the previous pager in the list of configured pagers. |
|
||||
| `` <esc> `` | 取消 | |
|
||||
| `` ? `` | 打开菜单 | |
|
||||
| `` <ctrl+s> `` | 查看按路径过滤选项 | 查看用于过滤提交日志的选项,以便仅显示与过滤器匹配的提交。 |
|
||||
@@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` q, <ctrl+c> `` | 退出 | |
|
||||
| `` <ctrl+z> `` | 挂起应用程序 | |
|
||||
| `` <ctrl+w> `` | 切换是否在差异视图中显示空白字符差异 | 切换是否在差异视图中显示空白字符更改。<br><br>默认值可在配置文件中通过键 'git.ignoreWhitespaceInDiffView' 更改。 |
|
||||
| `` <alt+shift+c> `` | 编辑配置文件 | 使用外部编辑器打开文件 |
|
||||
| `` z `` | 撤销 | Reflog将用于确定运行哪个git命令来撤消最后一个git命令。这并不包括对工作树的更改,只考虑提交。 |
|
||||
| `` Z `` | 重做 | Reflog将用于确定运行哪个git命令来重做上一个git命令。这并不包括对工作树的更改,只考虑提交。 |
|
||||
|
||||
@@ -60,6 +62,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | 在浏览器中打开提交 | |
|
||||
| `` n `` | 从提交创建新分支 | |
|
||||
| `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 |
|
||||
| `` w `` | 新建工作树 | |
|
||||
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
|
||||
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
|
||||
| `` <ctrl+r> `` | 重置已拣选(复制)的提交 | |
|
||||
@@ -67,7 +70,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` * `` | 选择当前分支的提交 | |
|
||||
| `` 0 `` | 聚焦主视图 | |
|
||||
| `` <enter> `` | 查看提交的文件 | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
## 子模块
|
||||
@@ -104,6 +106,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | 在浏览器中打开提交 | |
|
||||
| `` n `` | 从提交创建新分支 | |
|
||||
| `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 |
|
||||
| `` w `` | 新建工作树 | |
|
||||
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
|
||||
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
|
||||
| `` <ctrl+r> `` | 重置已拣选(复制)的提交 | |
|
||||
@@ -111,7 +114,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` * `` | 选择当前分支的提交 | |
|
||||
| `` 0 `` | 聚焦主视图 | |
|
||||
| `` <enter> `` | 查看提交 | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
## 提交
|
||||
@@ -147,13 +149,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | 在浏览器中打开提交 | |
|
||||
| `` n `` | 从提交创建新分支 | |
|
||||
| `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 |
|
||||
| `` w `` | 新建工作树 | |
|
||||
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
|
||||
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
|
||||
| `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | |
|
||||
| `` * `` | 选择当前分支的提交 | |
|
||||
| `` 0 `` | 聚焦主视图 | |
|
||||
| `` <enter> `` | 查看提交的文件 | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
## 提交信息
|
||||
@@ -225,6 +227,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <space> `` | 检出 | 检出选中的项目 |
|
||||
| `` n `` | 新分支 | |
|
||||
| `` N `` | 移动提交至新分支 | 创建一个新分支,并将当前分支未推送的提交移动到该分支。如果您打算开始新工作但忘记先创建新分支,这会很有用。<br><br>请注意,此操作忽略选择,新分支总是从主分支创建或堆叠在当前分支之上(您可以选择哪种方式)。 |
|
||||
| `` w `` | 新建工作树 | |
|
||||
| `` o `` | 创建拉取请求 | |
|
||||
| `` O `` | 创建拉取请求选项 | |
|
||||
| `` G `` | 在浏览器中打开拉取请求 | |
|
||||
@@ -244,7 +247,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | |
|
||||
| `` 0 `` | 聚焦主视图 | |
|
||||
| `` <enter> `` | 查看提交 | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
## 构建补丁中
|
||||
@@ -270,13 +272,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | 复制标签到剪贴板 | |
|
||||
| `` <space> `` | 检出 | 检出选择的标签作为分离的HEAD |
|
||||
| `` n `` | 创建标签 | 基于当前提交创建一个新标签。您将在弹窗中输入标签名称和描述(可选)。 |
|
||||
| `` w `` | 新建工作树 | |
|
||||
| `` d `` | 删除 | 查看本地/远程标签的删除选项 |
|
||||
| `` P `` | 推送标签 | 推送选择的标签到远端。您将在弹窗中选择一个远端。 |
|
||||
| `` g `` | 重置 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
|
||||
| `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | |
|
||||
| `` 0 `` | 聚焦主视图 | |
|
||||
| `` <enter> `` | 查看提交 | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
## 次要
|
||||
@@ -292,7 +294,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | 选中区块 | |
|
||||
| `` b `` | 选中所有区块 | |
|
||||
| `` b `` | Pick both hunks | |
|
||||
| `` <up>, k `` | 选择顶部块 | |
|
||||
| `` <down>, j `` | 选择底部块 | |
|
||||
| `` <left>, h `` | 选择上一个冲突 | |
|
||||
@@ -339,7 +341,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` o `` | 打开配置文件 | 使用默认程序打开该文件 |
|
||||
| `` e `` | 编辑配置文件 | 使用外部编辑器打开文件 |
|
||||
| `` u `` | 检查更新 | |
|
||||
| `` <enter> `` | 切换到最近的仓库 | |
|
||||
@@ -371,10 +372,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` g `` | 应用并删除 | 将存储项应用到工作目录并删除存储项。 |
|
||||
| `` d `` | 删除 | 从贮藏列表中删除该贮藏项 |
|
||||
| `` n `` | 新分支 | 从选定的贮藏项创建一个新分支。这是通过 git 检查创建贮藏项的提交,从该提交创建一个新分支,然后将贮藏项作为附加提交应用到新分支来实现的。 |
|
||||
| `` w `` | 新建工作树 | |
|
||||
| `` r `` | 重命名贮藏 | |
|
||||
| `` 0 `` | 聚焦主视图 | |
|
||||
| `` <enter> `` | 查看提交的文件 | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
## 输入提示
|
||||
@@ -403,6 +404,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | 复制分支名称到剪贴板 | |
|
||||
| `` <space> `` | 检出 | 基于当前选中的远程分支检出一个新的本地分支,或者将远程分支作分离的HEAD。 |
|
||||
| `` n `` | 新分支 | |
|
||||
| `` w `` | 新建工作树 | |
|
||||
| `` M `` | 合并到当前检出的分支 | 查看将选中项合并到当前分支的选项(正常合并,压缩合并) |
|
||||
| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 |
|
||||
| `` d `` | 删除 | 从远程删除远程分支。 |
|
||||
@@ -412,5 +414,4 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+t> `` | 使用外部差异比较工具(git difftool) | |
|
||||
| `` 0 `` | 聚焦主视图 | |
|
||||
| `` <enter> `` | 查看提交 | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
@@ -22,7 +22,8 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` R `` | 重新整理 | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
|
||||
| `` + `` | 下一個螢幕模式(常規/半螢幕/全螢幕) | |
|
||||
| `` _ `` | 上一個螢幕模式 | |
|
||||
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers |
|
||||
| `` \| `` | Cycle pagers | Choose the next pager in the list of configured pagers. |
|
||||
| `` \ `` | Cycle pagers (reverse) | Choose the previous pager in the list of configured pagers. |
|
||||
| `` <esc> `` | 取消 | |
|
||||
| `` ? `` | 開啟選單 | |
|
||||
| `` <ctrl+s> `` | 檢視篩選路徑選項 | View options for filtering the commit log, so that only commits matching the filter are shown. |
|
||||
@@ -30,6 +31,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` q, <ctrl+c> `` | 結束 | |
|
||||
| `` <ctrl+z> `` | Suspend the application | |
|
||||
| `` <ctrl+w> `` | 切換是否在差異檢視中顯示空格變更 | Toggle whether or not whitespace changes are shown in the diff view.<br><br>The default can be changed in the config file with the key 'git.ignoreWhitespaceInDiffView'. |
|
||||
| `` <alt+shift+c> `` | 編輯設定檔案 | 使用外部編輯器開啟 |
|
||||
| `` z `` | 復原 | 將使用 reflog 確任 git 指令以復原。這不包括工作區更改;只考慮提交。 |
|
||||
| `` Z `` | 取消復原 | 將使用 reflog 確任 git 指令以重作。這不包括工作區更改;只考慮提交。 |
|
||||
|
||||
@@ -88,7 +90,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | 挑選程式碼片段 | |
|
||||
| `` b `` | 挑選所有程式碼片段 | |
|
||||
| `` b `` | Pick both hunks | |
|
||||
| `` <up>, k `` | 選擇上一段 | |
|
||||
| `` <down>, j `` | 選擇下一段 | |
|
||||
| `` <left>, h `` | 選擇上一個衝突 | |
|
||||
@@ -139,6 +141,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | 在瀏覽器中開啟提交 | |
|
||||
| `` n `` | 從提交建立新分支 | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` 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. |
|
||||
| `` <ctrl+r> `` | 重設選定的揀選 (複製) 提交 | |
|
||||
@@ -146,7 +149,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | 檢視所選項目的檔案 | |
|
||||
| `` w `` | 檢視工作目錄選項 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
|
||||
## 子模組
|
||||
@@ -206,13 +208,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | 在瀏覽器中開啟提交 | |
|
||||
| `` n `` | 從提交建立新分支 | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` 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. |
|
||||
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | 檢視所選項目的檔案 | |
|
||||
| `` w `` | 檢視工作目錄選項 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
|
||||
## 提交摘要
|
||||
@@ -250,10 +252,10 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` 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. |
|
||||
| `` w `` | New worktree | |
|
||||
| `` r `` | 重新命名收藏 | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | 檢視所選項目的檔案 | |
|
||||
| `` w `` | 檢視工作目錄選項 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
|
||||
## 日誌
|
||||
@@ -266,6 +268,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` o `` | 在瀏覽器中開啟提交 | |
|
||||
| `` n `` | 從提交建立新分支 | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` 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. |
|
||||
| `` <ctrl+r> `` | 重設選定的揀選 (複製) 提交 | |
|
||||
@@ -273,7 +276,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | 檢視提交 | |
|
||||
| `` w `` | 檢視工作目錄選項 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
|
||||
## 本地分支
|
||||
@@ -285,6 +287,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <space> `` | 檢出 | 檢出選定的項目。 |
|
||||
| `` n `` | 新分支 | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.<br><br>Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` w `` | New worktree | |
|
||||
| `` o `` | 建立拉取請求 | |
|
||||
| `` O `` | 建立拉取請求選項 | |
|
||||
| `` G `` | Open pull request in browser | |
|
||||
@@ -304,7 +307,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | 檢視提交 | |
|
||||
| `` w `` | 檢視工作目錄選項 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
|
||||
## 標籤
|
||||
@@ -314,13 +316,13 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | Copy tag to clipboard | |
|
||||
| `` <space> `` | 檢出 | Checkout the selected tag as a detached HEAD. |
|
||||
| `` n `` | 建立標籤 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` w `` | New worktree | |
|
||||
| `` 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 `` | 重設 | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | 檢視提交 | |
|
||||
| `` w `` | 檢視工作目錄選項 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
|
||||
## 檔案
|
||||
@@ -368,7 +370,6 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` o `` | 開啟設定檔案 | 使用預設軟體開啟 |
|
||||
| `` e `` | 編輯設定檔案 | 使用外部編輯器開啟 |
|
||||
| `` u `` | 檢查更新 | |
|
||||
| `` <enter> `` | 切換到最近使用的版本庫 | |
|
||||
@@ -403,6 +404,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+o> `` | 複製分支名稱到剪貼簿 | |
|
||||
| `` <space> `` | 檢出 | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
|
||||
| `` n `` | 新分支 | |
|
||||
| `` w `` | New worktree | |
|
||||
| `` 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 the remote branch from the remote. |
|
||||
@@ -412,5 +414,4 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
| `` <ctrl+t> `` | 開啟外部差異工具 (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | 檢視提交 | |
|
||||
| `` w `` | 檢視工作目錄選項 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
|
||||
@@ -25,6 +25,7 @@ require (
|
||||
github.com/lucasb-eyer/go-colorful v1.4.0
|
||||
github.com/mgutz/str v1.2.0
|
||||
github.com/mitchellh/go-ps v1.0.0
|
||||
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe
|
||||
github.com/rivo/uniseg v0.4.7
|
||||
github.com/sahilm/fuzzy v0.1.3
|
||||
github.com/samber/lo v1.53.0
|
||||
@@ -62,7 +63,6 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/onsi/ginkgo v1.10.3 // indirect
|
||||
github.com/onsi/gomega v1.34.1 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
golang.org/x/mod v0.35.0 // indirect
|
||||
|
||||
+2
-2
@@ -117,8 +117,8 @@ func NewGitCommandAux(
|
||||
rebaseCommands := git_commands.NewRebaseCommands(gitCommon, commitCommands, workingTreeCommands)
|
||||
stashCommands := git_commands.NewStashCommands(gitCommon, fileLoader, workingTreeCommands)
|
||||
patchBuilder := patch.NewPatchBuilder(cmn.Log,
|
||||
func(from string, to string, reverse bool, filename string, plain bool) (string, error) {
|
||||
return workingTreeCommands.ShowFileDiff(from, to, reverse, filename, plain)
|
||||
func(from string, to string, reverse bool, filename string, previousPath string, plain bool) (string, error) {
|
||||
return workingTreeCommands.ShowFileDiff(from, to, reverse, filename, previousPath, plain)
|
||||
})
|
||||
patchCommands := git_commands.NewPatchCommands(gitCommon, rebaseCommands, commitCommands, statusCommands, stashCommands, patchBuilder)
|
||||
bisectCommands := git_commands.NewBisectCommands(gitCommon)
|
||||
|
||||
@@ -156,7 +156,7 @@ func (self *BranchLoader) GetBehindBaseBranchValuesForAllBranches(
|
||||
}
|
||||
|
||||
if self.version.IsAtLeast(2, 41, 0) {
|
||||
return self.getBehindBaseBranchValuesFast(branches, mainBranches, renderFunc)
|
||||
return self.getBehindBaseBranchValuesFast(branches, mainBranchRefs, renderFunc)
|
||||
}
|
||||
return self.getBehindBaseBranchValuesLegacy(branches, mainBranches, renderFunc)
|
||||
}
|
||||
@@ -171,11 +171,31 @@ func (self *BranchLoader) getBehindBaseBranchValuesLegacy(
|
||||
|
||||
for _, branch := range branches {
|
||||
errg.Go(func() error {
|
||||
_, behinds, err := self.baseBranchCandidatesAndBehinds(branch, mainBranches)
|
||||
baseBranch, err := self.GetBaseBranch(branch, mainBranches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
branch.BehindBaseBranch.Store(classifyBehind(behinds))
|
||||
behind := 0 // prime it in case something below fails
|
||||
if baseBranch != "" {
|
||||
output, err := self.cmd.New(
|
||||
NewGitCmd("rev-list").
|
||||
Arg("--left-right").
|
||||
Arg("--count").
|
||||
Arg(fmt.Sprintf("%s...%s", branch.FullRefName(), baseBranch)).
|
||||
ToArgv(),
|
||||
).DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// The format of the output is "<ahead>\t<behind>"
|
||||
aheadBehindStr := strings.Split(strings.TrimSpace(output), "\t")
|
||||
if len(aheadBehindStr) == 2 {
|
||||
if value, err := strconv.Atoi(aheadBehindStr[1]); err == nil {
|
||||
behind = value
|
||||
}
|
||||
}
|
||||
}
|
||||
branch.BehindBaseBranch.Store(int32(behind))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -186,13 +206,9 @@ func (self *BranchLoader) getBehindBaseBranchValuesLegacy(
|
||||
return err
|
||||
}
|
||||
|
||||
// Holds parsed values from a single %(ahead-behind:<base>) field. `valid`
|
||||
// is false when the field failed to parse (e.g. the base was unreachable
|
||||
// from this ref); the entry is preserved so that the slice stays index-
|
||||
// aligned with the configured main branches.
|
||||
// Holds parsed values from a single %(ahead-behind:<base>) field.
|
||||
type aheadBehind struct {
|
||||
ahead, behind int
|
||||
valid bool
|
||||
}
|
||||
|
||||
type branchAheadBehind struct {
|
||||
@@ -206,7 +222,7 @@ type branchAheadBehind struct {
|
||||
//
|
||||
// Lines whose NUL-split column count doesn't match (1 + numBases) are dropped.
|
||||
// Blank lines are ignored.
|
||||
// Individual malformed ahead-behind fields produce {valid: false} entries.
|
||||
// Individual malformed ahead-behind fields produce {valid: false} entries
|
||||
func parseAheadBehindForEachRefOutput(
|
||||
output string,
|
||||
numBases int, // number of %(ahead-behind:...) tokens
|
||||
@@ -222,7 +238,7 @@ func parseAheadBehindForEachRefOutput(
|
||||
continue
|
||||
}
|
||||
refName := cols[0]
|
||||
aheadBehinds := lo.Map(cols[1:], func(col string, _ int) aheadBehind {
|
||||
aheadBehinds := lo.FilterMap(cols[1:], func(col string, _ int) (aheadBehind, bool) {
|
||||
return parseAheadBehindField(col)
|
||||
})
|
||||
entry := branchAheadBehind{
|
||||
@@ -234,77 +250,27 @@ func parseAheadBehindForEachRefOutput(
|
||||
return result
|
||||
}
|
||||
|
||||
func parseAheadBehindField(s string) aheadBehind {
|
||||
func parseAheadBehindField(s string) (aheadBehind, bool) {
|
||||
parts := strings.Fields(s)
|
||||
if len(parts) != 2 {
|
||||
return aheadBehind{}
|
||||
return aheadBehind{}, false
|
||||
}
|
||||
ahead, err1 := strconv.Atoi(parts[0])
|
||||
behind, err2 := strconv.Atoi(parts[1])
|
||||
if err1 != nil || err2 != nil {
|
||||
return aheadBehind{}
|
||||
return aheadBehind{}, false
|
||||
}
|
||||
return aheadBehind{ahead: ahead, behind: behind, valid: true}
|
||||
return aheadBehind{ahead: ahead, behind: behind}, true
|
||||
}
|
||||
|
||||
// selectBaseForBranch picks the closest base(s) for a branch given
|
||||
// (ahead, behind) measurements against each configured main branch.
|
||||
// "Closest" = smallest ahead value (fewest branch commits not in the
|
||||
// base). Ties are broken by the order of mainRefs (i.e. config order).
|
||||
//
|
||||
// aheadBehinds must be index-aligned with mainRefs; invalid entries are
|
||||
// skipped. Returns parallel slices: the refs tied at the minimum ahead
|
||||
// (in config order) and their behind values. The caller picks
|
||||
// candidates[0] for a single answer, or detects ambiguity via
|
||||
// `len(candidates) > 1`.
|
||||
func selectBaseForBranch(
|
||||
aheadBehinds []aheadBehind, mainRefs []string,
|
||||
) (candidates []string, behinds []int) {
|
||||
bestAhead := -1
|
||||
for i, ab := range aheadBehinds {
|
||||
if !ab.valid {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case bestAhead < 0 || ab.ahead < bestAhead:
|
||||
bestAhead = ab.ahead
|
||||
candidates = []string{mainRefs[i]}
|
||||
behinds = []int{ab.behind}
|
||||
case ab.ahead == bestAhead:
|
||||
candidates = append(candidates, mainRefs[i])
|
||||
behinds = append(behinds, ab.behind)
|
||||
}
|
||||
}
|
||||
return candidates, behinds
|
||||
}
|
||||
|
||||
// classifyBehind condenses per-candidate behind values into the single
|
||||
// number stored on Branch.BehindBaseBranch for column display. When the
|
||||
// candidates all agree (possibly on 0), return that value; otherwise
|
||||
// return one of the BehindBaseAmbiguous* sentinels so the renderer can
|
||||
// show "?" or "↓?" instead of a misleadingly precise count.
|
||||
func classifyBehind(behinds []int) int32 {
|
||||
if len(behinds) == 0 {
|
||||
return 0
|
||||
}
|
||||
first := behinds[0]
|
||||
allEqual := true
|
||||
anyZero := first == 0
|
||||
for _, b := range behinds[1:] {
|
||||
if b != first {
|
||||
allEqual = false
|
||||
}
|
||||
if b == 0 {
|
||||
anyZero = true
|
||||
}
|
||||
}
|
||||
if allEqual {
|
||||
return int32(first)
|
||||
}
|
||||
if anyZero {
|
||||
return models.BehindBaseAmbiguousMaybeUpToDate
|
||||
}
|
||||
return models.BehindBaseAmbiguousDefinitelyBehind
|
||||
// Picks the "closest" base by smallest ahead value (commits the branch
|
||||
// has that the base doesn't = roughly "since fork point") and returns
|
||||
// its behind value.
|
||||
// Ties are broken by index order
|
||||
func selectBehindForBranch(aheadBehinds []aheadBehind) int {
|
||||
return lo.MinBy(aheadBehinds, func(a, b aheadBehind) bool {
|
||||
return a.ahead < b.ahead
|
||||
}).behind
|
||||
}
|
||||
|
||||
// The output format is:
|
||||
@@ -330,12 +296,11 @@ func buildAheadBehindForEachRefArgs(mainBranchRefs []string) []string {
|
||||
|
||||
func (self *BranchLoader) getBehindBaseBranchValuesFast(
|
||||
branches []*models.Branch,
|
||||
mainBranches *MainBranches,
|
||||
mainBranchRefs []string,
|
||||
renderFunc func(),
|
||||
) error {
|
||||
t := time.Now()
|
||||
|
||||
mainBranchRefs := mainBranches.Get()
|
||||
output, err := self.cmd.New(
|
||||
buildAheadBehindForEachRefArgs(mainBranchRefs),
|
||||
).DontLog().RunWithOutput()
|
||||
@@ -348,8 +313,8 @@ func (self *BranchLoader) getBehindBaseBranchValuesFast(
|
||||
|
||||
for _, p := range parsed {
|
||||
if branch, ok := branchByRef[p.refName]; ok {
|
||||
_, behinds := selectBaseForBranch(p.aheadBehinds, mainBranchRefs)
|
||||
branch.BehindBaseBranch.Store(classifyBehind(behinds))
|
||||
behind := selectBehindForBranch(p.aheadBehinds)
|
||||
branch.BehindBaseBranch.Store(int32(behind))
|
||||
delete(branchByRef, p.refName)
|
||||
}
|
||||
}
|
||||
@@ -364,83 +329,37 @@ func (self *BranchLoader) getBehindBaseBranchValuesFast(
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBaseBranchCandidates returns the configured main branches that are the
|
||||
// closest base for the given branch — typically a single ref, but more
|
||||
// when the closeness rule (smallest ahead value) leaves a tie. Candidates
|
||||
// are returned in config order, so callers wanting one answer can use
|
||||
// candidates[0] as the config-order tiebreak. An empty slice (with nil
|
||||
// error) means no configured main branch contains the branch's merge-base.
|
||||
func (self *BranchLoader) GetBaseBranchCandidates(branch *models.Branch, mainBranches *MainBranches) ([]string, error) {
|
||||
candidates, _, err := self.baseBranchCandidatesAndBehinds(branch, mainBranches)
|
||||
return candidates, err
|
||||
}
|
||||
|
||||
// baseBranchCandidatesAndBehinds is the full computation behind
|
||||
// GetBaseBranchCandidates: it also reports the behind count for each
|
||||
// returned candidate, which the legacy behind-base loader needs in order
|
||||
// to classify the column display when the candidates disagree. Slices
|
||||
// are parallel and in config order.
|
||||
func (self *BranchLoader) baseBranchCandidatesAndBehinds(branch *models.Branch, mainBranches *MainBranches) ([]string, []int, error) {
|
||||
// Find the base branch for the given branch (i.e. the main branch that the
|
||||
// given branch was forked off of)
|
||||
//
|
||||
// Note that this function may return an empty string even if the returned error
|
||||
// is nil, e.g. when none of the configured main branches exist. This is not
|
||||
// considered an error condition, so callers need to check both the returned
|
||||
// error and whether the returned base branch is empty (and possibly react
|
||||
// differently in both cases).
|
||||
func (self *BranchLoader) GetBaseBranch(branch *models.Branch, mainBranches *MainBranches) (string, error) {
|
||||
mergeBase := mainBranches.GetMergeBase(branch.FullRefName())
|
||||
if mergeBase == "" {
|
||||
return nil, nil, nil
|
||||
return "", nil
|
||||
}
|
||||
|
||||
mainBranchRefs := mainBranches.Get()
|
||||
output, err := self.cmd.New(
|
||||
NewGitCmd("for-each-ref").
|
||||
Arg("--contains").
|
||||
Arg(mergeBase).
|
||||
Arg("--format=%(refname)").
|
||||
Arg(mainBranchRefs...).
|
||||
Arg(mainBranches.Get()...).
|
||||
ToArgv(),
|
||||
).DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return "", err
|
||||
}
|
||||
trimmedOutput := strings.TrimSpace(output)
|
||||
if trimmedOutput == "" {
|
||||
return nil, nil, nil
|
||||
split := strings.Split(trimmedOutput, "\n")
|
||||
if len(split) == 0 || split[0] == "" {
|
||||
return "", nil
|
||||
}
|
||||
contained := strings.Split(trimmedOutput, "\n")
|
||||
|
||||
// for-each-ref sorts its output alphabetically by refname regardless of
|
||||
// the order we passed the refs in. Restore the user's configured order so
|
||||
// it can serve as the natural tiebreaker.
|
||||
containing := lo.Filter(mainBranchRefs, func(ref string, _ int) bool {
|
||||
return lo.Contains(contained, ref)
|
||||
})
|
||||
if len(containing) == 0 {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// Measure ahead/behind against each containing ref and hand off to
|
||||
// selectBaseForBranch — the same selector the fast path uses — so
|
||||
// both paths agree on the closeness rule and the config-order
|
||||
// tiebreak. We do this even when there's only one containing ref,
|
||||
// because the legacy column display still needs the behind value.
|
||||
aheadBehinds := make([]aheadBehind, len(containing))
|
||||
for i, ref := range containing {
|
||||
revListOutput, err := self.cmd.New(
|
||||
NewGitCmd("rev-list").
|
||||
Arg("--left-right").
|
||||
Arg("--count").
|
||||
Arg(fmt.Sprintf("%s...%s", branch.FullRefName(), ref)).
|
||||
ToArgv(),
|
||||
).DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
aheadBehinds[i] = parseAheadBehindField(strings.TrimSpace(revListOutput))
|
||||
}
|
||||
|
||||
candidates, behinds := selectBaseForBranch(aheadBehinds, containing)
|
||||
if len(candidates) == 0 {
|
||||
// Every rev-list output was malformed; fall back to config order
|
||||
// with no reliable behinds.
|
||||
return containing, nil, nil
|
||||
}
|
||||
return candidates, behinds, nil
|
||||
return split[0], nil
|
||||
}
|
||||
|
||||
func (self *BranchLoader) obtainBranches() []*models.Branch {
|
||||
|
||||
@@ -142,7 +142,7 @@ func TestParseAheadBehindForEachRefOutput(t *testing.T) {
|
||||
expected: []branchAheadBehind{
|
||||
{
|
||||
refName: "refs/heads/feat",
|
||||
aheadBehinds: []aheadBehind{{ahead: 2, behind: 5, valid: true}},
|
||||
aheadBehinds: []aheadBehind{{ahead: 2, behind: 5}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -155,15 +155,15 @@ func TestParseAheadBehindForEachRefOutput(t *testing.T) {
|
||||
{
|
||||
refName: "refs/heads/feat",
|
||||
aheadBehinds: []aheadBehind{
|
||||
{ahead: 2, behind: 5, valid: true},
|
||||
{ahead: 10, behind: 1, valid: true},
|
||||
{ahead: 2, behind: 5},
|
||||
{ahead: 10, behind: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
refName: "refs/heads/main",
|
||||
aheadBehinds: []aheadBehind{
|
||||
{ahead: 0, behind: 0, valid: true},
|
||||
{ahead: 0, behind: 0, valid: true},
|
||||
{ahead: 0, behind: 0},
|
||||
{ahead: 0, behind: 0},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -176,8 +176,7 @@ func TestParseAheadBehindForEachRefOutput(t *testing.T) {
|
||||
{
|
||||
refName: "refs/heads/feat",
|
||||
aheadBehinds: []aheadBehind{
|
||||
{valid: false},
|
||||
{ahead: 2, behind: 5, valid: true},
|
||||
{ahead: 2, behind: 5},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -189,7 +188,7 @@ func TestParseAheadBehindForEachRefOutput(t *testing.T) {
|
||||
expected: []branchAheadBehind{
|
||||
{
|
||||
refName: "refs/heads/feat/foo-bar",
|
||||
aheadBehinds: []aheadBehind{{ahead: 1, behind: 2, valid: true}},
|
||||
aheadBehinds: []aheadBehind{{ahead: 1, behind: 2}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -200,7 +199,7 @@ func TestParseAheadBehindForEachRefOutput(t *testing.T) {
|
||||
expected: []branchAheadBehind{
|
||||
{
|
||||
refName: "refs/heads/feat",
|
||||
aheadBehinds: []aheadBehind{{ahead: 1, behind: 2, valid: true}},
|
||||
aheadBehinds: []aheadBehind{{ahead: 1, behind: 2}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -213,11 +212,11 @@ func TestParseAheadBehindForEachRefOutput(t *testing.T) {
|
||||
expected: []branchAheadBehind{
|
||||
{
|
||||
refName: "refs/heads/good",
|
||||
aheadBehinds: []aheadBehind{{ahead: 1, behind: 2, valid: true}},
|
||||
aheadBehinds: []aheadBehind{{ahead: 1, behind: 2}},
|
||||
},
|
||||
{
|
||||
refName: "refs/heads/also_good",
|
||||
aheadBehinds: []aheadBehind{{ahead: 3, behind: 4, valid: true}},
|
||||
aheadBehinds: []aheadBehind{{ahead: 3, behind: 4}},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -228,7 +227,7 @@ func TestParseAheadBehindForEachRefOutput(t *testing.T) {
|
||||
expected: []branchAheadBehind{
|
||||
{
|
||||
refName: "refs/heads/feat",
|
||||
aheadBehinds: []aheadBehind{{valid: false}},
|
||||
aheadBehinds: []aheadBehind{},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -248,58 +247,26 @@ func TestParseAheadBehindForEachRefOutput(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestClassifyBehind(t *testing.T) {
|
||||
func TestSelectBehindForBranch(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
behinds []int
|
||||
expected int32
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{"empty", nil, 0},
|
||||
{"single zero", []int{0}, 0},
|
||||
{"single non-zero", []int{5}, 5},
|
||||
{"all equal zero", []int{0, 0, 0}, 0},
|
||||
{"all equal non-zero", []int{7, 7}, 7},
|
||||
{"mixed zero and non-zero → ?", []int{0, 5}, models.BehindBaseAmbiguousMaybeUpToDate},
|
||||
{"mixed zero and non-zero, reversed → ?", []int{5, 0}, models.BehindBaseAmbiguousMaybeUpToDate},
|
||||
{"all non-zero, different → ↓?", []int{3, 5}, models.BehindBaseAmbiguousDefinitelyBehind},
|
||||
{"all non-zero, different (three) → ↓?", []int{3, 5, 7}, models.BehindBaseAmbiguousDefinitelyBehind},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
assert.Equal(t, s.expected, classifyBehind(s.behinds))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectBaseForBranch(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
aheadBehinds []aheadBehind
|
||||
mainRefs []string
|
||||
expectedCandidates []string
|
||||
expectedBehinds []int
|
||||
testName string
|
||||
aheadBehinds []aheadBehind
|
||||
expected int
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "single base, valid value",
|
||||
aheadBehinds: []aheadBehind{{ahead: 3, behind: 7, valid: true}},
|
||||
mainRefs: []string{"refs/heads/master"},
|
||||
expectedCandidates: []string{"refs/heads/master"},
|
||||
expectedBehinds: []int{7},
|
||||
testName: "single base, valid value",
|
||||
aheadBehinds: []aheadBehind{{ahead: 3, behind: 7}},
|
||||
expected: 7,
|
||||
},
|
||||
{
|
||||
testName: "multi-base, clear winner by ahead",
|
||||
aheadBehinds: []aheadBehind{
|
||||
{ahead: 50, behind: 10, valid: true}, // master
|
||||
{ahead: 5, behind: 2, valid: true}, // develop ← smallest ahead
|
||||
{ahead: 50, behind: 10}, // master
|
||||
{ahead: 5, behind: 2}, // develop ← smallest ahead
|
||||
},
|
||||
mainRefs: []string{"refs/heads/master", "refs/heads/develop"},
|
||||
expectedCandidates: []string{"refs/heads/develop"},
|
||||
expectedBehinds: []int{2},
|
||||
expected: 2,
|
||||
},
|
||||
{
|
||||
testName: "develop forked from master case (ancestor-of-each-other)",
|
||||
@@ -308,60 +275,42 @@ func TestSelectBaseForBranch(t *testing.T) {
|
||||
// ahead vs master = 5 + 50 = 55; behind vs master = 0
|
||||
// ahead vs develop = 5; behind vs develop = 5
|
||||
aheadBehinds: []aheadBehind{
|
||||
{ahead: 55, behind: 0, valid: true}, // master
|
||||
{ahead: 5, behind: 5, valid: true}, // develop ← smallest ahead
|
||||
{ahead: 55, behind: 0}, // master
|
||||
{ahead: 5, behind: 5}, // develop ← smallest ahead
|
||||
},
|
||||
mainRefs: []string{"refs/heads/master", "refs/heads/develop"},
|
||||
expectedCandidates: []string{"refs/heads/develop"},
|
||||
expectedBehinds: []int{5},
|
||||
expected: 5,
|
||||
},
|
||||
{
|
||||
testName: "tie on ahead - both candidates returned in config order",
|
||||
testName: "tie on ahead - first base wins (config order)",
|
||||
aheadBehinds: []aheadBehind{
|
||||
{ahead: 5, behind: 10, valid: true}, // first
|
||||
{ahead: 5, behind: 99, valid: true}, // second, same ahead
|
||||
{ahead: 5, behind: 10}, // first
|
||||
{ahead: 5, behind: 99}, // second, same ahead
|
||||
},
|
||||
mainRefs: []string{"refs/heads/main", "refs/heads/develop"},
|
||||
expectedCandidates: []string{
|
||||
"refs/heads/main",
|
||||
"refs/heads/develop",
|
||||
},
|
||||
expectedBehinds: []int{10, 99},
|
||||
expected: 10,
|
||||
},
|
||||
{
|
||||
testName: "first base invalid, second valid",
|
||||
aheadBehinds: []aheadBehind{
|
||||
{valid: false},
|
||||
{ahead: 3, behind: 8, valid: true},
|
||||
{ahead: 3, behind: 8},
|
||||
},
|
||||
mainRefs: []string{"refs/heads/master", "refs/heads/develop"},
|
||||
expectedCandidates: []string{"refs/heads/develop"},
|
||||
expectedBehinds: []int{8},
|
||||
expected: 8,
|
||||
},
|
||||
{
|
||||
testName: "all invalid - returns empty",
|
||||
aheadBehinds: []aheadBehind{
|
||||
{valid: false},
|
||||
{valid: false},
|
||||
},
|
||||
mainRefs: []string{"refs/heads/master", "refs/heads/develop"},
|
||||
expectedCandidates: nil,
|
||||
expectedBehinds: nil,
|
||||
testName: "all invalid - returns 0",
|
||||
aheadBehinds: []aheadBehind{},
|
||||
expected: 0,
|
||||
},
|
||||
{
|
||||
testName: "empty - returns empty",
|
||||
aheadBehinds: nil,
|
||||
mainRefs: nil,
|
||||
expectedCandidates: nil,
|
||||
expectedBehinds: nil,
|
||||
testName: "empty - returns 0",
|
||||
aheadBehinds: nil,
|
||||
expected: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
candidates, behinds := selectBaseForBranch(s.aheadBehinds, s.mainRefs)
|
||||
assert.Equal(t, s.expectedCandidates, candidates)
|
||||
assert.Equal(t, s.expectedBehinds, behinds)
|
||||
result := selectBehindForBranch(s.aheadBehinds)
|
||||
assert.Equal(t, s.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -509,8 +458,8 @@ func TestGetBehindBaseBranchValuesForAllBranches_LegacyPath(t *testing.T) {
|
||||
{Name: "feat-x"},
|
||||
}
|
||||
|
||||
// In legacy path: per-branch GetBaseBranchCandidates (merge-base +
|
||||
// for-each-ref --contains) then rev-list --left-right --count.
|
||||
// In legacy path: per-branch GetBaseBranch (merge-base + for-each-ref --contains)
|
||||
// then rev-list --left-right --count.
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "refs/heads/feat-x", "refs/heads/master"}, "abc123\n", nil).
|
||||
ExpectGitArgs([]string{"for-each-ref", "--contains", "abc123", "--format=%(refname)", "refs/heads/master"}, "refs/heads/master\n", nil).
|
||||
@@ -542,98 +491,3 @@ func TestGetBehindBaseBranchValuesForAllBranches_LegacyPath(t *testing.T) {
|
||||
|
||||
runner.CheckForMissingCalls()
|
||||
}
|
||||
|
||||
// When the branch's merge-base is contained in more than one configured main
|
||||
// branch and the ahead counts are equal, the candidate list must preserve
|
||||
// the user's configured order rather than the alphabetical order of
|
||||
// for-each-ref's output.
|
||||
func TestGetBaseBranchCandidates_AmbiguousReturnsAllInConfigOrder(t *testing.T) {
|
||||
mainBranchRefs := []string{"refs/heads/main", "refs/heads/develop"}
|
||||
branch := &models.Branch{Name: "feat-x"}
|
||||
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs(
|
||||
[]string{"merge-base", "refs/heads/feat-x", "refs/heads/main", "refs/heads/develop"},
|
||||
"abc123\n", nil).
|
||||
ExpectGitArgs(
|
||||
[]string{
|
||||
"for-each-ref", "--contains", "abc123", "--format=%(refname)",
|
||||
"refs/heads/main", "refs/heads/develop",
|
||||
},
|
||||
"refs/heads/develop\nrefs/heads/main\n", nil).
|
||||
ExpectGitArgs(
|
||||
[]string{"rev-list", "--left-right", "--count", "refs/heads/feat-x...refs/heads/main"},
|
||||
"5\t10\n", nil).
|
||||
ExpectGitArgs(
|
||||
[]string{"rev-list", "--left-right", "--count", "refs/heads/feat-x...refs/heads/develop"},
|
||||
"5\t8\n", nil)
|
||||
|
||||
gitCommon := buildGitCommon(commonDeps{runner: runner})
|
||||
|
||||
loader := &BranchLoader{
|
||||
Common: gitCommon.Common,
|
||||
GitCommon: gitCommon,
|
||||
cmd: gitCommon.cmd,
|
||||
}
|
||||
|
||||
mainBranches := &MainBranches{
|
||||
c: gitCommon.Common,
|
||||
cmd: gitCommon.cmd,
|
||||
existingMainBranches: mainBranchRefs,
|
||||
previousMainBranches: gitCommon.Common.UserConfig().Git.MainBranches,
|
||||
}
|
||||
|
||||
candidates, err := loader.GetBaseBranchCandidates(branch, mainBranches)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"refs/heads/main", "refs/heads/develop"}, candidates)
|
||||
|
||||
runner.CheckForMissingCalls()
|
||||
}
|
||||
|
||||
// When a configured main branch has a strictly smaller ahead count than any
|
||||
// other (e.g. the branch was forked off `main` after main's last merge into
|
||||
// `develop`, so `develop` doesn't yet contain the fork point's recent main
|
||||
// history), that base wins outright regardless of config order, so only
|
||||
// that one ref is returned.
|
||||
func TestGetBaseBranchCandidates_UnambiguousReturnsSmallestAheadOnly(t *testing.T) {
|
||||
mainBranchRefs := []string{"refs/heads/develop", "refs/heads/main"}
|
||||
branch := &models.Branch{Name: "feat-x"}
|
||||
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs(
|
||||
[]string{"merge-base", "refs/heads/feat-x", "refs/heads/develop", "refs/heads/main"},
|
||||
"abc123\n", nil).
|
||||
ExpectGitArgs(
|
||||
[]string{
|
||||
"for-each-ref", "--contains", "abc123", "--format=%(refname)",
|
||||
"refs/heads/develop", "refs/heads/main",
|
||||
},
|
||||
"refs/heads/develop\nrefs/heads/main\n", nil).
|
||||
ExpectGitArgs(
|
||||
[]string{"rev-list", "--left-right", "--count", "refs/heads/feat-x...refs/heads/develop"},
|
||||
"8\t3\n", nil).
|
||||
ExpectGitArgs(
|
||||
[]string{"rev-list", "--left-right", "--count", "refs/heads/feat-x...refs/heads/main"},
|
||||
"5\t10\n", nil)
|
||||
|
||||
gitCommon := buildGitCommon(commonDeps{runner: runner})
|
||||
|
||||
loader := &BranchLoader{
|
||||
Common: gitCommon.Common,
|
||||
GitCommon: gitCommon,
|
||||
cmd: gitCommon.cmd,
|
||||
}
|
||||
|
||||
mainBranches := &MainBranches{
|
||||
c: gitCommon.Common,
|
||||
cmd: gitCommon.cmd,
|
||||
existingMainBranches: mainBranchRefs,
|
||||
previousMainBranches: gitCommon.Common.UserConfig().Git.MainBranches,
|
||||
}
|
||||
|
||||
candidates, err := loader.GetBaseBranchCandidates(branch, mainBranches)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{"refs/heads/main"}, candidates)
|
||||
|
||||
runner.CheckForMissingCalls()
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type CommitFileLoader struct {
|
||||
@@ -29,7 +29,7 @@ func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse boo
|
||||
Arg("--no-ext-diff").
|
||||
Arg("--name-status").
|
||||
Arg("-z").
|
||||
Arg("--no-renames").
|
||||
Arg(fmt.Sprintf("--find-renames=%d%%", self.UserConfig().Git.RenameSimilarityThreshold)).
|
||||
ArgIf(reverse, "-R").
|
||||
Arg(from).
|
||||
Arg(to).
|
||||
@@ -44,18 +44,37 @@ func (self *CommitFileLoader) GetFilesInDiff(from string, to string, reverse boo
|
||||
}
|
||||
|
||||
// filenames string is something like "MM\x00file1\x00MU\x00file2\x00AA\x00file3\x00"
|
||||
// so we need to split it by the null character and then map each status-name pair to a commit file
|
||||
// so we need to split it by the null character and then map each status-name pair
|
||||
// to a commit file. Renames (and copies) are special: their status is followed by
|
||||
// two paths (the old one and the new one) rather than one, e.g.
|
||||
// "R100\x00old\x00new\x00".
|
||||
func getCommitFilesFromFilenames(filenames string) []*models.CommitFile {
|
||||
lines := strings.Split(strings.TrimRight(filenames, "\x00"), "\x00")
|
||||
if len(lines) == 1 {
|
||||
fields := strings.Split(strings.TrimRight(filenames, "\x00"), "\x00")
|
||||
if len(fields) == 1 {
|
||||
return []*models.CommitFile{}
|
||||
}
|
||||
|
||||
// typical result looks like 'A my_file' meaning my_file was added
|
||||
return lo.Map(lo.Chunk(lines, 2), func(chunk []string, _ int) *models.CommitFile {
|
||||
return &models.CommitFile{
|
||||
ChangeStatus: chunk[0],
|
||||
Path: chunk[1],
|
||||
commitFiles := make([]*models.CommitFile, 0, len(fields)/2)
|
||||
for i := 0; i < len(fields)-1; {
|
||||
changeStatus := fields[i]
|
||||
if changeStatus[0] == 'R' || changeStatus[0] == 'C' {
|
||||
// The status has a similarity score appended (e.g. "R100"); drop it
|
||||
// so the rest of the code only has to deal with a plain "R" or "C".
|
||||
commitFiles = append(commitFiles, &models.CommitFile{
|
||||
ChangeStatus: changeStatus[:1],
|
||||
PreviousPath: fields[i+1],
|
||||
Path: fields[i+2],
|
||||
})
|
||||
i += 3
|
||||
} else {
|
||||
// typical result looks like 'A my_file' meaning my_file was added
|
||||
commitFiles = append(commitFiles, &models.CommitFile{
|
||||
ChangeStatus: changeStatus,
|
||||
Path: fields[i+1],
|
||||
})
|
||||
i += 2
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return commitFiles
|
||||
}
|
||||
|
||||
@@ -60,6 +60,25 @@ func TestGetCommitFilesFromFilenames(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "a rename among regular files",
|
||||
input: "M\x00Myfile\x00R100\x00before\x00after\x00A\x00Added\x00",
|
||||
output: []*models.CommitFile{
|
||||
{
|
||||
Path: "Myfile",
|
||||
ChangeStatus: "M",
|
||||
},
|
||||
{
|
||||
Path: "after",
|
||||
PreviousPath: "before",
|
||||
ChangeStatus: "R",
|
||||
},
|
||||
{
|
||||
Path: "Added",
|
||||
ChangeStatus: "A",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
@@ -432,8 +432,14 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
|
||||
|
||||
// ShowFileDiff get the diff of specified from and to. Typically this will be used for a single commit so it'll be 123abc^..123abc
|
||||
// but when we're in diff mode it could be any 'from' to any 'to'. The reverse flag is also here thanks to diff mode.
|
||||
func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool) (string, error) {
|
||||
return self.ShowFileDiffCmdObj(from, to, reverse, []string{fileName}, plain).RunWithOutput()
|
||||
// For a renamed file, previousPath is the path it was renamed from (empty otherwise);
|
||||
// both paths must be passed to git for the rename to be detected.
|
||||
func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bool, fileName string, previousPath string, plain bool) (string, error) {
|
||||
fileNames := []string{fileName}
|
||||
if previousPath != "" {
|
||||
fileNames = append(fileNames, previousPath)
|
||||
}
|
||||
return self.ShowFileDiffCmdObj(from, to, reverse, fileNames, plain).RunWithOutput()
|
||||
}
|
||||
|
||||
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileNames []string, plain bool) *oscommands.CmdObj {
|
||||
@@ -454,7 +460,7 @@ func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reve
|
||||
ArgIfElse(useExtDiff || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff").
|
||||
Arg("--submodule").
|
||||
Arg(fmt.Sprintf("--unified=%d", contextSize)).
|
||||
Arg("--no-renames").
|
||||
Arg(fmt.Sprintf("--find-renames=%d%%", self.UserConfig().Git.RenameSimilarityThreshold)).
|
||||
Arg(fmt.Sprintf("--color=%s", colorArg)).
|
||||
Arg(from).
|
||||
Arg(to).
|
||||
|
||||
@@ -339,6 +339,8 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
|
||||
from string
|
||||
to string
|
||||
reverse bool
|
||||
fileName string
|
||||
previousPath string
|
||||
plain bool
|
||||
ignoreWhitespace bool
|
||||
contextSize uint64
|
||||
@@ -353,33 +355,49 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
|
||||
from: "1234567890",
|
||||
to: "0987654321",
|
||||
reverse: false,
|
||||
fileName: "test.txt",
|
||||
plain: false,
|
||||
ignoreWhitespace: false,
|
||||
contextSize: 3,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
|
||||
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--find-renames=50%", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
|
||||
},
|
||||
{
|
||||
testName: "Show diff with custom context size",
|
||||
from: "1234567890",
|
||||
to: "0987654321",
|
||||
reverse: false,
|
||||
fileName: "test.txt",
|
||||
plain: false,
|
||||
ignoreWhitespace: false,
|
||||
contextSize: 123,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--submodule", "--unified=123", "--no-renames", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
|
||||
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--submodule", "--unified=123", "--find-renames=50%", "--color=always", "1234567890", "0987654321", "--", "test.txt"}, expectedResult, nil),
|
||||
},
|
||||
{
|
||||
testName: "Default case (ignore whitespace)",
|
||||
from: "1234567890",
|
||||
to: "0987654321",
|
||||
reverse: false,
|
||||
fileName: "test.txt",
|
||||
plain: false,
|
||||
ignoreWhitespace: true,
|
||||
contextSize: 3,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--no-renames", "--color=always", "1234567890", "0987654321", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
|
||||
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--find-renames=50%", "--color=always", "1234567890", "0987654321", "--ignore-all-space", "--", "test.txt"}, expectedResult, nil),
|
||||
},
|
||||
{
|
||||
testName: "Renamed file passes both paths so the rename is detected",
|
||||
from: "1234567890",
|
||||
to: "0987654321",
|
||||
reverse: false,
|
||||
fileName: "new.txt",
|
||||
previousPath: "old.txt",
|
||||
plain: false,
|
||||
ignoreWhitespace: false,
|
||||
contextSize: 3,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"-C", "/path/to/worktree", "-c", "diff.noprefix=false", "diff", "--no-ext-diff", "--submodule", "--unified=3", "--find-renames=50%", "--color=always", "1234567890", "0987654321", "--", "new.txt", "old.txt"}, expectedResult, nil),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -394,7 +412,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
|
||||
|
||||
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, userConfig: userConfig, appState: &config.AppState{}, repoPaths: &repoPaths})
|
||||
|
||||
result, err := instance.ShowFileDiff(s.from, s.to, s.reverse, "test.txt", s.plain)
|
||||
result, err := instance.ShowFileDiff(s.from, s.to, s.reverse, s.fileName, s.previousPath, s.plain)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expectedResult, result)
|
||||
s.runner.CheckForMissingCalls()
|
||||
|
||||
@@ -39,26 +39,9 @@ type Branch struct {
|
||||
// How far we have fallen behind our base branch. 0 means either not
|
||||
// determined yet, or up to date with base branch. (We don't need to
|
||||
// distinguish the two, as we don't draw anything in both cases.)
|
||||
// Negative values are sentinels for the ambiguous case where we can't
|
||||
// pick a single base; see BehindBase* constants below.
|
||||
BehindBaseBranch atomic.Int32
|
||||
}
|
||||
|
||||
// Sentinel values stored in Branch.BehindBaseBranch when the base branch
|
||||
// is ambiguous (more than one configured main branch tied at the closest
|
||||
// position) and the candidates disagree on the behind count.
|
||||
const (
|
||||
// BehindBaseAmbiguousMaybeUpToDate means at least one candidate has
|
||||
// the branch up to date and at least one has it behind; we don't
|
||||
// know which. Rendered as "?".
|
||||
BehindBaseAmbiguousMaybeUpToDate int32 = -1
|
||||
|
||||
// BehindBaseAmbiguousDefinitelyBehind means every candidate has the
|
||||
// branch behind by a non-zero amount, but the amounts differ — so we
|
||||
// know the branch is behind, just not by how much. Rendered as "↓?".
|
||||
BehindBaseAmbiguousDefinitelyBehind int32 = -2
|
||||
)
|
||||
|
||||
func (b *Branch) FullRefName() string {
|
||||
if b.DetachedHead {
|
||||
return b.Name
|
||||
|
||||
@@ -4,6 +4,9 @@ package models
|
||||
type CommitFile struct {
|
||||
Path string
|
||||
|
||||
// For a renamed file, the path it was renamed from; empty otherwise.
|
||||
PreviousPath string
|
||||
|
||||
ChangeStatus string // e.g. 'A' for added or 'M' for modified. This is based on the result from git diff --name-status
|
||||
}
|
||||
|
||||
@@ -23,6 +26,24 @@ func (f *CommitFile) Deleted() bool {
|
||||
return f.ChangeStatus == "D"
|
||||
}
|
||||
|
||||
func (f *CommitFile) IsRename() bool {
|
||||
return f.PreviousPath != ""
|
||||
}
|
||||
|
||||
// Names returns an array containing just the path, or in the case of a rename,
|
||||
// the after path and the before path.
|
||||
func (f *CommitFile) Names() []string {
|
||||
result := []string{f.Path}
|
||||
if f.PreviousPath != "" {
|
||||
result = append(result, f.PreviousPath)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (f *CommitFile) GetPath() string {
|
||||
return f.Path
|
||||
}
|
||||
|
||||
func (f *CommitFile) GetPreviousPath() string {
|
||||
return f.PreviousPath
|
||||
}
|
||||
|
||||
@@ -25,10 +25,14 @@ type fileInfo struct {
|
||||
mode PatchStatus
|
||||
includedLineIndices []int
|
||||
diff string
|
||||
// For a renamed file, the path it was renamed from; empty otherwise. We
|
||||
// need to keep hold of it so we can re-render the file's patch (which is
|
||||
// keyed by the new path) without the caller having to supply it again.
|
||||
previousPath string
|
||||
}
|
||||
|
||||
type (
|
||||
loadFileDiffFunc func(from string, to string, reverse bool, filename string, plain bool) (string, error)
|
||||
loadFileDiffFunc func(from string, to string, reverse bool, filename string, previousPath string, plain bool) (string, error)
|
||||
)
|
||||
|
||||
// PatchBuilder manages the building of a patch for a commit to be applied to another commit (or the working tree, or removed from the current commit). We also support building patches from things like stashes, for which there is less flexibility
|
||||
@@ -75,6 +79,7 @@ func (p *PatchBuilder) PatchToApply(reverse bool, turnAddedFilesIntoDiffAgainstE
|
||||
|
||||
patch.WriteString(p.RenderPatchForFile(RenderPatchForFileOpts{
|
||||
Filename: filename,
|
||||
PreviousPath: info.previousPath,
|
||||
Plain: true,
|
||||
Reverse: reverse,
|
||||
TurnAddedFilesIntoDiffAgainstEmptyFile: turnAddedFilesIntoDiffAgainstEmptyFile,
|
||||
@@ -102,8 +107,8 @@ func (p *PatchBuilder) removeFile(info *fileInfo) {
|
||||
info.includedLineIndices = nil
|
||||
}
|
||||
|
||||
func (p *PatchBuilder) AddFileWhole(filename string) error {
|
||||
info, err := p.getFileInfo(filename)
|
||||
func (p *PatchBuilder) AddFileWhole(filename string, previousPath string) error {
|
||||
info, err := p.getFileInfo(filename, previousPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -113,8 +118,8 @@ func (p *PatchBuilder) AddFileWhole(filename string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PatchBuilder) RemoveFile(filename string) error {
|
||||
info, err := p.getFileInfo(filename)
|
||||
func (p *PatchBuilder) RemoveFile(filename string, previousPath string) error {
|
||||
info, err := p.getFileInfo(filename, previousPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -124,19 +129,20 @@ func (p *PatchBuilder) RemoveFile(filename string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PatchBuilder) getFileInfo(filename string) (*fileInfo, error) {
|
||||
func (p *PatchBuilder) getFileInfo(filename string, previousPath string) (*fileInfo, error) {
|
||||
info, ok := p.fileInfoMap[filename]
|
||||
if ok {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
diff, err := p.loadFileDiff(p.From, p.To, p.reverse, filename, true)
|
||||
diff, err := p.loadFileDiff(p.From, p.To, p.reverse, filename, previousPath, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info = &fileInfo{
|
||||
mode: UNSELECTED,
|
||||
diff: diff,
|
||||
mode: UNSELECTED,
|
||||
diff: diff,
|
||||
previousPath: previousPath,
|
||||
}
|
||||
|
||||
p.fileInfoMap[filename] = info
|
||||
@@ -144,8 +150,8 @@ func (p *PatchBuilder) getFileInfo(filename string) (*fileInfo, error) {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (p *PatchBuilder) AddFileLineRange(filename string, lineIndices []int) error {
|
||||
info, err := p.getFileInfo(filename)
|
||||
func (p *PatchBuilder) AddFileLineRange(filename string, previousPath string, lineIndices []int) error {
|
||||
info, err := p.getFileInfo(filename, previousPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -155,8 +161,8 @@ func (p *PatchBuilder) AddFileLineRange(filename string, lineIndices []int) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PatchBuilder) RemoveFileLineRange(filename string, lineIndices []int) error {
|
||||
info, err := p.getFileInfo(filename)
|
||||
func (p *PatchBuilder) RemoveFileLineRange(filename string, previousPath string, lineIndices []int) error {
|
||||
info, err := p.getFileInfo(filename, previousPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -171,13 +177,14 @@ func (p *PatchBuilder) RemoveFileLineRange(filename string, lineIndices []int) e
|
||||
|
||||
type RenderPatchForFileOpts struct {
|
||||
Filename string
|
||||
PreviousPath string
|
||||
Plain bool
|
||||
Reverse bool
|
||||
TurnAddedFilesIntoDiffAgainstEmptyFile bool
|
||||
}
|
||||
|
||||
func (p *PatchBuilder) RenderPatchForFile(opts RenderPatchForFileOpts) string {
|
||||
info, err := p.getFileInfo(opts.Filename)
|
||||
info, err := p.getFileInfo(opts.Filename, opts.PreviousPath)
|
||||
if err != nil {
|
||||
p.Log.Error(err)
|
||||
return ""
|
||||
@@ -198,7 +205,12 @@ func (p *PatchBuilder) RenderPatchForFile(opts RenderPatchForFileOpts) string {
|
||||
Transform(TransformOpts{
|
||||
Reverse: opts.Reverse,
|
||||
TurnAddedFilesIntoDiffAgainstEmptyFile: opts.TurnAddedFilesIntoDiffAgainstEmptyFile,
|
||||
IncludedLineIndices: info.includedLineIndices,
|
||||
// For a partial selection of a renamed file we keep only the
|
||||
// content change and drop the rename, so that the rename stays in
|
||||
// the commit. A whole-file selection keeps the rename (and short-
|
||||
// circuits before this for plain output).
|
||||
StripRename: info.mode == PART && info.previousPath != "",
|
||||
IncludedLineIndices: info.includedLineIndices,
|
||||
})
|
||||
|
||||
if opts.Plain {
|
||||
@@ -215,6 +227,7 @@ func (p *PatchBuilder) renderEachFilePatch(plain bool) []string {
|
||||
patches := lo.Map(filenames, func(filename string, _ int) string {
|
||||
return p.RenderPatchForFile(RenderPatchForFileOpts{
|
||||
Filename: filename,
|
||||
PreviousPath: p.fileInfoMap[filename].previousPath,
|
||||
Plain: plain,
|
||||
Reverse: false,
|
||||
TurnAddedFilesIntoDiffAgainstEmptyFile: true,
|
||||
@@ -244,8 +257,8 @@ func (p *PatchBuilder) GetFileStatus(filename string, parent string) PatchStatus
|
||||
return info.mode
|
||||
}
|
||||
|
||||
func (p *PatchBuilder) GetFileIncLineIndices(filename string) ([]int, error) {
|
||||
info, err := p.getFileInfo(filename)
|
||||
func (p *PatchBuilder) GetFileIncLineIndices(filename string, previousPath string) ([]int, error) {
|
||||
info, err := p.getFileInfo(filename, previousPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -19,6 +19,22 @@ index dcd3485..1ba5540 100644
|
||||
...
|
||||
`
|
||||
|
||||
const renameWithModificationDiff = `diff --git a/oldname b/newname
|
||||
similarity index 62%
|
||||
rename from oldname
|
||||
rename to newname
|
||||
index dcd3485..1ba5540 100644
|
||||
--- a/oldname
|
||||
+++ b/newname
|
||||
@@ -1,5 +1,5 @@
|
||||
apple
|
||||
-orange
|
||||
+grape
|
||||
...
|
||||
...
|
||||
...
|
||||
`
|
||||
|
||||
const addNewlineToEndOfFile = `diff --git a/filename b/filename
|
||||
index 80a73f1..e48a11c 100644
|
||||
--- a/filename
|
||||
@@ -152,6 +168,7 @@ func TestTransform(t *testing.T) {
|
||||
firstLineIndex int
|
||||
lastLineIndex int
|
||||
reverse bool
|
||||
stripRename bool
|
||||
expected string
|
||||
}
|
||||
|
||||
@@ -515,6 +532,43 @@ func TestTransform(t *testing.T) {
|
||||
orange
|
||||
banana
|
||||
lemon
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "renamed file, whole change selected, strips the rename so only the content change is applied",
|
||||
firstLineIndex: 9,
|
||||
lastLineIndex: 10,
|
||||
stripRename: true,
|
||||
diffText: renameWithModificationDiff,
|
||||
expected: `diff --git a/newname b/newname
|
||||
index dcd3485..1ba5540 100644
|
||||
--- a/newname
|
||||
+++ b/newname
|
||||
@@ -1,5 +1,5 @@
|
||||
apple
|
||||
-orange
|
||||
+grape
|
||||
...
|
||||
...
|
||||
...
|
||||
`,
|
||||
},
|
||||
{
|
||||
testName: "renamed file, only removal selected, strips the rename",
|
||||
firstLineIndex: 9,
|
||||
lastLineIndex: 9,
|
||||
stripRename: true,
|
||||
diffText: renameWithModificationDiff,
|
||||
expected: `diff --git a/newname b/newname
|
||||
index dcd3485..1ba5540 100644
|
||||
--- a/newname
|
||||
+++ b/newname
|
||||
@@ -1,5 +1,4 @@
|
||||
apple
|
||||
-orange
|
||||
...
|
||||
...
|
||||
...
|
||||
`,
|
||||
},
|
||||
}
|
||||
@@ -527,6 +581,7 @@ func TestTransform(t *testing.T) {
|
||||
Transform(TransformOpts{
|
||||
Reverse: s.reverse,
|
||||
FileNameOverride: s.filename,
|
||||
StripRename: s.stripRename,
|
||||
IncludedLineIndices: lineIndices,
|
||||
}).
|
||||
FormatPlain()
|
||||
|
||||
@@ -33,6 +33,13 @@ type TransformOpts struct {
|
||||
// treat it as a diff against an empty file.
|
||||
TurnAddedFilesIntoDiffAgainstEmptyFile bool
|
||||
|
||||
// When building a partial patch for a renamed file, strip the rename
|
||||
// metadata from the header and point it at the new path. Applying the
|
||||
// resulting patch then only changes the file's contents and leaves the
|
||||
// rename itself in place. (For a whole-file selection we keep the rename
|
||||
// so that it moves or is discarded together with the contents.)
|
||||
StripRename bool
|
||||
|
||||
// The indices of lines that should be included in the patch.
|
||||
IncludedLineIndices []int
|
||||
}
|
||||
@@ -72,21 +79,61 @@ func (self *patchTransformer) transformHeader() []string {
|
||||
"--- a/" + self.opts.FileNameOverride,
|
||||
"+++ b/" + self.opts.FileNameOverride,
|
||||
}
|
||||
} else if self.opts.TurnAddedFilesIntoDiffAgainstEmptyFile {
|
||||
result := make([]string, 0, len(self.patch.header))
|
||||
for idx, line := range self.patch.header {
|
||||
}
|
||||
|
||||
header := self.patch.header
|
||||
if self.opts.StripRename {
|
||||
header = stripRenameFromHeader(header)
|
||||
}
|
||||
|
||||
if self.opts.TurnAddedFilesIntoDiffAgainstEmptyFile {
|
||||
result := make([]string, 0, len(header))
|
||||
for idx, line := range header {
|
||||
if strings.HasPrefix(line, "new file mode") {
|
||||
continue
|
||||
}
|
||||
if line == "--- /dev/null" && strings.HasPrefix(self.patch.header[idx+1], "+++ b/") {
|
||||
line = "--- a/" + self.patch.header[idx+1][6:]
|
||||
if line == "--- /dev/null" && strings.HasPrefix(header[idx+1], "+++ b/") {
|
||||
line = "--- a/" + header[idx+1][6:]
|
||||
}
|
||||
result = append(result, line)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
return self.patch.header
|
||||
return header
|
||||
}
|
||||
|
||||
// stripRenameFromHeader rewrites a rename diff header so that it looks like a
|
||||
// plain modification of the new path: it drops the rename metadata and points
|
||||
// the diff at the new path on both sides, while keeping the blob index line so
|
||||
// that `git apply --3way` can still fall back to a blob merge. See the
|
||||
// StripRename option for why we do this.
|
||||
func stripRenameFromHeader(header []string) []string {
|
||||
newPath := ""
|
||||
for _, line := range header {
|
||||
if path, ok := strings.CutPrefix(line, "+++ b/"); ok {
|
||||
newPath = path
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
result := make([]string, 0, len(header))
|
||||
for _, line := range header {
|
||||
switch {
|
||||
case strings.HasPrefix(line, "similarity index "),
|
||||
strings.HasPrefix(line, "dissimilarity index "),
|
||||
strings.HasPrefix(line, "rename from "),
|
||||
strings.HasPrefix(line, "rename to "):
|
||||
// drop the rename metadata
|
||||
case strings.HasPrefix(line, "diff --git "):
|
||||
result = append(result, "diff --git a/"+newPath+" b/"+newPath)
|
||||
case strings.HasPrefix(line, "--- "):
|
||||
result = append(result, "--- a/"+newPath)
|
||||
default:
|
||||
result = append(result, line)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (self *patchTransformer) transformHunks() []*Hunk {
|
||||
|
||||
@@ -749,7 +749,7 @@ git:
|
||||
# The commit message to use for a squash merge commit. Can contain "{{selectedRef}}" and "{{currentBranch}}" placeholders.
|
||||
squashMergeMessage: Squash merge {{selectedRef}} into {{currentBranch}}
|
||||
|
||||
# list of branches that are considered 'main' branches, used when displaying commits and for determining each branch's base. Lazygit prompts when a branch could be based on more than one of these (typical when one is regularly merged into another).
|
||||
# list of branches that are considered 'main' branches, used when displaying commits
|
||||
mainBranches:
|
||||
- master
|
||||
- main
|
||||
|
||||
@@ -91,6 +91,7 @@ type GuiConfig struct {
|
||||
TabWidth int `yaml:"tabWidth" jsonschema:"minimum=1"`
|
||||
// If true, capture mouse events.
|
||||
// When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.
|
||||
// Mouse capture can also be toggled at runtime with the 'toggleMouseCapture' keybinding ('%' by default).
|
||||
MouseEvents bool `yaml:"mouseEvents"`
|
||||
// If true, do not show a warning when amending a commit.
|
||||
SkipAmendWarning bool `yaml:"skipAmendWarning"`
|
||||
@@ -304,7 +305,7 @@ type GitConfig struct {
|
||||
Commit CommitConfig `yaml:"commit"`
|
||||
// Config relating to merging
|
||||
Merging MergingConfig `yaml:"merging"`
|
||||
// list of branches that are considered 'main' branches, used when displaying commits and for determining each branch's base. Lazygit prompts when a branch could be based on more than one of these (typical when one is regularly merged into another).
|
||||
// list of branches that are considered 'main' branches, used when displaying commits
|
||||
MainBranches []string `yaml:"mainBranches" jsonschema:"uniqueItems=true"`
|
||||
// Prefix to use when skipping hooks. E.g. if set to 'WIP', then pre-commit hooks will be skipped when the commit message starts with 'WIP'
|
||||
SkipHookPrefix string `yaml:"skipHookPrefix"`
|
||||
@@ -548,6 +549,7 @@ type KeybindingUniversalConfig struct {
|
||||
SubmitEditorText Keybinding `yaml:"submitEditorText"`
|
||||
ExtrasMenu Keybinding `yaml:"extrasMenu"`
|
||||
ToggleWhitespaceInDiffView Keybinding `yaml:"toggleWhitespaceInDiffView"`
|
||||
ToggleMouseCapture Keybinding `yaml:"toggleMouseCapture"`
|
||||
IncreaseContextInDiffView Keybinding `yaml:"increaseContextInDiffView"`
|
||||
DecreaseContextInDiffView Keybinding `yaml:"decreaseContextInDiffView"`
|
||||
IncreaseRenameSimilarityThreshold Keybinding `yaml:"increaseRenameSimilarityThreshold"`
|
||||
@@ -1066,6 +1068,7 @@ func GetDefaultConfigForPlatform(platform string) *UserConfig {
|
||||
SubmitEditorText: Keybinding{"<enter>"},
|
||||
ExtrasMenu: Keybinding{"@"},
|
||||
ToggleWhitespaceInDiffView: Keybinding{"<ctrl+w>"},
|
||||
ToggleMouseCapture: Keybinding{"%"},
|
||||
IncreaseContextInDiffView: Keybinding{"}"},
|
||||
DecreaseContextInDiffView: Keybinding{"{"},
|
||||
IncreaseRenameSimilarityThreshold: Keybinding{")"},
|
||||
|
||||
+115
-6
@@ -9,11 +9,13 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gdamore/tcell/v3"
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/petermattis/goid"
|
||||
"github.com/rivo/uniseg"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
@@ -193,7 +195,18 @@ type Gui struct {
|
||||
|
||||
taskManager *TaskManager
|
||||
|
||||
// The task of the event currently being processed on the main goroutine, if
|
||||
// any. Only touched from the main goroutine (in processEvent). It's excluded
|
||||
// from the Busy() check so that an event handler asking "is anything else
|
||||
// busy?" doesn't count itself.
|
||||
currentTask Task
|
||||
|
||||
lastHoverView *View
|
||||
|
||||
// uiThreadID is the goroutine id of the main event loop, recorded when
|
||||
// MainLoop starts. IsUIThread compares against it. Written once, read from
|
||||
// worker goroutines, so it's atomic.
|
||||
uiThreadID atomic.Int64
|
||||
}
|
||||
|
||||
type NewGuiOpts struct {
|
||||
@@ -273,7 +286,22 @@ func NewGui(opts NewGuiOpts) (*Gui, error) {
|
||||
}
|
||||
|
||||
func (g *Gui) NewTask() *TaskImpl {
|
||||
return g.taskManager.NewTask()
|
||||
return g.taskManager.NewTask(false)
|
||||
}
|
||||
|
||||
// NewBackgroundTask creates a task that is tracked for idle detection but does
|
||||
// not count towards the program being busy for repo-switch safety. See
|
||||
// TaskImpl.background.
|
||||
func (g *Gui) NewBackgroundTask() *TaskImpl {
|
||||
return g.taskManager.NewTask(true)
|
||||
}
|
||||
|
||||
// Busy reports whether any foreground work is in flight, ignoring the event
|
||||
// currently being processed on the main goroutine (see currentTask). Background
|
||||
// routines (auto-fetch etc.) don't count. It's used to decide whether it's safe
|
||||
// to switch repos. Must be called on the main goroutine.
|
||||
func (g *Gui) Busy() bool {
|
||||
return g.taskManager.hasBusyForegroundTaskExcept(g.currentTask)
|
||||
}
|
||||
|
||||
// An idle listener listens for when the program is idle. This is useful for
|
||||
@@ -628,7 +656,18 @@ type userEvent struct {
|
||||
// never fire in practice; if it does, that's a signal to investigate, not
|
||||
// to grow the buffer reflexively.
|
||||
func (g *Gui) Update(f func(*Gui) error) {
|
||||
task := g.NewTask()
|
||||
g.update(f, false)
|
||||
}
|
||||
|
||||
// Like Update, but the enqueued work is a background routine (or triggered by
|
||||
// one), so it doesn't count towards the program being busy for repo-switch
|
||||
// safety. See TaskImpl.background.
|
||||
func (g *Gui) UpdateBackground(f func(*Gui) error) {
|
||||
g.update(f, true)
|
||||
}
|
||||
|
||||
func (g *Gui) update(f func(*Gui) error, background bool) {
|
||||
task := g.taskManager.NewTask(background)
|
||||
|
||||
select {
|
||||
case g.userEvents <- userEvent{f: f, task: task}:
|
||||
@@ -639,10 +678,59 @@ func (g *Gui) Update(f func(*Gui) error) {
|
||||
|
||||
// Like Update, but signals that the callback only modifies content.
|
||||
func (g *Gui) UpdateContentOnly(f func(*Gui) error) {
|
||||
task := g.NewTask()
|
||||
g.updateContentOnly(f, false)
|
||||
}
|
||||
|
||||
// Like UpdateContentOnly, but for background work (see UpdateBackground).
|
||||
func (g *Gui) UpdateContentOnlyBackground(f func(*Gui) error) {
|
||||
g.updateContentOnly(f, true)
|
||||
}
|
||||
|
||||
func (g *Gui) updateContentOnly(f func(*Gui) error, background bool) {
|
||||
task := g.taskManager.NewTask(background)
|
||||
g.userEvents <- userEvent{f: f, task: task, contentOnly: true}
|
||||
}
|
||||
|
||||
// IsUIThread reports whether the caller is running on the main event-loop
|
||||
// goroutine (the one running MainLoop). It calls goid.Get, so use it only for
|
||||
// debug assertions, not to drive production control flow.
|
||||
func (g *Gui) IsUIThread() bool {
|
||||
return goid.Get() == g.uiThreadID.Load()
|
||||
}
|
||||
|
||||
// OnUIThreadAndWait runs f on the main event-loop goroutine and blocks the
|
||||
// caller until f has run, returning f's error. Use it to read UI-thread-owned
|
||||
// state (the model, contexts) from a worker without racing the UI thread.
|
||||
//
|
||||
// It must be called from a worker goroutine, never from the UI thread itself:
|
||||
// the UI thread would block waiting for a callback only it can run, which
|
||||
// deadlocks. Callers arrange this by construction (see the refresh helper's
|
||||
// RefreshFromWorker); a debug-only assertion there guards against getting it
|
||||
// wrong.
|
||||
func (g *Gui) OnUIThreadAndWait(f func() error) error {
|
||||
return g.onUIThreadAndWait(f, false)
|
||||
}
|
||||
|
||||
// Like OnUIThreadAndWait, but the enqueued work belongs to a background routine,
|
||||
// so it doesn't count towards the program being busy (see UpdateBackground).
|
||||
func (g *Gui) OnUIThreadAndWaitBackground(f func() error) error {
|
||||
return g.onUIThreadAndWait(f, true)
|
||||
}
|
||||
|
||||
func (g *Gui) onUIThreadAndWait(f func() error, background bool) error {
|
||||
enqueue := g.Update
|
||||
if background {
|
||||
enqueue = g.UpdateBackground
|
||||
}
|
||||
|
||||
result := make(chan error, 1)
|
||||
enqueue(func(*Gui) error {
|
||||
result <- f()
|
||||
return nil
|
||||
})
|
||||
return <-result
|
||||
}
|
||||
|
||||
// Calls a function in a goroutine. Handles panics gracefully and tracks
|
||||
// number of background tasks.
|
||||
// Always use this when you want to spawn a goroutine and you want lazygit to
|
||||
@@ -650,7 +738,18 @@ func (g *Gui) UpdateContentOnly(f func(*Gui) error) {
|
||||
// background goroutines where you wouldn't want lazygit to be considered busy
|
||||
// (i.e. when you wouldn't want a loader to be shown to the user)
|
||||
func (g *Gui) OnWorker(f func(Task) error) {
|
||||
task := g.NewTask()
|
||||
g.onWorker(f, false)
|
||||
}
|
||||
|
||||
// Like OnWorker, but for a background routine (or work triggered by one), so it
|
||||
// doesn't count towards the program being busy for repo-switch safety. See
|
||||
// TaskImpl.background.
|
||||
func (g *Gui) OnWorkerBackground(f func(Task) error) {
|
||||
g.onWorker(f, true)
|
||||
}
|
||||
|
||||
func (g *Gui) onWorker(f func(Task) error, background bool) {
|
||||
task := g.taskManager.NewTask(background)
|
||||
go func() {
|
||||
g.onWorkerAux(f, task)
|
||||
task.Done()
|
||||
@@ -714,6 +813,8 @@ func (g *Gui) SetManagerFunc(manager func(*Gui) error) {
|
||||
// MainLoop runs the main loop until an error is returned. A successful
|
||||
// finish should return ErrQuit.
|
||||
func (g *Gui) MainLoop() error {
|
||||
g.uiThreadID.Store(goid.Get())
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
@@ -758,17 +859,25 @@ func (g *Gui) handleError(err error) error {
|
||||
func (g *Gui) processEvent() error {
|
||||
contentOnly := false
|
||||
|
||||
// currentTask is the task of the event we're about to handle; recording it
|
||||
// lets Busy() ignore it, so a handler asking "is anything else busy?" (the
|
||||
// repo-switch guard does) doesn't count itself. Handlers of the remaining
|
||||
// events drained below run with currentTask still set to this primary event;
|
||||
// that's fine because the only Busy() callers are keybinding handlers, which
|
||||
// are always the primary event here.
|
||||
select {
|
||||
case ev := <-g.gEvents:
|
||||
task := g.NewTask()
|
||||
defer func() { task.Done() }()
|
||||
g.currentTask = task
|
||||
defer func() { g.currentTask = nil; task.Done() }()
|
||||
|
||||
if err := g.handleError(g.handleEvent(&ev)); err != nil {
|
||||
return err
|
||||
}
|
||||
case ev := <-g.userEvents:
|
||||
contentOnly = ev.contentOnly
|
||||
defer func() { ev.task.Done() }()
|
||||
g.currentTask = ev.task
|
||||
defer func() { g.currentTask = nil; ev.task.Done() }()
|
||||
|
||||
if err := g.handleError(ev.f(g)); err != nil {
|
||||
return err
|
||||
|
||||
+21
-1
@@ -8,8 +8,9 @@ type Task interface {
|
||||
Done()
|
||||
Pause()
|
||||
Continue()
|
||||
// not exporting because we don't need to
|
||||
// not exporting these because we don't need to
|
||||
isBusy() bool
|
||||
isBackground() bool
|
||||
}
|
||||
|
||||
type TaskImpl struct {
|
||||
@@ -17,6 +18,17 @@ type TaskImpl struct {
|
||||
busy bool
|
||||
onDone func()
|
||||
withMutex func(func())
|
||||
// Background tasks don't count towards the program being "busy" for the
|
||||
// purpose of deciding whether a repo switch is safe (see
|
||||
// TaskManager.hasBusyForegroundTaskExcept). Two kinds of work are tagged
|
||||
// this way: the ongoing background routines (auto-fetch, files refresh,
|
||||
// external-change detection) and the refreshes they trigger, whose model
|
||||
// writes are already guarded against a concurrent repo switch by the repo
|
||||
// generation; and view-buffer content rendering, which only paints a view
|
||||
// and so is harmless to leave running across a switch. What stays
|
||||
// foreground is lazygit driving a git operation and applying its results
|
||||
// to the model — exactly the work a repo switch must not run underneath.
|
||||
background bool
|
||||
}
|
||||
|
||||
func (self *TaskImpl) Done() {
|
||||
@@ -39,6 +51,10 @@ func (self *TaskImpl) isBusy() bool {
|
||||
return self.busy
|
||||
}
|
||||
|
||||
func (self *TaskImpl) isBackground() bool {
|
||||
return self.background
|
||||
}
|
||||
|
||||
type TaskStatus int
|
||||
|
||||
const (
|
||||
@@ -73,6 +89,10 @@ func (self *FakeTask) isBusy() bool {
|
||||
return self.status == TaskStatusBusy
|
||||
}
|
||||
|
||||
func (self *FakeTask) isBackground() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *FakeTask) Status() TaskStatus {
|
||||
return self.status
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ func newTaskManager() *TaskManager {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TaskManager) NewTask() *TaskImpl {
|
||||
func (self *TaskManager) NewTask(background bool) *TaskImpl {
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
@@ -30,12 +30,34 @@ func (self *TaskManager) NewTask() *TaskImpl {
|
||||
taskId := self.nextId
|
||||
|
||||
onDone := func() { self.delete(taskId) }
|
||||
task := &TaskImpl{id: taskId, busy: true, onDone: onDone, withMutex: self.withMutex}
|
||||
task := &TaskImpl{id: taskId, busy: true, background: background, onDone: onDone, withMutex: self.withMutex}
|
||||
self.tasks[taskId] = task
|
||||
|
||||
return task
|
||||
}
|
||||
|
||||
// hasBusyForegroundTaskExcept reports whether any task other than `ignore` is
|
||||
// currently busy and not a background task. It's used to decide whether a repo
|
||||
// switch is safe: a foreground operation (or the refresh it triggers, or that
|
||||
// refresh's follow-up callbacks) still in flight means the switch must wait, so
|
||||
// it doesn't run against a repo that's about to be swapped out.
|
||||
//
|
||||
// `ignore` is the event currently being processed on the UI thread — the switch
|
||||
// attempt itself — which is always busy and so must not count as a reason to
|
||||
// refuse itself.
|
||||
func (self *TaskManager) hasBusyForegroundTaskExcept(ignore Task) bool {
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
for _, task := range self.tasks {
|
||||
if task != ignore && task.isBusy() && !task.isBackground() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *TaskManager) addIdleListener(c chan struct{}) {
|
||||
self.idleListeners = append(self.idleListeners, c)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
package gocui
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTaskManagerHasBusyForegroundTaskExcept(t *testing.T) {
|
||||
t.Run("no tasks", func(t *testing.T) {
|
||||
tm := newTaskManager()
|
||||
assert.False(t, tm.hasBusyForegroundTaskExcept(nil))
|
||||
})
|
||||
|
||||
t.Run("a busy foreground task counts", func(t *testing.T) {
|
||||
tm := newTaskManager()
|
||||
tm.NewTask(false)
|
||||
assert.True(t, tm.hasBusyForegroundTaskExcept(nil))
|
||||
})
|
||||
|
||||
t.Run("a busy background task does not count", func(t *testing.T) {
|
||||
tm := newTaskManager()
|
||||
tm.NewTask(true)
|
||||
assert.False(t, tm.hasBusyForegroundTaskExcept(nil))
|
||||
})
|
||||
|
||||
t.Run("a done foreground task does not count", func(t *testing.T) {
|
||||
tm := newTaskManager()
|
||||
task := tm.NewTask(false)
|
||||
task.Done()
|
||||
assert.False(t, tm.hasBusyForegroundTaskExcept(nil))
|
||||
})
|
||||
|
||||
t.Run("a paused foreground task does not count", func(t *testing.T) {
|
||||
tm := newTaskManager()
|
||||
task := tm.NewTask(false)
|
||||
task.Pause()
|
||||
assert.False(t, tm.hasBusyForegroundTaskExcept(nil))
|
||||
})
|
||||
|
||||
t.Run("the ignored task does not count", func(t *testing.T) {
|
||||
tm := newTaskManager()
|
||||
task := tm.NewTask(false)
|
||||
assert.False(t, tm.hasBusyForegroundTaskExcept(task))
|
||||
})
|
||||
|
||||
t.Run("another foreground task counts even when one is ignored", func(t *testing.T) {
|
||||
tm := newTaskManager()
|
||||
ignored := tm.NewTask(false)
|
||||
tm.NewTask(false)
|
||||
assert.True(t, tm.hasBusyForegroundTaskExcept(ignored))
|
||||
})
|
||||
|
||||
t.Run("only a background task alongside the ignored current event", func(t *testing.T) {
|
||||
// This is the repo-switch case: the switch is handled as the current
|
||||
// event (ignored) while a background refresh is in flight; it must not
|
||||
// be considered busy.
|
||||
tm := newTaskManager()
|
||||
current := tm.NewTask(false)
|
||||
tm.NewTask(true)
|
||||
assert.False(t, tm.hasBusyForegroundTaskExcept(current))
|
||||
})
|
||||
}
|
||||
+79
-41
@@ -50,6 +50,14 @@ type View struct {
|
||||
// tained is true if the viewLines must be updated
|
||||
tainted bool
|
||||
|
||||
// firstDirtyLine is the index of the lowest line in `lines` that has been
|
||||
// written to or highlighted since viewLines was last refreshed, and whose
|
||||
// cached wrapping (lineType.wrappedCells) may therefore be stale. Lines
|
||||
// below it are unchanged and can reuse their cached wrapping instead of
|
||||
// being re-wrapped, which keeps refreshViewLinesIfNeeded cheap while
|
||||
// scrolling appends new lines to a long buffer.
|
||||
firstDirtyLine int
|
||||
|
||||
// the last position that the mouse was hovering over; nil if the mouse is outside of
|
||||
// this view, or not hovering over a cell
|
||||
lastHoverPosition *pos
|
||||
@@ -457,6 +465,16 @@ type viewLine struct {
|
||||
type lineType struct {
|
||||
cells cells
|
||||
trailingFillAttributes *trailingFillAttributes
|
||||
|
||||
// wrappedCells caches the result of wrapping `cells` to `wrappedColumns`
|
||||
// columns, so that unchanged lines don't have to be re-wrapped on every
|
||||
// refreshViewLinesIfNeeded (which runs on every scroll event, via
|
||||
// ViewLinesHeight). Wrapping measures every cell's width and allocates, so
|
||||
// for a long buffer that dominates the cost of scrolling. The cache is used
|
||||
// only for lines below View.firstDirtyLine whose wrappedColumns still
|
||||
// matches the current width; nil means nothing is cached yet.
|
||||
wrappedCells [][]cell
|
||||
wrappedColumns int
|
||||
}
|
||||
|
||||
// trailingFillAttributes describes the fg/bg colors that draw() should
|
||||
@@ -815,6 +833,9 @@ func (v *View) Write(p []byte) (n int, err error) {
|
||||
|
||||
func (v *View) write(p []byte) {
|
||||
v.tainted = true
|
||||
// write only ever touches lines from v.wy onwards, so any cached wrapping
|
||||
// below that stays valid.
|
||||
v.firstDirtyLine = min(v.firstDirtyLine, v.wy)
|
||||
v.clearHover()
|
||||
|
||||
// Fill with empty cells, if writing outside current view buffer
|
||||
@@ -1358,48 +1379,64 @@ func (v *View) draw() {
|
||||
}
|
||||
|
||||
func (v *View) refreshViewLinesIfNeeded() {
|
||||
if v.tainted {
|
||||
maxX := v.InnerWidth()
|
||||
lineIdx := 0
|
||||
lines := v.lines
|
||||
for i, line := range lines {
|
||||
wrap := 0
|
||||
if v.Wrap {
|
||||
wrap = maxX
|
||||
}
|
||||
|
||||
ls := lineWrap(line.cells, wrap)
|
||||
for j := range ls {
|
||||
// Per-segment trailing fill. When the source line opted in
|
||||
// via '\x1b[K', the LAST wrapped segment uses those colors
|
||||
// directly; earlier segments use the colors of their own
|
||||
// last cell, so the trailing area matches the bg active
|
||||
// where that segment ended rather than bleeding the
|
||||
// '\x1b[K' bg back across color changes in the line.
|
||||
var attrs *trailingFillAttributes
|
||||
if line.trailingFillAttributes != nil {
|
||||
if j == len(ls)-1 {
|
||||
attrs = line.trailingFillAttributes
|
||||
} else if len(ls[j]) > 0 {
|
||||
last := ls[j][len(ls[j])-1]
|
||||
attrs = &trailingFillAttributes{fg: last.fgColor, bg: last.bgColor}
|
||||
}
|
||||
}
|
||||
vline := viewLine{
|
||||
linesX: j, linesY: i, line: ls[j],
|
||||
trailingFillAttributes: attrs,
|
||||
}
|
||||
|
||||
if lineIdx > len(v.viewLines)-1 {
|
||||
v.viewLines = append(v.viewLines, vline)
|
||||
} else {
|
||||
v.viewLines[lineIdx] = vline
|
||||
}
|
||||
lineIdx++
|
||||
}
|
||||
}
|
||||
v.tainted = false
|
||||
if !v.tainted {
|
||||
return
|
||||
}
|
||||
|
||||
maxX := v.InnerWidth()
|
||||
wrap := 0
|
||||
if v.Wrap {
|
||||
wrap = maxX
|
||||
}
|
||||
|
||||
lineIdx := 0
|
||||
lines := v.lines
|
||||
for i := range lines {
|
||||
line := &lines[i]
|
||||
|
||||
// Reuse the previously wrapped result for lines that haven't changed
|
||||
// since the last refresh (i.e. below firstDirtyLine) and were wrapped at
|
||||
// the current width. Wrapping is expensive and this loop runs on every
|
||||
// scroll event, so only the lines that were actually just read (or
|
||||
// re-highlighted) should be wrapped afresh.
|
||||
if line.wrappedCells == nil || line.wrappedColumns != wrap || i >= v.firstDirtyLine {
|
||||
line.wrappedCells = lineWrap(line.cells, wrap)
|
||||
line.wrappedColumns = wrap
|
||||
}
|
||||
ls := line.wrappedCells
|
||||
|
||||
for j := range ls {
|
||||
// Per-segment trailing fill. When the source line opted in
|
||||
// via '\x1b[K', the LAST wrapped segment uses those colors
|
||||
// directly; earlier segments use the colors of their own
|
||||
// last cell, so the trailing area matches the bg active
|
||||
// where that segment ended rather than bleeding the
|
||||
// '\x1b[K' bg back across color changes in the line.
|
||||
var attrs *trailingFillAttributes
|
||||
if line.trailingFillAttributes != nil {
|
||||
if j == len(ls)-1 {
|
||||
attrs = line.trailingFillAttributes
|
||||
} else if len(ls[j]) > 0 {
|
||||
last := ls[j][len(ls[j])-1]
|
||||
attrs = &trailingFillAttributes{fg: last.fgColor, bg: last.bgColor}
|
||||
}
|
||||
}
|
||||
vline := viewLine{
|
||||
linesX: j, linesY: i, line: ls[j],
|
||||
trailingFillAttributes: attrs,
|
||||
}
|
||||
|
||||
if lineIdx > len(v.viewLines)-1 {
|
||||
v.viewLines = append(v.viewLines, vline)
|
||||
} else {
|
||||
v.viewLines[lineIdx] = vline
|
||||
}
|
||||
lineIdx++
|
||||
}
|
||||
}
|
||||
|
||||
v.firstDirtyLine = len(lines)
|
||||
v.tainted = false
|
||||
}
|
||||
|
||||
// if autoscroll is enabled but we only have a single row of cells shown to the
|
||||
@@ -1599,6 +1636,7 @@ func (v *View) SetHighlight(y int, on bool) {
|
||||
cells = append(cells, c)
|
||||
}
|
||||
v.tainted = true
|
||||
v.firstDirtyLine = min(v.firstDirtyLine, y)
|
||||
v.lines[y].cells = cells
|
||||
v.clearHover()
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ func (self *BackgroundRoutineMgr) startBackgroundFetch() {
|
||||
if self.gui.UserConfig().Gui.ShowBottomLine || firstTimeOrRetriggered {
|
||||
return self.gui.helpers.AppStatus.WithWaitingStatusImpl(self.gui.Tr.FetchingStatus, func(gocui.Task) error {
|
||||
return self.backgroundFetch()
|
||||
}, nil)
|
||||
}, nil, true)
|
||||
}
|
||||
|
||||
return self.backgroundFetch()
|
||||
@@ -133,7 +133,7 @@ func (self *BackgroundRoutineMgr) startBackgroundFilesRefresh() {
|
||||
|
||||
userConfig := self.gui.UserConfig()
|
||||
self.goEvery(userConfig.Refresher.RefreshIntervalDuration(), self.gui.stopChan, func(_ bool) error {
|
||||
self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Background: true})
|
||||
self.gui.c.RefreshFromWorker(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Background: true})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -184,7 +184,7 @@ func (self *BackgroundRoutineMgr) checkForExternalChanges() {
|
||||
|
||||
// No need to update the stored snapshot here; Refresh does that.
|
||||
self.gui.c.Log.Info("External ref change detected — refreshing")
|
||||
self.gui.c.Refresh(types.RefreshOptions{Background: true})
|
||||
self.gui.c.RefreshFromWorker(types.RefreshOptions{Background: true})
|
||||
}
|
||||
|
||||
// returns a channel that can be used to trigger the callback immediately
|
||||
@@ -198,7 +198,10 @@ func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan stru
|
||||
if self.backgroundRefreshesPaused() {
|
||||
return
|
||||
}
|
||||
self.gui.c.OnWorker(func(gocui.Task) error {
|
||||
// OnWorkerBackground, not OnWorker: these routines and the refreshes
|
||||
// they trigger must not count towards lazygit being busy, or they'd
|
||||
// spuriously block a repo switch every time one happens to be running.
|
||||
self.gui.c.OnWorkerBackground(func(gocui.Task) error {
|
||||
_ = function(retriggered)
|
||||
done <- struct{}{}
|
||||
return nil
|
||||
|
||||
@@ -27,10 +27,20 @@ func (gui *Gui) LogAction(action string) {
|
||||
return
|
||||
}
|
||||
|
||||
gui.Views.Extras.Autoscroll = true
|
||||
// LogAction and LogCommand are called both from the UI thread and from git
|
||||
// worker goroutines, so bounce the writes onto the UI thread: they touch the
|
||||
// view's autoscroll flag and the GuiLog slice, which the layout/draw code
|
||||
// reads. Ordering between successive log calls is preserved by the FIFO the
|
||||
// bounce enqueues onto. It's a background bounce because writing the command
|
||||
// log is incidental display work that must not count towards lazygit being
|
||||
// busy (otherwise it could block a repo switch).
|
||||
gui.onUIThreadBackground(func() error {
|
||||
gui.Views.Extras.Autoscroll = true
|
||||
|
||||
gui.GuiLog = append(gui.GuiLog, action)
|
||||
fmt.Fprint(gui.Views.Extras, "\n"+style.FgYellow.Sprint(action))
|
||||
gui.GuiLog = append(gui.GuiLog, action)
|
||||
fmt.Fprint(gui.Views.Extras, "\n"+style.FgYellow.Sprint(action))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
|
||||
@@ -38,17 +48,23 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
|
||||
return
|
||||
}
|
||||
|
||||
gui.Views.Extras.Autoscroll = true
|
||||
|
||||
textStyle := theme.DefaultTextColor
|
||||
if !commandLine {
|
||||
// if we're not dealing with a direct command that could be run on the command line,
|
||||
// we style it differently to communicate that
|
||||
textStyle = style.FgMagenta
|
||||
}
|
||||
gui.GuiLog = append(gui.GuiLog, cmdStr)
|
||||
indentedCmdStr := " " + strings.ReplaceAll(cmdStr, "\n", "\n ")
|
||||
fmt.Fprint(gui.Views.Extras, "\n"+textStyle.Sprint(indentedCmdStr))
|
||||
|
||||
// See the comment in LogAction: bounce onto the UI thread since we may be
|
||||
// called from a git worker, in the background so it can't block a repo switch.
|
||||
gui.onUIThreadBackground(func() error {
|
||||
gui.Views.Extras.Autoscroll = true
|
||||
|
||||
gui.GuiLog = append(gui.GuiLog, cmdStr)
|
||||
fmt.Fprint(gui.Views.Extras, "\n"+textStyle.Sprint(indentedCmdStr))
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) printCommandLogHeader() {
|
||||
|
||||
@@ -32,34 +32,76 @@ type ListRenderer struct {
|
||||
getNonModelItems func() []*NonModelItem
|
||||
|
||||
// The remaining fields are private and shouldn't be initialized by clients
|
||||
numNonModelItems int
|
||||
viewIndicesByModelIndex []int
|
||||
modelIndicesByViewIndex []int
|
||||
columnPositions []int
|
||||
columnPositions []int
|
||||
}
|
||||
|
||||
func (self *ListRenderer) GetList() types.IList {
|
||||
return self.list
|
||||
}
|
||||
|
||||
func (self *ListRenderer) ModelIndexToViewIndex(modelIndex int) int {
|
||||
modelIndex = lo.Clamp(modelIndex, 0, self.list.Len())
|
||||
if self.viewIndicesByModelIndex != nil {
|
||||
return self.viewIndicesByModelIndex[modelIndex]
|
||||
func (self *ListRenderer) getNonModelItemList() []*NonModelItem {
|
||||
if self.getNonModelItems == nil {
|
||||
return nil
|
||||
}
|
||||
return self.getNonModelItems()
|
||||
}
|
||||
|
||||
return modelIndex
|
||||
func (self *ListRenderer) ModelIndexToViewIndex(modelIndex int) int {
|
||||
return modelIndexToViewIndex(self.list.Len(), self.getNonModelItemList(), modelIndex)
|
||||
}
|
||||
|
||||
func (self *ListRenderer) ViewIndexToModelIndex(viewIndex int) int {
|
||||
viewIndex = lo.Clamp(viewIndex, 0, self.list.Len()+self.numNonModelItems)
|
||||
if self.modelIndicesByViewIndex != nil {
|
||||
return self.modelIndicesByViewIndex[viewIndex]
|
||||
}
|
||||
return viewIndexToModelIndex(self.list.Len(), self.getNonModelItemList(), viewIndex)
|
||||
}
|
||||
|
||||
// modelToViewIndexConverter returns a model-to-view index conversion that
|
||||
// reuses a single snapshot of the non-model items. Callers that convert many
|
||||
// indices in a row (e.g. search, which converts every commit) should use this
|
||||
// rather than calling ModelIndexToViewIndex per index, which would rebuild the
|
||||
// non-model items each time.
|
||||
func (self *ListRenderer) modelToViewIndexConverter() func(modelIndex int) int {
|
||||
listLength := self.list.Len()
|
||||
nonModelItems := self.getNonModelItemList()
|
||||
return func(modelIndex int) int {
|
||||
return modelIndexToViewIndex(listLength, nonModelItems, modelIndex)
|
||||
}
|
||||
}
|
||||
|
||||
// The view shows the model items with the non-model items (e.g. section
|
||||
// headers) inserted at their model indices. The two conversions below are
|
||||
// computed directly from the current list length and non-model items, so they
|
||||
// don't depend on the list having been rendered, and they can never be stale
|
||||
// with respect to a model that changed since the last render (which used to
|
||||
// cause both wrong results and index-out-of-range panics).
|
||||
//
|
||||
// The non-model items are assumed to be ordered by their Index, which is how
|
||||
// all producers build them; the i-th one therefore ends up at view index
|
||||
// Index+i.
|
||||
func modelIndexToViewIndex(listLength int, nonModelItems []*NonModelItem, modelIndex int) int {
|
||||
modelIndex = lo.Clamp(modelIndex, 0, listLength)
|
||||
// Each non-model item inserted at or before this model item pushes it down
|
||||
// by one row in the view.
|
||||
viewIndex := modelIndex
|
||||
for _, item := range nonModelItems {
|
||||
if item.Index <= modelIndex {
|
||||
viewIndex++
|
||||
}
|
||||
}
|
||||
return viewIndex
|
||||
}
|
||||
|
||||
func viewIndexToModelIndex(listLength int, nonModelItems []*NonModelItem, viewIndex int) int {
|
||||
viewIndex = lo.Clamp(viewIndex, 0, listLength+len(nonModelItems))
|
||||
// Subtract the non-model items that appear before this view index.
|
||||
modelIndex := viewIndex
|
||||
for i, item := range nonModelItems {
|
||||
if item.Index+i < viewIndex {
|
||||
modelIndex--
|
||||
}
|
||||
}
|
||||
return modelIndex
|
||||
}
|
||||
|
||||
func (self *ListRenderer) ColumnPositions() []int {
|
||||
return self.columnPositions
|
||||
}
|
||||
@@ -71,23 +113,18 @@ func (self *ListRenderer) renderLines(startIdx int, endIdx int) string {
|
||||
if self.getColumnAlignments != nil {
|
||||
columnAlignments = self.getColumnAlignments()
|
||||
}
|
||||
nonModelItems := []*NonModelItem{}
|
||||
self.numNonModelItems = 0
|
||||
if self.getNonModelItems != nil {
|
||||
nonModelItems = self.getNonModelItems()
|
||||
self.prepareConversionArrays(nonModelItems)
|
||||
}
|
||||
nonModelItems := self.getNonModelItemList()
|
||||
startModelIdx := 0
|
||||
if startIdx == -1 {
|
||||
startIdx = 0
|
||||
} else {
|
||||
startModelIdx = self.ViewIndexToModelIndex(startIdx)
|
||||
startModelIdx = viewIndexToModelIndex(self.list.Len(), nonModelItems, startIdx)
|
||||
}
|
||||
endModelIdx := self.list.Len()
|
||||
if endIdx == -1 {
|
||||
endIdx = endModelIdx + len(nonModelItems)
|
||||
} else {
|
||||
endModelIdx = self.ViewIndexToModelIndex(endIdx)
|
||||
endModelIdx = viewIndexToModelIndex(self.list.Len(), nonModelItems, endIdx)
|
||||
}
|
||||
lines, columnPositions := utils.RenderDisplayStrings(
|
||||
self.getDisplayStrings(startModelIdx, endModelIdx),
|
||||
@@ -97,23 +134,6 @@ func (self *ListRenderer) renderLines(startIdx int, endIdx int) string {
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func (self *ListRenderer) prepareConversionArrays(nonModelItems []*NonModelItem) {
|
||||
self.numNonModelItems = len(nonModelItems)
|
||||
viewIndicesByModelIndex := lo.Range(self.list.Len() + 1)
|
||||
modelIndicesByViewIndex := lo.Range(self.list.Len() + 1)
|
||||
offset := 0
|
||||
for _, item := range nonModelItems {
|
||||
for i := item.Index; i <= self.list.Len(); i++ {
|
||||
viewIndicesByModelIndex[i]++
|
||||
}
|
||||
modelIndicesByViewIndex = slices.Insert(
|
||||
modelIndicesByViewIndex, item.Index+offset, modelIndicesByViewIndex[item.Index+offset])
|
||||
offset++
|
||||
}
|
||||
self.viewIndicesByModelIndex = viewIndicesByModelIndex
|
||||
self.modelIndicesByViewIndex = modelIndicesByViewIndex
|
||||
}
|
||||
|
||||
func (self *ListRenderer) insertNonModelItems(
|
||||
nonModelItems []*NonModelItem, endIdx int, startIdx int, lines []string, columnPositions []int,
|
||||
) []string {
|
||||
|
||||
@@ -254,9 +254,6 @@ func TestListRenderer_ModelIndexToViewIndex_and_back(t *testing.T) {
|
||||
getNonModelItems: getNonModelItems,
|
||||
}
|
||||
|
||||
// Need to render first so that it knows the non-model items
|
||||
self.renderLines(-1, -1)
|
||||
|
||||
for i := range len(s.modelIndices) {
|
||||
assert.Equal(t, s.expectedViewIndices[i], self.ModelIndexToViewIndex(s.modelIndices[i]))
|
||||
}
|
||||
@@ -267,3 +264,27 @@ func TestListRenderer_ModelIndexToViewIndex_and_back(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The index conversions must not depend on the list having been rendered
|
||||
// first. It used to be renderLines that populated the conversion arrays, so
|
||||
// converting an index before the first render silently ignored the non-model
|
||||
// items (and converting after the model changed used a stale snapshot).
|
||||
func TestListRenderer_IndexConversionsAreRenderIndependent(t *testing.T) {
|
||||
modelInts := lo.Map(lo.Range(3), func(i int, _ int) myint { return myint(i) })
|
||||
self := &ListRenderer{
|
||||
list: NewListViewModel(func() []myint { return modelInts }),
|
||||
getDisplayStrings: func(startIdx int, endIdx int) [][]string {
|
||||
return lo.Map(modelInts[startIdx:endIdx],
|
||||
func(i myint, _ int) []string { return []string{fmt.Sprint(i)} })
|
||||
},
|
||||
// A section header sits at model index 1, so model item 1 is pushed down
|
||||
// to view index 2, and view index 2 maps back to model item 1.
|
||||
getNonModelItems: func() []*NonModelItem {
|
||||
return []*NonModelItem{{Index: 1, Content: "--- header ---"}}
|
||||
},
|
||||
}
|
||||
|
||||
// Deliberately convert without rendering first.
|
||||
assert.Equal(t, 2, self.ModelIndexToViewIndex(1))
|
||||
assert.Equal(t, 1, self.ViewIndexToModelIndex(2))
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
@@ -142,7 +143,9 @@ type LocalCommitsViewModel struct {
|
||||
|
||||
// If this is true we limit the amount of commits we load, for the sake of keeping things fast.
|
||||
// If the user attempts to scroll past the end of the list, we will load more commits.
|
||||
limitCommits bool
|
||||
// Atomic because a checkout or reset sets it from a worker goroutine while the
|
||||
// commits refresh reads it on the UI thread to decide how many commits to load.
|
||||
limitCommits atomic.Bool
|
||||
|
||||
// If this is true we'll use git log --all when fetching the commits.
|
||||
showWholeGitGraph bool
|
||||
@@ -151,9 +154,9 @@ type LocalCommitsViewModel struct {
|
||||
func NewLocalCommitsViewModel(getModel func() []*models.Commit, c *ContextCommon) *LocalCommitsViewModel {
|
||||
self := &LocalCommitsViewModel{
|
||||
ListViewModel: NewListViewModel(getModel),
|
||||
limitCommits: true,
|
||||
showWholeGitGraph: c.UserConfig().Git.Log.ShowWholeGraph,
|
||||
}
|
||||
self.limitCommits.Store(true)
|
||||
|
||||
return self
|
||||
}
|
||||
@@ -221,15 +224,15 @@ func (self *LocalCommitsContext) RefForAdjustingLineNumberInDiff() string {
|
||||
}
|
||||
|
||||
func (self *LocalCommitsContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
|
||||
return searchModelCommits(caseSensitive, self.GetCommits(), self.ColumnPositions(), self.ModelIndexToViewIndex, searchStr)
|
||||
return searchModelCommits(caseSensitive, self.GetCommits(), self.ColumnPositions(), self.modelToViewIndexConverter(), searchStr)
|
||||
}
|
||||
|
||||
func (self *LocalCommitsViewModel) SetLimitCommits(value bool) {
|
||||
self.limitCommits = value
|
||||
self.limitCommits.Store(value)
|
||||
}
|
||||
|
||||
func (self *LocalCommitsViewModel) GetLimitCommits() bool {
|
||||
return self.limitCommits
|
||||
return self.limitCommits.Load()
|
||||
}
|
||||
|
||||
func (self *LocalCommitsViewModel) SetShowWholeGitGraph(value bool) {
|
||||
|
||||
@@ -60,8 +60,11 @@ func NewContextTree(c *ContextCommon) *ContextTree {
|
||||
"main",
|
||||
PATCH_BUILDING_MAIN_CONTEXT_KEY,
|
||||
func() []int {
|
||||
filename := commitFilesContext.GetSelectedPath()
|
||||
includedLineIndices, err := c.Git().Patch.PatchBuilder.GetFileIncLineIndices(filename)
|
||||
file := commitFilesContext.GetSelectedFile()
|
||||
if file == nil {
|
||||
return nil
|
||||
}
|
||||
includedLineIndices, err := c.Git().Patch.PatchBuilder.GetFileIncLineIndices(file.Path, file.PreviousPath)
|
||||
if err != nil {
|
||||
c.Log.Error(err)
|
||||
return nil
|
||||
|
||||
@@ -223,7 +223,7 @@ func (self *SubCommitsContext) RefForAdjustingLineNumberInDiff() string {
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
|
||||
return searchModelCommits(caseSensitive, self.GetCommits(), self.ColumnPositions(), self.ModelIndexToViewIndex, searchStr)
|
||||
return searchModelCommits(caseSensitive, self.GetCommits(), self.ColumnPositions(), self.modelToViewIndexConverter(), searchStr)
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) IndexForGotoBottom() int {
|
||||
|
||||
@@ -25,9 +25,8 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
helperCommon := gui.c
|
||||
recordDirectoryHelper := helpers.NewRecordDirectoryHelper(helperCommon)
|
||||
reposHelper := helpers.NewRecentReposHelper(helperCommon, recordDirectoryHelper, gui.onSwitchToNewRepo)
|
||||
baseBranchHelper := helpers.NewBaseBranchHelper(helperCommon)
|
||||
rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon, baseBranchHelper)
|
||||
refsHelper := helpers.NewRefsHelper(helperCommon, rebaseHelper, baseBranchHelper)
|
||||
rebaseHelper := helpers.NewMergeAndRebaseHelper(helperCommon)
|
||||
refsHelper := helpers.NewRefsHelper(helperCommon, rebaseHelper)
|
||||
suggestionsHelper := helpers.NewSuggestionsHelper(helperCommon)
|
||||
worktreeHelper := helpers.NewWorktreeHelper(helperCommon, reposHelper, refsHelper, suggestionsHelper)
|
||||
|
||||
@@ -130,7 +129,6 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
Search: searchHelper,
|
||||
Worktree: worktreeHelper,
|
||||
SubCommits: helpers.NewSubCommitsHelper(helperCommon, refreshHelper),
|
||||
BaseBranch: helpers.NewBaseBranchHelper(helperCommon),
|
||||
}
|
||||
|
||||
gui.CustomCommandsClient = custom_commands.NewClient(
|
||||
|
||||
@@ -274,10 +274,11 @@ func (self *BisectController) afterMark(selectCurrent bool, waitToReselect bool)
|
||||
}
|
||||
|
||||
func (self *BisectController) afterBisectMarkRefresh(selectCurrent bool, waitToReselect bool) error {
|
||||
selectFn := func() {
|
||||
selectFn := func() error {
|
||||
if selectCurrent {
|
||||
self.selectCurrentBisectCommit()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if waitToReselect {
|
||||
@@ -285,7 +286,9 @@ func (self *BisectController) afterBisectMarkRefresh(selectCurrent bool, waitToR
|
||||
return nil
|
||||
}
|
||||
|
||||
selectFn()
|
||||
if err := selectFn(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
return nil
|
||||
|
||||
@@ -291,26 +291,18 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
|
||||
}
|
||||
|
||||
var disabledReason *types.DisabledReason
|
||||
baseBranch, baseAmbiguous, baseCandidates, err := self.c.Helpers().BaseBranch.ResolveBaseBranch(selectedBranch)
|
||||
baseBranch, err := self.c.Git().Loaders.BranchLoader.GetBaseBranch(selectedBranch, self.c.Model().MainBranches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
baseBranchLabel := helpers.BaseBranchDisplayName(baseBranch)
|
||||
switch {
|
||||
case baseBranch == "":
|
||||
baseBranchLabel = self.c.Tr.CouldNotDetermineBaseBranch
|
||||
if baseBranch == "" {
|
||||
baseBranch = self.c.Tr.CouldNotDetermineBaseBranch
|
||||
disabledReason = &types.DisabledReason{Text: self.c.Tr.CouldNotDetermineBaseBranch}
|
||||
case baseAmbiguous:
|
||||
shortNames := lo.Map(baseCandidates, func(ref string, _ int) string {
|
||||
return helpers.BaseBranchDisplayName(ref)
|
||||
})
|
||||
baseBranchLabel = utils.ResolvePlaceholderString(self.c.Tr.PickBaseBranchLabel,
|
||||
map[string]string{"candidates": strings.Join(shortNames, ", ")},
|
||||
)
|
||||
}
|
||||
shortBaseBranchName := helpers.ShortBranchName(baseBranch)
|
||||
label := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ViewDivergenceFromBaseBranch,
|
||||
map[string]string{"baseBranch": baseBranchLabel},
|
||||
map[string]string{"baseBranch": shortBaseBranchName},
|
||||
)
|
||||
viewDivergenceFromBaseBranchItem := &types.MenuItem{
|
||||
LabelColumns: []string{label},
|
||||
@@ -320,19 +312,14 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
|
||||
if branch == nil {
|
||||
return nil
|
||||
}
|
||||
showDivergence := func(base string) error {
|
||||
return self.c.Helpers().SubCommits.ViewSubCommits(helpers.ViewSubCommitsOpts{
|
||||
Ref: branch,
|
||||
TitleRef: fmt.Sprintf("%s <-> %s", branch.RefName(), helpers.BaseBranchDisplayName(base)),
|
||||
RefToShowDivergenceFrom: base,
|
||||
Context: self.context(),
|
||||
ShowBranchHeads: false,
|
||||
})
|
||||
}
|
||||
if baseAmbiguous {
|
||||
return self.c.Helpers().BaseBranch.ShowPicker(branch, baseCandidates, showDivergence)
|
||||
}
|
||||
return showDivergence(baseBranch)
|
||||
|
||||
return self.c.Helpers().SubCommits.ViewSubCommits(helpers.ViewSubCommitsOpts{
|
||||
Ref: branch,
|
||||
TitleRef: fmt.Sprintf("%s <-> %s", branch.RefName(), shortBaseBranchName),
|
||||
RefToShowDivergenceFrom: baseBranch,
|
||||
Context: self.context(),
|
||||
ShowBranchHeads: false,
|
||||
})
|
||||
},
|
||||
DisabledReason: disabledReason,
|
||||
}
|
||||
@@ -612,11 +599,11 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Helpers().Refs.SelectFirstBranchAndFirstCommit()
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.ASYNC,
|
||||
KeepBranchSelectionIndex: true,
|
||||
CommitSelection: types.KeepCommitSelectionIndex,
|
||||
Mode: types.ASYNC,
|
||||
BranchSelection: types.SelectCheckedOutBranch,
|
||||
CommitSelection: types.SelectHeadCommit,
|
||||
SelectTopReflogCommit: true,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@@ -723,9 +710,9 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
}
|
||||
|
||||
action := self.c.Tr.Actions.FastForwardBranch
|
||||
worktree, ok := self.worktreeForBranch(branch)
|
||||
|
||||
return self.c.WithInlineStatus(branch, types.ItemOperationFastForwarding, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error {
|
||||
worktree, ok := self.worktreeForBranch(branch)
|
||||
if ok {
|
||||
self.c.LogAction(action)
|
||||
|
||||
@@ -747,7 +734,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
WorktreePath: worktreePath,
|
||||
},
|
||||
)
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.SYNC})
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -756,7 +743,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
err := self.c.Git().Sync.FastForward(
|
||||
task, branch.Name, branch.UpstreamRemote, branch.UpstreamBranch,
|
||||
)
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
return err
|
||||
})
|
||||
}
|
||||
@@ -796,20 +783,25 @@ func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// need to find where the branch is now so that we can re-select it. That means we need to refetch the branches synchronously and then find our branch
|
||||
// need to find where the branch is now so that we can re-select it. That means we need to
|
||||
// refetch the branches and then find our branch. The branches model update is bounced
|
||||
// onto the UI thread, so the re-selection (which reads Model.Branches) has to run in
|
||||
// Then; reading it inline here would see the previous model.
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC,
|
||||
Scope: []types.RefreshableView{types.BRANCHES, types.WORKTREES},
|
||||
Then: func() error {
|
||||
// now that we've got our stuff again we need to find that branch and reselect it.
|
||||
for i, newBranch := range self.c.Model().Branches {
|
||||
if newBranch.Name == newBranchName {
|
||||
self.context().SetSelection(i)
|
||||
self.context().HandleRender()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
// now that we've got our stuff again we need to find that branch and reselect it.
|
||||
for i, newBranch := range self.c.Model().Branches {
|
||||
if newBranch.Name == newBranchName {
|
||||
self.context().SetSelection(i)
|
||||
self.context().HandleRender()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
@@ -191,11 +191,11 @@ func (self *CommitFilesController) GetOnRenderToMain() func() {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) copyDiffToClipboard(path string, toastMessage string) error {
|
||||
func (self *CommitFilesController) copyDiffToClipboard(paths []string, toastMessage string) error {
|
||||
from, to := self.context().GetFromAndToForDiff()
|
||||
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from)
|
||||
|
||||
cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, []string{path}, true)
|
||||
cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, paths, true)
|
||||
diff, err := cmdObj.RunWithOutput()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -263,7 +263,7 @@ func (self *CommitFilesController) openCopyMenu() error {
|
||||
copyFileDiffItem := &types.MenuItem{
|
||||
Label: self.c.Tr.CopySelectedDiff,
|
||||
OnPress: func() error {
|
||||
return self.copyDiffToClipboard(node.GetPath(), self.c.Tr.FileDiffCopiedToast)
|
||||
return self.copyDiffToClipboard(self.pathsForDiff(node), self.c.Tr.FileDiffCopiedToast)
|
||||
},
|
||||
DisabledReason: self.require(self.singleItemSelected())(),
|
||||
Keys: menuKey('s'),
|
||||
@@ -271,7 +271,7 @@ func (self *CommitFilesController) openCopyMenu() error {
|
||||
copyAllDiff := &types.MenuItem{
|
||||
Label: self.c.Tr.CopyAllFilesDiff,
|
||||
OnPress: func() error {
|
||||
return self.copyDiffToClipboard(".", self.c.Tr.AllFilesDiffCopiedToast)
|
||||
return self.copyDiffToClipboard([]string{"."}, self.c.Tr.AllFilesDiffCopiedToast)
|
||||
},
|
||||
DisabledReason: self.require(self.itemsSelected())(),
|
||||
Keys: menuKey('a'),
|
||||
@@ -337,6 +337,8 @@ func (self *CommitFilesController) discard(selectedNodes []*filetree.CommitFileN
|
||||
Title: self.c.Tr.DiscardFileChangesTitle,
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
commits := self.c.Model().Commits
|
||||
selectedLineIdx := self.c.Contexts().LocalCommits.GetSelectedLineIdx()
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
var filePaths []string
|
||||
selectedNodes = normalisedSelectedCommitFileNodes(selectedNodes)
|
||||
@@ -348,19 +350,25 @@ func (self *CommitFilesController) discard(selectedNodes []*filetree.CommitFileN
|
||||
|
||||
for _, node := range selectedNodes {
|
||||
_ = node.ForEachFile(func(file *models.CommitFile) error {
|
||||
filePaths = append(filePaths, file.GetPath())
|
||||
// For a rename we discard both the new and the old path,
|
||||
// so that the new file is removed and the old one is
|
||||
// restored.
|
||||
filePaths = append(filePaths, file.Names()...)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
err := self.c.Git().Rebase.DiscardOldFileChanges(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), filePaths)
|
||||
err := self.c.Git().Rebase.DiscardOldFileChanges(commits, selectedLineIdx, filePaths)
|
||||
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if self.context().RangeSelectEnabled() {
|
||||
self.context().GetList().CancelRangeSelect()
|
||||
}
|
||||
self.c.OnUIThread(func() error {
|
||||
if self.context().RangeSelectEnabled() {
|
||||
self.context().GetList().CancelRangeSelect()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
@@ -439,20 +447,16 @@ func (self *CommitFilesController) toggleForPatch(selectedNodes []*filetree.Comm
|
||||
self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView)
|
||||
}
|
||||
|
||||
refName := self.context().GetRef().RefName()
|
||||
|
||||
toggle := func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.UpdatingPatch, func(gocui.Task) error {
|
||||
if !self.c.Git().Patch.PatchBuilder.Active() {
|
||||
if err := self.startPatchBuilder(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
selectedNodes = normalisedSelectedCommitFileNodes(selectedNodes)
|
||||
|
||||
// Find if any file in the selection is unselected or partially added
|
||||
adding := lo.SomeBy(selectedNodes, func(node *filetree.CommitFileNode) bool {
|
||||
return node.SomeFile(func(file *models.CommitFile) bool {
|
||||
fileStatus := self.c.Git().Patch.PatchBuilder.GetFileStatus(file.Path, self.context().GetRef().RefName())
|
||||
fileStatus := self.c.Git().Patch.PatchBuilder.GetFileStatus(file.Path, refName)
|
||||
return fileStatus == patch.PART || fileStatus == patch.UNSELECTED
|
||||
})
|
||||
})
|
||||
@@ -465,7 +469,7 @@ func (self *CommitFilesController) toggleForPatch(selectedNodes []*filetree.Comm
|
||||
|
||||
for _, node := range selectedNodes {
|
||||
err := node.ForEachFile(func(file *models.CommitFile) error {
|
||||
return patchOperationFunction(file.Path)
|
||||
return patchOperationFunction(file.Path, file.PreviousPath)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -495,6 +499,12 @@ func (self *CommitFilesController) toggleForPatch(selectedNodes []*filetree.Comm
|
||||
self.c.Git().Patch.PatchBuilder.Reset()
|
||||
}
|
||||
|
||||
if !self.c.Git().Patch.PatchBuilder.Active() {
|
||||
if err := self.startPatchBuilder(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return toggle()
|
||||
},
|
||||
})
|
||||
@@ -610,11 +620,16 @@ func (self *CommitFilesController) pathsForDiff(node *filetree.CommitFileNode) [
|
||||
if !node.IsFile() && self.context().IsFiltering() {
|
||||
var paths []string
|
||||
_ = node.ForEachFile(func(file *models.CommitFile) error {
|
||||
paths = append(paths, file.Path)
|
||||
// For a rename we need to pass both paths so that git detects it as
|
||||
// a rename rather than an unrelated delete and add.
|
||||
paths = append(paths, file.Names()...)
|
||||
return nil
|
||||
})
|
||||
return paths
|
||||
}
|
||||
if file := node.GetFile(); file != nil {
|
||||
return file.Names()
|
||||
}
|
||||
return []string{node.GetPath()}
|
||||
}
|
||||
|
||||
|
||||
@@ -132,10 +132,11 @@ func (self *CustomPatchOptionsMenuAction) returnFocusFromPatchExplorerIfNecessar
|
||||
func (self *CustomPatchOptionsMenuAction) handleDeletePatchFromCommit() error {
|
||||
self.returnFocusFromPatchExplorerIfNecessary()
|
||||
|
||||
commits := self.c.Model().Commits
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.RemovePatchFromCommit)
|
||||
err := self.c.Git().Patch.DeletePatchesFromCommit(self.c.Model().Commits, commitIndex)
|
||||
err := self.c.Git().Patch.DeletePatchesFromCommit(commits, commitIndex)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
@@ -143,10 +144,12 @@ func (self *CustomPatchOptionsMenuAction) handleDeletePatchFromCommit() error {
|
||||
func (self *CustomPatchOptionsMenuAction) handleMovePatchToSelectedCommit() error {
|
||||
self.returnFocusFromPatchExplorerIfNecessary()
|
||||
|
||||
commits := self.c.Model().Commits
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
toCommitIndex := self.c.Contexts().LocalCommits.GetSelectedLineIdx()
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchToSelectedCommit)
|
||||
err := self.c.Git().Patch.MovePatchToSelectedCommit(self.c.Model().Commits, commitIndex, self.c.Contexts().LocalCommits.GetSelectedLineIdx())
|
||||
err := self.c.Git().Patch.MovePatchToSelectedCommit(commits, commitIndex, toCommitIndex)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
@@ -159,10 +162,11 @@ func (self *CustomPatchOptionsMenuAction) handleMovePatchIntoWorkingTree() error
|
||||
Title: self.c.Tr.MustStashTitle,
|
||||
Prompt: self.c.Tr.MustStashWarning,
|
||||
HandleConfirm: func() error {
|
||||
commits := self.c.Model().Commits
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoIndex)
|
||||
err := self.c.Git().Patch.MovePatchIntoIndex(self.c.Model().Commits, commitIndex, mustStash)
|
||||
err := self.c.Git().Patch.MovePatchIntoIndex(commits, commitIndex, mustStash)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
},
|
||||
@@ -183,14 +187,18 @@ func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error {
|
||||
DescriptionTitle: self.c.Tr.CommitDescriptionTitle,
|
||||
PreserveMessage: false,
|
||||
OnConfirm: func(summary string, description string) error {
|
||||
commits := self.c.Model().Commits
|
||||
self.c.Helpers().Commits.CloseCommitMessagePanel()
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
self.c.Helpers().Commits.CloseCommitMessagePanel()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit)
|
||||
err := self.c.Git().Patch.PullPatchIntoNewCommit(self.c.Model().Commits, commitIndex, summary, description)
|
||||
err := self.c.Git().Patch.PullPatchIntoNewCommit(commits, commitIndex, summary, description)
|
||||
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
|
||||
self.c.OnUIThread(func() error {
|
||||
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
},
|
||||
@@ -214,14 +222,18 @@ func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommitBefore() e
|
||||
DescriptionTitle: self.c.Tr.CommitDescriptionTitle,
|
||||
PreserveMessage: false,
|
||||
OnConfirm: func(summary string, description string) error {
|
||||
commits := self.c.Model().Commits
|
||||
self.c.Helpers().Commits.CloseCommitMessagePanel()
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
self.c.Helpers().Commits.CloseCommitMessagePanel()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit)
|
||||
err := self.c.Git().Patch.PullPatchIntoNewCommitBefore(self.c.Model().Commits, commitIndex, summary, description)
|
||||
err := self.c.Git().Patch.PullPatchIntoNewCommitBefore(commits, commitIndex, summary, description)
|
||||
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
|
||||
self.c.OnUIThread(func() error {
|
||||
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
},
|
||||
|
||||
@@ -44,7 +44,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
|
||||
{
|
||||
Keys: opts.GetKeys(opts.Config.Universal.Select),
|
||||
Handler: self.withItems(self.press),
|
||||
GetDisabledReason: self.require(self.withFileTreeViewModelMutex(self.itemsSelected(self.canStageSelection))),
|
||||
GetDisabledReason: self.require(self.itemsSelected(self.canStageSelection)),
|
||||
Description: self.c.Tr.Stage,
|
||||
Tooltip: self.c.Tr.StageTooltip,
|
||||
DisplayOnScreen: true,
|
||||
@@ -91,7 +91,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
|
||||
{
|
||||
Keys: opts.GetKeys(opts.Config.Universal.Edit),
|
||||
Handler: self.withItems(self.edit),
|
||||
GetDisabledReason: self.require(self.withFileTreeViewModelMutex(self.itemsSelected(self.canEditFiles))),
|
||||
GetDisabledReason: self.require(self.itemsSelected(self.canEditFiles)),
|
||||
Description: self.c.Tr.Edit,
|
||||
Tooltip: self.c.Tr.EditFileTooltip,
|
||||
DisplayOnScreen: true,
|
||||
@@ -145,7 +145,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
|
||||
{
|
||||
Keys: opts.GetKeys(opts.Config.Universal.Remove),
|
||||
Handler: self.withItems(self.remove),
|
||||
GetDisabledReason: self.withFileTreeViewModelMutex(self.require(self.itemsSelected(self.canRemove))),
|
||||
GetDisabledReason: self.require(self.itemsSelected(self.canRemove)),
|
||||
Description: self.c.Tr.Discard,
|
||||
Tooltip: self.c.Tr.DiscardFileChangesTooltip,
|
||||
OpensMenu: true,
|
||||
@@ -182,7 +182,7 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
|
||||
Handler: self.withItems(self.openMergeConflictMenu),
|
||||
Description: self.c.Tr.ViewMergeConflictOptions,
|
||||
Tooltip: self.c.Tr.ViewMergeConflictOptionsTooltip,
|
||||
GetDisabledReason: self.require(self.withFileTreeViewModelMutex(self.itemsSelected(self.canOpenMergeConflictMenu))),
|
||||
GetDisabledReason: self.require(self.itemsSelected(self.canOpenMergeConflictMenu)),
|
||||
OpensMenu: true,
|
||||
DisplayOnScreen: true,
|
||||
},
|
||||
@@ -209,15 +209,6 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilesController) withFileTreeViewModelMutex(callback func() *types.DisabledReason) func() *types.DisabledReason {
|
||||
return func() *types.DisabledReason {
|
||||
self.c.Contexts().Files.FileTreeViewModel.RWMutex.RLock()
|
||||
defer self.c.Contexts().Files.FileTreeViewModel.RWMutex.RUnlock()
|
||||
|
||||
return callback()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilesController) GetMouseKeybindings(opts types.KeybindingsOpts) []*gocui.ViewMouseBinding {
|
||||
return []*gocui.ViewMouseBinding{
|
||||
{
|
||||
@@ -574,11 +565,6 @@ func (self *FilesController) toggleStaged(
|
||||
}
|
||||
|
||||
func (self *FilesController) pressWithLock(selectedNodes []*filetree.FileNode) error {
|
||||
// Obtaining this lock because optimistic rendering requires us to mutate
|
||||
// the files in our model.
|
||||
self.c.Mutexes().RefreshingFilesMutex.Lock()
|
||||
defer self.c.Mutexes().RefreshingFilesMutex.Unlock()
|
||||
|
||||
// When filtering, expand directory nodes to individual visible file paths
|
||||
// so that only filtered files are staged/unstaged.
|
||||
toPaths := func(nodes []*filetree.FileNode) []string {
|
||||
@@ -942,9 +928,6 @@ func (self *FilesController) toggleStagedAll() error {
|
||||
}
|
||||
|
||||
func (self *FilesController) toggleStagedAllWithLock() error {
|
||||
self.c.Mutexes().RefreshingFilesMutex.Lock()
|
||||
defer self.c.Mutexes().RefreshingFilesMutex.Unlock()
|
||||
|
||||
root := self.context().FileTreeViewModel.GetRoot()
|
||||
|
||||
stage := func(unstagedNodes []*filetree.FileNode) error {
|
||||
@@ -1808,10 +1791,10 @@ func (self *FilesController) remove(selectedNodes []*filetree.FileNode) error {
|
||||
}
|
||||
|
||||
func (self *FilesController) ResetSubmodule(submodule *models.SubmoduleConfig) error {
|
||||
file := self.c.Helpers().WorkingTree.FileForSubmodule(submodule)
|
||||
return self.c.WithWaitingStatus(self.c.Tr.ResettingSubmoduleStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.ResetSubmodule)
|
||||
|
||||
file := self.c.Helpers().WorkingTree.FileForSubmodule(submodule)
|
||||
if file != nil {
|
||||
if err := self.c.Git().WorkingTree.UnStageFile(file.Names(), file.Tracked); err != nil {
|
||||
return err
|
||||
@@ -1825,7 +1808,7 @@ func (self *FilesController) ResetSubmodule(submodule *models.SubmoduleConfig) e
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.SUBMODULES}})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.SUBMODULES}})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -122,9 +122,10 @@ func (self *FilteringMenuAction) setFiltering() error {
|
||||
|
||||
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Scope: helpers.ScopesToRefreshWhenFilteringModeChanges(), Then: func() {
|
||||
self.c.Refresh(types.RefreshOptions{Scope: helpers.ScopesToRefreshWhenFilteringModeChanges(), Then: func() error {
|
||||
self.c.Contexts().LocalCommits.SetSelection(0)
|
||||
self.c.Contexts().LocalCommits.HandleFocus(types.OnFocusOpts{})
|
||||
return nil
|
||||
}})
|
||||
|
||||
return nil
|
||||
|
||||
@@ -136,6 +136,12 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type
|
||||
Description: self.c.Tr.ToggleWhitespaceInDiffView,
|
||||
Tooltip: self.c.Tr.ToggleWhitespaceInDiffViewTooltip,
|
||||
},
|
||||
{
|
||||
Keys: opts.GetKeys(opts.Config.Universal.ToggleMouseCapture),
|
||||
Handler: self.toggleMouseCapture,
|
||||
Description: self.c.Tr.ToggleMouseCapture,
|
||||
Tooltip: self.c.Tr.ToggleMouseCaptureTooltip,
|
||||
},
|
||||
{
|
||||
Keys: opts.GetKeys(opts.Config.Universal.EditConfig),
|
||||
Handler: self.editConfig,
|
||||
@@ -273,6 +279,20 @@ func (self *GlobalController) toggleWhitespace() error {
|
||||
return (&ToggleWhitespaceAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
func (self *GlobalController) toggleMouseCapture() error {
|
||||
userConfig := self.c.UserConfig()
|
||||
userConfig.Gui.MouseEvents = !userConfig.Gui.MouseEvents
|
||||
self.c.GocuiGui().Mouse = userConfig.Gui.MouseEvents
|
||||
|
||||
if userConfig.Gui.MouseEvents {
|
||||
self.c.Toast(self.c.Tr.MouseCaptureEnabled)
|
||||
} else {
|
||||
self.c.Toast(self.c.Tr.MouseCaptureDisabled)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *GlobalController) editConfig() error {
|
||||
return (&EditConfigAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
@@ -34,7 +34,12 @@ func (self *AppStatusHelper) Toast(message string, kind types.ToastKind) {
|
||||
|
||||
self.statusMgr().AddToastStatus(message, kind)
|
||||
|
||||
self.renderAppStatus()
|
||||
// Render the toast in the background: it's a transient notification, not
|
||||
// lazygit driving an operation, so it must not count towards being busy —
|
||||
// otherwise a toast (e.g. the "can't switch, operation in progress" one)
|
||||
// would itself block a repo switch until it faded. A real operation showing
|
||||
// a toast still keeps its own foreground task busy independently.
|
||||
self.renderAppStatus(true)
|
||||
}
|
||||
|
||||
// A custom task for WithWaitingStatus calls; it wraps the original one and
|
||||
@@ -61,11 +66,14 @@ func (self appStatusHelperTask) Continue() {
|
||||
// WithWaitingStatus wraps a function and shows a waiting status while the function is still executing
|
||||
func (self *AppStatusHelper) WithWaitingStatus(message string, f func(gocui.Task) error) {
|
||||
self.c.OnWorker(func(task gocui.Task) error {
|
||||
return self.WithWaitingStatusImpl(message, f, task)
|
||||
return self.WithWaitingStatusImpl(message, f, task, false)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *AppStatusHelper) WithWaitingStatusImpl(message string, f func(gocui.Task) error, task gocui.Task) error {
|
||||
// background reports whether this waiting status belongs to a background routine
|
||||
// (the auto-fetch poller); when it does, the spinner it drives must not count
|
||||
// towards lazygit being busy, or it'd block repo switches while a fetch runs.
|
||||
func (self *AppStatusHelper) WithWaitingStatusImpl(message string, f func(gocui.Task) error, task gocui.Task, background bool) error {
|
||||
// A waiting status means lazygit is driving a git operation itself (often
|
||||
// one that internally runs a rebase and continues it). Pause the background
|
||||
// routines for its duration so they don't refresh from an intermediate
|
||||
@@ -73,7 +81,7 @@ func (self *AppStatusHelper) WithWaitingStatusImpl(message string, f func(gocui.
|
||||
self.c.PauseBackgroundRefreshes(true)
|
||||
defer self.c.PauseBackgroundRefreshes(false)
|
||||
|
||||
return self.statusMgr().WithWaitingStatus(message, self.renderAppStatus, func(waitingStatusHandle *status.WaitingStatusHandle) error {
|
||||
return self.statusMgr().WithWaitingStatus(message, func() { self.renderAppStatus(background) }, func(waitingStatusHandle *status.WaitingStatusHandle) error {
|
||||
return f(appStatusHelperTask{task, waitingStatusHandle})
|
||||
})
|
||||
}
|
||||
@@ -100,21 +108,33 @@ func (self *AppStatusHelper) GetStatusString() string {
|
||||
return appStatus
|
||||
}
|
||||
|
||||
func (self *AppStatusHelper) renderAppStatus() {
|
||||
self.c.OnWorker(func(_ gocui.Task) error {
|
||||
func (self *AppStatusHelper) renderAppStatus(background bool) {
|
||||
// A background waiting status (auto-fetch) must not count towards lazygit
|
||||
// being busy, so its spinner worker and per-frame UI updates go through the
|
||||
// background variants.
|
||||
onWorker := self.c.OnWorker
|
||||
onUIThread := self.c.OnUIThread
|
||||
onUIThreadContentOnly := self.c.OnUIThreadContentOnly
|
||||
if background {
|
||||
onWorker = self.c.OnWorkerBackground
|
||||
onUIThread = self.c.OnUIThreadBackground
|
||||
onUIThreadContentOnly = self.c.OnUIThreadContentOnlyBackground
|
||||
}
|
||||
|
||||
onWorker(func(_ gocui.Task) error {
|
||||
ticker := time.NewTicker(time.Millisecond * time.Duration(self.c.UserConfig().Gui.Spinner.Rate))
|
||||
defer ticker.Stop()
|
||||
prevAppStatus := ""
|
||||
for range ticker.C {
|
||||
appStatus, color := self.statusMgr().GetStatusString(self.c.UserConfig())
|
||||
|
||||
update := self.c.OnUIThreadContentOnly
|
||||
update := onUIThreadContentOnly
|
||||
if utils.StringWidth(appStatus) != utils.StringWidth(prevAppStatus) {
|
||||
// Need a full layout whenever the width of the status string changes. This can't
|
||||
// happen during normal spinning because we validate that all spinner frames have
|
||||
// the same width, so typically this will only be triggered at the beginning and end
|
||||
// of a status, or if the status string changes midway for some reason.
|
||||
update = self.c.OnUIThread
|
||||
update = onUIThread
|
||||
}
|
||||
update(func() error {
|
||||
self.c.Views().AppStatus.FgColor = color
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// BaseBranchHelper resolves the base branch for a given branch. The
|
||||
// closeness rule (smallest ahead value) usually picks a single answer
|
||||
// but can leave a tie when the branch's fork point is reachable from
|
||||
// more than one main branch — in that case the helper surfaces the
|
||||
// candidates so the caller can disambiguate.
|
||||
type BaseBranchHelper struct {
|
||||
c *HelperCommon
|
||||
}
|
||||
|
||||
func NewBaseBranchHelper(c *HelperCommon) *BaseBranchHelper {
|
||||
return &BaseBranchHelper{c: c}
|
||||
}
|
||||
|
||||
// ResolveBaseBranch returns the base branch for the given branch, the
|
||||
// full set of tied candidates (for any disambiguation UI), and whether
|
||||
// the answer is genuinely ambiguous (more than one candidate tied at
|
||||
// the closest position).
|
||||
//
|
||||
// An empty baseRef (with no error) means no configured main branch
|
||||
// contains the branch — not an error condition.
|
||||
func (self *BaseBranchHelper) ResolveBaseBranch(branch *models.Branch) (baseRef string, ambiguous bool, candidates []string, err error) {
|
||||
mainBranches := self.c.Model().MainBranches
|
||||
candidates, err = self.c.Git().Loaders.BranchLoader.GetBaseBranchCandidates(branch, mainBranches)
|
||||
if err != nil {
|
||||
return "", false, nil, err
|
||||
}
|
||||
if len(candidates) == 0 {
|
||||
return "", false, nil, nil
|
||||
}
|
||||
return candidates[0], len(candidates) > 1, candidates, nil
|
||||
}
|
||||
|
||||
// ShowPicker presents a menu of candidate base branches and runs
|
||||
// onPicked with the user's selection. Callers should only invoke this
|
||||
// when ResolveBaseBranch reported ambiguous=true; for the
|
||||
// single-candidate case there is nothing to pick.
|
||||
func (self *BaseBranchHelper) ShowPicker(
|
||||
branch *models.Branch,
|
||||
candidates []string,
|
||||
onPicked func(baseRef string) error,
|
||||
) error {
|
||||
items := lo.Map(candidates, func(ref string, _ int) *types.MenuItem {
|
||||
return &types.MenuItem{
|
||||
Label: BaseBranchDisplayName(ref),
|
||||
OnPress: func() error { return onPicked(ref) },
|
||||
}
|
||||
})
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: utils.ResolvePlaceholderString(self.c.Tr.PickBaseBranchTitle,
|
||||
map[string]string{"branchName": branch.Name}),
|
||||
Prompt: self.c.Tr.PickBaseBranchPrompt,
|
||||
Items: items,
|
||||
})
|
||||
}
|
||||
@@ -45,8 +45,11 @@ func (self *BranchesHelper) ConfirmLocalDelete(branches []*models.Branch) error
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Contexts().Branches.CollapseRangeSelectionToTop()
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
self.c.OnUIThread(func() error {
|
||||
self.c.Contexts().Branches.CollapseRangeSelectionToTop()
|
||||
return nil
|
||||
})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
return nil
|
||||
})
|
||||
})
|
||||
@@ -84,9 +87,12 @@ func (self *BranchesHelper) ConfirmDeleteRemote(remoteBranches []*models.RemoteB
|
||||
if err := self.deleteRemoteBranches(remoteBranches, task); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
|
||||
if resetRemoteBranchesSelection {
|
||||
self.c.Contexts().RemoteBranches.CollapseRangeSelectionToTop()
|
||||
self.c.OnUIThread(func() error {
|
||||
self.c.Contexts().RemoteBranches.CollapseRangeSelectionToTop()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -151,8 +157,11 @@ func (self *BranchesHelper) ConfirmLocalAndRemoteDelete(branches []*models.Branc
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Contexts().Branches.CollapseRangeSelectionToTop()
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
|
||||
self.c.OnUIThread(func() error {
|
||||
self.c.Contexts().Branches.CollapseRangeSelectionToTop()
|
||||
return nil
|
||||
})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
|
||||
return nil
|
||||
})
|
||||
},
|
||||
@@ -161,30 +170,8 @@ func (self *BranchesHelper) ConfirmLocalAndRemoteDelete(branches []*models.Branc
|
||||
return nil
|
||||
}
|
||||
|
||||
// BaseBranchDisplayName returns the user-facing name of a configured main
|
||||
// branch from its resolved full ref:
|
||||
//
|
||||
// refs/heads/main → main
|
||||
// refs/remotes/origin/main → main
|
||||
// refs/remotes/origin/feat/x → feat/x
|
||||
//
|
||||
// For remote-tracking refs the remote name is dropped along with the prefix:
|
||||
// the user configured plain "main" in mainBranches and shouldn't have to see
|
||||
// whether lazygit ultimately resolved it to a local or remote ref. The remote
|
||||
// is only meaningful internally, so this function is intended specifically for
|
||||
// base-branch display — don't use it where the local/remote distinction
|
||||
// matters.
|
||||
func BaseBranchDisplayName(fullBranchName string) string {
|
||||
if name, ok := strings.CutPrefix(fullBranchName, "refs/heads/"); ok {
|
||||
return name
|
||||
}
|
||||
if name, ok := strings.CutPrefix(fullBranchName, "refs/remotes/"); ok {
|
||||
if _, withoutRemote, found := strings.Cut(name, "/"); found {
|
||||
return withoutRemote
|
||||
}
|
||||
return name
|
||||
}
|
||||
return fullBranchName
|
||||
func ShortBranchName(fullBranchName string) string {
|
||||
return strings.TrimPrefix(strings.TrimPrefix(fullBranchName, "refs/heads/"), "refs/remotes/")
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) checkedOutByOtherWorktree(branch *models.Branch) bool {
|
||||
@@ -333,8 +320,11 @@ func (self *BranchesHelper) deleteLocalBranchesContinuation(branches []*models.B
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Contexts().Branches.CollapseRangeSelectionToTop()
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
self.c.OnUIThread(func() error {
|
||||
self.c.Contexts().Branches.CollapseRangeSelectionToTop()
|
||||
return nil
|
||||
})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{
|
||||
Mode: types.ASYNC,
|
||||
Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES},
|
||||
})
|
||||
@@ -351,8 +341,11 @@ func (self *BranchesHelper) deleteLocalAndRemoteBranchesContinuation(branches []
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Contexts().Branches.CollapseRangeSelectionToTop()
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
self.c.OnUIThread(func() error {
|
||||
self.c.Contexts().Branches.CollapseRangeSelectionToTop()
|
||||
return nil
|
||||
})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{
|
||||
Mode: types.ASYNC,
|
||||
Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.REMOTES, types.FILES},
|
||||
})
|
||||
@@ -409,14 +402,31 @@ func (self *BranchesHelper) PostFetchRefresh(fetchErr error, background bool) er
|
||||
if self.c.UserConfig().Git.AutoForwardBranches != "none" {
|
||||
scope = append(scope, types.WORKTREES)
|
||||
}
|
||||
self.c.Refresh(types.RefreshOptions{Scope: scope, Mode: types.SYNC, Background: background})
|
||||
if fetchErr != nil {
|
||||
return fetchErr
|
||||
}
|
||||
return self.AutoForwardBranches()
|
||||
// AutoForwardBranches reads Model.Branches, which the branches refresh writes
|
||||
// via a bounce, so it has to run in Then rather than right after Refresh
|
||||
// returns (where it would still see the previous branches).
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{
|
||||
Scope: scope,
|
||||
Mode: types.SYNC,
|
||||
Background: background,
|
||||
Then: func() error {
|
||||
if fetchErr != nil {
|
||||
return nil
|
||||
}
|
||||
err := self.AutoForwardBranches(background)
|
||||
if background && err != nil {
|
||||
// The background poller discards this return value, so surface
|
||||
// the error in the log rather than as a popup for background work.
|
||||
self.c.Log.Error(err)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
},
|
||||
})
|
||||
return fetchErr
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) AutoForwardBranches() error {
|
||||
func (self *BranchesHelper) AutoForwardBranches(background bool) error {
|
||||
if self.c.UserConfig().Git.AutoForwardBranches == "none" {
|
||||
return nil
|
||||
}
|
||||
@@ -448,7 +458,7 @@ func (self *BranchesHelper) AutoForwardBranches() error {
|
||||
self.c.LogCommand(strings.TrimRight(updateCommands, "\n"), false)
|
||||
err := self.c.Git().Branch.UpdateBranchRefs(updateCommands)
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES}, Mode: types.SYNC})
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES}, Mode: types.SYNC, Background: background})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ func (self *CherryPickHelper) Paste() error {
|
||||
|
||||
cherryPickedCommits := self.getData().CherryPickedCommits
|
||||
result := self.c.Git().Rebase.CherryPickCommits(cherryPickedCommits)
|
||||
err := self.rebaseHelper.CheckMergeOrRebaseWithRefreshOptions(result, types.RefreshOptions{Mode: types.SYNC})
|
||||
err := self.rebaseHelper.CheckMergeOrRebaseWithRefreshOptionsFromUIThread(result, types.RefreshOptions{Mode: types.SYNC})
|
||||
if err != nil {
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ func (self *GpgHelper) runAndStream(
|
||||
) error {
|
||||
return self.c.WithWaitingStatus(waitingStatus, func(gocui.Task) error {
|
||||
if err := cmdObj.StreamOutput().Run(); err != nil {
|
||||
self.c.Refresh(failureRefreshOptions)
|
||||
self.c.RefreshFromWorker(failureRefreshOptions)
|
||||
return fmt.Errorf(
|
||||
self.c.Tr.GitCommandFailed, self.c.UserConfig().Keybinding.Universal.ExtrasMenu,
|
||||
)
|
||||
@@ -100,7 +100,7 @@ func (self *GpgHelper) runAndStream(
|
||||
}
|
||||
}
|
||||
|
||||
self.c.Refresh(successRefreshOptions)
|
||||
self.c.RefreshFromWorker(successRefreshOptions)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ type Helpers struct {
|
||||
Search *SearchHelper
|
||||
Worktree *WorktreeHelper
|
||||
SubCommits *SubCommitsHelper
|
||||
BaseBranch *BaseBranchHelper
|
||||
}
|
||||
|
||||
func NewStubHelpers() *Helpers {
|
||||
@@ -91,6 +90,5 @@ func NewStubHelpers() *Helpers {
|
||||
Search: &SearchHelper{},
|
||||
Worktree: &WorktreeHelper{},
|
||||
SubCommits: &SubCommitsHelper{},
|
||||
BaseBranch: &BaseBranchHelper{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,22 +138,18 @@ func (self *InlineStatusHelper) stop(opts InlineStatusOpts) {
|
||||
|
||||
self.c.State().ClearItemOperation(opts.Item)
|
||||
|
||||
// When recording a demo we need to re-render the context again here to
|
||||
// remove the inline status. In normal usage we don't want to do this
|
||||
// because in the case of pushing a branch this would first reveal the ↑3↓7
|
||||
// status from before the push for a brief moment, to be replaced by a green
|
||||
// checkmark a moment later when the async refresh is done. This looks
|
||||
// jarring, so normally we rely on the async refresh to redraw with the
|
||||
// status removed. (In some rare cases, where there's no refresh at all, we
|
||||
// need to redraw manually in the controller; see TagsController.push() for
|
||||
// an example.)
|
||||
//
|
||||
// In demos, however, we turn all async refreshes into sync ones, because
|
||||
// this looks better in demos. In this case the refresh happens while the
|
||||
// status is still set, so we need to render again after removing it.
|
||||
if self.c.InDemo() {
|
||||
self.renderContext(opts.ContextKey)
|
||||
}
|
||||
// Re-render the context to remove the inline status now that the operation
|
||||
// finished. Any refresh it triggered must be synchronous, not async: by the
|
||||
// time we get here a synchronous refresh has already updated the model and
|
||||
// queued its own re-render, and since UI-thread callbacks run in order, the
|
||||
// render we queue here runs after it and draws the up-to-date model without
|
||||
// the inline status. An async refresh might not have updated the model yet,
|
||||
// so this render could briefly show the stale, pre-operation model: when
|
||||
// pushing a branch, for example, it would flash the old ↑3↓7 ahead/behind
|
||||
// counts for a moment before the refresh replaced them with a green
|
||||
// checkmark. (Operations that don't refresh at all are fine too: there's
|
||||
// nothing stale to show, so this just drops the status.)
|
||||
self.renderContext(opts.ContextKey)
|
||||
}
|
||||
|
||||
func (self *InlineStatusHelper) renderContext(contextKey types.ContextKey) {
|
||||
|
||||
@@ -19,8 +19,6 @@ import (
|
||||
type MergeAndRebaseHelper struct {
|
||||
c *HelperCommon
|
||||
|
||||
baseBranchHelper *BaseBranchHelper
|
||||
|
||||
// Whether the "continue the rebase/merge?" prompt is currently on screen.
|
||||
// We use this to auto-dismiss it if the operation stops being in the state
|
||||
// that the prompt is offering to act on (e.g. it was continued or aborted
|
||||
@@ -31,11 +29,9 @@ type MergeAndRebaseHelper struct {
|
||||
|
||||
func NewMergeAndRebaseHelper(
|
||||
c *HelperCommon,
|
||||
baseBranchHelper *BaseBranchHelper,
|
||||
) *MergeAndRebaseHelper {
|
||||
return &MergeAndRebaseHelper{
|
||||
c: c,
|
||||
baseBranchHelper: baseBranchHelper,
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +79,22 @@ func (self *MergeAndRebaseHelper) ContinueRebase() error {
|
||||
}
|
||||
|
||||
func (self *MergeAndRebaseHelper) genericMergeCommand(command string) error {
|
||||
// The menu/prompt/confirm handlers that reach here run on the UI thread and
|
||||
// spin up a worker (via the waiting status below) to do the actual work.
|
||||
return self.genericMergeCommandImpl(command, true, false)
|
||||
}
|
||||
|
||||
// genericMergeCommandImpl runs a merge/rebase continue/skip/abort and handles
|
||||
// the result. Continuing can be slow (it may replay many commits), so the
|
||||
// non-subprocess path runs on a worker with a waiting status.
|
||||
//
|
||||
// showWaitingStatus is false only for the recursive auto-skip in
|
||||
// checkMergeOrRebaseImpl: that call already runs on the caller's thread (the
|
||||
// worker of the enclosing waiting status, or the UI thread for the synchronous
|
||||
// callers), so it must not spin up a second one. calledFromWorker says which of
|
||||
// those two the body runs on, so the post-action refresh picks Refresh vs
|
||||
// RefreshFromWorker correctly.
|
||||
func (self *MergeAndRebaseHelper) genericMergeCommandImpl(command string, showWaitingStatus bool, calledFromWorker bool) error {
|
||||
status := self.c.Git().Status.WorkingTreeState()
|
||||
|
||||
if status.None() {
|
||||
@@ -120,19 +132,30 @@ func (self *MergeAndRebaseHelper) genericMergeCommand(command string) error {
|
||||
if needsSubprocess {
|
||||
// TODO: see if we should be calling more of the code from self.Git.Rebase.GenericMergeOrRebaseAction
|
||||
success, err := self.c.RunSubprocess(self.c.Git().Rebase.GenericMergeOrRebaseActionCmdObj(commandType, command))
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
self.refreshAfterMergeOrRebase(types.RefreshOptions{
|
||||
Mode: types.ASYNC,
|
||||
CommitSelection: commitSelectionAfterMerge(success && selectHeadCommitOnSuccess),
|
||||
})
|
||||
}, calledFromWorker)
|
||||
self.RecordWhetherMergeOrRebaseStartedInLazygit()
|
||||
return err
|
||||
}
|
||||
result := self.c.Git().Rebase.GenericMergeOrRebaseAction(commandType, command)
|
||||
return self.CheckMergeOrRebaseWithRefreshOptions(result,
|
||||
types.RefreshOptions{
|
||||
Mode: types.ASYNC,
|
||||
CommitSelection: commitSelectionAfterMerge(result == nil && selectHeadCommitOnSuccess),
|
||||
|
||||
runAction := func(calledFromWorker bool) error {
|
||||
result := self.c.Git().Rebase.GenericMergeOrRebaseAction(commandType, command)
|
||||
return self.checkMergeOrRebaseImpl(result,
|
||||
types.RefreshOptions{
|
||||
Mode: types.ASYNC,
|
||||
CommitSelection: commitSelectionAfterMerge(result == nil && selectHeadCommitOnSuccess),
|
||||
}, calledFromWorker)
|
||||
}
|
||||
|
||||
if showWaitingStatus {
|
||||
return self.c.WithWaitingStatus(status.Title(self.c.Tr), func(gocui.Task) error {
|
||||
// The waiting status ran runAction on a worker.
|
||||
return runAction(true)
|
||||
})
|
||||
}
|
||||
return runAction(calledFromWorker)
|
||||
}
|
||||
|
||||
// commitSelectionAfterMerge maps whether a merge/rebase/pull created a new
|
||||
@@ -187,17 +210,34 @@ func (self *MergeAndRebaseHelper) RecordWhetherMergeOrRebaseStartedInLazygit() {
|
||||
self.c.Git().Status.WorkingTreeState().Any())
|
||||
}
|
||||
|
||||
// CheckMergeOrRebaseWithRefreshOptions handles the result of a merge/rebase
|
||||
// step and refreshes. It's for callers running on a worker (the
|
||||
// WithWaitingStatus / WithInlineStatus handlers), which is the large majority;
|
||||
// UI-thread callers use CheckMergeOrRebaseWithRefreshOptionsFromUIThread.
|
||||
func (self *MergeAndRebaseHelper) CheckMergeOrRebaseWithRefreshOptions(result error, refreshOptions types.RefreshOptions) error {
|
||||
self.c.Refresh(refreshOptions)
|
||||
return self.checkMergeOrRebaseImpl(result, refreshOptions, true)
|
||||
}
|
||||
|
||||
// CheckMergeOrRebaseWithRefreshOptionsFromUIThread is like
|
||||
// CheckMergeOrRebaseWithRefreshOptions, but for the callers that run the
|
||||
// merge/rebase synchronously on the UI thread (the WithWaitingStatusSync
|
||||
// move/revert/squash-fixups/cherry-pick-paste/patch-discard handlers, kept sync
|
||||
// so rapid key presses batch) rather than on a worker.
|
||||
func (self *MergeAndRebaseHelper) CheckMergeOrRebaseWithRefreshOptionsFromUIThread(result error, refreshOptions types.RefreshOptions) error {
|
||||
return self.checkMergeOrRebaseImpl(result, refreshOptions, false)
|
||||
}
|
||||
|
||||
func (self *MergeAndRebaseHelper) checkMergeOrRebaseImpl(result error, refreshOptions types.RefreshOptions, calledFromWorker bool) error {
|
||||
self.refreshAfterMergeOrRebase(refreshOptions, calledFromWorker)
|
||||
|
||||
self.RecordWhetherMergeOrRebaseStartedInLazygit()
|
||||
|
||||
if result == nil {
|
||||
return nil
|
||||
} else if strings.Contains(result.Error(), "No changes - did you forget to use") {
|
||||
return self.genericMergeCommand(REBASE_OPTION_SKIP)
|
||||
return self.genericMergeCommandImpl(REBASE_OPTION_SKIP, false, calledFromWorker)
|
||||
} else if strings.Contains(result.Error(), "The previous cherry-pick is now empty") {
|
||||
return self.genericMergeCommand(REBASE_OPTION_SKIP)
|
||||
return self.genericMergeCommandImpl(REBASE_OPTION_SKIP, false, calledFromWorker)
|
||||
} else if strings.Contains(result.Error(), "No rebase in progress?") {
|
||||
// assume in this case that we're already done
|
||||
return nil
|
||||
@@ -205,6 +245,18 @@ func (self *MergeAndRebaseHelper) CheckMergeOrRebaseWithRefreshOptions(result er
|
||||
return self.CheckForConflicts(result)
|
||||
}
|
||||
|
||||
// refreshAfterMergeOrRebase issues the post-action refresh on the entry point
|
||||
// that matches the thread the merge/rebase ran on: RefreshFromWorker for the
|
||||
// worker callers, Refresh for the ones that stayed synchronously on the UI
|
||||
// thread.
|
||||
func (self *MergeAndRebaseHelper) refreshAfterMergeOrRebase(refreshOptions types.RefreshOptions, calledFromWorker bool) {
|
||||
if calledFromWorker {
|
||||
self.c.RefreshFromWorker(refreshOptions)
|
||||
} else {
|
||||
self.c.Refresh(refreshOptions)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeAndRebaseHelper) CheckMergeOrRebase(result error) error {
|
||||
return self.CheckMergeOrRebaseWithRefreshOptions(result, types.RefreshOptions{Mode: types.ASYNC})
|
||||
}
|
||||
@@ -215,7 +267,7 @@ func (self *MergeAndRebaseHelper) CheckMergeOrRebase(result error) error {
|
||||
// before the refresh.
|
||||
func (self *MergeAndRebaseHelper) CheckMergeOrRebaseAndSelectHeadCommit(result error) error {
|
||||
return self.CheckMergeOrRebaseWithRefreshOptions(result,
|
||||
types.RefreshOptions{Mode: types.ASYNC, CommitSelection: commitSelectionAfterMerge(result == nil)})
|
||||
types.RefreshOptions{Mode: types.SYNC, CommitSelection: commitSelectionAfterMerge(result == nil)})
|
||||
}
|
||||
|
||||
func (self *MergeAndRebaseHelper) CheckForConflicts(result error) error {
|
||||
@@ -289,29 +341,36 @@ func (self *MergeAndRebaseHelper) PromptToContinueRebase() error {
|
||||
// Need to refresh the files to be really sure if this is the case.
|
||||
// We would otherwise be relying on lazygit's auto-refresh on focus,
|
||||
// but this is not supported by all terminals or on all platforms.
|
||||
//
|
||||
// The model.Files update is bounced onto the UI thread, so we have
|
||||
// to read it in Then; reading it inline here would see the previous
|
||||
// model.
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES},
|
||||
Then: func() error {
|
||||
unstagedFiles := GetUnstagedFilesExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules)
|
||||
if len(unstagedFiles) > 0 {
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.Continue,
|
||||
Prompt: self.c.Tr.UnstagedFilesAfterConflictsResolved,
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.StageAllFiles)
|
||||
if err := self.c.Git().WorkingTree.StageFiles(unstagedFiles, []string{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.genericMergeCommand(REBASE_OPTION_CONTINUE)
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.genericMergeCommand(REBASE_OPTION_CONTINUE)
|
||||
},
|
||||
})
|
||||
|
||||
unstagedFiles := GetUnstagedFilesExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules)
|
||||
if len(unstagedFiles) > 0 {
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.Continue,
|
||||
Prompt: self.c.Tr.UnstagedFilesAfterConflictsResolved,
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.StageAllFiles)
|
||||
if err := self.c.Git().WorkingTree.StageFiles(unstagedFiles, []string{}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.genericMergeCommand(REBASE_OPTION_CONTINUE)
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.genericMergeCommand(REBASE_OPTION_CONTINUE)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
@@ -347,22 +406,13 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
|
||||
disabledReason = &types.DisabledReason{Text: self.c.Tr.CantRebaseOntoSelf}
|
||||
}
|
||||
|
||||
baseBranch, baseAmbiguous, baseCandidates, err := self.baseBranchHelper.ResolveBaseBranch(checkedOutBranch)
|
||||
baseBranch, err := self.c.Git().Loaders.BranchLoader.GetBaseBranch(checkedOutBranch, self.c.Model().MainBranches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
baseBranchLabel := BaseBranchDisplayName(baseBranch)
|
||||
switch {
|
||||
case baseBranch == "":
|
||||
baseBranchLabel = self.c.Tr.CouldNotDetermineBaseBranch
|
||||
if baseBranch == "" {
|
||||
baseBranch = self.c.Tr.CouldNotDetermineBaseBranch
|
||||
baseBranchDisabledReason = &types.DisabledReason{Text: self.c.Tr.CouldNotDetermineBaseBranch}
|
||||
case baseAmbiguous:
|
||||
shortNames := lo.Map(baseCandidates, func(ref string, _ int) string {
|
||||
return BaseBranchDisplayName(ref)
|
||||
})
|
||||
baseBranchLabel = utils.ResolvePlaceholderString(self.c.Tr.PickBaseBranchLabel,
|
||||
map[string]string{"candidates": strings.Join(shortNames, ", ")},
|
||||
)
|
||||
}
|
||||
|
||||
menuItems := []*types.MenuItem{
|
||||
@@ -374,8 +424,8 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
|
||||
DisabledReason: disabledReason,
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.RebaseBranch)
|
||||
baseCommit := self.c.Modes().MarkedBaseCommit.GetHash()
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(task gocui.Task) error {
|
||||
baseCommit := self.c.Modes().MarkedBaseCommit.GetHash()
|
||||
var err error
|
||||
if baseCommit != "" {
|
||||
err = self.c.Git().Rebase.RebaseBranchFromBaseCommit(ref, baseCommit)
|
||||
@@ -384,7 +434,9 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
|
||||
}
|
||||
err = self.CheckMergeOrRebase(err)
|
||||
if err == nil {
|
||||
return self.ResetMarkedBaseCommit()
|
||||
self.c.OnUIThread(func() error {
|
||||
return self.ResetMarkedBaseCommit()
|
||||
})
|
||||
}
|
||||
return err
|
||||
})
|
||||
@@ -400,51 +452,52 @@ func (self *MergeAndRebaseHelper) RebaseOntoRef(ref string) error {
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.RebaseBranch)
|
||||
baseCommit := self.c.Modes().MarkedBaseCommit.GetHash()
|
||||
var err error
|
||||
if baseCommit != "" {
|
||||
err = self.c.Git().Rebase.EditRebaseFromBaseCommit(ref, baseCommit)
|
||||
} else {
|
||||
err = self.c.Git().Rebase.EditRebase(ref)
|
||||
}
|
||||
if err = self.CheckMergeOrRebase(err); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = self.ResetMarkedBaseCommit(); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
|
||||
return nil
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(task gocui.Task) error {
|
||||
var err error
|
||||
if baseCommit != "" {
|
||||
err = self.c.Git().Rebase.EditRebaseFromBaseCommit(ref, baseCommit)
|
||||
} else {
|
||||
err = self.c.Git().Rebase.EditRebase(ref)
|
||||
}
|
||||
if err = self.CheckMergeOrRebase(err); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.OnUIThread(func() error {
|
||||
if err := self.ResetMarkedBaseCommit(); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: utils.ResolvePlaceholderString(self.c.Tr.RebaseOntoBaseBranch,
|
||||
map[string]string{"baseBranch": baseBranchLabel},
|
||||
map[string]string{"baseBranch": ShortBranchName(baseBranch)},
|
||||
),
|
||||
Keys: menuKey('b'),
|
||||
DisabledReason: baseBranchDisabledReason,
|
||||
Tooltip: self.c.Tr.RebaseOntoBaseBranchTooltip,
|
||||
OnPress: func() error {
|
||||
doRebase := func(base string) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.RebaseBranch)
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(task gocui.Task) error {
|
||||
baseCommit := self.c.Modes().MarkedBaseCommit.GetHash()
|
||||
var err error
|
||||
if baseCommit != "" {
|
||||
err = self.c.Git().Rebase.RebaseBranchFromBaseCommit(base, baseCommit)
|
||||
} else {
|
||||
err = self.c.Git().Rebase.RebaseBranch(base)
|
||||
}
|
||||
err = self.CheckMergeOrRebase(err)
|
||||
if err == nil {
|
||||
self.c.LogAction(self.c.Tr.Actions.RebaseBranch)
|
||||
baseCommit := self.c.Modes().MarkedBaseCommit.GetHash()
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(task gocui.Task) error {
|
||||
var err error
|
||||
if baseCommit != "" {
|
||||
err = self.c.Git().Rebase.RebaseBranchFromBaseCommit(baseBranch, baseCommit)
|
||||
} else {
|
||||
err = self.c.Git().Rebase.RebaseBranch(baseBranch)
|
||||
}
|
||||
err = self.CheckMergeOrRebase(err)
|
||||
if err == nil {
|
||||
self.c.OnUIThread(func() error {
|
||||
return self.ResetMarkedBaseCommit()
|
||||
}
|
||||
return err
|
||||
})
|
||||
}
|
||||
if baseAmbiguous {
|
||||
return self.baseBranchHelper.ShowPicker(checkedOutBranch, baseCandidates, doRebase)
|
||||
}
|
||||
return doRebase(baseBranch)
|
||||
})
|
||||
}
|
||||
return err
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -583,36 +636,42 @@ func (self *MergeAndRebaseHelper) MergeRefIntoCheckedOutBranch(refName string) e
|
||||
func (self *MergeAndRebaseHelper) RegularMerge(refName string, variant git_commands.MergeVariant) func() error {
|
||||
return func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.Merge)
|
||||
err := self.c.Git().Branch.Merge(refName, variant)
|
||||
return self.CheckMergeOrRebaseAndSelectHeadCommit(err)
|
||||
return self.c.WithWaitingStatus(self.c.Tr.MergingStatus, func(gocui.Task) error {
|
||||
err := self.c.Git().Branch.Merge(refName, variant)
|
||||
return self.CheckMergeOrRebaseAndSelectHeadCommit(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeAndRebaseHelper) SquashMergeUncommitted(refName string) func() error {
|
||||
return func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.SquashMerge)
|
||||
err := self.c.Git().Branch.Merge(refName, git_commands.MERGE_VARIANT_SQUASH)
|
||||
return self.CheckMergeOrRebase(err)
|
||||
return self.c.WithWaitingStatus(self.c.Tr.MergingStatus, func(gocui.Task) error {
|
||||
err := self.c.Git().Branch.Merge(refName, git_commands.MERGE_VARIANT_SQUASH)
|
||||
return self.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *MergeAndRebaseHelper) SquashMergeCommitted(refName, checkedOutBranchName string) func() error {
|
||||
return func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.SquashMerge)
|
||||
err := self.c.Git().Branch.Merge(refName, git_commands.MERGE_VARIANT_SQUASH)
|
||||
if err = self.CheckMergeOrRebase(err); err != nil {
|
||||
return err
|
||||
}
|
||||
message := utils.ResolvePlaceholderString(self.c.UserConfig().Git.Merging.SquashMergeMessage, map[string]string{
|
||||
"selectedRef": refName,
|
||||
"currentBranch": checkedOutBranchName,
|
||||
return self.c.WithWaitingStatus(self.c.Tr.MergingStatus, func(gocui.Task) error {
|
||||
err := self.c.Git().Branch.Merge(refName, git_commands.MERGE_VARIANT_SQUASH)
|
||||
if err = self.CheckMergeOrRebase(err); err != nil {
|
||||
return err
|
||||
}
|
||||
message := utils.ResolvePlaceholderString(self.c.UserConfig().Git.Merging.SquashMergeMessage, map[string]string{
|
||||
"selectedRef": refName,
|
||||
"currentBranch": checkedOutBranchName,
|
||||
})
|
||||
err = self.c.Git().Commit.CommitCmdObj(message, "", false).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return nil
|
||||
})
|
||||
err = self.c.Git().Commit.CommitCmdObj(message, "", false).Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,11 +51,17 @@ func (self *MergeConflictsHelper) resetMergeState() {
|
||||
self.context().GetState().Reset()
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) EscapeMerge() error {
|
||||
func (self *MergeConflictsHelper) EscapeMerge(background bool) error {
|
||||
self.resetMergeState()
|
||||
|
||||
// doing this in separate UI thread so that we're not still holding the lock by the time refresh the file
|
||||
self.c.OnUIThread(func() error {
|
||||
onUIThread := self.c.OnUIThread
|
||||
if background {
|
||||
// Reached from a background files refresh; keep it off the busy count
|
||||
// (see the *Background dispatch methods) so it doesn't block a repo switch.
|
||||
onUIThread = self.c.OnUIThreadBackground
|
||||
}
|
||||
onUIThread(func() error {
|
||||
// There is a race condition here: refreshing the files scope can trigger the
|
||||
// confirmation context to be pushed if all conflicts are resolved (prompting
|
||||
// to continue the merge/rebase. In that case, we don't want to then push the
|
||||
@@ -120,7 +126,7 @@ func (self *MergeConflictsHelper) Render() {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *MergeConflictsHelper) RefreshMergeState() error {
|
||||
func (self *MergeConflictsHelper) RefreshMergeState(background bool) error {
|
||||
self.c.Contexts().MergeConflicts.GetMutex().Lock()
|
||||
defer self.c.Contexts().MergeConflicts.GetMutex().Unlock()
|
||||
|
||||
@@ -134,7 +140,7 @@ func (self *MergeConflictsHelper) RefreshMergeState() error {
|
||||
}
|
||||
|
||||
if !hasConflicts {
|
||||
return self.EscapeMerge()
|
||||
return self.EscapeMerge(background)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -191,7 +191,7 @@ func (self *ModeHelper) ClearFiltering() error {
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Scope: ScopesToRefreshWhenFilteringModeChanges(),
|
||||
Then: func() {
|
||||
Then: func() error {
|
||||
// Find the commit that was last selected in filtering mode, and select it again after refreshing
|
||||
if !self.c.Contexts().LocalCommits.SelectCommitByHash(selectedCommitHash) {
|
||||
// If we couldn't find it (either because no commit was selected
|
||||
@@ -202,6 +202,7 @@ func (self *ModeHelper) ClearFiltering() error {
|
||||
}
|
||||
|
||||
self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
return nil
|
||||
|
||||
@@ -66,20 +66,21 @@ func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpt
|
||||
}
|
||||
|
||||
// get diff from commit file that's currently selected
|
||||
path := self.c.Contexts().CommitFiles.GetSelectedPath()
|
||||
if path == "" {
|
||||
file := self.c.Contexts().CommitFiles.GetSelectedFile()
|
||||
if file == nil {
|
||||
return
|
||||
}
|
||||
|
||||
from, to := self.c.Contexts().CommitFiles.GetFromAndToForDiff()
|
||||
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from)
|
||||
diff, err := self.c.Git().WorkingTree.ShowFileDiff(from, to, reverse, path, true)
|
||||
diff, err := self.c.Git().WorkingTree.ShowFileDiff(from, to, reverse, file.Path, file.PreviousPath, true)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
secondaryDiff := self.c.Git().Patch.PatchBuilder.RenderPatchForFile(patch.RenderPatchForFileOpts{
|
||||
Filename: path,
|
||||
Filename: file.Path,
|
||||
PreviousPath: file.PreviousPath,
|
||||
Plain: false,
|
||||
Reverse: false,
|
||||
TurnAddedFilesIntoDiffAgainstEmptyFile: true,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,31 +18,19 @@ import (
|
||||
type RefsHelper struct {
|
||||
c *HelperCommon
|
||||
|
||||
rebaseHelper *MergeAndRebaseHelper
|
||||
baseBranchHelper *BaseBranchHelper
|
||||
rebaseHelper *MergeAndRebaseHelper
|
||||
}
|
||||
|
||||
func NewRefsHelper(
|
||||
c *HelperCommon,
|
||||
rebaseHelper *MergeAndRebaseHelper,
|
||||
baseBranchHelper *BaseBranchHelper,
|
||||
) *RefsHelper {
|
||||
return &RefsHelper{
|
||||
c: c,
|
||||
rebaseHelper: rebaseHelper,
|
||||
baseBranchHelper: baseBranchHelper,
|
||||
c: c,
|
||||
rebaseHelper: rebaseHelper,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *RefsHelper) SelectFirstBranchAndFirstCommit() {
|
||||
self.c.Contexts().Branches.SetSelection(0)
|
||||
self.c.Contexts().ReflogCommits.SetSelection(0)
|
||||
self.c.Contexts().LocalCommits.SetSelection(0)
|
||||
self.c.Contexts().Branches.GetView().SetOriginY(0)
|
||||
self.c.Contexts().ReflogCommits.GetView().SetOriginY(0)
|
||||
self.c.Contexts().LocalCommits.GetView().SetOriginY(0)
|
||||
}
|
||||
|
||||
func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions) error {
|
||||
waitingStatus := options.WaitingStatus
|
||||
if waitingStatus == "" {
|
||||
@@ -52,8 +40,6 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
|
||||
cmdOptions := git_commands.CheckoutOptions{Force: false, EnvVars: options.EnvVars}
|
||||
|
||||
refresh := func() {
|
||||
self.SelectFirstBranchAndFirstCommit()
|
||||
|
||||
// loading a heap of commits is slow so we limit them whenever doing a reset
|
||||
self.c.Contexts().LocalCommits.SetLimitCommits(true)
|
||||
|
||||
@@ -69,11 +55,12 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
|
||||
if options.RefreshPullRequests {
|
||||
scope = append(scope, types.PULL_REQUESTS)
|
||||
}
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.BLOCK_UI,
|
||||
Scope: scope,
|
||||
KeepBranchSelectionIndex: true,
|
||||
CommitSelection: types.KeepCommitSelectionIndex,
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{
|
||||
Mode: types.BLOCK_UI,
|
||||
Scope: scope,
|
||||
BranchSelection: types.SelectCheckedOutBranch,
|
||||
CommitSelection: types.SelectHeadCommit,
|
||||
SelectTopReflogCommit: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -212,12 +199,14 @@ func (self *RefsHelper) ResetToRef(ref string, strength string, envVars []string
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Contexts().LocalCommits.SetSelection(0)
|
||||
self.c.Contexts().ReflogCommits.SetSelection(0)
|
||||
// loading a heap of commits is slow so we limit them whenever doing a reset
|
||||
self.c.Contexts().LocalCommits.SetLimitCommits(true)
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES, types.BRANCHES, types.REFLOG, types.COMMITS}, CommitSelection: types.KeepCommitSelectionIndex})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{
|
||||
Scope: []types.RefreshableView{types.FILES, types.BRANCHES, types.REFLOG, types.COMMITS},
|
||||
CommitSelection: types.SelectHeadCommit,
|
||||
SelectTopReflogCommit: true,
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -291,7 +280,9 @@ func (self *RefsHelper) CreateGitResetMenu(name string, ref string) error {
|
||||
Prompt: self.c.Tr.ResetHardConfirmation,
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction("Reset")
|
||||
return self.ResetToRef(ref, row.strength, []string{})
|
||||
return self.c.WithWaitingStatus(self.c.Tr.ResettingStatus, func(gocui.Task) error {
|
||||
return self.ResetToRef(ref, row.strength, []string{})
|
||||
})
|
||||
},
|
||||
})
|
||||
},
|
||||
@@ -376,12 +367,11 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
|
||||
self.c.Context().Push(self.c.Contexts().Branches, types.OnFocusOpts{})
|
||||
}
|
||||
|
||||
self.SelectFirstBranchAndFirstCommit()
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.BLOCK_UI,
|
||||
KeepBranchSelectionIndex: true,
|
||||
CommitSelection: types.KeepCommitSelectionIndex,
|
||||
Mode: types.BLOCK_UI,
|
||||
BranchSelection: types.SelectCheckedOutBranch,
|
||||
CommitSelection: types.SelectHeadCommit,
|
||||
SelectTopReflogCommit: true,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -431,11 +421,13 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
|
||||
|
||||
func (self *RefsHelper) MoveCommitsToNewBranch() error {
|
||||
currentBranch := self.c.Model().Branches[0]
|
||||
baseBranchRef, baseAmbiguous, baseCandidates, err := self.baseBranchHelper.ResolveBaseBranch(currentBranch)
|
||||
baseBranchRef, err := self.c.Git().Loaders.BranchLoader.GetBaseBranch(currentBranch, self.c.Model().MainBranches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mustStash := IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules)
|
||||
|
||||
withNewBranchNamePrompt := func(baseBranchName string, f func(string) error) error {
|
||||
prompt := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.NewBranchNameBranchOff,
|
||||
@@ -474,25 +466,19 @@ func (self *RefsHelper) MoveCommitsToNewBranch() error {
|
||||
Title: self.c.Tr.MoveCommitsToNewBranch,
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
return withNewBranchNamePrompt(currentBranch.Name, self.moveCommitsToNewBranchStackedOnCurrentBranch)
|
||||
return withNewBranchNamePrompt(currentBranch.Name, func(newBranchName string) error {
|
||||
return self.moveCommitsToNewBranchStackedOnCurrentBranch(newBranchName, mustStash)
|
||||
})
|
||||
},
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
baseBranchLabel := BaseBranchDisplayName(baseBranchRef)
|
||||
if baseAmbiguous {
|
||||
shortNames := lo.Map(baseCandidates, func(ref string, _ int) string {
|
||||
return BaseBranchDisplayName(ref)
|
||||
})
|
||||
baseBranchLabel = utils.ResolvePlaceholderString(self.c.Tr.PickBaseBranchLabel,
|
||||
map[string]string{"candidates": strings.Join(shortNames, ", ")},
|
||||
)
|
||||
}
|
||||
shortBaseBranchName := ShortBranchName(baseBranchRef)
|
||||
prompt := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.MoveCommitsToNewBranchMenuPrompt,
|
||||
map[string]string{
|
||||
"baseBranchName": baseBranchLabel,
|
||||
"baseBranchName": shortBaseBranchName,
|
||||
},
|
||||
)
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
@@ -500,35 +486,33 @@ func (self *RefsHelper) MoveCommitsToNewBranch() error {
|
||||
Prompt: prompt,
|
||||
Items: []*types.MenuItem{
|
||||
{
|
||||
Label: fmt.Sprintf(self.c.Tr.MoveCommitsToNewBranchFromBaseItem, baseBranchLabel),
|
||||
Label: fmt.Sprintf(self.c.Tr.MoveCommitsToNewBranchFromBaseItem, shortBaseBranchName),
|
||||
OnPress: func() error {
|
||||
moveOff := func(base string) error {
|
||||
return withNewBranchNamePrompt(BaseBranchDisplayName(base), func(newBranchName string) error {
|
||||
return self.moveCommitsToNewBranchOffOfMainBranch(newBranchName, base)
|
||||
})
|
||||
}
|
||||
if baseAmbiguous {
|
||||
return self.baseBranchHelper.ShowPicker(currentBranch, baseCandidates, moveOff)
|
||||
}
|
||||
return moveOff(baseBranchRef)
|
||||
commitsToCherryPick := lo.Filter(self.c.Model().Commits, func(commit *models.Commit, _ int) bool {
|
||||
return commit.Status == models.StatusUnpushed
|
||||
})
|
||||
return withNewBranchNamePrompt(shortBaseBranchName, func(newBranchName string) error {
|
||||
return self.moveCommitsToNewBranchOffOfMainBranch(newBranchName, baseBranchRef, commitsToCherryPick, mustStash)
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: fmt.Sprintf(self.c.Tr.MoveCommitsToNewBranchStackedItem, currentBranch.Name),
|
||||
OnPress: func() error {
|
||||
return withNewBranchNamePrompt(currentBranch.Name, self.moveCommitsToNewBranchStackedOnCurrentBranch)
|
||||
return withNewBranchNamePrompt(currentBranch.Name, func(newBranchName string) error {
|
||||
return self.moveCommitsToNewBranchStackedOnCurrentBranch(newBranchName, mustStash)
|
||||
})
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *RefsHelper) moveCommitsToNewBranchStackedOnCurrentBranch(newBranchName string) error {
|
||||
func (self *RefsHelper) moveCommitsToNewBranchStackedOnCurrentBranch(newBranchName string, mustStash bool) error {
|
||||
if err := self.c.Git().Branch.NewWithoutCheckout(newBranchName, "HEAD"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mustStash := IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules)
|
||||
if mustStash {
|
||||
if err := self.c.Git().Stash.Push(fmt.Sprintf(self.c.Tr.AutoStashForNewBranch, newBranchName)); err != nil {
|
||||
return err
|
||||
@@ -549,22 +533,16 @@ func (self *RefsHelper) moveCommitsToNewBranchStackedOnCurrentBranch(newBranchNa
|
||||
}
|
||||
}
|
||||
|
||||
self.SelectFirstBranchAndFirstCommit()
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.BLOCK_UI,
|
||||
KeepBranchSelectionIndex: true,
|
||||
CommitSelection: types.KeepCommitSelectionIndex,
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{
|
||||
Mode: types.BLOCK_UI,
|
||||
BranchSelection: types.SelectCheckedOutBranch,
|
||||
CommitSelection: types.SelectHeadCommit,
|
||||
SelectTopReflogCommit: true,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *RefsHelper) moveCommitsToNewBranchOffOfMainBranch(newBranchName string, baseBranchRef string) error {
|
||||
commitsToCherryPick := lo.Filter(self.c.Model().Commits, func(commit *models.Commit, _ int) bool {
|
||||
return commit.Status == models.StatusUnpushed
|
||||
})
|
||||
|
||||
mustStash := IsWorkingTreeDirtyExceptSubmodules(self.c.Model().Files, self.c.Model().Submodules)
|
||||
func (self *RefsHelper) moveCommitsToNewBranchOffOfMainBranch(newBranchName string, baseBranchRef string, commitsToCherryPick []*models.Commit, mustStash bool) error {
|
||||
if mustStash {
|
||||
if err := self.c.Git().Stash.Push(fmt.Sprintf(self.c.Tr.AutoStashForNewBranch, newBranchName)); err != nil {
|
||||
return err
|
||||
@@ -591,12 +569,11 @@ func (self *RefsHelper) moveCommitsToNewBranchOffOfMainBranch(newBranchName stri
|
||||
}
|
||||
}
|
||||
|
||||
self.SelectFirstBranchAndFirstCommit()
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.BLOCK_UI,
|
||||
KeepBranchSelectionIndex: true,
|
||||
CommitSelection: types.KeepCommitSelectionIndex,
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{
|
||||
Mode: types.BLOCK_UI,
|
||||
BranchSelection: types.SelectCheckedOutBranch,
|
||||
CommitSelection: types.SelectHeadCommit,
|
||||
SelectTopReflogCommit: true,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/direnv"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/env"
|
||||
"github.com/jesseduffield/lazygit/pkg/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
@@ -44,13 +43,20 @@ func NewRecentReposHelper(
|
||||
}
|
||||
|
||||
func (self *ReposHelper) EnterSubmodule(submodule *models.SubmoduleConfig) error {
|
||||
// Check before pushing onto the repo-path stack, so a refused switch
|
||||
// doesn't leave a stale entry there (which escape would later switch back
|
||||
// to, needlessly reloading the current repo).
|
||||
if self.switchRefusedBecauseBusy() {
|
||||
return nil
|
||||
}
|
||||
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.State().GetRepoPathStack().Push(wd)
|
||||
|
||||
return self.DispatchSwitchToRepo(submodule.FullPath(), context.NO_CONTEXT)
|
||||
return self.switchTo(submodule.FullPath(), self.c.Tr.ErrRepositoryMovedOrDeleted, context.NO_CONTEXT)
|
||||
}
|
||||
|
||||
func (self *ReposHelper) getCurrentBranch(path string) string {
|
||||
@@ -130,10 +136,16 @@ func (self *ReposHelper) CreateRecentReposMenu() error {
|
||||
style.FgMagenta.Sprint(path),
|
||||
},
|
||||
OnPress: func() error {
|
||||
// Check before clearing the stack, so a refused switch doesn't
|
||||
// forget the submodule breadcrumb (which would leave escape
|
||||
// unable to return to the parent repo).
|
||||
if self.switchRefusedBecauseBusy() {
|
||||
return nil
|
||||
}
|
||||
// if we were in a submodule, we want to forget about that stack of repos
|
||||
// so that hitting escape in the new repo does nothing
|
||||
self.c.State().GetRepoPathStack().Clear()
|
||||
return self.DispatchSwitchToRepo(path, context.NO_CONTEXT)
|
||||
return self.switchTo(path, self.c.Tr.ErrRepositoryMovedOrDeleted, context.NO_CONTEXT)
|
||||
},
|
||||
}
|
||||
})
|
||||
@@ -141,59 +153,89 @@ func (self *ReposHelper) CreateRecentReposMenu() error {
|
||||
return self.c.Menu(types.CreateMenuOptions{Title: self.c.Tr.RecentRepos, Items: menuItems})
|
||||
}
|
||||
|
||||
func (self *ReposHelper) DispatchSwitchToRepo(path string, contextKey types.ContextKey) error {
|
||||
return self.DispatchSwitchTo(path, self.c.Tr.ErrRepositoryMovedOrDeleted, contextKey)
|
||||
// SwitchToParentRepo switches back to the repo the current submodule was
|
||||
// entered from (the top of the repo-path stack). Like the other callers that do
|
||||
// work before switching, it checks for an in-flight operation *before* popping
|
||||
// the stack, so a refused switch leaves the stack intact — otherwise the entry
|
||||
// would be consumed and escape would no longer return to the parent once the
|
||||
// operation finished. The caller must only call this when the stack is
|
||||
// non-empty.
|
||||
func (self *ReposHelper) SwitchToParentRepo() error {
|
||||
if self.switchRefusedBecauseBusy() {
|
||||
return nil
|
||||
}
|
||||
return self.switchTo(self.c.State().GetRepoPathStack().Pop(), self.c.Tr.ErrRepositoryMovedOrDeleted, context.NO_CONTEXT)
|
||||
}
|
||||
|
||||
func (self *ReposHelper) DispatchSwitchTo(path string, errMsg string, contextKey types.ContextKey) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.Switching, func(gocui.Task) error {
|
||||
env.UnsetGitLocationEnvVars()
|
||||
originalPath, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil
|
||||
if self.switchRefusedBecauseBusy() {
|
||||
return nil
|
||||
}
|
||||
return self.switchTo(path, errMsg, contextKey)
|
||||
}
|
||||
|
||||
// switchRefusedBecauseBusy reports (and shows a toast) whether a repo switch
|
||||
// must be refused because a foreground git operation is in flight. Switching
|
||||
// reassigns gui.git and the process cwd, so switching mid-operation would run
|
||||
// the operation's remaining git commands against the wrong repo. Callers that
|
||||
// do work before the switch (creating a worktree, recording the repo-path
|
||||
// stack) check this up front, so they don't do that work only to have the
|
||||
// switch refused; the switch itself (switchTo) is then unguarded.
|
||||
func (self *ReposHelper) switchRefusedBecauseBusy() bool {
|
||||
if self.c.GocuiGui().Busy() {
|
||||
self.c.ErrorToast(self.c.Tr.CantSwitchWhileOperationInProgress)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// switchTo switches lazygit to the repository (or worktree) at the given path.
|
||||
// It runs synchronously on the UI thread: the switch swaps gui.State (in
|
||||
// resetState) and reassigns gui.git and the process cwd, all of which the UI
|
||||
// thread also reads, so doing it here rather than on a worker avoids racing
|
||||
// those reads. The heavy data loading is still dispatched asynchronously by the
|
||||
// refresh that onNewRepo kicks off.
|
||||
func (self *ReposHelper) switchTo(path string, errMsg string, contextKey types.ContextKey) error {
|
||||
env.UnsetGitLocationEnvVars()
|
||||
originalPath, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
msg := utils.ResolvePlaceholderString(self.c.Tr.ChangingDirectoryTo, map[string]string{"path": path})
|
||||
self.c.LogCommand(msg, false)
|
||||
|
||||
if err := os.Chdir(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
msg := utils.ResolvePlaceholderString(self.c.Tr.ChangingDirectoryTo, map[string]string{"path": path})
|
||||
self.c.LogCommand(msg, false)
|
||||
|
||||
if err := os.Chdir(path); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errors.New(errMsg)
|
||||
}
|
||||
if err := commands.VerifyInGitRepo(self.c.OS()); err != nil {
|
||||
if err := os.Chdir(originalPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := commands.VerifyInGitRepo(self.c.OS()); err != nil {
|
||||
if err := os.Chdir(originalPath); err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
direnvResult := self.logDirenvResult(direnv.Load(self.c.OS().Cmd))
|
||||
|
||||
direnvResult := self.logDirenvResult(direnv.Load(self.c.OS().Cmd))
|
||||
if err := self.recordDirectoryHelper.RecordCurrentDirectory(); err != nil {
|
||||
self.c.Log.Errorf("error recording current directory: %v", err)
|
||||
}
|
||||
|
||||
if err := self.recordDirectoryHelper.RecordCurrentDirectory(); err != nil {
|
||||
self.c.Log.Errorf("error recording current directory: %v", err)
|
||||
}
|
||||
if err := self.onNewRepo(appTypes.StartArgs{}, contextKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Mutexes().RefreshingFilesMutex.Lock()
|
||||
defer self.c.Mutexes().RefreshingFilesMutex.Unlock()
|
||||
if direnvResult.Blocked {
|
||||
self.promptDirenvApproval(direnvResult.EnvrcPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := self.onNewRepo(appTypes.StartArgs{}, contextKey); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if direnvResult.Blocked {
|
||||
self.c.OnUIThread(func() error {
|
||||
self.promptDirenvApproval(direnvResult.EnvrcPath)
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
return direnvResult.Err
|
||||
})
|
||||
return direnvResult.Err
|
||||
}
|
||||
|
||||
// logDirenvResult writes whatever direnv emitted to the command log and the
|
||||
|
||||
@@ -49,7 +49,7 @@ func (self *SubCommitsHelper) ViewSubCommits(opts ViewSubCommitsOpts) error {
|
||||
return err
|
||||
}
|
||||
|
||||
self.setSubCommits(commits)
|
||||
self.c.Model().SubCommits = commits
|
||||
self.refreshHelper.RefreshAuthors(commits)
|
||||
|
||||
subCommitsContext := self.c.Contexts().SubCommits
|
||||
@@ -71,10 +71,3 @@ func (self *SubCommitsHelper) ViewSubCommits(opts ViewSubCommitsOpts) error {
|
||||
self.c.Context().Push(self.c.Contexts().SubCommits, types.OnFocusOpts{})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SubCommitsHelper) setSubCommits(commits []*models.Commit) {
|
||||
self.c.Mutexes().SubCommitsMutex.Lock()
|
||||
defer self.c.Mutexes().SubCommitsMutex.Unlock()
|
||||
|
||||
self.c.Model().SubCommits = commits
|
||||
}
|
||||
|
||||
@@ -137,10 +137,12 @@ func (self *SuggestionsHelper) GetFilePathSuggestionsFunc() func(string) []*type
|
||||
trie.Insert(patricia.Prefix(file), file)
|
||||
}
|
||||
|
||||
// cache the trie for future use
|
||||
self.c.Model().FilesTrie = trie
|
||||
|
||||
self.c.Contexts().Suggestions.RefreshSuggestions()
|
||||
self.c.OnUIThread(func() error {
|
||||
// cache the trie for future use
|
||||
self.c.Model().FilesTrie = trie
|
||||
self.c.Contexts().Suggestions.RefreshSuggestions()
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
@@ -149,7 +149,12 @@ func (self *WorkingTreeHelper) handleCommit(summary string, description string,
|
||||
self.c.LogAction(self.c.Tr.Actions.Commit)
|
||||
return self.gpgHelper.WithGpgHandlingAndSelectHeadCommit(cmdObj, git_commands.CommitGpgSign, self.c.Tr.CommittingStatus,
|
||||
func() error {
|
||||
self.commitsHelper.ClearPreservedCommitMessage()
|
||||
// This runs on a worker when the commit output is streamed, so
|
||||
// bounce the preserved-message write to the UI thread.
|
||||
self.c.OnUIThread(func() error {
|
||||
self.commitsHelper.ClearPreservedCommitMessage()
|
||||
return nil
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -222,15 +227,24 @@ func (self *WorkingTreeHelper) HandleCommitPress() error {
|
||||
}
|
||||
|
||||
func (self *WorkingTreeHelper) WithEnsureCommittableFiles(handler func() error) error {
|
||||
if err := self.prepareFilesForCommit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(self.c.Model().Files) == 0 {
|
||||
return errors.New(self.c.Tr.NoFilesStagedTitle)
|
||||
}
|
||||
|
||||
if !self.AnyStagedFiles() {
|
||||
if self.c.UserConfig().Gui.SkipNoStagedFilesWarning {
|
||||
self.c.LogAction(self.c.Tr.Actions.StageAllFiles)
|
||||
if err := self.c.Git().WorkingTree.StageAll(false); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC,
|
||||
Scope: []types.RefreshableView{types.FILES},
|
||||
Then: handler,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.promptToStageAllAndRetry(handler)
|
||||
}
|
||||
|
||||
@@ -246,7 +260,7 @@ func (self *WorkingTreeHelper) promptToStageAllAndRetry(retry func() error) erro
|
||||
if err := self.c.Git().WorkingTree.StageAll(false); err != nil {
|
||||
return err
|
||||
}
|
||||
self.syncRefresh()
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}})
|
||||
|
||||
return retry()
|
||||
},
|
||||
@@ -255,26 +269,6 @@ func (self *WorkingTreeHelper) promptToStageAllAndRetry(retry func() error) erro
|
||||
return nil
|
||||
}
|
||||
|
||||
// for when you need to refetch files before continuing an action. Runs synchronously.
|
||||
func (self *WorkingTreeHelper) syncRefresh() {
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.FILES}})
|
||||
}
|
||||
|
||||
func (self *WorkingTreeHelper) prepareFilesForCommit() error {
|
||||
noStagedFiles := !self.AnyStagedFiles()
|
||||
if noStagedFiles && self.c.UserConfig().Gui.SkipNoStagedFilesWarning {
|
||||
self.c.LogAction(self.c.Tr.Actions.StageAllFiles)
|
||||
err := self.c.Git().WorkingTree.StageAll(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.syncRefresh()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *WorkingTreeHelper) commitPrefixConfigsForRepo() []config.CommitPrefixConfig {
|
||||
cfg, ok := self.c.UserConfig().Git.CommitPrefixes[self.c.Git().RepoPaths.RepoName()]
|
||||
if ok {
|
||||
|
||||
@@ -163,7 +163,7 @@ func (self *WorktreeHelper) remove(worktree *models.Worktree, force bool, then f
|
||||
return then(task)
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -181,7 +181,7 @@ func (self *WorktreeHelper) Detach(worktree *models.Worktree, then func(gocui.Ta
|
||||
return then(task)
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.WORKTREES, types.BRANCHES, types.FILES}})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -426,12 +426,27 @@ func (self *WorktreeHelper) promptForWorktreeLocation(dirName string, prompt str
|
||||
}
|
||||
|
||||
func (self *WorktreeHelper) createWorktree(opts git_commands.NewWorktreeOpts, contextKey types.ContextKey) error {
|
||||
// Check now, before we create the worktree, rather than when we come to
|
||||
// switch to it afterwards: by then this operation's own waiting-status
|
||||
// spinner would make Busy() true and refuse our own switch.
|
||||
if self.reposHelper.switchRefusedBecauseBusy() {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AddingWorktree, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.AddWorktree)
|
||||
if err := self.c.Git().Worktree.New(opts); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.reposHelper.DispatchSwitchTo(opts.Path, self.c.Tr.ErrWorktreeMovedOrRemoved, contextKey)
|
||||
// The switch swaps gui.State and must run on the UI thread, but
|
||||
// we're on a worker here (creating the worktree is git work), so
|
||||
// dispatch it. It's unguarded (switchTo, not DispatchSwitchTo)
|
||||
// because we checked above and creating the worktree is now
|
||||
// complete, so switching to it is safe.
|
||||
self.c.OnUIThread(func() error {
|
||||
return self.reposHelper.switchTo(opts.Path, self.c.Tr.ErrWorktreeMovedOrRemoved, contextKey)
|
||||
})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -340,9 +340,11 @@ func (self *LocalCommitsController) squashDown(selectedCommits []*models.Commit,
|
||||
Title: self.c.Tr.Squash,
|
||||
Prompt: self.c.Tr.SureSquashThisCommit,
|
||||
HandleConfirm: func() error {
|
||||
commits := self.c.Model().Commits
|
||||
self.selectRebaseResultCommit(startIdx)
|
||||
return self.c.WithWaitingStatus(self.c.Tr.SquashingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.SquashCommitDown)
|
||||
return self.interactiveRebase(todo.Squash, startIdx, endIdx)
|
||||
return self.interactiveRebase(commits, todo.Squash, startIdx, endIdx)
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -362,9 +364,11 @@ func (self *LocalCommitsController) fixup(selectedCommits []*models.Commit, star
|
||||
Label: self.c.Tr.Fixup,
|
||||
Keys: menuKey('f'),
|
||||
OnPress: func() error {
|
||||
commits := self.c.Model().Commits
|
||||
self.selectRebaseResultCommit(startIdx)
|
||||
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.FixupCommit)
|
||||
return self.interactiveRebase(todo.Fixup, startIdx, endIdx)
|
||||
return self.interactiveRebase(commits, todo.Fixup, startIdx, endIdx)
|
||||
})
|
||||
},
|
||||
Tooltip: self.c.Tr.FixupTooltip,
|
||||
@@ -373,9 +377,11 @@ func (self *LocalCommitsController) fixup(selectedCommits []*models.Commit, star
|
||||
Label: self.c.Tr.FixupKeepMessage,
|
||||
Keys: menuKey('c'),
|
||||
OnPress: func() error {
|
||||
commits := self.c.Model().Commits
|
||||
self.selectRebaseResultCommit(startIdx)
|
||||
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.FixupCommitKeepMessage)
|
||||
return self.interactiveRebaseWithFlag(todo.Fixup, startIdx, endIdx, "-C")
|
||||
return self.interactiveRebaseWithFlag(commits, todo.Fixup, startIdx, endIdx, "-C")
|
||||
})
|
||||
},
|
||||
Tooltip: self.c.Tr.FixupKeepMessageTooltip,
|
||||
@@ -475,7 +481,9 @@ func (self *LocalCommitsController) switchFromCommitMessagePanelToEditor(filepat
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) handleReword(summary string, description string) error {
|
||||
if models.IsHeadCommit(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx()) {
|
||||
commits := self.c.Model().Commits
|
||||
selectedIdx := self.c.Contexts().LocalCommits.GetSelectedLineIdx()
|
||||
if models.IsHeadCommit(commits, selectedIdx) {
|
||||
// we've selected the top commit so no rebase is required
|
||||
return self.c.Helpers().GPG.WithGpgHandling(self.c.Git().Commit.RewordLastCommit(summary, description),
|
||||
git_commands.CommitGpgSign,
|
||||
@@ -483,11 +491,11 @@ func (self *LocalCommitsController) handleReword(summary string, description str
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RewordingStatus, func(gocui.Task) error {
|
||||
err := self.c.Git().Rebase.RewordCommit(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), summary, description)
|
||||
err := self.c.Git().Rebase.RewordCommit(commits, selectedIdx, summary, description)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -564,12 +572,16 @@ func (self *LocalCommitsController) drop(selectedCommits []*models.Commit, start
|
||||
Title: self.c.Tr.DropCommitTitle,
|
||||
Prompt: lo.Ternary(isMerge, self.c.Tr.DropMergeCommitPrompt, self.c.Tr.DropCommitPrompt),
|
||||
HandleConfirm: func() error {
|
||||
commits := self.c.Model().Commits
|
||||
if !isMerge {
|
||||
self.selectRebaseResultCommit(startIdx)
|
||||
}
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DroppingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DropCommit)
|
||||
if isMerge {
|
||||
return self.dropMergeCommit(startIdx)
|
||||
return self.dropMergeCommit(commits, startIdx)
|
||||
}
|
||||
return self.interactiveRebase(todo.Drop, startIdx, endIdx)
|
||||
return self.interactiveRebase(commits, todo.Drop, startIdx, endIdx)
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -577,8 +589,8 @@ func (self *LocalCommitsController) drop(selectedCommits []*models.Commit, start
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) dropMergeCommit(commitIdx int) error {
|
||||
err := self.c.Git().Rebase.DropMergeCommit(self.c.Model().Commits, commitIdx)
|
||||
func (self *LocalCommitsController) dropMergeCommit(commits []*models.Commit, commitIdx int) error {
|
||||
err := self.c.Git().Rebase.DropMergeCommit(commits, commitIdx)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
}
|
||||
|
||||
@@ -589,9 +601,11 @@ func (self *LocalCommitsController) edit(selectedCommits []*models.Commit, start
|
||||
|
||||
commits := self.c.Model().Commits
|
||||
if !commits[endIdx].IsMerge() {
|
||||
err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, todo.Edit, "")
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
err, types.RefreshOptions{Mode: types.BLOCK_UI})
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, todo.Edit, "")
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
err, types.RefreshOptions{Mode: types.BLOCK_UI})
|
||||
})
|
||||
}
|
||||
|
||||
return self.startInteractiveRebaseWithEdit(selectedCommits)
|
||||
@@ -614,7 +628,7 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
|
||||
err := self.c.Git().Rebase.EditRebase(commitsToEdit[len(commitsToEdit)-1].Hash())
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
err,
|
||||
types.RefreshOptions{Mode: types.BLOCK_UI, Then: func() {
|
||||
types.RefreshOptions{Mode: types.BLOCK_UI, Then: func() error {
|
||||
todos := make([]*models.Commit, 0, len(commitsToEdit)-1)
|
||||
for _, c := range commitsToEdit[:len(commitsToEdit)-1] {
|
||||
// Merge commits can't be set to "edit", so just skip them
|
||||
@@ -623,11 +637,9 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
|
||||
}
|
||||
}
|
||||
if len(todos) > 0 {
|
||||
err := self.updateTodos(todo.Edit, todos)
|
||||
if err != nil {
|
||||
self.c.Log.Errorf("error when updating todos: %v", err)
|
||||
}
|
||||
return self.updateTodos(todo.Edit, todos)
|
||||
}
|
||||
return nil
|
||||
}})
|
||||
})
|
||||
}
|
||||
@@ -656,22 +668,25 @@ func (self *LocalCommitsController) pick(selectedCommits []*models.Commit) error
|
||||
panic("should be disabled when not rebasing")
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) interactiveRebase(action todo.TodoCommand, startIdx int, endIdx int) error {
|
||||
return self.interactiveRebaseWithFlag(action, startIdx, endIdx, "")
|
||||
func (self *LocalCommitsController) interactiveRebase(commits []*models.Commit, action todo.TodoCommand, startIdx int, endIdx int) error {
|
||||
return self.interactiveRebaseWithFlag(commits, action, startIdx, endIdx, "")
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) interactiveRebaseWithFlag(action todo.TodoCommand, startIdx int, endIdx int, flag string) error {
|
||||
// When performing an action that will remove the selected commits, we need to select the
|
||||
// next commit down (which will end up at the start index after the action is performed)
|
||||
if action == todo.Drop || action == todo.Fixup || action == todo.Squash {
|
||||
self.context().SetSelection(startIdx)
|
||||
}
|
||||
|
||||
err := self.c.Git().Rebase.InteractiveRebase(self.c.Model().Commits, startIdx, endIdx, action, flag)
|
||||
func (self *LocalCommitsController) interactiveRebaseWithFlag(commits []*models.Commit, action todo.TodoCommand, startIdx int, endIdx int, flag string) error {
|
||||
err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, action, flag)
|
||||
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
}
|
||||
|
||||
// selectRebaseResultCommit selects the commit that a drop/fixup/squash starting
|
||||
// at startIdx will leave there. It must run on the UI thread before the rebase:
|
||||
// the commit currently at startIdx is removed, so the refresh's
|
||||
// keep-selection-by-hash can't restore it and falls back to the index, which by
|
||||
// then holds the commit that shifted up into its place.
|
||||
func (self *LocalCommitsController) selectRebaseResultCommit(startIdx int) {
|
||||
self.context().SetSelection(startIdx)
|
||||
}
|
||||
|
||||
// updateTodos sees if the selected commit is in fact a rebasing
|
||||
// commit meaning you are trying to edit the todo file rather than actually
|
||||
// begin a rebase. It then updates the todo file with that action
|
||||
@@ -741,7 +756,7 @@ func (self *LocalCommitsController) moveDown(selectedCommits []*models.Commit, s
|
||||
self.context().MoveSelection(1)
|
||||
self.context().HandleFocus(types.OnFocusOpts{ScrollSelectionIntoView: true})
|
||||
}
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptionsFromUIThread(
|
||||
err, types.RefreshOptions{Mode: types.SYNC, CommitSelection: types.KeepCommitSelectionIndex})
|
||||
})
|
||||
}
|
||||
@@ -769,7 +784,7 @@ func (self *LocalCommitsController) moveUp(selectedCommits []*models.Commit, sta
|
||||
self.context().MoveSelection(-1)
|
||||
self.context().HandleFocus(types.OnFocusOpts{ScrollSelectionIntoView: true})
|
||||
}
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptionsFromUIThread(
|
||||
err, types.RefreshOptions{Mode: types.SYNC, CommitSelection: types.KeepCommitSelectionIndex})
|
||||
})
|
||||
}
|
||||
@@ -788,11 +803,13 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
|
||||
})
|
||||
}
|
||||
} else {
|
||||
commits := self.c.Model().Commits
|
||||
selectedIdx := self.context().GetView().SelectedLineIdx()
|
||||
handleCommit = func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.AmendCommit)
|
||||
err := self.c.Git().Rebase.AmendTo(self.c.Model().Commits, self.context().GetView().SelectedLineIdx())
|
||||
err := self.c.Git().Rebase.AmendTo(commits, selectedIdx)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
})
|
||||
@@ -820,26 +837,30 @@ func (self *LocalCommitsController) canAmend(_ *models.Commit) *types.DisabledRe
|
||||
return self.canAmendRange(self.c.Model().Commits, idx, idx)
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) amendAttribute(commits []*models.Commit, start, end int) error {
|
||||
func (self *LocalCommitsController) amendAttribute(_ []*models.Commit, start, end int) error {
|
||||
// The author operations index into the full commit list by absolute
|
||||
// start/end, so capture that here on the UI thread rather than reading
|
||||
// Model().Commits from the worker the menu items dispatch to.
|
||||
commits := self.c.Model().Commits
|
||||
opts := self.c.KeybindingsOpts()
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: "Amend commit attribute",
|
||||
Items: []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.ResetAuthor,
|
||||
OnPress: func() error { return self.resetAuthor(start, end) },
|
||||
OnPress: func() error { return self.resetAuthor(commits, start, end) },
|
||||
Keys: opts.GetKeys(opts.Config.AmendAttribute.ResetAuthor),
|
||||
Tooltip: self.c.Tr.ResetAuthorTooltip,
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.SetAuthor,
|
||||
OnPress: func() error { return self.setAuthor(start, end) },
|
||||
OnPress: func() error { return self.setAuthor(commits, start, end) },
|
||||
Keys: opts.GetKeys(opts.Config.AmendAttribute.SetAuthor),
|
||||
Tooltip: self.c.Tr.SetAuthorTooltip,
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.AddCoAuthor,
|
||||
OnPress: func() error { return self.addCoAuthor(start, end) },
|
||||
OnPress: func() error { return self.addCoAuthor(commits, start, end) },
|
||||
Keys: opts.GetKeys(opts.Config.AmendAttribute.AddCoAuthor),
|
||||
Tooltip: self.c.Tr.AddCoAuthorTooltip,
|
||||
},
|
||||
@@ -847,30 +868,30 @@ func (self *LocalCommitsController) amendAttribute(commits []*models.Commit, sta
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) resetAuthor(start, end int) error {
|
||||
func (self *LocalCommitsController) resetAuthor(commits []*models.Commit, start, end int) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.ResetCommitAuthor)
|
||||
if err := self.c.Git().Rebase.ResetCommitAuthor(self.c.Model().Commits, start, end); err != nil {
|
||||
if err := self.c.Git().Rebase.ResetCommitAuthor(commits, start, end); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) setAuthor(start, end int) error {
|
||||
func (self *LocalCommitsController) setAuthor(commits []*models.Commit, start, end int) error {
|
||||
self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.SetAuthorPromptTitle,
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(),
|
||||
HandleConfirm: func(value string) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.SetCommitAuthor)
|
||||
if err := self.c.Git().Rebase.SetCommitAuthor(self.c.Model().Commits, start, end, value); err != nil {
|
||||
if err := self.c.Git().Rebase.SetCommitAuthor(commits, start, end, value); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return nil
|
||||
})
|
||||
},
|
||||
@@ -879,17 +900,17 @@ func (self *LocalCommitsController) setAuthor(start, end int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) addCoAuthor(start, end int) error {
|
||||
func (self *LocalCommitsController) addCoAuthor(commits []*models.Commit, start, end int) error {
|
||||
self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.AddCoAuthorPromptTitle,
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetAuthorsSuggestionsFunc(),
|
||||
HandleConfirm: func(value string) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.AddCommitCoAuthor)
|
||||
if err := self.c.Git().Rebase.AddCommitCoAuthor(self.c.Model().Commits, start, end, value); err != nil {
|
||||
if err := self.c.Git().Rebase.AddCommitCoAuthor(commits, start, end, value); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return nil
|
||||
})
|
||||
},
|
||||
@@ -927,7 +948,7 @@ func (self *LocalCommitsController) revert(commits []*models.Commit, start, end
|
||||
}
|
||||
|
||||
result := self.c.Git().Commit.Revert(hashes, isMerge)
|
||||
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(result, types.RefreshOptions{Mode: types.SYNC}); err != nil {
|
||||
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptionsFromUIThread(result, types.RefreshOptions{Mode: types.SYNC}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1127,7 +1148,7 @@ func (self *LocalCommitsController) squashFixupsImpl(commit *models.Commit, reba
|
||||
self.c.LogAction(self.c.Tr.Actions.SquashAllAboveFixupCommits)
|
||||
err := self.c.Git().Rebase.SquashAllAboveFixupCommits(commit)
|
||||
self.context().MoveSelectedLine(-selectionOffset)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptionsFromUIThread(
|
||||
err, types.RefreshOptions{Mode: types.SYNC})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -138,8 +138,8 @@ func (self *PatchBuildingController) toggleSelection() error {
|
||||
self.context().GetMutex().Lock()
|
||||
defer self.context().GetMutex().Unlock()
|
||||
|
||||
filename := self.c.Contexts().CommitFiles.GetSelectedPath()
|
||||
if filename == "" {
|
||||
file := self.c.Contexts().CommitFiles.GetSelectedFile()
|
||||
if file == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -152,7 +152,7 @@ func (self *PatchBuildingController) toggleSelection() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
includedLineIndices, err := self.c.Git().Patch.PatchBuilder.GetFileIncLineIndices(filename)
|
||||
includedLineIndices, err := self.c.Git().Patch.PatchBuilder.GetFileIncLineIndices(file.Path, file.PreviousPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -164,7 +164,7 @@ func (self *PatchBuildingController) toggleSelection() error {
|
||||
}
|
||||
|
||||
// add range of lines to those set for the file
|
||||
if err := toggleFunc(filename, lineIndicesToToggle); err != nil {
|
||||
if err := toggleFunc(file.Path, file.PreviousPath, lineIndicesToToggle); err != nil {
|
||||
// might actually want to return an error here
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
@@ -228,7 +228,7 @@ func (self *PatchBuildingController) discardSelectionFromCommit() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.RemovePatchFromCommit)
|
||||
err := self.c.Git().Patch.DeletePatchesFromCommit(self.c.Model().Commits, commitIndex)
|
||||
self.c.Helpers().PatchBuilding.Escape()
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptionsFromUIThread(
|
||||
err, types.RefreshOptions{Mode: types.SYNC})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package controllers
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
|
||||
@@ -81,9 +80,8 @@ func (self *QuitActions) Escape() error {
|
||||
}
|
||||
}
|
||||
|
||||
repoPathStack := self.c.State().GetRepoPathStack()
|
||||
if !repoPathStack.IsEmpty() {
|
||||
return self.c.Helpers().Repos.DispatchSwitchToRepo(repoPathStack.Pop(), context.NO_CONTEXT)
|
||||
if !self.c.State().GetRepoPathStack().IsEmpty() {
|
||||
return self.c.Helpers().Repos.SwitchToParentRepo()
|
||||
}
|
||||
|
||||
if self.c.UserConfig().QuitOnTopLevelReturn {
|
||||
|
||||
@@ -156,24 +156,28 @@ func (self *RemotesController) addAndCheckoutRemote(remoteName string, remoteUrl
|
||||
return err
|
||||
}
|
||||
|
||||
// Do a sync refresh of the remotes so that we can select
|
||||
// the new one. Loading remotes is not expensive, so we can
|
||||
// afford it.
|
||||
// Refresh the remotes so that we can select the new one. The remotes model
|
||||
// update is bounced onto the UI thread, so the selection (which reads
|
||||
// Model.Remotes) has to run in Then; reading it inline here would see the
|
||||
// previous model. Loading remotes is not expensive, so a sync refresh is
|
||||
// affordable.
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Scope: []types.RefreshableView{types.REMOTES},
|
||||
Mode: types.SYNC,
|
||||
Then: func() error {
|
||||
// Select the remote
|
||||
for idx, remote := range self.c.Model().Remotes {
|
||||
if remote.Name == remoteName {
|
||||
self.c.Contexts().Remotes.SetSelection(idx)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the remote
|
||||
return self.fetchAndCheckout(self.c.Contexts().Remotes.GetSelected(), branchToCheckout)
|
||||
},
|
||||
})
|
||||
|
||||
// Select the remote
|
||||
for idx, remote := range self.c.Model().Remotes {
|
||||
if remote.Name == remoteName {
|
||||
self.c.Contexts().Remotes.SetSelection(idx)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the remote
|
||||
return self.fetchAndCheckout(self.c.Contexts().Remotes.GetSelected(), branchToCheckout)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensures the fork remote exists (matching the given URL).
|
||||
@@ -367,18 +371,27 @@ func (self *RemotesController) fetchAndCheckout(remote *models.Remote, branchNam
|
||||
}
|
||||
refreshOptions := types.RefreshOptions{
|
||||
Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES},
|
||||
Mode: types.ASYNC,
|
||||
Mode: types.SYNC,
|
||||
}
|
||||
if branchName != "" {
|
||||
err = self.c.Git().Branch.New(branchName, remote.Name+"/"+branchName)
|
||||
if err == nil {
|
||||
self.c.Context().Push(self.c.Contexts().Branches, types.OnFocusOpts{})
|
||||
self.c.Helpers().Refs.SelectFirstBranchAndFirstCommit()
|
||||
refreshOptions.KeepBranchSelectionIndex = true
|
||||
refreshOptions.CommitSelection = types.KeepCommitSelectionIndex
|
||||
// Branch.New checks the new branch out, so HEAD moves: refresh the
|
||||
// reflog (and, via scope expansion, the commits) as well, and select
|
||||
// the newly checked-out branch and its head commit.
|
||||
refreshOptions.Scope = append(refreshOptions.Scope, types.REFLOG)
|
||||
refreshOptions.BranchSelection = types.SelectCheckedOutBranch
|
||||
refreshOptions.CommitSelection = types.SelectHeadCommit
|
||||
refreshOptions.SelectTopReflogCommit = true
|
||||
// Focus the branches panel on the UI thread once the refresh has
|
||||
// selected the newly checked-out branch.
|
||||
refreshOptions.Then = func() error {
|
||||
self.c.Context().Push(self.c.Contexts().Branches, types.OnFocusOpts{})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
self.c.Refresh(refreshOptions)
|
||||
self.c.RefreshFromWorker(refreshOptions)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
@@ -49,6 +50,10 @@ func (self *RenameSimilarityThresholdController) Context() types.Context {
|
||||
}
|
||||
|
||||
func (self *RenameSimilarityThresholdController) Increase() error {
|
||||
if err := self.checkCanChangeThreshold(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
old_size := self.c.UserConfig().Git.RenameSimilarityThreshold
|
||||
|
||||
if old_size < 100 {
|
||||
@@ -59,6 +64,10 @@ func (self *RenameSimilarityThresholdController) Increase() error {
|
||||
}
|
||||
|
||||
func (self *RenameSimilarityThresholdController) Decrease() error {
|
||||
if err := self.checkCanChangeThreshold(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
old_size := self.c.UserConfig().Git.RenameSimilarityThreshold
|
||||
|
||||
if old_size > 5 {
|
||||
@@ -73,11 +82,23 @@ func (self *RenameSimilarityThresholdController) applyChange() error {
|
||||
|
||||
currentContext := self.c.Context().CurrentSide()
|
||||
switch currentContext.GetKey() {
|
||||
// we make an exception for our files context, because it actually need to refresh its state afterwards.
|
||||
// we make an exception for the files and commit-files contexts, because
|
||||
// they actually need to refresh their state afterwards: a changed threshold
|
||||
// can turn a rename into a separate delete and add, or vice versa.
|
||||
case context.FILES_CONTEXT_KEY:
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
case context.COMMIT_FILES_CONTEXT_KEY:
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMIT_FILES}})
|
||||
default:
|
||||
currentContext.HandleRenderToMain()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *RenameSimilarityThresholdController) checkCanChangeThreshold() error {
|
||||
if self.c.Git().Patch.PatchBuilder.Active() {
|
||||
return errors.New(self.c.Tr.CantChangeRenameThresholdError)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ func (self *SubmodulesController) add() error {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
||||
return nil
|
||||
})
|
||||
},
|
||||
@@ -193,7 +193,7 @@ func (self *SubmodulesController) editURL(submodule *models.SubmoduleConfig) err
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
||||
return nil
|
||||
})
|
||||
},
|
||||
@@ -210,7 +210,7 @@ func (self *SubmodulesController) init(submodule *models.SubmoduleConfig) error
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
@@ -229,7 +229,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
||||
return nil
|
||||
})
|
||||
},
|
||||
@@ -244,7 +244,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
||||
return nil
|
||||
})
|
||||
},
|
||||
@@ -259,7 +259,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
||||
return nil
|
||||
})
|
||||
},
|
||||
@@ -274,7 +274,7 @@ func (self *SubmodulesController) openBulkActionsMenu() error {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
||||
return nil
|
||||
})
|
||||
},
|
||||
@@ -292,7 +292,7 @@ func (self *SubmodulesController) update(submodule *models.SubmoduleConfig) erro
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Scope: []types.RefreshableView{types.SUBMODULES}})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -90,18 +90,19 @@ func (self *SwitchToDiffFilesController) enter() error {
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Scope: []types.RefreshableView{types.COMMIT_FILES},
|
||||
Then: func() error {
|
||||
if filterPath := self.c.Modes().Filtering.GetPath(); filterPath != "" {
|
||||
path, err := filepath.Rel(self.c.Git().RepoPaths.RepoPath(), filterPath)
|
||||
if err != nil {
|
||||
path = filterPath
|
||||
}
|
||||
commitFilesContext.CommitFileTreeViewModel.SelectPath(
|
||||
filepath.ToSlash(path), self.c.UserConfig().Gui.ShowRootItemInFileTree)
|
||||
}
|
||||
self.c.Context().Push(commitFilesContext, types.OnFocusOpts{})
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
if filterPath := self.c.Modes().Filtering.GetPath(); filterPath != "" {
|
||||
path, err := filepath.Rel(self.c.Git().RepoPaths.RepoPath(), filterPath)
|
||||
if err != nil {
|
||||
path = filterPath
|
||||
}
|
||||
commitFilesContext.CommitFileTreeViewModel.SelectPath(
|
||||
filepath.ToSlash(path), self.c.UserConfig().Gui.ShowRootItemInFileTree)
|
||||
}
|
||||
|
||||
self.c.Context().Push(commitFilesContext, types.OnFocusOpts{})
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -229,7 +229,7 @@ func (self *SyncController) pushAux(currentBranch *models.Branch, opts pushOpts)
|
||||
}
|
||||
return err
|
||||
}
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.SYNC})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ func (self *TagsController) localDelete(tag *models.Tag) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteLocalTag)
|
||||
err := self.c.Git().Tag.LocalDelete(tag.Name)
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
|
||||
return err
|
||||
})
|
||||
}
|
||||
@@ -210,7 +210,7 @@ func (self *TagsController) remoteDelete(tag *models.Tag) error {
|
||||
return err
|
||||
}
|
||||
self.c.Toast(self.c.Tr.RemoteTagDeletedMessage)
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
|
||||
return nil
|
||||
})
|
||||
},
|
||||
@@ -264,7 +264,7 @@ func (self *TagsController) localAndRemoteDelete(tag *models.Tag) error {
|
||||
if err := self.c.Git().Tag.LocalDelete(tag.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS}})
|
||||
return nil
|
||||
})
|
||||
},
|
||||
@@ -332,15 +332,7 @@ func (self *TagsController) push(tag *models.Tag) error {
|
||||
HandleConfirm: func(response string) error {
|
||||
return self.c.WithInlineStatus(tag, types.ItemOperationPushing, context.TAGS_CONTEXT_KEY, func(task gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.PushTag)
|
||||
err := self.c.Git().Tag.Push(task, response, tag.Name)
|
||||
|
||||
// Render again to remove the inline status:
|
||||
self.c.OnUIThread(func() error {
|
||||
self.c.Contexts().Tags.HandleRender()
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
return self.c.Git().Tag.Push(task, response, tag.Name)
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -271,7 +271,7 @@ func (self *UndoController) hardResetWithAutoStash(commitHash string, options ha
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Refresh(types.RefreshOptions{})
|
||||
self.c.RefreshFromWorker(types.RefreshOptions{})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -66,12 +66,8 @@ func (self *VerticalScrollController) HandleScrollUp() error {
|
||||
}
|
||||
|
||||
func (self *VerticalScrollController) HandleScrollDown() error {
|
||||
scrollHeight := self.c.UserConfig().Gui.ScrollHeight
|
||||
self.context.GetViewTrait().ScrollDown(scrollHeight)
|
||||
|
||||
if manager := self.c.GetViewBufferManagerForView(self.context.GetView()); manager != nil {
|
||||
manager.ReadLines(scrollHeight)
|
||||
}
|
||||
self.context.GetViewTrait().ScrollDown(self.c.UserConfig().Gui.ScrollHeight)
|
||||
self.c.ReadLinesToFillView(self.context.GetView())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -50,17 +50,12 @@ func (self *ViewSelectionController) GetMouseKeybindings(opts types.KeybindingsO
|
||||
}
|
||||
|
||||
func (self *ViewSelectionController) handleLineChange(delta int) {
|
||||
if delta > 0 {
|
||||
if manager := self.c.GetViewBufferManagerForView(self.context.GetView()); manager != nil {
|
||||
manager.ReadLines(delta)
|
||||
}
|
||||
}
|
||||
|
||||
v := self.Context().GetView()
|
||||
if delta < 0 {
|
||||
v.ScrollUp(-delta)
|
||||
} else {
|
||||
v.ScrollDown(delta)
|
||||
self.c.ReadLinesToFillView(v)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package filetree
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
@@ -26,7 +25,6 @@ type ICommitFileTreeViewModel interface {
|
||||
}
|
||||
|
||||
type CommitFileTreeViewModel struct {
|
||||
sync.RWMutex
|
||||
types.IListCursor
|
||||
ICommitFileTree
|
||||
|
||||
@@ -144,6 +142,22 @@ func (self *CommitFileTreeViewModel) GetSelectedPath() string {
|
||||
return node.GetPath()
|
||||
}
|
||||
|
||||
// SetTree rebuilds the tree and clamps the selection so it stays in range. The
|
||||
// embedded tree's SetTree only rebuilds the node list and doesn't touch the
|
||||
// cursor, so after a shrinking rebuild (e.g. moving a patch out into the index)
|
||||
// the selection index could be left past the end of the tree; GetSelectedItems
|
||||
// would then return a nil node and crash callers such as canEditFiles when the
|
||||
// options map is rendered during layout.
|
||||
//
|
||||
// Unlike FileTreeViewModel.SetTree we don't re-find the selected node by path
|
||||
// afterwards: that walk lands on the containing directory when a file is removed
|
||||
// from a dir that then collapses, whereas keeping the (clamped) index lands on
|
||||
// the sibling file, which is what we want here.
|
||||
func (self *CommitFileTreeViewModel) SetTree() {
|
||||
self.ICommitFileTree.SetTree()
|
||||
self.ClampSelection()
|
||||
}
|
||||
|
||||
// duplicated from file_tree_view_model.go. Generics will help here
|
||||
func (self *CommitFileTreeViewModel) ToggleShowTree() {
|
||||
selectedNode := self.GetSelected()
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package filetree
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// When the tree shrinks under the selection - e.g. moving a patch out into the
|
||||
// index removes a file - SetTree must keep the selection in range. Otherwise
|
||||
// GetSelectedItems returns a nil node, which crashes callers such as
|
||||
// canEditFiles when the options map is rendered during layout.
|
||||
func TestCommitFileTreeViewModelSetTreeClampsSelectionOnShrink(t *testing.T) {
|
||||
files := []*models.CommitFile{
|
||||
{Path: "file1"},
|
||||
{Path: "file2"},
|
||||
{Path: "file3"},
|
||||
}
|
||||
viewModel := NewCommitFileTreeViewModel(
|
||||
func() []*models.CommitFile { return files },
|
||||
common.NewDummyCommon(),
|
||||
false, // flat list
|
||||
)
|
||||
viewModel.SetTree()
|
||||
viewModel.SetSelectedLineIdx(viewModel.Len() - 1)
|
||||
|
||||
// The file under the cursor goes away and the tree shrinks.
|
||||
files = []*models.CommitFile{{Path: "file1"}}
|
||||
viewModel.SetTree()
|
||||
|
||||
assert.Less(t, viewModel.GetSelectedLineIdx(), viewModel.Len())
|
||||
assert.NotNil(t, viewModel.GetSelected())
|
||||
items, _, _ := viewModel.GetSelectedItems()
|
||||
assert.NotEmpty(t, items)
|
||||
for _, item := range items {
|
||||
assert.NotNil(t, item)
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ package filetree
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
@@ -22,7 +21,6 @@ type IFileTreeViewModel interface {
|
||||
// which item is selected. It also contains logic for repositioning that cursor
|
||||
// after the files are refreshed
|
||||
type FileTreeViewModel struct {
|
||||
sync.RWMutex
|
||||
types.IListCursor
|
||||
IFileTree
|
||||
searchHistory *utils.HistoryBuffer[string]
|
||||
|
||||
@@ -17,12 +17,8 @@ func (gui *Gui) scrollUpView(view *gocui.View) {
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollDownView(view *gocui.View) {
|
||||
scrollHeight := gui.c.UserConfig().Gui.ScrollHeight
|
||||
view.ScrollDown(scrollHeight)
|
||||
|
||||
if manager := gui.getViewBufferManagerForView(view); manager != nil {
|
||||
manager.ReadLines(scrollHeight)
|
||||
}
|
||||
view.ScrollDown(gui.c.UserConfig().Gui.ScrollHeight)
|
||||
gui.readLinesToFillView(view)
|
||||
}
|
||||
|
||||
func (gui *Gui) scrollUpMain() error {
|
||||
|
||||
+61
-16
@@ -12,6 +12,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/lazycore/pkg/boxlayout"
|
||||
@@ -111,7 +112,10 @@ type Gui struct {
|
||||
|
||||
PopupHandler types.IPopupHandler
|
||||
|
||||
IsRefreshingFiles bool
|
||||
// Bumped every time we switch to a different repository (in resetState).
|
||||
// Used to drop refresh results that were computed for a repo we've since
|
||||
// navigated away from. See RefreshHelper.onUIThreadUnlessRepoChanged.
|
||||
repoGeneration atomic.Int32
|
||||
|
||||
// we use this to decide whether we'll return to the original directory that
|
||||
// lazygit was opened in, or if we'll retain the one we're currently in.
|
||||
@@ -171,18 +175,14 @@ func (self *StateAccessor) GetRepoState() types.IRepoStateAccessor {
|
||||
return self.gui.State
|
||||
}
|
||||
|
||||
func (self *StateAccessor) GetRepoGeneration() int {
|
||||
return int(self.gui.repoGeneration.Load())
|
||||
}
|
||||
|
||||
func (self *StateAccessor) GetPagerConfig() *config.PagerConfig {
|
||||
return self.gui.pagerConfig
|
||||
}
|
||||
|
||||
func (self *StateAccessor) GetIsRefreshingFiles() bool {
|
||||
return self.gui.IsRefreshingFiles
|
||||
}
|
||||
|
||||
func (self *StateAccessor) SetIsRefreshingFiles(value bool) {
|
||||
self.gui.IsRefreshingFiles = value
|
||||
}
|
||||
|
||||
func (self *StateAccessor) GetShowExtrasWindow() bool {
|
||||
return self.gui.ShowExtrasWindow
|
||||
}
|
||||
@@ -234,8 +234,11 @@ type GuiRepoState struct {
|
||||
|
||||
SplitMainPanel bool
|
||||
|
||||
SearchState *types.SearchState
|
||||
StartupStage types.StartupStage // Allows us to not load everything at once
|
||||
SearchState *types.SearchState
|
||||
// Lets us not load everything at once. Written and read from refresh
|
||||
// workers (the reflog/branches load transitions it INITIAL->COMPLETE), so
|
||||
// it's atomic. Holds a types.StartupStage.
|
||||
startupStage atomic.Int32
|
||||
|
||||
ContextMgr *ContextMgr
|
||||
Contexts *context.ContextTree
|
||||
@@ -262,7 +265,11 @@ type GuiRepoState struct {
|
||||
// continue such an operation once its conflicts are resolved if we started
|
||||
// it ourselves; for an externally started one, popping up unbidden would be
|
||||
// confusing. Reset whenever we observe that no operation is in progress.
|
||||
mergeOrRebaseStartedInLazygit bool
|
||||
//
|
||||
// Written from both the files refresh worker and the merge/rebase result
|
||||
// path (which runs on a worker for the async callers), and read from the
|
||||
// files refresh worker, so it's atomic.
|
||||
mergeOrRebaseStartedInLazygit atomic.Bool
|
||||
}
|
||||
|
||||
var _ types.IRepoStateAccessor = new(GuiRepoState)
|
||||
@@ -276,11 +283,11 @@ func (self *GuiRepoState) GetWindowViewNameMap() *utils.ThreadSafeMap[string, st
|
||||
}
|
||||
|
||||
func (self *GuiRepoState) GetStartupStage() types.StartupStage {
|
||||
return self.StartupStage
|
||||
return types.StartupStage(self.startupStage.Load())
|
||||
}
|
||||
|
||||
func (self *GuiRepoState) SetStartupStage(value types.StartupStage) {
|
||||
self.StartupStage = value
|
||||
self.startupStage.Store(int32(value))
|
||||
}
|
||||
|
||||
func (self *GuiRepoState) GetCurrentPopupOpts() *types.CreatePopupPanelOpts {
|
||||
@@ -292,11 +299,11 @@ func (self *GuiRepoState) SetCurrentPopupOpts(value *types.CreatePopupPanelOpts)
|
||||
}
|
||||
|
||||
func (self *GuiRepoState) GetMergeOrRebaseStartedInLazygit() bool {
|
||||
return self.mergeOrRebaseStartedInLazygit
|
||||
return self.mergeOrRebaseStartedInLazygit.Load()
|
||||
}
|
||||
|
||||
func (self *GuiRepoState) SetMergeOrRebaseStartedInLazygit(value bool) {
|
||||
self.mergeOrRebaseStartedInLazygit = value
|
||||
self.mergeOrRebaseStartedInLazygit.Store(value)
|
||||
}
|
||||
|
||||
func (self *GuiRepoState) GetScreenMode() types.ScreenMode {
|
||||
@@ -585,6 +592,11 @@ func (gui *Gui) checkForChangedConfigsThatDontAutoReload(oldConfig *config.UserC
|
||||
// resetState reuses the repo state from our repo state map, if the repo was
|
||||
// open before; otherwise it creates a new one.
|
||||
func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context {
|
||||
// Bump the repo generation so that any refresh still in flight for the
|
||||
// previous repo drops its model update instead of applying it here (see
|
||||
// RefreshHelper.onUIThreadUnlessRepoChanged).
|
||||
gui.repoGeneration.Add(1)
|
||||
|
||||
// Un-highlight the current view if there is one. The reason we do this is
|
||||
// that the repo we are switching to might have a different view focused,
|
||||
// and would then show an inactive highlight for the previous view.
|
||||
@@ -679,6 +691,23 @@ func (gui *Gui) getViewBufferManagerForView(view *gocui.View) *tasks.ViewBufferM
|
||||
return manager
|
||||
}
|
||||
|
||||
// When scrolling a lazy-loaded view, we read enough lines to fill the viewport
|
||||
// plus this many extra screenfuls, so that further scrolling has some runway
|
||||
// and doesn't have to block on reading (and re-rendering) more lines on every
|
||||
// wheel notch.
|
||||
const scrollReadAheadScreenfuls = 3
|
||||
|
||||
// readLinesToFillView reads enough lines into the view's buffer to cover
|
||||
// everything currently scrolled into view, plus a few screenfuls of read-ahead.
|
||||
// Reading is idempotent (see ViewBufferManager.ReadLines), so if the buffer
|
||||
// already extends far enough this does nothing.
|
||||
func (gui *Gui) readLinesToFillView(view *gocui.View) {
|
||||
if manager := gui.getViewBufferManagerForView(view); manager != nil {
|
||||
viewportBottom := view.OriginY() + view.InnerHeight()
|
||||
manager.ReadLines(viewportBottom + scrollReadAheadScreenfuls*view.InnerHeight())
|
||||
}
|
||||
}
|
||||
|
||||
func (gui *Gui) initialWindowViewNameMap(contextTree *context.ContextTree) *utils.ThreadSafeMap[string, string] {
|
||||
result := utils.NewThreadSafeMap[string, string]()
|
||||
|
||||
@@ -1189,16 +1218,32 @@ func (gui *Gui) onUIThread(f func() error) {
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) onUIThreadBackground(f func() error) {
|
||||
gui.g.UpdateBackground(func(*gocui.Gui) error {
|
||||
return f()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) onUIThreadContentOnly(f func() error) {
|
||||
gui.g.UpdateContentOnly(func(*gocui.Gui) error {
|
||||
return f()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) onUIThreadContentOnlyBackground(f func() error) {
|
||||
gui.g.UpdateContentOnlyBackground(func(*gocui.Gui) error {
|
||||
return f()
|
||||
})
|
||||
}
|
||||
|
||||
func (gui *Gui) onWorker(f func(gocui.Task) error) {
|
||||
gui.g.OnWorker(f)
|
||||
}
|
||||
|
||||
func (gui *Gui) onWorkerBackground(f func(gocui.Task) error) {
|
||||
gui.g.OnWorkerBackground(f)
|
||||
}
|
||||
|
||||
func (gui *Gui) getWindowDimensions(informationStr string, appStatus string) map[string]boxlayout.Dimensions {
|
||||
return gui.helpers.WindowArrangement.GetWindowDimensions(informationStr, appStatus)
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@ func (self *guiCommon) Refresh(opts types.RefreshOptions) {
|
||||
self.gui.helpers.Refresh.Refresh(opts)
|
||||
}
|
||||
|
||||
func (self *guiCommon) RefreshFromWorker(opts types.RefreshOptions) {
|
||||
self.gui.helpers.Refresh.RefreshFromWorker(opts)
|
||||
}
|
||||
|
||||
func (self *guiCommon) PostRefreshUpdate(context types.Context) {
|
||||
self.gui.postRefreshUpdate(context)
|
||||
}
|
||||
@@ -124,14 +128,26 @@ func (self *guiCommon) OnUIThread(f func() error) {
|
||||
self.gui.onUIThread(f)
|
||||
}
|
||||
|
||||
func (self *guiCommon) OnUIThreadBackground(f func() error) {
|
||||
self.gui.onUIThreadBackground(f)
|
||||
}
|
||||
|
||||
func (self *guiCommon) OnUIThreadContentOnly(f func() error) {
|
||||
self.gui.onUIThreadContentOnly(f)
|
||||
}
|
||||
|
||||
func (self *guiCommon) OnUIThreadContentOnlyBackground(f func() error) {
|
||||
self.gui.onUIThreadContentOnlyBackground(f)
|
||||
}
|
||||
|
||||
func (self *guiCommon) OnWorker(f func(gocui.Task) error) {
|
||||
self.gui.onWorker(f)
|
||||
}
|
||||
|
||||
func (self *guiCommon) OnWorkerBackground(f func(gocui.Task) error) {
|
||||
self.gui.onWorkerBackground(f)
|
||||
}
|
||||
|
||||
func (self *guiCommon) RenderToMainViews(opts types.RefreshMainOpts) {
|
||||
self.gui.refreshMainViews(opts)
|
||||
}
|
||||
@@ -149,6 +165,10 @@ func (self *guiCommon) GetViewBufferManagerForView(view *gocui.View) *tasks.View
|
||||
return self.gui.getViewBufferManagerForView(view)
|
||||
}
|
||||
|
||||
func (self *guiCommon) ReadLinesToFillView(view *gocui.View) {
|
||||
self.gui.readLinesToFillView(view)
|
||||
}
|
||||
|
||||
func (self *guiCommon) State() types.IStateAccessor {
|
||||
return self.gui.stateAccessor
|
||||
}
|
||||
|
||||
+9
-4
@@ -37,13 +37,18 @@ func (gui *Gui) layout(g *gocui.Gui) error {
|
||||
if prevMainView != nil {
|
||||
prevMainHeight := prevMainView.Height()
|
||||
newMainHeight := viewDimensions["main"].Y1 - viewDimensions["main"].Y0 + 1
|
||||
heightDiff := newMainHeight - prevMainHeight
|
||||
if heightDiff > 0 {
|
||||
if newMainHeight > prevMainHeight {
|
||||
// The main views have grown taller, so make sure enough lines are
|
||||
// loaded to fill them. The views haven't been resized yet at this
|
||||
// point, so we can't rely on their current height; compute the target
|
||||
// total from the new height instead. (Reading past the actual content
|
||||
// is harmless: ReadLines stops at the end of input.)
|
||||
linesToRead := prevMainView.OriginY() + newMainHeight
|
||||
if manager := gui.getViewBufferManagerForView(gui.Views.Main); manager != nil {
|
||||
manager.ReadLines(heightDiff)
|
||||
manager.ReadLines(linesToRead)
|
||||
}
|
||||
if manager := gui.getViewBufferManagerForView(gui.Views.Secondary); manager != nil {
|
||||
manager.ReadLines(heightDiff)
|
||||
manager.ReadLines(linesToRead)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -253,28 +253,12 @@ func divergenceStr(
|
||||
result := ""
|
||||
if ItemOperationToString(itemOperation, tr) == "" && userConfig.Gui.ShowDivergenceFromBaseBranch != "none" {
|
||||
behind := branch.BehindBaseBranch.Load()
|
||||
showNumber := userConfig.Gui.ShowDivergenceFromBaseBranch == "arrowAndNumber"
|
||||
switch {
|
||||
case behind > 0:
|
||||
if showNumber {
|
||||
if behind != 0 {
|
||||
if userConfig.Gui.ShowDivergenceFromBaseBranch == "arrowAndNumber" {
|
||||
result += fmt.Sprintf("↓%d", behind)
|
||||
} else {
|
||||
result += "↓"
|
||||
}
|
||||
case behind == models.BehindBaseAmbiguousMaybeUpToDate:
|
||||
// We don't know whether the branch is up to date or behind,
|
||||
// because the candidate bases disagree. "?" intentionally has
|
||||
// no arrow — we'd be implying "behind" if it did.
|
||||
result += "?"
|
||||
case behind == models.BehindBaseAmbiguousDefinitelyBehind:
|
||||
// Every candidate has the branch behind, but by different
|
||||
// amounts — show that it's behind without committing to a
|
||||
// specific count.
|
||||
if showNumber {
|
||||
result += "↓?"
|
||||
} else {
|
||||
result += "↓"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -284,66 +284,6 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "", "branc… Pushing |"},
|
||||
},
|
||||
// Ambiguous base, candidates disagree on whether branch is up to date
|
||||
// → render "?" without an arrow (we don't know if it's behind).
|
||||
{
|
||||
branch: &models.Branch{
|
||||
Name: "branch_name",
|
||||
Recency: "1m",
|
||||
BehindBaseBranch: *makeAtomic(models.BehindBaseAmbiguousMaybeUpToDate),
|
||||
},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
viewWidth: 20,
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "arrowAndNumber",
|
||||
expected: []string{"1m", "", "branch_name ?"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{
|
||||
Name: "branch_name",
|
||||
Recency: "1m",
|
||||
BehindBaseBranch: *makeAtomic(models.BehindBaseAmbiguousMaybeUpToDate),
|
||||
},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
viewWidth: 20,
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "onlyArrow",
|
||||
expected: []string{"1m", "", "branch_name ?"},
|
||||
},
|
||||
// Ambiguous base, every candidate has branch behind by some non-zero
|
||||
// amount → render "↓?" (arrowAndNumber) or "↓" (onlyArrow).
|
||||
{
|
||||
branch: &models.Branch{
|
||||
Name: "branch_name",
|
||||
Recency: "1m",
|
||||
BehindBaseBranch: *makeAtomic(models.BehindBaseAmbiguousDefinitelyBehind),
|
||||
},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
viewWidth: 20,
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "arrowAndNumber",
|
||||
expected: []string{"1m", "", "branch_name ↓?"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{
|
||||
Name: "branch_name",
|
||||
Recency: "1m",
|
||||
BehindBaseBranch: *makeAtomic(models.BehindBaseAmbiguousDefinitelyBehind),
|
||||
},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
viewWidth: 20,
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "onlyArrow",
|
||||
expected: []string{"1m", "", "branch_name ↓"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "abc", Recency: "1m"},
|
||||
itemOperation: types.ItemOperationPushing,
|
||||
|
||||
@@ -328,7 +328,8 @@ func fileNameAtDepth(node *filetree.Node[models.File], depth int, showRootItem b
|
||||
|
||||
func commitFileNameAtDepth(node *filetree.Node[models.CommitFile], depth int) string {
|
||||
splitName := split(node.GetInternalPath())
|
||||
if depth == 0 && splitName[0] == "." {
|
||||
showRootItem := splitName[0] == "."
|
||||
if depth == 0 && showRootItem {
|
||||
if len(splitName) == 1 {
|
||||
return "/"
|
||||
}
|
||||
@@ -336,6 +337,20 @@ func commitFileNameAtDepth(node *filetree.Node[models.CommitFile], depth int) st
|
||||
}
|
||||
name := join(splitName[depth:])
|
||||
|
||||
if node.File != nil && node.File.IsRename() {
|
||||
splitPrevName := filetree.SplitFileTreePath(node.File.PreviousPath, showRootItem)
|
||||
|
||||
prevName := node.File.PreviousPath
|
||||
// if the file has just been renamed inside the same directory, we can shave off
|
||||
// the prefix for the previous path too. Otherwise we'll keep it unchanged
|
||||
sameParentDir := len(splitName) == len(splitPrevName) && join(splitName[0:depth]) == join(splitPrevName[0:depth])
|
||||
if sameParentDir {
|
||||
prevName = join(splitPrevName[depth:])
|
||||
}
|
||||
|
||||
return prevName + " → " + name
|
||||
}
|
||||
|
||||
return name
|
||||
}
|
||||
|
||||
|
||||
@@ -151,6 +151,14 @@ func TestRenderCommitFileTree(t *testing.T) {
|
||||
showRootItem: true,
|
||||
expected: []string{"A test"},
|
||||
},
|
||||
{
|
||||
name: "renamed file",
|
||||
files: []*models.CommitFile{
|
||||
{Path: "new.txt", PreviousPath: "old.txt", ChangeStatus: "R"},
|
||||
},
|
||||
showRootItem: false,
|
||||
expected: []string{"R old.txt → new.txt"},
|
||||
},
|
||||
{
|
||||
name: "big example",
|
||||
files: []*models.CommitFile{
|
||||
@@ -219,7 +227,7 @@ M file1
|
||||
}
|
||||
patchBuilder := patch.NewPatchBuilder(
|
||||
utils.NewDummyLog(),
|
||||
func(from string, to string, reverse bool, filename string, plain bool) (string, error) {
|
||||
func(from string, to string, reverse bool, filename string, previousPath string, plain bool) (string, error) {
|
||||
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